diff --git a/src/features/constraintMenu/ConstraintMenu.ts b/src/features/constraintMenu/ConstraintMenu.ts
index e9e392c..6e35e68 100644
--- a/src/features/constraintMenu/ConstraintMenu.ts
+++ b/src/features/constraintMenu/ConstraintMenu.ts
@@ -1,4 +1,4 @@
-import { inject, injectable, optional } from "inversify";
+import { inject, injectable, optional } from "inversify";
import "./constraintMenu.css";
import { AbstractUIExtension, IActionDispatcher, LocalModelSource, TYPES } from "sprotty";
import { ConstraintRegistry } from "./constraintRegistry";
@@ -19,6 +19,7 @@ import { LabelTypeRegistry } from "../labels/labelTypeRegistry";
import { EditorModeController } from "../editorMode/editorModeController";
import { Switchable, ThemeManager } from "../settingsMenu/themeManager";
import { AnalyzeDiagramAction } from "../serialize/analyze";
+import { ChooseConstraintAction } from "./actions";
@injectable()
export class ConstraintMenu extends AbstractUIExtension implements Switchable {
@@ -28,6 +29,8 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
private editor?: monaco.editor.IStandaloneCodeEditor;
private tree: AutoCompleteTree;
private forceReadOnly: boolean;
+ private optionsMenu?: HTMLDivElement;
+ private ignoreCheckboxChange = false;
constructor(
@inject(ConstraintRegistry) private readonly constraintRegistry: ConstraintRegistry,
@@ -72,6 +75,10 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
`;
+
+ const title = containerElement.querySelector("#constraint-menu-expand-title") as HTMLElement;
+ title.appendChild(this.buildOptionsButton());
+
const accordionContent = document.createElement("div");
accordionContent.classList.add("accordion-content");
const contentDiv = document.createElement("div");
@@ -222,4 +229,111 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
switchTheme(useDark: boolean): void {
this.editor?.updateOptions({ theme: useDark ? "vs-dark" : "vs" });
}
+
+ private buildOptionsButton(): HTMLElement {
+ const btn = document.createElement("button");
+ btn.id = "constraint-options-button";
+ btn.title = "Filter…";
+ btn.innerHTML = '';
+ btn.onclick = () => this.toggleOptionsMenu();
+ return btn;
+ }
+
+ /** show or hide the menu, generate checkboxes on the fly */
+ private toggleOptionsMenu(): void {
+ if (this.optionsMenu) {
+ this.optionsMenu.remove();
+ this.optionsMenu = undefined;
+ return;
+ }
+
+ // 1) create container
+ this.optionsMenu = document.createElement("div");
+ this.optionsMenu.id = "constraint-options-menu";
+
+ // 2) add the “All constraints” checkbox at the top
+ const allConstraints = document.createElement("label");
+ allConstraints.classList.add("options-item");
+
+ const allCb = document.createElement("input");
+ allCb.type = "checkbox";
+ allCb.value = "ALL";
+ allCb.checked = this.constraintRegistry
+ .getConstraintList()
+ .map((c) => c.name)
+ .every((c) => this.constraintRegistry.getSelectedConstraints().includes(c));
+
+ allCb.onchange = () => {
+ if (!this.optionsMenu) return;
+
+ this.ignoreCheckboxChange = true;
+ try {
+ if (allCb.checked) {
+ this.optionsMenu.querySelectorAll("input[type=checkbox]").forEach((cb) => {
+ if (cb !== allCb) cb.checked = true;
+ });
+ this.dispatcher.dispatch(
+ ChooseConstraintAction.create(this.constraintRegistry.getConstraintList().map((c) => c.name)),
+ );
+ } else {
+ this.optionsMenu.querySelectorAll("input[type=checkbox]").forEach((cb) => {
+ if (cb !== allCb) cb.checked = false;
+ });
+ this.dispatcher.dispatch(ChooseConstraintAction.create([]));
+ }
+ } finally {
+ this.ignoreCheckboxChange = false;
+ }
+ };
+
+ allConstraints.appendChild(allCb);
+ allConstraints.appendChild(document.createTextNode("All constraints"));
+ this.optionsMenu.appendChild(allConstraints);
+
+ // 2) pull your dynamic items
+ const items = this.constraintRegistry.getConstraintList();
+
+ // 3) for each item build a checkbox
+ items.forEach((item) => {
+ const label = document.createElement("label");
+ label.classList.add("options-item");
+
+ const cb = document.createElement("input");
+ cb.type = "checkbox";
+ cb.value = item.name;
+ cb.checked = this.constraintRegistry.getSelectedConstraints().includes(cb.value);
+
+ cb.onchange = () => {
+ if (this.ignoreCheckboxChange) return;
+
+ const checkboxes = this.optionsMenu!.querySelectorAll("input[type=checkbox]");
+ const individualCheckboxes = Array.from(checkboxes).filter((cb) => cb !== allCb);
+ const selected = individualCheckboxes.filter((cb) => cb.checked).map((cb) => cb.value);
+
+ allCb.checked = individualCheckboxes.every((cb) => cb.checked);
+
+ this.dispatcher.dispatch(ChooseConstraintAction.create(selected));
+ };
+
+ label.appendChild(cb);
+ label.appendChild(document.createTextNode(item.name));
+ this.optionsMenu!.appendChild(label);
+ });
+
+ this.editorContainer.appendChild(this.optionsMenu);
+
+ // optional: click-outside handler
+ const onClickOutside = (e: MouseEvent) => {
+ const target = e.target as Node;
+ if (!this.optionsMenu || this.optionsMenu.contains(target)) return;
+
+ const button = document.getElementById("constraint-options-button");
+ if (button && button.contains(target)) return;
+
+ this.optionsMenu.remove();
+ this.optionsMenu = undefined;
+ document.removeEventListener("click", onClickOutside);
+ };
+ document.addEventListener("click", onClickOutside);
+ }
}
diff --git a/src/features/constraintMenu/actions.ts b/src/features/constraintMenu/actions.ts
new file mode 100644
index 0000000..f69f110
--- /dev/null
+++ b/src/features/constraintMenu/actions.ts
@@ -0,0 +1,14 @@
+import { Action } from "sprotty-protocol";
+
+export interface ChooseConstraintAction extends Action {
+ kind: typeof ChooseConstraintAction.KIND;
+ names: string[];
+}
+
+export namespace ChooseConstraintAction {
+ export const KIND = "choose-constraint";
+
+ export function create(names: string[]): ChooseConstraintAction {
+ return { kind: KIND, names };
+ }
+}
diff --git a/src/features/constraintMenu/commands.ts b/src/features/constraintMenu/commands.ts
new file mode 100644
index 0000000..d131ca6
--- /dev/null
+++ b/src/features/constraintMenu/commands.ts
@@ -0,0 +1,73 @@
+import { inject, injectable } from "inversify";
+import { Command, CommandExecutionContext, CommandReturn, TYPES } from "sprotty";
+import { DfdNodeImpl } from "../dfdElements/nodes";
+import { ChooseConstraintAction } from "./actions";
+import { getBasicType } from "sprotty-protocol";
+import { AnnnotationsManager } from "../settingsMenu/annotationManager";
+import { ConstraintRegistry } from "./constraintRegistry";
+
+@injectable()
+export class ChooseConstraintCommand extends Command {
+ static readonly KIND = ChooseConstraintAction.KIND;
+
+ constructor(
+ @inject(TYPES.Action) private action: ChooseConstraintAction,
+ @inject(AnnnotationsManager) private annnotationsManager: AnnnotationsManager,
+ @inject(ConstraintRegistry) private constraintRegistry: ConstraintRegistry,
+ ) {
+ super();
+ }
+
+ execute(context: CommandExecutionContext): CommandReturn {
+ this.annnotationsManager.clearTfgs();
+ const names = this.action.names;
+ this.constraintRegistry.setSelectedConstraints(names);
+
+ const nodes = context.root.children.filter((node) => getBasicType(node) === "node") as DfdNodeImpl[];
+ if (names.length === 0) {
+ nodes.forEach((node) => {
+ node.setColor("var(--color-primary)");
+ });
+ return context.root;
+ }
+
+ nodes.forEach((node) => {
+ const annotations = node.annotations!;
+ let wasAdjusted = false;
+ if (this.constraintRegistry.selectedContainsAllConstraints()) {
+ annotations.forEach((annotation) => {
+ if (annotation.message.startsWith("Constraint")) {
+ wasAdjusted = true;
+ node.setColor(annotation.color!);
+ }
+ });
+ }
+ names.forEach((name) => {
+ annotations.forEach((annotation) => {
+ if (annotation.message.startsWith("Constraint ") && annotation.message.split(" ")[1] === name) {
+ node.setColor(annotation.color!);
+ wasAdjusted = true;
+ this.annnotationsManager.addTfg(annotation.tfg!);
+ }
+ });
+ });
+ if (!wasAdjusted) node.setColor("var(--color-primary)");
+ });
+
+ nodes.forEach((node) => {
+ const inTFG = node.annotations!.filter((annotation) =>
+ this.annnotationsManager.getSelectedTfgs().has(annotation.tfg!),
+ );
+ if (inTFG.length > 0) node.setColor("var(--color-highlighted)", false);
+ });
+
+ return context.root;
+ }
+
+ undo(context: CommandExecutionContext): CommandReturn {
+ return context.root;
+ }
+ redo(context: CommandExecutionContext): CommandReturn {
+ return context.root;
+ }
+}
diff --git a/src/features/constraintMenu/constraintMenu.css b/src/features/constraintMenu/constraintMenu.css
index ca768f5..5801bc3 100644
--- a/src/features/constraintMenu/constraintMenu.css
+++ b/src/features/constraintMenu/constraintMenu.css
@@ -106,3 +106,40 @@ div.constraint-menu {
align-items: center;
gap: 5px;
}
+
+#constraint-options-button {
+ position: absolute;
+ top: 6px;
+ right: 6px;
+ background: transparent;
+ border: none;
+ font-size: 1.2em;
+ cursor: pointer;
+ color: var(--color-foreground);
+ padding: 2px;
+}
+
+#constraint-options-menu {
+ position: absolute;
+ top: 30px; /* just under the header */
+ right: 6px;
+ background: var(--color-background);
+ border: 1px solid var(--color-foreground);
+ border-radius: 4px;
+ padding: 8px;
+ z-index: 100;
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
+}
+
+#constraint-options-menu .options-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 4px;
+ font-size: 0.9em;
+ color: var(--color-foreground);
+}
+
+#constraint-options-menu .options-item:last-child {
+ margin-bottom: 0;
+}
diff --git a/src/features/constraintMenu/constraintRegistry.ts b/src/features/constraintMenu/constraintRegistry.ts
index ff8df71..3b77363 100644
--- a/src/features/constraintMenu/constraintRegistry.ts
+++ b/src/features/constraintMenu/constraintRegistry.ts
@@ -9,6 +9,7 @@ export interface Constraint {
export class ConstraintRegistry {
private constraints: Constraint[] = [];
private updateCallbacks: (() => void)[] = [];
+ private selectedConstraints: string[] = this.constraints.map((c) => c.name);
public setConstraints(constraints: string[]): void {
this.constraints = this.splitIntoConstraintTexts(constraints).map((c) => this.mapToConstraint(c));
@@ -22,6 +23,14 @@ export class ConstraintRegistry {
this.constraintListChanged();
}
+ public setSelectedConstraints(constraints: string[]): void {
+ this.selectedConstraints = constraints;
+ }
+
+ public getSelectedConstraints(): string[] {
+ return this.selectedConstraints;
+ }
+
public clearConstraints(): void {
this.constraints = [];
this.constraintListChanged();
@@ -43,6 +52,16 @@ export class ConstraintRegistry {
return this.constraints;
}
+ public selectedContainsAllConstraints(): boolean {
+ return this.getConstraintList()
+ .map((c) => c.name)
+ .every((c) => this.getSelectedConstraints().includes(c));
+ }
+
+ public setAllConstraintsAsSelected(): void {
+ this.selectedConstraints = this.constraints.map((c) => c.name);
+ }
+
private splitIntoConstraintTexts(text: string[]): string[] {
const constraints: string[] = [];
let currentConstraint = "";
diff --git a/src/features/constraintMenu/di.config.ts b/src/features/constraintMenu/di.config.ts
index a08ac1a..9a35f76 100644
--- a/src/features/constraintMenu/di.config.ts
+++ b/src/features/constraintMenu/di.config.ts
@@ -1,19 +1,23 @@
import { ContainerModule } from "inversify";
import { EDITOR_TYPES } from "../../utils";
import { ConstraintMenu } from "./ConstraintMenu";
-import { TYPES } from "sprotty";
+import { configureCommand, TYPES } from "sprotty";
import { ConstraintRegistry } from "./constraintRegistry";
import { SWITCHABLE } from "../settingsMenu/themeManager";
+import { ChooseConstraintCommand } from "./commands";
// This module contains an UI extension that adds a tool palette to the editor.
// This tool palette allows the user to create new nodes and edges.
// Additionally it contains the tools that are used to create the nodes and edges.
-export const constraintMenuModule = new ContainerModule((bind) => {
+export const constraintMenuModule = new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ConstraintRegistry).toSelf().inSingletonScope();
bind(ConstraintMenu).toSelf().inSingletonScope();
bind(TYPES.IUIExtension).toService(ConstraintMenu);
bind(EDITOR_TYPES.DefaultUIElement).toService(ConstraintMenu);
bind(SWITCHABLE).toService(ConstraintMenu);
+
+ const context = { bind, unbind, isBound, rebind };
+ configureCommand(context, ChooseConstraintCommand);
});
diff --git a/src/features/dfdElements/elementStyles.css b/src/features/dfdElements/elementStyles.css
index 8828623..d472ff5 100644
--- a/src/features/dfdElements/elementStyles.css
+++ b/src/features/dfdElements/elementStyles.css
@@ -14,12 +14,12 @@
Used as a highlighter to mark nodes with errors.
This is essentially a "optional parameter" to this css rule.
See https://stackoverflow.com/questions/17893823/how-to-pass-parameters-to-css-classes */
- stroke: var(--color, var(--color-foreground));
+ stroke: var(--color-foreground);
stroke-width: 1;
/* Background fill of the node.
When --color is unset this is just --color-primary.
If this node is annotated and --color is set, it will be included in the color mix. */
- fill: color-mix(in srgb, var(--color-primary), var(--color, transparent) 25%);
+ fill: color-mix(in srgb, var(--color-primary), var(--color, transparent) 40%);
}
.sprotty-node .node-label text {
diff --git a/src/features/dfdElements/nodeAnnotationUi.ts b/src/features/dfdElements/nodeAnnotationUi.ts
index e59c78e..b8bc097 100644
--- a/src/features/dfdElements/nodeAnnotationUi.ts
+++ b/src/features/dfdElements/nodeAnnotationUi.ts
@@ -14,6 +14,8 @@ import { DfdNodeImpl } from "./nodes";
import "@fortawesome/fontawesome-free/css/all.min.css";
import "./nodeAnnotationUi.css";
+import { SettingsManager } from "../settingsMenu/SettingsManager";
+import { Mode } from "../settingsMenu/annotationManager";
export class DfdNodeAnnotationUIMouseListener extends MouseListener {
private stillTimeout: number | undefined;
@@ -70,7 +72,7 @@ export class DfdNodeAnnotationUIMouseListener extends MouseListener {
}
private showPopup(target: DfdNodeImpl): void {
- if (!target.annotation) {
+ if (!target.annotations) {
// no annotation. No need to show the popup.
return;
}
@@ -98,6 +100,7 @@ export class DfdNodeAnnotationUI extends AbstractUIExtension {
constructor(
@inject(DfdNodeAnnotationUIMouseListener)
private readonly mouseListener: DfdNodeAnnotationUIMouseListener,
+ @inject(SettingsManager) private settings: SettingsManager,
) {
super();
}
@@ -152,7 +155,7 @@ export class DfdNodeAnnotationUI extends AbstractUIExtension {
}
// Clear previous content
- this.annotationParagraph.innerHTML = "";
+ this.annotationParagraph.innerText = "";
// Set position
// 2 offset to ensure the mouse is inside the popup when showing it.
@@ -178,18 +181,38 @@ export class DfdNodeAnnotationUI extends AbstractUIExtension {
containerElement.style.maxHeight = `${Math.max(screenHeight - annotationPosition.y - 50, 50)}px`;
// Set content
- if (!node.annotation) {
+ if (!node.annotations || node.annotations.length == 0) {
this.annotationParagraph.innerText = "No errors";
return;
}
- const { message, icon } = node.annotation;
- this.annotationParagraph.innerHTML = message;
+ this.annotationParagraph.innerHTML = "";
- if (icon) {
- const iconI = document.createElement("i");
- iconI.classList.add("fa", `fa-${icon}`);
- this.annotationParagraph.prepend(iconI);
- }
+ const mode = this.settings.getCurrentLabelMode();
+
+ node.annotations.forEach((a) => {
+ if (
+ ((mode === Mode.INCOMING || mode === Mode.ALL) && a.message.trim().startsWith("Incoming")) ||
+ ((mode === Mode.OUTGOING || mode === Mode.ALL) && a.message.trim().startsWith("Propagated")) ||
+ a.message.startsWith("Constraint")
+ ) {
+ const line = document.createElement("div");
+ line.style.display = "flex";
+ line.style.alignItems = "center";
+ line.style.gap = "6px"; // some spacing between icon and text
+
+ if (a.icon) {
+ const iconI = document.createElement("i");
+ iconI.classList.add("fa", `fa-${a.icon}`);
+ line.appendChild(iconI);
+ }
+
+ const textSpan = document.createElement("span");
+ textSpan.innerText = a.message;
+ line.appendChild(textSpan);
+
+ this.annotationParagraph.appendChild(line);
+ }
+ });
}
}
diff --git a/src/features/dfdElements/nodes.tsx b/src/features/dfdElements/nodes.tsx
index 790f8b8..8bae434 100644
--- a/src/features/dfdElements/nodes.tsx
+++ b/src/features/dfdElements/nodes.tsx
@@ -25,26 +25,30 @@ export interface DfdNode extends SNode {
text: string;
labels: LabelAssignment[];
ports: SPort[];
- annotation?: DfdNodeAnnotation;
+ annotations?: DfdNodeAnnotation[];
}
export interface DfdNodeAnnotation {
message: string;
color?: string;
icon?: string;
+ tfg?: number;
}
export abstract class DfdNodeImpl extends DynamicChildrenNode implements WithEditableLabel {
static readonly DEFAULT_FEATURES = [...SNodeImpl.DEFAULT_FEATURES, withEditLabelFeature, containsDfdLabelFeature];
static readonly DEFAULT_WIDTH = 50;
static readonly WIDTH_PADDING = 12;
+ static readonly NODE_COLOR = "var(--color-primary)";
+ static readonly HIGHLIGHTED_COLOR = "var(--color-highlighted)";
text: string = "";
+ color?: string;
labels: LabelAssignment[] = [];
ports: SPort[] = [];
hideLabels: boolean = false;
minimumWidth: number = DfdNodeImpl.DEFAULT_WIDTH;
- annotation?: DfdNodeAnnotation;
+ annotations: DfdNodeAnnotation[] = [];
override setChildren(schema: DfdNode): void {
const children: SModelElement[] = [
@@ -90,12 +94,11 @@ export abstract class DfdNodeImpl extends DynamicChildrenNode implements WithEdi
return this.minimumWidth + DfdNodeImpl.WIDTH_PADDING;
}
const textWidth = calculateTextSize(this.text).width;
- const editableLabelWidth = this.editableLabel ? calculateTextSize(this.editableLabel.text).width : 0;
const labelWidths = this.labels.map(
(labelAssignment) => DfdNodeLabelRenderer.computeLabelContent(labelAssignment)[1],
);
- const neededWidth = Math.max(...labelWidths, textWidth, editableLabelWidth, DfdNodeImpl.DEFAULT_WIDTH);
+ const neededWidth = Math.max(...labelWidths, textWidth, DfdNodeImpl.DEFAULT_WIDTH);
return neededWidth + DfdNodeImpl.WIDTH_PADDING;
}
@@ -149,12 +152,16 @@ export abstract class DfdNodeImpl extends DynamicChildrenNode implements WithEdi
opacity: this.opacity.toString(),
};
- if (this.annotation?.color) {
- style["--color"] = this.annotation.color;
- }
+ style["--border"] = "#FFFFFF";
+
+ if (this.color) style["--color"] = this.color;
return style;
}
+
+ public setColor(color: string, override: boolean = true) {
+ if (override || this.color === DfdNodeImpl.NODE_COLOR) this.color = color;
+ }
}
@injectable()
diff --git a/src/features/editorMode/command.ts b/src/features/editorMode/command.ts
index a1e67c1..4ea79a2 100644
--- a/src/features/editorMode/command.ts
+++ b/src/features/editorMode/command.ts
@@ -23,7 +23,7 @@ export class ChangeEditorModeCommand extends Command {
static readonly KIND = ChangeEditorModeAction.KIND;
private oldMode?: EditorMode;
- private oldNodeAnnotations: Map = new Map();
+ private oldNodeAnnotations: Map = new Map();
@inject(EditorModeController)
private readonly controller?: EditorModeController;
@@ -65,9 +65,9 @@ export class ChangeEditorModeCommand extends Command {
this.oldNodeAnnotations.clear();
context.root.index.all().forEach((element) => {
- if (element instanceof DfdNodeImpl && element.annotation) {
- this.oldNodeAnnotations.set(element.id, element.annotation);
- element.annotation = undefined;
+ if (element instanceof DfdNodeImpl && element.annotations) {
+ this.oldNodeAnnotations.set(element.id, element.annotations);
+ element.annotations = [];
}
});
}
@@ -79,7 +79,7 @@ export class ChangeEditorModeCommand extends Command {
this.oldNodeAnnotations.forEach((annotation, id) => {
const element = context.root.index.getById(id);
if (element instanceof DfdNodeImpl) {
- element.annotation = annotation;
+ element.annotations = annotation;
}
});
}
diff --git a/src/features/serialize/load.ts b/src/features/serialize/load.ts
index 07a0a2a..5ce725b 100644
--- a/src/features/serialize/load.ts
+++ b/src/features/serialize/load.ts
@@ -20,6 +20,7 @@ import { LayoutModelAction } from "../autoLayout/command";
import { EditorMode, EditorModeController } from "../editorMode/editorModeController";
import { Constraint, ConstraintRegistry } from "../constraintMenu/constraintRegistry";
import { LoadingIndicator } from "../../common/loadingIndicator";
+import { ChooseConstraintAction } from "../constraintMenu/actions";
export interface LoadDiagramAction extends Action {
kind: typeof LoadDiagramAction.KIND;
@@ -225,6 +226,13 @@ export class LoadDiagramCommand extends Command {
postLoadActions(this.newRoot, this.actionDispatcher);
+ if (this.constraintRegistry) {
+ this.constraintRegistry.setAllConstraintsAsSelected();
+ this.actionDispatcher.dispatch(
+ ChooseConstraintAction.create(this.constraintRegistry!.getConstraintList().map((c) => c.name)),
+ );
+ }
+
this.oldFileName = currentFileName;
this.newFileName = file.name;
setFileNameInPageTitle(file.name);
diff --git a/src/features/serialize/webSocketHandler.ts b/src/features/serialize/webSocketHandler.ts
index bf8045e..51fa921 100644
--- a/src/features/serialize/webSocketHandler.ts
+++ b/src/features/serialize/webSocketHandler.ts
@@ -33,8 +33,8 @@ function initWebSocket() {
logger.log(ws, event.data);
// Example of specific handling for certain messages:
- if (event.data === "Error:Cycle") {
- alert("Error analyzing model: Model terminates in cycle!");
+ if (event.data.startsWith("Error:")) {
+ alert(event.data);
loadingIndicator.hideIndicator();
return;
}
@@ -43,12 +43,13 @@ function initWebSocket() {
loadingIndicator.hideIndicator();
return;
}
- if (event.data === "Shutdown") {
- loadingIndicator.hideIndicator();
- return;
- }
+
+ let message = event.data;
+ const name = message.split(":")[0];
+ message = message.replace(name + ":", "");
+
if (event.data.trim().endsWith("")) {
- const saveDFDandDD = new SaveDFDandDD(event.data);
+ const saveDFDandDD = new SaveDFDandDD(message);
saveDFDandDD.saveDiagramAsDFD();
loadingIndicator.hideIndicator();
return;
@@ -56,7 +57,7 @@ function initWebSocket() {
// Otherwise, treat incoming data as JSON for model source:
setModelSource(
- new File([new Blob([event.data], { type: "application/json" })], getModelFileName() + ".json", {
+ new File([new Blob([message], { type: "application/json" })], name + ".json", {
type: "application/json",
}),
);
@@ -65,7 +66,7 @@ function initWebSocket() {
}
export function sendMessage(message: string) {
- ws.send(wsId + ":" + message);
+ ws.send(wsId + ":" + getModelFileName() + ":" + message);
}
// Initialize immediately upon module load
diff --git a/src/features/settingsMenu/SettingsManager.ts b/src/features/settingsMenu/SettingsManager.ts
index 39bbbaf..3916d73 100644
--- a/src/features/settingsMenu/SettingsManager.ts
+++ b/src/features/settingsMenu/SettingsManager.ts
@@ -2,6 +2,7 @@ import { inject, injectable } from "inversify";
import { ActionDispatcher, TYPES } from "sprotty";
import { ChangeEdgeLabelVisibilityAction, CompleteLayoutProcessAction, SimplifyNodeNamesAction } from "./actions";
import { LayoutMethod } from "./LayoutMethod";
+import { Mode } from "./annotationManager";
@injectable()
export class SettingsManager {
@@ -11,6 +12,7 @@ export class SettingsManager {
private _hideEdgeLabelsCheckbox?: HTMLInputElement;
private _simplifyNodeNames = false;
private _simplifyNodeNamesCheckbox?: HTMLInputElement;
+ private _labelModeSelector?: HTMLSelectElement;
private static readonly layoutMethodLocalStorageKey = "dfdwebeditor:settings";
constructor(@inject(TYPES.IActionDispatcher) protected readonly dispatcher: ActionDispatcher) {
@@ -80,4 +82,13 @@ export class SettingsManager {
);
});
}
+
+ public bindLabelModeSelector(labelModeSelector: HTMLSelectElement) {
+ this._labelModeSelector = labelModeSelector;
+ labelModeSelector.value = Mode.INCOMING;
+ }
+
+ public getCurrentLabelMode(): Mode {
+ return this._labelModeSelector!.value as Mode;
+ }
}
diff --git a/src/features/settingsMenu/annotationManager.ts b/src/features/settingsMenu/annotationManager.ts
new file mode 100644
index 0000000..064304b
--- /dev/null
+++ b/src/features/settingsMenu/annotationManager.ts
@@ -0,0 +1,24 @@
+import { injectable } from "inversify";
+
+export enum Mode {
+ INCOMING = "Incoming Labels",
+ OUTGOING = "Outgoing Labels",
+ ALL = "All Labels",
+}
+
+@injectable()
+export class AnnnotationsManager {
+ private selectedTfgs = new Set();
+
+ public getSelectedTfgs(): Set {
+ return this.selectedTfgs;
+ }
+ public clearTfgs() {
+ this.selectedTfgs = new Set();
+ }
+ public addTfg(hash: number) {
+ this.selectedTfgs.add(hash);
+ }
+
+ constructor() {}
+}
diff --git a/src/features/settingsMenu/di.config.ts b/src/features/settingsMenu/di.config.ts
index e299c6b..0cffe85 100644
--- a/src/features/settingsMenu/di.config.ts
+++ b/src/features/settingsMenu/di.config.ts
@@ -11,12 +11,14 @@ import {
SimplifyNodeNamesCommand,
} from "./commands";
import { SettingsManager } from "./SettingsManager";
+import { AnnnotationsManager } from "./annotationManager";
export const settingsModule = new ContainerModule((bind, unbind, isBound, rebind) => {
bind(SettingsManager).toSelf().inSingletonScope();
bind(NodeNameReplacementRegistry).toSelf().inSingletonScope();
bind(ThemeManager).toSelf().inSingletonScope();
bind(SettingsUI).toSelf().inSingletonScope();
+ bind(AnnnotationsManager).toSelf().inSingletonScope();
bind(TYPES.IUIExtension).toService(SettingsUI);
bind(EDITOR_TYPES.DefaultUIElement).toService(SettingsUI);
const context = { bind, unbind, isBound, rebind };
diff --git a/src/features/settingsMenu/settingsMenu.ts b/src/features/settingsMenu/settingsMenu.ts
index 9e743f8..b7adb98 100644
--- a/src/features/settingsMenu/settingsMenu.ts
+++ b/src/features/settingsMenu/settingsMenu.ts
@@ -7,6 +7,7 @@ import { SettingsManager } from "./SettingsManager";
import { LayoutMethod } from "./LayoutMethod";
import { EditorModeController } from "../editorMode/editorModeController";
import { ChangeEditorModeAction } from "../editorMode/command";
+import { Mode } from "./annotationManager";
@injectable()
export class SettingsUI extends AbstractUIExtension {
@@ -52,6 +53,12 @@ export class SettingsUI extends AbstractUIExtension {
+
+