1- import { inject , injectable , optional } from "inversify" ;
1+ import { inject , injectable , optional } from "inversify" ;
22import "./constraintMenu.css" ;
33import { AbstractUIExtension , IActionDispatcher , LocalModelSource , TYPES } from "sprotty" ;
44import { ConstraintRegistry } from "./constraintRegistry" ;
@@ -19,6 +19,7 @@ import { LabelTypeRegistry } from "../labels/labelTypeRegistry";
1919import { EditorModeController } from "../editorMode/editorModeController" ;
2020import { Switchable , ThemeManager } from "../settingsMenu/themeManager" ;
2121import { AnalyzeDiagramAction } from "../serialize/analyze" ;
22+ import { ChooseConstraintAction } from "./actions" ;
2223
2324@injectable ( )
2425export class ConstraintMenu extends AbstractUIExtension implements Switchable {
@@ -28,6 +29,8 @@ 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 ;
33+ private ignoreCheckboxChange = false ;
3134
3235 constructor (
3336 @inject ( ConstraintRegistry ) private readonly constraintRegistry : ConstraintRegistry ,
@@ -72,6 +75,10 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
7275 </div>
7376 </label>
7477 ` ;
78+
79+ const title = containerElement . querySelector ( "#constraint-menu-expand-title" ) as HTMLElement ;
80+ title . appendChild ( this . buildOptionsButton ( ) ) ;
81+
7582 const accordionContent = document . createElement ( "div" ) ;
7683 accordionContent . classList . add ( "accordion-content" ) ;
7784 const contentDiv = document . createElement ( "div" ) ;
@@ -222,4 +229,111 @@ export class ConstraintMenu extends AbstractUIExtension implements Switchable {
222229 switchTheme ( useDark : boolean ) : void {
223230 this . editor ?. updateOptions ( { theme : useDark ? "vs-dark" : "vs" } ) ;
224231 }
232+
233+ private buildOptionsButton ( ) : HTMLElement {
234+ const btn = document . createElement ( "button" ) ;
235+ btn . id = "constraint-options-button" ;
236+ btn . title = "Filter…" ;
237+ btn . innerHTML = '<span class="codicon codicon-kebab-vertical"></span>' ;
238+ btn . onclick = ( ) => this . toggleOptionsMenu ( ) ;
239+ return btn ;
240+ }
241+
242+ /** show or hide the menu, generate checkboxes on the fly */
243+ private toggleOptionsMenu ( ) : void {
244+ if ( this . optionsMenu ) {
245+ this . optionsMenu . remove ( ) ;
246+ this . optionsMenu = undefined ;
247+ return ;
248+ }
249+
250+ // 1) create container
251+ this . optionsMenu = document . createElement ( "div" ) ;
252+ this . optionsMenu . id = "constraint-options-menu" ;
253+
254+ // 2) add the “All constraints” checkbox at the top
255+ const allConstraints = document . createElement ( "label" ) ;
256+ allConstraints . classList . add ( "options-item" ) ;
257+
258+ const allCb = document . createElement ( "input" ) ;
259+ allCb . type = "checkbox" ;
260+ allCb . value = "ALL" ;
261+ allCb . checked = this . constraintRegistry
262+ . getConstraintList ( )
263+ . map ( ( c ) => c . name )
264+ . every ( ( c ) => this . constraintRegistry . getSelectedConstraints ( ) . includes ( c ) ) ;
265+
266+ allCb . onchange = ( ) => {
267+ if ( ! this . optionsMenu ) return ;
268+
269+ this . ignoreCheckboxChange = true ;
270+ try {
271+ if ( allCb . checked ) {
272+ this . optionsMenu . querySelectorAll < HTMLInputElement > ( "input[type=checkbox]" ) . forEach ( ( cb ) => {
273+ if ( cb !== allCb ) cb . checked = true ;
274+ } ) ;
275+ this . dispatcher . dispatch (
276+ ChooseConstraintAction . create ( this . constraintRegistry . getConstraintList ( ) . map ( ( c ) => c . name ) ) ,
277+ ) ;
278+ } else {
279+ this . optionsMenu . querySelectorAll < HTMLInputElement > ( "input[type=checkbox]" ) . forEach ( ( cb ) => {
280+ if ( cb !== allCb ) cb . checked = false ;
281+ } ) ;
282+ this . dispatcher . dispatch ( ChooseConstraintAction . create ( [ ] ) ) ;
283+ }
284+ } finally {
285+ this . ignoreCheckboxChange = false ;
286+ }
287+ } ;
288+
289+ allConstraints . appendChild ( allCb ) ;
290+ allConstraints . appendChild ( document . createTextNode ( "All constraints" ) ) ;
291+ this . optionsMenu . appendChild ( allConstraints ) ;
292+
293+ // 2) pull your dynamic items
294+ const items = this . constraintRegistry . getConstraintList ( ) ;
295+
296+ // 3) for each item build a checkbox
297+ items . forEach ( ( item ) => {
298+ const label = document . createElement ( "label" ) ;
299+ label . classList . add ( "options-item" ) ;
300+
301+ const cb = document . createElement ( "input" ) ;
302+ cb . type = "checkbox" ;
303+ cb . value = item . name ;
304+ cb . checked = this . constraintRegistry . getSelectedConstraints ( ) . includes ( cb . value ) ;
305+
306+ cb . onchange = ( ) => {
307+ if ( this . ignoreCheckboxChange ) return ;
308+
309+ const checkboxes = this . optionsMenu ! . querySelectorAll < HTMLInputElement > ( "input[type=checkbox]" ) ;
310+ const individualCheckboxes = Array . from ( checkboxes ) . filter ( ( cb ) => cb !== allCb ) ;
311+ const selected = individualCheckboxes . filter ( ( cb ) => cb . checked ) . map ( ( cb ) => cb . value ) ;
312+
313+ allCb . checked = individualCheckboxes . every ( ( cb ) => cb . checked ) ;
314+
315+ this . dispatcher . dispatch ( ChooseConstraintAction . create ( selected ) ) ;
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+ const target = e . target as Node ;
328+ if ( ! this . optionsMenu || this . optionsMenu . contains ( target ) ) return ;
329+
330+ const button = document . getElementById ( "constraint-options-button" ) ;
331+ if ( button && button . contains ( target ) ) return ;
332+
333+ this . optionsMenu . remove ( ) ;
334+ this . optionsMenu = undefined ;
335+ document . removeEventListener ( "click" , onClickOutside ) ;
336+ } ;
337+ document . addEventListener ( "click" , onClickOutside ) ;
338+ }
225339}
0 commit comments