diff --git a/.vscode/settings.json b/.vscode/settings.json index 2d9dc4b..71d5ef0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,9 +10,14 @@ }, "cSpell.words": [ "admzip", + "antd", "APPDATA", "catched", + "Classname", "execa", + "getstation", + "imjs", + "ragestudio", "rclone", "sevenzip", "unzipper", diff --git a/packages/core/package.json b/packages/core/package.json index 5768cb2..ea3e7fc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -29,6 +29,7 @@ "open": "8.4.2", "request": "^2.88.2", "rimraf": "^5.0.5", + "signal-exit": "^4.1.0", "unzipper": "^0.10.14", "upath": "^2.0.1", "uuid": "^9.0.1", diff --git a/packages/core/src/classes/ManifestAuthDB.js b/packages/core/src/classes/ManifestAuthDB.js new file mode 100644 index 0000000..fe32684 --- /dev/null +++ b/packages/core/src/classes/ManifestAuthDB.js @@ -0,0 +1,36 @@ +import path from "path" +import { JSONFilePreset } from "../libraries/lowdb/presets/node" + +import Vars from "../vars" + +//! WARNING: Please DO NOT storage any password or sensitive data here, +// cause its not use any encryption method, and it will be stored in plain text. +// This is intended to store session tokens among other vars. + +export default class ManifestAuthService { + static vaultPath = path.resolve(Vars.runtime_path, "auth.json") + + static async withDB() { + return await JSONFilePreset(ManifestAuthService.vaultPath, {}) + } + + static has = async (pkg_id) => { + const db = await this.withDB() + + return !!db.data[pkg_id] + } + + static set = async (pkg_id, value) => { + const db = await this.withDB() + + return await db.update((data) => { + data[pkg_id] = value + }) + } + + static get = async (pkg_id) => { + const db = await this.withDB() + + return await db.data[pkg_id] + } +} \ No newline at end of file diff --git a/packages/core/src/handlers/authorize.js b/packages/core/src/handlers/authorize.js new file mode 100644 index 0000000..e1d19ab --- /dev/null +++ b/packages/core/src/handlers/authorize.js @@ -0,0 +1,33 @@ +import ManifestAuthDB from "../classes/ManifestAuthDB" +import DB from "../db" + +import Logger from "../logger" + +const Log = Logger.child({ service: "AUTH" }) + +export default async (pkg_id, value) => { + if (!pkg_id) { + Log.error("pkg_id is required") + return false + } + + if (!value) { + Log.error("value is required") + return false + } + + const pkg = await DB.getPackages(pkg_id) + + if (!pkg) { + Log.error("Package not found") + return false + } + + Log.info(`Setting auth for [${pkg_id}]`) + + await ManifestAuthDB.set(pkg_id, value) + + global._relic_eventBus.emit("pkg:authorized", pkg) + + return true +} \ No newline at end of file diff --git a/packages/core/src/handlers/execute.js b/packages/core/src/handlers/execute.js index 6f31d7b..4351d24 100644 --- a/packages/core/src/handlers/execute.js +++ b/packages/core/src/handlers/execute.js @@ -67,7 +67,9 @@ export default async function execute(pkg_id, { useRemote = false, force = false } catch (error) { global._relic_eventBus.emit(`pkg:error`, { id: pkg_id, - error + event: "execute", + last_status: "installed", + error, }) BaseLog.error(`Failed to execute package [${pkg_id}]`, error) diff --git a/packages/core/src/helpers/setup.js b/packages/core/src/helpers/setup.js index 737040b..0058dcc 100644 --- a/packages/core/src/helpers/setup.js +++ b/packages/core/src/helpers/setup.js @@ -32,17 +32,40 @@ export default async () => { if (!fs.existsSync(prerequisite.finalBin)) { Log.info(`Missing prerequisite: ${prerequisite.id}, installing...`) + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Installing ${prerequisite.id}`, + }) + if (fs.existsSync(prerequisite.destination)) { Log.info(`Deleting temporal file [${prerequisite.destination}]`) + + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Deleting temporal file [${prerequisite.destination}]`, + }) + await fs.promises.rm(prerequisite.destination) } if (fs.existsSync(prerequisite.extract)) { Log.info(`Deleting temporal directory [${prerequisite.extract}]`) + + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Deleting temporal directory [${prerequisite.extract}]`, + }) + await fs.promises.rm(prerequisite.extract, { recursive: true }) } Log.info(`Creating base directory: ${Vars.binaries_path}/${prerequisite.id}...`) + + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Creating base directory: ${Vars.binaries_path}/${prerequisite.id}`, + }) + await fs.promises.mkdir(path.resolve(Vars.binaries_path, prerequisite.id), { recursive: true }) if (typeof prerequisite.url === "function") { @@ -52,10 +75,21 @@ export default async () => { Log.info(`Downloading ${prerequisite.id} from [${prerequisite.url}] to destination [${prerequisite.destination}]...`) + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Starting download ${prerequisite.id} from [${prerequisite.url}] to destination [${prerequisite.destination}]`, + }) + try { await downloadFile( prerequisite.url, - prerequisite.destination + prerequisite.destination, + (progress) => { + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Downloaded ${progress.transferredString} / ${progress.totalString} | ${progress.speedString}/s`, + }) + } ) } catch (error) { await fs.promises.rm(prerequisite.destination) @@ -66,6 +100,11 @@ export default async () => { if (typeof prerequisite.extract === "string") { Log.info(`Extracting ${prerequisite.id} to destination [${prerequisite.extract}]...`) + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Extracting ${prerequisite.id} to destination [${prerequisite.extract}]`, + }) + const zip = new admzip(prerequisite.destination) await zip.extractAllTo(prerequisite.extract, true) @@ -88,6 +127,12 @@ export default async () => { if (prerequisite.deleteBeforeExtract === true) { Log.info(`Deleting temporal file [${prerequisite.destination}]`) + + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Deleting temporal file [${prerequisite.destination}]`, + }) + await fs.promises.unlink(prerequisite.destination) } @@ -97,6 +142,12 @@ export default async () => { prerequisite.finalBin Log.info(`Rewriting permissions to ${to}...`) + + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Rewriting permissions to ${to}`, + }) + await chmodRecursive(to, 0o755) } @@ -104,6 +155,11 @@ export default async () => { for (const dir of prerequisite.moveDirs) { Log.info(`Moving ${dir.from} to ${dir.to}...`) + global._relic_eventBus.emit("app:setup", { + installed: false, + message: `Moving ${dir.from} to ${dir.to}`, + }) + await fs.promises.rename(dir.from, dir.to) if (dir.deleteParentBefore === true) { @@ -113,8 +169,19 @@ export default async () => { } } + global._relic_eventBus.emit("app:setup", { + installed: true, + message: null, + }) + Log.info(`Prerequisite: ${prerequisite.id} is ready!`) } catch (error) { + global._relic_eventBus.emit("app:setup", { + installed: false, + error: error, + message: error.message, + }) + Log.error(error) Log.error("Aborting setup due to an error...") return false diff --git a/packages/core/src/index.js b/packages/core/src/index.js index 0b24d70..841c1f7 100644 --- a/packages/core/src/index.js +++ b/packages/core/src/index.js @@ -16,6 +16,7 @@ import PackageUpdate from "./handlers/update" import PackageApply from "./handlers/apply" import PackageList from "./handlers/list" import PackageRead from "./handlers/read" +import PackageAuthorize from "./handlers/authorize" export default class RelicCore { constructor(params) { @@ -26,8 +27,6 @@ export default class RelicCore { logger = Logger - db = DB - async initialize() { await DB.initialize() @@ -52,6 +51,7 @@ export default class RelicCore { apply: PackageApply, list: PackageList, read: PackageRead, + authorize: PackageAuthorize, } openPath(pkg_id) { diff --git a/packages/core/src/manifest/libs/auth/index.js b/packages/core/src/manifest/libs/auth/index.js index f095692..2f1371a 100644 --- a/packages/core/src/manifest/libs/auth/index.js +++ b/packages/core/src/manifest/libs/auth/index.js @@ -1,5 +1,6 @@ import open from "open" import axios from "axios" +import ManifestAuthDB from "../../../classes/ManifestAuthDB" export default class Auth { constructor(ctx) { @@ -7,31 +8,24 @@ export default class Auth { } async get() { - return { - assigned_username: "test", - } + const storagedData = await ManifestAuthDB.get(this.manifest.id) - const authData = global.authService.getAuth(this.manifest.id) + if (storagedData && this.manifest.authService) { + if (!this.manifest.authService.getter) { + return storagedData + } - console.log(authData) - - if (authData && this.manifest.auth && this.manifest.auth.getter) { const result = await axios({ method: "POST", - url: this.manifest.auth.getter, + url: this.manifest.authService.getter, headers: { "Content-Type": "application/json", }, data: { - auth_data: authData, + auth_data: storagedData, } }).catch((err) => { - sendToRender(`new:notification`, { - type: "error", - message: "Failed to authorize", - description: err.response.data.message ?? err.response.data.error ?? err.message, - duration: 10 - }) + global._relic_eventBus.emit("auth:getter:error", err) return err }) @@ -41,20 +35,19 @@ export default class Auth { } console.log(result.data) - + return result.data } - return authData + return storagedData } request() { - return true - if (!this.manifest.auth) { + if (!this.manifest.authService || !this.manifest.authService.fetcher) { return false } - const authURL = this.manifest.auth.fetcher + const authURL = this.manifest.authService.fetcher open(authURL) } diff --git a/packages/gui/electron-builder.yml b/packages/gui/electron-builder.yml index 1293417..5ec3133 100644 --- a/packages/gui/electron-builder.yml +++ b/packages/gui/electron-builder.yml @@ -19,12 +19,7 @@ nsis: uninstallDisplayName: ${productName} createDesktopShortcut: always mac: - entitlementsInherit: build/entitlements.mac.plist - extendInfo: - - NSCameraUsageDescription: Application requests access to the device's camera. - - NSMicrophoneUsageDescription: Application requests access to the device's microphone. - - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. - - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. + icon: resources/icon.icns notarize: false dmg: artifactName: ${name}-${version}.${ext} diff --git a/packages/gui/src/main/classes/CoreAdapter.js b/packages/gui/src/main/classes/CoreAdapter.js index 8f6ed83..9fa931d 100644 --- a/packages/gui/src/main/classes/CoreAdapter.js +++ b/packages/gui/src/main/classes/CoreAdapter.js @@ -19,7 +19,7 @@ export default class CoreAdapter { if (!data.id) { return false } - + if (data.use_id_only === true) { return sendToRender(`pkg:update:state:${data.id}`, data) } @@ -28,6 +28,33 @@ export default class CoreAdapter { }, "pkg:new:done": (pkg) => { sendToRender("pkg:new:done", pkg) + }, + "app:setup": (data) => { + sendToRender("app:setup", data) + }, + "auth:getter:error": (err) => { + sendToRender(`new:notification`, { + type: "error", + message: "Failed to authorize", + description: err.response.data.message ?? err.response.data.error ?? err.message, + duration: 10 + }) + }, + "pkg:authorized": (pkg) => { + sendToRender(`new:notification`, { + type: "success", + message: "Package authorized", + description: `${pkg.name} has been authorized! You can start the package now.`, + }) + }, + "pkg:error": (data) => { + sendToRender(`new:notification`, { + type: "error", + message: `An error occurred`, + description: `Something failed to ${data.event} package ${data.pkg_id}`, + }) + + sendToRender(`pkg:update:state`, data) } } diff --git a/packages/gui/src/main/index.js b/packages/gui/src/main/index.js index a54b0b0..c21f305 100644 --- a/packages/gui/src/main/index.js +++ b/packages/gui/src/main/index.js @@ -169,18 +169,18 @@ class ElectronApp { case "authorize": { if (!explicitAction[2]) { const [pkg_id, token] = explicitAction[1].split("%23") - return this.core.auth.authorize(pkg_id, token) + return this.core.package.authorize(pkg_id, token) } else { - return this.core.auth.authorize(explicitAction[1], explicitAction[2]) + return this.core.package.authorize(explicitAction[1], explicitAction[2]) } } default: { - return sendToRender("installation:invoked", explicitAction[0]) + return sendToRender("pkg:installation:invoked", explicitAction[0]) } } } else { // by default if no action is specified, assume is a install action - return sendToRender("installation:invoked", urlValue) + return sendToRender("pkg:installation:invoked", urlValue) } } } diff --git a/packages/gui/src/renderer/index.html b/packages/gui/src/renderer/index.html index c994f68..3155c2f 100644 --- a/packages/gui/src/renderer/index.html +++ b/packages/gui/src/renderer/index.html @@ -1,16 +1,15 @@ - - - Relic - - - -
- - - + + + Relic + + + + +
+ + + + \ No newline at end of file diff --git a/packages/gui/src/renderer/src/App.jsx b/packages/gui/src/renderer/src/App.jsx index dda0d7a..995491d 100644 --- a/packages/gui/src/renderer/src/App.jsx +++ b/packages/gui/src/renderer/src/App.jsx @@ -17,26 +17,24 @@ window.app = GlobalApp class App extends React.Component { state = { - loading: true, + initializing: true, pkg: null, - initializing: false, - setup_step: null, - updateAvailable: false, - updateText: null, - - authorizedServices: { - drive: false, + appSetup: { + error: false, + installed: false, + message: null, }, + + appUpdate: { + changelog: null, + available: false, + }, + + authorizedServices: [], } ipcEvents = { - "runtime:error": (event, data) => { - antd.message.error(data) - }, - "runtime:info": (event, data) => { - antd.message.info(data) - }, "new:notification": (event, data) => { app.notification[data.type || "info"]({ message: data.message, @@ -50,8 +48,13 @@ class App extends React.Component { "new:message": (event, data) => { antd.message[data.type || "info"](data.message) }, + "app:setup": (event, data) => { + this.setState({ + appSetup: data, + }) + }, "app:update_available": (event, data) => { - if (this.state.loading) { + if (this.state.initializing) { return false } @@ -62,73 +65,49 @@ class App extends React.Component { app.appUpdateAvailable(data) }, "pkg:install:ask": (event, data) => { - if (this.state.loading) { + if (this.state.initializing) { return false } app.pkgInstallWizard(data) }, "pkg:update_available": (event, data) => { - if (this.state.loading) { + if (this.state.initializing) { return false } app.pkgUpdateAvailable(data) }, - "installation:invoked": (event, manifest) => { - if (this.state.loading) { - return false - } + "pkg:installation:invoked": (event, data) => { + if (this.state.initializing) { + return false + } - app.invokeInstall(manifest) - }, - "drive:authorized": (event, data) => { - this.setState({ - authorizedServices: { - drive: true, - }, - }) - - message.success("Google Drive API authorized") - }, - "drive:unauthorized": (event, data) => { - this.setState({ - authorizedServices: { - drive: false, - }, - }) - - message.success("Google Drive API unauthorized") - }, - "setup_step": (event, data) => { - console.log(`setup:step`, data) - - this.setState({ - setup_step: data, - }) - }, + app.invokeInstall(data) + } } componentDidMount = async () => { - const initResult = await ipc.exec("app:init") - - console.log(`Using React version > ${versions["react"]}`) - console.log(`Using DOMRouter version > ${versions["react-router-dom"]}`) - console.log(`[APP] app:init() | Result >`, initResult) + window.app.style.appendClassname("initializing") for (const event in this.ipcEvents) { ipc.exclusiveListen(event, this.ipcEvents[event]) } + const mainInitialization = await ipc.exec("app:init") + + console.log(`React version > ${versions["react"]}`) + console.log(`DOMRouter version > ${versions["react-router-dom"]}`) + console.log(`app:init() | Result >`, mainInitialization) + + await this.setState({ + initializing: false, + pkg: mainInitialization.pkg, + }) + app.location.push("/") - this.setState({ - loading: false, - pkg: initResult.pkg, - authorizedServices: { - drive: initResult.authorizedServices?.drive ?? false - }, - }) + window.app.style.removeClassname("initializing") } render() { diff --git a/packages/gui/src/renderer/src/GlobalApp.jsx b/packages/gui/src/renderer/src/GlobalApp.jsx index 054bd05..432d170 100644 --- a/packages/gui/src/renderer/src/GlobalApp.jsx +++ b/packages/gui/src/renderer/src/GlobalApp.jsx @@ -10,7 +10,25 @@ globalThis.getRootCssVar = getRootCssVar globalThis.notification = notification globalThis.message = message +class GlobalStyleController { + static root = document.getElementById("root") + + static appendClassname = (classname) => { + console.log(`appending classname >`, classname) + GlobalStyleController.root.classList.add(classname) + } + + static removeClassname = (classname) => { + console.log(`removing classname >`, classname) + GlobalStyleController.root.classList.remove(classname) + } + + static getRootCssVar = getRootCssVar +} + export default class GlobalCTXApp { + static style = GlobalStyleController + static applyUpdate = () => { message.loading("Updating, please wait...") diff --git a/packages/gui/src/renderer/src/components/Splash/index.jsx b/packages/gui/src/renderer/src/components/Splash/index.jsx new file mode 100644 index 0000000..d0fa2cc --- /dev/null +++ b/packages/gui/src/renderer/src/components/Splash/index.jsx @@ -0,0 +1,47 @@ +import React from "react" +import * as antd from "antd" +import { BarLoader } from "react-spinners" +import GlobalStateContext from "contexts/global" + +import "./index.less" + +const Splash = (props) => { + const globalState = React.useContext(GlobalStateContext) + + return
+ { + !!globalState.appSetup.message &&
+

+ Setting up... +

+

+ Please wait while the application is being set up. +

+
+ } + + { + globalState.appSetup.message && <> +
+ + {globalState.appSetup.message} + +
+ + + + } + + { + !globalState.appSetup.message && + } +
+} + +export default Splash \ No newline at end of file diff --git a/packages/gui/src/renderer/src/components/Splash/index.less b/packages/gui/src/renderer/src/components/Splash/index.less new file mode 100644 index 0000000..711cf82 --- /dev/null +++ b/packages/gui/src/renderer/src/components/Splash/index.less @@ -0,0 +1,32 @@ +.splash { + display: flex; + flex-direction: column; + + justify-content: center; + + height: 100%; + + gap: 20px; + + .app-setup_header { + display: flex; + flex-direction: column; + + gap: 10px; + + h1 { + font-size: 1.7rem; + } + } + + .app-setup_message { + padding: 15px 10px; + border-radius: 12px; + + transition: all 150ms ease-in-out; + + background-color: var(--background-color-secondary); + + font-family: "DM Mono", monospace; + } +} \ No newline at end of file diff --git a/packages/gui/src/renderer/src/contexts/packages.jsx b/packages/gui/src/renderer/src/contexts/packages.jsx index 8ef709a..a4bf844 100644 --- a/packages/gui/src/renderer/src/contexts/packages.jsx +++ b/packages/gui/src/renderer/src/contexts/packages.jsx @@ -5,8 +5,8 @@ export const Context = React.createContext([]) export class WithContext extends React.Component { state = { + loading: true, packages: [], - pendingInstallation: false, } ipcEvents = { @@ -62,24 +62,27 @@ export class WithContext extends React.Component { packages: newData }) } - - console.log(`[ipc] pkg:update:state >`, data) } } - componentDidMount = async () => { + loadPackages = async () => { + await this.setState({ + loading: true, + }) + const packages = await ipc.exec("pkg:list") + await this.setState({ + packages: packages, + }) + } + + componentDidMount = async () => { for (const event in this.ipcEvents) { ipc.exclusiveListen(event, this.ipcEvents[event]) } - this.setState({ - packages: [ - ...this.state.packages, - ...packages, - ] - }) + await this.loadPackages() } render() { diff --git a/packages/gui/src/renderer/src/layout/components/Header/index.less b/packages/gui/src/renderer/src/layout/components/Header/index.less index 78ac904..61f04f3 100644 --- a/packages/gui/src/renderer/src/layout/components/Header/index.less +++ b/packages/gui/src/renderer/src/layout/components/Header/index.less @@ -12,19 +12,22 @@ view-transition-name: main-header; + height: var(--app_header_height); + display: inline-flex; flex-direction: row; align-items: center; - background-color: darken(@var-background-color-primary, 10%); - gap: 30px; - - height: var(--app_header_height); - padding: 0 20px; + overflow: hidden; + + transition: 150ms ease-in-out; + + background-color: darken(@var-background-color-primary, 10%); + .branding { display: inline-flex; flex-direction: row; @@ -69,9 +72,12 @@ justify-content: center; border-radius: 50%; + padding: 3px; background-color: var(--primary-color); - font-size: 1.4rem; + font-size: 1.6rem; + + color: #3b3b3b; } .app_header_nav_title { diff --git a/packages/gui/src/renderer/src/pages/index.jsx b/packages/gui/src/renderer/src/pages/index.jsx index 055fe62..992ae26 100644 --- a/packages/gui/src/renderer/src/pages/index.jsx +++ b/packages/gui/src/renderer/src/pages/index.jsx @@ -10,25 +10,26 @@ import NewInstallation from "components/NewInstallation" import "./index.less" -class InstallationsManager extends React.Component { +class Packages extends React.Component { static contextType = InstallationsContext render() { - const { packages } = this.context + const { packages, loading } = this.context const empty = packages.length == 0 - return
-
+ return
+
} onClick={() => app.drawer.open(NewInstallation, { - title: "Add new installation", + title: "Install new package", height: "200px", })} + className="add-btn" > - Add new installation + Add new
-
+
{ - empty && + loading && } { - packages.map((manifest) => { + !loading && empty && + } + + { + !loading && packages.map((manifest) => { return }) } @@ -54,10 +59,10 @@ class InstallationsManager extends React.Component { } } -const InstallationsManagerPage = (props) => { +const PackagesPage = (props) => { return - + } -export default InstallationsManagerPage \ No newline at end of file +export default PackagesPage \ No newline at end of file diff --git a/packages/gui/src/renderer/src/pages/index.less b/packages/gui/src/renderer/src/pages/index.less index 7149fb5..5249677 100644 --- a/packages/gui/src/renderer/src/pages/index.less +++ b/packages/gui/src/renderer/src/pages/index.less @@ -1,4 +1,4 @@ -.installations_manager { +.packages { display: flex; flex-direction: column; @@ -6,17 +6,52 @@ gap: 10px; - .installations_manager-header { + .packages-header { display: flex; - flex-direction: column; + flex-direction: row; gap: 10px; .ant-btn-default { background-color: var(--background-color-secondary); } + + .add-btn { + display: flex; + flex-direction: row; + + padding: 5px; + border-radius: 12px; + gap: 0px; + + span:not(.ant-btn-icon) { + opacity: 0; + + transition: all 150ms ease; + + max-width: 0px; + } + + &:hover { + padding: 5px 10px; + border-radius: 8px; + + gap: 7px; + + span:not(.ant-btn-icon) { + opacity: 1; + max-width: 200px; + } + } + + .ant-btn-icon { + margin: 0; + + font-size: 1.2rem; + } + } } - .installations_list { + .packages-list { display: flex; flex-direction: column; diff --git a/packages/gui/src/renderer/src/router.jsx b/packages/gui/src/renderer/src/router.jsx index 68f1867..599c16e 100644 --- a/packages/gui/src/renderer/src/router.jsx +++ b/packages/gui/src/renderer/src/router.jsx @@ -1,5 +1,4 @@ import React from "react" -import BarLoader from "react-spinners/BarLoader" import { Skeleton } from "antd" import { HashRouter, Route, Routes, useNavigate, useParams } from "react-router-dom" @@ -7,12 +6,14 @@ import loadable from "@loadable/component" import GlobalStateContext from "contexts/global" +import SplashScreen from "components/Splash" + const DefaultNotFoundRender = () => { return
Not found
} const DefaultLoadingRender = () => { - return + return } const BuildPageController = (route, element, bindProps) => { @@ -155,19 +156,8 @@ export const PageRender = (props) => { const globalState = React.useContext(GlobalStateContext) - if (globalState.setup_step || globalState.loading) { - return
- - -

Setting up...

- - -
{globalState.setup_step}
-
-
+ if (globalState.initializing) { + return } return diff --git a/packages/gui/src/renderer/src/style/index.less b/packages/gui/src/renderer/src/style/index.less index 5a729c1..bf6bd32 100644 --- a/packages/gui/src/renderer/src/style/index.less +++ b/packages/gui/src/renderer/src/style/index.less @@ -41,6 +41,12 @@ body { height: 100%; overflow: hidden; + + &.initializing { + .app_header { + height: 0px; + } + } } .app_layout {