import path from "path"; import fs from "fs"; import { clipboard, ipcRenderer } from "electron"; import { Menu, MenuItem } from "@electron/remote"; import React from "react"; import { computed, action, observable, runInAction, makeObservable, autorun } from "mobx"; import { observer } from "mobx-react"; import { ButtonAction, IconAction } from "eez-studio-ui/action"; import { stringCompare } from "eez-studio-shared/string"; import { IListNode, List, ListContainer, ListItem } from "eez-studio-ui/list"; import { settingsController } from "home/settings"; import type { IMruItem } from "main/settings"; import { SearchInput } from "eez-studio-ui/search-input"; import { getProjectIcon } from "home/helper"; import { ProjectStore, loadProject } from "project-editor/store"; import { ProjectEditorTab, tabs } from "home/tabs-store"; import { initProjectEditor } from "project-editor/project-editor-bootstrap"; import { HOME_TAB_OPEN_ICON } from "project-editor/ui-components/icons"; //////////////////////////////////////////////////////////////////////////////// const SORT_ALPHA_ICON = ( ); const SORT_RECENT_ICON = ( ); //////////////////////////////////////////////////////////////////////////////// interface ProjectInfo { baseName: string; dirName: string; hasFlowSupport: boolean; } class OpenProjectsStore { selectedMruItem: IMruItem | undefined; selectedProjectInfo: ProjectInfo | undefined; searchText: string = ""; sortAlphabetically: boolean = false; constructor() { this.sortAlphabetically = localStorage.getItem("homeTabProjectsSort") == "alphabetically" ? true : false; makeObservable(this, { selectedMruItem: observable, selectedProjectInfo: observable, searchText: observable, sortAlphabetically: observable, mru: computed, mruAlpha: computed, allMruItems: computed, toggleSort: action, onSearchChange: action, removeFromList: action }); autorun(async () => { const mruItem = this.selectedMruItem; if (mruItem) { const isProject = mruItem.filePath.endsWith(".eez-project"); let extension = isProject ? ".eez-project" : ".eez-dashboard"; const baseName = path.basename(mruItem.filePath, extension); const dirName = path.dirname(mruItem.filePath); runInAction(() => { this.selectedProjectInfo = { baseName, dirName, hasFlowSupport: mruItem.hasFlowSupport }; }); try { const jsonStr = await fs.promises.readFile( mruItem.filePath, "utf8" ); await initProjectEditor(tabs, ProjectEditorTab); const projectStore = ProjectStore.create({ type: "read-only" }); const project = loadProject(projectStore, jsonStr, false); projectStore.setProject(project, ""); runInAction(() => { if (this.selectedProjectInfo) { this.selectedProjectInfo.hasFlowSupport = projectStore.projectTypeTraits.hasFlowSupport; } }); } catch (err) { console.error(err); } } else { runInAction(() => { this.selectedProjectInfo = undefined; }); } }); } get mruAlpha() { const mru = [...settingsController.mru]; mru.sort((mruItem1, mruItem2) => { const baseName1 = path.basename(mruItem1.filePath); const baseName2 = path.basename(mruItem2.filePath); return stringCompare(baseName1, baseName2); }); return mru; } get mru() { return this.sortAlphabetically ? this.mruAlpha : settingsController.mru; } get allMruItems() { return openProjectsStore.mru .filter( mruItem => mruItem.filePath .toLowerCase() .indexOf( openProjectsStore.searchText.trim().toLowerCase() ) != -1 ) .map(mruItem => ({ id: mruItem.filePath, data: mruItem, selected: mruItem == openProjectsStore.selectedMruItem })); } toggleSort = () => { this.sortAlphabetically = !this.sortAlphabetically; localStorage.setItem( "homeTabProjectsSort", this.sortAlphabetically ? "alphabetically" : "most-recent" ); }; onSearchChange = (event: any) => { this.searchText = $(event.target).val() as string; if (this.allMruItems.length > 0) { this.selectedMruItem = this.allMruItems[0].data; } }; editProject = () => { if (this.selectedMruItem) { ipcRenderer.send("open-file", this.selectedMruItem!.filePath); } }; runProject = () => { if (this.selectedMruItem && this.selectedMruItem.hasFlowSupport) { ipcRenderer.send("open-file", this.selectedMruItem!.filePath, true); } }; copyProjectPath = () => { if (this.selectedMruItem) { clipboard.writeText(this.selectedMruItem.filePath); } }; removeFromList = () => { if (openProjectsStore.selectedMruItem) { settingsController.removeItemFromMRU( openProjectsStore.selectedMruItem ); openProjectsStore.selectedMruItem = undefined; } }; } const openProjectsStore = new OpenProjectsStore(); //////////////////////////////////////////////////////////////////////////////// export const Projects = observer( class Projects extends React.Component { onContextMenu = (node: IListNode) => { runInAction(() => (openProjectsStore.selectedMruItem = node.data)); const menu = new Menu(); menu.append( new MenuItem({ label: "Edit Project", click: openProjectsStore.editProject }) ); if (node.data.hasFlowSupport) { menu.append( new MenuItem({ label: "Run Project", click: openProjectsStore.runProject }) ); } menu.append( new MenuItem({ label: "Copy Project Path", click: openProjectsStore.copyProjectPath }) ); menu.append( new MenuItem({ label: "Remove From List", click: openProjectsStore.removeFromList }) ); menu.popup(); }; render() { return ( { openProjectsStore.searchText = ""; })} onChange={openProjectsStore.onSearchChange} onKeyDown={openProjectsStore.onSearchChange} /> { ipcRenderer.send("open-project"); }} /> ) => { let mruItem = node.data; const isProject = mruItem.filePath.endsWith( ".eez-project" ); let extension = isProject ? ".eez-project" : ".eez-dashboard"; const baseName = path.basename( mruItem.filePath, extension ); return ( {baseName} {extension} {path.dirname( mruItem.filePath )} } /> ); }} selectNode={(node: IListNode) => { runInAction( () => (openProjectsStore.selectedMruItem = node.data) ); }} onContextMenu={this.onContextMenu} onDoubleClick={openProjectsStore.editProject} > ); } } ); export const ProjectInfo = observer( class ProjectInfo extends React.Component { render() { return ( {openProjectsStore.selectedProjectInfo && ( {openProjectsStore.selectedProjectInfo .hasFlowSupport && ( )} )} ); } } );