From 8188c5328ee477a16f12d11e87960b363af0e880 Mon Sep 17 00:00:00 2001 From: corn-potage Date: Tue, 7 May 2024 12:24:48 -0700 Subject: [PATCH 01/29] added podman modal --- assets/locales/en/translation.json | 7 +- package-lock.json | 4 +- .../Generics/redesign/Modal/modal.css.ts | 3 + .../ModalManager/ModalManager.tsx | 27 ++-- .../ModalManager/PodmanModal.tsx | 62 ++++++++ .../ModalManager/UpdatePodmanModal.tsx | 32 ---- .../ModalManager/modalUtils.tsx | 40 ++--- .../NodePackageScreen/NodePackageScreen.tsx | 112 +++++++------- .../Presentational/NodeScreen/NodeScreen.tsx | 110 +++++++------- .../PodmanInstallation/PodmanInstallation.tsx | 140 ++++++++++-------- .../podmanInstallation.css.ts | 5 + .../PodmanModal/PodmanWrapper.tsx | 21 +++ .../UpdatePodman/UpdatePodman.tsx | 34 ----- .../UpdatePodman/updatePodman.css.ts | 45 ------ src/renderer/state/modal.ts | 1 + 15 files changed, 323 insertions(+), 320 deletions(-) create mode 100644 src/renderer/Presentational/ModalManager/PodmanModal.tsx delete mode 100644 src/renderer/Presentational/ModalManager/UpdatePodmanModal.tsx create mode 100644 src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx delete mode 100644 src/renderer/Presentational/UpdatePodman/UpdatePodman.tsx delete mode 100644 src/renderer/Presentational/UpdatePodman/updatePodman.css.ts diff --git a/assets/locales/en/translation.json b/assets/locales/en/translation.json index eeb11560e..814463299 100644 --- a/assets/locales/en/translation.json +++ b/assets/locales/en/translation.json @@ -166,5 +166,10 @@ "CalculatingDataSize": "(calculating data size...)", "KeepNodeData": "Keep node related chain data {{data}}", "NoResults": "No results", - "TrySearching": "Try searching for another keyword or clear all filters" + "TrySearching": "Try searching for another keyword or clear all filters", + "RunningOutdatedPodman": "You are running an outdated version of Podman", + "CurrentPodman": "Your current Podman installation ({{currentPodmanVersion}}) is incompatible with NiceNode and requires version {{requiredPodmanVersion}} or higher for it to run.", + "PodmanIsRequiredComponent": "Podman is a required component for NiceNode to run the many client options. Podman facilitates the running of containers within a virtualised Linux environment and will operate in the background.", + "DownloadAndUpdate": "Download and update", + "PodmanUpdate": "Podman update" } diff --git a/package-lock.json b/package-lock.json index 4178a62ac..88f6001a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nice-node", - "version": "6.0.0-alpha", + "version": "6.0.1-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nice-node", - "version": "6.0.0-alpha", + "version": "6.0.1-alpha", "license": "MIT", "dependencies": { "@reduxjs/toolkit": "^2.2.3", diff --git a/src/renderer/Generics/redesign/Modal/modal.css.ts b/src/renderer/Generics/redesign/Modal/modal.css.ts index 09933bd15..90c2e075a 100644 --- a/src/renderer/Generics/redesign/Modal/modal.css.ts +++ b/src/renderer/Generics/redesign/Modal/modal.css.ts @@ -44,6 +44,9 @@ export const modalContentStyle = style({ '&.failSystemRequirements': { width: '380px', }, + '&.podman': { + width: '624px', + }, }, }); diff --git a/src/renderer/Presentational/ModalManager/ModalManager.tsx b/src/renderer/Presentational/ModalManager/ModalManager.tsx index b755430e3..cb99e337f 100644 --- a/src/renderer/Presentational/ModalManager/ModalManager.tsx +++ b/src/renderer/Presentational/ModalManager/ModalManager.tsx @@ -1,16 +1,17 @@ -import { useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { useCallback } from "react"; +import { useSelector } from "react-redux"; -import { useAppDispatch } from '../../state/hooks'; -import { getModalState, setModalState } from '../../state/modal'; -import { AddNodeModal } from './AddNodeModal'; -import { AlphaBuildModal } from './AlphaBuildModal'; -import FailSystemRequirementsModal from './FailSystemRequirementsModal'; -import { NodeSettingsModal } from './NodeSettingsModal'; -import { PreferencesModal } from './PreferencesModal'; -import { RemoveNodeModal } from './RemoveNodeModal'; -import { ResetConfigModal } from './ResetConfigModal'; -import { modalRoutes } from './modalUtils'; +import { useAppDispatch } from "../../state/hooks"; +import { getModalState, setModalState } from "../../state/modal"; +import { AddNodeModal } from "./AddNodeModal"; +import { AlphaBuildModal } from "./AlphaBuildModal"; +import FailSystemRequirementsModal from "./FailSystemRequirementsModal"; +import { NodeSettingsModal } from "./NodeSettingsModal"; +import { PreferencesModal } from "./PreferencesModal"; +import { RemoveNodeModal } from "./RemoveNodeModal"; +import { ResetConfigModal } from "./ResetConfigModal"; +import { PodmanModal } from "./PodmanModal"; +import { modalRoutes } from "./modalUtils"; const ModalManager = () => { const { isModalOpen, screen } = useSelector(getModalState); @@ -40,6 +41,8 @@ const ModalManager = () => { return ; case modalRoutes.failSystemRequirements: return ; + case modalRoutes.podman: + return ; case modalRoutes.addValidator: return null; case modalRoutes.clientVersions: diff --git a/src/renderer/Presentational/ModalManager/PodmanModal.tsx b/src/renderer/Presentational/ModalManager/PodmanModal.tsx new file mode 100644 index 000000000..dbb65f5a5 --- /dev/null +++ b/src/renderer/Presentational/ModalManager/PodmanModal.tsx @@ -0,0 +1,62 @@ +import { useState, useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { Modal } from "../../Generics/redesign/Modal/Modal.js"; +import electron from "../../electronGlobal.js"; +import { reportEvent } from "../../events/reportEvent.js"; +import PodmanWrapper from "../PodmanModal/PodmanWrapper.js"; +import { type ModalConfig, modalOnChangeConfig } from "./modalUtils.js"; + +type Props = { + modalOnClose: () => void; + data: { view: string }; +}; + +export const PodmanModal = ({ modalOnClose, data }: Props) => { + const [modalConfig, setModalConfig] = useState({}); + const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(false); + const { t } = useTranslation(); + const modalTitle = + data.view === "update" ? "Update Podman" : t("RemoveThisNode"); + const buttonSaveLabel = t("Done"); + + const modalOnSaveConfig = async (updatedConfig: ModalConfig | undefined) => { + try { + console.log("set some kind of setting here?"); + } catch (err) { + console.error(err); + throw new Error( + "There was an error removing the node. Try again and please report the error to the NiceNode team in Discord.", + ); + } + modalOnClose(); + }; + + const disableSaveButton = useCallback((value: boolean) => { + setIsSaveButtonDisabled(value); + }, []); + + return ( + + { + modalOnChangeConfig( + config, + modalConfig, + setModalConfig, + save, + modalOnSaveConfig, + ); + }} + disableSaveButton={disableSaveButton} + /> + + ); +}; diff --git a/src/renderer/Presentational/ModalManager/UpdatePodmanModal.tsx b/src/renderer/Presentational/ModalManager/UpdatePodmanModal.tsx deleted file mode 100644 index d1d286abc..000000000 --- a/src/renderer/Presentational/ModalManager/UpdatePodmanModal.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Modal } from '../../Generics/redesign/Modal/Modal'; -import electron from '../../electronGlobal'; -import UpdatePodman from '../UpdatePodman/UpdatePodman'; -import { modalRoutes } from './modalUtils'; - -type Props = { - modalOnClose: () => void; -}; - -export const AlphaBuildModal = ({ modalOnClose }: Props) => { - const buttonSaveLabel = 'I Understand'; - - const modalOnSaveConfig = async () => { - await electron.getSetHasSeenAlphaModal(true); - console.log('save!'); - modalOnClose(); - }; - - return ( - - - - ); -}; diff --git a/src/renderer/Presentational/ModalManager/modalUtils.tsx b/src/renderer/Presentational/ModalManager/modalUtils.tsx index 592f99eb2..51cc4fbef 100644 --- a/src/renderer/Presentational/ModalManager/modalUtils.tsx +++ b/src/renderer/Presentational/ModalManager/modalUtils.tsx @@ -1,12 +1,12 @@ -import type React from 'react'; -import type Node from '../../../common/node'; -import type { NodePackage } from '../../../common/node'; +import type React from "react"; +import type Node from "../../../common/node"; +import type { NodePackage } from "../../../common/node"; import type { NodeLibrary, NodePackageLibrary, -} from '../../../main/state/nodeLibrary'; -import type { ThemeSetting } from '../../../main/state/settings'; -import type { ClientSelections } from '../AddNodeConfiguration/AddNodeConfiguration'; +} from "../../../main/state/nodeLibrary"; +import type { ThemeSetting } from "../../../main/state/settings"; +import type { ClientSelections } from "../AddNodeConfiguration/AddNodeConfiguration"; export interface ModalConfig { node?: string; @@ -29,18 +29,18 @@ export interface ModalConfig { } export const modalRoutes = Object.freeze({ - addNode: 'addNode', - nodeSettings: 'nodeSettings', - preferences: 'preferences', - addValidator: 'addValidator', - clientVersions: 'clientVersions', - stopNode: 'stopNode', - removeNode: 'removeNode', - resetConfig: 'resetConfig', - updateUnavailable: 'updateUnavailable', - failSystemRequirements: 'failSystemRequirements', - alphaBuild: 'alphaBuild', - updatePodman: 'updatePodman', + addNode: "addNode", + nodeSettings: "nodeSettings", + preferences: "preferences", + addValidator: "addValidator", + clientVersions: "clientVersions", + stopNode: "stopNode", + removeNode: "removeNode", + resetConfig: "resetConfig", + updateUnavailable: "updateUnavailable", + failSystemRequirements: "failSystemRequirements", + alphaBuild: "alphaBuild", + podman: "podman", }); /* Use this to change config settings, saved temporarily in the modal file with backend apis until it's saved by modalOnSaveConfig @@ -55,7 +55,7 @@ export const modalOnChangeConfig = async ( modalOnSaveConfig?: (newConfig: ModalConfig) => Promise, ) => { if (!setModalConfig || !modalConfig) { - throw new Error('modal config is not defined'); + throw new Error("modal config is not defined"); } let updatedConfig = {}; @@ -74,7 +74,7 @@ export const modalOnChangeConfig = async ( // }; // } console.log( - 'modalOnChangeConfig: config, modalConfig, updatedConfig', + "modalOnChangeConfig: config, modalConfig, updatedConfig", config, modalConfig, updatedConfig, diff --git a/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx b/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx index e8f5b8e71..482a4fe6c 100644 --- a/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx +++ b/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx @@ -1,38 +1,38 @@ // import { useTranslation } from 'react-i18next'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from "react"; // import { NodeStatus } from '../common/node'; -import { useTranslation } from 'react-i18next'; -import { NodeStatus } from '../../../common/node'; -import Button from '../../Generics/redesign/Button/Button'; -import type { ClientProps, NodeAction } from '../../Generics/redesign/consts'; -import type { NodeBackgroundId } from '../../assets/images/nodeBackgrounds'; -import electron from '../../electronGlobal'; +import { useTranslation } from "react-i18next"; +import { NodeStatus } from "../../../common/node"; +import Button from "../../Generics/redesign/Button/Button"; +import type { ClientProps, NodeAction } from "../../Generics/redesign/consts"; +import type { NodeBackgroundId } from "../../assets/images/nodeBackgrounds"; +import electron from "../../electronGlobal"; // import { useGetNodesQuery } from './state/nodeService'; -import { useAppDispatch, useAppSelector } from '../../state/hooks'; -import { setModalState } from '../../state/modal'; +import { useAppDispatch, useAppSelector } from "../../state/hooks"; +import { setModalState } from "../../state/modal"; import { selectIsAvailableForPolling, selectSelectedNodePackage, selectUserNodes, -} from '../../state/node'; +} from "../../state/node"; import { useGetExecutionIsSyncingQuery, useGetExecutionLatestBlockQuery, useGetExecutionPeersQuery, useGetNodeVersionQuery, -} from '../../state/services'; -import { useGetIsPodmanRunningQuery } from '../../state/settingsService'; -import { hexToDecimal } from '../../utils'; -import ContentMultipleClients from '../ContentMultipleClients/ContentMultipleClients'; +} from "../../state/services"; +import { useGetIsPodmanRunningQuery } from "../../state/settingsService"; +import { hexToDecimal } from "../../utils"; +import ContentMultipleClients from "../ContentMultipleClients/ContentMultipleClients"; // import { useGetNetworkConnectedQuery } from './state/network'; -import type { SingleNodeContent } from '../ContentSingleClient/ContentSingleClient'; +import type { SingleNodeContent } from "../ContentSingleClient/ContentSingleClient"; import { container, contentContainer, descriptionFont, titleFont, -} from './NodePackageScreen.css'; +} from "./NodePackageScreen.css"; let alphaModalRendered = false; @@ -47,7 +47,7 @@ const NodePackageScreen = () => { const [sFormattedServices, setFormattedServices] = useState(); // we will bring these vars back in the future const [sIsSyncing, setIsSyncing] = useState(); - const [sSyncPercent, setSyncPercent] = useState(''); + const [sSyncPercent, setSyncPercent] = useState(""); const [sPeers, setPeers] = useState(); const [sDiskUsed, setDiskUsed] = useState(0); const [sCpuPercentUsed, setCpuPercentUsed] = useState(0); @@ -82,7 +82,7 @@ const NodePackageScreen = () => { isPodmanRunning = qIsPodmanRunning.data; } // temporary until network is set at the node package level - const [sNetworkNodePackage, setNetworkNodePackage] = useState(''); + const [sNetworkNodePackage, setNetworkNodePackage] = useState(""); useEffect(() => { if (selectedNodePackage?.config?.configValuesMap?.network) { @@ -90,7 +90,7 @@ const NodePackageScreen = () => { selectedNodePackage?.config?.configValuesMap?.network, ); } else { - setNetworkNodePackage(''); + setNetworkNodePackage(""); } }, [selectedNodePackage]); // use to show if internet is disconnected @@ -120,7 +120,7 @@ const NodePackageScreen = () => { useEffect(() => { if (!sIsAvailableForPolling) { // clear all node data when it becomes unavailable to get - setSyncPercent(''); + setSyncPercent(""); setIsSyncing(undefined); setPeers(undefined); setLatestBlockNumber(0); @@ -128,35 +128,35 @@ const NodePackageScreen = () => { }, [sIsAvailableForPolling]); useEffect(() => { - console.log('qExecutionIsSyncing: ', qExecutionIsSyncing); + console.log("qExecutionIsSyncing: ", qExecutionIsSyncing); if (qExecutionIsSyncing.isError) { - setSyncPercent(''); + setSyncPercent(""); setIsSyncing(undefined); return; } const syncingData = qExecutionIsSyncing.data; - if (typeof syncingData === 'object') { + if (typeof syncingData === "object") { setSyncPercent(syncingData.syncPercent); setIsSyncing(syncingData.isSyncing); } else if (syncingData === false) { // for nodes that do not have sync percent or other sync data - setSyncPercent(''); + setSyncPercent(""); setIsSyncing(false); } else { - setSyncPercent(''); + setSyncPercent(""); setIsSyncing(undefined); } }, [qExecutionIsSyncing]); useEffect(() => { - console.log('qExecutionPeers: ', qExecutionPeers.data); + console.log("qExecutionPeers: ", qExecutionPeers.data); if (qExecutionPeers.isError) { setPeers(undefined); return; } - if (typeof qExecutionPeers.data === 'string') { + if (typeof qExecutionPeers.data === "string") { setPeers(qExecutionPeers.data); - } else if (typeof qExecutionPeers.data === 'number') { + } else if (typeof qExecutionPeers.data === "number") { setPeers(qExecutionPeers.data.toString()); } else { setPeers(undefined); @@ -188,14 +188,14 @@ const NodePackageScreen = () => { let latestBlockNum = 0; if ( blockNumber && - typeof blockNumber === 'string' && - rpcTranslation === 'eth-l1' + typeof blockNumber === "string" && + rpcTranslation === "eth-l1" ) { latestBlockNum = hexToDecimal(blockNumber); } else if ( slotNumber && - typeof slotNumber === 'string' && - rpcTranslation === 'eth-l1-beacon' + typeof slotNumber === "string" && + rpcTranslation === "eth-l1-beacon" ) { latestBlockNum = Number.parseFloat(slotNumber); } @@ -208,15 +208,15 @@ const NodePackageScreen = () => { const onNodeAction = useCallback( (action: NodeAction) => { - console.log('NodeAction for node: ', action, selectedNodePackage); + console.log("NodeAction for node: ", action, selectedNodePackage); if (selectedNodePackage) { - if (action === 'start') { + if (action === "start") { electron.startNode(selectedNodePackage.id); - } else if (action === 'stop') { + } else if (action === "stop") { electron.stopNode(selectedNodePackage.id); - } else if (action === 'logs') { + } else if (action === "logs") { // show logs - } else if (action === 'settings') { + } else if (action === "settings") { // show settings } } @@ -242,7 +242,7 @@ const NodePackageScreen = () => { id: service.node.id, name: service.node.spec.specId, displayName: service.node.spec.displayName as NodeBackgroundId, - version: '', + version: "", nodeType: service.serviceName, status: { running: @@ -252,7 +252,7 @@ const NodePackageScreen = () => { node?.status === NodeStatus.stopped || node?.status === NodeStatus.stopping, updating: node?.status === NodeStatus.updating, - error: node?.status.includes('error'), + error: node?.status.includes("error"), // synchronized: !sIsSyncing && parseFloat(sSyncPercent) > 99.9, }, stats: {}, @@ -267,7 +267,7 @@ const NodePackageScreen = () => { dispatch( setModalState({ isModalOpen: true, - screen: { route: 'alphaBuild', type: 'info' }, + screen: { route: "alphaBuild", type: "info" }, }), ); alphaModalRendered = true; @@ -278,10 +278,10 @@ const NodePackageScreen = () => { return (
-
{t('NoActiveNodes')}
-
{t('AddFirstNode')}
+
{t("NoActiveNodes")}
+
{t("AddFirstNode")}
diff --git a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx index d17a4c15e..be4af4484 100644 --- a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx +++ b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx @@ -1,24 +1,24 @@ -import { useCallback, useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; -import type { FileDownloadProgress } from '../../../main/downloadFile'; +import type { FileDownloadProgress } from "../../../main/downloadFile"; // import { reportEvent } from '../../events/reportEvent'; -import { CHANNELS } from '../../../main/messenger'; -import type { IpcMessage } from '../../../main/podman/messageFrontEnd'; -import Button from '../../Generics/redesign/Button/Button'; -import { Icon } from '../../Generics/redesign/Icon/Icon'; -import ExternalLink from '../../Generics/redesign/Link/ExternalLink'; -import { Message } from '../../Generics/redesign/Message/Message'; -import ProgressBar from '../../Generics/redesign/ProgressBar/ProgressBar'; -import TimedProgressBar from '../../Generics/redesign/ProgressBar/TimedProgressBar'; -import electron from '../../electronGlobal'; +import { CHANNELS } from "../../../main/messenger"; +import type { IpcMessage } from "../../../main/podman/messageFrontEnd"; +import Button from "../../Generics/redesign/Button/Button"; +import { Icon } from "../../Generics/redesign/Icon/Icon"; +import ExternalLink from "../../Generics/redesign/Link/ExternalLink"; +import { Message } from "../../Generics/redesign/Message/Message"; +import ProgressBar from "../../Generics/redesign/ProgressBar/ProgressBar"; +import TimedProgressBar from "../../Generics/redesign/ProgressBar/TimedProgressBar"; +import electron from "../../electronGlobal"; import { useGetIsPodmanInstalledQuery, useGetIsPodmanRunningQuery, useGetPodmanDetailsQuery, -} from '../../state/settingsService'; -import { bytesToMB } from '../../utils'; -import { arePodmanRequirementsMet } from '../AddNodeStepper/podmanRequirements'; +} from "../../state/settingsService"; +import { bytesToMB } from "../../utils"; +import { arePodmanRequirementsMet } from "../AddNodeStepper/podmanRequirements"; import { captionText, container, @@ -30,7 +30,8 @@ import { installationSteps, learnMore, titleFont, -} from './podmanInstallation.css'; +} from "./podmanInstallation.css"; +import { messageContainer } from "./podmanInstallation.css.js"; // 6.5(docker), ? min on 2022 MacbookPro 16inch, baseline const TOTAL_INSTALL_TIME_SEC = 5 * 60; @@ -46,7 +47,7 @@ export interface PodmanInstallationProps { const PodmanInstallation = ({ onChange, disableSaveButton, - type = '', + type = "", }: PodmanInstallationProps) => { const { t } = useTranslation(); const qIsPodmanInstalled = useGetIsPodmanInstalledQuery(); @@ -76,18 +77,18 @@ const PodmanInstallation = ({ // if (disableSaveButton) disableSaveButton(false); }, []); - console.log('isPodmanRunning: ', isPodmanRunning); + console.log("isPodmanRunning: ", isPodmanRunning); useEffect(() => { // wait until podman is running and not outdated before confirming 'done' if (arePodmanRequirementsMet(podmanDetails)) { - onChange('done'); + onChange("done"); } }, [isPodmanRunning, podmanDetails, onChange]); const onClickDownloadAndInstall = async () => { setHasStartedDownload(true); - if (navigator.userAgent.indexOf('Linux') !== -1) { + if (navigator.userAgent.indexOf("Linux") !== -1) { setDownloadComplete(true); } const installResult = await electron.installPodman(); @@ -99,19 +100,19 @@ const PodmanInstallation = ({ // notify parent that everything is done // todo: confirm/check podman version installed? } - console.log('installPodman finished. Install result: ', installResult); + console.log("installPodman finished. Install result: ", installResult); }; const onClickStartPodman = async () => { setHasStartedDownload(true); const startResult = await electron.startPodman(); qIsPodmanRunning.refetch(); - console.log('startPodman finished. Start result: ', startResult); + console.log("startPodman finished. Start result: ", startResult); }; const onClickUpdatePodman = async () => { setHasStartedDownload(true); - if (navigator.userAgent.indexOf('Linux') !== -1) { + if (navigator.userAgent.indexOf("Linux") !== -1) { setDownloadComplete(true); } const updateResult = await electron.updatePodman(); @@ -122,13 +123,13 @@ const PodmanInstallation = ({ if (updateResult && !updateResult.error) { setInstallComplete(true); } - console.log('updatePodman finished. Start result: ', updateResult); + console.log("updatePodman finished. Start result: ", updateResult); }; const podmanMessageListener = (message: FileDownloadProgress[]) => { // set totalSize & progress if (message[0]) { - console.log('downloadupdate: ', message[0]); + console.log("downloadupdate: ", message[0]); setTotalSizeBytes(message[0].totalBytes); setDownloadedBytes(message[0].downloadedBytes); setDownloadProgress( @@ -136,11 +137,11 @@ const PodmanInstallation = ({ ); // if downloaded = total, then complete? if (message[0].downloadedBytes === message[0].totalBytes) { - console.log('downloaded = total bytes. download complete'); + console.log("downloaded = total bytes. download complete"); setDownloadComplete(true); } } else { - console.error('recieved empty podman message'); + console.error("recieved empty podman message"); } }; @@ -148,9 +149,9 @@ const PodmanInstallation = ({ (messageArray: [IpcMessage]) => { // set totalSize & progress const message = messageArray[0]; - console.log('podmanInstallMessageListener message: ', message); - if (message.messageId === 'isGrantPermission') { - console.log('message: ', message); + console.log("podmanInstallMessageListener message: ", message); + if (message.messageId === "isGrantPermission") { + console.log("message: ", message); // if false, notify user that podman is required and allow them another try // to grant permissions setDidUserGrantPermissionToInstallPodman(message?.value as boolean); @@ -167,7 +168,7 @@ const PodmanInstallation = ({ }, [sDidUserGrantPermissionToInstallPodman]); useEffect(() => { - console.log('PodmanInstallation.tsx: listenForPodmanInstallUpdates .'); + console.log("PodmanInstallation.tsx: listenForPodmanInstallUpdates ."); electron.ipcRenderer.on(CHANNELS.podman, podmanMessageListener); electron.ipcRenderer.on( CHANNELS.podmanInstall, @@ -189,30 +190,33 @@ const PodmanInstallation = ({ // react-hooks/exhaustive-deps // }, []); - console.log('isPodmanInstalled', isPodmanInstalled); - console.log('podmanDetails', podmanDetails); + console.log("isPodmanInstalled", isPodmanInstalled); + console.log("podmanDetails", podmanDetails); // listen to podman install messages return ( -
- {type !== 'modal' && ( -
{t('PodmanInstallation')}
+
+ {type !== "modal" && ( +
{t("PodmanInstallation")}
)} -
{t('podmanPurpose')}
+ {podmanDetails?.isOutdated && ( +
+ +
+ )} +
{t("podmanPurpose")}
- +
{/* Podman is not installed */}
- {podmanDetails?.isOutdated && ( -
- -
- )} {(!isPodmanInstalled || podmanDetails?.isOutdated) && ( <> {!sDownloadComplete && !sInstallComplete && ( @@ -221,7 +225,11 @@ const PodmanInstallation = ({
) : ( )} @@ -259,26 +267,28 @@ const PodmanInstallation = ({
- {t('PodmanInstallComplete')} + {t("PodmanInstallComplete")}
- {t('PodmanIsInstalled')}{' '} - {!isPodmanRunning ? t('StartPodman') : t('StartNode')} + {t("PodmanIsInstalled")}{" "} + {!isPodmanRunning ? t("StartPodman") : t("StartNode")}
)} {/* Podman is installed but not running */} - {isPodmanInstalled && !isPodmanRunning && ( - <> -
diff --git a/src/renderer/Presentational/PodmanInstallation/podmanInstallation.css.ts b/src/renderer/Presentational/PodmanInstallation/podmanInstallation.css.ts index 93af13baa..c98ac74cf 100644 --- a/src/renderer/Presentational/PodmanInstallation/podmanInstallation.css.ts +++ b/src/renderer/Presentational/PodmanInstallation/podmanInstallation.css.ts @@ -80,3 +80,8 @@ export const installationSteps = style({ lineHeight: '14px', color: vars.color.font50, }); + +export const messageContainer = style({ + marginBottom: 16, + width: '100%', +}); diff --git a/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx b/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx new file mode 100644 index 000000000..f5164ed58 --- /dev/null +++ b/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx @@ -0,0 +1,21 @@ +import type { ModalConfig } from "../ModalManager/modalUtils.js"; +import PodmanInstallation from "../PodmanInstallation/PodmanInstallation.tsx"; + +export interface PodmanWrapperProps { + modalOnChangeConfig: (config: ModalConfig, save?: boolean) => void; + disableSaveButton: (value: boolean) => void; +} + +const PodmanWrapper = ({ + modalOnChangeConfig, + disableSaveButton, +}: PodmanWrapperProps) => { + return ( + + ); +}; + +export default PodmanWrapper; diff --git a/src/renderer/Presentational/UpdatePodman/UpdatePodman.tsx b/src/renderer/Presentational/UpdatePodman/UpdatePodman.tsx deleted file mode 100644 index 18e7855d3..000000000 --- a/src/renderer/Presentational/UpdatePodman/UpdatePodman.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import Linking from '../../Generics/redesign/Link/Linking'; -import { - container, - contentContainer, - contentMajorTitle, - contentSection, - contentTitle, -} from './updatePodman.css'; - -const UpdatePodman = () => { - const { t } = useTranslation(); - return ( -
-
-
- Update Podman -

{t('YouShouldKnow')}

-
-
- {t('ExpectHiccups')} -

{t('WorkInProgress')}

- -
-
- {t('ErrorReportingOn')} -

{t('ReportingErrors')}

-
-
-
- ); -}; - -export default UpdatePodman; diff --git a/src/renderer/Presentational/UpdatePodman/updatePodman.css.ts b/src/renderer/Presentational/UpdatePodman/updatePodman.css.ts deleted file mode 100644 index 17bf01a34..000000000 --- a/src/renderer/Presentational/UpdatePodman/updatePodman.css.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { common } from '../../Generics/redesign/theme.css'; - -export const container = style({ - display: 'flex', - flexDirection: 'column', - width: 380, -}); - -export const topBanner = style({ - height: 92, - backgroundColor: common.color.pink100, - borderTopLeftRadius: 14, - borderTopRightRadius: 14, - display: 'flex', - justifyContent: 'center', -}); - -export const contentContainer = style({ - padding: '24px 24px 0px 24px', - gap: 24, - display: 'flex', - flexDirection: 'column', -}); - -export const contentSection = style({ - display: 'flex', - flexDirection: 'column', - gap: 8, - fontSize: 13, - fontWeight: 400, - lineHeight: '18px', - letterSpacing: '-0.08px', -}); - -export const contentMajorTitle = style({ - fontWeight: 590, - fontSize: 20, - lineHeight: '24px', - letterSpacing: '-0.4px', -}); - -export const contentTitle = style({ - fontWeight: 590, -}); diff --git a/src/renderer/state/modal.ts b/src/renderer/state/modal.ts index 9f82a37b6..34f565095 100644 --- a/src/renderer/state/modal.ts +++ b/src/renderer/state/modal.ts @@ -5,6 +5,7 @@ import type { RootState } from './store'; export interface ModalScreen { route: string | undefined; type: 'alert' | 'modal' | 'info' | undefined; + data?: Record; } // Define a type for the slice state From a80d60df5ab5a30c81420afc214a77cf10d43371 Mon Sep 17 00:00:00 2001 From: corn-potage Date: Thu, 9 May 2024 16:34:03 -0700 Subject: [PATCH 02/29] fixed developer menu, added mac os version check for podman 5 --- assets/locales/en/systemRequirements.json | 2 + src/main/nn-auto-updater/main.ts | 6 +- .../requirementsChecklistUtil.tsx | 148 +++++++++++------- .../PodmanModal/PodmanWrapper.tsx | 9 +- 4 files changed, 106 insertions(+), 59 deletions(-) diff --git a/assets/locales/en/systemRequirements.json b/assets/locales/en/systemRequirements.json index 919216bdc..8be9abf6f 100644 --- a/assets/locales/en/systemRequirements.json +++ b/assets/locales/en/systemRequirements.json @@ -1,4 +1,6 @@ { + "macOSTitle": "MacOS version is at least {{minVersion}} to run Podman 5.0", + "macOSDescription": "MacOS version: {{version}}", "processorCoresTitle": "Processor has {{minCores}} cores or more", "processorCoresDescription": "Processor cores: {{cores}}", "memorySizeTitle": "At least {{minSize}}GB of system memory (RAM)", diff --git a/src/main/nn-auto-updater/main.ts b/src/main/nn-auto-updater/main.ts index 991190dd3..5bbb83f33 100644 --- a/src/main/nn-auto-updater/main.ts +++ b/src/main/nn-auto-updater/main.ts @@ -86,7 +86,11 @@ export class nnAutoUpdater if (isLinux()) { console.log('nnAutoUpdater setFeedURL in linux!'); } else { - this.nativeUpdater.setFeedURL(options); + try { + this.nativeUpdater.setFeedURL(options); + } catch (e) { + console.error('Error in setFeedURL: ', e); + } } } } diff --git a/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx b/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx index cb330b75e..902d07bc2 100644 --- a/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx +++ b/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx @@ -1,18 +1,35 @@ -import type { TFunction } from 'i18next'; -import type { ReactElement } from 'react'; +import type { TFunction } from "i18next"; +import type { ReactElement } from "react"; import type { CpuRequirements, // DockerRequirements, InternetRequirements, MemoryRequirements, StorageRequirements, -} from '../../../common/systemRequirements'; +} from "../../../common/systemRequirements"; -import type { ChecklistItemProps } from '../../Generics/redesign/Checklist/ChecklistItem'; -import ExternalLink from '../../Generics/redesign/Link/ExternalLink'; -import { bytesToGB } from '../../utils'; -import type { NodeRequirementsProps } from './NodeRequirements'; -import { findSystemStorageDetailsAtALocation } from './nodeStorageUtil'; +import type { ChecklistItemProps } from "../../Generics/redesign/Checklist/ChecklistItem"; +import ExternalLink from "../../Generics/redesign/Link/ExternalLink"; +import { bytesToGB } from "../../utils"; +import type { NodeRequirementsProps } from "./NodeRequirements"; +import { findSystemStorageDetailsAtALocation } from "./nodeStorageUtil"; + +const isVersionHigher = (currentVersion: string, targetVersion: string) => { + const parseVersion = (version: string) => version.split(".").map(Number); + + const current = parseVersion(currentVersion); + const target = parseVersion(targetVersion); + + for (let i = 0; i < Math.max(current.length, target.length); i++) { + const currentPart = current[i] || 0; + const targetPart = target[i] || 0; + if (currentPart > targetPart) return true; + if (currentPart < targetPart) return false; + } + return false; +}; + +const TARGET_MACOS_VERSION = "13.0.0"; export const makeCheckList = ( { nodeRequirements, systemData, nodeStorageLocation }: NodeRequirementsProps, @@ -30,90 +47,107 @@ export const makeCheckList = ( nodeStorageLocation, ); } - console.log('nodeLocationStorageDetails', nodeLocationStorageDetails); + console.log("nodeLocationStorageDetails", nodeLocationStorageDetails); + if (systemData?.os?.platform === "darwin") { + const checkListItem: ChecklistItemProps = { + checkTitle: t("macOSTitle", { + minVersion: TARGET_MACOS_VERSION, + }), + valueText: t("macOSDescription", { + version: systemData?.os?.release, + }), + status: "", + }; + if (isVersionHigher(systemData?.os?.release, TARGET_MACOS_VERSION)) { + checkListItem.status = "complete"; + } else { + checkListItem.status = "error"; + } + newChecklistItems.push(checkListItem); + } for (const [nodeReqKey, nodeReqValue] of Object.entries(nodeRequirements)) { console.log(`${nodeReqKey}: ${nodeReqValue}`); - if (nodeReqKey === 'documentationUrl' || nodeReqKey === 'description') { + if (nodeReqKey === "documentationUrl" || nodeReqKey === "description") { continue; } // title and desc depends on req type // title and desc depends on whether the req is met or not // if cpu, if cores meets, add success // if minSpeed doesn't meet - let checkTitle = ''; - let valueText = ''; + let checkTitle = ""; + let valueText = ""; let valueComponent: ReactElement = <>; - const captionText = ''; - let status: ChecklistItemProps['status'] = 'loading'; - if (nodeReqKey === 'cpu') { + const captionText = ""; + let status: ChecklistItemProps["status"] = "loading"; + if (nodeReqKey === "cpu") { const req = nodeReqValue as CpuRequirements; if (req.cores !== undefined) { - checkTitle = t('processorCoresTitle', { + checkTitle = t("processorCoresTitle", { minCores: req.cores, }); if (systemData?.cpu?.cores) { - valueText = t('processorCoresDescription', { + valueText = t("processorCoresDescription", { cores: systemData?.cpu.cores, }); if (systemData?.cpu.cores >= req.cores) { - status = 'complete'; + status = "complete"; } else { - status = 'incomplete'; + status = "incomplete"; } } else { - status = 'error'; + status = "error"; } } } - if (nodeReqKey === 'memory') { + if (nodeReqKey === "memory") { const req = nodeReqValue as MemoryRequirements; if (req.minSizeGBs !== undefined) { - checkTitle = t('memorySizeTitle', { + checkTitle = t("memorySizeTitle", { minSize: req.minSizeGBs, }); if (systemData?.memLayout[0]?.size) { - valueText = t('memorySizeDescription', { + valueText = t("memorySizeDescription", { size: bytesToGB(systemData?.memLayout[0]?.size), }); if (systemData?.memLayout[0]?.size >= req.minSizeGBs) { - status = 'complete'; + status = "complete"; } else { - status = 'incomplete'; + status = "incomplete"; } } else { - status = 'error'; + status = "error"; } } } - if (nodeReqKey === 'storage') { + if (nodeReqKey === "storage") { const req = nodeReqValue as StorageRequirements; const disk = nodeLocationStorageDetails; if (req.ssdRequired === true) { - checkTitle = t('storageTypeTitle', { - type: 'SSD', + checkTitle = t("storageTypeTitle", { + type: "SSD", }); if (disk?.type || disk?.name) { - if (disk?.type.includes('NVMe') || disk?.name.includes('NVMe')) { - valueText = t('storageTypeDescription', { - type: disk?.type ? disk?.type : 'NVMe SSD', + if (disk?.type.includes("NVMe") || disk?.name.includes("NVMe")) { + valueText = t("storageTypeDescription", { + type: disk?.type ? disk?.type : "NVMe SSD", }); - status = 'complete'; - } else if (disk?.type.includes('SSD') || disk?.name.includes('SSD')) { - valueText = t('storageTypeDescription', { - type: disk?.type ? disk?.type : 'SSD', + status = "complete"; + } else if (disk?.type.includes("SSD") || disk?.name.includes("SSD")) { + valueText = t("storageTypeDescription", { + type: disk?.type ? disk?.type : "SSD", }); - status = 'complete'; - } else if (disk?.type.includes('HDD') || disk?.name.includes('HDD')) { - valueText = t('storageTypeDescription', { - type: disk?.type ? disk?.type : 'HDD', + status = "complete"; + } else if (disk?.type.includes("HDD") || disk?.name.includes("HDD")) { + valueText = t("storageTypeDescription", { + type: disk?.type ? disk?.type : "HDD", }); - status = 'error'; + status = "error"; } else { - status = 'incomplete'; + status = "incomplete"; } } else { - status = 'error'; + status = "error"; } const checkListItem: ChecklistItemProps = { checkTitle, @@ -123,51 +157,51 @@ export const makeCheckList = ( newChecklistItems.push(checkListItem); } if (req.minSizeGBs !== undefined) { - checkTitle = t('storageSizeTitle', { + checkTitle = t("storageSizeTitle", { minSize: req.minSizeGBs, }); if (disk?.freeSpaceGBs) { const diskSizeGbs = Math.round(disk.freeSpaceGBs); // todo: use free space for storage calculations? - valueText = t('storageSizeDescription', { + valueText = t("storageSizeDescription", { freeSize: diskSizeGbs, storageName: disk.name, }); if (diskSizeGbs >= req.minSizeGBs) { - status = 'complete'; + status = "complete"; } else { - status = 'incomplete'; + status = "incomplete"; } } else { - status = 'error'; + status = "error"; } } } - if (nodeReqKey === 'internet') { + if (nodeReqKey === "internet") { const req = nodeReqValue as InternetRequirements; if (req.minDownloadSpeedMbps !== undefined) { - checkTitle = t('internetSpeedTitle', { + checkTitle = t("internetSpeedTitle", { minDownloadSpeed: req.minDownloadSpeedMbps, minUploadSpeed: req.minUploadSpeedMbps, }); - valueText = t('internetSpeedPleaseTest'); + valueText = t("internetSpeedPleaseTest"); valueComponent = ( <> - {`${t('internetSpeedTestWebsites')} `} + {`${t("internetSpeedTestWebsites")} `} {' '} - or{' '} + />{" "} + or{" "} ); - status = 'information'; + status = "information"; } } // Todoo: Don't make user think about Podman? diff --git a/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx b/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx index f5164ed58..7ceef7714 100644 --- a/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx +++ b/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx @@ -1,3 +1,4 @@ +import { useCallback } from "react"; import type { ModalConfig } from "../ModalManager/modalUtils.js"; import PodmanInstallation from "../PodmanInstallation/PodmanInstallation.tsx"; @@ -10,10 +11,16 @@ const PodmanWrapper = ({ modalOnChangeConfig, disableSaveButton, }: PodmanWrapperProps) => { + const onChangeDockerInstall = useCallback((newValue: string) => { + if (newValue === "done") { + disableSaveButton(false); + } + }, []); + return ( ); }; From 7f1352c5da6787f965f01009ba5582522b860637 Mon Sep 17 00:00:00 2001 From: corn-potage Date: Thu, 9 May 2024 17:15:06 -0700 Subject: [PATCH 03/29] fixed preferences modal width --- .../Generics/redesign/Modal/modal.css.ts | 3 ++ .../ModalManager/PreferencesModal.tsx | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/renderer/Generics/redesign/Modal/modal.css.ts b/src/renderer/Generics/redesign/Modal/modal.css.ts index 90c2e075a..1910a778b 100644 --- a/src/renderer/Generics/redesign/Modal/modal.css.ts +++ b/src/renderer/Generics/redesign/Modal/modal.css.ts @@ -47,6 +47,9 @@ export const modalContentStyle = style({ '&.podman': { width: '624px', }, + '&.preferences': { + width: '624px', + }, }, }); diff --git a/src/renderer/Presentational/ModalManager/PreferencesModal.tsx b/src/renderer/Presentational/ModalManager/PreferencesModal.tsx index 97c1dae4a..f1eacb8be 100644 --- a/src/renderer/Presentational/ModalManager/PreferencesModal.tsx +++ b/src/renderer/Presentational/ModalManager/PreferencesModal.tsx @@ -1,15 +1,15 @@ -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import type { ThemeSetting } from '../../../main/state/settings'; -import { Modal } from '../../Generics/redesign/Modal/Modal'; -import electron from '../../electronGlobal'; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import type { ThemeSetting } from "../../../main/state/settings"; +import { Modal } from "../../Generics/redesign/Modal/Modal"; +import electron from "../../electronGlobal"; import { reportEvent, setRemoteEventReportingEnabled, -} from '../../events/reportEvent'; -import { useGetSettingsQuery } from '../../state/settingsService'; -import PreferencesWrapper from '../Preferences/PreferencesWrapper'; -import { type ModalConfig, modalOnChangeConfig } from './modalUtils'; +} from "../../events/reportEvent"; +import { useGetSettingsQuery } from "../../state/settingsService"; +import PreferencesWrapper from "../Preferences/PreferencesWrapper"; +import { type ModalConfig, modalOnChangeConfig } from "./modalUtils"; type Props = { modalOnClose: () => void; @@ -22,15 +22,15 @@ interface MetaElement extends HTMLMetaElement { export const PreferencesModal = ({ modalOnClose }: Props) => { const [modalConfig, setModalConfig] = useState({}); const { t } = useTranslation(); - const modalTitle = t('Preferences'); - const buttonSaveLabel = t('SaveChanges'); + const modalTitle = t("Preferences"); + const buttonSaveLabel = t("SaveChanges"); const qSettings = useGetSettingsQuery(); const handleColorSchemeChange = (colorScheme: ThemeSetting) => { const meta = document.querySelector( 'meta[name="color-scheme"]', ) as MetaElement; - const colorValue = colorScheme === 'auto' ? 'light dark' : colorScheme; + const colorValue = colorScheme === "auto" ? "light dark" : colorScheme; if (meta) meta.content = colorValue as ThemeSetting; }; @@ -63,9 +63,9 @@ export const PreferencesModal = ({ modalOnClose }: Props) => { isPreReleaseUpdatesEnabled, ); if (isPreReleaseUpdatesEnabled) { - reportEvent('EnablePreReleaseUpdates'); + reportEvent("EnablePreReleaseUpdates"); } else { - reportEvent('DisablePreReleaseUpdates'); + reportEvent("DisablePreReleaseUpdates"); } } if (language) { @@ -78,6 +78,7 @@ export const PreferencesModal = ({ modalOnClose }: Props) => { return ( Date: Thu, 9 May 2024 20:55:30 -0700 Subject: [PATCH 04/29] fixed banner configs, added updateAvailable --- .../Generics/redesign/Banner/Banner.tsx | 54 +++---- .../Presentational/Sidebar/Sidebar.tsx | 137 +++++++----------- .../SidebarWrapper/SidebarWrapper.tsx | 54 ++++--- 3 files changed, 114 insertions(+), 131 deletions(-) diff --git a/src/renderer/Generics/redesign/Banner/Banner.tsx b/src/renderer/Generics/redesign/Banner/Banner.tsx index 9d3e4e9fe..1cc7ad723 100644 --- a/src/renderer/Generics/redesign/Banner/Banner.tsx +++ b/src/renderer/Generics/redesign/Banner/Banner.tsx @@ -1,14 +1,14 @@ -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import type { IconId } from '../../../assets/images/icons'; -import { Icon } from '../Icon/Icon'; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import type { IconId } from "../../../assets/images/icons"; +import { Icon } from "../Icon/Icon"; import { container, descriptionStyle, innerContainer, textContainer, titleStyle, -} from './banner.css'; +} from "./banner.css"; export interface BannerProps { /** @@ -41,30 +41,30 @@ export const Banner = ({ onClick, }: BannerProps) => { // Use useState to manage description dynamically - const [description, setDescription] = useState(''); - const [iconId, setIconId] = useState('blank'); - const [title, setTitle] = useState(''); + const [description, setDescription] = useState(""); + const [iconId, setIconId] = useState("blank"); + const [title, setTitle] = useState(""); const [loading, setLoading] = useState(false); const [isClicked, setIsClicked] = useState(false); - const { t: g } = useTranslation('genericComponents'); + const { t: g } = useTranslation("genericComponents"); useEffect(() => { if (offline) { - setIconId('boltstrike'); - setTitle(g('CurrentlyOffline')); - setDescription(g('PleaseReconnect')); - } else if (!podmanInstalled) { - setIconId('warningcircle'); - setTitle(g('PodmanIsNotInstalled')); - setDescription(g('ClickToInstallPodman')); + setIconId("boltstrike"); + setTitle(g("CurrentlyOffline")); + setDescription(g("PleaseReconnect")); + } else if (podmanInstalled === false) { + setIconId("warningcircle"); + setTitle(g("PodmanIsNotInstalled")); + setDescription(g("ClickToInstallPodman")); } else if (updateAvailable) { - setIconId('download1'); - setTitle(g('UpdateAvailable')); - setDescription(g('NewVersionAvailable')); + setIconId("download1"); + setTitle(g("UpdateAvailable")); + setDescription(g("NewVersionAvailable")); } else if (podmanStopped) { - setIconId('play'); - setTitle(g('PodmanIsNotRunning')); - setDescription(g('ClickToStartPodman')); + setIconId("play"); + setTitle(g("PodmanIsNotRunning")); + setDescription(g("ClickToStartPodman")); } }, [offline, updateAvailable, podmanStopped, podmanInstalled, g]); @@ -76,13 +76,13 @@ export const Banner = ({ setIsClicked(true); if (!podmanInstalled) { - setDescription(g('PodmanInstalling')); + setDescription(g("PodmanInstalling")); setLoading(true); } else if (podmanStopped) { - setDescription(g('PodmanLoading')); + setDescription(g("PodmanLoading")); setLoading(true); } else if (updateAvailable) { - console.log('update nice node!'); + console.log("update nice node!"); } if (onClick) { @@ -92,14 +92,14 @@ export const Banner = ({ return (
-
+
{title}
diff --git a/src/renderer/Presentational/Sidebar/Sidebar.tsx b/src/renderer/Presentational/Sidebar/Sidebar.tsx index 02865ab34..71bbd4669 100644 --- a/src/renderer/Presentational/Sidebar/Sidebar.tsx +++ b/src/renderer/Presentational/Sidebar/Sidebar.tsx @@ -1,17 +1,17 @@ -import { forwardRef } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import type { NodeId, UserNodePackages } from '../../../common/node'; -import { Banner } from '../../Generics/redesign/Banner/Banner'; -import type { NotificationItemProps } from '../../Generics/redesign/NotificationItem/NotificationItem'; -import { SidebarLinkItem } from '../../Generics/redesign/SidebarLinkItem/SidebarLinkItem'; -import { SidebarTitleItem } from '../../Generics/redesign/SidebarTitleItem/SidebarTitleItem'; -import type { IconId } from '../../assets/images/icons'; -import { useAppDispatch } from '../../state/hooks'; -import { setModalState } from '../../state/modal'; -import { updateSelectedNodePackageId } from '../../state/node'; -import { SidebarNodeItemWrapper } from '../SidebarNodeItemWrapper/SidebarNodeItemWrapper'; -import { container, itemList, nodeList, titleItem } from './sidebar.css'; +import { forwardRef } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import type { NodeId, UserNodePackages } from "../../../common/node"; +import { Banner } from "../../Generics/redesign/Banner/Banner"; +import type { NotificationItemProps } from "../../Generics/redesign/NotificationItem/NotificationItem"; +import { SidebarLinkItem } from "../../Generics/redesign/SidebarLinkItem/SidebarLinkItem"; +import { SidebarTitleItem } from "../../Generics/redesign/SidebarTitleItem/SidebarTitleItem"; +import type { IconId } from "../../assets/images/icons"; +import { useAppDispatch } from "../../state/hooks"; +import { setModalState } from "../../state/modal"; +import { updateSelectedNodePackageId } from "../../state/node"; +import { SidebarNodeItemWrapper } from "../SidebarNodeItemWrapper/SidebarNodeItemWrapper"; +import { container, itemList, nodeList, titleItem } from "./sidebar.css"; // import { NodeIconId } from '../../assets/images/nodeIcons'; export interface SidebarProps { @@ -57,82 +57,55 @@ const Sidebar = forwardRef( const itemListData: { iconId: IconId; label: string; count?: number }[] = [ { - iconId: 'bell', - label: t('Notifications'), + iconId: "bell", + label: t("Notifications"), count: notifications?.length, }, { - iconId: 'add', - label: t('AddNode'), + iconId: "add", + label: t("AddNode"), }, { - iconId: 'preferences', - label: t('Preferences'), + iconId: "preferences", + label: t("Preferences"), }, { - iconId: 'health', - label: t('SystemMonitor'), + iconId: "health", + label: t("SystemMonitor"), }, ]; - // const nodeListObject = { nodeService: [], validator: [], singleClients: [] }; - // sUserNodes?.nodeIds.forEach((nodeId: NodeId) => { - // const node = sUserNodes.nodes[nodeId]; - // // TODO: add validator logic here eventually - // if ( - // node.spec.category === 'L1/ExecutionClient' || - // node.spec.category === 'L1/ConsensusClient/BeaconNode' - // ) { - // nodeListObject.nodeService.push(node); - // } else { - // nodeListObject.singleClients.push(node); - // } - // }); + const bannerConfigs = [ + { key: "offline", condition: offline, props: { offline: true } }, + { + key: "updateAvailable", + condition: updateAvailable, + props: { updateAvailable: true, onClick: onClickInstallPodman }, + }, + { + key: "podmanNotInstalled", + condition: !podmanInstalled, + props: { podmanInstalled: false, onClick: onClickInstallPodman }, + }, + { + key: "podmanStopped", + condition: podmanStopped, + props: { podmanStopped: true, onClick: onClickStartPodman }, + }, + ]; - const onClickBanner = () => { - if (podmanInstalled) { - onClickStartPodman(); - } else { - onClickInstallPodman(); - } - }; + const banners = bannerConfigs + .filter((config) => config.condition) + .map((config) => ); - const banners = []; - const bannerProps = { - updateAvailable, - offline, - }; - Object.keys(bannerProps).forEach((key, index) => { - if (bannerProps[key as keyof typeof bannerProps]) { - // ^ not sure if this is correct - banners.push( - , - ); - } - }); - if (!podmanInstalled || podmanStopped) { - banners.push( - , - ); - } const navigate = useNavigate(); return ( -
+
{banners}
- +
{/* {nodeListObject.nodeService.length === 2 && ( ( node={node} selected={selectedNodePackageId === node.id} onClick={() => { - navigate('/main/nodePackage'); + navigate("/main/nodePackage"); dispatch(updateSelectedNodePackageId(node.id)); }} /> @@ -172,25 +145,25 @@ const Sidebar = forwardRef( label={item.label} count={item.count} onClick={() => { - console.log('sidebar link item clicked: ', item.iconId); - if (item.iconId === 'add') { + console.log("sidebar link item clicked: ", item.iconId); + if (item.iconId === "add") { dispatch( setModalState({ isModalOpen: true, - screen: { route: 'addNode', type: 'modal' }, + screen: { route: "addNode", type: "modal" }, }), ); - } else if (item.iconId === 'preferences') { + } else if (item.iconId === "preferences") { dispatch( setModalState({ isModalOpen: true, - screen: { route: 'preferences', type: 'modal' }, + screen: { route: "preferences", type: "modal" }, }), ); - } else if (item.iconId === 'bell') { - navigate('/main/notification'); - } else if (item.iconId === 'health') { - navigate('/main/system'); + } else if (item.iconId === "bell") { + navigate("/main/notification"); + } else if (item.iconId === "health") { + navigate("/main/system"); } }} /> diff --git a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx index fa044ffa9..c27ad7cab 100644 --- a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx +++ b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx @@ -4,23 +4,25 @@ import { useCallback, useEffect, useState, -} from 'react'; -import { CHANNELS } from '../../../main/messenger'; -import type { NotificationItemProps } from '../../Generics/redesign/NotificationItem/NotificationItem'; -import electron from '../../electronGlobal'; -import { useAppDispatch, useAppSelector } from '../../state/hooks'; -import { useGetNetworkConnectedQuery } from '../../state/network'; +} from "react"; +import { CHANNELS } from "../../../main/messenger"; +import type { NotificationItemProps } from "../../Generics/redesign/NotificationItem/NotificationItem"; +import electron from "../../electronGlobal"; +import { useAppDispatch, useAppSelector } from "../../state/hooks"; +import { useGetNetworkConnectedQuery } from "../../state/network"; import { selectSelectedNodePackageId, selectUserNodePackages, updateSelectedNodePackageId, -} from '../../state/node'; -import { useGetNotificationsQuery } from '../../state/notificationsService'; +} from "../../state/node"; +import { useGetNotificationsQuery } from "../../state/notificationsService"; import { useGetIsPodmanInstalledQuery, useGetIsPodmanRunningQuery, -} from '../../state/settingsService'; -import Sidebar from '../Sidebar/Sidebar'; + useGetPodmanDetailsQuery, +} from "../../state/settingsService"; +import Sidebar from "../Sidebar/Sidebar"; +import { setModalState } from "../../state/modal.js"; export interface SidebarWrapperProps { children: ReactElement; @@ -39,7 +41,11 @@ export const SidebarWrapper = forwardRef((_, ref) => { const qNetwork = useGetNetworkConnectedQuery(null, { pollingInterval: 30000, }); - const [platform, setPlatform] = useState(''); + const qPodmanDetails = useGetPodmanDetailsQuery(null, { + pollingInterval: 15000, + }); + const podmanDetails = qPodmanDetails?.data; + const [platform, setPlatform] = useState(""); // default to docker is running while data is being fetched, so // the user isn't falsely warned let isPodmanRunning = true; @@ -48,16 +54,20 @@ export const SidebarWrapper = forwardRef((_, ref) => { } const onClickInstallPodman = async () => { - console.log('install podman'); - const installResult = await electron.installPodman(); - qIsPodmanInstalled.refetch(); - qIsPodmanRunning.refetch(); - if (installResult && !installResult.error) { - console.log('podman installed, do something'); - } + dispatch( + setModalState({ + isModalOpen: true, + screen: { + route: "podman", + type: "modal", + data: { view: "update" }, + }, + }), + ); }; const onClickStartPodman = async () => { + console.log("test"); await electron.startPodman(); // todo: verify it is started and changed banner for 5 secs? qIsPodmanRunning.refetch(); @@ -69,7 +79,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { // subscribes to a channel which notifies when dark mode settings change const onNotificationChange = useCallback(() => { - console.log('onNotificationChange'); + console.log("onNotificationChange"); qNotifications?.refetch(); }, []); @@ -98,7 +108,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { useEffect(() => { const asyncData = async () => { const userSettings = await electron.getSettings(); - setPlatform(userSettings.osPlatform || ''); + setPlatform(userSettings.osPlatform || ""); }; asyncData(); }, []); @@ -108,8 +118,8 @@ export const SidebarWrapper = forwardRef((_, ref) => { platform={platform} ref={ref} notifications={notifications} - offline={qNetwork.status === 'rejected'} - updateAvailable={false} + offline={qNetwork.status === "rejected"} + updateAvailable={podmanDetails?.isOutdated || false} podmanInstalled={isPodmanInstalled} podmanStopped={!isPodmanRunning} sUserNodePackages={sUserNodePackages} From faacb18b49abd64ea37aecc1d5417c9a574a4c8f Mon Sep 17 00:00:00 2001 From: corn-potage Date: Fri, 10 May 2024 14:18:00 -0700 Subject: [PATCH 05/29] added openPodmanModal --- src/main/messenger.ts | 1 + src/main/podman/podman.ts | 6 +++++- src/main/tray.ts | 16 +++++++-------- .../ModalManager/PodmanModal.tsx | 5 +---- .../NodePackageScreen/NodePackageScreen.tsx | 1 - .../PodmanInstallation/PodmanInstallation.tsx | 3 --- .../SidebarWrapper/SidebarWrapper.tsx | 20 +++++++++++++++++-- 7 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/main/messenger.ts b/src/main/messenger.ts index 95dbb7d8f..e90e182f9 100644 --- a/src/main/messenger.ts +++ b/src/main/messenger.ts @@ -22,6 +22,7 @@ export const CHANNELS = { nodeLogs: 'nodeLogs', podman: 'podman', podmanInstall: 'podmanInstall', + openPodmanModal: 'openPodmanModal', theme: 'theme', notifications: 'notifications', reportEvent: 'reportEvent', diff --git a/src/main/podman/podman.ts b/src/main/podman/podman.ts index f4bc072d7..7ce48af73 100644 --- a/src/main/podman/podman.ts +++ b/src/main/podman/podman.ts @@ -18,7 +18,6 @@ import { type ConfigValuesMap, buildCliConfig, } from '../../common/nodeConfig'; -import { send } from '../messenger'; import { restartNodes } from '../nodePackageManager'; import { isLinux } from '../platform'; import { killChildProcess } from '../processExit'; @@ -28,6 +27,7 @@ import * as metricsPolling from './metricsPolling'; import { execPromise as podmanExecPromise } from './podman-desktop/podman-cli'; import { getPodmanEnvWithPath } from './podman-env-path'; import startPodman, { onStartUp } from './start'; +import { send, CHANNELS } from '../messenger.js'; let podmanWatchProcess: ChildProcess; @@ -755,6 +755,10 @@ export const isPodmanStarting = async () => { return bIsPodmanStarting; }; +export const openPodmanModal = async () => { + send(CHANNELS.openPodmanModal); +}; + // todoo // setTimeout(() => { // isPodmanRunning(); diff --git a/src/main/tray.ts b/src/main/tray.ts index 1c0e1e905..5872e633a 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -8,6 +8,7 @@ import { stopMachineIfCreated, } from './podman/machine'; import { getUserNodePackages } from './state/nodePackages'; +import { openPodmanModal } from './podman/podman.js'; // Can't import from main because of circular dependency let _getAssetPath: (...paths: string[]) => string; @@ -35,23 +36,19 @@ export const setTrayIcon = (style: 'Default' | 'Alert') => { export const setTrayMenu = () => { const menuTemplate = [ - // todo: change icon if there are any status errors + // Podman status with start, stop, and delete? ...nodePackageTrayMenu, { type: 'separator' }, - // todo: show in developer mode - - // todo: add podman status with start, stop, and delete? + ...podmanTrayMenu, + { type: 'separator' }, ...openNiceNodeMenu, { - label: 'Full Quit', + label: 'Quit', click: () => { fullQuit(); // app no longer runs in the background }, }, ]; - if (process.env.NODE_ENV === 'development') { - menuTemplate.push(...podmanTrayMenu, { type: 'separator' }); - } const contextMenu = Menu.buildFromTemplate(menuTemplate as MenuItem[]); if (tray) { @@ -126,8 +123,9 @@ const getPodmanMenuItem = async () => { // stop stopMachineIfCreated(); } else { + openPodmanModal(); // try to start if any other start - startMachineIfCreated(); + // startMachineIfCreated(); } }, type: 'checkbox', diff --git a/src/renderer/Presentational/ModalManager/PodmanModal.tsx b/src/renderer/Presentational/ModalManager/PodmanModal.tsx index dbb65f5a5..a8e2c216f 100644 --- a/src/renderer/Presentational/ModalManager/PodmanModal.tsx +++ b/src/renderer/Presentational/ModalManager/PodmanModal.tsx @@ -8,15 +8,12 @@ import { type ModalConfig, modalOnChangeConfig } from "./modalUtils.js"; type Props = { modalOnClose: () => void; - data: { view: string }; }; -export const PodmanModal = ({ modalOnClose, data }: Props) => { +export const PodmanModal = ({ modalOnClose }: Props) => { const [modalConfig, setModalConfig] = useState({}); const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(false); const { t } = useTranslation(); - const modalTitle = - data.view === "update" ? "Update Podman" : t("RemoveThisNode"); const buttonSaveLabel = t("Done"); const modalOnSaveConfig = async (updatedConfig: ModalConfig | undefined) => { diff --git a/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx b/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx index 482a4fe6c..47d202c89 100644 --- a/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx +++ b/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx @@ -292,7 +292,6 @@ const NodePackageScreen = () => { screen: { route: "podman", type: "modal", - data: { view: "update" }, }, }), ); diff --git a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx index be4af4484..0e2aecb6c 100644 --- a/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx +++ b/src/renderer/Presentational/PodmanInstallation/PodmanInstallation.tsx @@ -190,9 +190,6 @@ const PodmanInstallation = ({ // react-hooks/exhaustive-deps // }, []); - console.log("isPodmanInstalled", isPodmanInstalled); - console.log("podmanDetails", podmanDetails); - // listen to podman install messages return (
diff --git a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx index c27ad7cab..97d7044d0 100644 --- a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx +++ b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx @@ -67,7 +67,6 @@ export const SidebarWrapper = forwardRef((_, ref) => { }; const onClickStartPodman = async () => { - console.log("test"); await electron.startPodman(); // todo: verify it is started and changed banner for 5 secs? qIsPodmanRunning.refetch(); @@ -83,15 +82,32 @@ export const SidebarWrapper = forwardRef((_, ref) => { qNotifications?.refetch(); }, []); + const onOpenPodmanModal = useCallback(() => { + dispatch( + setModalState({ + isModalOpen: true, + screen: { + route: "podman", + type: "modal", + }, + }), + ); + }, []); + useEffect(() => { electron.ipcRenderer.on(CHANNELS.notifications, onNotificationChange); + electron.ipcRenderer.on(CHANNELS.openPodmanModal, onOpenPodmanModal); return () => { electron.ipcRenderer.removeListener( CHANNELS.notifications, onNotificationChange, ); + electron.ipcRenderer.removeListener( + CHANNELS.openPodmanModal, + onOpenPodmanModal, + ); }; - }, [onNotificationChange]); + }, [onNotificationChange, onOpenPodmanModal]); // Default selected node to be the first node useEffect(() => { From 1d3207cb867d7e7fd218f2f68905013a8e7bf2d3 Mon Sep 17 00:00:00 2001 From: corn-potage Date: Fri, 10 May 2024 14:30:48 -0700 Subject: [PATCH 06/29] added window focus/creation when selecting podman status --- src/main/tray.ts | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 5872e633a..05b8eb4f6 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -34,6 +34,21 @@ export const setTrayIcon = (style: 'Default' | 'Alert') => { } }; +const openOrFocusWindow = () => { + const mainWindow = getMainWindow(); + if (mainWindow === null) { + // Create a new window if none exists + createWindow(); + } else { + // Show and focus on the existing window + mainWindow.show(); + if (mainWindow.isMinimized()) { + mainWindow.restore(); // Restore if minimized + } + mainWindow.focus(); // Focus on the window + } +} + export const setTrayMenu = () => { const menuTemplate = [ // Podman status with start, stop, and delete? @@ -57,18 +72,14 @@ export const setTrayMenu = () => { }; const getOpenNiceNodeMenu = () => { - if (getMainWindow() === null) { - openNiceNodeMenu = [ - { - label: 'Open NiceNode', - click: () => { - createWindow(); // app no longer runs in the background - }, + openNiceNodeMenu = [ + { + label: 'Open NiceNode', + click: () => { + openOrFocusWindow(); }, - ]; - } else { - openNiceNodeMenu = []; - } + }, + ]; setTrayMenu(); }; @@ -123,8 +134,8 @@ const getPodmanMenuItem = async () => { // stop stopMachineIfCreated(); } else { + openOrFocusWindow(); openPodmanModal(); - // try to start if any other start // startMachineIfCreated(); } }, From abf64731a2dbfa92d3350df8a47b2e5cfa0455ee Mon Sep 17 00:00:00 2001 From: corn-potage Date: Fri, 10 May 2024 16:01:31 -0700 Subject: [PATCH 07/29] added node package open through tray --- src/main/messenger.ts | 1 + src/main/podman/install/install.ts | 4 ++-- src/main/state/nodePackages.ts | 4 ++++ src/main/tray.ts | 5 ++++- .../SidebarWrapper/SidebarWrapper.tsx | 18 +++++++++++++++++- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/messenger.ts b/src/main/messenger.ts index e90e182f9..1064808a7 100644 --- a/src/main/messenger.ts +++ b/src/main/messenger.ts @@ -23,6 +23,7 @@ export const CHANNELS = { podman: 'podman', podmanInstall: 'podmanInstall', openPodmanModal: 'openPodmanModal', + openNodePackageScreen: 'openNodePackageScreen', theme: 'theme', notifications: 'notifications', reportEvent: 'reportEvent', diff --git a/src/main/podman/install/install.ts b/src/main/podman/install/install.ts index ffbe8d05d..bc5e7ac0d 100644 --- a/src/main/podman/install/install.ts +++ b/src/main/podman/install/install.ts @@ -5,8 +5,8 @@ import installOnLinux from './installOnLinux'; import installOnMac from './installOnMac'; import installOnWindows from './installOnWindows'; -export const PODMAN_LATEST_VERSION = '5.0.2'; -export const PODMAN_MIN_VERSION = '4.6.0'; +export const PODMAN_LATEST_VERSION = '4.9.3'; +export const PODMAN_MIN_VERSION = '4.9.3'; const installPodman = async (): Promise => { logger.info('Starting podman install...'); diff --git a/src/main/state/nodePackages.ts b/src/main/state/nodePackages.ts index 3987e628b..1ab45f687 100644 --- a/src/main/state/nodePackages.ts +++ b/src/main/state/nodePackages.ts @@ -196,3 +196,7 @@ export const getUserNodePackagesWithNodes = async () => { }); return userNodePackages; }; + +export const openNodePackageScreen = async(nodeId: NodeId) => { + send(CHANNELS.openNodePackageScreen, nodeId); +} diff --git a/src/main/tray.ts b/src/main/tray.ts index 05b8eb4f6..611fb63d7 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -9,6 +9,7 @@ import { } from './podman/machine'; import { getUserNodePackages } from './state/nodePackages'; import { openPodmanModal } from './podman/podman.js'; +import { openNodePackageScreen } from './state/nodePackages.js'; // Can't import from main because of circular dependency let _getAssetPath: (...paths: string[]) => string; @@ -92,8 +93,10 @@ const getNodePackageListMenu = () => { isAlert = true; } return { - label: `${nodePackage.spec.displayName} Node ${nodePackage.status}`, + label: `${nodePackage.spec.displayName} Node ${nodePackage.status}`, click: () => { + openOrFocusWindow(); + openNodePackageScreen(nodePackage.id); logger.info(`clicked on ${nodePackage.spec.displayName}`); }, }; diff --git a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx index 97d7044d0..b883e2eaf 100644 --- a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx +++ b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx @@ -23,6 +23,8 @@ import { } from "../../state/settingsService"; import Sidebar from "../Sidebar/Sidebar"; import { setModalState } from "../../state/modal.js"; +import { NodeId } from "src/common/node.js"; +import { useNavigate } from "react-router-dom"; export interface SidebarWrapperProps { children: ReactElement; @@ -32,6 +34,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { const sSelectedNodePackageId = useAppSelector(selectSelectedNodePackageId); const sUserNodePackages = useAppSelector(selectUserNodePackages); const dispatch = useAppDispatch(); + const navigate = useNavigate(); // todo: implement a back-off polling strategy which can be "reset" const qIsPodmanInstalled = useGetIsPodmanInstalledQuery(); const isPodmanInstalled = qIsPodmanInstalled?.data; @@ -94,9 +97,18 @@ export const SidebarWrapper = forwardRef((_, ref) => { ); }, []); + const onOpenNodePackageScreen = useCallback((nodeId: NodeId) => { + navigate("/main/nodePackage"); + dispatch(updateSelectedNodePackageId(nodeId)); + }, []); + useEffect(() => { electron.ipcRenderer.on(CHANNELS.notifications, onNotificationChange); electron.ipcRenderer.on(CHANNELS.openPodmanModal, onOpenPodmanModal); + electron.ipcRenderer.on( + CHANNELS.openNodePackageScreen, + onOpenNodePackageScreen, + ); return () => { electron.ipcRenderer.removeListener( CHANNELS.notifications, @@ -106,8 +118,12 @@ export const SidebarWrapper = forwardRef((_, ref) => { CHANNELS.openPodmanModal, onOpenPodmanModal, ); + electron.ipcRenderer.removeListener( + CHANNELS.openNodePackageScreen, + onOpenNodePackageScreen, + ); }; - }, [onNotificationChange, onOpenPodmanModal]); + }, [onNotificationChange, onOpenPodmanModal, onOpenNodePackageScreen]); // Default selected node to be the first node useEffect(() => { From 1c57b57e8d35a1a64668b9ff0b480168f5f181a4 Mon Sep 17 00:00:00 2001 From: corn-potage Date: Mon, 13 May 2024 14:23:47 -0700 Subject: [PATCH 08/29] added alert dialog --- src/main/tray.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 611fb63d7..599bed9e2 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,4 +1,4 @@ -import { Menu, MenuItem, Tray } from 'electron'; +import { Menu, MenuItem, Tray, dialog } from 'electron'; import logger from './logger'; import { createWindow, fullQuit, getMainWindow } from './main'; import { isLinux, isWindows } from './platform'; @@ -61,8 +61,24 @@ export const setTrayMenu = () => { { label: 'Quit', click: () => { - fullQuit(); // app no longer runs in the background - }, + // Show confirmation dialog before quitting + // TODO: get translated strings for this + dialog.showMessageBox({ + type: 'question', + buttons: ['Yes', 'No'], + defaultId: 1, // Focus on 'No' by default + title: 'Confirm', + message: 'Are you sure you want to quit? Nodes will stop syncing.', + detail: 'Confirming will close the application.' + }).then(result => { + if (result.response === 0) { // The 'Yes' button is at index 0 + fullQuit(); // app no longer runs in the background + } + // Do nothing if the user selects 'No' + }).catch(err => { + logger.error('Error showing dialog:', err); + }); + } }, ]; const contextMenu = Menu.buildFromTemplate(menuTemplate as MenuItem[]); From 1b0e4f5e2313a58446d4df9a52fce32df1b4136b Mon Sep 17 00:00:00 2001 From: cornpotage Date: Tue, 21 May 2024 17:23:25 -0700 Subject: [PATCH 09/29] custom menu working --- src/main/main.ts | 15 ++- src/main/tray.ts | 115 ++++++++++++++++----- src/renderer/Presentational/Tray/tray.html | 34 ++++++ src/renderer/Presentational/Tray/tray.js | 48 +++++++++ 4 files changed, 187 insertions(+), 25 deletions(-) create mode 100644 src/renderer/Presentational/Tray/tray.html create mode 100644 src/renderer/Presentational/Tray/tray.js diff --git a/src/main/main.ts b/src/main/main.ts index 9aa680678..8fb273e7b 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -100,6 +100,19 @@ const getAssetPath = (...paths: string[]): string => { return path.join(RESOURCES_PATH, ...paths); }; +const getTrayPath = (...paths: string[]): string => { + return path.join( + __dirname, + '..', + '..', + 'src', + 'renderer', + 'Presentational', + 'Tray', + ...paths, + ); +}; + export const createWindow = async () => { // let name: string; // if (windowName === 'log') { @@ -268,7 +281,7 @@ const initialize = () => { monitor.initialize(); cronJobs.initialize(); i18nMain.initialize(); - tray.initialize(getAssetPath); + tray.initialize(getAssetPath, getTrayPath); console.log('app locale: ', app.getLocale()); console.log('app LocaleCountryCode: ', app.getLocaleCountryCode()); }; diff --git a/src/main/tray.ts b/src/main/tray.ts index 599bed9e2..605d33067 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,4 +1,4 @@ -import { Menu, MenuItem, Tray, dialog } from 'electron'; +import { BrowserWindow, Menu, MenuItem, Tray, dialog } from 'electron'; import logger from './logger'; import { createWindow, fullQuit, getMainWindow } from './main'; import { isLinux, isWindows } from './platform'; @@ -13,10 +13,12 @@ import { openNodePackageScreen } from './state/nodePackages.js'; // Can't import from main because of circular dependency let _getAssetPath: (...paths: string[]) => string; +let _getTrayPath: (...paths: string[]) => string; let tray: Tray; +let trayWindow: BrowserWindow | null = null; -// Can get asyncronously updated separately +// Can get asynchronously updated separately let nodePackageTrayMenu: { label: string; click: () => void }[] = []; let podmanTrayMenu: MenuItem[] = []; let openNiceNodeMenu: { label: string; click: () => void }[] = []; @@ -48,7 +50,7 @@ const openOrFocusWindow = () => { } mainWindow.focus(); // Focus on the window } -} +}; export const setTrayMenu = () => { const menuTemplate = [ @@ -63,22 +65,26 @@ export const setTrayMenu = () => { click: () => { // Show confirmation dialog before quitting // TODO: get translated strings for this - dialog.showMessageBox({ - type: 'question', - buttons: ['Yes', 'No'], - defaultId: 1, // Focus on 'No' by default - title: 'Confirm', - message: 'Are you sure you want to quit? Nodes will stop syncing.', - detail: 'Confirming will close the application.' - }).then(result => { - if (result.response === 0) { // The 'Yes' button is at index 0 - fullQuit(); // app no longer runs in the background - } - // Do nothing if the user selects 'No' - }).catch(err => { - logger.error('Error showing dialog:', err); - }); - } + dialog + .showMessageBox({ + type: 'question', + buttons: ['Yes', 'No'], + defaultId: 1, // Focus on 'No' by default + title: 'Confirm', + message: 'Are you sure you want to quit? Nodes will stop syncing.', + detail: 'Confirming will close the application.', + }) + .then((result) => { + if (result.response === 0) { + // The 'Yes' button is at index 0 + fullQuit(); // app no longer runs in the background + } + // Do nothing if the user selects 'No' + }) + .catch((err) => { + logger.error('Error showing dialog:', err); + }); + }, }, ]; const contextMenu = Menu.buildFromTemplate(menuTemplate as MenuItem[]); @@ -171,23 +177,82 @@ export const updateTrayMenu = () => { getOpenNiceNodeMenu(); }; -export const initialize = (getAssetPath: (...paths: string[]) => string) => { +function createTrayWindow() { + trayWindow = new BrowserWindow({ + width: 300, + height: 100, // Initial height + show: false, + frame: false, + resizable: false, + webPreferences: { + // preload: _getTrayPath('tray.js'), + contextIsolation: false, + nodeIntegration: true, + }, + }); + + trayWindow.loadURL(`file://${_getTrayPath('tray.html')}`); + + trayWindow.on('blur', () => { + if (trayWindow) { + trayWindow.hide(); + } + }); + + trayWindow.webContents.on('did-finish-load', () => { + trayWindow!.webContents + .executeJavaScript(` + new Promise((resolve) => { + const height = document.body.scrollHeight; + resolve(height); + }); + `) + .then((height: number) => { + trayWindow!.setSize(300, height); + trayWindow!.webContents.openDevTools(); + }); + }); +} + +function toggleTrayWindow() { + if (trayWindow) { + if (trayWindow.isVisible()) { + trayWindow.hide(); + } else { + const trayBounds = tray.getBounds(); + const windowBounds = trayWindow.getBounds(); + const x = Math.round( + trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2, + ); + const y = Math.round(trayBounds.y + trayBounds.height); + trayWindow.setPosition(x, y, false); + trayWindow.show(); + } + } +} + +export const initialize = ( + getAssetPath: (...paths: string[]) => string, + getTrayPath: (...paths: string[]) => string, +) => { logger.info('tray initializing...'); _getAssetPath = getAssetPath; + _getTrayPath = getTrayPath; + let icon = getAssetPath('icons', 'tray', 'NNIconDefaultInvertedTemplate.png'); if (isWindows()) { icon = getAssetPath('icon.ico'); } tray = new Tray(icon); - // on windows, show a colored icon, 64x64 default icon seems ok - updateTrayMenu(); - // Update the status of everything in the tray when it is opened + createTrayWindow(); + // updateTrayMenu(); + tray.on('click', () => { // on windows, default is open/show window on click // on mac, default is open menu on click (no code needed) // on linux? - updateTrayMenu(); + toggleTrayWindow(); if (isWindows()) { const window = getMainWindow(); if (window) { @@ -199,8 +264,10 @@ export const initialize = (getAssetPath: (...paths: string[]) => string) => { } } }); + // on windows, the menu opens with a right click (no code needed) // also, the 'right-click' event is not triggered on windows + logger.info('tray initialized'); }; diff --git a/src/renderer/Presentational/Tray/tray.html b/src/renderer/Presentational/Tray/tray.html new file mode 100644 index 000000000..c8deaff6e --- /dev/null +++ b/src/renderer/Presentational/Tray/tray.html @@ -0,0 +1,34 @@ + + + + + Tray Menu + + + + + + + diff --git a/src/renderer/Presentational/Tray/tray.js b/src/renderer/Presentational/Tray/tray.js new file mode 100644 index 000000000..0199dd91b --- /dev/null +++ b/src/renderer/Presentational/Tray/tray.js @@ -0,0 +1,48 @@ +const { ipcRenderer } = require('electron'); + +window.addEventListener('DOMContentLoaded', () => { + const menuItems = [ + { name: 'Ethereum', status: 'error stopping' }, + { name: 'Minecraft Server', status: 'error stopping' }, + { separator: true }, + { name: 'Podman', status: 'Running', checked: true }, + { separator: true }, + { name: 'Open NiceNode', action: 'show-main-window' }, + { name: 'Quit', action: 'quit' }, + ]; + + const container = document.getElementById('menu-container'); + + menuItems.forEach((item) => { + if (item.separator) { + const separator = document.createElement('div'); + separator.className = 'separator'; + container.appendChild(separator); + } else { + const menuItem = document.createElement('div'); + menuItem.className = 'menu-item'; + + const nameSpan = document.createElement('span'); + nameSpan.textContent = item.name; + menuItem.appendChild(nameSpan); + + if (item.status) { + const statusSpan = document.createElement('span'); + statusSpan.textContent = item.status; + menuItem.appendChild(statusSpan); + } + + menuItem.addEventListener('click', () => { + if (item.action) { + ipcRenderer.send(item.action); + } + }); + + container.appendChild(menuItem); + } + }); + + ipcRenderer.on('update-menu', (event, updatedItems) => { + // Update menu items if necessary + }); +}); From bccd7eec5295908bae6e1a8ffa2b3c48323c9411 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 23 May 2024 17:43:31 -0700 Subject: [PATCH 10/29] added theme support, and first try at ipc commands --- src/main/tray.ts | 39 +++++++++++++++++++++- src/renderer/Presentational/Tray/tray.html | 10 +++--- src/renderer/Presentational/Tray/tray.js | 17 +++++++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 605d33067..1f3fee5f4 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,4 +1,12 @@ -import { BrowserWindow, Menu, MenuItem, Tray, dialog } from 'electron'; +import { + BrowserWindow, + Menu, + MenuItem, + Tray, + dialog, + nativeTheme, + ipcMain, +} from 'electron'; import logger from './logger'; import { createWindow, fullQuit, getMainWindow } from './main'; import { isLinux, isWindows } from './platform'; @@ -184,11 +192,13 @@ function createTrayWindow() { show: false, frame: false, resizable: false, + transparent: true, webPreferences: { // preload: _getTrayPath('tray.js'), contextIsolation: false, nodeIntegration: true, }, + vibrancy: process.platform === 'darwin' ? 'sidebar' : undefined, }); trayWindow.loadURL(`file://${_getTrayPath('tray.html')}`); @@ -199,6 +209,11 @@ function createTrayWindow() { } }); + nativeTheme.on('updated', () => { + const theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; + trayWindow!.webContents.send('set-theme', theme); + }); + trayWindow.webContents.on('did-finish-load', () => { trayWindow!.webContents .executeJavaScript(` @@ -271,5 +286,27 @@ export const initialize = ( logger.info('tray initialized'); }; +ipcMain.on('quit-app', (event) => { + dialog + .showMessageBox({ + type: 'question', + buttons: ['Yes', 'No'], + defaultId: 1, // Focus on 'No' by default + title: 'Confirm', + message: 'Are you sure you want to quit? Nodes will stop syncing.', + detail: 'Confirming will close the application.', + }) + .then((result) => { + if (result.response === 0) { + // The 'Yes' button is at index 0 + fullQuit(); // app no longer runs in the background + } + // Do nothing if the user selects 'No' + }) + .catch((err) => { + logger.error('Error showing dialog:', err); + }); +}); + // todo: handle a node status change // this could include a new node, removed node, or just a status change diff --git a/src/renderer/Presentational/Tray/tray.html b/src/renderer/Presentational/Tray/tray.html index c8deaff6e..8c7e82793 100644 --- a/src/renderer/Presentational/Tray/tray.html +++ b/src/renderer/Presentational/Tray/tray.html @@ -7,10 +7,10 @@ body { margin: 0; padding: 6px; - font-size: 13px; + font-size: 12.5px; font-family: Arial, sans-serif; - background: #2b2b2b; - color: #ffffff; + /* background: #2b2b2b; */ + color: white; } .menu-item { display: flex; @@ -19,7 +19,9 @@ cursor: pointer; } .menu-item:hover { - background: #3b3b3b; + border-radius: 4px; + color: white; + background: rgba(0, 122, 255, 0.8); } .separator { border-bottom: 1px solid #555; diff --git a/src/renderer/Presentational/Tray/tray.js b/src/renderer/Presentational/Tray/tray.js index 0199dd91b..79dc9cddd 100644 --- a/src/renderer/Presentational/Tray/tray.js +++ b/src/renderer/Presentational/Tray/tray.js @@ -8,7 +8,7 @@ window.addEventListener('DOMContentLoaded', () => { { name: 'Podman', status: 'Running', checked: true }, { separator: true }, { name: 'Open NiceNode', action: 'show-main-window' }, - { name: 'Quit', action: 'quit' }, + { name: 'Quit', action: 'quit-app' }, ]; const container = document.getElementById('menu-container'); @@ -42,6 +42,21 @@ window.addEventListener('DOMContentLoaded', () => { } }); + ipcRenderer.on('set-theme', (event, theme) => { + applyTheme(theme); + }); + + // Apply theme-based styles + const applyTheme = (theme) => { + const body = document.body; + const menuItems = document.querySelectorAll('.menu-item'); + if (theme === 'dark') { + body.style.color = 'white'; + } else { + body.style.color = 'black'; + } + }; + ipcRenderer.on('update-menu', (event, updatedItems) => { // Update menu items if necessary }); From f35fcc5aeda69ba8969df53f3bea27b2e1d2cff0 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 23 May 2024 18:53:33 -0700 Subject: [PATCH 11/29] separate out native and custom menu, add node status checking func --- src/main/tray.ts | 138 +++++++++++++++++------ src/renderer/Presentational/Tray/tray.js | 127 +++++++++++---------- 2 files changed, 171 insertions(+), 94 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 1f3fee5f4..fa921bb9a 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -185,7 +185,7 @@ export const updateTrayMenu = () => { getOpenNiceNodeMenu(); }; -function createTrayWindow() { +function createCustomTrayWindow() { trayWindow = new BrowserWindow({ width: 300, height: 100, // Initial height @@ -215,21 +215,108 @@ function createTrayWindow() { }); trayWindow.webContents.on('did-finish-load', () => { - trayWindow!.webContents - .executeJavaScript(` - new Promise((resolve) => { - const height = document.body.scrollHeight; - resolve(height); - }); - `) - .then((height: number) => { - trayWindow!.setSize(300, height); - trayWindow!.webContents.openDevTools(); + updateCustomTrayMenu(); + }); + + ipcMain.on('adjust-height', (event, height) => { + if (trayWindow) { + trayWindow.setSize(300, height); + } + }); + + ipcMain.on('node-package-click', (event, id) => { + openOrFocusWindow(); + openNodePackageScreen(id); + logger.info(`clicked on node package with id ${id}`); + }); + + ipcMain.on('podman-click', async (event) => { + const { status } = await getCustomPodmanMenuItem(); + logger.info('clicked on podman machine'); + if (status === 'Running' || status === 'Starting') { + stopMachineIfCreated(); + } else { + openOrFocusWindow(); + openPodmanModal(); + } + }); + + ipcMain.on('quit-app', (event) => { + dialog + .showMessageBox({ + type: 'question', + buttons: ['Yes', 'No'], + defaultId: 1, // Focus on 'No' by default + title: 'Confirm', + message: 'Are you sure you want to quit? Nodes will stop syncing.', + detail: 'Confirming will close the application.', + }) + .then((result) => { + if (result.response === 0) { + // The 'Yes' button is at index 0 + fullQuit(); // app no longer runs in the background + } + // Do nothing if the user selects 'No' + }) + .catch((err) => { + logger.error('Error showing dialog:', err); }); }); } -function toggleTrayWindow() { +const getCustomNodePackageListMenu = () => { + const userNodes = getUserNodePackages(); + let isAlert = false; + const nodePackageTrayMenu = userNodes.nodeIds.map((nodeId) => { + const nodePackage = userNodes.nodes[nodeId]; + if (nodePackage.status.toLowerCase().includes('error')) { + isAlert = true; + } + return { + name: nodePackage.spec.displayName, + status: nodePackage.status, + id: nodePackage.id, + }; + }); + + console.log('getCustomNodePackageListMenu'); + return { nodePackageTrayMenu, isAlert }; +}; + +const getCustomPodmanMenuItem = async () => { + if (isLinux()) { + return { status: 'N/A' }; + } + let status = 'Loading...'; + try { + const podmanMachine = await getNiceNodeMachine(); + if (podmanMachine) { + status = podmanMachine.Running ? 'Running' : 'Stopped'; + if (podmanMachine.Starting === true) { + status = 'Starting'; + } + } + } catch (e) { + console.error('tray podmanMachine error: ', e); + status = 'Not found'; + } + return { status }; +}; + +export const updateCustomTrayMenu = async () => { + const { nodePackageTrayMenu, isAlert } = getCustomNodePackageListMenu(); + const podmanMenuItem = await getCustomPodmanMenuItem(); + + if (trayWindow) { + trayWindow.webContents.send('update-menu', { + nodePackageTrayMenu, + podmanMenuItem, + }); + } + setTrayIcon(isAlert ? 'Alert' : 'Default'); +}; + +function toggleCustomTrayWindow() { if (trayWindow) { if (trayWindow.isVisible()) { trayWindow.hide(); @@ -260,14 +347,15 @@ export const initialize = ( } tray = new Tray(icon); - createTrayWindow(); + createCustomTrayWindow(); // updateTrayMenu(); tray.on('click', () => { // on windows, default is open/show window on click // on mac, default is open menu on click (no code needed) // on linux? - toggleTrayWindow(); + toggleCustomTrayWindow(); + updateCustomTrayMenu(); if (isWindows()) { const window = getMainWindow(); if (window) { @@ -286,27 +374,5 @@ export const initialize = ( logger.info('tray initialized'); }; -ipcMain.on('quit-app', (event) => { - dialog - .showMessageBox({ - type: 'question', - buttons: ['Yes', 'No'], - defaultId: 1, // Focus on 'No' by default - title: 'Confirm', - message: 'Are you sure you want to quit? Nodes will stop syncing.', - detail: 'Confirming will close the application.', - }) - .then((result) => { - if (result.response === 0) { - // The 'Yes' button is at index 0 - fullQuit(); // app no longer runs in the background - } - // Do nothing if the user selects 'No' - }) - .catch((err) => { - logger.error('Error showing dialog:', err); - }); -}); - // todo: handle a node status change // this could include a new node, removed node, or just a status change diff --git a/src/renderer/Presentational/Tray/tray.js b/src/renderer/Presentational/Tray/tray.js index 79dc9cddd..6544b7f5e 100644 --- a/src/renderer/Presentational/Tray/tray.js +++ b/src/renderer/Presentational/Tray/tray.js @@ -1,63 +1,74 @@ const { ipcRenderer } = require('electron'); -window.addEventListener('DOMContentLoaded', () => { - const menuItems = [ - { name: 'Ethereum', status: 'error stopping' }, - { name: 'Minecraft Server', status: 'error stopping' }, - { separator: true }, - { name: 'Podman', status: 'Running', checked: true }, - { separator: true }, - { name: 'Open NiceNode', action: 'show-main-window' }, - { name: 'Quit', action: 'quit-app' }, - ]; - - const container = document.getElementById('menu-container'); - - menuItems.forEach((item) => { - if (item.separator) { - const separator = document.createElement('div'); - separator.className = 'separator'; - container.appendChild(separator); - } else { - const menuItem = document.createElement('div'); - menuItem.className = 'menu-item'; - - const nameSpan = document.createElement('span'); - nameSpan.textContent = item.name; - menuItem.appendChild(nameSpan); - - if (item.status) { - const statusSpan = document.createElement('span'); - statusSpan.textContent = item.status; - menuItem.appendChild(statusSpan); - } +ipcRenderer.on( + 'update-menu', + (event, { nodePackageTrayMenu, podmanMenuItem }) => { + const menuItems = [ + ...nodePackageTrayMenu.map((item) => ({ + name: item.name, + status: item.status, + action: () => ipcRenderer.send('node-package-click', item.id), + })), + { separator: true }, + { + name: 'Podman', + status: podmanMenuItem.status, + action: () => ipcRenderer.send('podman-click'), + }, + { separator: true }, + { + name: 'Open NiceNode', + action: () => ipcRenderer.send('show-main-window'), + }, + { name: 'Quit', action: () => ipcRenderer.send('quit-app') }, + ]; + + const container = document.getElementById('menu-container'); + container.innerHTML = ''; // Clear existing items + + menuItems.forEach((item) => { + if (item.separator) { + const separator = document.createElement('div'); + separator.className = 'separator'; + container.appendChild(separator); + } else { + const menuItem = document.createElement('div'); + menuItem.className = 'menu-item'; - menuItem.addEventListener('click', () => { - if (item.action) { - ipcRenderer.send(item.action); + const nameSpan = document.createElement('span'); + nameSpan.textContent = item.name; + menuItem.appendChild(nameSpan); + + if (item.status) { + const statusSpan = document.createElement('span'); + statusSpan.textContent = item.status; + menuItem.appendChild(statusSpan); } - }); - - container.appendChild(menuItem); - } - }); - - ipcRenderer.on('set-theme', (event, theme) => { - applyTheme(theme); - }); - - // Apply theme-based styles - const applyTheme = (theme) => { - const body = document.body; - const menuItems = document.querySelectorAll('.menu-item'); - if (theme === 'dark') { - body.style.color = 'white'; - } else { - body.style.color = 'black'; - } - }; - - ipcRenderer.on('update-menu', (event, updatedItems) => { - // Update menu items if necessary - }); + + menuItem.addEventListener('click', item.action); + + container.appendChild(menuItem); + } + }); + ipcRenderer.send('adjust-height', document.body.scrollHeight); + }, +); + +ipcRenderer.on('set-theme', (event, theme) => { + applyTheme(theme); +}); + +// Apply theme-based styles +const applyTheme = (theme) => { + const body = document.body; + const menuItems = document.querySelectorAll('.menu-item'); + if (theme === 'dark') { + body.style.color = 'white'; + } else { + body.style.color = 'black'; + } +}; + +ipcRenderer.on('update-menu', (event, updatedItems) => { + // Update menu items if necessary }); From 7d6daafe3f5b159b0461642c770c0d71ac6c1aed Mon Sep 17 00:00:00 2001 From: cornpotage Date: Tue, 28 May 2024 16:51:52 -0700 Subject: [PATCH 12/29] improved node package status for running --- src/main/nodePackageManager.ts | 5 ++++- src/main/tray.ts | 3 ++- src/renderer/Presentational/Tray/tray.html | 2 +- src/renderer/Presentational/Tray/tray.js | 16 ++++++++++------ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/nodePackageManager.ts b/src/main/nodePackageManager.ts index f80e8868a..5520054aa 100644 --- a/src/main/nodePackageManager.ts +++ b/src/main/nodePackageManager.ts @@ -144,6 +144,7 @@ export const startNodePackage = async (nodeId: NodeId) => { node.lastStartedTimestampMs = Date.now(); node.stoppedBy = undefined; nodePackageStore.updateNodePackage(node); + let allServicesStarted = true; const isEthereumPackage = node.spec.specId === 'ethereum'; @@ -153,6 +154,7 @@ export const startNodePackage = async (nodeId: NodeId) => { } catch (e) { logger.error(`Unable to start node service: ${JSON.stringify(service)}`); nodePackageStatus = NodeStatus.errorStarting; + allServicesStarted = false; // try to start all services, or stop other services? // todo: set as partially started? // throw e; @@ -182,7 +184,8 @@ export const startNodePackage = async (nodeId: NodeId) => { } // If all node services start without error, the package is considered running - if (nodePackageStatus === NodeStatus.running) { + if (allServicesStarted) { + nodePackageStatus = NodeStatus.running; setLastRunningTime(nodeId, 'node'); } diff --git a/src/main/tray.ts b/src/main/tray.ts index fa921bb9a..9381e1160 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -187,7 +187,7 @@ export const updateTrayMenu = () => { function createCustomTrayWindow() { trayWindow = new BrowserWindow({ - width: 300, + // width: 277, height: 100, // Initial height show: false, frame: false, @@ -348,6 +348,7 @@ export const initialize = ( tray = new Tray(icon); createCustomTrayWindow(); + trayWindow!.webContents.openDevTools(); // updateTrayMenu(); tray.on('click', () => { diff --git a/src/renderer/Presentational/Tray/tray.html b/src/renderer/Presentational/Tray/tray.html index 8c7e82793..f5a078348 100644 --- a/src/renderer/Presentational/Tray/tray.html +++ b/src/renderer/Presentational/Tray/tray.html @@ -15,7 +15,7 @@ .menu-item { display: flex; justify-content: space-between; - padding: 3px 10px; + padding: 4px 10px; cursor: pointer; } .menu-item:hover { diff --git a/src/renderer/Presentational/Tray/tray.js b/src/renderer/Presentational/Tray/tray.js index 6544b7f5e..2ff6cddfb 100644 --- a/src/renderer/Presentational/Tray/tray.js +++ b/src/renderer/Presentational/Tray/tray.js @@ -10,12 +10,16 @@ ipcRenderer.on( action: () => ipcRenderer.send('node-package-click', item.id), })), { separator: true }, - { - name: 'Podman', - status: podmanMenuItem.status, - action: () => ipcRenderer.send('podman-click'), - }, - { separator: true }, + ...(podmanMenuItem.status !== 'Running' + ? [ + { + name: 'Podman', + status: podmanMenuItem.status, + action: () => ipcRenderer.send('podman-click'), + }, + { separator: true }, + ] + : []), { name: 'Open NiceNode', action: () => ipcRenderer.send('show-main-window'), From 0772091ae943e57a2d34eb1f1484483f5d3bac98 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 30 May 2024 17:57:50 -0700 Subject: [PATCH 13/29] added svg icons --- assets/icons/tray/status/error.svg | 3 + assets/icons/tray/status/stopped.svg | 3 + assets/icons/tray/status/synced.svg | 3 + assets/icons/tray/status/syncing.svg | 3 + src/main/tray.ts | 35 +++++++-- src/renderer/Presentational/Tray/index.html | 71 +++++++++++++++++++ .../Presentational/Tray/{tray.js => index.js} | 30 ++++++-- src/renderer/Presentational/Tray/tray.html | 36 ---------- 8 files changed, 138 insertions(+), 46 deletions(-) create mode 100644 assets/icons/tray/status/error.svg create mode 100644 assets/icons/tray/status/stopped.svg create mode 100644 assets/icons/tray/status/synced.svg create mode 100644 assets/icons/tray/status/syncing.svg create mode 100644 src/renderer/Presentational/Tray/index.html rename src/renderer/Presentational/Tray/{tray.js => index.js} (65%) delete mode 100644 src/renderer/Presentational/Tray/tray.html diff --git a/assets/icons/tray/status/error.svg b/assets/icons/tray/status/error.svg new file mode 100644 index 000000000..51c88b5e6 --- /dev/null +++ b/assets/icons/tray/status/error.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/tray/status/stopped.svg b/assets/icons/tray/status/stopped.svg new file mode 100644 index 000000000..37aa73bfa --- /dev/null +++ b/assets/icons/tray/status/stopped.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/tray/status/synced.svg b/assets/icons/tray/status/synced.svg new file mode 100644 index 000000000..808f05627 --- /dev/null +++ b/assets/icons/tray/status/synced.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/tray/status/syncing.svg b/assets/icons/tray/status/syncing.svg new file mode 100644 index 000000000..dfe4dc511 --- /dev/null +++ b/assets/icons/tray/status/syncing.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/main/tray.ts b/src/main/tray.ts index 9381e1160..84e84c171 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -18,6 +18,7 @@ import { import { getUserNodePackages } from './state/nodePackages'; import { openPodmanModal } from './podman/podman.js'; import { openNodePackageScreen } from './state/nodePackages.js'; +import fs from 'node:fs/promises'; // Can't import from main because of circular dependency let _getAssetPath: (...paths: string[]) => string; @@ -194,18 +195,17 @@ function createCustomTrayWindow() { resizable: false, transparent: true, webPreferences: { - // preload: _getTrayPath('tray.js'), contextIsolation: false, nodeIntegration: true, }, vibrancy: process.platform === 'darwin' ? 'sidebar' : undefined, }); - trayWindow.loadURL(`file://${_getTrayPath('tray.html')}`); + trayWindow.loadURL(`file://${_getTrayPath('index.html')}`); trayWindow.on('blur', () => { if (trayWindow) { - trayWindow.hide(); + // trayWindow.hide(); } }); @@ -273,7 +273,8 @@ const getCustomNodePackageListMenu = () => { isAlert = true; } return { - name: nodePackage.spec.displayName, + //TODO: localization + name: `${nodePackage.spec.displayName} Node`, status: nodePackage.status, id: nodePackage.id, }; @@ -303,14 +304,40 @@ const getCustomPodmanMenuItem = async () => { return { status }; }; +const readSVGContent = async (filePath) => { + try { + const data = await fs.readFile(filePath, 'utf8'); + return data; + } catch (error) { + console.error(`Error reading file from path ${filePath}`, error); + return ''; + } +}; + export const updateCustomTrayMenu = async () => { const { nodePackageTrayMenu, isAlert } = getCustomNodePackageListMenu(); const podmanMenuItem = await getCustomPodmanMenuItem(); + const statusIcons = { + synced: await readSVGContent( + _getAssetPath('icons', 'tray', 'status', 'synced.svg'), + ), + error: await readSVGContent( + _getAssetPath('icons', 'tray', 'status', 'error.svg'), + ), + syncing: await readSVGContent( + _getAssetPath('icons', 'tray', 'status', 'syncing.svg'), + ), + stopped: await readSVGContent( + _getAssetPath('icons', 'tray', 'status', 'stopped.svg'), + ), + }; + if (trayWindow) { trayWindow.webContents.send('update-menu', { nodePackageTrayMenu, podmanMenuItem, + statusIcons, }); } setTrayIcon(isAlert ? 'Alert' : 'Default'); diff --git a/src/renderer/Presentational/Tray/index.html b/src/renderer/Presentational/Tray/index.html new file mode 100644 index 000000000..828252831 --- /dev/null +++ b/src/renderer/Presentational/Tray/index.html @@ -0,0 +1,71 @@ + + + + + Tray Menu + + + + + + + diff --git a/src/renderer/Presentational/Tray/tray.js b/src/renderer/Presentational/Tray/index.js similarity index 65% rename from src/renderer/Presentational/Tray/tray.js rename to src/renderer/Presentational/Tray/index.js index 2ff6cddfb..bab31bd53 100644 --- a/src/renderer/Presentational/Tray/tray.js +++ b/src/renderer/Presentational/Tray/index.js @@ -2,7 +2,7 @@ const { ipcRenderer } = require('electron'); ipcRenderer.on( 'update-menu', - (event, { nodePackageTrayMenu, podmanMenuItem }) => { + (event, { nodePackageTrayMenu, podmanMenuItem, statusIcons }) => { const menuItems = [ ...nodePackageTrayMenu.map((item) => ({ name: item.name, @@ -44,9 +44,25 @@ ipcRenderer.on( menuItem.appendChild(nameSpan); if (item.status) { - const statusSpan = document.createElement('span'); - statusSpan.textContent = item.status; - menuItem.appendChild(statusSpan); + const statusContainer = document.createElement('div'); + statusContainer.className = 'menu-status-container'; + + const statusIconContainer = document.createElement('div'); + statusIconContainer.className = 'menu-status-icon'; + + const statusIcon = document.createElement('div'); + statusIcon.innerHTML = + statusIcons[item.status] || statusIcons['default']; + statusIcon.className = 'status-icon'; + + const statusText = document.createElement('div'); + statusText.className = 'menu-status'; + statusText.textContent = item.status; + + statusIconContainer.appendChild(statusIcon); + statusContainer.appendChild(statusIconContainer); + statusContainer.appendChild(statusText); + menuItem.appendChild(statusContainer); } menuItem.addEventListener('click', item.action); @@ -67,9 +83,11 @@ const applyTheme = (theme) => { const body = document.body; const menuItems = document.querySelectorAll('.menu-item'); if (theme === 'dark') { - body.style.color = 'white'; + body.classList.add('dark'); + body.classList.remove('light'); } else { - body.style.color = 'black'; + body.classList.add('light'); + body.classList.remove('dark'); } }; diff --git a/src/renderer/Presentational/Tray/tray.html b/src/renderer/Presentational/Tray/tray.html deleted file mode 100644 index f5a078348..000000000 --- a/src/renderer/Presentational/Tray/tray.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Tray Menu - - - - - - - From d78ec77958fa274f7eb5a89a370aa751e44ce5b8 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 30 May 2024 18:05:17 -0700 Subject: [PATCH 14/29] added styling for stopped nodes --- src/renderer/Presentational/Tray/index.html | 17 +++++++++++++++++ src/renderer/Presentational/Tray/index.js | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/src/renderer/Presentational/Tray/index.html b/src/renderer/Presentational/Tray/index.html index 828252831..cd7f2d69c 100644 --- a/src/renderer/Presentational/Tray/index.html +++ b/src/renderer/Presentational/Tray/index.html @@ -27,6 +27,15 @@ align-items: center; padding: 4px 10px; } + + .light .menu-item.stopped { + color: rgba(0, 0, 0, 0.25); + } + + .dark .menu-item.stopped { + color: rgba(255,255,255,0.25); + } + .menu-item:hover { border-radius: 4px; color: white; @@ -47,10 +56,18 @@ color: rgba(0, 0, 0, 0.5); } + .light .menu-item.stopped .menu-status-container { + color: rgba(0, 0, 0, 0.25); + } + .dark .menu-status-container { color: rgba(255,255,255,0.5); } + .dark .menu-item.stopped .menu-status-container { + color: rgba(255,255,255,0.25); + } + .menu-status-icon { display: flex; align-items: center; diff --git a/src/renderer/Presentational/Tray/index.js b/src/renderer/Presentational/Tray/index.js index bab31bd53..6e4640cdd 100644 --- a/src/renderer/Presentational/Tray/index.js +++ b/src/renderer/Presentational/Tray/index.js @@ -39,6 +39,10 @@ ipcRenderer.on( const menuItem = document.createElement('div'); menuItem.className = 'menu-item'; + if (item.status === 'stopped') { + menuItem.classList.add('stopped'); + } + const nameSpan = document.createElement('span'); nameSpan.textContent = item.name; menuItem.appendChild(nameSpan); From 731ab4d6c148a760edde42e271619a6dfc83e7e2 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 30 May 2024 18:18:45 -0700 Subject: [PATCH 15/29] stronger logic for status and icons --- src/main/tray.ts | 3 +++ src/renderer/Presentational/Tray/index.html | 4 ++++ src/renderer/Presentational/Tray/index.js | 7 +++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 84e84c171..7dde49574 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -328,6 +328,9 @@ export const updateCustomTrayMenu = async () => { syncing: await readSVGContent( _getAssetPath('icons', 'tray', 'status', 'syncing.svg'), ), + default: await readSVGContent( + _getAssetPath('icons', 'tray', 'status', 'syncing.svg'), + ), stopped: await readSVGContent( _getAssetPath('icons', 'tray', 'status', 'stopped.svg'), ), diff --git a/src/renderer/Presentational/Tray/index.html b/src/renderer/Presentational/Tray/index.html index cd7f2d69c..91ac66732 100644 --- a/src/renderer/Presentational/Tray/index.html +++ b/src/renderer/Presentational/Tray/index.html @@ -68,6 +68,10 @@ color: rgba(255,255,255,0.25); } + .menu-status { + text-transform: capitalize; + } + .menu-status-icon { display: flex; align-items: center; diff --git a/src/renderer/Presentational/Tray/index.js b/src/renderer/Presentational/Tray/index.js index 6e4640cdd..efb408275 100644 --- a/src/renderer/Presentational/Tray/index.js +++ b/src/renderer/Presentational/Tray/index.js @@ -55,8 +55,11 @@ ipcRenderer.on( statusIconContainer.className = 'menu-status-icon'; const statusIcon = document.createElement('div'); - statusIcon.innerHTML = - statusIcons[item.status] || statusIcons['default']; + const icon = + item.status === 'running' || item.status === 'starting' + ? 'syncing' + : item.status; + statusIcon.innerHTML = statusIcons[icon] || statusIcons['default']; statusIcon.className = 'status-icon'; const statusText = document.createElement('div'); From 4451ec1b2b4b5cc3523e0aca102ae94853bd67b7 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 30 May 2024 19:14:07 -0700 Subject: [PATCH 16/29] style changes. prepare to try react component --- src/main/tray.ts | 16 ++++++++++++++-- src/renderer/Presentational/Tray/index.html | 13 ++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 7dde49574..0b17f33e3 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -205,7 +205,7 @@ function createCustomTrayWindow() { trayWindow.on('blur', () => { if (trayWindow) { - // trayWindow.hide(); + trayWindow.hide(); } }); @@ -241,6 +241,11 @@ function createCustomTrayWindow() { } }); + ipcMain.on('show-main-window', async (event) => { + logger.info('clicked on show-main-window'); + openOrFocusWindow(); + }); + ipcMain.on('quit-app', (event) => { dialog .showMessageBox({ @@ -304,9 +309,16 @@ const getCustomPodmanMenuItem = async () => { return { status }; }; -const readSVGContent = async (filePath) => { +const svgCache = new Map(); + +const readSVGContent = async (filePath: string) => { + if (svgCache.has(filePath)) { + return svgCache.get(filePath); + } + try { const data = await fs.readFile(filePath, 'utf8'); + svgCache.set(filePath, data); return data; } catch (error) { console.error(`Error reading file from path ${filePath}`, error); diff --git a/src/renderer/Presentational/Tray/index.html b/src/renderer/Presentational/Tray/index.html index 91ac66732..2402ec656 100644 --- a/src/renderer/Presentational/Tray/index.html +++ b/src/renderer/Presentational/Tray/index.html @@ -42,8 +42,19 @@ background: rgba(0, 122, 255, 0.8); } + .menu-item.stopped:hover { + background: none; + } + + .dark .separator { + border-bottom: 1px solid rgba(255, 255, 255, 0.1) + } + + .light .separator { + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + } + .separator { - border-bottom: 1px solid #555; margin: 3px 0; } From 0e9e616a2cee0660d6c754387f7796bc28d7f330 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 30 May 2024 20:11:55 -0700 Subject: [PATCH 17/29] refined podman status --- src/main/tray.ts | 22 ++++++++++-- src/renderer/Presentational/Tray/index.js | 44 +++++++++++++++++++---- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 0b17f33e3..7835c5108 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -19,6 +19,7 @@ import { getUserNodePackages } from './state/nodePackages'; import { openPodmanModal } from './podman/podman.js'; import { openNodePackageScreen } from './state/nodePackages.js'; import fs from 'node:fs/promises'; +import { getPodmanDetails } from './podman/details.js'; // Can't import from main because of circular dependency let _getAssetPath: (...paths: string[]) => string; @@ -297,10 +298,24 @@ const getCustomPodmanMenuItem = async () => { try { const podmanMachine = await getNiceNodeMachine(); if (podmanMachine) { - status = podmanMachine.Running ? 'Running' : 'Stopped'; - if (podmanMachine.Starting === true) { - status = 'Starting'; + const podmanDetails = await getPodmanDetails(); + switch (true) { + case !podmanDetails.isInstalled: + status = 'notInstalled'; + break; + case podmanDetails.isOutdated: + status = 'isOutdated'; + break; + case !podmanDetails.isRunning: + status = 'notRunning'; + break; + case podmanDetails.isRunning: + status = 'isRunning'; + break; + default: + status = 'isRunning'; } + status = 'notRunning'; } } catch (e) { console.error('tray podmanMachine error: ', e); @@ -399,6 +414,7 @@ export const initialize = ( // on linux? toggleCustomTrayWindow(); updateCustomTrayMenu(); + // updateTrayMenu(); if (isWindows()) { const window = getMainWindow(); if (window) { diff --git a/src/renderer/Presentational/Tray/index.js b/src/renderer/Presentational/Tray/index.js index efb408275..2e9117fe8 100644 --- a/src/renderer/Presentational/Tray/index.js +++ b/src/renderer/Presentational/Tray/index.js @@ -1,5 +1,38 @@ const { ipcRenderer } = require('electron'); +const getIconKey = (status) => { + let icon = 'syncing'; + if (status === 'running' || status === 'starting') { + icon = 'syncing'; + } else if ( + status === 'notInstalled' || + status === 'notRunning' || + status === 'isOutdated' + ) { + icon = 'error'; + } else { + icon = status; + } + return icon; +}; + +const getStatusText = (status) => { + let text = status; + console.log('status', status); + switch (status) { + case 'notInstalled': + text = 'Not Installed'; + break; + case 'notRunning': + text = 'Not Running'; + break; + case 'isOutdated': + text = 'Update Now'; + break; + } + return text; +}; + ipcRenderer.on( 'update-menu', (event, { nodePackageTrayMenu, podmanMenuItem, statusIcons }) => { @@ -10,7 +43,7 @@ ipcRenderer.on( action: () => ipcRenderer.send('node-package-click', item.id), })), { separator: true }, - ...(podmanMenuItem.status !== 'Running' + ...(podmanMenuItem.status !== 'isRunning' ? [ { name: 'Podman', @@ -55,16 +88,13 @@ ipcRenderer.on( statusIconContainer.className = 'menu-status-icon'; const statusIcon = document.createElement('div'); - const icon = - item.status === 'running' || item.status === 'starting' - ? 'syncing' - : item.status; - statusIcon.innerHTML = statusIcons[icon] || statusIcons['default']; + statusIcon.innerHTML = + statusIcons[getIconKey(item.status)] || statusIcons['default']; statusIcon.className = 'status-icon'; const statusText = document.createElement('div'); statusText.className = 'menu-status'; - statusText.textContent = item.status; + statusText.textContent = getStatusText(item.status); statusIconContainer.appendChild(statusIcon); statusContainer.appendChild(statusIconContainer); From 99daca9515a49e80e88ef15ccd0bb6a04590bae3 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Fri, 31 May 2024 15:34:01 -0700 Subject: [PATCH 18/29] improved logic on banners --- src/main/tray.ts | 3 +- .../Generics/redesign/Banner/Banner.tsx | 54 ++++++------- .../Presentational/Sidebar/Sidebar.tsx | 78 +++++++++---------- src/renderer/Presentational/Tray/index.js | 36 ++++----- 4 files changed, 82 insertions(+), 89 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 7835c5108..696bd55f0 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -294,7 +294,7 @@ const getCustomPodmanMenuItem = async () => { if (isLinux()) { return { status: 'N/A' }; } - let status = 'Loading...'; + let status = 'notInstalled'; try { const podmanMachine = await getNiceNodeMachine(); if (podmanMachine) { @@ -315,7 +315,6 @@ const getCustomPodmanMenuItem = async () => { default: status = 'isRunning'; } - status = 'notRunning'; } } catch (e) { console.error('tray podmanMachine error: ', e); diff --git a/src/renderer/Generics/redesign/Banner/Banner.tsx b/src/renderer/Generics/redesign/Banner/Banner.tsx index 1cc7ad723..d6b0ff24e 100644 --- a/src/renderer/Generics/redesign/Banner/Banner.tsx +++ b/src/renderer/Generics/redesign/Banner/Banner.tsx @@ -1,14 +1,14 @@ -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import type { IconId } from "../../../assets/images/icons"; -import { Icon } from "../Icon/Icon"; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { IconId } from '../../../assets/images/icons'; +import { Icon } from '../Icon/Icon'; import { container, descriptionStyle, innerContainer, textContainer, titleStyle, -} from "./banner.css"; +} from './banner.css'; export interface BannerProps { /** @@ -41,32 +41,32 @@ export const Banner = ({ onClick, }: BannerProps) => { // Use useState to manage description dynamically - const [description, setDescription] = useState(""); - const [iconId, setIconId] = useState("blank"); - const [title, setTitle] = useState(""); + const [description, setDescription] = useState(''); + const [iconId, setIconId] = useState('blank'); + const [title, setTitle] = useState(''); const [loading, setLoading] = useState(false); const [isClicked, setIsClicked] = useState(false); - const { t: g } = useTranslation("genericComponents"); + const { t: g } = useTranslation('genericComponents'); useEffect(() => { if (offline) { - setIconId("boltstrike"); - setTitle(g("CurrentlyOffline")); - setDescription(g("PleaseReconnect")); + setIconId('boltstrike'); + setTitle(g('CurrentlyOffline')); + setDescription(g('PleaseReconnect')); } else if (podmanInstalled === false) { - setIconId("warningcircle"); - setTitle(g("PodmanIsNotInstalled")); - setDescription(g("ClickToInstallPodman")); + setIconId('warningcircle'); + setTitle(g('PodmanIsNotInstalled')); + setDescription(g('ClickToInstallPodman')); } else if (updateAvailable) { - setIconId("download1"); - setTitle(g("UpdateAvailable")); - setDescription(g("NewVersionAvailable")); + setIconId('download1'); + setTitle(g('UpdateAvailable')); + setDescription(g('NewVersionAvailable')); } else if (podmanStopped) { - setIconId("play"); - setTitle(g("PodmanIsNotRunning")); - setDescription(g("ClickToStartPodman")); + setIconId('play'); + setTitle(g('PodmanIsNotRunning')); + setDescription(g('ClickToStartPodman')); } - }, [offline, updateAvailable, podmanStopped, podmanInstalled, g]); + }, [offline, updateAvailable, podmanStopped, podmanInstalled]); const onClickBanner = () => { if (isClicked || offline) { @@ -76,13 +76,13 @@ export const Banner = ({ setIsClicked(true); if (!podmanInstalled) { - setDescription(g("PodmanInstalling")); + setDescription(g('PodmanInstalling')); setLoading(true); } else if (podmanStopped) { - setDescription(g("PodmanLoading")); + setDescription(g('PodmanLoading')); setLoading(true); } else if (updateAvailable) { - console.log("update nice node!"); + console.log('update nice node!'); } if (onClick) { @@ -92,14 +92,14 @@ export const Banner = ({ return (
-
+
{title}
diff --git a/src/renderer/Presentational/Sidebar/Sidebar.tsx b/src/renderer/Presentational/Sidebar/Sidebar.tsx index 71bbd4669..b3792134b 100644 --- a/src/renderer/Presentational/Sidebar/Sidebar.tsx +++ b/src/renderer/Presentational/Sidebar/Sidebar.tsx @@ -1,17 +1,17 @@ -import { forwardRef } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import type { NodeId, UserNodePackages } from "../../../common/node"; -import { Banner } from "../../Generics/redesign/Banner/Banner"; -import type { NotificationItemProps } from "../../Generics/redesign/NotificationItem/NotificationItem"; -import { SidebarLinkItem } from "../../Generics/redesign/SidebarLinkItem/SidebarLinkItem"; -import { SidebarTitleItem } from "../../Generics/redesign/SidebarTitleItem/SidebarTitleItem"; -import type { IconId } from "../../assets/images/icons"; -import { useAppDispatch } from "../../state/hooks"; -import { setModalState } from "../../state/modal"; -import { updateSelectedNodePackageId } from "../../state/node"; -import { SidebarNodeItemWrapper } from "../SidebarNodeItemWrapper/SidebarNodeItemWrapper"; -import { container, itemList, nodeList, titleItem } from "./sidebar.css"; +import { forwardRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import type { NodeId, UserNodePackages } from '../../../common/node'; +import { Banner } from '../../Generics/redesign/Banner/Banner'; +import type { NotificationItemProps } from '../../Generics/redesign/NotificationItem/NotificationItem'; +import { SidebarLinkItem } from '../../Generics/redesign/SidebarLinkItem/SidebarLinkItem'; +import { SidebarTitleItem } from '../../Generics/redesign/SidebarTitleItem/SidebarTitleItem'; +import type { IconId } from '../../assets/images/icons'; +import { useAppDispatch } from '../../state/hooks'; +import { setModalState } from '../../state/modal'; +import { updateSelectedNodePackageId } from '../../state/node'; +import { SidebarNodeItemWrapper } from '../SidebarNodeItemWrapper/SidebarNodeItemWrapper'; +import { container, itemList, nodeList, titleItem } from './sidebar.css'; // import { NodeIconId } from '../../assets/images/nodeIcons'; export interface SidebarProps { @@ -57,39 +57,39 @@ const Sidebar = forwardRef( const itemListData: { iconId: IconId; label: string; count?: number }[] = [ { - iconId: "bell", - label: t("Notifications"), + iconId: 'bell', + label: t('Notifications'), count: notifications?.length, }, { - iconId: "add", - label: t("AddNode"), + iconId: 'add', + label: t('AddNode'), }, { - iconId: "preferences", - label: t("Preferences"), + iconId: 'preferences', + label: t('Preferences'), }, { - iconId: "health", - label: t("SystemMonitor"), + iconId: 'health', + label: t('SystemMonitor'), }, ]; const bannerConfigs = [ - { key: "offline", condition: offline, props: { offline: true } }, + { key: 'offline', condition: offline, props: { offline: true } }, { - key: "updateAvailable", + key: 'updateAvailable', condition: updateAvailable, props: { updateAvailable: true, onClick: onClickInstallPodman }, }, { - key: "podmanNotInstalled", + key: 'podmanNotInstalled', condition: !podmanInstalled, props: { podmanInstalled: false, onClick: onClickInstallPodman }, }, { - key: "podmanStopped", - condition: podmanStopped, + key: 'podmanStopped', + condition: podmanInstalled && podmanStopped, props: { podmanStopped: true, onClick: onClickStartPodman }, }, ]; @@ -101,11 +101,11 @@ const Sidebar = forwardRef( const navigate = useNavigate(); return ( -
+
{banners}
- +
{/* {nodeListObject.nodeService.length === 2 && ( ( node={node} selected={selectedNodePackageId === node.id} onClick={() => { - navigate("/main/nodePackage"); + navigate('/main/nodePackage'); dispatch(updateSelectedNodePackageId(node.id)); }} /> @@ -145,25 +145,25 @@ const Sidebar = forwardRef( label={item.label} count={item.count} onClick={() => { - console.log("sidebar link item clicked: ", item.iconId); - if (item.iconId === "add") { + console.log('sidebar link item clicked: ', item.iconId); + if (item.iconId === 'add') { dispatch( setModalState({ isModalOpen: true, - screen: { route: "addNode", type: "modal" }, + screen: { route: 'addNode', type: 'modal' }, }), ); - } else if (item.iconId === "preferences") { + } else if (item.iconId === 'preferences') { dispatch( setModalState({ isModalOpen: true, - screen: { route: "preferences", type: "modal" }, + screen: { route: 'preferences', type: 'modal' }, }), ); - } else if (item.iconId === "bell") { - navigate("/main/notification"); - } else if (item.iconId === "health") { - navigate("/main/system"); + } else if (item.iconId === 'bell') { + navigate('/main/notification'); + } else if (item.iconId === 'health') { + navigate('/main/system'); } }} /> diff --git a/src/renderer/Presentational/Tray/index.js b/src/renderer/Presentational/Tray/index.js index 2e9117fe8..ca1c5d947 100644 --- a/src/renderer/Presentational/Tray/index.js +++ b/src/renderer/Presentational/Tray/index.js @@ -1,36 +1,30 @@ const { ipcRenderer } = require('electron'); const getIconKey = (status) => { - let icon = 'syncing'; - if (status === 'running' || status === 'starting') { - icon = 'syncing'; - } else if ( - status === 'notInstalled' || - status === 'notRunning' || - status === 'isOutdated' - ) { - icon = 'error'; - } else { - icon = status; + switch (status) { + case 'running': + case 'starting': + return 'syncing'; + case 'notInstalled': + case 'notRunning': + case 'isOutdated': + return 'error'; + default: + return status; } - return icon; }; const getStatusText = (status) => { - let text = status; - console.log('status', status); switch (status) { case 'notInstalled': - text = 'Not Installed'; - break; + return 'Not Installed'; case 'notRunning': - text = 'Not Running'; - break; + return 'Not Running'; case 'isOutdated': - text = 'Update Now'; - break; + return 'Update Now'; + default: + return status; } - return text; }; ipcRenderer.on( From a20544ea2cc0f2416194e3d161f3b91a4778be07 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Fri, 31 May 2024 15:45:34 -0700 Subject: [PATCH 19/29] disable click prevention in case user quits modal --- src/renderer/Generics/redesign/Banner/Banner.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/Generics/redesign/Banner/Banner.tsx b/src/renderer/Generics/redesign/Banner/Banner.tsx index d6b0ff24e..f5e271d45 100644 --- a/src/renderer/Generics/redesign/Banner/Banner.tsx +++ b/src/renderer/Generics/redesign/Banner/Banner.tsx @@ -45,7 +45,7 @@ export const Banner = ({ const [iconId, setIconId] = useState('blank'); const [title, setTitle] = useState(''); const [loading, setLoading] = useState(false); - const [isClicked, setIsClicked] = useState(false); + // const [isClicked, setIsClicked] = useState(false); const { t: g } = useTranslation('genericComponents'); useEffect(() => { @@ -69,11 +69,11 @@ export const Banner = ({ }, [offline, updateAvailable, podmanStopped, podmanInstalled]); const onClickBanner = () => { - if (isClicked || offline) { + if (offline) { return; } - setIsClicked(true); + // setIsClicked(true); if (!podmanInstalled) { setDescription(g('PodmanInstalling')); From d1cad6134a7b00d79206d16fe2b9aab62075aaa1 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Fri, 31 May 2024 16:15:03 -0700 Subject: [PATCH 20/29] added additional errors for nodes --- src/renderer/Presentational/Tray/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/renderer/Presentational/Tray/index.js b/src/renderer/Presentational/Tray/index.js index ca1c5d947..631246d2c 100644 --- a/src/renderer/Presentational/Tray/index.js +++ b/src/renderer/Presentational/Tray/index.js @@ -5,6 +5,10 @@ const getIconKey = (status) => { case 'running': case 'starting': return 'syncing'; + //TODO: consider camelcased strings + case 'error running': + case 'error starting': + case 'error stopping': case 'notInstalled': case 'notRunning': case 'isOutdated': From 33f68e37e2e5dfd3782257663e1827df85edfa63 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Sun, 2 Jun 2024 18:02:21 -0700 Subject: [PATCH 21/29] added additional positioning for other OS --- src/main/podman/podman.ts | 2 +- src/main/ports.ts | 2 +- src/main/state/nodePackages.ts | 4 +- src/main/tray.ts | 57 ++++++-- .../ModalManager/ModalManager.tsx | 26 ++-- .../ModalManager/PodmanModal.tsx | 20 +-- .../ModalManager/PreferencesModal.tsx | 28 ++-- .../ModalManager/modalUtils.tsx | 40 +++--- .../requirementsChecklistUtil.tsx | 130 +++++++++--------- .../Presentational/NodeScreen/NodeScreen.tsx | 110 +++++++-------- .../PodmanModal/PodmanWrapper.tsx | 8 +- .../SidebarWrapper/SidebarWrapper.tsx | 46 +++---- 12 files changed, 252 insertions(+), 221 deletions(-) diff --git a/src/main/podman/podman.ts b/src/main/podman/podman.ts index c5ccec644..5b93be64c 100644 --- a/src/main/podman/podman.ts +++ b/src/main/podman/podman.ts @@ -20,6 +20,7 @@ import { type ConfigValuesMap, buildCliConfig, } from '../../common/nodeConfig'; +import { CHANNELS, send } from '../messenger.js'; import { restartNodes } from '../nodePackageManager'; import { isLinux } from '../platform'; import { killChildProcess } from '../processExit'; @@ -29,7 +30,6 @@ import * as metricsPolling from './metricsPolling'; import { execPromise as podmanExecPromise } from './podman-desktop/podman-cli'; import { getPodmanEnvWithPath } from './podman-env-path'; import startPodman, { onStartUp } from './start'; -import { send, CHANNELS } from '../messenger.js'; let podmanWatchProcess: ChildProcess; diff --git a/src/main/ports.ts b/src/main/ports.ts index 0c5817c4b..3562c4cdb 100644 --- a/src/main/ports.ts +++ b/src/main/ports.ts @@ -4,9 +4,9 @@ import type { ConfigValue } from '../common/nodeConfig'; import { NOTIFICATIONS } from './consts/notifications'; import { httpGet } from './httpReq'; import { getNodePackageByServiceNodeId } from './state/nodePackages'; +import { getNodePackages } from './state/nodePackages'; import { getNode, getNodes, getSetPortHasChanged } from './state/nodes'; import { addNotification } from './state/notifications'; -import { getNodePackages } from './state/nodePackages'; export const getPodmanPortsForNode = ( node: Node, diff --git a/src/main/state/nodePackages.ts b/src/main/state/nodePackages.ts index 5955d22b7..f64d5c36d 100644 --- a/src/main/state/nodePackages.ts +++ b/src/main/state/nodePackages.ts @@ -207,6 +207,6 @@ export const getUserNodePackagesWithNodes = async () => { return userNodePackages; }; -export const openNodePackageScreen = async(nodeId: NodeId) => { +export const openNodePackageScreen = async (nodeId: NodeId) => { send(CHANNELS.openNodePackageScreen, nodeId); -} +}; diff --git a/src/main/tray.ts b/src/main/tray.ts index 696bd55f0..ffeebb0f4 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -1,25 +1,26 @@ +import fs from 'node:fs/promises'; import { BrowserWindow, Menu, MenuItem, Tray, dialog, - nativeTheme, ipcMain, + nativeTheme, + screen, } from 'electron'; import logger from './logger'; import { createWindow, fullQuit, getMainWindow } from './main'; import { isLinux, isWindows } from './platform'; +import { getPodmanDetails } from './podman/details.js'; import { getNiceNodeMachine, startMachineIfCreated, stopMachineIfCreated, } from './podman/machine'; -import { getUserNodePackages } from './state/nodePackages'; import { openPodmanModal } from './podman/podman.js'; +import { getUserNodePackages } from './state/nodePackages'; import { openNodePackageScreen } from './state/nodePackages.js'; -import fs from 'node:fs/promises'; -import { getPodmanDetails } from './podman/details.js'; // Can't import from main because of circular dependency let _getAssetPath: (...paths: string[]) => string; @@ -373,19 +374,49 @@ export const updateCustomTrayMenu = async () => { }; function toggleCustomTrayWindow() { - if (trayWindow) { - if (trayWindow.isVisible()) { - trayWindow.hide(); + if (trayWindow.isVisible()) { + trayWindow.hide(); + } else { + const trayBounds = tray.getBounds(); // Get the bounds of the tray icon + const windowBounds = trayWindow.getBounds(); + let x; + let y; + + if (process.platform === 'darwin') { + x = Math.round( + trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2, + ); + y = Math.round(trayBounds.y + trayBounds.height); + } else if (process.platform === 'win32') { + const display = screen.getPrimaryDisplay(); // Get the primary display details + const workArea = display.workArea; + x = Math.round( + trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2, + ); + + // Check if taskbar is at the bottom or the top + if (workArea.y < trayBounds.y) { + y = Math.round(trayBounds.y - windowBounds.height); // Taskbar is at the bottom + } else { + y = Math.round(trayBounds.y + trayBounds.height); // Taskbar is at the top + } } else { - const trayBounds = tray.getBounds(); - const windowBounds = trayWindow.getBounds(); - const x = Math.round( + // Assume Linux behaves like Windows in this context + // This could require adjustments based on the Linux distro and environment + const display = screen.getPrimaryDisplay(); + const workArea = display.workArea; + x = Math.round( trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2, ); - const y = Math.round(trayBounds.y + trayBounds.height); - trayWindow.setPosition(x, y, false); - trayWindow.show(); + y = + workArea.y < trayBounds.y + ? Math.round(trayBounds.y - windowBounds.height) + : Math.round(trayBounds.y + trayBounds.height); } + + trayWindow.setPosition(x, y, false); + trayWindow.show(); + trayWindow.focus(); } } diff --git a/src/renderer/Presentational/ModalManager/ModalManager.tsx b/src/renderer/Presentational/ModalManager/ModalManager.tsx index cb99e337f..7d6312514 100644 --- a/src/renderer/Presentational/ModalManager/ModalManager.tsx +++ b/src/renderer/Presentational/ModalManager/ModalManager.tsx @@ -1,17 +1,17 @@ -import { useCallback } from "react"; -import { useSelector } from "react-redux"; +import { useCallback } from 'react'; +import { useSelector } from 'react-redux'; -import { useAppDispatch } from "../../state/hooks"; -import { getModalState, setModalState } from "../../state/modal"; -import { AddNodeModal } from "./AddNodeModal"; -import { AlphaBuildModal } from "./AlphaBuildModal"; -import FailSystemRequirementsModal from "./FailSystemRequirementsModal"; -import { NodeSettingsModal } from "./NodeSettingsModal"; -import { PreferencesModal } from "./PreferencesModal"; -import { RemoveNodeModal } from "./RemoveNodeModal"; -import { ResetConfigModal } from "./ResetConfigModal"; -import { PodmanModal } from "./PodmanModal"; -import { modalRoutes } from "./modalUtils"; +import { useAppDispatch } from '../../state/hooks'; +import { getModalState, setModalState } from '../../state/modal'; +import { AddNodeModal } from './AddNodeModal'; +import { AlphaBuildModal } from './AlphaBuildModal'; +import FailSystemRequirementsModal from './FailSystemRequirementsModal'; +import { NodeSettingsModal } from './NodeSettingsModal'; +import { PodmanModal } from './PodmanModal'; +import { PreferencesModal } from './PreferencesModal'; +import { RemoveNodeModal } from './RemoveNodeModal'; +import { ResetConfigModal } from './ResetConfigModal'; +import { modalRoutes } from './modalUtils'; const ModalManager = () => { const { isModalOpen, screen } = useSelector(getModalState); diff --git a/src/renderer/Presentational/ModalManager/PodmanModal.tsx b/src/renderer/Presentational/ModalManager/PodmanModal.tsx index a8e2c216f..874bdaf2c 100644 --- a/src/renderer/Presentational/ModalManager/PodmanModal.tsx +++ b/src/renderer/Presentational/ModalManager/PodmanModal.tsx @@ -1,10 +1,10 @@ -import { useState, useCallback } from "react"; -import { useTranslation } from "react-i18next"; -import { Modal } from "../../Generics/redesign/Modal/Modal.js"; -import electron from "../../electronGlobal.js"; -import { reportEvent } from "../../events/reportEvent.js"; -import PodmanWrapper from "../PodmanModal/PodmanWrapper.js"; -import { type ModalConfig, modalOnChangeConfig } from "./modalUtils.js"; +import { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Modal } from '../../Generics/redesign/Modal/Modal.js'; +import electron from '../../electronGlobal.js'; +import { reportEvent } from '../../events/reportEvent.js'; +import PodmanWrapper from '../PodmanModal/PodmanWrapper.js'; +import { type ModalConfig, modalOnChangeConfig } from './modalUtils.js'; type Props = { modalOnClose: () => void; @@ -14,15 +14,15 @@ export const PodmanModal = ({ modalOnClose }: Props) => { const [modalConfig, setModalConfig] = useState({}); const [isSaveButtonDisabled, setIsSaveButtonDisabled] = useState(false); const { t } = useTranslation(); - const buttonSaveLabel = t("Done"); + const buttonSaveLabel = t('Done'); const modalOnSaveConfig = async (updatedConfig: ModalConfig | undefined) => { try { - console.log("set some kind of setting here?"); + console.log('set some kind of setting here?'); } catch (err) { console.error(err); throw new Error( - "There was an error removing the node. Try again and please report the error to the NiceNode team in Discord.", + 'There was an error removing the node. Try again and please report the error to the NiceNode team in Discord.', ); } modalOnClose(); diff --git a/src/renderer/Presentational/ModalManager/PreferencesModal.tsx b/src/renderer/Presentational/ModalManager/PreferencesModal.tsx index f1eacb8be..0d77eb515 100644 --- a/src/renderer/Presentational/ModalManager/PreferencesModal.tsx +++ b/src/renderer/Presentational/ModalManager/PreferencesModal.tsx @@ -1,15 +1,15 @@ -import { useState } from "react"; -import { useTranslation } from "react-i18next"; -import type { ThemeSetting } from "../../../main/state/settings"; -import { Modal } from "../../Generics/redesign/Modal/Modal"; -import electron from "../../electronGlobal"; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import type { ThemeSetting } from '../../../main/state/settings'; +import { Modal } from '../../Generics/redesign/Modal/Modal'; +import electron from '../../electronGlobal'; import { reportEvent, setRemoteEventReportingEnabled, -} from "../../events/reportEvent"; -import { useGetSettingsQuery } from "../../state/settingsService"; -import PreferencesWrapper from "../Preferences/PreferencesWrapper"; -import { type ModalConfig, modalOnChangeConfig } from "./modalUtils"; +} from '../../events/reportEvent'; +import { useGetSettingsQuery } from '../../state/settingsService'; +import PreferencesWrapper from '../Preferences/PreferencesWrapper'; +import { type ModalConfig, modalOnChangeConfig } from './modalUtils'; type Props = { modalOnClose: () => void; @@ -22,15 +22,15 @@ interface MetaElement extends HTMLMetaElement { export const PreferencesModal = ({ modalOnClose }: Props) => { const [modalConfig, setModalConfig] = useState({}); const { t } = useTranslation(); - const modalTitle = t("Preferences"); - const buttonSaveLabel = t("SaveChanges"); + const modalTitle = t('Preferences'); + const buttonSaveLabel = t('SaveChanges'); const qSettings = useGetSettingsQuery(); const handleColorSchemeChange = (colorScheme: ThemeSetting) => { const meta = document.querySelector( 'meta[name="color-scheme"]', ) as MetaElement; - const colorValue = colorScheme === "auto" ? "light dark" : colorScheme; + const colorValue = colorScheme === 'auto' ? 'light dark' : colorScheme; if (meta) meta.content = colorValue as ThemeSetting; }; @@ -63,9 +63,9 @@ export const PreferencesModal = ({ modalOnClose }: Props) => { isPreReleaseUpdatesEnabled, ); if (isPreReleaseUpdatesEnabled) { - reportEvent("EnablePreReleaseUpdates"); + reportEvent('EnablePreReleaseUpdates'); } else { - reportEvent("DisablePreReleaseUpdates"); + reportEvent('DisablePreReleaseUpdates'); } } if (language) { diff --git a/src/renderer/Presentational/ModalManager/modalUtils.tsx b/src/renderer/Presentational/ModalManager/modalUtils.tsx index 51cc4fbef..3e14120dd 100644 --- a/src/renderer/Presentational/ModalManager/modalUtils.tsx +++ b/src/renderer/Presentational/ModalManager/modalUtils.tsx @@ -1,12 +1,12 @@ -import type React from "react"; -import type Node from "../../../common/node"; -import type { NodePackage } from "../../../common/node"; +import type React from 'react'; +import type Node from '../../../common/node'; +import type { NodePackage } from '../../../common/node'; import type { NodeLibrary, NodePackageLibrary, -} from "../../../main/state/nodeLibrary"; -import type { ThemeSetting } from "../../../main/state/settings"; -import type { ClientSelections } from "../AddNodeConfiguration/AddNodeConfiguration"; +} from '../../../main/state/nodeLibrary'; +import type { ThemeSetting } from '../../../main/state/settings'; +import type { ClientSelections } from '../AddNodeConfiguration/AddNodeConfiguration'; export interface ModalConfig { node?: string; @@ -29,18 +29,18 @@ export interface ModalConfig { } export const modalRoutes = Object.freeze({ - addNode: "addNode", - nodeSettings: "nodeSettings", - preferences: "preferences", - addValidator: "addValidator", - clientVersions: "clientVersions", - stopNode: "stopNode", - removeNode: "removeNode", - resetConfig: "resetConfig", - updateUnavailable: "updateUnavailable", - failSystemRequirements: "failSystemRequirements", - alphaBuild: "alphaBuild", - podman: "podman", + addNode: 'addNode', + nodeSettings: 'nodeSettings', + preferences: 'preferences', + addValidator: 'addValidator', + clientVersions: 'clientVersions', + stopNode: 'stopNode', + removeNode: 'removeNode', + resetConfig: 'resetConfig', + updateUnavailable: 'updateUnavailable', + failSystemRequirements: 'failSystemRequirements', + alphaBuild: 'alphaBuild', + podman: 'podman', }); /* Use this to change config settings, saved temporarily in the modal file with backend apis until it's saved by modalOnSaveConfig @@ -55,7 +55,7 @@ export const modalOnChangeConfig = async ( modalOnSaveConfig?: (newConfig: ModalConfig) => Promise, ) => { if (!setModalConfig || !modalConfig) { - throw new Error("modal config is not defined"); + throw new Error('modal config is not defined'); } let updatedConfig = {}; @@ -74,7 +74,7 @@ export const modalOnChangeConfig = async ( // }; // } console.log( - "modalOnChangeConfig: config, modalConfig, updatedConfig", + 'modalOnChangeConfig: config, modalConfig, updatedConfig', config, modalConfig, updatedConfig, diff --git a/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx b/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx index 902d07bc2..6267bbbfe 100644 --- a/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx +++ b/src/renderer/Presentational/NodeRequirements/requirementsChecklistUtil.tsx @@ -1,21 +1,21 @@ -import type { TFunction } from "i18next"; -import type { ReactElement } from "react"; +import type { TFunction } from 'i18next'; +import type { ReactElement } from 'react'; import type { CpuRequirements, // DockerRequirements, InternetRequirements, MemoryRequirements, StorageRequirements, -} from "../../../common/systemRequirements"; +} from '../../../common/systemRequirements'; -import type { ChecklistItemProps } from "../../Generics/redesign/Checklist/ChecklistItem"; -import ExternalLink from "../../Generics/redesign/Link/ExternalLink"; -import { bytesToGB } from "../../utils"; -import type { NodeRequirementsProps } from "./NodeRequirements"; -import { findSystemStorageDetailsAtALocation } from "./nodeStorageUtil"; +import type { ChecklistItemProps } from '../../Generics/redesign/Checklist/ChecklistItem'; +import ExternalLink from '../../Generics/redesign/Link/ExternalLink'; +import { bytesToGB } from '../../utils'; +import type { NodeRequirementsProps } from './NodeRequirements'; +import { findSystemStorageDetailsAtALocation } from './nodeStorageUtil'; const isVersionHigher = (currentVersion: string, targetVersion: string) => { - const parseVersion = (version: string) => version.split(".").map(Number); + const parseVersion = (version: string) => version.split('.').map(Number); const current = parseVersion(currentVersion); const target = parseVersion(targetVersion); @@ -29,7 +29,7 @@ const isVersionHigher = (currentVersion: string, targetVersion: string) => { return false; }; -const TARGET_MACOS_VERSION = "13.0.0"; +const TARGET_MACOS_VERSION = '13.0.0'; export const makeCheckList = ( { nodeRequirements, systemData, nodeStorageLocation }: NodeRequirementsProps, @@ -47,107 +47,107 @@ export const makeCheckList = ( nodeStorageLocation, ); } - console.log("nodeLocationStorageDetails", nodeLocationStorageDetails); + console.log('nodeLocationStorageDetails', nodeLocationStorageDetails); - if (systemData?.os?.platform === "darwin") { + if (systemData?.os?.platform === 'darwin') { const checkListItem: ChecklistItemProps = { - checkTitle: t("macOSTitle", { + checkTitle: t('macOSTitle', { minVersion: TARGET_MACOS_VERSION, }), - valueText: t("macOSDescription", { + valueText: t('macOSDescription', { version: systemData?.os?.release, }), - status: "", + status: '', }; if (isVersionHigher(systemData?.os?.release, TARGET_MACOS_VERSION)) { - checkListItem.status = "complete"; + checkListItem.status = 'complete'; } else { - checkListItem.status = "error"; + checkListItem.status = 'error'; } newChecklistItems.push(checkListItem); } for (const [nodeReqKey, nodeReqValue] of Object.entries(nodeRequirements)) { console.log(`${nodeReqKey}: ${nodeReqValue}`); - if (nodeReqKey === "documentationUrl" || nodeReqKey === "description") { + if (nodeReqKey === 'documentationUrl' || nodeReqKey === 'description') { continue; } // title and desc depends on req type // title and desc depends on whether the req is met or not // if cpu, if cores meets, add success // if minSpeed doesn't meet - let checkTitle = ""; - let valueText = ""; + let checkTitle = ''; + let valueText = ''; let valueComponent: ReactElement = <>; - const captionText = ""; - let status: ChecklistItemProps["status"] = "loading"; - if (nodeReqKey === "cpu") { + const captionText = ''; + let status: ChecklistItemProps['status'] = 'loading'; + if (nodeReqKey === 'cpu') { const req = nodeReqValue as CpuRequirements; if (req.cores !== undefined) { - checkTitle = t("processorCoresTitle", { + checkTitle = t('processorCoresTitle', { minCores: req.cores, }); if (systemData?.cpu?.cores) { - valueText = t("processorCoresDescription", { + valueText = t('processorCoresDescription', { cores: systemData?.cpu.cores, }); if (systemData?.cpu.cores >= req.cores) { - status = "complete"; + status = 'complete'; } else { - status = "incomplete"; + status = 'incomplete'; } } else { - status = "error"; + status = 'error'; } } } - if (nodeReqKey === "memory") { + if (nodeReqKey === 'memory') { const req = nodeReqValue as MemoryRequirements; if (req.minSizeGBs !== undefined) { - checkTitle = t("memorySizeTitle", { + checkTitle = t('memorySizeTitle', { minSize: req.minSizeGBs, }); if (systemData?.memLayout[0]?.size) { - valueText = t("memorySizeDescription", { + valueText = t('memorySizeDescription', { size: bytesToGB(systemData?.memLayout[0]?.size), }); if (systemData?.memLayout[0]?.size >= req.minSizeGBs) { - status = "complete"; + status = 'complete'; } else { - status = "incomplete"; + status = 'incomplete'; } } else { - status = "error"; + status = 'error'; } } } - if (nodeReqKey === "storage") { + if (nodeReqKey === 'storage') { const req = nodeReqValue as StorageRequirements; const disk = nodeLocationStorageDetails; if (req.ssdRequired === true) { - checkTitle = t("storageTypeTitle", { - type: "SSD", + checkTitle = t('storageTypeTitle', { + type: 'SSD', }); if (disk?.type || disk?.name) { - if (disk?.type.includes("NVMe") || disk?.name.includes("NVMe")) { - valueText = t("storageTypeDescription", { - type: disk?.type ? disk?.type : "NVMe SSD", + if (disk?.type.includes('NVMe') || disk?.name.includes('NVMe')) { + valueText = t('storageTypeDescription', { + type: disk?.type ? disk?.type : 'NVMe SSD', }); - status = "complete"; - } else if (disk?.type.includes("SSD") || disk?.name.includes("SSD")) { - valueText = t("storageTypeDescription", { - type: disk?.type ? disk?.type : "SSD", + status = 'complete'; + } else if (disk?.type.includes('SSD') || disk?.name.includes('SSD')) { + valueText = t('storageTypeDescription', { + type: disk?.type ? disk?.type : 'SSD', }); - status = "complete"; - } else if (disk?.type.includes("HDD") || disk?.name.includes("HDD")) { - valueText = t("storageTypeDescription", { - type: disk?.type ? disk?.type : "HDD", + status = 'complete'; + } else if (disk?.type.includes('HDD') || disk?.name.includes('HDD')) { + valueText = t('storageTypeDescription', { + type: disk?.type ? disk?.type : 'HDD', }); - status = "error"; + status = 'error'; } else { - status = "incomplete"; + status = 'incomplete'; } } else { - status = "error"; + status = 'error'; } const checkListItem: ChecklistItemProps = { checkTitle, @@ -157,51 +157,51 @@ export const makeCheckList = ( newChecklistItems.push(checkListItem); } if (req.minSizeGBs !== undefined) { - checkTitle = t("storageSizeTitle", { + checkTitle = t('storageSizeTitle', { minSize: req.minSizeGBs, }); if (disk?.freeSpaceGBs) { const diskSizeGbs = Math.round(disk.freeSpaceGBs); // todo: use free space for storage calculations? - valueText = t("storageSizeDescription", { + valueText = t('storageSizeDescription', { freeSize: diskSizeGbs, storageName: disk.name, }); if (diskSizeGbs >= req.minSizeGBs) { - status = "complete"; + status = 'complete'; } else { - status = "incomplete"; + status = 'incomplete'; } } else { - status = "error"; + status = 'error'; } } } - if (nodeReqKey === "internet") { + if (nodeReqKey === 'internet') { const req = nodeReqValue as InternetRequirements; if (req.minDownloadSpeedMbps !== undefined) { - checkTitle = t("internetSpeedTitle", { + checkTitle = t('internetSpeedTitle', { minDownloadSpeed: req.minDownloadSpeedMbps, minUploadSpeed: req.minUploadSpeedMbps, }); - valueText = t("internetSpeedPleaseTest"); + valueText = t('internetSpeedPleaseTest'); valueComponent = ( <> - {`${t("internetSpeedTestWebsites")} `} + {`${t('internetSpeedTestWebsites')} `} {" "} - or{" "} + />{' '} + or{' '} ); - status = "information"; + status = 'information'; } } // Todoo: Don't make user think about Podman? diff --git a/src/renderer/Presentational/NodeScreen/NodeScreen.tsx b/src/renderer/Presentational/NodeScreen/NodeScreen.tsx index 75adeb1e8..a3c57b21c 100644 --- a/src/renderer/Presentational/NodeScreen/NodeScreen.tsx +++ b/src/renderer/Presentational/NodeScreen/NodeScreen.tsx @@ -1,38 +1,38 @@ -import { useTranslation } from "react-i18next"; - -import { useCallback, useEffect, useState } from "react"; -import { useNavigate } from "react-router-dom"; - -import { NodeStatus } from "../../../common/node"; -import Button from "../../Generics/redesign/Button/Button"; -import { HeaderButton } from "../../Generics/redesign/HeaderButton/HeaderButton"; -import type { NodeAction } from "../../Generics/redesign/consts"; -import type { NodeBackgroundId } from "../../assets/images/nodeBackgrounds"; -import electron from "../../electronGlobal"; -import { useAppDispatch, useAppSelector } from "../../state/hooks"; -import { setModalState } from "../../state/modal"; +import { useTranslation } from 'react-i18next'; + +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { NodeStatus } from '../../../common/node'; +import Button from '../../Generics/redesign/Button/Button'; +import { HeaderButton } from '../../Generics/redesign/HeaderButton/HeaderButton'; +import type { NodeAction } from '../../Generics/redesign/consts'; +import type { NodeBackgroundId } from '../../assets/images/nodeBackgrounds'; +import electron from '../../electronGlobal'; +import { useAppDispatch, useAppSelector } from '../../state/hooks'; +import { setModalState } from '../../state/modal'; import { selectIsAvailableForPolling, selectSelectedNode, -} from "../../state/node"; +} from '../../state/node'; import { useGetExecutionIsSyncingQuery, useGetExecutionLatestBlockQuery, useGetExecutionPeersQuery, useGetNodeVersionQuery, -} from "../../state/services"; -import { useGetIsPodmanRunningQuery } from "../../state/settingsService"; -import { hexToDecimal } from "../../utils"; +} from '../../state/services'; +import { useGetIsPodmanRunningQuery } from '../../state/settingsService'; +import { hexToDecimal } from '../../utils'; import ContentSingleClient, { type SingleNodeContent, -} from "../ContentSingleClient/ContentSingleClient"; +} from '../ContentSingleClient/ContentSingleClient'; import { backButtonContainer, container, contentContainer, descriptionFont, titleFont, -} from "./NodeScreen.css"; +} from './NodeScreen.css'; let alphaModalRendered = false; @@ -45,7 +45,7 @@ const NodeScreen = () => { ); const [sIsSyncing, setIsSyncing] = useState(); // we will bring this var back in the future - const [sSyncPercent, setSyncPercent] = useState(""); + const [sSyncPercent, setSyncPercent] = useState(''); const [sPeers, setPeers] = useState(); const [sFreeStorageGBs, setFreeStorageGBs] = useState(0); const [sTotalDiskSize, setTotalDiskSize] = useState(0); @@ -127,7 +127,7 @@ const NodeScreen = () => { useEffect(() => { if (!sIsAvailableForPolling) { // clear all node data when it becomes unavailable to get - setSyncPercent(""); + setSyncPercent(''); setIsSyncing(undefined); setPeers(undefined); setLatestBlockNumber(0); @@ -135,35 +135,35 @@ const NodeScreen = () => { }, [sIsAvailableForPolling]); useEffect(() => { - console.log("qExecutionIsSyncing: ", qExecutionIsSyncing); + console.log('qExecutionIsSyncing: ', qExecutionIsSyncing); if (qExecutionIsSyncing.isError) { - setSyncPercent(""); + setSyncPercent(''); setIsSyncing(undefined); return; } const syncingData = qExecutionIsSyncing.data; - if (typeof syncingData === "object") { + if (typeof syncingData === 'object') { setSyncPercent(syncingData.syncPercent); setIsSyncing(syncingData.isSyncing); } else if (syncingData === false) { // light client geth, it is done syncing if data is false - setSyncPercent(""); + setSyncPercent(''); setIsSyncing(false); } else { - setSyncPercent(""); + setSyncPercent(''); setIsSyncing(undefined); } }, [qExecutionIsSyncing]); useEffect(() => { - console.log("qExecutionPeers: ", qExecutionPeers.data); + console.log('qExecutionPeers: ', qExecutionPeers.data); if (qExecutionPeers.isError) { setPeers(undefined); return; } - if (typeof qExecutionPeers.data === "string") { + if (typeof qExecutionPeers.data === 'string') { setPeers(qExecutionPeers.data); - } else if (typeof qExecutionPeers.data === "number") { + } else if (typeof qExecutionPeers.data === 'number') { setPeers(qExecutionPeers.data.toString()); } else { setPeers(undefined); @@ -191,17 +191,17 @@ const NodeScreen = () => { let latestBlockNum = 0; if ( blockNumber && - typeof blockNumber === "string" && - rpcTranslation === "eth-l1" + typeof blockNumber === 'string' && + rpcTranslation === 'eth-l1' ) { latestBlockNum = hexToDecimal(blockNumber); } else if ( slotNumber && - typeof slotNumber === "string" && - rpcTranslation === "eth-l1-beacon" + typeof slotNumber === 'string' && + rpcTranslation === 'eth-l1-beacon' ) { latestBlockNum = Number.parseFloat(slotNumber); - } else if (rpcTranslation === "farcaster-l1") { + } else if (rpcTranslation === 'farcaster-l1') { latestBlockNum = qLatestBlock.data; } @@ -213,15 +213,15 @@ const NodeScreen = () => { const onNodeAction = useCallback( (action: NodeAction) => { - console.log("NodeAction for node: ", action, selectedNode); + console.log('NodeAction for node: ', action, selectedNode); if (selectedNode) { - if (action === "start") { + if (action === 'start') { electron.startNode(selectedNode.id); - } else if (action === "stop") { + } else if (action === 'stop') { electron.stopNode(selectedNode.id); - } else if (action === "logs") { + } else if (action === 'logs') { // show logs - } else if (action === "settings") { + } else if (action === 'settings') { // show settings } } @@ -255,7 +255,7 @@ const NodeScreen = () => { dispatch( setModalState({ isModalOpen: true, - screen: { route: "alphaBuild", type: "info" }, + screen: { route: 'alphaBuild', type: 'info' }, }), ); alphaModalRendered = true; @@ -266,10 +266,10 @@ const NodeScreen = () => { return (
-
{t("NoActiveNodes")}
-
{t("AddFirstNode")}
+
{t('NoActiveNodes')}
+
{t('AddFirstNode')}
diff --git a/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx b/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx index 7ceef7714..c09d7bb86 100644 --- a/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx +++ b/src/renderer/Presentational/PodmanModal/PodmanWrapper.tsx @@ -1,6 +1,6 @@ -import { useCallback } from "react"; -import type { ModalConfig } from "../ModalManager/modalUtils.js"; -import PodmanInstallation from "../PodmanInstallation/PodmanInstallation.tsx"; +import { useCallback } from 'react'; +import type { ModalConfig } from '../ModalManager/modalUtils.js'; +import PodmanInstallation from '../PodmanInstallation/PodmanInstallation.tsx'; export interface PodmanWrapperProps { modalOnChangeConfig: (config: ModalConfig, save?: boolean) => void; @@ -12,7 +12,7 @@ const PodmanWrapper = ({ disableSaveButton, }: PodmanWrapperProps) => { const onChangeDockerInstall = useCallback((newValue: string) => { - if (newValue === "done") { + if (newValue === 'done') { disableSaveButton(false); } }, []); diff --git a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx index b883e2eaf..ef655e810 100644 --- a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx +++ b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx @@ -4,27 +4,27 @@ import { useCallback, useEffect, useState, -} from "react"; -import { CHANNELS } from "../../../main/messenger"; -import type { NotificationItemProps } from "../../Generics/redesign/NotificationItem/NotificationItem"; -import electron from "../../electronGlobal"; -import { useAppDispatch, useAppSelector } from "../../state/hooks"; -import { useGetNetworkConnectedQuery } from "../../state/network"; +} from 'react'; +import { useNavigate } from 'react-router-dom'; +import type { NodeId } from 'src/common/node.js'; +import { CHANNELS } from '../../../main/messenger'; +import type { NotificationItemProps } from '../../Generics/redesign/NotificationItem/NotificationItem'; +import electron from '../../electronGlobal'; +import { useAppDispatch, useAppSelector } from '../../state/hooks'; +import { setModalState } from '../../state/modal.js'; +import { useGetNetworkConnectedQuery } from '../../state/network'; import { selectSelectedNodePackageId, selectUserNodePackages, updateSelectedNodePackageId, -} from "../../state/node"; -import { useGetNotificationsQuery } from "../../state/notificationsService"; +} from '../../state/node'; +import { useGetNotificationsQuery } from '../../state/notificationsService'; import { useGetIsPodmanInstalledQuery, useGetIsPodmanRunningQuery, useGetPodmanDetailsQuery, -} from "../../state/settingsService"; -import Sidebar from "../Sidebar/Sidebar"; -import { setModalState } from "../../state/modal.js"; -import { NodeId } from "src/common/node.js"; -import { useNavigate } from "react-router-dom"; +} from '../../state/settingsService'; +import Sidebar from '../Sidebar/Sidebar'; export interface SidebarWrapperProps { children: ReactElement; @@ -48,7 +48,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { pollingInterval: 15000, }); const podmanDetails = qPodmanDetails?.data; - const [platform, setPlatform] = useState(""); + const [platform, setPlatform] = useState(''); // default to docker is running while data is being fetched, so // the user isn't falsely warned let isPodmanRunning = true; @@ -61,9 +61,9 @@ export const SidebarWrapper = forwardRef((_, ref) => { setModalState({ isModalOpen: true, screen: { - route: "podman", - type: "modal", - data: { view: "update" }, + route: 'podman', + type: 'modal', + data: { view: 'update' }, }, }), ); @@ -81,7 +81,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { // subscribes to a channel which notifies when dark mode settings change const onNotificationChange = useCallback(() => { - console.log("onNotificationChange"); + console.log('onNotificationChange'); qNotifications?.refetch(); }, []); @@ -90,15 +90,15 @@ export const SidebarWrapper = forwardRef((_, ref) => { setModalState({ isModalOpen: true, screen: { - route: "podman", - type: "modal", + route: 'podman', + type: 'modal', }, }), ); }, []); const onOpenNodePackageScreen = useCallback((nodeId: NodeId) => { - navigate("/main/nodePackage"); + navigate('/main/nodePackage'); dispatch(updateSelectedNodePackageId(nodeId)); }, []); @@ -140,7 +140,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { useEffect(() => { const asyncData = async () => { const userSettings = await electron.getSettings(); - setPlatform(userSettings.osPlatform || ""); + setPlatform(userSettings.osPlatform || ''); }; asyncData(); }, []); @@ -150,7 +150,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { platform={platform} ref={ref} notifications={notifications} - offline={qNetwork.status === "rejected"} + offline={qNetwork.status === 'rejected'} updateAvailable={podmanDetails?.isOutdated || false} podmanInstalled={isPodmanInstalled} podmanStopped={!isPodmanRunning} From 0b6e945cd640b2a4753ec05ad6c8a9435aee3304 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Sun, 2 Jun 2024 18:05:50 -0700 Subject: [PATCH 22/29] fixed lint errors --- src/main/tray.ts | 7 +++++-- src/renderer/Presentational/Tray/index.js | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index ffeebb0f4..7a89220c7 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -213,7 +213,7 @@ function createCustomTrayWindow() { nativeTheme.on('updated', () => { const theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'; - trayWindow!.webContents.send('set-theme', theme); + trayWindow?.webContents.send('set-theme', theme); }); trayWindow.webContents.on('did-finish-load', () => { @@ -435,7 +435,10 @@ export const initialize = ( tray = new Tray(icon); createCustomTrayWindow(); - trayWindow!.webContents.openDevTools(); + + if (process.env.NODE_ENV === 'development') { + trayWindow?.webContents.openDevTools(); + } // updateTrayMenu(); tray.on('click', () => { diff --git a/src/renderer/Presentational/Tray/index.js b/src/renderer/Presentational/Tray/index.js index 631246d2c..df274f16b 100644 --- a/src/renderer/Presentational/Tray/index.js +++ b/src/renderer/Presentational/Tray/index.js @@ -87,7 +87,7 @@ ipcRenderer.on( const statusIcon = document.createElement('div'); statusIcon.innerHTML = - statusIcons[getIconKey(item.status)] || statusIcons['default']; + statusIcons[getIconKey(item.status)] || statusIcons.default; statusIcon.className = 'status-icon'; const statusText = document.createElement('div'); From 067f451c40b49243e46c3b6a50b53587a85c9d58 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Mon, 3 Jun 2024 17:21:43 -0700 Subject: [PATCH 23/29] reverted to fix --- src/main/tray.ts | 6 +++--- .../Presentational/NodePackageScreen/NodePackageScreen.tsx | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 7a89220c7..c7c518a87 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -436,9 +436,9 @@ export const initialize = ( tray = new Tray(icon); createCustomTrayWindow(); - if (process.env.NODE_ENV === 'development') { - trayWindow?.webContents.openDevTools(); - } + // if (process.env.NODE_ENV === 'development') { + // trayWindow?.webContents.openDevTools(); + // } // updateTrayMenu(); tray.on('click', () => { diff --git a/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx b/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx index 83c2ac287..b116b7855 100644 --- a/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx +++ b/src/renderer/Presentational/NodePackageScreen/NodePackageScreen.tsx @@ -318,10 +318,7 @@ const NodePackageScreen = () => { dispatch( setModalState({ isModalOpen: true, - screen: { - route: 'podman', - type: 'modal', - }, + screen: { route: 'addNode', type: 'modal' }, }), ); }} From 4525a7c55aee400739cd9a678dcaf5ed9de76ea2 Mon Sep 17 00:00:00 2001 From: jgresham Date: Mon, 3 Jun 2024 23:31:01 -0400 Subject: [PATCH 24/29] fix: put tray files in assets as main loads them --- .../Tray/index.html => assets/trayIndex.html | 2 +- .../Tray/index.js => assets/trayIndex.js | 0 src/main/main.ts | 15 +-------------- src/main/tray.ts | 9 ++------- 4 files changed, 4 insertions(+), 22 deletions(-) rename src/renderer/Presentational/Tray/index.html => assets/trayIndex.html (98%) rename src/renderer/Presentational/Tray/index.js => assets/trayIndex.js (100%) diff --git a/src/renderer/Presentational/Tray/index.html b/assets/trayIndex.html similarity index 98% rename from src/renderer/Presentational/Tray/index.html rename to assets/trayIndex.html index 2402ec656..4b55b551a 100644 --- a/src/renderer/Presentational/Tray/index.html +++ b/assets/trayIndex.html @@ -98,6 +98,6 @@ - + diff --git a/src/renderer/Presentational/Tray/index.js b/assets/trayIndex.js similarity index 100% rename from src/renderer/Presentational/Tray/index.js rename to assets/trayIndex.js diff --git a/src/main/main.ts b/src/main/main.ts index af6bc3861..e3842619c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -99,19 +99,6 @@ const getAssetPath = (...paths: string[]): string => { return path.join(RESOURCES_PATH, ...paths); }; -const getTrayPath = (...paths: string[]): string => { - return path.join( - __dirname, - '..', - '..', - 'src', - 'renderer', - 'Presentational', - 'Tray', - ...paths, - ); -}; - export const createWindow = async () => { // let name: string; // if (windowName === 'log') { @@ -288,7 +275,7 @@ const initialize = () => { monitor.initialize(); cronJobs.initialize(); i18nMain.initialize(); - tray.initialize(getAssetPath, getTrayPath); + tray.initialize(getAssetPath); console.log('app locale: ', app.getLocale()); console.log('app LocaleCountryCode: ', app.getLocaleCountryCode()); }; diff --git a/src/main/tray.ts b/src/main/tray.ts index c7c518a87..cdc969097 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -24,7 +24,6 @@ import { openNodePackageScreen } from './state/nodePackages.js'; // Can't import from main because of circular dependency let _getAssetPath: (...paths: string[]) => string; -let _getTrayPath: (...paths: string[]) => string; let tray: Tray; let trayWindow: BrowserWindow | null = null; @@ -203,7 +202,7 @@ function createCustomTrayWindow() { vibrancy: process.platform === 'darwin' ? 'sidebar' : undefined, }); - trayWindow.loadURL(`file://${_getTrayPath('index.html')}`); + trayWindow.loadURL(`file://${_getAssetPath('trayIndex.html')}`); trayWindow.on('blur', () => { if (trayWindow) { @@ -420,13 +419,9 @@ function toggleCustomTrayWindow() { } } -export const initialize = ( - getAssetPath: (...paths: string[]) => string, - getTrayPath: (...paths: string[]) => string, -) => { +export const initialize = (getAssetPath: (...paths: string[]) => string) => { logger.info('tray initializing...'); _getAssetPath = getAssetPath; - _getTrayPath = getTrayPath; let icon = getAssetPath('icons', 'tray', 'NNIconDefaultInvertedTemplate.png'); if (isWindows()) { From 0737be329b26aef6060993fceb8c673a970bd599 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Thu, 6 Jun 2024 14:29:28 -0700 Subject: [PATCH 25/29] replaced icons, added conditional on separator --- assets/icons/tray/status/error.svg | 4 ++-- assets/icons/tray/status/stopped.svg | 4 ++-- assets/icons/tray/status/synced.svg | 4 ++-- assets/icons/tray/status/syncing.svg | 4 ++-- assets/trayIndex.html | 4 ++-- assets/trayIndex.js | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/assets/icons/tray/status/error.svg b/assets/icons/tray/status/error.svg index 51c88b5e6..998c06e2f 100644 --- a/assets/icons/tray/status/error.svg +++ b/assets/icons/tray/status/error.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/tray/status/stopped.svg b/assets/icons/tray/status/stopped.svg index 37aa73bfa..62e03c0d3 100644 --- a/assets/icons/tray/status/stopped.svg +++ b/assets/icons/tray/status/stopped.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/tray/status/synced.svg b/assets/icons/tray/status/synced.svg index 808f05627..1e99f1228 100644 --- a/assets/icons/tray/status/synced.svg +++ b/assets/icons/tray/status/synced.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/tray/status/syncing.svg b/assets/icons/tray/status/syncing.svg index dfe4dc511..c1185c77f 100644 --- a/assets/icons/tray/status/syncing.svg +++ b/assets/icons/tray/status/syncing.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/trayIndex.html b/assets/trayIndex.html index 4b55b551a..2b6f28205 100644 --- a/assets/trayIndex.html +++ b/assets/trayIndex.html @@ -90,8 +90,8 @@ } .status-icon { - width: 16px; /* Adjust size as needed */ - height: 16px; /* Adjust size as needed */ + width: 12px; /* Adjust size as needed */ + height: 12px; /* Adjust size as needed */ vertical-align: middle; /* Align icon with text */ } diff --git a/assets/trayIndex.js b/assets/trayIndex.js index df274f16b..ffa90bf82 100644 --- a/assets/trayIndex.js +++ b/assets/trayIndex.js @@ -40,7 +40,7 @@ ipcRenderer.on( status: item.status, action: () => ipcRenderer.send('node-package-click', item.id), })), - { separator: true }, + ...(nodePackageTrayMenu.length >= 1 ? [{ separator: true }] : []), ...(podmanMenuItem.status !== 'isRunning' ? [ { From ba324a995653b3cf729b6ea0e3cc6e749f55c34c Mon Sep 17 00:00:00 2001 From: jgresham Date: Thu, 6 Jun 2024 15:11:48 -0400 Subject: [PATCH 26/29] stop all nodes and podman machine on tray quit --- src/main/tray.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index cdc969097..28ce163bb 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -9,8 +9,10 @@ import { nativeTheme, screen, } from 'electron'; +import { NodeStoppedBy } from '../common/node.js'; import logger from './logger'; import { createWindow, fullQuit, getMainWindow } from './main'; +import { stopAllNodePackages } from './nodePackageManager.js'; import { isLinux, isWindows } from './platform'; import { getPodmanDetails } from './podman/details.js'; import { @@ -84,9 +86,11 @@ export const setTrayMenu = () => { message: 'Are you sure you want to quit? Nodes will stop syncing.', detail: 'Confirming will close the application.', }) - .then((result) => { + .then(async (result) => { if (result.response === 0) { // The 'Yes' button is at index 0 + await stopAllNodePackages(NodeStoppedBy.shutdown); + await stopMachineIfCreated(); fullQuit(); // app no longer runs in the background } // Do nothing if the user selects 'No' @@ -257,9 +261,11 @@ function createCustomTrayWindow() { message: 'Are you sure you want to quit? Nodes will stop syncing.', detail: 'Confirming will close the application.', }) - .then((result) => { + .then(async (result) => { if (result.response === 0) { // The 'Yes' button is at index 0 + await stopAllNodePackages(NodeStoppedBy.shutdown); + await stopMachineIfCreated(); fullQuit(); // app no longer runs in the background } // Do nothing if the user selects 'No' From 54c90961d036655a2d15dbc7a01272e012d15ac7 Mon Sep 17 00:00:00 2001 From: cornpotage Date: Mon, 29 Jul 2024 13:46:26 -0700 Subject: [PATCH 27/29] enabled custom tray only for mac --- src/main/tray.ts | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 28ce163bb..1bc81db70 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -13,13 +13,9 @@ import { NodeStoppedBy } from '../common/node.js'; import logger from './logger'; import { createWindow, fullQuit, getMainWindow } from './main'; import { stopAllNodePackages } from './nodePackageManager.js'; -import { isLinux, isWindows } from './platform'; +import { isLinux, isWindows, isMac } from './platform'; import { getPodmanDetails } from './podman/details.js'; -import { - getNiceNodeMachine, - startMachineIfCreated, - stopMachineIfCreated, -} from './podman/machine'; +import { getNiceNodeMachine, stopMachineIfCreated } from './podman/machine'; import { openPodmanModal } from './podman/podman.js'; import { getUserNodePackages } from './state/nodePackages'; import { openNodePackageScreen } from './state/nodePackages.js'; @@ -203,7 +199,7 @@ function createCustomTrayWindow() { contextIsolation: false, nodeIntegration: true, }, - vibrancy: process.platform === 'darwin' ? 'sidebar' : undefined, + vibrancy: isMac() ? 'sidebar' : undefined, }); trayWindow.loadURL(`file://${_getAssetPath('trayIndex.html')}`); @@ -387,12 +383,12 @@ function toggleCustomTrayWindow() { let x; let y; - if (process.platform === 'darwin') { + if (isMac()) { x = Math.round( trayBounds.x + trayBounds.width / 2 - windowBounds.width / 2, ); y = Math.round(trayBounds.y + trayBounds.height); - } else if (process.platform === 'win32') { + } else if (isWindows()) { const display = screen.getPrimaryDisplay(); // Get the primary display details const workArea = display.workArea; x = Math.round( @@ -435,20 +431,23 @@ export const initialize = (getAssetPath: (...paths: string[]) => string) => { } tray = new Tray(icon); - createCustomTrayWindow(); - - // if (process.env.NODE_ENV === 'development') { - // trayWindow?.webContents.openDevTools(); - // } - // updateTrayMenu(); + if (isMac()) { + createCustomTrayWindow(); + } else { + updateTrayMenu(); + } tray.on('click', () => { // on windows, default is open/show window on click // on mac, default is open menu on click (no code needed) // on linux? - toggleCustomTrayWindow(); - updateCustomTrayMenu(); - // updateTrayMenu(); + if (isMac()) { + toggleCustomTrayWindow(); + updateCustomTrayMenu(); + } else { + updateTrayMenu(); + } + if (isWindows()) { const window = getMainWindow(); if (window) { From 7a356b59ce65c1158a6521679d7b6e30e5259ec4 Mon Sep 17 00:00:00 2001 From: jgresham Date: Tue, 30 Jul 2024 15:56:32 -0700 Subject: [PATCH 28/29] use modelRoutes.podman in sidebar --- .../Presentational/SidebarWrapper/SidebarWrapper.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx index ef655e810..4d1471013 100644 --- a/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx +++ b/src/renderer/Presentational/SidebarWrapper/SidebarWrapper.tsx @@ -24,6 +24,7 @@ import { useGetIsPodmanRunningQuery, useGetPodmanDetailsQuery, } from '../../state/settingsService'; +import { modalRoutes } from '../ModalManager/modalUtils.js'; import Sidebar from '../Sidebar/Sidebar'; export interface SidebarWrapperProps { @@ -61,7 +62,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { setModalState({ isModalOpen: true, screen: { - route: 'podman', + route: modalRoutes.podman, type: 'modal', data: { view: 'update' }, }, @@ -90,7 +91,7 @@ export const SidebarWrapper = forwardRef((_, ref) => { setModalState({ isModalOpen: true, screen: { - route: 'podman', + route: modalRoutes.podman, type: 'modal', }, }), From 2cce995fc771559bc179804462193a53abce7422 Mon Sep 17 00:00:00 2001 From: jgresham Date: Tue, 30 Jul 2024 15:58:56 -0700 Subject: [PATCH 29/29] lint in tray imports --- src/main/tray.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/tray.ts b/src/main/tray.ts index 1bc81db70..ecfc5f3ae 100644 --- a/src/main/tray.ts +++ b/src/main/tray.ts @@ -13,7 +13,7 @@ import { NodeStoppedBy } from '../common/node.js'; import logger from './logger'; import { createWindow, fullQuit, getMainWindow } from './main'; import { stopAllNodePackages } from './nodePackageManager.js'; -import { isLinux, isWindows, isMac } from './platform'; +import { isLinux, isMac, isWindows } from './platform'; import { getPodmanDetails } from './podman/details.js'; import { getNiceNodeMachine, stopMachineIfCreated } from './podman/machine'; import { openPodmanModal } from './podman/podman.js';