Skip to content
This repository was archived by the owner on Oct 21, 2025. It is now read-only.

Commit db4d84d

Browse files
committed
add command and basic testing setup
1 parent ed5b5c8 commit db4d84d

5 files changed

Lines changed: 227 additions & 2 deletions

File tree

src/features/labels/commands.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {
44
CommandExecutionContext,
55
CommandReturn,
66
ISnapper,
7+
isSelected,
8+
SChildElementImpl,
79
SModelElementImpl,
810
SNodeImpl,
911
SParentElementImpl,
@@ -314,3 +316,171 @@ export class DeleteLabelTypeCommand extends Command {
314316
return this.execute(context);
315317
}
316318
}
319+
320+
interface LabelToAllOrSelectionAction extends Action {
321+
labelAssignment: LabelAssignment;
322+
}
323+
324+
abstract class LabelToAllOrSelectionCommand extends Command {
325+
@inject(EditorModeController)
326+
@optional()
327+
protected readonly editorModeController?: EditorModeController;
328+
329+
protected elements?: SModelElementImpl[];
330+
331+
constructor(
332+
@inject(TYPES.Action) protected action: LabelToAllOrSelectionAction,
333+
@inject(TYPES.ISnapper) protected snapper: ISnapper,
334+
) {
335+
super();
336+
}
337+
338+
protected fetchElements(context: CommandExecutionContext): SModelElementImpl[] {
339+
if (this.editorModeController?.isReadOnly()) {
340+
return [];
341+
}
342+
343+
const allElements = getAllElements(context.root.children);
344+
const selectedElements = allElements.filter((element) => isSelected(element));
345+
return selectedElements.length === 0 ? allElements : selectedElements;
346+
}
347+
348+
protected addLabel(context: CommandExecutionContext) {
349+
if (this.editorModeController?.isReadOnly()) {
350+
return context.root;
351+
}
352+
353+
if (this.elements === undefined) {
354+
this.elements = this.fetchElements(context);
355+
}
356+
357+
this.elements.forEach((element) => {
358+
if (containsDfdLabels(element)) {
359+
const hasBeenAdded =
360+
element.labels.find((as) => {
361+
return (
362+
as.labelTypeId === this.action.labelAssignment.labelTypeId &&
363+
as.labelTypeValueId === this.action.labelAssignment.labelTypeValueId
364+
);
365+
}) !== undefined;
366+
if (!hasBeenAdded) {
367+
element.labels.push(this.action.labelAssignment);
368+
if (element instanceof SNodeImpl) {
369+
snapPortsOfNode(element, this.snapper);
370+
}
371+
}
372+
}
373+
});
374+
375+
return context.root;
376+
}
377+
378+
protected removeLabel(context: CommandExecutionContext) {
379+
if (this.editorModeController?.isReadOnly()) {
380+
return context.root;
381+
}
382+
383+
if (this.elements === undefined) {
384+
this.elements = this.fetchElements(context);
385+
}
386+
387+
this.elements.forEach((element) => {
388+
if (containsDfdLabels(element)) {
389+
const labels = element.labels;
390+
const idx = labels.findIndex(
391+
(l) =>
392+
l.labelTypeId == this.action.labelAssignment.labelTypeId &&
393+
l.labelTypeValueId == this.action.labelAssignment.labelTypeValueId,
394+
);
395+
if (idx >= 0) {
396+
labels.splice(idx, 1);
397+
if (element instanceof SNodeImpl) {
398+
snapPortsOfNode(element, this.snapper);
399+
}
400+
}
401+
}
402+
});
403+
404+
return context.root;
405+
}
406+
}
407+
408+
export interface AddLabelToAllOrSelectionAction extends LabelToAllOrSelectionAction {
409+
kind: typeof AddLabelToAllOrSelectionAction.TYPE;
410+
}
411+
export namespace AddLabelToAllOrSelectionAction {
412+
export const TYPE = "add-label-to-all-or-selection";
413+
export function create(labelAssignment: LabelAssignment): AddLabelToAllOrSelectionAction {
414+
return {
415+
kind: TYPE,
416+
labelAssignment,
417+
};
418+
}
419+
}
420+
@injectable()
421+
export class AddLabelToAllOrSelectionCommand extends LabelToAllOrSelectionCommand {
422+
public static readonly KIND = AddLabelToAllOrSelectionAction.TYPE;
423+
424+
constructor(@inject(TYPES.Action) action: AddLabelAssignmentAction, @inject(TYPES.ISnapper) snapper: ISnapper) {
425+
super(action, snapper);
426+
}
427+
428+
execute(context: CommandExecutionContext): CommandReturn {
429+
return this.addLabel(context);
430+
}
431+
432+
undo(context: CommandExecutionContext): CommandReturn {
433+
return this.removeLabel(context);
434+
}
435+
436+
redo(context: CommandExecutionContext): CommandReturn {
437+
return this.execute(context);
438+
}
439+
}
440+
441+
export interface DeleteLabelFromAllOrSelectionAction extends LabelToAllOrSelectionAction {
442+
kind: typeof DeleteLabelFromAllOrSelectionAction.TYPE;
443+
}
444+
export namespace DeleteLabelFromAllOrSelectionAction {
445+
export const TYPE = "delete-label-from-all-or-selection";
446+
export function create(labelAssignment: LabelAssignment): DeleteLabelFromAllOrSelectionAction {
447+
return {
448+
kind: TYPE,
449+
labelAssignment,
450+
};
451+
}
452+
}
453+
@injectable()
454+
export class DeleteLabelFromAllOrSelectionCommand extends LabelToAllOrSelectionCommand {
455+
public static readonly KIND = DeleteLabelFromAllOrSelectionAction.TYPE;
456+
457+
constructor(
458+
@inject(TYPES.Action) action: DeleteLabelFromAllOrSelectionAction,
459+
@inject(TYPES.ISnapper) snapper: ISnapper,
460+
) {
461+
super(action, snapper);
462+
}
463+
464+
execute(context: CommandExecutionContext): CommandReturn {
465+
return this.removeLabel(context);
466+
}
467+
468+
undo(context: CommandExecutionContext): CommandReturn {
469+
return this.addLabel(context);
470+
}
471+
472+
redo(context: CommandExecutionContext): CommandReturn {
473+
return this.execute(context);
474+
}
475+
}
476+
477+
function getAllElements(elements: readonly SChildElementImpl[]): SModelElementImpl[] {
478+
const elementsList: SModelElementImpl[] = [];
479+
for (const element of elements) {
480+
elementsList.push(element);
481+
if ("children" in element) {
482+
elementsList.push(...getAllElements(element.children));
483+
}
484+
}
485+
return elementsList;
486+
}

