Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[24.2] Activity bar add new workflow button #19281

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions client/src/components/ActivityBar/Items/InteractiveItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props>();
const props = defineProps<Props>();

const emit = defineEmits<{
(e: "click"): void;
Expand All @@ -37,7 +37,7 @@ const tooltip = computed(() =>
<ActivityItem
v-if="totalCount > 0"
:id="id"
:activity-bar-id="activityBarId"
:activity-bar-id="props.activityBarId"
:icon="icon"
:indicator="totalCount"
:is-active="isActive"
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/ActivityBar/Items/NotificationItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface Props {
isActive: boolean;
}

defineProps<Props>();
const props = defineProps<Props>();

const emit = defineEmits<{
(e: "click"): void;
Expand All @@ -33,7 +33,7 @@ const tooltip = computed(() =>
<template>
<ActivityItem
:id="id"
:activity-bar-id="activityBarId"
:activity-bar-id="props.activityBarId"
:icon="icon"
:indicator="totalUnreadCount"
:is-active="isActive"
Expand Down
6 changes: 3 additions & 3 deletions client/src/components/ActivityBar/Items/UploadItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import ActivityItem from "@/components/ActivityBar/ActivityItem.vue";

export interface Props {
id: string;
activityBarId: string;
title: string;
icon: IconDefinition;
tooltip: string;
activityBarId: string;
}

defineProps<Props>();
const props = defineProps<Props>();

const emit = defineEmits<{
(e: "click"): void;
Expand All @@ -41,7 +41,7 @@ function onUploadModal() {
<template>
<ActivityItem
:id="id"
:activity-bar-id="activityBarId"
:activity-bar-id="props.activityBarId"
:title="title"
:tooltip="tooltip"
:icon="icon"
Expand Down
6 changes: 4 additions & 2 deletions client/src/components/Panels/ActivityPanel.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { BButton } from "bootstrap-vue";
import { BButton, BButtonGroup } from "bootstrap-vue";
import { computed } from "vue";

interface Props {
Expand All @@ -26,7 +26,9 @@ const hasGoToAll = computed(() => props.goToAllTitle && props.href);
<nav unselectable="on" class="activity-panel-header-top">
<h2 id="activity-panel-heading" v-localize class="activity-panel-heading h-sm">{{ props.title }}</h2>

<slot name="header-buttons" />
<BButtonGroup>
<slot name="header-buttons" />
</BButtonGroup>
</nav>

<slot name="header" class="activity-panel-header-description" />
Expand Down
24 changes: 24 additions & 0 deletions client/src/components/Panels/WorkflowPanel.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<script setup lang="ts">
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { useMemoize, watchImmediate } from "@vueuse/core";
import { BButton } from "bootstrap-vue";
import { computed, ref, watch } from "vue";

import { loadWorkflows, type Workflow } from "@/components/Workflow/workflows.services";
Expand All @@ -19,6 +22,7 @@ const props = defineProps<{
const emit = defineEmits<{
(e: "insertWorkflow", id: string, name: string): void;
(e: "insertWorkflowSteps", id: string, stepCount: number): void;
(e: "createWorkflow"): void;
}>();

const scrollable = ref<HTMLDivElement | null>(null);
Expand Down Expand Up @@ -138,11 +142,26 @@ watch(
function scrollToTop() {
scrollable.value?.scrollTo({ top: 0, behavior: "smooth" });
}

function createNew(event: Event) {
event.preventDefault();
emit("createWorkflow");
}
</script>

<template>
<ActivityPanel title="Workflows">
<template v-slot:header-buttons>
<BButton
v-b-tooltip.hover.top.noninteractive
size="sm"
variant="link"
class="create-button"
title="Create new workflow"
href="/workflows/edit"
@click="createNew">
<FontAwesomeIcon :icon="faPlus" />
</BButton>
<FavoritesButton v-model="showFavorites" tooltip="Show bookmarked" />
</template>

Expand Down Expand Up @@ -189,4 +208,9 @@ function scrollToTop() {
color: $text-light;
margin: 0.5rem;
}

.create-button:hover {
background-color: #c8cfd6;
border-color: #c1c9d0;
}
</style>
7 changes: 1 addition & 6 deletions client/src/components/Panels/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
12 changes: 11 additions & 1 deletion client/src/components/Workflow/Editor/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
v-else-if="isActiveSideBar('workflow-editor-workflows')"
:current-workflow-id="id"
@insertWorkflow="onInsertWorkflow"
@insertWorkflowSteps="onInsertWorkflowSteps" />
@insertWorkflowSteps="onInsertWorkflowSteps"
@createWorkflow="createNewWorkflow" />
<WorkflowAttributes
v-else-if="isActiveSideBar('workflow-editor-attributes')"
:id="id"
Expand Down Expand Up @@ -738,6 +739,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(
Expand Down Expand Up @@ -788,6 +793,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);
Expand All @@ -810,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) {
Expand Down
11 changes: 11 additions & 0 deletions client/src/components/Workflow/Editor/modules/activities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
faMagic,
faPencilAlt,
faPlay,
faPlus,
faRecycle,
faSave,
faSignOutAlt,
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions client/src/composables/hashedUserId.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
const valueUtf8 = new TextEncoder().encode(value);
const hashBuffer = await crypto.subtle.digest("SHA-256", valueUtf8);
Expand Down Expand Up @@ -43,7 +44,7 @@ export function useHashedUserId(user?: Ref<AnyUser>) {
}

// 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,
Expand Down
67 changes: 67 additions & 0 deletions client/src/composables/persistentRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
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<T>(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 syncRefToLocalStorage<T>(key: string, refToSync: Ref<T>) {
const stored = window.localStorage.getItem(key);

const sync = () => {
const stringified = stringify(refToSync.value);
window.localStorage.setItem(key, stringified);
};

if (stored) {
try {
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`);
sync();
}
} else {
sync();
}

watch(
() => refToSync.value,
() => {
sync();
},
{ deep: true }
);
}

export function usePersistentRef(key: string, defaultValue: string): Ref<string>;
export function usePersistentRef(key: string, defaultValue: number): Ref<number>;
export function usePersistentRef<T>(key: string, defaultValue: T): Ref<T>;
export function usePersistentRef<T extends string | number | boolean | object | null>(
key: string,
defaultValue: T
): Ref<T> {
const storageSyncedRef = ref(defaultValue) as Ref<T>;

syncRefToLocalStorage(key, storageSyncedRef);

return storageSyncedRef;
}
36 changes: 14 additions & 22 deletions client/src/composables/userLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useLocalStorage } from "@vueuse/core";
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 { syncRefToLocalStorage } from "./persistentRef";

/**
* Local storage composable specific to current user.
Expand All @@ -13,27 +14,18 @@ import { useHashedUserId } from "./hashedUserId";
export function useUserLocalStorage<T>(key: string, initialValue: T, user?: Ref<AnyUser>) {
const { hashedUserId } = useHashedUserId(user);

let refSyncedRawValue = initialValue;
const refToSync = ref(initialValue);
let hasSynced = false;

const storedRef = computed(() => {
if (hashedUserId.value) {
return useLocalStorage(`${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;
}
9 changes: 6 additions & 3 deletions client/src/entry/analysis/modules/WorkflowEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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)" />
</template>
<script>
import Editor from "components/Workflow/Editor/Index";
Expand All @@ -25,6 +26,7 @@ export default {
version: null,
editorConfig: null,
editorReloadKey: 0,
skipNextReload: false,
};
},
watch: {
Expand All @@ -39,14 +41,15 @@ export default {
async getEditorConfig() {
let reloadEditor = true;

// this will only be the case the first time the route updates from a new workflow
if (!this.storedWorkflowId && !this.workflowId) {
if (this.skipNextReload) {
reloadEditor = false;
this.skipNextReload = false;
}

this.storedWorkflowId = Query.get("id");
this.workflowId = Query.get("workflow_id");
this.version = Query.get("version");
this.previousHistoryLength = window.history.length;

const params = {};

Expand Down
Loading
Loading