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 { + +