import React from "react"; import { observer } from "mobx-react"; import * as FlexLayout from "flexlayout-react"; import { Menu, MenuItem } from "@electron/remote"; import classNames from "classnames"; import { action, computed, makeObservable, runInAction } from "mobx"; import { Messages } from "project-editor/ui-components/Output"; import { Toolbar } from "project-editor/project/ui/Toolbar"; import { Icon } from "eez-studio-ui/icon"; import { Loader } from "eez-studio-ui/loader"; import { Button } from "eez-studio-ui/button"; import { IExtension } from "eez-studio-shared/extensions/extension"; import * as notification from "eez-studio-ui/notification"; import { FlexLayoutContainer } from "eez-studio-ui/FlexLayout"; import { PageEditor, PageTabState } from "project-editor/features/page/PageEditor"; import { ProjectContext } from "project-editor/project/context"; import { Editor, LayoutModels, Section } from "project-editor/store"; import { PropertiesPanel } from "./PropertiesPanel"; import { ComponentsPalette } from "project-editor/flow/editor/ComponentsPalette"; import { BreakpointsPanel } from "project-editor/flow/debugger/BreakpointsPanel"; import { ThemesSideView } from "project-editor/features/style/theme"; import { getEditorComponent } from "project-editor/project/ui/EditorComponentFactory"; import { QueuePanel } from "project-editor/flow/debugger/QueuePanel"; import { WatchPanel } from "project-editor/flow/debugger/WatchPanel"; import { ActiveFlowsPanel } from "project-editor/flow/debugger/ActiveFlowsPanel"; import { LogsPanel } from "project-editor/flow/debugger/LogsPanel"; import { ListNavigation } from "project-editor/ui-components/ListNavigation"; import { VariablesTab } from "project-editor/features/variable/VariablesNavigation"; import { FlowStructureTab } from "project-editor/flow/FlowStructureTab"; import { StylesTab } from "project-editor/features/style/StylesNavigation"; import { FontsTab } from "project-editor/features/font/FontsNavigation"; import { BitmapsTab } from "project-editor/features/bitmap/BitmapsNavigation"; import { TextsTab } from "project-editor/features/texts/navigation"; import { ScpiTab } from "project-editor/features/scpi/ScpiNavigation"; import { InstrumentCommandsList } from "project-editor/features/instrument-commands/InstrumentCommandsNavigation"; import { ExtensionDefinitionsTab } from "project-editor/features/extension-definitions/extension-definitions"; import { ChangesTab } from "project-editor/features/changes/navigation"; import { SearchPanel } from "project-editor/project/ui/SearchPanel"; import { ReferencesPanel } from "project-editor/project/ui/ReferencesPanel"; import { downloadAndInstallExtension, extensionsManagerStore } from "home/extensions-manager/extensions-manager"; import { LVGLGroupsTab } from "project-editor/lvgl/groups"; import { settingsController } from "home/settings"; //////////////////////////////////////////////////////////////////////////////// export const ProjectEditorView = observer( class ProjectEditorView extends React.Component<{ showToolbar: boolean; }> { static contextType = ProjectContext; declare context: React.ContextType; render() { if (!this.context.project || !this.context.project._fullyLoaded) { return
; } if ( this.context.project.missingExtensions.length > 0 && !this.context.missingExtensionsResolved ) { return ; } if ( this.context.context.type != "project-editor" && !this.context.runtime ) { return
; } return (
{this.props.showToolbar && }
); } } ); //////////////////////////////////////////////////////////////////////////////// const Content = observer( class Content extends React.Component { static contextType = ProjectContext; declare context: React.ContextType; _prevPageTabState: PageTabState | undefined; componentDidMount(): void { this.context.editorsStore?.openInitialEditors(); this.context.editorsStore?.refresh(true); } factory = (node: FlexLayout.TabNode) => { var component = node.getComponent(); if (component === "pages") { return ( ); } if (component === "widgets") { return ( ); } if (component === "actions") { return ( ); } if (component === "flow-structure") { return ; } if (component === "variables") { return ; } if (component === "styles") { return ; } if (component === "fonts") { return ; } if (component === "bitmaps") { return ; } if (component === "changes") { return ; } if (component === "texts") { return ; } if (component === "scpi") { return ; } if (component === "instrument-commands") { return ; } if (component === "extension-definitions") { return ; } if (this.context.runtime) { if (component === "queue") { return ; } if (component === "watch") { return ; } if (component === "active-flows") { return ; } if (component === "logs") { return ; } } if (component === "propertiesPanel") { return ; } if (component === "componentsPalette") { return ; } if (component === "breakpointsPanel") { return ; } if (component === "themesSideView") { return ; } if (component === "checksMessages") { return ( ); } if (component === "outputMessages") { return ( ); } if (component === "search") { return ; } if (component === "references") { return ; } if (component === "editor") { const editor = this.context.editorsStore.tabIdToEditorMap.get( node.getId() ); node.setEventListener("visibility", (p: any) => { this.context.editorsStore.refresh(true); }); node.setEventListener("close", (p: any) => { this.context.editorsStore.refresh(true); }); if (editor) { let result = getEditorComponent( editor.object, editor.params ); if (result) { return ; } } return null; } if (component === "lvgl-groups") { return ; } return null; }; onRenderTab = ( node: FlexLayout.TabNode, renderValues: FlexLayout.ITabRenderValues ) => { if ( node.getId() == LayoutModels.CHECKS_TAB_ID || node.getId() == LayoutModels.OUTPUT_TAB_ID ) { const section = this.context.outputSectionsStore.getSection( node.getId() == LayoutModels.CHECKS_TAB_ID ? Section.CHECKS : Section.OUTPUT ); let icon; let numMessages; if (section.numErrors > 0) { icon = ; numMessages = section.numErrors; } else if (section.numWarnings > 0) { icon = ; numMessages = section.numWarnings; } else { icon = ( ); numMessages = 0; } renderValues.leading = section.loading ? ( ) : ( icon ); renderValues.content = section.name + (numMessages > 0 ? ` (${numMessages})` : ""); } else if ( node.getId() == LayoutModels.SEARCH_TAB_ID || node.getId() == LayoutModels.REFERENCES_TAB_ID ) { const section = this.context.outputSectionsStore.getSection( node.getId() == LayoutModels.SEARCH_TAB_ID ? Section.SEARCH : Section.REFERENCES ); renderValues.leading = section.loading ? ( ) : null; renderValues.content = section.name + (section.messages.searchResults.length > 0 ? ` (${section.messages.searchResults.length})` : ""); } else if (node.getId() == LayoutModels.DEBUGGER_LOGS_TAB_ID) { if (this.context.runtime && this.context.runtime.error) { renderValues.leading = (
); } } else if (node.getComponent() == "editor") { const editor = this.context.editorsStore.tabIdToEditorMap.get( node.getId() ); renderValues.content = (
{node.getName()}
); } }; onAuxMouseClick = ( node: | FlexLayout.TabNode | FlexLayout.TabSetNode | FlexLayout.BorderNode, event: React.MouseEvent ) => { if ( node instanceof FlexLayout.TabNode && node.getComponent() == "editor" ) { if (event.button == 1) { // delete tab on mouse middle click node.getModel().doAction( FlexLayout.Actions.deleteTab(node.getId()) ); } } }; onContextMenu = ( node: | FlexLayout.TabNode | FlexLayout.TabSetNode | FlexLayout.BorderNode, event: React.MouseEvent ) => { event.preventDefault(); event.stopPropagation(); if ( node instanceof FlexLayout.TabNode && node.getComponent() == "editor" ) { const editor = this.context.editorsStore.tabIdToEditorMap.get( node.getId() ); if (editor && !editor.permanent) { // open context menu const menu = new Menu(); menu.append( new MenuItem({ label: "Keep Tab Open", click: () => { runInAction(() => (editor.permanent = true)); this.context.editorsStore.tabsModel.doAction( FlexLayout.Actions.updateNodeAttributes( node.getId(), { config: editor.getConfig() } ) ); } }) ); menu.popup(); } } }; onModelChange = ( model: FlexLayout.Model, action: FlexLayout.Action ) => { this.context.editorsStore.refresh(false); }; render() { if ( this.context.runtime && !this.context.runtime.isDebuggerActive ) { const pageTabState = new PageTabState( this.context.runtime.selectedPage, this._prevPageTabState ? this._prevPageTabState.transform : undefined ); if (this.context.projectTypeTraits.isLVGL) { // prevent flickering when changing selected page this._prevPageTabState = pageTabState; } return ( ); } // to make sure onRenderTab is observable this.context.editorsStore.editors.forEach(editor => { editor.permanent; }); const checksSection = this.context.outputSectionsStore.getSection( Section.CHECKS ); checksSection.numErrors; checksSection.numWarnings; checksSection.loading; this.context.lastRevisionStable; this.context.lastSuccessfulBuildRevision; const sectionOutput = this.context.outputSectionsStore.getSection( Section.OUTPUT ); sectionOutput.numErrors; sectionOutput.numWarnings; sectionOutput.loading; const sectionSearch = this.context.outputSectionsStore.getSection( Section.SEARCH ); sectionSearch.messages.searchResults.length; sectionSearch.loading; const sectionReferences = this.context.outputSectionsStore.getSection(Section.REFERENCES); sectionReferences.messages.searchResults.length; sectionReferences.loading; this.context.runtime && this.context.runtime.error; return (
); } } ); const MissingExtensions = observer( class MissingExtensions extends React.Component { static contextType = ProjectContext; declare context: React.ContextType; constructor(props: any) { super(props); makeObservable(this, { installableExtensions: computed }); } get installableExtensions() { return extensionsManagerStore.extensionsVersionsCatalogBuilder .get() .filter(extensionsVersions => this.context.project.missingExtensions.find( missingExtension => extensionsVersions.versionInFocus.name == missingExtension.extensionName ) ); } installExtension = async (extensionToInstall: IExtension) => { const progressToastId = notification.info("Updating...", { autoClose: false }); await new Promise(resolve => setTimeout(resolve, 500)); await downloadAndInstallExtension( extensionToInstall, progressToastId ); if (this.installableExtensions.length == 0) { this.context.reloadProject(); } }; installAll = () => {}; render() { return (
{this.context.project.missingExtensions.length > 1 ? "Install missing extensions" : "Install missing extension"} :
{this.context.project.missingExtensions.map( extension => { const installableExtension = this.installableExtensions.find( installableExtension => installableExtension.versionInFocus .name === extension.extensionName )?.versionInFocus; return (
{extension.extensionName} {installableExtension ? ( ) : (
Unknown extension
)}
); } )}
{this.installableExtensions.length >= 1 && ( )}
); } } );