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

Commit 5626595

Browse files
committed
Feat: Implement Constraint Selection, Incoming/Outgoing Selection
1 parent 4460d3c commit 5626595

13 files changed

Lines changed: 380 additions & 32 deletions

File tree

src/features/constraintMenu/ConstraintMenu.ts

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { inject, injectable, optional } from "inversify";
1+
import { inject, injectable, optional } from "inversify";
22
import "./constraintMenu.css";
33
import { AbstractUIExtension, IActionDispatcher, LocalModelSource, TYPES } from "sprotty";
4-
import { ConstraintRegistry } from "./constraintRegistry";
4+
import { Constraint, ConstraintRegistry } from "./constraintRegistry";
55

66
// Enable hover feature that is used to show validation errors.
77
// Inline completions are enabled to allow autocompletion of keywords and inputs/label types/label values.
@@ -19,6 +19,7 @@ import { LabelTypeRegistry } from "../labels/labelTypeRegistry";
1919
import { EditorModeController } from "../editorMode/editorModeController";
2020
import { Switchable, ThemeManager } from "../settingsMenu/themeManager";
2121
import { AnalyzeDiagramAction } from "../serialize/analyze";
22+
import { ChooseConstraintAction } from "./actions";
2223

2324
@injectable()
2425
export class ConstraintMenu extends AbstractUIExtension implements Switchable {
@@ -28,6 +29,7 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
2829
private editor?: monaco.editor.IStandaloneCodeEditor;
2930
private tree: AutoCompleteTree;
3031
private forceReadOnly: boolean;
32+
private optionsMenu?: HTMLDivElement;
3133

3234
constructor(
3335
@inject(ConstraintRegistry) private readonly constraintRegistry: ConstraintRegistry,
@@ -72,6 +74,10 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
7274
</div>
7375
</label>
7476
`;
77+
78+
const title = containerElement.querySelector("#constraint-menu-expand-title") as HTMLElement;
79+
title.appendChild(this.buildOptionsButton());
80+
7581
const accordionContent = document.createElement("div");
7682
accordionContent.classList.add("accordion-content");
7783
const contentDiv = document.createElement("div");
@@ -225,4 +231,105 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
225231
switchTheme(useDark: boolean): void {
226232
this.editor?.updateOptions({ theme: useDark ? "vs-dark" : "vs" });
227233
}
234+
235+
private buildOptionsButton(): HTMLElement {
236+
const btn = document.createElement("button");
237+
btn.id = "constraint-options-button";
238+
btn.title = "Filter…";
239+
btn.innerHTML = "⋮"; // or insert a font-awesome icon
240+
btn.onclick = () => this.toggleOptionsMenu();
241+
return btn;
242+
}
243+
244+
/** show or hide the menu, generate checkboxes on the fly */
245+
private toggleOptionsMenu(): void {
246+
if (this.optionsMenu) {
247+
this.optionsMenu.remove();
248+
this.optionsMenu = undefined;
249+
return;
250+
}
251+
252+
// 1) create container
253+
this.optionsMenu = document.createElement("div");
254+
this.optionsMenu.id = "constraint-options-menu";
255+
256+
// 2) add the “All constraints” checkbox at the top
257+
const allConstraints = document.createElement("label");
258+
allConstraints.classList.add("options-item");
259+
260+
const allCb = document.createElement("input");
261+
allCb.type = "checkbox";
262+
allCb.value = "ALL";
263+
// initially checked if no specific constraint is selected
264+
allCb.checked = this.constraintRegistry.getSelectedConstraints().includes("ALL");
265+
266+
allCb.onchange = () => {
267+
if (!this.optionsMenu) return;
268+
if (allCb.checked) {
269+
// uncheck every other constraint-checkbox
270+
this.optionsMenu
271+
.querySelectorAll<HTMLInputElement>("input[type=checkbox]")
272+
.forEach(cb => {
273+
if (cb !== allCb) cb.checked = false;
274+
});
275+
// dispatch with empty array to mean “all”
276+
this.dispatcher.dispatch(
277+
ChooseConstraintAction.create(["ALL"])
278+
);
279+
} else {
280+
this.dispatcher.dispatch(
281+
ChooseConstraintAction.create([])
282+
);
283+
}
284+
285+
};
286+
287+
allConstraints.appendChild(allCb);
288+
allConstraints.appendChild(document.createTextNode("All constraints"));
289+
this.optionsMenu.appendChild(allConstraints);
290+
291+
// 2) pull your dynamic items (replace with your real API)
292+
const items = this.constraintRegistry.getConstraintList();
293+
294+
// 3) for each item build a checkbox
295+
items.forEach(item => {
296+
const label = document.createElement("label");
297+
label.classList.add("options-item");
298+
299+
const cb = document.createElement("input");
300+
cb.type = "checkbox";
301+
cb.value = item.name;
302+
cb.checked = this.constraintRegistry.getSelectedConstraints().includes(cb.value);
303+
304+
cb.onchange = () => {
305+
if (cb.checked) allCb.checked = false;
306+
307+
const selected = Array.from(
308+
this.optionsMenu!.querySelectorAll<HTMLInputElement>("input[type=checkbox]:checked")
309+
).map(cb => cb.value);
310+
311+
// dispatch your action with either an array or
312+
// a comma-joined string—whatever your action expects
313+
this.dispatcher.dispatch(
314+
ChooseConstraintAction.create(selected)
315+
);
316+
};
317+
318+
label.appendChild(cb);
319+
label.appendChild(document.createTextNode(item.name));
320+
this.optionsMenu!.appendChild(label);
321+
});
322+
323+
this.editorContainer.appendChild(this.optionsMenu);
324+
325+
// optional: click-outside handler
326+
const onClickOutside = (e: MouseEvent) => {
327+
if (this.optionsMenu && !this.optionsMenu.contains(e.target as Node)
328+
&& !(e.target as Element).matches("#constraint-options-button")) {
329+
this.toggleOptionsMenu();
330+
document.removeEventListener("click", onClickOutside);
331+
}
332+
};
333+
setTimeout(() => document.addEventListener("click", onClickOutside), 0);
334+
}
228335
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Action } from "sprotty-protocol";
2+
3+
export interface ChooseConstraintAction extends Action {
4+
kind: typeof ChooseConstraintAction.KIND;
5+
names: string[];
6+
}
7+
8+
export namespace ChooseConstraintAction {
9+
export const KIND = "choose-constraint";
10+
11+
export function create(names: string[]): ChooseConstraintAction {
12+
return { kind: KIND, names };
13+
}
14+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { inject, injectable } from "inversify";
2+
import {
3+
Command,
4+
CommandExecutionContext,
5+
CommandReturn,
6+
TYPES,
7+
} from "sprotty";
8+
import { DfdNodeImpl } from "../dfdElements/nodes";
9+
import {
10+
ChooseConstraintAction
11+
} from "./actions";
12+
import { getBasicType} from "sprotty-protocol";
13+
import { AnnnotationsManager } from "../settingsMenu/annotationManager";
14+
import { ConstraintRegistry } from "./constraintRegistry";
15+
16+
17+
@injectable()
18+
export class ChooseConstraintCommand extends Command {
19+
static readonly KIND = ChooseConstraintAction.KIND;
20+
21+
constructor(
22+
@inject(TYPES.Action) private action: ChooseConstraintAction,
23+
@inject(AnnnotationsManager) private annnotationsManager: AnnnotationsManager,
24+
@inject(ConstraintRegistry) private constraintRegistry: ConstraintRegistry
25+
) {
26+
super();
27+
}
28+
29+
execute(context: CommandExecutionContext): CommandReturn {
30+
this.annnotationsManager.clearTfgs();
31+
const names = this.action.names;
32+
this.constraintRegistry.setSelectedConstraints(names);
33+
const nodes = context.root.children.filter((node) => getBasicType(node) === "node") as DfdNodeImpl[];
34+
if (names.includes("NO")) {
35+
nodes.forEach((node) => {
36+
node.setColor("#1D1C22");
37+
});
38+
return context.root;
39+
}
40+
41+
nodes.forEach((node) => {
42+
const annotations = node.annotations!;
43+
let wasAdjusted = false;
44+
if (names.includes("ALL")) {
45+
annotations.forEach((annotation) => {
46+
if (annotation.message.startsWith("Constraint")) {
47+
wasAdjusted = true;
48+
node.setColor(annotation.color!);
49+
}
50+
});
51+
}
52+
names.forEach((name) => {
53+
annotations.forEach((annotation) => {
54+
if (annotation.message.startsWith("Constraint " + name)) {
55+
node.setColor(annotation.color!);
56+
wasAdjusted = true;
57+
this.annnotationsManager.addTfg(annotation.tfg!);
58+
}
59+
});
60+
});
61+
console.log("Node" + node.text + "Was adjusted: " + wasAdjusted);
62+
if (!wasAdjusted) node.setColor("#1D1C22");
63+
});
64+
65+
if (!names.includes("ALL") && names.length > 0) {
66+
nodes.forEach((node) => {
67+
const inTFG = node.annotations!.filter((annotation) =>
68+
this.annnotationsManager.getSelectedTfgs().has(annotation.tfg!),
69+
);
70+
if (inTFG.length > 0) node.setColor("#77777A", false);
71+
});
72+
}
73+
74+
return context.root;
75+
}
76+
77+
undo(context: CommandExecutionContext): CommandReturn {
78+
return context.root;
79+
}
80+
redo(context: CommandExecutionContext): CommandReturn {
81+
return context.root;
82+
}
83+
}

src/features/constraintMenu/constraintMenu.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,40 @@ div.constraint-menu {
102102
align-items: center;
103103
gap: 5px;
104104
}
105+
106+
#constraint-options-button {
107+
position: absolute;
108+
top: 6px;
109+
right: 6px;
110+
background: transparent;
111+
border: none;
112+
font-size: 1.2em;
113+
cursor: pointer;
114+
color: var(--color-foreground);
115+
padding: 2px;
116+
}
117+
118+
#constraint-options-menu {
119+
position: absolute;
120+
top: 30px; /* just under the header */
121+
right: 6px;
122+
background: var(--color-background);
123+
border: 1px solid var(--color-foreground);
124+
border-radius: 4px;
125+
padding: 8px;
126+
z-index: 100;
127+
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
128+
}
129+
130+
#constraint-options-menu .options-item {
131+
display: flex;
132+
align-items: center;
133+
gap: 6px;
134+
margin-bottom: 4px;
135+
font-size: 0.9em;
136+
color: var(--color-foreground);
137+
}
138+
139+
#constraint-options-menu .options-item:last-child {
140+
margin-bottom: 0;
141+
}

src/features/constraintMenu/constraintRegistry.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,16 @@ export interface Constraint {
1111
export class ConstraintRegistry {
1212
private constraints: Constraint[] = [];
1313
private updateCallbacks: (() => void)[] = [];
14+
private selectedConstraints: string[] = ["ALL"];
1415

1516
public setConstraints(constraints: string): void {
16-
this.constraints = constraints.split("\r?\n").map(this.constraintFromLine);
17+
const lines = constraints
18+
.trim()
19+
.split(/\r?\n(?=-)/)
20+
.map(line => line.trim())
21+
.filter(line => line.length > 0);
22+
23+
this.constraints = lines.map(this.constraintFromLine);
1724
this.constraintListChanged();
1825
}
1926

@@ -26,6 +33,14 @@ export class ConstraintRegistry {
2633
this.constraintListChanged();
2734
}
2835

36+
public setSelectedConstraints(constraints: string[]): void {
37+
this.selectedConstraints = constraints;
38+
}
39+
40+
public getSelectedConstraints(): string[] {
41+
return this.selectedConstraints;
42+
}
43+
2944
private constraintFromLine(line: string): Constraint {
3045
const parts = line.split(" ");
3146
if (parts.length < 2) {
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
import { ContainerModule } from "inversify";
22
import { EDITOR_TYPES } from "../../utils";
33
import { ConstraintMenu } from "./ConstraintMenu";
4-
import { TYPES } from "sprotty";
4+
import { configureCommand, TYPES } from "sprotty";
55
import { ConstraintRegistry } from "./constraintRegistry";
66
import { SWITCHABLE } from "../settingsMenu/themeManager";
7+
import { ChooseConstraintCommand } from "./commands";
78

89
// This module contains an UI extension that adds a tool palette to the editor.
910
// This tool palette allows the user to create new nodes and edges.
1011
// Additionally it contains the tools that are used to create the nodes and edges.
1112

12-
export const constraintMenuModule = new ContainerModule((bind) => {
13+
export const constraintMenuModule = new ContainerModule((bind, unbind, isBound, rebind) => {
1314
bind(ConstraintRegistry).toSelf().inSingletonScope();
1415

1516
bind(ConstraintMenu).toSelf().inSingletonScope();
1617
bind(TYPES.IUIExtension).toService(ConstraintMenu);
1718
bind(EDITOR_TYPES.DefaultUIElement).toService(ConstraintMenu);
1819
bind(SWITCHABLE).toService(ConstraintMenu);
20+
21+
const context = { bind, unbind, isBound, rebind };
22+
configureCommand(context, ChooseConstraintCommand);
1923
});

0 commit comments

Comments
 (0)