From e7ea508f2bdc6be268a9804484b64841c817eb6b Mon Sep 17 00:00:00 2001 From: GeoffreyChen777 Date: Sun, 2 Jun 2024 12:53:21 +0100 Subject: [PATCH] feat: select categorizers jointly (#553) #512 --- app/common/utils.ts | 25 ++++++++++ app/renderer/services/uistate-service.ts | 8 ++-- app/renderer/ui/app-view.vue | 15 ++---- app/renderer/ui/main-view/main-view.vue | 2 +- .../components/tree/tree-node.vue | 21 +++++++-- .../components/tree/tree-root.vue | 4 +- .../ui/sidebar-view/sidebar-library-view.vue | 47 +++++++++++++++---- app/renderer/ui/sidebar-view/sidebar-view.vue | 3 +- 8 files changed, 92 insertions(+), 33 deletions(-) diff --git a/app/common/utils.ts b/app/common/utils.ts index f0a72f4d..cac3122a 100644 --- a/app/common/utils.ts +++ b/app/common/utils.ts @@ -82,3 +82,28 @@ export const convertKeyboardEvent = (e: KeyboardEvent): ShortcutEvent => { target: e.target, }; }; + +export const formatMouseModifiers = (event: PointerEvent): string[] => { + let mouseModifiers: string[] = []; + + if (event.ctrlKey) { + mouseModifiers.push("Control"); + } + + if (event.metaKey) { + if (isMac) { + mouseModifiers.push("Command"); + } else { + mouseModifiers.push("Meta"); + } + } + + if (event.altKey) { + mouseModifiers.push("Alt"); + } + if (event.shiftKey) { + mouseModifiers.push("Shift"); + } + + return mouseModifiers; +}; \ No newline at end of file diff --git a/app/renderer/services/uistate-service.ts b/app/renderer/services/uistate-service.ts index dee9fd77..6b1f1b14 100644 --- a/app/renderer/services/uistate-service.ts +++ b/app/renderer/services/uistate-service.ts @@ -30,12 +30,12 @@ export interface IUIStateServiceState { // It can be accessed in any component. But it is read-only. It can be only changed by the event listener of selectedIndex in the dataview. selectedPaperEntities: Array; selectedFeedEntities: Array; - selectedQuerySentenceId: string; + selectedQuerySentenceIds: string[]; selectedFeed: string; editingPaperSmartFilter: PaperSmartFilter; - querySentenceSidebar: string; + querySentencesSidebar: Array; querySentenceCommandbar: string; dragingIds: Array; @@ -81,11 +81,11 @@ export class UIStateService extends Eventable { selectedIds: [], selectedPaperEntities: [], selectedFeedEntities: [], - selectedQuerySentenceId: "", + selectedQuerySentenceIds: ["lib-all"], selectedFeed: "feed-all", dragingIds: [], - querySentenceSidebar: "", + querySentencesSidebar: [], querySentenceCommandbar: "", editingPaperSmartFilter: new PaperSmartFilter(), diff --git a/app/renderer/ui/app-view.vue b/app/renderer/ui/app-view.vue index 27c8c074..5d73ecdb 100644 --- a/app/renderer/ui/app-view.vue +++ b/app/renderer/ui/app-view.vue @@ -64,12 +64,12 @@ const reloadPaperEntities = async () => { let querySentence: string; let fulltextQuerySetence: string | undefined = undefined; if (uiState.querySentenceCommandbar.includes("(fulltext contains")) { - querySentence = uiState.querySentenceSidebar; + querySentence = uiState.querySentencesSidebar.map((x) => `(${x})`).join(" AND "); fulltextQuerySetence = uiState.querySentenceCommandbar; } else { querySentence = [ uiState.querySentenceCommandbar, - uiState.querySentenceSidebar, + ...uiState.querySentencesSidebar, ] .filter((x) => x) .map((x) => `(${x})`) @@ -194,7 +194,7 @@ disposable( [ "selectedFeed", "contentType", - "querySentenceSidebar", + "querySentencesSidebar", "querySentenceCommandbar", ], (value) => { @@ -207,15 +207,6 @@ disposable( ) ); -disposable( - uiStateService.onChanged( - ["querySentenceSidebar", "querySentenceCommandbar"], - (value) => { - reloadPaperEntities(); - } - ) -); - disposable( preferenceService.onChanged("appLibFolder", async (value) => { await databaseService.initialize(); diff --git a/app/renderer/ui/main-view/main-view.vue b/app/renderer/ui/main-view/main-view.vue index 7a5d7788..f45a1e0a 100644 --- a/app/renderer/ui/main-view/main-view.vue +++ b/app/renderer/ui/main-view/main-view.vue @@ -747,7 +747,7 @@ disposable( [ "contentType", "selectedFeed", - "querySentenceSidebar", + "querySentencesSidebar", "querySentenceCommandbar", ], clearSelected diff --git a/app/renderer/ui/sidebar-view/components/tree/tree-node.vue b/app/renderer/ui/sidebar-view/components/tree/tree-node.vue index 05ac5fb7..32a9feeb 100644 --- a/app/renderer/ui/sidebar-view/components/tree/tree-node.vue +++ b/app/renderer/ui/sidebar-view/components/tree/tree-node.vue @@ -12,6 +12,7 @@ import { import { Ref, inject, ref } from "vue"; import Counter from "../counter.vue"; import TreeNode from "./tree-node.vue"; +import { formatMouseModifiers } from "@/common/utils"; const props = defineProps({ parent_id: { @@ -96,7 +97,7 @@ const props = defineProps({ const collopsed = ref(props.defaultCollopsed); const clickEvent = inject>("clickEvent"); -const onClicked = () => { +const onClicked = (e: PointerEvent) => { if (props.isRoot) { collopsed.value = !collopsed.value; return; @@ -104,9 +105,23 @@ const onClicked = () => { if (!props.isRoot) { collopsed.value = false; if (!clickEvent) return; + + const modifiers = formatMouseModifiers(e).join("+"); + + let modifiersPayload: string | undefined = undefined; + + if ( + (modifiers === "Control" && + (process.platform === "win32" || process.platform === "linux")) || + (modifiers === "Command" && process.platform === "darwin") + ) { + modifiersPayload = modifiers; + } + clickEvent.value = { _id: props.id, query: props.query, + modifiers: modifiersPayload, }; } }; @@ -164,7 +179,7 @@ const onEditSubmit = (patch: { }; const editingItemId = inject>("editingItemId"); -const selectedItemId = inject>("selectedItemId"); +const selectedItemId = inject>("selectedItemId"); const dropEvent = inject>("dropEvent"); @@ -314,7 +329,7 @@ const onDragged = (event: DragEvent) => { :indent="indent" :count="child.count" :with-spinner="withSpinner" - :activated="`${child._id}` === selectedItemId" + :activated="selectedItemId?.includes(`${child._id}`)" :item-draggable="itemDraggable" /> diff --git a/app/renderer/ui/sidebar-view/components/tree/tree-root.vue b/app/renderer/ui/sidebar-view/components/tree/tree-root.vue index 83291786..ca2dddd5 100644 --- a/app/renderer/ui/sidebar-view/components/tree/tree-root.vue +++ b/app/renderer/ui/sidebar-view/components/tree/tree-root.vue @@ -32,8 +32,8 @@ const props = defineProps({ default: "", }, selectedItemId: { - type: String, - default: "", + type: Array, + default: [], }, itemDraggable: { type: Boolean, diff --git a/app/renderer/ui/sidebar-view/sidebar-library-view.vue b/app/renderer/ui/sidebar-view/sidebar-library-view.vue index 0e2933c6..3ce7ee79 100644 --- a/app/renderer/ui/sidebar-view/sidebar-library-view.vue +++ b/app/renderer/ui/sidebar-view/sidebar-library-view.vue @@ -66,11 +66,38 @@ const smartfiltersViewTree = computed(() => { // ================================ // Event Functions // ================================ -const onSelect = (payload: { _id: string; query: string }) => { - uiState.selectedQuerySentenceId = payload._id; - uiState.querySentenceSidebar = payload.query; +const onSelect = (payload: { + _id: string; + query: string; + modifiers?: string; +}) => { if (payload._id === "lib-all") { + uiState.selectedQuerySentenceIds = ["lib-all"]; + uiState.querySentencesSidebar = []; uiState.querySentenceCommandbar = ""; + return; + } + + if (payload.modifiers === "Control" || payload.modifiers === "Command") { + if ( + uiState.selectedQuerySentenceIds.includes(payload._id) && + uiState.selectedQuerySentenceIds.length > 1 + ) { + uiState.selectedQuerySentenceIds = uiState.selectedQuerySentenceIds.filter( + (id) => id !== payload._id + ); + + uiState.querySentencesSidebar = Array.from(uiState.querySentencesSidebar.filter( + (query) => query !== payload.query + )); + } else { + uiState.selectedQuerySentenceIds.push(payload._id); + uiState.querySentencesSidebar = [...uiState.querySentencesSidebar, payload.query]; + } + } else { + // Single selection + uiState.selectedQuerySentenceIds = [payload._id]; + uiState.querySentencesSidebar = [payload.query]; } }; @@ -100,7 +127,7 @@ const onDroped = async ( categorizerService.update(type, sourceCategorizer, categorizer); - return + return; } // For others @@ -192,7 +219,7 @@ disposable( newValue.value.data, ]); } - uiState.selectedQuerySentenceId = "lib-all"; + uiState.selectedQuerySentenceIds = ["lib-all"]; } } ) @@ -255,7 +282,7 @@ disposable( 'h-6': prefState.isSidebarCompact, 'h-7': !prefState.isSidebarCompact, 'bg-neutral-400 bg-opacity-30': - uiState.selectedQuerySentenceId === 'lib-all', + uiState.selectedQuerySentenceIds.includes('lib-all'), }" @click="onSelect({ _id: 'lib-all', query: '' })" > @@ -283,7 +310,7 @@ disposable( 'h-6': prefState.isSidebarCompact, 'h-7': !prefState.isSidebarCompact, 'bg-neutral-400 bg-opacity-30': - uiState.selectedQuerySentenceId === 'lib-flag', + uiState.selectedQuerySentenceIds.includes('lib-flag'), }" @click="onSelect({ _id: 'lib-flag', query: 'flag == true' })" > @@ -306,7 +333,7 @@ disposable( :compact="prefState.isSidebarCompact" :with-spinner="false" :editing-item-id="editingItemId" - :selected-item-id="uiState.selectedQuerySentenceId" + :selected-item-id="uiState.selectedQuerySentenceIds" @event:click="onSelect" @event:update=" (value) => onUpdateSmartFilter(PaperSmartFilterType.smartfilter, value) @@ -330,7 +357,7 @@ disposable( :compact="prefState.isSidebarCompact" :with-spinner="false" :editing-item-id="editingItemId" - :selected-item-id="uiState.selectedQuerySentenceId" + :selected-item-id="uiState.selectedQuerySentenceIds" @event:click="onSelect" @event:update="(value) => onUpdate(CategorizerType.PaperTag, value)" @event:contextmenu=" @@ -353,7 +380,7 @@ disposable( :compact="prefState.isSidebarCompact" :with-spinner="false" :editing-item-id="editingItemId" - :selected-item-id="uiState.selectedQuerySentenceId" + :selected-item-id="uiState.selectedQuerySentenceIds" :item-draggable="true" :root-dropable="true" @event:click="onSelect" diff --git a/app/renderer/ui/sidebar-view/sidebar-view.vue b/app/renderer/ui/sidebar-view/sidebar-view.vue index 557b4401..5b2e2ef3 100644 --- a/app/renderer/ui/sidebar-view/sidebar-view.vue +++ b/app/renderer/ui/sidebar-view/sidebar-view.vue @@ -13,7 +13,8 @@ const uiState = uiStateService.useState(); const onViewContentSwitch = (view: number) => { if (view === 0) { uiState.selectedIndex = []; - uiState.selectedQuerySentenceId = "lib-all"; + uiState.selectedQuerySentenceIds = ["lib-all"]; + uiState.querySentencesSidebar = []; } else { uiState.selectedIndex = []; uiState.selectedFeed = "feed-all";