src/features/labels/di.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import { LabelTypeEditorUI } from "./labelTypeEditor";
77
import { TYPES, configureCommand } from "sprotty";
88
import {
99
AddLabelAssignmentCommand,
10+
AddLabelToAllOrSelectionCommand,
1011
DeleteLabelAssignmentCommand,
12+
DeleteLabelFromAllOrSelectionCommand,
1113
DeleteLabelTypeCommand,
1214
DeleteLabelTypeValueCommand,
1315
} from "./commands";
@@ -32,4 +34,7 @@ export const dfdLabelModule = new ContainerModule((bind, unbind, isBound, rebind
3234
configureCommand(context, DeleteLabelAssignmentCommand);
3335
configureCommand(context, DeleteLabelTypeValueCommand);
3436
configureCommand(context, DeleteLabelTypeCommand);
37+
38+
configureCommand(context, AddLabelToAllOrSelectionCommand);
39+
configureCommand(context, DeleteLabelFromAllOrSelectionCommand);
3540
});

src/features/labels/labelTypeEditor.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,10 @@ div.label-type-editor-ui {
6767
#accordion-state-label-title {
6868
padding-right: 5px;
6969
}
70+
71+
.label-all {
72+
border: 1px solid var(--color-foreground);
73+
border-radius: 20px;
74+
padding: 3px 6px;
75+
margin-top: 4px;
76+
}

