refactor: simplify useRegisterCommand types (#1678)
This commit is contained in:
parent
2158b1cc25
commit
840c49e8fe
|
|
@ -13,8 +13,7 @@ import { tipcClient } from "~/lib/client"
|
|||
import { useTipModal } from "~/modules/wallet/hooks"
|
||||
import { entryActions, useEntryStore } from "~/store/entry"
|
||||
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
|
||||
import { defineFollowCommand } from "../registry/command"
|
||||
import { useRegisterFollowCommand } from "../hooks/use-register-command"
|
||||
import { COMMAND_ID } from "./id"
|
||||
|
||||
const useCollect = () => {
|
||||
|
|
@ -89,8 +88,8 @@ export const useRegisterEntryCommands = () => {
|
|||
const read = useRead()
|
||||
const unread = useUnread()
|
||||
|
||||
useRegisterCommandEffect([
|
||||
defineFollowCommand({
|
||||
useRegisterFollowCommand([
|
||||
{
|
||||
id: COMMAND_ID.entry.tip,
|
||||
label: t("entry_actions.tip"),
|
||||
icon: <i className="i-mgc-power-outline" />,
|
||||
|
|
@ -105,8 +104,8 @@ export const useRegisterEntryCommands = () => {
|
|||
}),
|
||||
)
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.star,
|
||||
label: t("entry_actions.star"),
|
||||
icon: <i className="i-mgc-star-cute-re" />,
|
||||
|
|
@ -127,8 +126,8 @@ export const useRegisterEntryCommands = () => {
|
|||
// }
|
||||
collect.mutate({ entryId, view })
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.unstar,
|
||||
label: t("entry_actions.unstar"),
|
||||
icon: <i className="i-mgc-star-cute-fi text-orange-500" />,
|
||||
|
|
@ -140,8 +139,8 @@ export const useRegisterEntryCommands = () => {
|
|||
}
|
||||
uncollect.mutate(entry.entries.id)
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.delete,
|
||||
label: t("entry_actions.delete"),
|
||||
icon: <i className="i-mgc-delete-2-cute-re" />,
|
||||
|
|
@ -153,8 +152,8 @@ export const useRegisterEntryCommands = () => {
|
|||
}
|
||||
deleteInboxEntry.mutate(entry.entries.id)
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.copyLink,
|
||||
label: t("entry_actions.copy_link"),
|
||||
icon: <i className="i-mgc-link-cute-re" />,
|
||||
|
|
@ -170,8 +169,8 @@ export const useRegisterEntryCommands = () => {
|
|||
duration: 1000,
|
||||
})
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.copyTitle,
|
||||
label: t("entry_actions.copy_title"),
|
||||
icon: <i className="i-mgc-copy-cute-re" />,
|
||||
|
|
@ -187,8 +186,8 @@ export const useRegisterEntryCommands = () => {
|
|||
duration: 1000,
|
||||
})
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.openInBrowser,
|
||||
label: t("entry_actions.open_in_browser", {
|
||||
which: t(IN_ELECTRON ? "words.browser" : "words.newTab"),
|
||||
|
|
@ -202,8 +201,8 @@ export const useRegisterEntryCommands = () => {
|
|||
}
|
||||
window.open(entry.entries.url, "_blank")
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.viewSourceContent,
|
||||
label: t("entry_actions.view_source_content"),
|
||||
icon: <i className="i-mgc-world-2-cute-re" />,
|
||||
|
|
@ -232,16 +231,16 @@ export const useRegisterEntryCommands = () => {
|
|||
}
|
||||
setShowSourceContent(true)
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.viewEntryContent,
|
||||
label: t("entry_actions.view_source_content"),
|
||||
icon: <i className="i-mgc-world-2-cute-fi" />,
|
||||
run: () => {
|
||||
setShowSourceContent(false)
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.share,
|
||||
label: t("entry_actions.share"),
|
||||
icon:
|
||||
|
|
@ -267,8 +266,8 @@ export const useRegisterEntryCommands = () => {
|
|||
}
|
||||
return
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.read,
|
||||
label: t("entry_actions.mark_as_read"),
|
||||
icon: <i className="i-mgc-round-cute-fi" />,
|
||||
|
|
@ -280,8 +279,8 @@ export const useRegisterEntryCommands = () => {
|
|||
}
|
||||
read.mutate({ entryId, feedId: entry.feedId })
|
||||
},
|
||||
}),
|
||||
defineFollowCommand({
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.unread,
|
||||
label: t("entry_actions.mark_as_unread"),
|
||||
icon: <i className="i-mgc-round-cute-re" />,
|
||||
|
|
@ -293,6 +292,6 @@ export const useRegisterEntryCommands = () => {
|
|||
}
|
||||
unread.mutate({ entryId, feedId: entry.feedId })
|
||||
},
|
||||
}),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { parseHtml } from "~/lib/parse-html"
|
|||
import type { FlatEntryModel } from "~/store/entry"
|
||||
import { useEntryStore } from "~/store/entry"
|
||||
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command"
|
||||
import { defineFollowCommand } from "../registry/command"
|
||||
import { COMMAND_ID } from "./id"
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { getRouteParams } from "~/hooks/biz/useRouteParams"
|
|||
import { useDeleteSubscription } from "~/hooks/biz/useSubscriptionActions"
|
||||
import { ListForm } from "~/modules/discover/list-form"
|
||||
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command"
|
||||
import { COMMAND_ID } from "./id"
|
||||
|
||||
export const useRegisterListCommands = () => {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"
|
|||
|
||||
import { useSetTheme } from "~/hooks/common"
|
||||
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command-effect"
|
||||
import { useRegisterCommandEffect } from "../hooks/use-register-command"
|
||||
|
||||
export const useRegisterThemeCommands = () => {
|
||||
const [t] = useTranslation("settings")
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
import { useEffect } from "react"
|
||||
|
||||
import { registerCommand } from "../registry/registry"
|
||||
import type { CommandOptions } from "../types"
|
||||
|
||||
export type RegisterOptions = {
|
||||
deps?: unknown[]
|
||||
enabled?: boolean
|
||||
// forceMountSection?: boolean
|
||||
// sectionMeta?: Record<string, unknown>
|
||||
// orderSection?: OrderSectionInstruction
|
||||
// orderCommands?: OrderCommandsInstruction
|
||||
}
|
||||
|
||||
export const useRegisterCommandEffect = (
|
||||
options: CommandOptions | CommandOptions[],
|
||||
registerOptions?: RegisterOptions,
|
||||
) => {
|
||||
// TODO memo command via useMemo
|
||||
// See https://github.com/supabase/supabase/blob/master/packages/ui-patterns/CommandMenu/api/hooks/commandsHooks.ts
|
||||
|
||||
useEffect(() => {
|
||||
if (!Array.isArray(options)) {
|
||||
return registerCommand(options)
|
||||
}
|
||||
|
||||
const unsubscribes = options.map((option) => registerCommand(option))
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe())
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- we want to run this effect only once
|
||||
}, registerOptions?.deps ?? [])
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { assertType, expectTypeOf, test } from "vitest"
|
||||
|
||||
import { COMMAND_ID } from "../commands/id"
|
||||
import { useRegisterFollowCommand } from "./use-register-command"
|
||||
|
||||
test("useRegisterFollowCommand types", () => {
|
||||
assertType(
|
||||
useRegisterFollowCommand({
|
||||
id: COMMAND_ID.entry.openInBrowser,
|
||||
label: "",
|
||||
run: ({ entryId }) => {
|
||||
expectTypeOf(entryId).toEqualTypeOf<string>()
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
assertType(
|
||||
useRegisterFollowCommand({
|
||||
id: "unknown id",
|
||||
label: "",
|
||||
run: (...args) => {
|
||||
expectTypeOf(args).toEqualTypeOf<[]>()
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
assertType(
|
||||
useRegisterFollowCommand([
|
||||
{
|
||||
id: COMMAND_ID.entry.star,
|
||||
label: "",
|
||||
run: ({ entryId }) => {
|
||||
expectTypeOf(entryId).toEqualTypeOf<string>()
|
||||
},
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.viewEntryContent,
|
||||
label: "",
|
||||
run: (...args) => {
|
||||
expectTypeOf(args).toEqualTypeOf<[]>()
|
||||
},
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
assertType(
|
||||
useRegisterFollowCommand([
|
||||
{
|
||||
id: "unknown id",
|
||||
label: "",
|
||||
run: (...args) => {
|
||||
expectTypeOf(args).toEqualTypeOf<[]>()
|
||||
},
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
assertType(
|
||||
useRegisterFollowCommand([
|
||||
{
|
||||
id: "unknown id",
|
||||
label: "",
|
||||
run: (...args) => {
|
||||
expectTypeOf(args).toEqualTypeOf<[]>()
|
||||
},
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.star,
|
||||
label: "",
|
||||
run: ({ entryId }) => {
|
||||
expectTypeOf(entryId).toEqualTypeOf<string>()
|
||||
},
|
||||
},
|
||||
{
|
||||
id: COMMAND_ID.entry.viewEntryContent,
|
||||
label: "",
|
||||
run: (...args) => {
|
||||
expectTypeOf(args).toEqualTypeOf<[]>()
|
||||
},
|
||||
},
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import { useEffect } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { registerCommand } from "../registry/registry"
|
||||
import type { CommandOptions, FollowCommandId, FollowCommandMap } from "../types"
|
||||
|
||||
export type RegisterOptions = {
|
||||
deps?: unknown[]
|
||||
enabled?: boolean
|
||||
// forceMountSection?: boolean
|
||||
// sectionMeta?: Record<string, unknown>
|
||||
// orderSection?: OrderSectionInstruction
|
||||
// orderCommands?: OrderCommandsInstruction
|
||||
}
|
||||
|
||||
export const useRegisterCommandEffect = (
|
||||
options: CommandOptions | CommandOptions[],
|
||||
registerOptions?: RegisterOptions,
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
useEffect(() => {
|
||||
if (!Array.isArray(options)) {
|
||||
return registerCommand(options)
|
||||
}
|
||||
|
||||
const unsubscribes = options.map((option) => registerCommand(option))
|
||||
return () => {
|
||||
unsubscribes.forEach((unsubscribe) => unsubscribe())
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- we want to run this effect only once
|
||||
}, [t, ...(registerOptions?.deps ?? [])])
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a follow command.
|
||||
*/
|
||||
export function useRegisterFollowCommand<T extends FollowCommandId>(
|
||||
options: CommandOptions<{ id: T; fn: FollowCommandMap[T]["run"] }>,
|
||||
registerOptions?: RegisterOptions,
|
||||
): void
|
||||
/**
|
||||
* Register a unknown command.
|
||||
*/
|
||||
export function useRegisterFollowCommand<T extends string>(
|
||||
options: CommandOptions<{ id: T; fn: () => void }>,
|
||||
registerOptions?: RegisterOptions,
|
||||
): void
|
||||
/**
|
||||
* Register multiple follow commands or unknown commands.
|
||||
*/
|
||||
export function useRegisterFollowCommand<T extends (FollowCommandId | string)[]>(
|
||||
options: [
|
||||
...{
|
||||
[K in keyof T]: T[K] extends FollowCommandId
|
||||
? CommandOptions<{ id: T[K]; fn: FollowCommandMap[T[K]]["run"] }>
|
||||
: CommandOptions<{ id: T[K]; fn: () => void }>
|
||||
},
|
||||
],
|
||||
registerOptions?: RegisterOptions,
|
||||
): void
|
||||
export function useRegisterFollowCommand(
|
||||
options: CommandOptions | CommandOptions[],
|
||||
registerOptions?: RegisterOptions,
|
||||
) {
|
||||
return useRegisterCommandEffect(options as CommandOptions | CommandOptions[], registerOptions)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { assertType, expectTypeOf, test } from "vitest"
|
||||
|
||||
import { COMMAND_ID } from "../commands/id"
|
||||
import { defineCommandArgsArray, defineFollowCommand, defineFollowCommandArgs } from "./command"
|
||||
import { defineFollowCommand } from "./command"
|
||||
|
||||
test("defineFollowCommand types", () => {
|
||||
assertType(
|
||||
|
|
@ -68,71 +68,3 @@ test("defineFollowCommand with keyBinding types", () => {
|
|||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("defineCommandArgs with keyBinding types", () => {
|
||||
assertType(
|
||||
defineFollowCommandArgs({
|
||||
commandId: COMMAND_ID.entry.star,
|
||||
args: [{ entryId: "1" }],
|
||||
}),
|
||||
)
|
||||
|
||||
assertType(
|
||||
defineFollowCommandArgs({
|
||||
commandId: COMMAND_ID.entry.star,
|
||||
// @ts-expect-error - invalid args
|
||||
args: [],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("defineCommandArgsArray with keyBinding types", () => {
|
||||
assertType(
|
||||
defineCommandArgsArray([
|
||||
{
|
||||
commandId: COMMAND_ID.entry.star,
|
||||
args: [{ entryId: "1" }],
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
assertType(
|
||||
defineCommandArgsArray([
|
||||
{
|
||||
commandId: COMMAND_ID.entry.star,
|
||||
// @ts-expect-error - invalid args
|
||||
args: [],
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
assertType(
|
||||
defineCommandArgsArray([
|
||||
{
|
||||
commandId: COMMAND_ID.entry.star,
|
||||
// @ts-expect-error - invalid args
|
||||
args: [],
|
||||
},
|
||||
{
|
||||
commandId: COMMAND_ID.entry.viewEntryContent,
|
||||
args: [],
|
||||
},
|
||||
]),
|
||||
)
|
||||
|
||||
assertType(
|
||||
defineCommandArgsArray<{ test: boolean }>([
|
||||
{
|
||||
commandId: COMMAND_ID.entry.star,
|
||||
args: [{ entryId: "1" }],
|
||||
test: true,
|
||||
},
|
||||
{
|
||||
commandId: COMMAND_ID.entry.viewEntryContent,
|
||||
args: [],
|
||||
// @ts-expect-error - invalid extra property
|
||||
test: 1,
|
||||
},
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import type { Command, CommandOptions, FollowCommand, FollowCommandId } from "../types"
|
||||
import type {
|
||||
Command,
|
||||
CommandOptions,
|
||||
FollowCommand,
|
||||
FollowCommandId,
|
||||
FollowCommandMap,
|
||||
} from "../types"
|
||||
|
||||
export function createCommand<
|
||||
T extends { id: string; fn: (...args: any[]) => unknown } = {
|
||||
|
|
@ -32,33 +38,7 @@ export function createFollowCommand<T extends FollowCommand>(
|
|||
}
|
||||
|
||||
export function defineFollowCommand<T extends FollowCommandId>(
|
||||
options: CommandOptions<{ id: T; fn: Extract<FollowCommand, { id: T }>["run"] }>,
|
||||
options: CommandOptions<{ id: T; fn: FollowCommandMap[T]["run"] }>,
|
||||
) {
|
||||
return options as CommandOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const defineFollowCommandArgs = <T extends FollowCommandId>(config: {
|
||||
commandId: T
|
||||
args: Parameters<Extract<FollowCommand, { id: T }>["run"]>
|
||||
}) => config
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const defineCommandArgsArray = <
|
||||
Ext extends Record<string, unknown>,
|
||||
T extends FollowCommandId[] = FollowCommandId[],
|
||||
>(
|
||||
config: [
|
||||
...{
|
||||
[K in keyof T]: {
|
||||
commandId: T[K]
|
||||
args: Parameters<Extract<FollowCommand, { id: T[K] }>["run"]>
|
||||
// [key: string]: unknown
|
||||
} & Ext
|
||||
},
|
||||
],
|
||||
) => config
|
||||
|
|
|
|||
Loading…
Reference in New Issue