refactor: move command registry to frontend/core (#7135)

move command registeration related logic out of infra module.
This commit is contained in:
pengx17
2024-06-05 14:09:20 +00:00
parent fa4e4c738a
commit de81527e29
24 changed files with 31 additions and 32 deletions

View File

@@ -23,7 +23,6 @@
"lodash-es": "^4.17.21",
"nanoid": "^5.0.7",
"react": "18.3.1",
"tinykeys": "patch:tinykeys@npm%3A2.1.0#~/.yarn/patches/tinykeys-npm-2.1.0-819feeaed0.patch",
"yjs": "^13.6.14",
"zod": "^3.22.4"
},

View File

@@ -1,5 +0,0 @@
# AFFiNE Command Abstractions
This package contains the command abstractions for the AFFiNE framework to be used for CMD-K.
The implementation is highly inspired by the [VSCode Command Abstractions](https://github.com/microsoft/vscode)

View File

@@ -1,103 +0,0 @@
import type { ReactNode } from 'react';
// TODO: need better way for composing different precondition strategies
export enum PreconditionStrategy {
Always,
InPaperOrEdgeless,
InPaper,
InEdgeless,
InEdgelessPresentationMode,
NoSearchResult,
Never,
}
export type CommandCategory =
| 'editor:insert-object'
| 'editor:page'
| 'editor:edgeless'
| 'affine:recent'
| 'affine:pages'
| 'affine:edgeless'
| 'affine:collections'
| 'affine:navigation'
| 'affine:creation'
| 'affine:settings'
| 'affine:layout'
| 'affine:updates'
| 'affine:help'
| 'affine:general'
| 'affine:results';
export interface KeybindingOptions {
binding: string;
// some keybindings are already registered in blocksuite
// we can skip the registration of these keybindings __FOR NOW__
skipRegister?: boolean;
}
export interface AffineCommandOptions {
id: string;
// a set of predefined precondition strategies, but also allow user to customize their own
preconditionStrategy?: PreconditionStrategy | (() => boolean);
// main text on the left..
// make text a function so that we can do i18n and interpolation when we need to
label:
| string
| (() => string)
| {
title: string;
subTitle?: string;
}
| (() => {
title: string;
subTitle?: string;
});
icon: ReactNode; // todo: need a mapping from string -> React element/SVG
category?: CommandCategory;
// we use https://github.com/jamiebuilds/tinykeys so that we can use the same keybinding definition
// for both mac and windows
// todo: render keybinding in command palette
keyBinding?: KeybindingOptions | string;
run: () => void | Promise<void>;
}
export interface AffineCommand {
readonly id: string;
readonly preconditionStrategy: PreconditionStrategy | (() => boolean);
readonly label: {
title: string;
subTitle?: string;
};
readonly icon?: ReactNode; // icon name
readonly category: CommandCategory;
readonly keyBinding?: KeybindingOptions;
run(): void | Promise<void>;
}
export function createAffineCommand(
options: AffineCommandOptions
): AffineCommand {
return {
id: options.id,
run: options.run,
icon: options.icon,
preconditionStrategy:
options.preconditionStrategy ?? PreconditionStrategy.Always,
category: options.category ?? 'affine:general',
get label() {
let label = options.label;
label = typeof label === 'function' ? label?.() : label;
label =
typeof label === 'string'
? {
title: label,
}
: label;
return label;
},
keyBinding:
typeof options.keyBinding === 'string'
? { binding: options.keyBinding }
: options.keyBinding,
};
}

View File

@@ -1,2 +0,0 @@
export * from './command';
export * from './registry';

View File

@@ -1,64 +0,0 @@
import { DebugLogger } from '@affine/debug';
// @ts-expect-error upstream type is wrong
import { tinykeys } from 'tinykeys';
import type { AffineCommand, AffineCommandOptions } from './command';
import { createAffineCommand } from './command';
const commandLogger = new DebugLogger('command:registry');
export const AffineCommandRegistry = new (class {
readonly commands: Map<string, AffineCommand> = new Map();
register(options: AffineCommandOptions) {
if (this.commands.has(options.id)) {
commandLogger.warn(`Command ${options.id} already registered.`);
return () => {};
}
const command = createAffineCommand(options);
this.commands.set(command.id, command);
let unsubKb: (() => void) | undefined;
if (
command.keyBinding &&
!command.keyBinding.skipRegister &&
typeof window !== 'undefined'
) {
const { binding: keybinding } = command.keyBinding;
unsubKb = tinykeys(window, {
[keybinding]: async (e: Event) => {
e.preventDefault();
try {
await command.run();
} catch (e) {
console.error(`Failed to invoke keybinding [${keybinding}]`, e);
}
},
});
}
commandLogger.debug(`Registered command ${command.id}`);
return () => {
unsubKb?.();
this.commands.delete(command.id);
commandLogger.debug(`Unregistered command ${command.id}`);
};
}
get(id: string): AffineCommand | undefined {
if (!this.commands.has(id)) {
commandLogger.warn(`Command ${id} not registered.`);
return undefined;
}
return this.commands.get(id);
}
getAll(): AffineCommand[] {
return Array.from(this.commands.values());
}
})();
export function registerAffineCommand(options: AffineCommandOptions) {
return AffineCommandRegistry.register(options);
}

View File

@@ -1,7 +1,6 @@
export * from './app-config-storage';
export * from './atom';
export * from './blocksuite';
export * from './command';
export * from './framework';
export * from './initialization';
export * from './livedata';