From ff0d12b23018c1317e9d52d6f64f384fdf6524ed Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Fri, 6 Dec 2024 22:41:17 +0100
Subject: [PATCH 1/8] add create new workflow activity
---
client/src/components/Workflow/Editor/Index.vue | 8 ++++++++
.../components/Workflow/Editor/modules/activities.ts | 11 +++++++++++
2 files changed, 19 insertions(+)
diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue
index 357482638c78..b974a4e824fa 100644
--- a/client/src/components/Workflow/Editor/Index.vue
+++ b/client/src/components/Workflow/Editor/Index.vue
@@ -738,6 +738,10 @@ export default {
onSaveAs() {
this.showSaveAsModal = true;
},
+ async createNewWorkflow() {
+ await this.saveOrCreate();
+ this.$router.push("/workflows/edit");
+ },
async saveOrCreate() {
if (this.hasInvalidConnections) {
const confirmed = await this.confirm(
@@ -788,6 +792,10 @@ export default {
if (activityId === "save-workflow-as") {
this.onSaveAs();
}
+
+ if (activityId === "workflow-create") {
+ this.createNewWorkflow();
+ }
},
onAnnotation(nodeId, newAnnotation) {
this.stepActions.setAnnotation(this.steps[nodeId], newAnnotation);
diff --git a/client/src/components/Workflow/Editor/modules/activities.ts b/client/src/components/Workflow/Editor/modules/activities.ts
index 5a5534a4af0e..365bbba411d3 100644
--- a/client/src/components/Workflow/Editor/modules/activities.ts
+++ b/client/src/components/Workflow/Editor/modules/activities.ts
@@ -6,6 +6,7 @@ import {
faMagic,
faPencilAlt,
faPlay,
+ faPlus,
faRecycle,
faSave,
faSignOutAlt,
@@ -126,6 +127,16 @@ export const workflowEditorActivities = [
click: true,
optional: true,
},
+ {
+ description: "Save this workflow and create a new workflow.",
+ icon: faPlus,
+ title: "Create new",
+ id: "workflow-create",
+ tooltip: "Save this workflow and create a new one",
+ visible: true,
+ click: true,
+ optional: true,
+ },
{
description: "Exit the workflow editor and return to the start screen.",
icon: faSignOutAlt,
From f58ba5cc9ddb0e0415c558ef69d9ed0fc773c55e Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Fri, 6 Dec 2024 22:53:58 +0100
Subject: [PATCH 2/8] add create button to activity panel
---
.../src/components/Panels/ActivityPanel.vue | 6 +++--
.../src/components/Panels/WorkflowPanel.vue | 24 +++++++++++++++++++
.../src/components/Workflow/Editor/Index.vue | 3 ++-
3 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/client/src/components/Panels/ActivityPanel.vue b/client/src/components/Panels/ActivityPanel.vue
index d97512c24cc5..e0d3dd646ab8 100644
--- a/client/src/components/Panels/ActivityPanel.vue
+++ b/client/src/components/Panels/ActivityPanel.vue
@@ -1,5 +1,5 @@
+
+
+
@@ -189,4 +208,9 @@ function scrollToTop() {
color: $text-light;
margin: 0.5rem;
}
+
+.create-button:hover {
+ background-color: #c8cfd6;
+ border-color: #c1c9d0;
+}
diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue
index b974a4e824fa..dec4617e26d4 100644
--- a/client/src/components/Workflow/Editor/Index.vue
+++ b/client/src/components/Workflow/Editor/Index.vue
@@ -77,7 +77,8 @@
v-else-if="isActiveSideBar('workflow-editor-workflows')"
:current-workflow-id="id"
@insertWorkflow="onInsertWorkflow"
- @insertWorkflowSteps="onInsertWorkflowSteps" />
+ @insertWorkflowSteps="onInsertWorkflowSteps"
+ @createWorkflow="createNewWorkflow" />
Date: Fri, 6 Dec 2024 23:08:26 +0100
Subject: [PATCH 3/8] reload if window is navigated to/from new workflow
---
client/src/entry/analysis/modules/WorkflowEditor.vue | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/client/src/entry/analysis/modules/WorkflowEditor.vue b/client/src/entry/analysis/modules/WorkflowEditor.vue
index 67634f4a3842..c3010426c026 100644
--- a/client/src/entry/analysis/modules/WorkflowEditor.vue
+++ b/client/src/entry/analysis/modules/WorkflowEditor.vue
@@ -25,6 +25,7 @@ export default {
version: null,
editorConfig: null,
editorReloadKey: 0,
+ previousHistoryLength: 0,
};
},
watch: {
@@ -39,14 +40,18 @@ export default {
async getEditorConfig() {
let reloadEditor = true;
+ const noStoredIds = !this.storedWorkflowId && !this.workflowId;
+ const historyNavigatedForwards = window.history.length > this.previousHistoryLength;
+
// this will only be the case the first time the route updates from a new workflow
- if (!this.storedWorkflowId && !this.workflowId) {
+ if (noStoredIds && historyNavigatedForwards) {
reloadEditor = false;
}
this.storedWorkflowId = Query.get("id");
this.workflowId = Query.get("workflow_id");
this.version = Query.get("version");
+ this.previousHistoryLength = window.history.length;
const params = {};
From 7bd81453d86d8b1f2be95df6caaa450507ee0575 Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Wed, 11 Dec 2024 10:27:40 +0100
Subject: [PATCH 4/8] add custom persistentRef fix type import
---
client/src/components/Panels/utilities.ts | 7 +--
client/src/composables/hashedUserId.ts | 5 +-
client/src/composables/persistentRef.ts | 59 +++++++++++++++++++
client/src/composables/userLocalStorage.ts | 2 +-
client/src/onload/console.js | 5 +-
.../src/stores/workflowNodeInspectorStore.ts | 6 +-
6 files changed, 70 insertions(+), 14 deletions(-)
create mode 100644 client/src/composables/persistentRef.ts
diff --git a/client/src/components/Panels/utilities.ts b/client/src/components/Panels/utilities.ts
index 27dde0d1cf86..38f7f4d335fe 100644
--- a/client/src/components/Panels/utilities.ts
+++ b/client/src/components/Panels/utilities.ts
@@ -4,12 +4,7 @@
*/
import { orderBy } from "lodash";
-import {
- type FilterSettings as ToolFilters,
- type Tool,
- type ToolSection,
- type ToolSectionLabel,
-} from "@/stores/toolStore";
+import type { FilterSettings as ToolFilters, Tool, ToolSection, ToolSectionLabel } from "@/stores/toolStore";
import levenshteinDistance from "@/utils/levenshtein";
const FILTER_KEYS = {
diff --git a/client/src/composables/hashedUserId.ts b/client/src/composables/hashedUserId.ts
index 5d86b2dd8c42..a7f651a3c628 100644
--- a/client/src/composables/hashedUserId.ts
+++ b/client/src/composables/hashedUserId.ts
@@ -1,10 +1,11 @@
-import { useLocalStorage } from "@vueuse/core";
import { storeToRefs } from "pinia";
import { computed, type Ref, ref, watch } from "vue";
import { type AnyUser } from "@/api";
import { useUserStore } from "@/stores/userStore";
+import { usePersistentRef } from "./persistentRef";
+
async function hash32(value: string): Promise {
const valueUtf8 = new TextEncoder().encode(value);
const hashBuffer = await crypto.subtle.digest("SHA-256", valueUtf8);
@@ -43,7 +44,7 @@ export function useHashedUserId(user?: Ref) {
}
// salt the local store, to make a user untraceable by id across different clients
- const localStorageSalt = useLocalStorage("local-storage-salt", createSalt());
+ const localStorageSalt = usePersistentRef("local-storage-salt", createSalt());
watch(
() => currentUser.value,
diff --git a/client/src/composables/persistentRef.ts b/client/src/composables/persistentRef.ts
new file mode 100644
index 000000000000..98c9e3542029
--- /dev/null
+++ b/client/src/composables/persistentRef.ts
@@ -0,0 +1,59 @@
+/*
+ vueuse local storage has some strange, buggy side-effects,
+ so were re-implementing just the parts we need here
+*/
+
+import { type Ref, ref, watch } from "vue";
+
+import { match } from "@/utils/utils";
+
+function stringify(value: unknown): string {
+ if (typeof value !== "object") {
+ return `${value}`;
+ } else {
+ return JSON.stringify(value);
+ }
+}
+
+function parse(value: string, type: "string" | "number" | "boolean" | "object"): T {
+ return match(type, {
+ string: () => value as T,
+ number: () => parseFloat(value) as T,
+ boolean: () => (value.toLowerCase().trim() === "true" ? true : false) as T,
+ object: () => JSON.parse(value),
+ });
+}
+
+export function usePersistentRef(key: string, defaultValue: string): Ref;
+export function usePersistentRef(key: string, defaultValue: number): Ref;
+export function usePersistentRef(key: string, defaultValue: T): Ref;
+export function usePersistentRef(
+ key: string,
+ defaultValue: T
+): Ref {
+ const storageSyncedRef = ref(defaultValue) as Ref;
+
+ const stored = window?.localStorage?.getItem(key);
+
+ if (stored) {
+ try {
+ storageSyncedRef.value = parse(stored, typeof defaultValue as "string" | "number" | "boolean" | "object");
+ } catch (e) {
+ console.error(`Failed to parse value "${stored}" from local storage key "${key}". Resetting key`);
+ window?.localStorage?.removeItem(key);
+ }
+ } else {
+ const stringified = stringify(storageSyncedRef.value);
+ window?.localStorage?.setItem(key, stringified);
+ }
+
+ watch(
+ () => storageSyncedRef.value,
+ () => {
+ const stringified = stringify(storageSyncedRef.value);
+ window?.localStorage?.setItem(key, stringified);
+ }
+ );
+
+ return storageSyncedRef;
+}
diff --git a/client/src/composables/userLocalStorage.ts b/client/src/composables/userLocalStorage.ts
index 619bbe88bfa0..2f179d66dc74 100644
--- a/client/src/composables/userLocalStorage.ts
+++ b/client/src/composables/userLocalStorage.ts
@@ -1,9 +1,9 @@
-import { useLocalStorage } from "@vueuse/core";
import { computed, customRef, type Ref, ref } from "vue";
import { type AnyUser } from "@/api";
import { useHashedUserId } from "./hashedUserId";
+import { usePersistentRef } from "./persistentRef";
/**
* Local storage composable specific to current user.
diff --git a/client/src/onload/console.js b/client/src/onload/console.js
index 3cc2afb908d9..de552ac937c0 100644
--- a/client/src/onload/console.js
+++ b/client/src/onload/console.js
@@ -4,9 +4,10 @@
* using the global functions
* enableDebugging() and disableDebugging()
*/
-import { useLocalStorage } from "@vueuse/core";
import { watch } from "vue";
+import { usePersistentRef } from "@/composables/persistentRef";
+
export function overrideProductionConsole() {
let defaultEnabled = true;
@@ -14,7 +15,7 @@ export function overrideProductionConsole() {
defaultEnabled = false;
}
- const isEnabled = useLocalStorage("console-debugging-enabled", defaultEnabled);
+ const isEnabled = usePersistentRef("console-debugging-enabled", defaultEnabled);
let storedConsole = null;
diff --git a/client/src/stores/workflowNodeInspectorStore.ts b/client/src/stores/workflowNodeInspectorStore.ts
index 6591baf1c8bb..e92683ac853d 100644
--- a/client/src/stores/workflowNodeInspectorStore.ts
+++ b/client/src/stores/workflowNodeInspectorStore.ts
@@ -1,7 +1,7 @@
-import { useLocalStorage } from "@vueuse/core";
import { defineStore } from "pinia";
import { computed, del, ref, set } from "vue";
+import { usePersistentRef } from "@/composables/persistentRef";
import { ensureDefined } from "@/utils/assertions";
import { getShortToolId } from "@/utils/tool";
import { match } from "@/utils/utils";
@@ -26,10 +26,10 @@ function getContentId(step: Step) {
export const useWorkflowNodeInspectorStore = defineStore("workflowNodeInspectorStore", () => {
/** width of the node inspector if no other width is stored */
- const generalWidth = useLocalStorage("workflowNodeInspectorGeneralWidth", 300);
+ const generalWidth = usePersistentRef("workflowNodeInspectorGeneralWidth", 300);
/** maximized state of the node inspector if no other value is stored */
const generalMaximized = ref(false);
- const storedSizes = useLocalStorage>("workflowNodeInspectorStoredSizes", {});
+ const storedSizes = usePersistentRef>("workflowNodeInspectorStoredSizes", {});
const isStored = computed(() => (step: Step) => {
const id = getContentId(step);
From fd042d23359af1d0ba8da7f2c38281b766402a06 Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Wed, 11 Dec 2024 23:09:23 +0100
Subject: [PATCH 5/8] fix wrong id passed to composable
---
.../components/ActivityBar/Items/InteractiveItem.vue | 6 +++---
.../components/ActivityBar/Items/NotificationItem.vue | 4 ++--
.../src/components/ActivityBar/Items/UploadItem.vue | 6 +++---
client/src/composables/persistentRef.ts | 11 ++++++-----
4 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/client/src/components/ActivityBar/Items/InteractiveItem.vue b/client/src/components/ActivityBar/Items/InteractiveItem.vue
index 6b14104c74ab..e6e80ea1d34d 100644
--- a/client/src/components/ActivityBar/Items/InteractiveItem.vue
+++ b/client/src/components/ActivityBar/Items/InteractiveItem.vue
@@ -13,14 +13,14 @@ const totalCount = computed(() => entryPoints.value.length);
export interface Props {
id: string;
+ activityBarId: string;
title: string;
icon: IconDefinition;
isActive: boolean;
to: string;
- activityBarId: string;
}
-defineProps();
+const props = defineProps();
const emit = defineEmits<{
(e: "click"): void;
@@ -37,7 +37,7 @@ const tooltip = computed(() =>
();
+const props = defineProps();
const emit = defineEmits<{
(e: "click"): void;
@@ -33,7 +33,7 @@ const tooltip = computed(() =>
();
+const props = defineProps();
const emit = defineEmits<{
(e: "click"): void;
@@ -41,7 +41,7 @@ function onUploadModal() {
{
const storageSyncedRef = ref(defaultValue) as Ref;
- const stored = window?.localStorage?.getItem(key);
+ const stored = window.localStorage.getItem(key);
if (stored) {
try {
storageSyncedRef.value = parse(stored, typeof defaultValue as "string" | "number" | "boolean" | "object");
} catch (e) {
console.error(`Failed to parse value "${stored}" from local storage key "${key}". Resetting key`);
- window?.localStorage?.removeItem(key);
+ window.localStorage.removeItem(key);
}
} else {
const stringified = stringify(storageSyncedRef.value);
- window?.localStorage?.setItem(key, stringified);
+ window.localStorage.setItem(key, stringified);
}
watch(
() => storageSyncedRef.value,
() => {
const stringified = stringify(storageSyncedRef.value);
- window?.localStorage?.setItem(key, stringified);
- }
+ window.localStorage.setItem(key, stringified);
+ },
+ { deep: true }
);
return storageSyncedRef;
From 36de47d5b09ca2d4a89447a7be8d7ab331ca6160 Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Wed, 11 Dec 2024 23:43:08 +0100
Subject: [PATCH 6/8] fix initial value overwrite
---
client/src/composables/userLocalStorage.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/composables/userLocalStorage.ts b/client/src/composables/userLocalStorage.ts
index 2f179d66dc74..b0aa58b1a9ca 100644
--- a/client/src/composables/userLocalStorage.ts
+++ b/client/src/composables/userLocalStorage.ts
@@ -17,7 +17,7 @@ export function useUserLocalStorage(key: string, initialValue: T, user?: Ref<
const storedRef = computed(() => {
if (hashedUserId.value) {
- return useLocalStorage(`${key}-${hashedUserId.value}`, refSyncedRawValue);
+ return usePersistentRef(`${key}-${hashedUserId.value}`, refSyncedRawValue);
} else {
return ref(initialValue);
}
From ba0f0d41654913258ba2fc0165e9f1deacfbf3f4 Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Thu, 12 Dec 2024 01:27:58 +0100
Subject: [PATCH 7/8] remove computed side-effect and simplify user scoped ref
allow to sync existing ref
---
client/src/composables/persistentRef.ts | 39 +++++++++++++---------
client/src/composables/userLocalStorage.ts | 36 ++++++++------------
2 files changed, 37 insertions(+), 38 deletions(-)
diff --git a/client/src/composables/persistentRef.ts b/client/src/composables/persistentRef.ts
index bd925f7965f7..ab511f74caaa 100644
--- a/client/src/composables/persistentRef.ts
+++ b/client/src/composables/persistentRef.ts
@@ -24,37 +24,44 @@ function parse(value: string, type: "string" | "number" | "boolean" | "object
});
}
-export function usePersistentRef(key: string, defaultValue: string): Ref;
-export function usePersistentRef(key: string, defaultValue: number): Ref;
-export function usePersistentRef(key: string, defaultValue: T): Ref;
-export function usePersistentRef(
- key: string,
- defaultValue: T
-): Ref {
- const storageSyncedRef = ref(defaultValue) as Ref;
-
+export function syncRefToLocalStorage(key: string, refToSync: Ref) {
const stored = window.localStorage.getItem(key);
+ const sync = () => {
+ const stringified = stringify(refToSync.value);
+ window.localStorage.setItem(key, stringified);
+ };
+
if (stored) {
try {
- storageSyncedRef.value = parse(stored, typeof defaultValue as "string" | "number" | "boolean" | "object");
+ refToSync.value = parse(stored, typeof refToSync.value as "string" | "number" | "boolean" | "object");
} catch (e) {
console.error(`Failed to parse value "${stored}" from local storage key "${key}". Resetting key`);
- window.localStorage.removeItem(key);
+ sync();
}
} else {
- const stringified = stringify(storageSyncedRef.value);
- window.localStorage.setItem(key, stringified);
+ sync();
}
watch(
- () => storageSyncedRef.value,
+ () => refToSync.value,
() => {
- const stringified = stringify(storageSyncedRef.value);
- window.localStorage.setItem(key, stringified);
+ sync();
},
{ deep: true }
);
+}
+
+export function usePersistentRef(key: string, defaultValue: string): Ref;
+export function usePersistentRef(key: string, defaultValue: number): Ref;
+export function usePersistentRef(key: string, defaultValue: T): Ref;
+export function usePersistentRef(
+ key: string,
+ defaultValue: T
+): Ref {
+ const storageSyncedRef = ref(defaultValue) as Ref;
+
+ syncRefToLocalStorage(key, storageSyncedRef);
return storageSyncedRef;
}
diff --git a/client/src/composables/userLocalStorage.ts b/client/src/composables/userLocalStorage.ts
index b0aa58b1a9ca..d81dfda55e09 100644
--- a/client/src/composables/userLocalStorage.ts
+++ b/client/src/composables/userLocalStorage.ts
@@ -1,9 +1,10 @@
-import { computed, customRef, type Ref, ref } from "vue";
+import { watchImmediate } from "@vueuse/core";
+import { type Ref, ref } from "vue";
import { type AnyUser } from "@/api";
import { useHashedUserId } from "./hashedUserId";
-import { usePersistentRef } from "./persistentRef";
+import { syncRefToLocalStorage } from "./persistentRef";
/**
* Local storage composable specific to current user.
@@ -13,27 +14,18 @@ import { usePersistentRef } from "./persistentRef";
export function useUserLocalStorage(key: string, initialValue: T, user?: Ref) {
const { hashedUserId } = useHashedUserId(user);
- let refSyncedRawValue = initialValue;
+ const refToSync = ref(initialValue);
+ let hasSynced = false;
- const storedRef = computed(() => {
- if (hashedUserId.value) {
- return usePersistentRef(`${key}-${hashedUserId.value}`, refSyncedRawValue);
- } else {
- return ref(initialValue);
+ watchImmediate(
+ () => hashedUserId.value,
+ () => {
+ if (hashedUserId.value && !hasSynced) {
+ syncRefToLocalStorage(`${key}-${hashedUserId.value}`, refToSync);
+ hasSynced = true;
+ }
}
- });
+ );
- const currentValue = customRef((track, trigger) => ({
- get() {
- track();
- return storedRef.value.value;
- },
- set(newValue) {
- storedRef.value.value = newValue;
- refSyncedRawValue = newValue as T;
- trigger();
- },
- }));
-
- return currentValue;
+ return refToSync;
}
From f4e37c9ed073a737ba9e8ad841c0cc017eec5e32 Mon Sep 17 00:00:00 2001
From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com>
Date: Thu, 12 Dec 2024 04:09:14 +0100
Subject: [PATCH 8/8] fix workflow creation
---
client/src/components/Workflow/Editor/Index.vue | 1 +
client/src/entry/analysis/modules/WorkflowEditor.vue | 12 +++++-------
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue
index dec4617e26d4..287d227488e1 100644
--- a/client/src/components/Workflow/Editor/Index.vue
+++ b/client/src/components/Workflow/Editor/Index.vue
@@ -819,6 +819,7 @@ export default {
const { id, name, number_of_steps } = await this.services.createWorkflow(this);
const message = `Created new workflow '${name}' with ${number_of_steps} steps.`;
this.hasChanges = false;
+ this.$emit("skipNextReload");
await this.routeToWorkflow(id);
Toast.success(message);
} catch (e) {
diff --git a/client/src/entry/analysis/modules/WorkflowEditor.vue b/client/src/entry/analysis/modules/WorkflowEditor.vue
index c3010426c026..b05627cfbdb1 100644
--- a/client/src/entry/analysis/modules/WorkflowEditor.vue
+++ b/client/src/entry/analysis/modules/WorkflowEditor.vue
@@ -7,7 +7,8 @@
:initial-version="editorConfig.initialVersion"
:module-sections="editorConfig.moduleSections"
:workflow-tags="editorConfig.tags"
- @update:confirmation="$emit('update:confirmation', $event)" />
+ @update:confirmation="$emit('update:confirmation', $event)"
+ @skipNextReload="() => (skipNextReload = true)" />