refactor(client/ts): use discriminated unions for triggering commands

This commit is contained in:
Elian Doran 2024-12-21 23:47:18 +02:00
parent 9d4841306f
commit 4e3417482e
No known key found for this signature in database
5 changed files with 51 additions and 35 deletions

View File

@ -17,7 +17,7 @@ import { t, initLocale } from "../services/i18n.js";
import NoteDetailWidget from "../widgets/note_detail.js"; import NoteDetailWidget from "../widgets/note_detail.js";
import { ResolveOptions } from "../widgets/dialogs/delete_notes.js"; import { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; import { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import { ConfirmWithMessageOptions } from "../widgets/dialogs/confirm.js"; import { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget; getRootWidget: (appContext: AppContext) => RootWidget;
@ -31,63 +31,76 @@ interface BeforeUploadListener extends Component {
beforeUnloadEvent(): boolean; beforeUnloadEvent(): boolean;
} }
interface ApiLogMessagesData { interface CommandData {
noteId?: string; ntxId?: string;
noteIds?: string[];
messages?: unknown[];
} }
interface FocusOnDetailData { interface ApiLogMessagesData extends CommandData {
}
interface FocusOnDetailData extends CommandData {
ntxId: string; ntxId: string;
} }
interface SearchNotesData { interface SearchNotesData extends CommandData {
searchString: string | undefined; searchString: string | undefined;
} }
interface ShowDeleteNotesDialogData { interface ShowDeleteNotesDialogData extends CommandData {
branchIdsToDelete: string[]; branchIdsToDelete: string[];
callback: (value: ResolveOptions) => void; callback: (value: ResolveOptions) => void;
forceDeleteAllClones: boolean; forceDeleteAllClones: boolean;
} }
interface OpenedFileUpdatedData { interface OpenedFileUpdatedData extends CommandData {
entityType: string; entityType: string;
entityId: string; entityId: string;
lastModifiedMs: number; lastModifiedMs: number;
filePath: string; filePath: string;
} }
interface FocusAndSelectTitleData { interface FocusAndSelectTitleData extends CommandData {
isNewNote: boolean; isNewNote: boolean;
} }
interface OpenNewNoteSplitData { interface OpenNewNoteSplitData extends CommandData {
ntxId: string; ntxId: string;
notePath: string; notePath: string;
} }
interface ExecuteInActiveNoteDetailWidgetData { interface ExecuteInActiveNoteDetailWidgetData extends CommandData {
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void
} }
interface AddTextToActiveEditorData { interface AddTextToActiveEditorData extends CommandData {
text: string; text: string;
} }
export type TriggerData = interface NoData extends CommandData { }
ApiLogMessagesData // For "api-log-messages"
| FocusOnDetailData // For "focusOnDetail" type CommandMappings = {
| SearchNotesData // For "searchNotes" "api-log-messages": ApiLogMessagesData;
| ShowDeleteNotesDialogData // For "showDeleteNotesDialog" focusOnDetail: FocusOnDetailData;
| OpenedFileUpdatedData // For "openedFileUpdated" searchNotes: SearchNotesData;
| FocusAndSelectTitleData // For "focusAndSelectTitle" showDeleteNotesDialog: ShowDeleteNotesDialogData;
| PromptDialogOptions // For "showPromptDialog" showConfirmDeleteNoteBoxWithNoteDialog: ConfirmWithTitleOptions;
| ConfirmWithMessageOptions // For "showConfirmDialog" openedFileUpdated: OpenedFileUpdatedData;
| OpenNewNoteSplitData // For "openNewNoteSplit" focusAndSelectTitle: FocusAndSelectTitleData;
| ExecuteInActiveNoteDetailWidgetData // For "executeInActiveNoteDetailWidget" showPromptDialog: PromptDialogOptions;
| AddTextToActiveEditorData // For "addTextToActiveEditor" showInfoDialog: ConfirmWithMessageOptions;
; showConfirmDialog: ConfirmWithMessageOptions;
openNewNoteSplit: OpenNewNoteSplitData;
executeInActiveNoteDetailWidget: ExecuteInActiveNoteDetailWidgetData;
addTextToActiveEditor: AddTextToActiveEditorData;
importMarkdownInline: NoData;
showPasswordNotSet: NoData;
showProtectedSessionPasswordDialog: NoData;
closeProtectedSessionPasswordDialog: NoData;
}
export type CommandNames = keyof CommandMappings;
class AppContext extends Component { class AppContext extends Component {
@ -182,11 +195,14 @@ class AppContext extends Component {
this.triggerEvent('initialRenderComplete'); this.triggerEvent('initialRenderComplete');
} }
triggerEvent(name: string, data: TriggerData = {}) { // TODO: Update signature once all client code is updated, to use a map similar to triggerCommand.
triggerEvent(name: string, data: unknown = {}) {
return this.handleEvent(name, data); return this.handleEvent(name, data);
} }
triggerCommand(name: string, data: TriggerData = {}) { // TODO: Remove ignore once all commands are mapped out.
//@ts-ignore
triggerCommand<K extends CommandNames>(name: K, data: CommandMappings[K] = {}) {
for (const executor of this.components) { for (const executor of this.components) {
const fun = (executor as any)[`${name}Command`]; const fun = (executor as any)[`${name}Command`];

View File

@ -9,7 +9,7 @@ import dateNotesService from './date_notes.js';
import searchService from './search.js'; import searchService from './search.js';
import RightPanelWidget from '../widgets/right_panel_widget.js'; import RightPanelWidget from '../widgets/right_panel_widget.js';
import ws from "./ws.js"; import ws from "./ws.js";
import appContext, { TriggerData } from "../components/app_context.js"; import appContext from "../components/app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js"; import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import BasicWidget from "../widgets/basic_widget.js"; import BasicWidget from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js"; import SpacedUpdate from "./spaced_update.js";
@ -241,12 +241,12 @@ interface Api {
/** /**
* Trigger command. This is a very low-level API which should be avoided if possible. * Trigger command. This is a very low-level API which should be avoided if possible.
*/ */
triggerCommand(name: string, data: TriggerData): void; triggerCommand: typeof appContext.triggerCommand;
/** /**
* Trigger event. This is a very low-level API which should be avoided if possible. * Trigger event. This is a very low-level API which should be avoided if possible.
*/ */
triggerEvent(name: string, data: TriggerData): void; triggerEvent: typeof appContext.triggerEvent;
/** /**
* Create a note link (jQuery object) for given note. * Create a note link (jQuery object) for given note.

View File

@ -1,5 +1,5 @@
import server from "./server.js"; import server from "./server.js";
import appContext from "../components/app_context.js"; import appContext, { CommandNames } from "../components/app_context.js";
import shortcutService from "./shortcuts.js"; import shortcutService from "./shortcuts.js";
import Component from "../components/component.js"; import Component from "../components/component.js";
@ -7,7 +7,7 @@ const keyboardActionRepo: Record<string, Action> = {};
// TODO: Deduplicate with server. // TODO: Deduplicate with server.
interface Action { interface Action {
actionName: string; actionName: CommandNames;
effectiveShortcuts: string[]; effectiveShortcuts: string[];
scope: string; scope: string;
} }

View File

@ -41,7 +41,7 @@ export interface ConfirmWithMessageOptions {
callback: ConfirmDialogCallback; callback: ConfirmDialogCallback;
} }
interface ConfirmWithTitleOptions { export interface ConfirmWithTitleOptions {
title: string; title: string;
callback: ConfirmDialogCallback; callback: ConfirmDialogCallback;
} }

View File

@ -32,7 +32,7 @@ export interface PromptDialogOptions {
message?: string; message?: string;
defaultValue?: string; defaultValue?: string;
shown: PromptShownDialogCallback; shown: PromptShownDialogCallback;
callback: () => void; callback: (value: unknown) => void;
} }
export type PromptShownDialogCallback = ((callback: ShownCallbackData) => void) | null; export type PromptShownDialogCallback = ((callback: ShownCallbackData) => void) | null;