From 59f7846627385a767a4035bbbca17ec885ac87f1 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:25:21 +0200 Subject: [PATCH 001/131] generalize activity bar --- .../ActivityBar/ActivityBar.test.js | 2 +- .../components/ActivityBar/ActivityBar.vue | 57 ++++++++++++++----- .../ActivityBar/ActivitySettings.test.js | 3 +- .../ActivityBar/ActivitySettings.vue | 3 +- .../src/components/Panels/SettingsPanel.vue | 8 ++- client/src/stores/activitySetup.ts | 4 +- client/src/stores/activityStore.test.ts | 6 +- client/src/stores/activityStore.ts | 52 ++++++++++++++--- 8 files changed, 101 insertions(+), 34 deletions(-) diff --git a/client/src/components/ActivityBar/ActivityBar.test.js b/client/src/components/ActivityBar/ActivityBar.test.js index 1fbab6c98cad..8d1b7fc8bfb4 100644 --- a/client/src/components/ActivityBar/ActivityBar.test.js +++ b/client/src/components/ActivityBar/ActivityBar.test.js @@ -45,7 +45,7 @@ describe("ActivityBar", () => { beforeEach(async () => { const pinia = createTestingPinia({ stubActions: false }); - activityStore = useActivityStore(); + activityStore = useActivityStore("default"); eventStore = useEventStore(); wrapper = shallowMount(mountTarget, { localVue, diff --git a/client/src/components/ActivityBar/ActivityBar.vue b/client/src/components/ActivityBar/ActivityBar.vue index cde4d7b190ad..af8bacca559f 100644 --- a/client/src/components/ActivityBar/ActivityBar.vue +++ b/client/src/components/ActivityBar/ActivityBar.vue @@ -1,11 +1,10 @@ diff --git a/client/src/components/ActivityBar/ActivitySettings.test.js b/client/src/components/ActivityBar/ActivitySettings.test.js index 2fd33b00d02e..fc80db0b912e 100644 --- a/client/src/components/ActivityBar/ActivitySettings.test.js +++ b/client/src/components/ActivityBar/ActivitySettings.test.js @@ -40,13 +40,14 @@ describe("ActivitySettings", () => { beforeEach(async () => { const pinia = createTestingPinia({ stubActions: false }); - activityStore = useActivityStore(); + activityStore = useActivityStore("default"); activityStore.sync(); wrapper = mount(mountTarget, { localVue, pinia, props: { query: "", + activityBarScope: "default", }, stubs: { icon: { template: "
" }, diff --git a/client/src/components/ActivityBar/ActivitySettings.vue b/client/src/components/ActivityBar/ActivitySettings.vue index fe2ecce28dd5..946c78a19965 100644 --- a/client/src/components/ActivityBar/ActivitySettings.vue +++ b/client/src/components/ActivityBar/ActivitySettings.vue @@ -19,10 +19,11 @@ library.add({ }); const props = defineProps<{ + activityBarScope: string; query: string; }>(); -const activityStore = useActivityStore(); +const activityStore = useActivityStore(props.activityBarScope); const { activities } = storeToRefs(activityStore); const activityAction = useActivityAction(); diff --git a/client/src/components/Panels/SettingsPanel.vue b/client/src/components/Panels/SettingsPanel.vue index a30b7329b705..013911c8e9ce 100644 --- a/client/src/components/Panels/SettingsPanel.vue +++ b/client/src/components/Panels/SettingsPanel.vue @@ -10,7 +10,11 @@ import ActivitySettings from "@/components/ActivityBar/ActivitySettings.vue"; import DelayedInput from "@/components/Common/DelayedInput.vue"; import ActivityPanel from "@/components/Panels/ActivityPanel.vue"; -const activityStore = useActivityStore(); +const props = defineProps<{ + activityBarScope: string; +}>(); + +const activityStore = useActivityStore(props.activityBarScope); const confirmRestore = ref(false); const query = ref(""); @@ -37,7 +41,7 @@ function onQuery(newQuery: string) { - + { }); it("initialize store", () => { - const activityStore = useActivityStore(); + const activityStore = useActivityStore("default"); expect(activityStore.getAll().length).toBe(0); activityStore.sync(); expect(activityStore.getAll().length).toBe(1); }); it("add activity", () => { - const activityStore = useActivityStore(); + const activityStore = useActivityStore("default"); activityStore.sync(); const initialActivities = activityStore.getAll(); expect(initialActivities[0]?.visible).toBeTruthy(); @@ -81,7 +81,7 @@ describe("Activity Store", () => { }); it("remove activity", () => { - const activityStore = useActivityStore(); + const activityStore = useActivityStore("default"); activityStore.sync(); const initialActivities = activityStore.getAll(); expect(initialActivities.length).toEqual(1); diff --git a/client/src/stores/activityStore.ts b/client/src/stores/activityStore.ts index 377a68e08964..d9dbce64ec69 100644 --- a/client/src/stores/activityStore.ts +++ b/client/src/stores/activityStore.ts @@ -1,13 +1,14 @@ /** * Stores the Activity Bar state */ +import { watchImmediate } from "@vueuse/core"; +import { computed, type Ref, ref } from "vue"; -import { defineStore } from "pinia"; -import { type Ref } from "vue"; - +import { useHashedUserId } from "@/composables/hashedUserId"; import { useUserLocalStorage } from "@/composables/userLocalStorage"; -import { Activities } from "./activitySetup"; +import { defaultActivities } from "./activitySetup"; +import { defineScopedStore } from "./scopedStore"; export interface Activity { // determine wether an anonymous user can access this activity @@ -34,14 +35,36 @@ export interface Activity { visible: boolean; } -export const useActivityStore = defineStore("activityStore", () => { - const activities: Ref> = useUserLocalStorage("activity-store-activities", []); +export const useActivityStore = defineScopedStore("activityStore", (scope) => { + const activities: Ref> = useUserLocalStorage(`activity-store-activities-${scope}`, []); + + const { hashedUserId } = useHashedUserId(); + + watchImmediate( + () => hashedUserId.value, + () => { + sync(); + } + ); + + const customDefaultActivities = ref(null); + const currentDefaultActivities = computed(() => customDefaultActivities.value ?? defaultActivities); + + function overrideDefaultActivities(activities: Activity[]) { + customDefaultActivities.value = activities; + sync(); + } + + function resetDefaultActivities() { + customDefaultActivities.value = null; + sync(); + } /** * Restores the default activity bar items */ function restore() { - activities.value = Activities.slice(); + activities.value = currentDefaultActivities.value.slice(); } /** @@ -52,12 +75,15 @@ export const useActivityStore = defineStore("activityStore", () => { function sync() { // create a map of built-in activities const activitiesMap: Record = {}; - Activities.forEach((a) => { + + currentDefaultActivities.value.forEach((a) => { activitiesMap[a.id] = a; }); + // create an updated array of activities const newActivities: Array = []; const foundActivity = new Set(); + activities.value.forEach((a: Activity) => { if (a.mutable) { // existing custom activity @@ -66,6 +92,7 @@ export const useActivityStore = defineStore("activityStore", () => { // update existing built-in activity attributes // skip legacy built-in activities const sourceActivity = activitiesMap[a.id]; + if (sourceActivity) { foundActivity.add(a.id); newActivities.push({ @@ -75,12 +102,14 @@ export const useActivityStore = defineStore("activityStore", () => { } } }); + // add new built-in activities - Activities.forEach((a) => { + currentDefaultActivities.value.forEach((a) => { if (!foundActivity.has(a.id)) { newActivities.push({ ...a }); } }); + // update activities stored in local cache only if changes were applied if (JSON.stringify(activities.value) !== JSON.stringify(newActivities)) { activities.value = newActivities; @@ -97,6 +126,7 @@ export const useActivityStore = defineStore("activityStore", () => { function remove(activityId: string) { const findIndex = activities.value.findIndex((a: Activity) => a.id === activityId); + if (findIndex !== -1) { activities.value.splice(findIndex, 1); } @@ -109,5 +139,9 @@ export const useActivityStore = defineStore("activityStore", () => { setAll, restore, sync, + customDefaultActivities, + currentDefaultActivities, + overrideDefaultActivities, + resetDefaultActivities, }; }); From dfb99706bb969c71f3c3792327194dbd8009613c Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:43:07 +0200 Subject: [PATCH 002/131] add activity bar to workflow editor --- .../components/ActivityBar/ActivityBar.vue | 17 ++++++++- .../src/components/Workflow/Editor/Index.vue | 38 +++++++++++++------ .../Workflow/Editor/modules/activities.ts | 31 +++++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 client/src/components/Workflow/Editor/modules/activities.ts diff --git a/client/src/components/ActivityBar/ActivityBar.vue b/client/src/components/ActivityBar/ActivityBar.vue index af8bacca559f..ab5de16f5431 100644 --- a/client/src/components/ActivityBar/ActivityBar.vue +++ b/client/src/components/ActivityBar/ActivityBar.vue @@ -1,4 +1,5 @@ - + + diff --git a/client/src/components/Workflow/List/WorkflowList.vue b/client/src/components/Workflow/List/WorkflowList.vue index 307ebd116404..06def93b18da 100644 --- a/client/src/components/Workflow/List/WorkflowList.vue +++ b/client/src/components/Workflow/List/WorkflowList.vue @@ -13,12 +13,12 @@ import { Toast } from "@/composables/toast"; import { useUserStore } from "@/stores/userStore"; import { rethrowSimple } from "@/utils/simple-error"; +import WorkflowCardList from "./WorkflowCardList.vue"; import FilterMenu from "@/components/Common/FilterMenu.vue"; import Heading from "@/components/Common/Heading.vue"; import ListHeader from "@/components/Common/ListHeader.vue"; import LoginRequired from "@/components/Common/LoginRequired.vue"; import LoadingSpan from "@/components/LoadingSpan.vue"; -import WorkflowCard from "@/components/Workflow/List/WorkflowCard.vue"; import WorkflowListActions from "@/components/Workflow/List/WorkflowListActions.vue"; library.add(faStar, faTrash); @@ -305,20 +305,11 @@ onMounted(() => { - - + @@ -358,32 +349,13 @@ onMounted(() => { } .cards-list { - container: card-list / inline-size; scroll-behavior: smooth; min-height: 150px; + display: flex; + flex-direction: column; overflow-y: auto; overflow-x: hidden; - - .list-view { - width: 100%; - } - - .grid-view { - width: calc(100% / 3); - } - - @container card-list (max-width: #{$breakpoint-xl}) { - .grid-view { - width: calc(100% / 2); - } - } - - @container card-list (max-width: #{$breakpoint-sm}) { - .grid-view { - width: 100%; - } - } } } From 1a55ad87de9a03010c437ecb8205a2b877635cb8 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:57:04 +0200 Subject: [PATCH 014/131] add workflows panel --- .../src/components/Panels/ActivityPanel.vue | 1 + .../src/components/Panels/WorkflowPanel.vue | 145 +++++++++++ .../src/components/Workflow/Editor/Index.vue | 5 +- .../Workflow/List/WorkflowActions.vue | 132 +++++----- .../Workflow/List/WorkflowActionsExtend.vue | 236 ++++++++++++------ .../components/Workflow/List/WorkflowCard.vue | 125 ++-------- .../Workflow/List/WorkflowCardList.vue | 2 + .../Workflow/List/WorkflowIndicators.vue | 52 ++-- .../Workflow/List/useWorkflowActions.ts | 110 ++++++++ .../Workflow/WorkflowInvocationsCount.vue | 4 +- .../components/Workflow/workflows.services.ts | 36 ++- 11 files changed, 576 insertions(+), 272 deletions(-) create mode 100644 client/src/components/Panels/WorkflowPanel.vue create mode 100644 client/src/components/Workflow/List/useWorkflowActions.ts diff --git a/client/src/components/Panels/ActivityPanel.vue b/client/src/components/Panels/ActivityPanel.vue index df4d58a6de64..b0349150a92b 100644 --- a/client/src/components/Panels/ActivityPanel.vue +++ b/client/src/components/Panels/ActivityPanel.vue @@ -84,6 +84,7 @@ const hasGoToAll = computed( flex-direction: column; flex-grow: 1; overflow-y: hidden; + position: relative; button:first-child { background: none; border: none; diff --git a/client/src/components/Panels/WorkflowPanel.vue b/client/src/components/Panels/WorkflowPanel.vue new file mode 100644 index 000000000000..ec20955e0745 --- /dev/null +++ b/client/src/components/Panels/WorkflowPanel.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index 25001ca8c418..0dddc75d8005 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -57,7 +57,8 @@ @onUnhighlight="onUnhighlight" @onRefactor="onAttemptRefactor" @onScrollTo="onScrollTo" /> - + +
@@ -231,6 +232,7 @@ import ActivityBar from "@/components/ActivityBar/ActivityBar.vue"; import MarkdownEditor from "@/components/Markdown/MarkdownEditor.vue"; import FlexPanel from "@/components/Panels/FlexPanel.vue"; import ToolPanel from "@/components/Panels/ToolPanel.vue"; +import WorkflowPanel from "@/components/Panels/WorkflowPanel.vue"; import UndoRedoStack from "@/components/UndoRedo/UndoRedoStack.vue"; import FormDefault from "@/components/Workflow/Editor/Forms/FormDefault.vue"; import FormTool from "@/components/Workflow/Editor/Forms/FormTool.vue"; @@ -255,6 +257,7 @@ export default { WorkflowGraph, FontAwesomeIcon, UndoRedoStack, + WorkflowPanel, }, props: { workflowId: { diff --git a/client/src/components/Workflow/List/WorkflowActions.vue b/client/src/components/Workflow/List/WorkflowActions.vue index 91c5524c1ac4..a80fbcab2f02 100644 --- a/client/src/components/Workflow/List/WorkflowActions.vue +++ b/client/src/components/Workflow/List/WorkflowActions.vue @@ -1,48 +1,48 @@ + + + @click="deleteWorkflow"> Delete diff --git a/client/src/components/Workflow/List/WorkflowActionsExtend.vue b/client/src/components/Workflow/List/WorkflowActionsExtend.vue index ae5878a44e7f..f896fe89ceff 100644 --- a/client/src/components/Workflow/List/WorkflowActionsExtend.vue +++ b/client/src/components/Workflow/List/WorkflowActionsExtend.vue @@ -1,56 +1,54 @@ diff --git a/client/src/components/Workflow/List/useWorkflowActions.ts b/client/src/components/Workflow/List/useWorkflowActions.ts new file mode 100644 index 000000000000..8b0855fb4bc0 --- /dev/null +++ b/client/src/components/Workflow/List/useWorkflowActions.ts @@ -0,0 +1,110 @@ +import { computed, type Ref, ref } from "vue"; + +import { + copyWorkflow as copyWorkflowService, + deleteWorkflow as deleteWorkflowService, + updateWorkflow as updateWorkflowService, +} from "@/components/Workflow/workflows.services"; +import { useConfig } from "@/composables/config"; +import { useConfirmDialog } from "@/composables/confirmDialog"; +import { useToast } from "@/composables/toast"; +import { copy } from "@/utils/clipboard"; +import { withPrefix } from "@/utils/redirect"; +import { getFullAppUrl } from "@/utils/utils"; + +// TODO: replace me with a more accurate type +type Workflow = any; + +export function useWorkflowActions(workflow: Ref, refreshCallback: () => void) { + const toast = useToast(); + const { config } = useConfig() as { config: Record }; + + const bookmarkLoading = ref(false); + + const toggleBookmark = async (checked: boolean) => { + try { + bookmarkLoading.value = true; + + await updateWorkflowService(workflow.value.id, { + show_in_tool_panel: checked, + }); + + toast.info(`Workflow ${checked ? "added to" : "removed from"} bookmarks`); + + if (checked) { + config.stored_workflow_menu_entries.push({ + id: workflow.value.id, + name: workflow.value.name, + }); + } else { + const indexToRemove = config.stored_workflow_menu_entries.findIndex( + (w: Workflow) => w.id === workflow.value.id + ); + config.stored_workflow_menu_entries.splice(indexToRemove, 1); + } + } catch (error) { + toast.error("Failed to update workflow bookmark status"); + } finally { + refreshCallback(); + bookmarkLoading.value = false; + } + }; + + const { confirm } = useConfirmDialog(); + + const deleteWorkflow = async () => { + const confirmed = await confirm("Are you sure you want to delete this workflow?", { + title: "Delete workflow", + okTitle: "Delete", + okVariant: "danger", + }); + + if (confirmed) { + await deleteWorkflowService(workflow.value.id); + refreshCallback(); + toast.info("Workflow deleted"); + } + }; + + const relativeLink = computed(() => { + return `/published/workflow?id=${workflow.value.id}`; + }); + + const fullLink = computed(() => { + return getFullAppUrl(relativeLink.value.substring(1)); + }); + + function copyPublicLink() { + copy(fullLink.value); + toast.success("Link to workflow copied"); + } + + async function copyWorkflow() { + const confirmed = await confirm("Are you sure you want to make a copy of this workflow?", "Copy workflow"); + + if (confirmed) { + await copyWorkflowService(workflow.value.id, workflow.value.owner); + refreshCallback(); + toast.success("Workflow copied"); + } + } + + const downloadUrl = computed(() => { + return withPrefix(`/api/workflows/${workflow.value.id}/download?format=json-download`); + }); + + async function importWorkflow() { + await copyWorkflowService(workflow.value.id, workflow.value.owner); + toast.success("Workflow imported successfully"); + } + + return { + bookmarkLoading, + toggleBookmark, + deleteWorkflow, + copyPublicLink, + copyWorkflow, + importWorkflow, + downloadUrl, + }; +} diff --git a/client/src/components/Workflow/WorkflowInvocationsCount.vue b/client/src/components/Workflow/WorkflowInvocationsCount.vue index 275173da3eb9..e9101f5def7a 100644 --- a/client/src/components/Workflow/WorkflowInvocationsCount.vue +++ b/client/src/components/Workflow/WorkflowInvocationsCount.vue @@ -41,7 +41,7 @@ onMounted(initCounts); - Delete + Delete - View on Dockstore + View on Dockstore - View external link + View external link - Export + Export diff --git a/client/src/components/Workflow/List/WorkflowCard.vue b/client/src/components/Workflow/List/WorkflowCard.vue index 953570a7019c..5fbf335957b2 100644 --- a/client/src/components/Workflow/List/WorkflowCard.vue +++ b/client/src/components/Workflow/List/WorkflowCard.vue @@ -3,7 +3,7 @@ import { faPen } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { BButton, BLink } from "bootstrap-vue"; import { storeToRefs } from "pinia"; -import { computed } from "vue"; +import { computed, ref } from "vue"; import { updateWorkflow } from "@/components/Workflow/workflows.services"; import { useUserStore } from "@/stores/userStore"; @@ -67,10 +67,12 @@ async function onTagsUpdate(tags: string[]) { async function onTagClick(tag: string) { emit("tagClick", tag); } + +const dropdownOpen = ref(false);
diff --git a/client/src/components/Workflow/List/WorkflowActionsExtend.vue b/client/src/components/Workflow/List/WorkflowActionsExtend.vue index f896fe89ceff..9992e4c33bcd 100644 --- a/client/src/components/Workflow/List/WorkflowActionsExtend.vue +++ b/client/src/components/Workflow/List/WorkflowActionsExtend.vue @@ -38,7 +38,7 @@ const props = withDefaults(defineProps(), { const emit = defineEmits<{ (e: "refreshList", overlayLoading?: boolean): void; (e: "insert"): void; - (e: "copySteps"): void; + (e: "insertSteps"): void; }>(); const userStore = useUserStore(); @@ -198,7 +198,7 @@ const { copyPublicLink, copyWorkflow, downloadUrl, importWorkflow } = useWorkflo size="sm" title="Copy steps into workflow" variant="outline-primary" - @click="emit('copySteps')"> + @click="emit('insertSteps')"> diff --git a/client/src/components/Workflow/List/WorkflowCard.vue b/client/src/components/Workflow/List/WorkflowCard.vue index 5fbf335957b2..6bfc210df4f7 100644 --- a/client/src/components/Workflow/List/WorkflowCard.vue +++ b/client/src/components/Workflow/List/WorkflowCard.vue @@ -38,6 +38,8 @@ const emit = defineEmits<{ (e: "updateFilter", key: string, value: any): void; (e: "rename", id: string, name: string): void; (e: "preview", id: string): void; + (e: "insert"): void; + (e: "insertSteps"): void; }>(); const userStore = useUserStore(); @@ -143,7 +145,9 @@ const dropdownOpen = ref(false); :workflow="workflow" :published="publishedView" :editor="editorView" - @refreshList="emit('refreshList', true)" /> + @refreshList="emit('refreshList', true)" + @insert="(...args) => emit('insert', ...args)" + @insertSteps="(...args) => emit('insertSteps', ...args)" />
diff --git a/client/src/components/Workflow/List/WorkflowCardList.vue b/client/src/components/Workflow/List/WorkflowCardList.vue index 86c9ff8eeb73..7b4aaf0cc61f 100644 --- a/client/src/components/Workflow/List/WorkflowCardList.vue +++ b/client/src/components/Workflow/List/WorkflowCardList.vue @@ -23,6 +23,8 @@ const emit = defineEmits<{ (e: "tagClick", tag: string): void; (e: "refreshList", overlayLoading?: boolean, silent?: boolean): void; (e: "updateFilter", key: string, value: any): void; + (e: "insertWorkflow", id: string, name: string): void; + (e: "insertWorkflowSteps", id: string, stepCount: number): void; }>(); const modalOptions = reactive({ @@ -54,6 +56,15 @@ function onPreview(id: string) { modalOptions.preview.id = id; showPreview.value = true; } + +// TODO: clean-up types, as soon as better Workflow type is available +function onInsert(workflow: Workflow) { + emit("insertWorkflow", workflow.id as any, workflow.name as any); +} + +function onInsertSteps(workflow: Workflow) { + emit("insertWorkflowSteps", workflow.id as any, workflow.number_of_steps as any); +} diff --git a/client/src/components/Workflow/List/WorkflowCardList.vue b/client/src/components/Workflow/List/WorkflowCardList.vue index 7b4aaf0cc61f..91a3b9f91151 100644 --- a/client/src/components/Workflow/List/WorkflowCardList.vue +++ b/client/src/components/Workflow/List/WorkflowCardList.vue @@ -117,7 +117,7 @@ function onInsertSteps(workflow: Workflow) { diff --git a/client/src/components/Panels/FlexPanel.vue b/client/src/components/Panels/FlexPanel.vue index 9d0031af395f..a01a8612b097 100644 --- a/client/src/components/Panels/FlexPanel.vue +++ b/client/src/components/Panels/FlexPanel.vue @@ -2,14 +2,9 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faChevronLeft, faChevronRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; -import { useDebounce, useDraggable } from "@vueuse/core"; import { computed, ref, watch } from "vue"; -import { useTimeoutThrottle } from "@/composables/throttle"; - -import { determineWidth } from "./utilities"; - -const { throttle } = useTimeoutThrottle(10); +import DraggableSeparator from "../Common/DraggableSeparator.vue"; library.add(faChevronLeft, faChevronRight); @@ -28,26 +23,18 @@ const props = withDefaults(defineProps(), { defaultWidth: 300, }); -const draggable = ref(null); -const root = ref(null); - const panelWidth = ref(props.defaultWidth); -const show = ref(true); - -const { position, isDragging } = useDraggable(draggable, { - preventDefault: true, - exact: true, -}); -const hoverDraggable = ref(false); -const hoverDraggableDebounced = useDebounce(hoverDraggable, 100); -const showHover = computed(() => (hoverDraggable.value && hoverDraggableDebounced.value) || isDragging.value); +const root = ref(null); +const show = ref(true); const showToggle = ref(false); const hoverToggle = ref(false); -const hoverDraggableOrToggle = computed( - () => (hoverDraggableDebounced.value || hoverToggle.value) && !isDragging.value -); + +const isHoveringDragHandle = ref(false); +const isDragging = ref(false); + +const hoverDraggableOrToggle = computed(() => (isHoveringDragHandle.value || hoverToggle.value) && !isDragging.value); const toggleLinger = 500; const toggleShowDelay = 600; @@ -70,72 +57,6 @@ watch( } ); -/** Watch position changes and adjust width accordingly */ -watch(position, () => { - throttle(() => { - if (!root.value || !draggable.value) { - return; - } - - const rectRoot = root.value.getBoundingClientRect(); - const rectDraggable = draggable.value.getBoundingClientRect(); - panelWidth.value = determineWidth( - rectRoot, - rectDraggable, - props.minWidth, - props.maxWidth, - props.side, - position.value.x - ); - }); -}); - -/** If the `maxWidth` changes, prevent the panel from exceeding it */ -watch( - () => props.maxWidth, - (newVal) => { - if (newVal && panelWidth.value > newVal) { - panelWidth.value = props.maxWidth; - } - }, - { immediate: true } -); - -/** If the `minWidth` changes, ensure the panel width is at least the `minWidth` */ -watch( - () => props.minWidth, - (newVal) => { - if (newVal && panelWidth.value < newVal) { - panelWidth.value = newVal; - } - }, - { immediate: true } -); - -function onKeyLeft() { - if (props.side === "left") { - decreaseWidth(); - } else { - increaseWidth(); - } -} - -function onKeyRight() { - if (props.side === "left") { - increaseWidth(); - } else { - decreaseWidth(); - } -} - -function increaseWidth(by = 50) { - panelWidth.value = Math.min(panelWidth.value + by, props.maxWidth); -} - -function decreaseWidth(by = 50) { - panelWidth.value = Math.max(panelWidth.value - by, props.minWidth); -} - const sideClasses = computed(() => ({ left: props.side === "left", right: props.side === "right", @@ -148,19 +69,9 @@ const sideClasses = computed(() => ({ :id="side" ref="root" class="flex-panel" - :class="{ ...sideClasses, 'show-hover': showHover }" + :class="{ ...sideClasses }" :style="`--width: ${panelWidth}px`"> - +