src/features/labels/labelTypeEditor.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import {
1111
TYPES,
1212
} from "sprotty";
1313
import { LabelAssignment, LabelType, LabelTypeRegistry, LabelTypeValue } from "./labelTypeRegistry";
14-
import { DeleteLabelTypeAction, DeleteLabelTypeValueAction } from "./commands";
14+
import {
15+
AddLabelToAllOrSelectionAction,
16+
DeleteLabelFromAllOrSelectionAction,
17+
DeleteLabelTypeAction,
18+
DeleteLabelTypeValueAction,
19+
} from "./commands";
1520
import { LABEL_ASSIGNMENT_MIME_TYPE } from "./dropListener";
1621
import { Action } from "sprotty-protocol";
1722
import { snapPortsOfNode } from "../dfdElements/portSnapper";
@@ -133,6 +138,8 @@ export class LabelTypeEditorUI extends AbstractUIExtension implements KeyListene
133138
inputElement?.focus();
134139
};
135140
innerContainerElement.appendChild(addButton);
141+
innerContainerElement.appendChild(this.buildAddToAllListener());
142+
innerContainerElement.appendChild(this.buildDeleteFromAllListener());
136143
}
137144

138145
private renderLabelType(labelType: LabelType): HTMLElement {
@@ -347,4 +354,40 @@ export class LabelTypeEditorUI extends AbstractUIExtension implements KeyListene
347354
const input = event.target as HTMLInputElement;
348355
input.value = input.value.replace(/[^a-zA-Z0-9_]/g, "");
349356
}
357+
358+
private buildAddToAllListener() {
359+
const holder = document.createElement("div");
360+
holder.classList.add("label-type-add-to-all", "label-all");
361+
holder.innerHTML = "Add to all/selection";
362+
holder.ondragover = (e: DragEvent) => {
363+
e.preventDefault(); // Necessary to allow a drop
364+
};
365+
holder.ondrop = (e: DragEvent) => {
366+
const labelAssignmentJson = e.dataTransfer?.getData(LABEL_ASSIGNMENT_MIME_TYPE);
367+
if (!labelAssignmentJson) {
368+
return;
369+
}
370+
const labelAssignment: LabelAssignment = JSON.parse(labelAssignmentJson);
371+
this.actionDispatcher.dispatch(AddLabelToAllOrSelectionAction.create(labelAssignment));
372+
};
373+
return holder;
374+
}
375+
376+
private buildDeleteFromAllListener() {
377+
const holder = document.createElement("div");
378+
holder.classList.add("label-type-delete-from-all", "label-all");
379+
holder.innerHTML = "Delete from all/selection";
380+
holder.ondragover = (e: DragEvent) => {
381+
e.preventDefault(); // Necessary to allow a drop
382+
};
383+
holder.ondrop = (e: DragEvent) => {
384+
const labelAssignmentJson = e.dataTransfer?.getData(LABEL_ASSIGNMENT_MIME_TYPE);
385+
if (!labelAssignmentJson) {
386+
return;
387+
}
388+
const labelAssignment: LabelAssignment = JSON.parse(labelAssignmentJson);
389+
this.actionDispatcher.dispatch(DeleteLabelFromAllOrSelectionAction.create(labelAssignment));
390+
};
391+
return holder;
392+
}
350393
}

src/features/serialize/defaultDiagram.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@
209209
"height": -1
210210
},
211211
"strokeWidth": 0,
212-
"selected": true,
212+
"selected": false,
213213
"hoverFeedback": false,
214214
"opacity": 1,
215215
"behavior": "forward items",

0 commit comments

Comments
 (0)