Skip to content

Commit 984f06d

Browse files
committed
add and remove labels
1 parent 9a636ae commit 984f06d

File tree

7 files changed

+370
-86
lines changed

7 files changed

+370
-86
lines changed

frontend/webEditor/src/diagram/nodes/DfdNodeLabels.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
/** @jsx svg */
22
// eslint-disable-next-line @typescript-eslint/no-unused-vars
3-
import { IActionDispatcher, SModelElementImpl, SNodeImpl, svg, TYPES } from "sprotty";
3+
import { IActionDispatcher, SNodeImpl, svg, TYPES } from "sprotty";
44
import { LabelAssignment, LabelType, LabelTypeValue } from "../../labels/LabelType";
55
import { inject, injectable } from "inversify";
66
import { LabelTypeRegistry } from "../../labels/LabelTypeRegistry";
77
import { calculateTextSize } from "../../utils/TextSize";
88
import { VNode } from "snabbdom";
9+
import { ContainsDfdLabels } from "../../labels/feature";
10+
import { RemoveLabelAssignmentAction } from "../../labels/command";
911

1012
@injectable()
1113
export class DfdNodeLabelRenderer {
@@ -56,9 +58,7 @@ export class DfdNodeLabelRenderer {
5658
const radius = height / 2;
5759

5860
const deleteLabelHandler = () => {
59-
// TODO
60-
/* const action = DeleteLabelAssignmentAction.create(label, node);
61-
this.actionDispatcher.dispatch(action);*/
61+
this.actionDispatcher.dispatch(RemoveLabelAssignmentAction.create(label, node));
6262
};
6363

6464
return (
@@ -124,12 +124,3 @@ export class DfdNodeLabelRenderer {
124124
}
125125
}
126126

127-
export const containsDfdLabelFeature = Symbol("dfd-label-feature");
128-
129-
export interface ContainsDfdLabels extends SModelElementImpl {
130-
labels: LabelAssignment[];
131-
}
132-
133-
export function containsDfdLabels<T extends SModelElementImpl>(element: T): element is T & ContainsDfdLabels {
134-
return element.features?.has(containsDfdLabelFeature) ?? false;
135-
}

frontend/webEditor/src/diagram/nodes/common.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { VNodeStyle } from "snabbdom";
88
import { DfdInputPortImpl } from "../ports/DfdInputPort";
99
import { inject } from "inversify";
1010
import { DfdNodeLabelRenderer } from "./DfdNodeLabels";
11+
import { containsDfdLabelFeature } from "../../labels/feature";
1112

1213
export interface DfdNode extends SNode {
1314
text: string;
@@ -17,7 +18,7 @@ export interface DfdNode extends SNode {
1718
}
1819

1920
export abstract class DfdNodeImpl extends SNodeImpl implements WithEditableLabel {
20-
static readonly DEFAULT_FEATURES = [...SNodeImpl.DEFAULT_FEATURES, withEditLabelFeature/*, containsDfdLabelFeature*/];
21+
static readonly DEFAULT_FEATURES = [...SNodeImpl.DEFAULT_FEATURES, withEditLabelFeature, containsDfdLabelFeature];
2122
static readonly DEFAULT_WIDTH = 50;
2223
static readonly WIDTH_PADDING = 12;
2324
static readonly NODE_COLOR = "var(--color-primary)";
Lines changed: 117 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,178 @@
11
import { AccordionUiExtension } from "../accordionUiExtension";
22
import { UiElementFactory } from "../utils/UiElementFactory";
3-
import { LabelType } from "./LabelType";
3+
import { LabelAssignment, LabelType } from "./LabelType";
44
import { inject } from "inversify";
55
import { LabelTypeRegistry } from "./LabelTypeRegistry";
66

7-
import './labelTypeEditorUi.css'
7+
import "./labelTypeEditorUi.css";
88
import { dynamicallySetInputSize } from "../utils/TextSize";
9+
import { LABEL_ASSIGNMENT_MIME_TYPE } from "./dragAndDrop";
10+
import { AddLabelAssignmentAction } from "./command";
11+
import { IActionDispatcher, TYPES } from "sprotty";
912

1013
export class LabelTypeEditorUi extends AccordionUiExtension {
1114
static readonly ID = "label-type-editor-ui";
12-
private labelSectionContainer?: HTMLElement
15+
private labelSectionContainer?: HTMLElement;
1316

14-
constructor(@inject(LabelTypeRegistry) private labelTypeRegistry: LabelTypeRegistry) {
15-
super('left', 'down')
16-
labelTypeRegistry.onUpdate(() => this.renderLabelTypes())
17+
constructor(@inject(LabelTypeRegistry) private labelTypeRegistry: LabelTypeRegistry, @inject(TYPES.IActionDispatcher) private readonly actionDispatcher: IActionDispatcher) {
18+
super("left", "down");
19+
labelTypeRegistry.onUpdate(() => this.renderLabelTypes());
1720
}
18-
21+
1922
id(): string {
20-
return LabelTypeEditorUi.ID
23+
return LabelTypeEditorUi.ID;
2124
}
2225
containerClass(): string {
23-
return LabelTypeEditorUi.ID
26+
return LabelTypeEditorUi.ID;
2427
}
2528

2629
protected initializeHidableContent(contentElement: HTMLElement) {
27-
const addButton = UiElementFactory.buildAddButton('Label Type')
30+
const addButton = UiElementFactory.buildAddButton("Label Type");
2831

2932
addButton.onclick = () => {
30-
this.labelTypeRegistry.registerLabelType('')
31-
}
33+
this.labelTypeRegistry.registerLabelType("");
34+
};
3235

33-
this.labelSectionContainer = document.createElement('div')
34-
this.renderLabelTypes()
36+
this.labelSectionContainer = document.createElement("div");
37+
this.renderLabelTypes();
3538

36-
contentElement.appendChild(this.labelSectionContainer)
37-
contentElement.appendChild(addButton)
39+
contentElement.appendChild(this.labelSectionContainer);
40+
contentElement.appendChild(addButton);
3841
}
3942
protected initializeHeaderContent(headerElement: HTMLElement) {
40-
headerElement.innerText = 'Label Types'
43+
headerElement.innerText = "Label Types";
4144
}
4245

4346
private renderLabelTypes(): void {
4447
if (!this.labelSectionContainer) {
45-
return
48+
return;
4649
}
47-
this.labelSectionContainer.innerHTML = '';
48-
const labelTypes = this.labelTypeRegistry.getLabelTypes()
50+
this.labelSectionContainer.innerHTML = "";
51+
const labelTypes = this.labelTypeRegistry.getLabelTypes();
4952
for (let i = 0; i < labelTypes.length; i++) {
50-
this.labelSectionContainer.appendChild(this.buildLabelTypeSection(labelTypes[i]))
53+
this.labelSectionContainer.appendChild(this.buildLabelTypeSection(labelTypes[i]));
5154
if (i < labelTypes.length - 1) {
52-
this.labelSectionContainer.appendChild(document.createElement('hr'))
55+
this.labelSectionContainer.appendChild(document.createElement("hr"));
5356
}
5457
}
5558
}
5659

5760
private buildLabelTypeSection(labelType: LabelType): HTMLElement {
58-
const section = document.createElement('div')
59-
section.classList.add('label-section')
60-
61-
const nameInput = document.createElement('input')
62-
nameInput.classList.add('label-type-name')
63-
const deleteButton = UiElementFactory.buildDeleteButton()
64-
const labelTypeValueHolder = document.createElement('div')
65-
labelTypeValueHolder.classList.add('label-type-values')
66-
const addButton = UiElementFactory.buildAddButton('Value')
67-
addButton.classList.add('label-type-value-add')
68-
69-
nameInput.value = labelType.name
70-
nameInput.placeholder = 'Label Type Name'
71-
nameInput.oninput = (e: InputEvent) => this.onInputHandler(e, nameInput)
72-
setTimeout(() => dynamicallySetInputSize(nameInput), 0)
61+
const section = document.createElement("div");
62+
section.classList.add("label-section");
63+
64+
const nameInput = document.createElement("input");
65+
nameInput.classList.add("label-type-name");
66+
const deleteButton = UiElementFactory.buildDeleteButton();
67+
const labelTypeValueHolder = document.createElement("div");
68+
labelTypeValueHolder.classList.add("label-type-values");
69+
const addButton = UiElementFactory.buildAddButton("Value");
70+
addButton.classList.add("label-type-value-add");
71+
72+
nameInput.value = labelType.name;
73+
nameInput.placeholder = "Label Type Name";
74+
nameInput.oninput = (e: InputEvent) => this.onInputHandler(e, nameInput);
75+
setTimeout(() => dynamicallySetInputSize(nameInput), 0);
7376
nameInput.onchange = () => {
74-
this.labelTypeRegistry.updateLabelTypeName(labelType.id, nameInput.value)
75-
}
77+
this.labelTypeRegistry.updateLabelTypeName(labelType.id, nameInput.value);
78+
};
7679

7780
for (let i = 0; i < labelType.values.length; i++) {
78-
labelTypeValueHolder.appendChild(this.buildLabelTypeValue(labelType, i))
81+
labelTypeValueHolder.appendChild(this.buildLabelTypeValue(labelType, i));
7982
}
8083

8184
addButton.onclick = () => {
82-
this.labelTypeRegistry.registerLabelTypeValue(labelType.id, '')
83-
}
85+
this.labelTypeRegistry.registerLabelTypeValue(labelType.id, "");
86+
};
8487

8588
deleteButton.onclick = () => {
86-
this.labelTypeRegistry.unregisterLabelType(labelType.id)
87-
}
89+
this.labelTypeRegistry.unregisterLabelType(labelType.id);
90+
};
8891

89-
section.appendChild(nameInput)
90-
section.appendChild(deleteButton)
91-
section.appendChild(labelTypeValueHolder)
92-
section.appendChild(addButton)
92+
section.appendChild(nameInput);
93+
section.appendChild(deleteButton);
94+
section.appendChild(labelTypeValueHolder);
95+
section.appendChild(addButton);
9396

94-
return section
97+
return section;
9598
}
9699

97100
private buildLabelTypeValue(labelType: LabelType, valueIndex: number) {
98-
const holder = document.createElement('div');
99-
holder.classList.add('label-type-value')
100-
const nameInput = document.createElement('input');
101-
nameInput.classList.add('label-type-value-name')
102-
const deleteButton = UiElementFactory.buildDeleteButton()
101+
const holder = document.createElement("div");
102+
holder.classList.add("label-type-value");
103+
const nameInput = document.createElement("input");
104+
nameInput.classList.add("label-type-value-name");
105+
const deleteButton = UiElementFactory.buildDeleteButton();
103106

104-
const value = labelType.values[valueIndex]
107+
const value = labelType.values[valueIndex];
105108

106-
107-
nameInput.value = value.text
108-
nameInput.placeholder = 'Value'
109-
nameInput.oninput = (e: InputEvent) => this.onInputHandler(e, nameInput)
110-
setTimeout(() => dynamicallySetInputSize(nameInput), 0)
109+
nameInput.value = value.text;
110+
nameInput.placeholder = "Value";
111+
nameInput.oninput = (e: InputEvent) => this.onInputHandler(e, nameInput);
112+
setTimeout(() => dynamicallySetInputSize(nameInput), 0);
111113

112114
nameInput.onchange = () => {
113-
this.labelTypeRegistry.updateLabelTypeValueText(labelType.id, value.id, nameInput.value)
114-
}
115+
this.labelTypeRegistry.updateLabelTypeValueText(labelType.id, value.id, nameInput.value);
116+
};
115117

116118
deleteButton.onclick = () => {
117-
this.labelTypeRegistry.unregisterLabelTypeValue(labelType.id, value.id)
118-
}
119+
this.labelTypeRegistry.unregisterLabelTypeValue(labelType.id, value.id);
120+
};
121+
122+
// Allow dragging to create a label assignment
123+
nameInput.draggable = true;
124+
nameInput.ondragstart = (event) => {
125+
const assignment: LabelAssignment = {
126+
labelTypeId: labelType.id,
127+
labelTypeValueId: value.id,
128+
};
129+
const assignmentJson = JSON.stringify(assignment);
130+
event.dataTransfer?.setData(LABEL_ASSIGNMENT_MIME_TYPE, assignmentJson);
131+
};
132+
133+
// Only edit on double click
134+
nameInput.onclick = () => {
135+
if (nameInput.getAttribute("clicked") === "true") {
136+
return;
137+
}
138+
139+
nameInput.setAttribute("clicked", "true");
140+
setTimeout(() => {
141+
if (nameInput.getAttribute("clicked") === "true") {
142+
this.actionDispatcher.dispatch(
143+
AddLabelAssignmentAction.create({
144+
labelTypeId: labelType.id,
145+
labelTypeValueId: value.id,
146+
}),
147+
);
148+
nameInput.removeAttribute("clicked");
149+
}
150+
}, 500);
151+
};
152+
nameInput.ondblclick = () => {
153+
nameInput.removeAttribute("clicked");
154+
nameInput.focus();
155+
};
156+
nameInput.onfocus = (event) => {
157+
// we check for the single click here, since this gets triggered before the ondblclick event
158+
if (nameInput.getAttribute("clicked") !== "true") {
159+
event.preventDefault();
160+
// the blur needs to occur with a delay, as otherwise chromium browsers prevent the drag
161+
setTimeout(() => {
162+
nameInput.blur();
163+
}, 0);
164+
}
165+
};
119166

120-
holder.appendChild(nameInput)
121-
holder.appendChild(deleteButton)
122-
return holder
167+
holder.appendChild(nameInput);
168+
holder.appendChild(deleteButton);
169+
return holder;
123170
}
124171

125172
private onInputHandler(event: InputEvent, input: HTMLInputElement) {
126173
if (!event.data?.match(/^[a-zA-Z0-9]*$/)) {
127-
event.preventDefault()
174+
event.preventDefault();
128175
}
129-
dynamicallySetInputSize(input)
176+
dynamicallySetInputSize(input);
130177
}
131-
}
178+
}

0 commit comments

Comments
 (0)