From eedda38902faa9b77347ea2ebbb1ef421548bc07 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:21:09 +0200 Subject: [PATCH 01/43] add database migration ddbdbc40bdc1 --- ...ddbdbc40bdc1_add_workflow_comment_table.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 lib/galaxy/model/migrations/alembic/versions_gxy/ddbdbc40bdc1_add_workflow_comment_table.py diff --git a/lib/galaxy/model/migrations/alembic/versions_gxy/ddbdbc40bdc1_add_workflow_comment_table.py b/lib/galaxy/model/migrations/alembic/versions_gxy/ddbdbc40bdc1_add_workflow_comment_table.py new file mode 100644 index 000000000000..1b297478eac5 --- /dev/null +++ b/lib/galaxy/model/migrations/alembic/versions_gxy/ddbdbc40bdc1_add_workflow_comment_table.py @@ -0,0 +1,61 @@ +"""add workflow_comment table +Revision ID: ddbdbc40bdc1 +Revises: 92fb564c7095 +Create Date: 2023-08-14 13:41:59.442243 +""" +import sqlalchemy as sa + +from galaxy.model.custom_types import ( + JSONType, + MutableJSONType, +) +from galaxy.model.migrations.util import ( + add_column, + create_foreign_key, + create_table, + drop_column, + drop_table, + transaction, +) + +# revision identifiers, used by Alembic. +revision = "ddbdbc40bdc1" +down_revision = "92fb564c7095" +branch_labels = None +depends_on = None + +WORKFLOW_COMMENT_TABLE_NAME = "workflow_comment" +WORKFLOW_STEP_TABLE_NAME = "workflow_step" +PARENT_COMMENT_COLUMN_NAME = "parent_comment_id" + + +def upgrade(): + with transaction(): + create_table( + WORKFLOW_COMMENT_TABLE_NAME, + sa.Column("id", sa.Integer, primary_key=True), + sa.Column("order_index", sa.Integer), + sa.Column("workflow_id", sa.Integer, sa.ForeignKey("workflow.id")), + sa.Column("position", MutableJSONType), + sa.Column("size", JSONType), + sa.Column("type", sa.String(16)), + sa.Column("colour", sa.String(16)), + sa.Column("data", JSONType), + sa.Column(PARENT_COMMENT_COLUMN_NAME, sa.Integer, sa.ForeignKey("workflow_comment.id"), nullable=True), + ) + + add_column(WORKFLOW_STEP_TABLE_NAME, sa.Column(PARENT_COMMENT_COLUMN_NAME, sa.Integer, nullable=True)) + + create_foreign_key( + "foreign_key_parent_comment_id", + WORKFLOW_STEP_TABLE_NAME, + WORKFLOW_COMMENT_TABLE_NAME, + [PARENT_COMMENT_COLUMN_NAME], + ["id"], + ) + + +def downgrade(): + with transaction(): + drop_column(WORKFLOW_STEP_TABLE_NAME, PARENT_COMMENT_COLUMN_NAME) + drop_table(WORKFLOW_COMMENT_TABLE_NAME) From 6d0ebce6d32a69c15b86e0b829f633511d1af996 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 8 Sep 2023 12:40:42 +0200 Subject: [PATCH 02/43] add WorkflowComment class --- lib/galaxy/model/__init__.py | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index b261a9b8337d..7980c143c5c6 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -7383,6 +7383,13 @@ class Workflow(Base, Dictifiable, RepresentById): cascade="all, delete-orphan", lazy=False, ) + comments: List["WorkflowComment"] = relationship( + "WorkflowComment", + back_populates="workflow", + primaryjoin=(lambda: Workflow.id == WorkflowComment.workflow_id), # type: ignore[has-type] + cascade="all, delete-orphan", + lazy=False, + ) parent_workflow_steps = relationship( "WorkflowStep", primaryjoin=(lambda: Workflow.id == WorkflowStep.subworkflow_id), # type: ignore[has-type] @@ -7958,6 +7965,47 @@ def _serialize(self, id_encoder, serialization_options): ) +class WorkflowComment(Base, RepresentById): + """ + WorkflowComment represents an in-editor comment which is no associated to any WorkflowStep. + It is purely decorative, and should not influence how a workflow is ran. + """ + + __tablename__ = "workflow_comment" + + id = Column(Integer, primary_key=True) + workflow_id = Column(Integer, ForeignKey("workflow.id"), index=True, nullable=False) + position = Column(MutableJSONType) + size = Column(JSONType) + type = Column(String(16)) + colour = Column(String(16)) + data = Column(JSONType) + workflow = relationship( + "Workflow", + primaryjoin=(lambda: Workflow.id == WorkflowComment.workflow_id), + back_populates="comments", + ) + + def to_dict(self): + return { + "id": self.id, + "position": self.position, + "size": self.size, + "type": self.type, + "colour": self.colour, + "data": self.data, + } + + def from_dict(dict): + comment = WorkflowComment() + comment.type = dict.get("type", "text") + comment.position = dict.get("position", None) + comment.size = dict.get("size", None) + comment.colour = dict.get("colour", "none") + comment.data = dict.get("data", None) + return comment + + class StoredWorkflowUserShareAssociation(Base, UserShareAssociation): __tablename__ = "stored_workflow_user_share_connection" From 7ebaff1a1b4a36e0b840c16515bf8dd3c261260a Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 11 Sep 2023 16:12:41 +0200 Subject: [PATCH 03/43] expose workflow comments via api add comment normalization logic --- lib/galaxy/managers/workflows.py | 10 ++++++++++ lib/galaxy/webapps/galaxy/api/workflows.py | 2 +- lib/galaxy/workflow/steps.py | 15 ++++++++++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/managers/workflows.py b/lib/galaxy/managers/workflows.py index d25a45a2c105..560c2fff0ea4 100644 --- a/lib/galaxy/managers/workflows.py +++ b/lib/galaxy/managers/workflows.py @@ -796,6 +796,14 @@ def _workflow_from_raw_description( workflow.has_cycles = True workflow.steps = steps + + comments: List[model.WorkflowComment] = [] + for comment_dict in data.get("comments", []): + comment = model.WorkflowComment.from_dict(comment_dict) + comments.append(comment) + + workflow.comments = comments + # we can't reorder subworkflows, as step connections would become invalid if not is_subworkflow: # Order the steps if possible @@ -1119,6 +1127,7 @@ def _workflow_to_dict_editor(self, trans, stored, workflow, tooltip=True, is_sub data["creator"] = workflow.creator_metadata data["source_metadata"] = workflow.source_metadata data["annotation"] = self.get_item_annotation_str(trans.sa_session, trans.user, stored) or "" + data["comments"] = [comment.to_dict() for comment in workflow.comments] output_label_index = set() input_step_types = set(workflow.input_step_types) @@ -1356,6 +1365,7 @@ def _workflow_to_dict_export(self, trans, stored=None, workflow=None, internal=F data["uuid"] = str(workflow.uuid) steps: Dict[int, Dict[str, Any]] = {} data["steps"] = steps + data["comments"] = [comment.to_dict() for comment in workflow.comments] if workflow.reports_config: data["report"] = workflow.reports_config if workflow.creator_metadata: diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py index c90de11b7a03..c8f9864d172e 100644 --- a/lib/galaxy/webapps/galaxy/api/workflows.py +++ b/lib/galaxy/webapps/galaxy/api/workflows.py @@ -530,7 +530,7 @@ def update(self, trans: GalaxyWebTransaction, id, payload, **kwds): with transaction(trans.sa_session): trans.sa_session.commit() - if "steps" in workflow_dict: + if "steps" in workflow_dict or "comments" in workflow_dict: try: workflow_update_options = WorkflowUpdateOptions(**payload) workflow, errors = self.workflow_contents_manager.update_workflow_from_raw_description( diff --git a/lib/galaxy/workflow/steps.py b/lib/galaxy/workflow/steps.py index 1e38574c0c53..8f6699014783 100644 --- a/lib/galaxy/workflow/steps.py +++ b/lib/galaxy/workflow/steps.py @@ -14,7 +14,7 @@ def attach_ordered_steps(workflow): """Attempt to topologically order steps and attach to workflow. If this fails - the workflow contains cycles so it mark it as such. """ - ordered_steps = order_workflow_steps(workflow.steps) + ordered_steps = order_workflow_steps(workflow.steps, workflow.comments) workflow.has_cycles = True if ordered_steps: workflow.has_cycles = False @@ -24,7 +24,7 @@ def attach_ordered_steps(workflow): return workflow.has_cycles -def order_workflow_steps(steps): +def order_workflow_steps(steps, comments): """ Perform topological sort of the steps, return ordered or None """ @@ -38,7 +38,16 @@ def order_workflow_steps(steps): # find minimum left and top values to normalize position min_left = min(step.position["left"] for step in steps) min_top = min(step.position["top"] for step in steps) - # normalize by min_left and min_top + # consider comments to find normalization position + if comments: + min_left_comments = min(comment.position[0] for comment in comments if comment.type != "freehand") + min_top_comments = min(comment.position[1] for comment in comments if comment.type != "freehand") + min_left = min(min_left_comments, min_left) + min_top = min(min_top_comments, min_top) + # normalize comments by min_left and min_top + for comment in comments: + comment.position = [comment.position[0] - min_left, comment.position[1] - min_top] + # normalize steps by min_left and min_top for step in steps: step.position = { "left": step.position["left"] - min_left, From 308fde42e747f54416e238a7ebd9e4b728af45cc Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:24:26 +0200 Subject: [PATCH 04/43] add base client structure for workflow comments --- .../Workflow/Editor/Comments/FrameComment.vue | 7 +++ .../Editor/Comments/FreehandComment.vue | 7 +++ .../Editor/Comments/MarkdownComment.vue | 7 +++ .../Workflow/Editor/Comments/TextComment.vue | 7 +++ .../Editor/Comments/WorkflowComment.vue | 40 +++++++++++++++ client/src/composables/workflowStores.ts | 6 +++ .../src/stores/workflowEditorCommentStore.ts | 49 +++++++++++++++++++ 7 files changed, 123 insertions(+) create mode 100644 client/src/components/Workflow/Editor/Comments/FrameComment.vue create mode 100644 client/src/components/Workflow/Editor/Comments/FreehandComment.vue create mode 100644 client/src/components/Workflow/Editor/Comments/MarkdownComment.vue create mode 100644 client/src/components/Workflow/Editor/Comments/TextComment.vue create mode 100644 client/src/components/Workflow/Editor/Comments/WorkflowComment.vue create mode 100644 client/src/stores/workflowEditorCommentStore.ts diff --git a/client/src/components/Workflow/Editor/Comments/FrameComment.vue b/client/src/components/Workflow/Editor/Comments/FrameComment.vue new file mode 100644 index 000000000000..2b3fba9381ae --- /dev/null +++ b/client/src/components/Workflow/Editor/Comments/FrameComment.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Comments/FreehandComment.vue b/client/src/components/Workflow/Editor/Comments/FreehandComment.vue new file mode 100644 index 000000000000..2b3fba9381ae --- /dev/null +++ b/client/src/components/Workflow/Editor/Comments/FreehandComment.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Comments/MarkdownComment.vue b/client/src/components/Workflow/Editor/Comments/MarkdownComment.vue new file mode 100644 index 000000000000..2b3fba9381ae --- /dev/null +++ b/client/src/components/Workflow/Editor/Comments/MarkdownComment.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Comments/TextComment.vue b/client/src/components/Workflow/Editor/Comments/TextComment.vue new file mode 100644 index 000000000000..2b3fba9381ae --- /dev/null +++ b/client/src/components/Workflow/Editor/Comments/TextComment.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue b/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue new file mode 100644 index 000000000000..05db8ede5cc1 --- /dev/null +++ b/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/client/src/composables/workflowStores.ts b/client/src/composables/workflowStores.ts index 40893840e5b0..54b495406cf1 100644 --- a/client/src/composables/workflowStores.ts +++ b/client/src/composables/workflowStores.ts @@ -1,6 +1,7 @@ import { inject, onScopeDispose, provide } from "vue"; import { useConnectionStore } from "@/stores/workflowConnectionStore"; +import { useWorkflowCommentStore } from "@/stores/workflowEditorCommentStore"; import { useWorkflowStateStore } from "@/stores/workflowEditorStateStore"; import { useWorkflowStepStore } from "@/stores/workflowStepStore"; @@ -19,17 +20,20 @@ export function provideScopedWorkflowStores(workflowId: string) { const connectionStore = useConnectionStore(workflowId); const stateStore = useWorkflowStateStore(workflowId); const stepStore = useWorkflowStepStore(workflowId); + const commentStore = useWorkflowCommentStore(workflowId); onScopeDispose(() => { connectionStore.$dispose(); stateStore.$dispose(); stepStore.$dispose(); + commentStore.$dispose(); }); return { connectionStore, stateStore, stepStore, + commentStore, }; } @@ -54,10 +58,12 @@ export function useWorkflowStores() { const connectionStore = useConnectionStore(workflowId); const stateStore = useWorkflowStateStore(workflowId); const stepStore = useWorkflowStepStore(workflowId); + const commentStore = useWorkflowCommentStore(workflowId); return { connectionStore, stateStore, stepStore, + commentStore, }; } diff --git a/client/src/stores/workflowEditorCommentStore.ts b/client/src/stores/workflowEditorCommentStore.ts new file mode 100644 index 000000000000..39831537b2f3 --- /dev/null +++ b/client/src/stores/workflowEditorCommentStore.ts @@ -0,0 +1,49 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; + +export type WorkflowCommentColour = string; + +export interface BaseWorkflowComment { + id: number; + type: string; + colour: WorkflowCommentColour; + position: [number, number]; + size: [number, number]; + data: unknown; +} + +export interface TextWorkflowComment extends BaseWorkflowComment { + type: "text"; +} + +export interface FrameWorkflowComment extends BaseWorkflowComment { + type: "frame"; +} + +export interface MarkdownWorkflowComment extends BaseWorkflowComment { + type: "markdown"; +} + +export interface FreehandWorkflowComment extends BaseWorkflowComment { + type: "freehand"; +} + +export type WorkflowComment = + | TextWorkflowComment + | FrameWorkflowComment + | MarkdownWorkflowComment + | FreehandWorkflowComment; + +export const useWorkflowCommentStore = (workflowId: string) => { + return defineStore(`workflowCommentStore${workflowId}`, () => { + const commentsRecord = ref>({}); + + function $reset() { + commentsRecord.value = {}; + } + + return { + $reset, + }; + })(); +}; From 86ff434fba2f49d8caeb513c223f91bfbb2bed46 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:48:45 +0200 Subject: [PATCH 05/43] load comments into store --- .../src/components/Workflow/Editor/Index.vue | 13 ++++++-- .../Workflow/Editor/modules/model.ts | 32 +++++++++++++------ .../src/stores/workflowEditorCommentStore.ts | 20 +++++++++++- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/client/src/components/Workflow/Editor/Index.vue b/client/src/components/Workflow/Editor/Index.vue index b28414828809..a3bd86ed3ca7 100644 --- a/client/src/components/Workflow/Editor/Index.vue +++ b/client/src/components/Workflow/Editor/Index.vue @@ -235,8 +235,9 @@ export default { setup(props, { emit }) { const { datatypes, datatypesMapper, datatypesMapperLoading } = useDatatypesMapper(); - const { connectionStore, stepStore, stateStore } = provideScopedWorkflowStores(props.id); + const { connectionStore, stepStore, stateStore, commentStore } = provideScopedWorkflowStores(props.id); + const { comments } = storeToRefs(commentStore); const { getStepIndex, steps } = storeToRefs(stepStore); const { activeNodeId } = storeToRefs(stateStore); const activeStep = computed(() => { @@ -249,7 +250,11 @@ export default { const hasChanges = ref(false); const hasInvalidConnections = computed(() => Object.keys(connectionStore.invalidConnections).length > 0); - stepStore.$subscribe((mutation, state) => { + stepStore.$subscribe((_mutation, _state) => { + hasChanges.value = true; + }); + + commentStore.$subscribe((_mutation, _state) => { hasChanges.value = true; }); @@ -257,17 +262,21 @@ export default { connectionStore.$reset(); stepStore.$reset(); stateStore.$reset(); + commentStore.$reset(); } + onUnmounted(() => { resetStores(); emit("update:confirmation", false); }); + return { connectionStore, hasChanges, hasInvalidConnections, stepStore, steps, + comments, nodeIndex: getStepIndex, datatypes, activeStep, diff --git a/client/src/components/Workflow/Editor/modules/model.ts b/client/src/components/Workflow/Editor/modules/model.ts index 36c5eff4e1ad..c9542aa3ba89 100644 --- a/client/src/components/Workflow/Editor/modules/model.ts +++ b/client/src/components/Workflow/Editor/modules/model.ts @@ -1,3 +1,4 @@ +import { useWorkflowCommentStore, type WorkflowComment } from "@/stores/workflowEditorCommentStore"; import { type ConnectionOutputLink, type Steps, useWorkflowStepStore } from "@/stores/workflowStepStore"; interface Workflow { @@ -8,6 +9,7 @@ interface Workflow { version: number; report: any; steps: Steps; + comments: WorkflowComment[]; } /** @@ -25,11 +27,14 @@ export async function fromSimple( defaultPosition = { top: 0, left: 0 } ) { const stepStore = useWorkflowStepStore(id); - const stepIdOffset = stepStore.getStepIndex + 1; - Object.values(data.steps).forEach((step) => { - // If workflow being copied into another, wipe UUID and let - // Galaxy assign new ones. - if (appendData) { + const commentStore = useWorkflowCommentStore(id); + + // If workflow being copied into another, wipe UUID and let + // Galaxy assign new ones. + if (appendData) { + const stepIdOffset = stepStore.getStepIndex + 1; + + Object.values(data.steps).forEach((step) => { delete step.uuid; if (!step.position) { // Should only happen for manually authored editor content, @@ -54,19 +59,28 @@ export async function fromSimple( }); } }); - } - }); + }); + + data.comments.forEach((comment, index) => { + comment.id = commentStore.highestCommentId + 1 + index; + }); + } + Object.values(data.steps).map((step) => { stepStore.addStep(step); }); + + commentStore.addComments(data.comments, [defaultPosition.left, defaultPosition.top]); } -export function toSimple(workflow: Workflow) { +export function toSimple(workflow: Workflow): Omit { const steps = workflow.steps; const report = workflow.report; const license = workflow.license; const creator = workflow.creator; const annotation = workflow.annotation; const name = workflow.name; - return { steps, report, license, creator, annotation, name }; + const comments = workflow.comments; + + return { steps, report, license, creator, annotation, name, comments }; } diff --git a/client/src/stores/workflowEditorCommentStore.ts b/client/src/stores/workflowEditorCommentStore.ts index 39831537b2f3..e5050c6d5f8e 100644 --- a/client/src/stores/workflowEditorCommentStore.ts +++ b/client/src/stores/workflowEditorCommentStore.ts @@ -1,5 +1,5 @@ import { defineStore } from "pinia"; -import { ref } from "vue"; +import { computed, ref, set } from "vue"; export type WorkflowCommentColour = string; @@ -38,11 +38,29 @@ export const useWorkflowCommentStore = (workflowId: string) => { return defineStore(`workflowCommentStore${workflowId}`, () => { const commentsRecord = ref>({}); + const comments = computed(() => Object.values(commentsRecord.value)); + function $reset() { commentsRecord.value = {}; } + const addComments = (commentsArray: WorkflowComment[], defaultPosition: [number, number] = [0, 0]) => { + commentsArray.forEach((comment) => { + const newComment = structuredClone(comment); + newComment.position[0] += defaultPosition[0]; + newComment.position[1] += defaultPosition[1]; + + set(commentsRecord.value, newComment.id, newComment); + }); + }; + + const highestCommentId = computed(() => comments.value[comments.value.length - 1]?.id ?? -1); + return { + commentsRecord, + comments, + addComments, + highestCommentId, $reset, }; })(); From 65159bc6a093ddcc0800152e3d2f53c762e6c408 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:58:01 +0200 Subject: [PATCH 06/43] add colours and ColourSelector --- .../Editor/Comments/ColourSelector.vue | 97 +++++++++++++++++++ .../Workflow/Editor/Comments/colours.ts | 46 +++++++++ .../src/stores/workflowEditorCommentStore.ts | 4 +- 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 client/src/components/Workflow/Editor/Comments/ColourSelector.vue create mode 100644 client/src/components/Workflow/Editor/Comments/colours.ts diff --git a/client/src/components/Workflow/Editor/Comments/ColourSelector.vue b/client/src/components/Workflow/Editor/Comments/ColourSelector.vue new file mode 100644 index 000000000000..6f7cd650faf2 --- /dev/null +++ b/client/src/components/Workflow/Editor/Comments/ColourSelector.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/client/src/components/Workflow/Editor/Comments/colours.ts b/client/src/components/Workflow/Editor/Comments/colours.ts new file mode 100644 index 000000000000..f2b630419473 --- /dev/null +++ b/client/src/components/Workflow/Editor/Comments/colours.ts @@ -0,0 +1,46 @@ +import { hexToHsluv, hsluvToHex } from "hsluv"; + +export const colours = { + black: "#000", + blue: "#004cec", + turquoise: "#00bbd9", + green: "#319400", + lime: "#68c000", + orange: "#f48400", + yellow: "#fdbd0b", + red: "#e31920", + pink: "#fb00a6", +} as const; + +export type Colour = keyof typeof colours; + +export const brightColours = (() => { + const brighter: Record = {}; + Object.entries(colours).forEach(([name, colour]) => { + const hsluv = hexToHsluv(colour); + let l = hsluv[2]; + l += (100 - l) * 0.5; + hsluv[2] = l; + brighter[name] = hsluvToHex(hsluv); + }); + return brighter as Record; +})(); + +export const brighterColours = (() => { + const brighter: Record = {}; + Object.entries(colours).forEach(([name, colour]) => { + const hsluv = hexToHsluv(colour); + let l = hsluv[2]; + l += (100 - l) * 0.95; + hsluv[2] = l; + brighter[name] = hsluvToHex(hsluv); + }); + return brighter as Record; +})(); + +export const darkenedColours = { + ...colours, + turquoise: "#00a6c0", + lime: "#5eae00", + yellow: "#e9ad00", +}; diff --git a/client/src/stores/workflowEditorCommentStore.ts b/client/src/stores/workflowEditorCommentStore.ts index e5050c6d5f8e..9c5fa68c3e0c 100644 --- a/client/src/stores/workflowEditorCommentStore.ts +++ b/client/src/stores/workflowEditorCommentStore.ts @@ -1,7 +1,9 @@ import { defineStore } from "pinia"; import { computed, ref, set } from "vue"; -export type WorkflowCommentColour = string; +import type { Colour } from "@/components/Workflow/Editor/Comments/colours"; + +export type WorkflowCommentColour = Colour | "none"; export interface BaseWorkflowComment { id: number; From 0f7405f56be94c9869490c0de64ee47a89309b20 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Fri, 8 Sep 2023 16:20:03 +0200 Subject: [PATCH 07/43] add TextComment show Comments on Workflow Graph remove empty text comments on save --- client/icons/all-icons.md | 2 + client/icons/galaxy/textLarger.duotone.svg | 9 + client/icons/galaxy/textSmaller.duotone.svg | 9 + client/package.json | 1 + .../Workflow/Editor/Comments/TextComment.vue | 349 +++++++++++++++++- .../Editor/Comments/WorkflowComment.vue | 50 ++- .../Editor/Comments/_buttonGroup.scss | 23 ++ .../Workflow/Editor/Comments/useResizable.ts | 48 +++ .../Workflow/Editor/Comments/utilities.ts | 12 + .../Workflow/Editor/WorkflowGraph.vue | 13 + .../Workflow/Editor/modules/geometry.ts | 40 ++ .../Workflow/Editor/modules/model.ts | 2 +- .../src/stores/workflowEditorCommentStore.ts | 83 ++++- client/src/utils/utils.ts | 46 +++ client/yarn.lock | 12 + 15 files changed, 692 insertions(+), 7 deletions(-) create mode 100755 client/icons/galaxy/textLarger.duotone.svg create mode 100755 client/icons/galaxy/textSmaller.duotone.svg create mode 100644 client/src/components/Workflow/Editor/Comments/_buttonGroup.scss create mode 100644 client/src/components/Workflow/Editor/Comments/useResizable.ts create mode 100644 client/src/components/Workflow/Editor/Comments/utilities.ts diff --git a/client/icons/all-icons.md b/client/icons/all-icons.md index d240632c9cd6..9549333a3944 100644 --- a/client/icons/all-icons.md +++ b/client/icons/all-icons.md @@ -7,5 +7,7 @@ This file is auto-generated by `build_icons.js`. Manual changes will be lost! --- - `` +- `` +- `` --- diff --git a/client/icons/galaxy/textLarger.duotone.svg b/client/icons/galaxy/textLarger.duotone.svg new file mode 100755 index 000000000000..ad1bfc9904f4 --- /dev/null +++ b/client/icons/galaxy/textLarger.duotone.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/icons/galaxy/textSmaller.duotone.svg b/client/icons/galaxy/textSmaller.duotone.svg new file mode 100755 index 000000000000..58369da0cdb8 --- /dev/null +++ b/client/icons/galaxy/textSmaller.duotone.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/client/package.json b/client/package.json index dcb5b09c60f7..ab08b16fbccc 100644 --- a/client/package.json +++ b/client/package.json @@ -141,6 +141,7 @@ "@pinia/testing": "0.1.3", "@testing-library/jest-dom": "^6.1.4", "@types/d3": "^7.4.2", + "@types/dompurify": "^3.0.2", "@types/jquery": "^3.5.24", "@types/lodash": "^4.14.200", "@types/lodash.isequal": "^4.5.7", diff --git a/client/src/components/Workflow/Editor/Comments/TextComment.vue b/client/src/components/Workflow/Editor/Comments/TextComment.vue index 2b3fba9381ae..1c75e5e87558 100644 --- a/client/src/components/Workflow/Editor/Comments/TextComment.vue +++ b/client/src/components/Workflow/Editor/Comments/TextComment.vue @@ -1,7 +1,350 @@ - + - + diff --git a/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue b/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue index 05db8ede5cc1..b55cb7fa148f 100644 --- a/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue +++ b/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue @@ -1,7 +1,9 @@ From 1caf6b6b61abc2f7638671a27720a4d7ecf355a1 Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:39:22 +0200 Subject: [PATCH 10/43] add FreehandComment --- .../Editor/Comments/FreehandComment.vue | 75 ++++++++++++++++++- .../Editor/Comments/WorkflowComment.vue | 2 +- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/client/src/components/Workflow/Editor/Comments/FreehandComment.vue b/client/src/components/Workflow/Editor/Comments/FreehandComment.vue index 2b3fba9381ae..35a8383bb5d8 100644 --- a/client/src/components/Workflow/Editor/Comments/FreehandComment.vue +++ b/client/src/components/Workflow/Editor/Comments/FreehandComment.vue @@ -1,7 +1,76 @@ - + - + diff --git a/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue b/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue index f36a6e0b5062..827d7a90573f 100644 --- a/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue +++ b/client/src/components/Workflow/Editor/Comments/WorkflowComment.vue @@ -93,7 +93,7 @@ function onSetColour(colour: WorkflowCommentColour) { @pan-by="onPan" @remove="onRemove" @set-colour="onSetColour" /> - + From d84cc0490ddc782cca8340da93a8a08b1657560f Mon Sep 17 00:00:00 2001 From: Laila Los <44241786+ElectronicBlueberry@users.noreply.github.com> Date: Mon, 11 Sep 2023 15:52:55 +0200 Subject: [PATCH 11/43] add ToolBar --- .../Workflow/Editor/Comments/useResizable.ts | 12 +- .../components/Workflow/Editor/Draggable.vue | 27 ++- .../Workflow/Editor/DraggablePan.vue | 8 +- .../Workflow/Editor/Tools/ToolBar.vue | 212 ++++++++++++++++++ .../Workflow/Editor/WorkflowEdges.vue | 2 + .../Workflow/Editor/WorkflowGraph.vue | 2 + client/src/composables/workflowStores.ts | 8 +- .../src/stores/workflowEditorToolbarStore.ts | 23 ++ 8 files changed, 288 insertions(+), 6 deletions(-) create mode 100644 client/src/components/Workflow/Editor/Tools/ToolBar.vue create mode 100644 client/src/stores/workflowEditorToolbarStore.ts diff --git a/client/src/components/Workflow/Editor/Comments/useResizable.ts b/client/src/components/Workflow/Editor/Comments/useResizable.ts index 4c362e3a1155..58dab7a963a1 100644 --- a/client/src/components/Workflow/Editor/Comments/useResizable.ts +++ b/client/src/components/Workflow/Editor/Comments/useResizable.ts @@ -1,6 +1,10 @@ import { useEventListener } from "@vueuse/core"; import { type Ref, watch } from "vue"; +import { useWorkflowStores } from "@/composables/workflowStores"; + +import { vecSnap } from "../modules/geometry"; + /** * Common functionality required for handling a user resizable element. * `resize: both` needs to be set on the elements style, this composable @@ -30,6 +34,8 @@ export function useResizable( let prevWidth = sizeControl.value[0]; let prevHeight = sizeControl.value[1]; + + const { toolbarStore } = useWorkflowStores(); useEventListener(target, "mouseup", () => { const element = target.value; @@ -38,7 +44,11 @@ export function useResizable( const height = element.offsetHeight; if (prevWidth !== width || prevHeight !== height) { - onResized([width, height]); + if (toolbarStore.snapActive) { + onResized(vecSnap([width, height], toolbarStore.snapDistance)); + } else { + onResized([width, height]); + } prevWidth = width; prevHeight = height; diff --git a/client/src/components/Workflow/Editor/Draggable.vue b/client/src/components/Workflow/Editor/Draggable.vue index 79634948bdcd..80e6d71c8b75 100644 --- a/client/src/components/Workflow/Editor/Draggable.vue +++ b/client/src/components/Workflow/Editor/Draggable.vue @@ -1,10 +1,12 @@