From 0e68e8069b998d961b4926748463b97ef765a51a Mon Sep 17 00:00:00 2001 From: zadam Date: Thu, 1 Dec 2022 16:22:04 +0100 Subject: [PATCH] launcher improvements --- .../app/components/shortcut_component.js | 10 +- .../doc_notes/launchbar_command_launcher.html | 1 + ...tcut.html => launchbar_note_launcher.html} | 0 ...ut.html => launchbar_script_launcher.html} | 0 ...ut.html => launchbar_widget_launcher.html} | 0 src/public/app/menus/launcher_context_menu.js | 8 +- src/public/app/widgets/containers/launcher.js | 149 ++++++++++++++++++ .../widgets/containers/launcher_container.js | 108 ++----------- src/routes/api/keys.js | 4 +- src/services/special_notes.js | 9 +- 10 files changed, 177 insertions(+), 112 deletions(-) create mode 100644 src/public/app/doc_notes/launchbar_command_launcher.html rename src/public/app/doc_notes/{launchbar_note_shortcut.html => launchbar_note_launcher.html} (100%) rename src/public/app/doc_notes/{launchbar_script_shortcut.html => launchbar_script_launcher.html} (100%) rename src/public/app/doc_notes/{launchbar_widget_shortcut.html => launchbar_widget_launcher.html} (100%) create mode 100644 src/public/app/widgets/containers/launcher.js diff --git a/src/public/app/components/shortcut_component.js b/src/public/app/components/shortcut_component.js index 21b718ac1..9df62d7bb 100644 --- a/src/public/app/components/shortcut_component.js +++ b/src/public/app/components/shortcut_component.js @@ -15,14 +15,14 @@ export default class ShortcutComponent extends Component { }); } - bindNoteShortcutHandler(attr) { - const handler = () => appContext.tabManager.getActiveContext().setNote(attr.noteId); - const namespace = attr.attributeId; + bindNoteShortcutHandler(label) { + const handler = () => appContext.tabManager.getActiveContext().setNote(label.noteId); + const namespace = label.attributeId; - if (attr.isDeleted) { + if (label.isDeleted) { shortcutService.removeGlobalShortcut(namespace); } else { - shortcutService.bindGlobalShortcut(attr.value, handler, namespace); + shortcutService.bindGlobalShortcut(label.value, handler, namespace); } } diff --git a/src/public/app/doc_notes/launchbar_command_launcher.html b/src/public/app/doc_notes/launchbar_command_launcher.html new file mode 100644 index 000000000..68eb051a2 --- /dev/null +++ b/src/public/app/doc_notes/launchbar_command_launcher.html @@ -0,0 +1 @@ +

Keyboard shortcut for this launcher action can be configured in Options -> Shortcuts.

diff --git a/src/public/app/doc_notes/launchbar_note_shortcut.html b/src/public/app/doc_notes/launchbar_note_launcher.html similarity index 100% rename from src/public/app/doc_notes/launchbar_note_shortcut.html rename to src/public/app/doc_notes/launchbar_note_launcher.html diff --git a/src/public/app/doc_notes/launchbar_script_shortcut.html b/src/public/app/doc_notes/launchbar_script_launcher.html similarity index 100% rename from src/public/app/doc_notes/launchbar_script_shortcut.html rename to src/public/app/doc_notes/launchbar_script_launcher.html diff --git a/src/public/app/doc_notes/launchbar_widget_shortcut.html b/src/public/app/doc_notes/launchbar_widget_launcher.html similarity index 100% rename from src/public/app/doc_notes/launchbar_widget_shortcut.html rename to src/public/app/doc_notes/launchbar_widget_launcher.html diff --git a/src/public/app/menus/launcher_context_menu.js b/src/public/app/menus/launcher_context_menu.js index d41f6ad6c..4635f8f23 100644 --- a/src/public/app/menus/launcher_context_menu.js +++ b/src/public/app/menus/launcher_context_menu.js @@ -36,9 +36,9 @@ export default class LauncherContextMenu { const canBeReset = note.noteId.startsWith("lb_"); return [ - (isVisibleRoot || isAvailableRoot) ? { title: 'Add note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add script launcher', command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null, - (isVisibleRoot || isAvailableRoot) ? { title: 'Add widget launcher', command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: 'Add a script launcher', command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null, + (isVisibleRoot || isAvailableRoot) ? { title: 'Add a custom widget', command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null, (isVisibleRoot || isAvailableRoot) ? { title: 'Add spacer', command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null, (isVisibleRoot || isAvailableRoot) ? { title: "----" } : null, { title: 'Delete ', command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted }, @@ -54,7 +54,7 @@ export default class LauncherContextMenu { async selectMenuItemHandler({command}) { if (command === 'resetLauncher') { const confirmed = await dialogService.confirm(`Do you really want to reset "${this.node.title}"? - All data / settings in this launcher (and its children) will be lost + All data / settings in this note (and its children) will be lost and the launcher will be returned to its original location.`); if (confirmed) { diff --git a/src/public/app/widgets/containers/launcher.js b/src/public/app/widgets/containers/launcher.js new file mode 100644 index 000000000..daec1e69a --- /dev/null +++ b/src/public/app/widgets/containers/launcher.js @@ -0,0 +1,149 @@ +import ButtonWidget from "../buttons/button_widget.js"; +import dialogService from "../../services/dialog.js"; +import appContext from "../../components/app_context.js"; +import CalendarWidget from "../buttons/calendar.js"; +import SpacerWidget from "../spacer.js"; +import BookmarkButtons from "../bookmark_buttons.js"; +import ProtectedSessionStatusWidget from "../buttons/protected_session_status.js"; +import SyncStatusWidget from "../sync_status.js"; +import BackInHistoryButtonWidget from "../buttons/history/history_back.js"; +import ForwardInHistoryButtonWidget from "../buttons/history/history_forward.js"; +import BasicWidget from "../basic_widget.js"; +import shortcutService from "../../services/shortcuts.js"; + +export default class LauncherWidget extends BasicWidget { + constructor(launcherNote) { + super(); + + if (launcherNote.type !== 'launcher') { + throw new Error(`Note '${this.note.noteId}' '${this.note.title}' is not a launcher even though it's in the launcher subtree`); + } + + this.note = launcherNote; + this.innerWidget = null; + this.handler = null; + } + + isEnabled() { + return this.innerWidget.isEnabled(); + } + + doRender() { + this.$widget = this.innerWidget.render(); + } + + async initLauncher() { + const launcherType = this.note.getLabelValue("launcherType"); + + if (launcherType === 'command') { + this.handler = () => this.triggerCommand(this.note.getLabelValue("command")); + + this.innerWidget = new ButtonWidget() + .title(this.note.title) + .icon(this.note.getIcon()) + .onClick(this.handler); + } else if (launcherType === 'note') { + // we're intentionally displaying the launcher title and icon instead of the target + // e.g. you want to make launchers to 2 mermaid diagrams which both have mermaid icon (ok), + // but on the launchpad you want them distinguishable. + // for titles, the note titles may follow a different scheme than maybe desirable on the launchpad + // another reason is the discrepancy between what user sees on the launchpad and in the config (esp. icons). + // The only (but major) downside is more work in setting up the typical case where you actually want to have both title and icon in sync. + + this.handler = () => { + const targetNoteId = this.note.getRelationValue('targetNote'); + + if (!targetNoteId) { + dialogService.info("This launcher doesn't define target note."); + return; + } + + appContext.tabManager.openTabWithNoteWithHoisting(targetNoteId, true) + }; + + this.innerWidget = new ButtonWidget() + .title(this.note.title) + .icon(this.note.getIcon()) + .onClick(this.handler); + } else if (launcherType === 'script') { + this.handler = async () => { + const script = await this.note.getRelationTarget('script'); + + await script.executeScript(); + }; + + this.innerWidget = new ButtonWidget() + .title(this.note.title) + .icon(this.note.getIcon()) + .onClick(this.handler); + } else if (launcherType === 'customWidget') { + const widget = await this.note.getRelationTarget('widget'); + + if (widget) { + this.innerWidget = await widget.executeScript(); + } else { + throw new Error(`Could not initiate custom widget of launcher '${this.note.noteId}' '${this.note.title}`); + } + } else if (launcherType === 'builtinWidget') { + const builtinWidget = this.note.getLabelValue("builtinWidget"); + + if (builtinWidget) { + if (builtinWidget === 'calendar') { + this.innerWidget = new CalendarWidget(this.note.title, this.note.getIcon()); + } else if (builtinWidget === 'spacer') { + // || has to be inside since 0 is a valid value + const baseSize = parseInt(this.note.getLabelValue("baseSize") || "40"); + const growthFactor = parseInt(this.note.getLabelValue("growthFactor") || "100"); + + this.innerWidget = new SpacerWidget(baseSize, growthFactor); + } else if (builtinWidget === 'bookmarks') { + this.innerWidget = new BookmarkButtons(); + } else if (builtinWidget === 'protectedSession') { + this.innerWidget = new ProtectedSessionStatusWidget(); + } else if (builtinWidget === 'syncStatus') { + this.innerWidget = new SyncStatusWidget(); + } else if (builtinWidget === 'backInHistoryButton') { + this.innerWidget = new BackInHistoryButtonWidget(); + } else if (builtinWidget === 'forwardInHistoryButton') { + this.innerWidget = new ForwardInHistoryButtonWidget(); + } else { + throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${this.note.noteId} "${this.note.title}"`); + } + } + } else { + throw new Error(`Unrecognized launcher type '${launcherType}' for launcher '${this.note.noteId}' title ${this.note.title}`); + } + + if (!this.innerWidget) { + throw new Error(`Unknown initialization error for note '${this.note.noteId}', title '${this.note.title}'`); + } + + for (const label of this.note.getLabels('keyboardShortcut')) { + this.bindNoteShortcutHandler(label); + } + + this.child(this.innerWidget); + } + + bindNoteShortcutHandler(label) { + if (!this.handler) { + return; + } + + const namespace = label.attributeId; + + if (label.isDeleted) { + shortcutService.removeGlobalShortcut(namespace); + } else { + shortcutService.bindGlobalShortcut(label.value, this.handler, namespace); + } + } + + entitiesReloadedEvent({loadResults}) { + for (const attr of loadResults.getAttributes()) { + if (attr.noteId === this.note.noteId && attr.type === 'label' && attr.name === 'keyboardShortcut') { + this.bindNoteShortcutHandler(attr); + } + } + } +} diff --git a/src/public/app/widgets/containers/launcher_container.js b/src/public/app/widgets/containers/launcher_container.js index 7048c550d..8e661adda 100644 --- a/src/public/app/widgets/containers/launcher_container.js +++ b/src/public/app/widgets/containers/launcher_container.js @@ -1,15 +1,7 @@ import FlexContainer from "./flex_container.js"; import froca from "../../services/froca.js"; -import ButtonWidget from "../buttons/button_widget.js"; -import CalendarWidget from "../buttons/calendar.js"; import appContext from "../../components/app_context.js"; -import SpacerWidget from "../spacer.js"; -import BookmarkButtons from "../bookmark_buttons.js"; -import ProtectedSessionStatusWidget from "../buttons/protected_session_status.js"; -import SyncStatusWidget from "../sync_status.js"; -import BackInHistoryButtonWidget from "../buttons/history/history_back.js"; -import ForwardInHistoryButtonWidget from "../buttons/history/history_forward.js"; -import dialogService from "../../services/dialog.js"; +import LauncherWidget from "./launcher.js"; export default class LauncherContainer extends FlexContainer { constructor() { @@ -35,7 +27,16 @@ export default class LauncherContainer extends FlexContainer { await Promise.allSettled( (await visibleLaunchersRoot.getChildNotes()) - .map(launcher => this.initLauncher(launcher)) + .map(async launcherNote => { + try { + const launcherWidget = new LauncherWidget(launcherNote); + await launcherWidget.initLauncher(); + this.child(launcherWidget); + } + catch (e) { + console.error(e.message); + } + }) ); this.$widget.empty(); @@ -59,93 +60,6 @@ export default class LauncherContainer extends FlexContainer { } } - async initLauncher(launcher) { - try { - if (launcher.type !== 'launcher') { - console.warn(`Note ${launcher.noteId} is not a launcher even though it's in launcher subtree`); - return; - } - - const launcherType = launcher.getLabelValue("launcherType"); - - if (launcherType === 'command') { - this.child(new ButtonWidget() - .title(launcher.title) - .icon(launcher.getIcon()) - .command(launcher.getLabelValue("command"))); - } else if (launcherType === 'note') { - // we're intentionally displaying the launcher title and icon instead of the target - // e.g. you want to make launchers to 2 mermaid diagrams which both have mermaid icon (ok), - // but on the launchpad you want them distinguishable. - // for titles, the note titles may follow a different scheme than maybe desirable on the launchpad - // another reason is the discrepancy between what user sees on the launchpad and in the config (esp. icons). - // The only (but major) downside is more work in setting up the typical case where you actually want to have both title and icon in sync. - - this.child(new ButtonWidget() - .title(launcher.title) - .icon(launcher.getIcon()) - .onClick(() => { - const targetNoteId = launcher.getRelationValue('targetNote'); - - if (!targetNoteId) { - dialogService.info("This launcher doesn't define target note."); - return; - } - - appContext.tabManager.openTabWithNoteWithHoisting(targetNoteId, true) - })); - } else if (launcherType === 'script') { - this.child(new ButtonWidget() - .title(launcher.title) - .icon(launcher.getIcon()) - .onClick(async () => { - const script = await launcher.getRelationTarget('script'); - - await script.executeScript(); - })); - } else if (launcherType === 'customWidget') { - const widget = await launcher.getRelationTarget('widget'); - - if (widget) { - const res = await widget.executeScript(); - - this.child(res); - } - } else if (launcherType === 'builtinWidget') { - const builtinWidget = launcher.getLabelValue("builtinWidget"); - - if (builtinWidget) { - if (builtinWidget === 'calendar') { - this.child(new CalendarWidget(launcher.title, launcher.getIcon())); - } else if (builtinWidget === 'spacer') { - // || has to be inside since 0 is a valid value - const baseSize = parseInt(launcher.getLabelValue("baseSize") || "40"); - const growthFactor = parseInt(launcher.getLabelValue("growthFactor") || "100"); - - this.child(new SpacerWidget(baseSize, growthFactor)); - } else if (builtinWidget === 'bookmarks') { - this.child(new BookmarkButtons()); - } else if (builtinWidget === 'protectedSession') { - this.child(new ProtectedSessionStatusWidget()); - } else if (builtinWidget === 'syncStatus') { - this.child(new SyncStatusWidget()); - } else if (builtinWidget === 'backInHistoryButton') { - this.child(new BackInHistoryButtonWidget()); - } else if (builtinWidget === 'forwardInHistoryButton') { - this.child(new ForwardInHistoryButtonWidget()); - } else { - console.warn(`Unrecognized builtin widget ${builtinWidget} for launcher ${launcher.noteId} "${launcher.title}"`); - } - } - } else { - console.warn(`Unrecognized launcher type ${launcherType} for launcher '${launcher.noteId}' title ${launcher.title}`); - } - } - catch (e) { - console.error(`Initialization of launcher '${launcher.noteId}' with title '${launcher.title}' failed with error: ${e.message} ${e.stack}`); - } - } - entitiesReloadedEvent({loadResults}) { if (loadResults.getNoteIds().find(noteId => froca.notes[noteId]?.isLaunchBarConfig()) || loadResults.getBranches().find(branch => branch.parentNoteId.startsWith("lb_")) diff --git a/src/routes/api/keys.js b/src/routes/api/keys.js index 14bd42d64..bc1b97d4a 100644 --- a/src/routes/api/keys.js +++ b/src/routes/api/keys.js @@ -8,10 +8,10 @@ function getKeyboardActions() { } function getShortcutsForNotes() { - const attrs = becca.findAttributes('label', 'keyboardShortcut'); + const labels = becca.findAttributes('label', 'keyboardShortcut'); // launchers have different handling - return attrs.filter(attr => becca.getNote(attr.noteId)?.type !== 'launcher'); + return labels.filter(attr => becca.getNote(attr.noteId)?.type !== 'launcher'); } module.exports = { diff --git a/src/services/special_notes.js b/src/services/special_notes.js index 8f595f727..c2fb9f8ea 100644 --- a/src/services/special_notes.js +++ b/src/services/special_notes.js @@ -474,16 +474,14 @@ function createLauncherTemplates() { } if (!(LBTPL_BASE in becca.notes)) { - const tpl = noteService.createNewNote({ + noteService.createNewNote({ branchId: LBTPL_BASE, noteId: LBTPL_BASE, title: 'Launch bar base launcher', type: 'doc', content: '', parentNoteId: getHiddenRoot().noteId - }).note; - - tpl.addLabel('label:keyboardLauncher', 'promoted,text'); + }); } if (!(LBTPL_COMMAND in becca.notes)) { @@ -498,6 +496,7 @@ function createLauncherTemplates() { tpl.addRelation('template', LBTPL_BASE); tpl.addLabel('launcherType', 'command'); + tpl.addLabel('docName', 'launchbar_command_launcher'); } if (!(LBTPL_NOTE_LAUNCHER in becca.notes)) { @@ -514,6 +513,7 @@ function createLauncherTemplates() { tpl.addLabel('launcherType', 'note'); tpl.addLabel('relation:targetNote', 'promoted'); tpl.addLabel('docName', 'launchbar_note_launcher'); + tpl.addLabel('label:keyboardShortcut', 'promoted,text'); } if (!(LBTPL_SCRIPT in becca.notes)) { @@ -530,6 +530,7 @@ function createLauncherTemplates() { tpl.addLabel('launcherType', 'script'); tpl.addLabel('relation:script', 'promoted'); tpl.addLabel('docName', 'launchbar_script_launcher'); + tpl.addLabel('label:keyboardShortcut', 'promoted,text'); } if (!(LBTPL_BUILTIN_WIDGET in becca.notes)) {