Skip to content

Commit

Permalink
Merge pull request #19281 from ElectronicBlueberry/activity-bar-add-n…
Browse files Browse the repository at this point in the history
…ew-workflow-button

[24.2] Activity bar add new workflow button
  • Loading branch information
dannon authored Dec 12, 2024
2 parents 3fec678 + f4e37c9 commit e921a29
Show file tree
Hide file tree
Showing 14 changed files with 155 additions and 49 deletions.
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

0 comments on commit e921a29

Please sign in to comment.