From 6152d6f7ebec8c5908bb657109bea6767184aa0c Mon Sep 17 00:00:00 2001 From: Martin Vladic Date: Sat, 2 Nov 2024 16:42:26 +0100 Subject: [PATCH] #616 --- packages/eez-studio-ui/_stylesheets/app.less | 4 + .../eez-studio-ui/_stylesheets/vars-dark.less | 2 + packages/eez-studio-ui/_stylesheets/vars.less | 2 + packages/eez-studio-ui/dialog.tsx | 54 +++++++++++-- packages/eez-studio-ui/generic-dialog.tsx | 11 +++ .../project-editor/features/action/action.tsx | 36 +++++++-- packages/project-editor/flow/component.tsx | 79 +++++++++++++++---- 7 files changed, 159 insertions(+), 29 deletions(-) diff --git a/packages/eez-studio-ui/_stylesheets/app.less b/packages/eez-studio-ui/_stylesheets/app.less index 116e83448..87f9a60e0 100644 --- a/packages/eez-studio-ui/_stylesheets/app.less +++ b/packages/eez-studio-ui/_stylesheets/app.less @@ -21,6 +21,10 @@ user-select: auto; } +.modal > .modal-dialog > .modal-content { + box-shadow: @modalDialogDropdownShadow; +} + body { overflow: hidden; font-size: 0.8rem; diff --git a/packages/eez-studio-ui/_stylesheets/vars-dark.less b/packages/eez-studio-ui/_stylesheets/vars-dark.less index ffc59d47e..2deb2df39 100644 --- a/packages/eez-studio-ui/_stylesheets/vars-dark.less +++ b/packages/eez-studio-ui/_stylesheets/vars-dark.less @@ -112,3 +112,5 @@ @tipBoxColor: @textColor; @tipBoxBackgroundColor: #003100; @tipBoxBorderColor: #009400; + +@modalDialogDropdownShadow: 4px 4px 24px rgba(0, 0, 0, 0.8); diff --git a/packages/eez-studio-ui/_stylesheets/vars.less b/packages/eez-studio-ui/_stylesheets/vars.less index 16254911f..7cab9f738 100644 --- a/packages/eez-studio-ui/_stylesheets/vars.less +++ b/packages/eez-studio-ui/_stylesheets/vars.less @@ -108,3 +108,5 @@ @tipBoxColor: @textColor; @tipBoxBackgroundColor: #e6f6e6; @tipBoxBorderColor: #c3e6c3; + +@modalDialogDropdownShadow: 4px 4px 16px rgba(64, 64, 64, 0.5); diff --git a/packages/eez-studio-ui/dialog.tsx b/packages/eez-studio-ui/dialog.tsx index b049dc8f3..aeb61d6d6 100644 --- a/packages/eez-studio-ui/dialog.tsx +++ b/packages/eez-studio-ui/dialog.tsx @@ -34,6 +34,8 @@ export function showDialog(dialog: JSX.Element, opts?: IDialogOptions) { const root = createRoot(element); root.render(dialog); + let jsPanelDialog; + if (opts && opts.jsPanel) { element.style.position = "absolute"; element.style.width = "100%"; @@ -129,15 +131,28 @@ export function showDialog(dialog: JSX.Element, opts?: IDialogOptions) { } } - const dialog = (opts.jsPanel.modeless ? jsPanel : jsPanel.modal).create( - config - ); - - return [dialog, element, root]; + jsPanelDialog = ( + opts.jsPanel.modeless ? jsPanel : jsPanel.modal + ).create(config); } else { document.body.appendChild(element); - return [undefined, element, root]; } + + // Unmount dialog if the element is removed from the DOM + const intervalID = setInterval(() => { + let parentElement = element.parentElement; + while (parentElement) { + if (parentElement instanceof HTMLBodyElement) { + return; + } + parentElement = parentElement.parentElement; + } + + clearInterval(intervalID); + root.unmount(); + }, 100); + + return [jsPanelDialog, element, root]; } //////////////////////////////////////////////////////////////////////////////// @@ -302,6 +317,7 @@ export const BootstrapDialog = observer( additionalFooterControl?: React.ReactNode; backdrop?: "static" | boolean; className?: string; + modalContentStyle?: React.CSSProperties; }> { div: HTMLDivElement | null = null; form: HTMLFormElement | null = null; @@ -325,21 +341,36 @@ export const BootstrapDialog = observer( } }; + static openBootstrapDialogs: React.Component[] = []; + + get numOpenBootstrapDialogs() { + return BootstrapDialog.openBootstrapDialogs.filter( + genericDialog => genericDialog !== this + ).length; + } + componentDidMount() { const div = this.div; if (div) { $(div).on("shown.bs.modal", () => { + BootstrapDialog.openBootstrapDialogs.push(this); + setTimeout(this.setFocus); }); $(div).on("hidden.bs.modal", () => { + BootstrapDialog.openBootstrapDialogs.pop(); + const parent = div.parentElement as HTMLElement; parent.remove(); this.props.onCancel(); }); this.modal = new bootstrap.Modal(div, { - backdrop: this.props.backdrop ?? true + backdrop: + this.numOpenBootstrapDialogs == 0 + ? this.props.backdrop ?? true + : false }); this.modal.show(); } @@ -464,7 +495,14 @@ export const BootstrapDialog = observer( }} onKeyPress={this.onKeyPress} > -
+
{props.title && (
Promise | boolean | void; onCancel?: () => void; onValueChange?: (name: string, value: string) => void; + setOnChangeCallback?: ( + onChange: (fieldProperties: any, value: any) => void + ) => void; } export const GenericDialog = observer( @@ -234,6 +237,10 @@ export const GenericDialog = observer( this.fieldValues = fieldValues; this.errorMessages = undefined; + + if (this.props.setOnChangeCallback) { + this.props.setOnChangeCallback(this.onChange.bind(this)); + } } get values() { @@ -736,6 +743,9 @@ export function showGenericDialog(conf: { onOk?: (result: GenericDialogResult) => Promise; opts?: IDialogOptions; dialogContext?: any; + setOnChangeCallback?: ( + onChange: (fieldProperties: any, value: any) => void + ) => void; }) { return new Promise((resolve, reject) => { const [modalDialog] = showDialog( @@ -774,6 +784,7 @@ export function showGenericDialog(conf: { } reject(); }} + setOnChangeCallback={conf.setOnChangeCallback} />, conf.opts ); diff --git a/packages/project-editor/features/action/action.tsx b/packages/project-editor/features/action/action.tsx index 05be0b53c..c2f6a4877 100644 --- a/packages/project-editor/features/action/action.tsx +++ b/packages/project-editor/features/action/action.tsx @@ -214,16 +214,19 @@ export class Action extends Flow { type: PropertyType.Enum, enumItems: [ { - id: "native" + id: "flow" }, { - id: "flow" + id: "native" } ], enumDisallowUndefined: true, propertyGridGroup: specificGroup, disabled: (action: Action) => { - return isNotV1Project(action) && !hasFlowSupport(action); + return ( + (isNotV1Project(action) && !hasFlowSupport(action)) || + isDashboardProject(action) + ); } }, { @@ -288,6 +291,8 @@ export class Action extends Flow { } }, newItem: async (parent: IEezObject) => { + const projectStore = getProjectStore(parent); + const result = await showGenericDialog({ dialogDefinition: { title: "New Action", @@ -300,21 +305,38 @@ export class Action extends Flow { validators.invalidCharacters("."), validators.unique({}, parent) ] + }, + { + name: "implementationType", + type: "enum", + enumItems: [ + { + id: "flow", + label: "Flow" + }, + { + id: "native", + label: "Native" + } + ], + visible: () => + !projectStore.projectTypeTraits.isDashboard && + projectStore.projectTypeTraits.hasFlowSupport } ] }, - values: {} + values: { + implementationType: "flow" + } }); - const projectStore = getProjectStore(parent); - const actionProperties: Partial = Object.assign( { name: result.values.name }, projectStore.projectTypeTraits.hasFlowSupport ? ({ - implementationType: "flow", + implementationType: result.values.implementationType, components: [], connectionLine: [] } as Partial) diff --git a/packages/project-editor/flow/component.tsx b/packages/project-editor/flow/component.tsx index a2b90dd5a..145bc76c5 100644 --- a/packages/project-editor/flow/component.tsx +++ b/packages/project-editor/flow/component.tsx @@ -1,13 +1,16 @@ import { MenuItem } from "@electron/remote"; import React from "react"; -import { observable, computed, makeObservable } from "mobx"; +import { observable, computed, makeObservable, runInAction } from "mobx"; import classNames from "classnames"; import { each } from "lodash"; import { validators } from "eez-studio-shared/validation"; import { BoundingRectBuilder, Point, Rect } from "eez-studio-shared/geometry"; -import { showGenericDialog } from "eez-studio-ui/generic-dialog"; +import { + IFieldProperties, + showGenericDialog +} from "eez-studio-ui/generic-dialog"; import * as notification from "eez-studio-ui/notification"; @@ -110,6 +113,7 @@ import { getComponentName } from "project-editor/flow/components/components-regi import { ProjectEditor } from "project-editor/project-editor-interface"; import { FLOW_ITERATOR_INDEX_VARIABLE } from "project-editor/features/variable/defs"; import type { + EnumItems, IActionComponentDefinition, IComponentProperty, IDashboardComponentContext @@ -164,6 +168,7 @@ import { } from "project-editor/store/serialization"; import { StylePropertyUI } from "project-editor/features/style/StylePropertyUI"; import { findVariable } from "project-editor/project/project"; +import type { Action } from "project-editor/features/action/action"; //////////////////////////////////////////////////////////////////////////////// @@ -2598,6 +2603,59 @@ export class EventHandler extends EezObject { return; } + function getActions() { + return project.actions.map(action => ({ + id: action.name, + label: action.name + })); + } + const actionEnumItems = observable.box([]); + actionEnumItems.set(getActions()); + + let onChangeCallback: (fieldProperties: any, value: any) => void; + + const actionProperty: IFieldProperties = { + name: "action", + type: "enum", + enumItems: actionEnumItems, + inputGroupButton: ( + + ), + visible: (values: any) => { + return values.handlerType == "action"; + } + }; + const result = await showGenericDialog({ dialogDefinition: { title: "New Event Handler", @@ -2619,17 +2677,7 @@ export class EventHandler extends EezObject { visible: () => project.projectTypeTraits.hasFlowSupport }, - { - name: "action", - type: "enum", - enumItems: project.actions.map(action => ({ - id: action.name, - label: action.name - })), - visible: (values: any) => { - return values.handlerType == "action"; - } - }, + actionProperty, { name: "userData", type: "number", @@ -2645,7 +2693,10 @@ export class EventHandler extends EezObject { : "action", userData: 0 }, - dialogContext: project + dialogContext: project, + setOnChangeCallback: callback => { + onChangeCallback = callback; + } }); const properties: Partial = {