From ed57567988aaec6703a52e9edfed5e2cb8ca3aca Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 1 Sep 2023 11:50:20 +0200 Subject: [PATCH 01/24] remove useCurrentUser --- client/src/composables/hashedUserId.ts | 5 ++-- client/src/composables/user.ts | 32 +++----------------------- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/client/src/composables/hashedUserId.ts b/client/src/composables/hashedUserId.ts index ead22c34bcbe..1453423a75af 100644 --- a/client/src/composables/hashedUserId.ts +++ b/client/src/composables/hashedUserId.ts @@ -1,7 +1,8 @@ import { useLocalStorage } from "@vueuse/core"; +import { storeToRefs } from "pinia"; import { computed, ref, watch } from "vue"; -import { useCurrentUser } from "./user"; +import { useUserStore } from "@/stores/userStore"; async function hash32(value: string): Promise { const valueUtf8 = new TextEncoder().encode(value); @@ -31,7 +32,7 @@ let unhashedId: string | null = null; * One way hashed ID of the current User */ export function useHashedUserId() { - const { currentUser } = useCurrentUser(true); + const { currentUser } = storeToRefs(useUserStore()); // salt the local store, to make a user untraceable by id across different clients const localStorageSalt = useLocalStorage("local-storage-salt", createSalt()); diff --git a/client/src/composables/user.ts b/client/src/composables/user.ts index fd16e731159e..ecc6bddadcfa 100644 --- a/client/src/composables/user.ts +++ b/client/src/composables/user.ts @@ -1,34 +1,8 @@ -import type { Ref } from "vue"; -import { computed, onMounted, ref, unref } from "vue"; +import { storeToRefs } from "pinia"; +import { computed, ref } from "vue"; import { useUserStore } from "@/stores/userStore"; -// TODO: support computed for "noFetch" -/** - * composable user store wrapper - * @param noFetch when true, the user will not be fetched from the server - * @param fetchOnce when true, the user will only be fetched from the server if it is not already in the store - * @returns currentUser computed - */ -export function useCurrentUser(noFetch: boolean | Ref = false, fetchOnce: boolean | Ref = false) { - // TODO: add store typing - const userStore = useUserStore(); - const currentUser = computed(() => userStore.currentUser); - const currentFavorites = computed(() => userStore.currentFavorites); - onMounted(() => { - if (!unref(noFetch) && !(Object.keys(currentUser).length > 0 && unref(fetchOnce))) { - userStore.loadUser(); - } - }); - const addFavoriteTool = async (toolId: string) => { - await userStore.addFavoriteTool(toolId); - }; - const removeFavoriteTool = async (toolId: string) => { - await userStore.removeFavoriteTool(toolId); - }; - return { currentUser, currentFavorites, addFavoriteTool, removeFavoriteTool }; -} - export function useCurrentTheme() { const userStore = useUserStore(); const currentTheme = computed(() => userStore.currentTheme); @@ -48,7 +22,7 @@ const localTags = ref([]); * Keeps tracks of the tags the current user has used. */ export function useUserTags() { - const { currentUser } = useCurrentUser(true); + const { currentUser } = storeToRefs(useUserStore()); const userTags = computed(() => { let tags: string[]; if (currentUser.value && !currentUser.value.isAnonymous) { From 554f66d243ca9f14928cfbd97438c5473124a626 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 1 Sep 2023 11:54:40 +0200 Subject: [PATCH 02/24] add embedded query param --- client/src/entry/analysis/App.vue | 41 ++++++++++++++++++++++++++++--- client/src/stores/userStore.ts | 8 ++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/client/src/entry/analysis/App.vue b/client/src/entry/analysis/App.vue index 15e8afd5edd9..d53a37abb86d 100644 --- a/client/src/entry/analysis/App.vue +++ b/client/src/entry/analysis/App.vue @@ -56,7 +56,8 @@ import Modal from "mvc/ui/ui-modal"; import { getAppRoot } from "onload"; import { storeToRefs } from "pinia"; import { withPrefix } from "utils/redirect"; -import { ref } from "vue"; +import { computed, ref, watch } from "vue"; +import { useRoute } from "vue-router/composables"; import { useHistoryStore } from "@/stores/historyStore"; import { useNotificationsStore } from "@/stores/notificationsStore"; @@ -83,8 +84,6 @@ export default { const { currentTheme } = storeToRefs(userStore); const { currentHistory } = storeToRefs(useHistoryStore()); - userStore.loadUser(); - const toastRef = ref(null); setToastComponentRef(toastRef); @@ -94,7 +93,33 @@ export default { const uploadModal = ref(null); setGlobalUploadModal(uploadModal); - return { toastRef, confirmDialogRef, uploadModal, currentTheme, currentHistory }; + const route = useRoute(); + const embedded = computed(() => "embed" in route.query && route.query.embed === "true"); + const showBroadcasts = computed(() => !embedded.value); + const showAlerts = computed(() => !embedded.value); + + watch( + () => embedded.value, + () => { + if (embedded.value) { + userStore.$reset(); + } else { + userStore.loadUser(); + } + }, + { immediate: true } + ); + + return { + toastRef, + confirmDialogRef, + uploadModal, + currentTheme, + currentHistory, + embedded, + showBroadcasts, + showAlerts, + }; }, data() { return { @@ -109,6 +134,10 @@ export default { return fetchMenu(this.config); }, showMasthead() { + if (this.embedded) { + return false; + } + const masthead = this.$route.query.hide_masthead; if (masthead !== undefined) { return masthead.toLowerCase() != "true"; @@ -116,6 +145,10 @@ export default { return true; }, theme() { + if (this.embedded) { + return null; + } + const themeKeys = Object.keys(this.config.themes); if (themeKeys.length > 0) { const foundTheme = themeKeys.includes(this.currentTheme); diff --git a/client/src/stores/userStore.ts b/client/src/stores/userStore.ts index 849cb491f981..8ed03dc9d5f7 100644 --- a/client/src/stores/userStore.ts +++ b/client/src/stores/userStore.ts @@ -37,6 +37,12 @@ export const useUserStore = defineStore("userStore", () => { let loadPromise: Promise | null = null; + function $reset() { + currentUser.value = null; + currentPreferences.value = null; + loadPromise = null; + } + const isAnonymous = computed(() => { return !("email" in (currentUser.value || [])); }); @@ -117,6 +123,7 @@ export const useUserStore = defineStore("userStore", () => { function toggleActivityBar() { showActivityBar.value = !showActivityBar.value; } + function toggleSideBar(currentOpen = "") { toggledSideBar.value = toggledSideBar.value === currentOpen ? "" : currentOpen; } @@ -136,5 +143,6 @@ export const useUserStore = defineStore("userStore", () => { removeFavoriteTool, toggleActivityBar, toggleSideBar, + $reset, }; }); From a69a0ca8f215a0223f54f4737f85dbb4fdacbab9 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:11:07 +0200 Subject: [PATCH 03/24] add optionally passing a user to localStorage --- client/src/composables/hashedUserId.ts | 15 +++++++++++---- client/src/composables/userLocalStorage.ts | 8 +++++--- client/src/stores/userStore.ts | 12 ++++++++---- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/client/src/composables/hashedUserId.ts b/client/src/composables/hashedUserId.ts index 1453423a75af..fcda4cfde8f0 100644 --- a/client/src/composables/hashedUserId.ts +++ b/client/src/composables/hashedUserId.ts @@ -1,8 +1,8 @@ import { useLocalStorage } from "@vueuse/core"; import { storeToRefs } from "pinia"; -import { computed, ref, watch } from "vue"; +import { computed, type Ref, ref, watch } from "vue"; -import { useUserStore } from "@/stores/userStore"; +import { GenericUser, useUserStore } from "@/stores/userStore"; async function hash32(value: string): Promise { const valueUtf8 = new TextEncoder().encode(value); @@ -31,8 +31,15 @@ let unhashedId: string | null = null; /** * One way hashed ID of the current User */ -export function useHashedUserId() { - const { currentUser } = storeToRefs(useUserStore()); +export function useHashedUserId(user?: Ref) { + let currentUser: Ref; + + if (user) { + currentUser = user; + } else { + const { currentUser: currentUserRef } = storeToRefs(useUserStore()); + currentUser = currentUserRef; + } // salt the local store, to make a user untraceable by id across different clients const localStorageSalt = useLocalStorage("local-storage-salt", createSalt()); diff --git a/client/src/composables/userLocalStorage.ts b/client/src/composables/userLocalStorage.ts index 0d53d219ca28..b8a85b91845b 100644 --- a/client/src/composables/userLocalStorage.ts +++ b/client/src/composables/userLocalStorage.ts @@ -1,5 +1,7 @@ import { useLocalStorage } from "@vueuse/core"; -import { computed, customRef, ref } from "vue"; +import { computed, customRef, type Ref, ref } from "vue"; + +import type { GenericUser } from "@/stores/userStore"; import { useHashedUserId } from "./hashedUserId"; @@ -8,8 +10,8 @@ import { useHashedUserId } from "./hashedUserId"; * @param key * @param initialValue */ -export function useUserLocalStorage(key: string, initialValue: T) { - const { hashedUserId } = useHashedUserId(); +export function useUserLocalStorage(key: string, initialValue: T, user?: Ref) { + const { hashedUserId } = useHashedUserId(user); const storedRef = computed(() => { if (hashedUserId.value) { diff --git a/client/src/stores/userStore.ts b/client/src/stores/userStore.ts index 8ed03dc9d5f7..3cfbfdb8ad82 100644 --- a/client/src/stores/userStore.ts +++ b/client/src/stores/userStore.ts @@ -13,28 +13,32 @@ import { type QuotaUsageResponse = components["schemas"]["UserQuotaUsage"]; -interface User extends QuotaUsageResponse { +export interface User extends QuotaUsageResponse { id: string; email: string; tags_used: string[]; isAnonymous: false; } -interface AnonymousUser { +export interface AnonymousUser { isAnonymous: true; } +export type GenericUser = User | AnonymousUser; + interface Preferences { theme: string; favorites: { tools: string[] }; } export const useUserStore = defineStore("userStore", () => { - const toggledSideBar = useUserLocalStorage("user-store-toggled-side-bar", "tools"); - const showActivityBar = useUserLocalStorage("user-store-show-activity-bar", false); const currentUser = ref(null); const currentPreferences = ref(null); + // explicitly pass current User, because userStore might not exist yet + const toggledSideBar = useUserLocalStorage("user-store-toggled-side-bar", "tools", currentUser); + const showActivityBar = useUserLocalStorage("user-store-show-activity-bar", false, currentUser); + let loadPromise: Promise | null = null; function $reset() { From ca20a3c9532b4d714d0c757896d7f6def1d7308c Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:34:20 +0200 Subject: [PATCH 04/24] add multiple query parameters --- .../Published/WorkflowInformation.vue | 5 +- .../Workflow/Published/WorkflowPublished.vue | 52 ++++++++++++++++--- client/src/composables/route.ts | 15 ++++++ client/src/entry/analysis/App.vue | 11 ++-- 4 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 client/src/composables/route.ts diff --git a/client/src/components/Workflow/Published/WorkflowInformation.vue b/client/src/components/Workflow/Published/WorkflowInformation.vue index ba74ff896c47..f923021b5782 100644 --- a/client/src/components/Workflow/Published/WorkflowInformation.vue +++ b/client/src/components/Workflow/Published/WorkflowInformation.vue @@ -24,6 +24,7 @@ interface WorkflowInformation { const props = defineProps<{ workflowInfo: WorkflowInformation; + embedded?: boolean; }>(); const gravatarSource = computed( @@ -51,7 +52,9 @@ const publishedByUser = computed(() => `/workflows/list_published?f-username=${p User Avatar - All published Workflows by {{ workflowInfo.owner }} + + All published Workflows by {{ workflowInfo.owner }} +
diff --git a/client/src/components/Workflow/Published/WorkflowPublished.vue b/client/src/components/Workflow/Published/WorkflowPublished.vue index 7452a4b62b58..8a2d614e1767 100644 --- a/client/src/components/Workflow/Published/WorkflowPublished.vue +++ b/client/src/components/Workflow/Published/WorkflowPublished.vue @@ -7,6 +7,7 @@ import { computed, ref, watch } from "vue"; import { fromSimple } from "@/components/Workflow/Editor/modules/model"; import { useDatatypesMapper } from "@/composables/datatypesMapper"; +import { useRouteQueryBool } from "@/composables/route"; import { usePanels } from "@/composables/usePanels"; import { provideScopedWorkflowStores } from "@/composables/workflowStores"; import { useUserStore } from "@/stores/userStore"; @@ -102,12 +103,19 @@ function logInTitle(title: string) { return title; } } + +const embedded = useRouteQueryBool("embed"); +const showButtons = useRouteQueryBool("buttons", true); +const showAbout = useRouteQueryBool("about", true); +const showHeading = useRouteQueryBool("heading", true); + +const viewUrl = computed(() => withPrefix(`/published/workflow?id=${props.id}`)); diff --git a/client/src/composables/route.ts b/client/src/composables/route.ts new file mode 100644 index 000000000000..fd8f3bddfe96 --- /dev/null +++ b/client/src/composables/route.ts @@ -0,0 +1,15 @@ +import { MaybeComputedRef, resolveUnref } from "@vueuse/core"; +import { computed } from "vue"; +import { useRoute } from "vue-router/composables"; + +/** use a route query parameter as a boolean computed */ +export function useRouteQueryBool( + parameter: MaybeComputedRef, + defaultValue: MaybeComputedRef = false +) { + const route = useRoute(); + return computed(() => { + const p = resolveUnref(parameter); + return p in route.query ? route.query[p] === "true" : resolveUnref(defaultValue); + }); +} diff --git a/client/src/entry/analysis/App.vue b/client/src/entry/analysis/App.vue index d53a37abb86d..927ac0578c8c 100644 --- a/client/src/entry/analysis/App.vue +++ b/client/src/entry/analysis/App.vue @@ -13,7 +13,7 @@ :window-tab="windowTab" @open-url="openUrl" /> @@ -22,7 +22,7 @@ @@ -38,7 +38,7 @@ - +
@@ -57,8 +57,8 @@ import { getAppRoot } from "onload"; import { storeToRefs } from "pinia"; import { withPrefix } from "utils/redirect"; import { computed, ref, watch } from "vue"; -import { useRoute } from "vue-router/composables"; +import { useRouteQueryBool } from "@/composables/route"; import { useHistoryStore } from "@/stores/historyStore"; import { useNotificationsStore } from "@/stores/notificationsStore"; import { useUserStore } from "@/stores/userStore"; @@ -93,8 +93,7 @@ export default { const uploadModal = ref(null); setGlobalUploadModal(uploadModal); - const route = useRoute(); - const embedded = computed(() => "embed" in route.query && route.query.embed === "true"); + const embedded = useRouteQueryBool("embed"); const showBroadcasts = computed(() => !embedded.value); const showAlerts = computed(() => !embedded.value); From 07b28b2bda20e820340b45ce35a54da0069a221a Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 1 Sep 2023 13:48:58 +0200 Subject: [PATCH 05/24] add parameters for position and zoom --- .../Workflow/Editor/WorkflowGraph.vue | 13 +++++++++---- .../Workflow/Published/WorkflowPublished.vue | 18 ++++++++++++++++-- client/src/composables/route.ts | 13 +++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/client/src/components/Workflow/Editor/WorkflowGraph.vue b/client/src/components/Workflow/Editor/WorkflowGraph.vue index 1af61b9fe7db..e6833b3bcde8 100644 --- a/client/src/components/Workflow/Editor/WorkflowGraph.vue +++ b/client/src/components/Workflow/Editor/WorkflowGraph.vue @@ -85,6 +85,7 @@ const props = defineProps({ highlightId: { type: Number as PropType, default: null }, scrollToId: { type: Number as PropType, default: null }, readonly: { type: Boolean, default: false }, + initialPosition: { type: Object as PropType<{ x: number; y: number }>, default: () => ({ x: 50, y: 20 }) }, }); const { stateStore, stepStore } = useWorkflowStores(); @@ -93,10 +94,14 @@ const canvas: Ref = ref(null); const elementBounding = useElementBounding(canvas, { windowResize: false, windowScroll: false }); const scroll = useScroll(canvas); -const { transform, panBy, setZoom, moveTo } = useD3Zoom(scale.value, minZoom, maxZoom, canvas, scroll, { - x: 50, - y: 20, -}); +const { transform, panBy, setZoom, moveTo } = useD3Zoom( + scale.value, + minZoom, + maxZoom, + canvas, + scroll, + props.initialPosition +); const { viewportBoundingBox } = useViewportBoundingBox(elementBounding, scale, transform); const isDragging = ref(false); diff --git a/client/src/components/Workflow/Published/WorkflowPublished.vue b/client/src/components/Workflow/Published/WorkflowPublished.vue index 8a2d614e1767..ca6a2611fe95 100644 --- a/client/src/components/Workflow/Published/WorkflowPublished.vue +++ b/client/src/components/Workflow/Published/WorkflowPublished.vue @@ -7,7 +7,7 @@ import { computed, ref, watch } from "vue"; import { fromSimple } from "@/components/Workflow/Editor/modules/model"; import { useDatatypesMapper } from "@/composables/datatypesMapper"; -import { useRouteQueryBool } from "@/composables/route"; +import { useRouteQueryBool, useRouteQueryNumber } from "@/composables/route"; import { usePanels } from "@/composables/usePanels"; import { provideScopedWorkflowStores } from "@/composables/workflowStores"; import { useUserStore } from "@/stores/userStore"; @@ -49,7 +49,13 @@ const { datatypesMapper } = useDatatypesMapper(); const { stateStore } = provideScopedWorkflowStores(props.id); -stateStore.setScale(0.75); +const defaultScale = useRouteQueryNumber("zoom", 0.75); + +watch( + () => defaultScale.value, + () => stateStore.setScale(defaultScale.value), + { immediate: true } +); watch( () => props.id, @@ -108,6 +114,13 @@ const embedded = useRouteQueryBool("embed"); const showButtons = useRouteQueryBool("buttons", true); const showAbout = useRouteQueryBool("about", true); const showHeading = useRouteQueryBool("heading", true); +const initialX = useRouteQueryNumber("initialX", -20); +const initialY = useRouteQueryNumber("initialY", -20); + +const initialPosition = computed(() => ({ + x: -initialX.value * defaultScale.value, + y: -initialY.value * defaultScale.value, +})); const viewUrl = computed(() => withPrefix(`/published/workflow?id=${props.id}`)); @@ -192,6 +205,7 @@ const viewUrl = computed(() => withPrefix(`/published/workflow?id=${props.id}`)) v-if="workflow && datatypesMapper" :steps="workflow.steps" :datatypes-mapper="datatypesMapper" + :initial-position="initialPosition" readonly /> diff --git a/client/src/composables/route.ts b/client/src/composables/route.ts index fd8f3bddfe96..36b5bace0d9b 100644 --- a/client/src/composables/route.ts +++ b/client/src/composables/route.ts @@ -13,3 +13,16 @@ export function useRouteQueryBool( return p in route.query ? route.query[p] === "true" : resolveUnref(defaultValue); }); } + +export function useRouteQueryNumber(parameter: MaybeComputedRef, defaultValue: MaybeComputedRef = 1) { + const route = useRoute(); + return computed(() => { + const p = resolveUnref(parameter); + if (p in route.query) { + const v = route.query[p]; + return typeof v === "string" ? parseFloat(v) : resolveUnref(defaultValue); + } else { + return resolveUnref(defaultValue); + } + }); +} From 4b24b63571ec5911c8d38c4b49bfff950fe90034 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 1 Sep 2023 17:40:13 +0200 Subject: [PATCH 06/24] move errors to separate component --- .../src/components/Sharing/ErrorMessage.vue | 31 ------ .../src/components/Sharing/ErrorMessages.vue | 46 +++++++++ client/src/components/Sharing/Sharing.vue | 96 +++++++++++-------- 3 files changed, 104 insertions(+), 69 deletions(-) delete mode 100644 client/src/components/Sharing/ErrorMessage.vue create mode 100644 client/src/components/Sharing/ErrorMessages.vue diff --git a/client/src/components/Sharing/ErrorMessage.vue b/client/src/components/Sharing/ErrorMessage.vue deleted file mode 100644 index dd5e92a3c2f0..000000000000 --- a/client/src/components/Sharing/ErrorMessage.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/client/src/components/Sharing/ErrorMessages.vue b/client/src/components/Sharing/ErrorMessages.vue new file mode 100644 index 000000000000..954a7d34db2c --- /dev/null +++ b/client/src/components/Sharing/ErrorMessages.vue @@ -0,0 +1,46 @@ + + + diff --git a/client/src/components/Sharing/Sharing.vue b/client/src/components/Sharing/Sharing.vue index 16b1be21a86d..c232f7225e34 100644 --- a/client/src/components/Sharing/Sharing.vue +++ b/client/src/components/Sharing/Sharing.vue @@ -1,14 +1,11 @@ diff --git a/client/src/components/Sharing/item.ts b/client/src/components/Sharing/item.ts index eb5dee581703..10f805384340 100644 --- a/client/src/components/Sharing/item.ts +++ b/client/src/components/Sharing/item.ts @@ -5,8 +5,10 @@ export interface Item { published: boolean; users_shared_with: Array<{ email: string }>; extra?: { - cannot_change: unknown[]; - can_change: unknown[]; + cannot_change: Array<{ id: string; name: string }>; + can_change: Array<{ id: string; name: string }>; can_share: boolean; }; } + +export type ShareOption = "make_public" | "make_accessible_to_shared" | "no_changes"; From 659927bcaf94a654945af8a0a52af25f791ac12e Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Wed, 6 Sep 2023 12:35:54 +0200 Subject: [PATCH 12/24] move permissions change to modal --- client/src/components/Sharing/Sharing.vue | 11 +- client/src/components/Sharing/UserSharing.vue | 164 ++++++++++-------- client/src/components/Sharing/item.ts | 5 +- 3 files changed, 97 insertions(+), 83 deletions(-) diff --git a/client/src/components/Sharing/Sharing.vue b/client/src/components/Sharing/Sharing.vue index 20db536a7e0b..265a4f4fd6ce 100644 --- a/client/src/components/Sharing/Sharing.vue +++ b/client/src/components/Sharing/Sharing.vue @@ -50,7 +50,8 @@ :item="item" :model-class="modelClass" @share="(users, option) => setSharing(actions.share_with, users, option)" - @error="onError" /> + @error="onError" + @cancel="getSharing" /> item.value.username_and_slug, (value) => { - const index = value.lastIndexOf("/"); + if (value) { + const index = value.lastIndexOf("/"); - itemUrl.prefix = itemRoot.value + value.substring(0, index + 1); - itemUrl.slug = value.substring(index + 1); + itemUrl.prefix = itemRoot.value + value.substring(0, index + 1); + itemUrl.slug = value.substring(index + 1); + } }, { immediate: true } ); diff --git a/client/src/components/Sharing/UserSharing.vue b/client/src/components/Sharing/UserSharing.vue index 5897aec3dc98..b707900b4a6a 100644 --- a/client/src/components/Sharing/UserSharing.vue +++ b/client/src/components/Sharing/UserSharing.vue @@ -1,6 +1,16 @@ @@ -276,3 +282,9 @@ function onShareAnyway() { } } + + diff --git a/client/src/components/Sharing/item.ts b/client/src/components/Sharing/item.ts index 10f805384340..d8dc5efaacec 100644 --- a/client/src/components/Sharing/item.ts +++ b/client/src/components/Sharing/item.ts @@ -1,13 +1,12 @@ export interface Item { title: string; - username_and_slug: `${string}/${string}`; + username_and_slug?: `${string}/${string}`; importable: boolean; published: boolean; - users_shared_with: Array<{ email: string }>; + users_shared_with?: Array<{ email: string }>; extra?: { cannot_change: Array<{ id: string; name: string }>; can_change: Array<{ id: string; name: string }>; - can_share: boolean; }; } From 5c57e53b5500ddb455cacdf9557a7be2b90d8223 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Wed, 6 Sep 2023 13:45:40 +0200 Subject: [PATCH 13/24] convert sharing to typescript --- client/src/components/Sharing/Sharing.vue | 758 ++++++------------ client/src/components/Sharing/UserSharing.vue | 18 +- client/src/components/Sharing/item.ts | 1 + 3 files changed, 234 insertions(+), 543 deletions(-) diff --git a/client/src/components/Sharing/Sharing.vue b/client/src/components/Sharing/Sharing.vue index 265a4f4fd6ce..e32e2bbb7de4 100644 --- a/client/src/components/Sharing/Sharing.vue +++ b/client/src/components/Sharing/Sharing.vue @@ -1,3 +1,224 @@ + + - - - + diff --git a/client/src/components/Sharing/UserSharing.vue b/client/src/components/Sharing/UserSharing.vue index b707900b4a6a..905a7ac10e74 100644 --- a/client/src/components/Sharing/UserSharing.vue +++ b/client/src/components/Sharing/UserSharing.vue @@ -12,7 +12,7 @@ import { BModal, } from "bootstrap-vue"; import { storeToRefs } from "pinia"; -import { computed, ref, watch } from "vue"; +import { computed, ref } from "vue"; import Multiselect from "vue-multiselect"; import { useConfig } from "@/composables/config"; @@ -49,16 +49,12 @@ const permissionsChangeRequired = computed(() => { const userIsAdmin = computed(() => currentUser.value && "is_admin" in currentUser.value && currentUser.value.is_admin); const exposeEmails = computed(() => config.value.expose_user_email || userIsAdmin.value); -const sharingCandidates = ref>([]); +const sharingCandidates = ref>([...(props.item.users_shared_with ?? [])]); const sharingCandidatesAsEmails = computed(() => sharingCandidates.value.map(({ email }) => email)); -watch( - () => props.item.users_shared_with, - (users) => { - sharingCandidates.value = [...(users ?? [])]; - }, - { immediate: true } -); +function reset() { + sharingCandidates.value = [...(props.item.users_shared_with ?? [])]; +} const userOptions = ref>([]); const currentSearch = ref(""); @@ -135,6 +131,10 @@ const selectedSharingOption = ref("make_public"); function onUpdatePermissions() { emit("share", sharingCandidatesAsEmails.value, selectedSharingOption.value); } + +defineExpose({ + reset, +}); - - diff --git a/client/src/components/Sharing/SharingPage.vue b/client/src/components/Sharing/SharingPage.vue index e9a007a1a290..3626deb08d49 100644 --- a/client/src/components/Sharing/SharingPage.vue +++ b/client/src/components/Sharing/SharingPage.vue @@ -2,6 +2,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faCaretDown, faCaretUp, faCopy, faEdit, faUserPlus, faUserSlash } from "@fortawesome/free-solid-svg-icons"; import axios from "axios"; +import { BFormCheckbox } from "bootstrap-vue"; import { computed, nextTick, reactive, ref, watch } from "vue"; import { getGalaxyInstance } from "@/app"; @@ -12,6 +13,7 @@ import { errorMessageAsString } from "@/utils/simple-error"; import type { Item, ShareOption } from "./item"; import EditableUrl from "./EditableUrl.vue"; +import WorkflowEmbed from "./Embeds/WorkflowEmbed.vue"; import ErrorMessages from "./ErrorMessages.vue"; import UserSharing from "./UserSharing.vue"; import Heading from "@/components/Common/Heading.vue"; @@ -217,10 +219,12 @@ async function setUsername() { }) .catch(onError); } + +const embedable = computed(() => item.value.importable && props.modelClass.toLocaleLowerCase() === "workflow"); - + From 0c68f175ca276639acb365b84d0b65d9022a7363 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Thu, 7 Sep 2023 11:30:47 +0200 Subject: [PATCH 19/24] move query params to params --- .../Sharing/Embeds/WorkflowEmbed.vue | 9 ++- .../Workflow/Published/WorkflowPublished.vue | 77 ++++++++++++------- client/src/entry/analysis/router.js | 12 ++- client/src/utils/utils.ts | 4 + 4 files changed, 71 insertions(+), 31 deletions(-) diff --git a/client/src/components/Sharing/Embeds/WorkflowEmbed.vue b/client/src/components/Sharing/Embeds/WorkflowEmbed.vue index 47fb3701d312..77487de07dc7 100644 --- a/client/src/components/Sharing/Embeds/WorkflowEmbed.vue +++ b/client/src/components/Sharing/Embeds/WorkflowEmbed.vue @@ -6,8 +6,8 @@ import { useDebounce } from "@vueuse/core"; import { BButton, BFormCheckbox, BFormInput, BInputGroup, BInputGroupAppend } from "bootstrap-vue"; import { computed, reactive, ref } from "vue"; +import { getAppRoot } from "@/onload/loadConfig"; import { copy } from "@/utils/clipboard"; -import { withPrefix } from "@/utils/redirect"; import ZoomControl from "@/components/Workflow/Editor/ZoomControl.vue"; @@ -25,8 +25,13 @@ const settings = reactive({ zoom: 1, }); +const root = computed(() => { + const port = window.location.port ? `:${window.location.port}` : ""; + return `${window.location.protocol}//${window.location.hostname}${port}${getAppRoot()}`; +}); + const embedUrl = computed(() => { - let url = withPrefix(`/published/workflow?id=${props.id}&embed=true`); + let url = `${root.value}published/workflow?id=${props.id}&embed=true`; url += `&buttons=${settings.buttons}`; url += `&about=${settings.about}`; url += `&heading=${settings.heading}`; diff --git a/client/src/components/Workflow/Published/WorkflowPublished.vue b/client/src/components/Workflow/Published/WorkflowPublished.vue index ca6a2611fe95..cccab2f44592 100644 --- a/client/src/components/Workflow/Published/WorkflowPublished.vue +++ b/client/src/components/Workflow/Published/WorkflowPublished.vue @@ -7,7 +7,6 @@ import { computed, ref, watch } from "vue"; import { fromSimple } from "@/components/Workflow/Editor/modules/model"; import { useDatatypesMapper } from "@/composables/datatypesMapper"; -import { useRouteQueryBool, useRouteQueryNumber } from "@/composables/route"; import { usePanels } from "@/composables/usePanels"; import { provideScopedWorkflowStores } from "@/composables/workflowStores"; import { useUserStore } from "@/stores/userStore"; @@ -24,9 +23,40 @@ import WorkflowGraph from "@/components/Workflow/Editor/WorkflowGraph.vue"; library.add(faSpinner, faUser, faBuilding, faPlay, faEdit, faDownload); -const props = defineProps<{ - id: string; -}>(); +const props = defineProps({ + id: { + type: String, + required: true, + }, + zoom: { + type: Number, + default: 0.75, + }, + embed: { + type: Boolean, + default: false, + }, + showButtons: { + type: Boolean, + default: true, + }, + showAbout: { + type: Boolean, + default: true, + }, + showHeading: { + type: Boolean, + default: true, + }, + initialX: { + type: Number, + default: -20, + }, + initialY: { + type: Number, + default: -20, + }, +}); const workflowInfo = ref< | { @@ -49,11 +79,9 @@ const { datatypesMapper } = useDatatypesMapper(); const { stateStore } = provideScopedWorkflowStores(props.id); -const defaultScale = useRouteQueryNumber("zoom", 0.75); - watch( - () => defaultScale.value, - () => stateStore.setScale(defaultScale.value), + () => props.zoom, + () => stateStore.setScale(props.zoom), { immediate: true } ); @@ -110,16 +138,9 @@ function logInTitle(title: string) { } } -const embedded = useRouteQueryBool("embed"); -const showButtons = useRouteQueryBool("buttons", true); -const showAbout = useRouteQueryBool("about", true); -const showHeading = useRouteQueryBool("heading", true); -const initialX = useRouteQueryNumber("initialX", -20); -const initialY = useRouteQueryNumber("initialY", -20); - const initialPosition = computed(() => ({ - x: -initialX.value * defaultScale.value, - y: -initialY.value * defaultScale.value, + x: -props.initialX * props.zoom, + y: -props.initialY * props.zoom, })); const viewUrl = computed(() => withPrefix(`/published/workflow?id=${props.id}`)); @@ -127,8 +148,8 @@ const viewUrl = computed(() => withPrefix(`/published/workflow?id=${props.id}`))