import React from "react"; import ReactDOM from "react-dom"; import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { ButtonAction, IconAction } from "eez-studio-ui/action"; import { BuildConfiguration } from "project-editor/project/project"; import { ProjectContext } from "project-editor/project/context"; import { PageTabState } from "project-editor/features/page/PageEditor"; import { getChildren, getObjectIcon, objectToString } from "project-editor/store"; import { RenderVariableStatus } from "project-editor/features/variable/global-variable-status"; import { FlowTabState } from "project-editor/flow/flow-tab-state"; import { RuntimeType } from "project-editor/project/project-type-traits"; import { PROJECT_EDITOR_SCRAPBOOK, RUN_ICON } from "project-editor/ui-components/icons"; import { getEditorComponent } from "./EditorComponentFactory"; import { getId } from "project-editor/core/object"; import type { IObjectVariableValue } from "eez-studio-types"; import { getObjectVariableTypeFromType } from "project-editor/features/variable/value-type"; import { isScrapbookItemFilePath, showScrapbookManager, model as scrapbookModel } from "project-editor/store/scrapbook"; import { closest } from "eez-studio-shared/dom"; import { Icon } from "eez-studio-ui/icon"; //////////////////////////////////////////////////////////////////////////////// export const Toolbar = observer( class Toolbar extends React.Component { static contextType = ProjectContext; declare context: React.ContextType; get globalVariableStatuses() { let globalVariablesStatus: React.ReactNode[] = []; for (const variable of this.context.project.allGlobalVariables) { const objectVariableType = getObjectVariableTypeFromType( this.context, variable.type ); if (objectVariableType) { let objectVariableValue: IObjectVariableValue | undefined = this.context.dataContext.get(variable.fullName); if (objectVariableValue) { const managedValue = objectVariableType.getValue ? objectVariableType.getValue(objectVariableValue) : undefined; if (managedValue) { objectVariableValue = managedValue; } } globalVariablesStatus.push( { if (objectVariableType.editConstructorParams) { const constructorParams = await objectVariableType.editConstructorParams( variable, objectVariableValue?.constructorParams || objectVariableValue, true ); if (constructorParams !== undefined) { this.context.runtime!.setObjectVariableValue( variable.fullName, objectVariableType.createValue( constructorParams, true ) ); } } }} /> ); } } return globalVariablesStatus; } render() { const showEditorButtons = this.context.context.type != "run-tab" && !this.context.project._isDashboardBuild && !( this.context.runtime && !this.context.runtime.isDebuggerActive ); const showRunEditSwitchControls = this.context.context.type != "run-tab" && !this.context.project._isDashboardBuild && this.context.projectTypeTraits.runtimeType != RuntimeType.NONE; const globalVariablesStatuses = this.context.runtime ? this.globalVariableStatuses : []; if ( !showEditorButtons && !showRunEditSwitchControls && globalVariablesStatuses.length == 0 ) { return null; } return ( ); } } ); //////////////////////////////////////////////////////////////////////////////// const EditorButtons = observer( class EditorButtons extends React.Component { static contextType = ProjectContext; declare context: React.ContextType; constructor(props: any) { super(props); makeObservable(this, { featureItems: computed }); } setFrontFace = action((enabled: boolean) => { if (this.pageTabState) { this.pageTabState.frontFace = enabled; } }); get pageTabState() { const editorState = this.context.editorsStore.activeEditor?.state; if (editorState instanceof PageTabState) { return editorState as PageTabState; } return undefined; } get flowTabState() { const editorState = this.context.editorsStore.activeEditor?.state; if (editorState instanceof FlowTabState) { return editorState as FlowTabState; } return undefined; } get isBuildConfigurationSelectorVisible() { return false; } onSelectedBuildConfigurationChange(event: any) { this.context.uiStateStore.setSelectedBuildConfiguration( event.target.value ); } toggleShowTimeline = action(() => { if (this.pageTabState) { this.pageTabState.timeline.isEditorActive = !this.pageTabState.timeline.isEditorActive; } }); get isShowTimeline() { if (this.pageTabState) { return this.pageTabState.timeline.isEditorActive; } return false; } get featureItems() { if (this.context.runtime) { return undefined; } let featureItems = getChildren(this.context.project).filter( object => getObjectIcon(object) && getEditorComponent(object, undefined) && !( object == this.context.project.userPages || object == this.context.project.userWidgets || object == this.context.project.actions || object == this.context.project.variables || object == this.context.project.styles || object == this.context.project.lvglStyles || object == this.context.project.fonts || object == this.context.project.bitmaps || object == this.context.project.texts || object == this.context.project.scpi || object == this.context.project.extensionDefinitions || object == this.context.project.changes ) ); // push Settings to the end if (featureItems) { const settingsIndex = featureItems.findIndex( item => item == this.context.project.settings ); if (settingsIndex != -1) { featureItems.splice(settingsIndex, 1); featureItems.push(this.context.project.settings); } } return featureItems; } render() { let configurations = this.context.project.settings.build.configurations.map( (item: BuildConfiguration) => { return ( ); } ); return (
{!this.context.runtime && (
this.context.save()} enabled={this.context.isModified} />
)} {!this.context.runtime && ( <>
this.context.undoManager.undo() } enabled={this.context.undoManager.canUndo} /> this.context.undoManager.redo() } enabled={this.context.undoManager.canRedo} />
{false && ( )}
showScrapbookManager()} selected={scrapbookModel.isVisible} />
)} {!this.context.runtime && this.isBuildConfigurationSelectorVisible && (
)} {!this.context.runtime && (
{!this.context.projectTypeTraits.isDashboard && ( this.context.check()} enabled={this.context.project._fullyLoaded} /> )} {!( this.context.filePath && isScrapbookItemFilePath(this.context.filePath) ) && ( this.context.build()} enabled={this.context.project._fullyLoaded} /> )}
)} {this.context.projectTypeTraits.isResource && this.context.project.micropython && (
this.context.project.micropython.runScript() } enabled={this.context.project._fullyLoaded} />
)} {this.context.projectTypeTraits.hasFlowSupport && ( <> {this.pageTabState && ( <>
this.setFrontFace(true) } selected={ this.pageTabState.frontFace } /> this.setFrontFace(false) } selected={ !this.pageTabState.frontFace } />
{!this.flowTabState?.flowState && (
} iconSize={24} onClick={() => this.toggleShowTimeline() } selected={this.isShowTimeline} />
)} )} {(this.flowTabState || (this.pageTabState && !this.pageTabState.frontFace)) && (
(this.context.uiStateStore.showComponentDescriptions = !this.context.uiStateStore .showComponentDescriptions) )} selected={ this.context.uiStateStore .showComponentDescriptions } />
)} )} {!this.context.runtime && this.context.project.texts?.languages.length > 0 && (
)} {this.featureItems && (
{this.featureItems.map(featureItem => { const title = objectToString(featureItem); let icon = getObjectIcon(featureItem); const editorComponent = getEditorComponent( featureItem, undefined )!; const onClick = action(() => { if (editorComponent) { this.context.editorsStore.openEditor( editorComponent.object, editorComponent.subObject ); } }); const isActive = editorComponent && this.context.editorsStore.activeEditor && this.context.editorsStore.getEditorByObject( editorComponent.object ) == this.context.editorsStore.activeEditor; return ( ); })}
)} {this.pageTabState && ( )}
); } } ); const SelectLanguage = observer( class SelectLanguage extends React.Component { static contextType = ProjectContext; declare context: React.ContextType; render() { return ( ); } } ); const PageZoomButton = observer( class PageZoomButton extends React.Component<{ pageTabState: PageTabState; }> { static contextType = ProjectContext; declare context: React.ContextType; buttonRef = React.createRef(); dropDownRef = React.createRef(); dropDownOpen: boolean | undefined = false; dropDownLeft = 0; dropDownTop = 0; dropDownWidth = 0; zoomInput: string | undefined; constructor(props: any) { super(props); makeObservable(this, { dropDownOpen: observable, dropDownLeft: observable, dropDownTop: observable, dropDownWidth: observable, zoomInput: observable }); } get zoom() { return this.globalZoom ? this.context.uiStateStore.flowZoom : this.props.pageTabState.transform.scale; } set zoom(value: number) { runInAction(() => { this.context.uiStateStore.flowZoom = value; }); if (!this.globalZoom) { const newTransform = this.props.pageTabState.transform.clone(); newTransform.scale = value; runInAction(() => { this.props.pageTabState.transform = newTransform; }); } } get globalZoom() { return this.context.uiStateStore.globalFlowZoom; } set globalZoom(value: boolean) { runInAction(() => { if (value) { this.context.uiStateStore.flowZoom = this.zoom; } else { for (const page of this.context.project.pages) { if (page == this.props.pageTabState.flow) { const newTransform = this.props.pageTabState.transform.clone(); newTransform.scale = this.zoom; runInAction(() => { this.props.pageTabState.transform = newTransform; }); } else { let uiState = this.context.uiStateStore.getObjectUIState( page, "flow-state" ); if (!uiState) { uiState = {}; } uiState.transform = { translate: uiState.transform?.translate, scale: this.zoom }; runInAction(() => { this.context.uiStateStore.updateObjectUIState( page, "flow-state", uiState ); }); } } } this.context.uiStateStore.globalFlowZoom = value; }); } setDropDownOpen = action((open: boolean) => { if (this.dropDownOpen === false) { document.removeEventListener( "pointerdown", this.onDocumentPointerDown, true ); } this.dropDownOpen = open; if (this.dropDownOpen) { document.addEventListener( "pointerdown", this.onDocumentPointerDown, true ); } }); openDropdown = action(() => { const buttonEl = this.buttonRef.current; if (!buttonEl) { return; } const dropDownEl = this.dropDownRef.current; if (!dropDownEl) { return; } this.setDropDownOpen(!this.dropDownOpen); if (this.dropDownOpen) { const rectInputGroup = buttonEl.parentElement!.getBoundingClientRect(); this.dropDownLeft = rectInputGroup.left; this.dropDownTop = rectInputGroup.bottom; this.dropDownWidth = rectInputGroup.width; if ( this.dropDownLeft + this.dropDownWidth > window.innerWidth ) { this.dropDownLeft = window.innerWidth - this.dropDownWidth; } const DROP_DOWN_HEIGHT = 270; if ( this.dropDownTop + DROP_DOWN_HEIGHT + 20 > window.innerHeight ) { this.dropDownTop = window.innerHeight - (DROP_DOWN_HEIGHT + 20); } } }); onDocumentPointerDown = action((event: MouseEvent) => { if (this.dropDownOpen) { if ( !closest( event.target, el => this.buttonRef.current == el || this.dropDownRef.current == el ) ) { event.preventDefault(); event.stopPropagation(); this.setDropDownOpen(false); } } }); render() { const portal = ReactDOM.createPortal(
    { this.zoomInput = event.target.value; })} onKeyDown={event => { if (event.key === "Enter") { let value = parseInt( this.zoomInput!.replace("%", "") ); if (value) { if (value < 5) value = 5; else if (value > 1600) value = 1600; this.zoom = value / 100; } this.zoomInput = undefined; this.setDropDownOpen(false); } }} />

    {[10, 25, 50, 75, 100, 150, 200, 400, 800, 1600].map( zoom => (
  • { this.zoom = zoom / 100; this.setDropDownOpen(false); }} > Zoom to {zoom}%
  • ) )}
  • { this.globalZoom = !this.globalZoom; this.setDropDownOpen(false); }} > {this.globalZoom ? ( ) : ( )} Global zoom
, document.body ); return (
{portal}
); } } ); //////////////////////////////////////////////////////////////////////////////// const RunEditSwitchControls = observer( class RunEditSwitchControls extends React.Component { static contextType = ProjectContext; declare context: React.ContextType; render() { const iconSize = 30; return (
} iconSize={iconSize} onClick={this.context.onSetDebuggerMode} selected={ this.context.runtime && this.context.runtime.isDebuggerActive } attention={ !!( this.context.runtime && this.context.runtime.error ) } />
); } } ); ////////////////////////////////////////////////////////////////////////////////