mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 10:34:18 +00:00
merge from local
This commit is contained in:
parent
363ad1cd97
commit
fd6358ef11
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -10,9 +10,14 @@
|
||||
},
|
||||
"cSpell.words": [
|
||||
"admzip",
|
||||
"antd",
|
||||
"APPDATA",
|
||||
"catched",
|
||||
"Classname",
|
||||
"execa",
|
||||
"getstation",
|
||||
"imjs",
|
||||
"ragestudio",
|
||||
"rclone",
|
||||
"sevenzip",
|
||||
"unzipper",
|
||||
|
@ -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",
|
||||
|
36
packages/core/src/classes/ManifestAuthDB.js
Normal file
36
packages/core/src/classes/ManifestAuthDB.js
Normal file
@ -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]
|
||||
}
|
||||
}
|
33
packages/core/src/handlers/authorize.js
Normal file
33
packages/core/src/handlers/authorize.js
Normal file
@ -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
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
||||
if (storagedData && this.manifest.authService) {
|
||||
if (!this.manifest.authService.getter) {
|
||||
return storagedData
|
||||
}
|
||||
|
||||
const authData = global.authService.getAuth(this.manifest.id)
|
||||
|
||||
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
|
||||
})
|
||||
@ -45,16 +39,15 @@ export default class Auth {
|
||||
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)
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,15 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Relic</title>
|
||||
<!-- <meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
||||
/> -->
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -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) {
|
||||
"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() {
|
||||
|
@ -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...")
|
||||
|
||||
|
47
packages/gui/src/renderer/src/components/Splash/index.jsx
Normal file
47
packages/gui/src/renderer/src/components/Splash/index.jsx
Normal file
@ -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 <div className="splash">
|
||||
{
|
||||
!!globalState.appSetup.message && <div className="app-setup_header">
|
||||
<h1>
|
||||
Setting up...
|
||||
</h1>
|
||||
<p>
|
||||
Please wait while the application is being set up.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
globalState.appSetup.message && <>
|
||||
<div className="app-setup_message">
|
||||
<span>
|
||||
{globalState.appSetup.message}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<BarLoader
|
||||
className="app_loader"
|
||||
color={getRootCssVar("--primary-color")}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
||||
{
|
||||
!globalState.appSetup.message && <antd.Skeleton
|
||||
active
|
||||
round
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Splash
|
32
packages/gui/src/renderer/src/components/Splash/index.less
Normal file
32
packages/gui/src/renderer/src/components/Splash/index.less
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
loadPackages = async () => {
|
||||
await this.setState({
|
||||
loading: true,
|
||||
})
|
||||
|
||||
const packages = await ipc.exec("pkg:list")
|
||||
|
||||
await this.setState({
|
||||
packages: packages,
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount = async () => {
|
||||
const packages = await ipc.exec("pkg:list")
|
||||
|
||||
for (const event in this.ipcEvents) {
|
||||
ipc.exclusiveListen(event, this.ipcEvents[event])
|
||||
}
|
||||
|
||||
this.setState({
|
||||
packages: [
|
||||
...this.state.packages,
|
||||
...packages,
|
||||
]
|
||||
})
|
||||
await this.loadPackages()
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -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 {
|
||||
|
@ -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 <div className="installations_manager">
|
||||
<div className="installations_manager-header">
|
||||
return <div className="packages">
|
||||
<div className="packages-header">
|
||||
<antd.Button
|
||||
type="primary"
|
||||
icon={<MdAdd />}
|
||||
onClick={() => app.drawer.open(NewInstallation, {
|
||||
title: "Add new installation",
|
||||
title: "Install new package",
|
||||
height: "200px",
|
||||
})}
|
||||
className="add-btn"
|
||||
>
|
||||
Add new installation
|
||||
Add new
|
||||
</antd.Button>
|
||||
|
||||
<antd.Input.Search
|
||||
@ -39,13 +40,17 @@ class InstallationsManager extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={empty ? "installations_list empty" : "installations_list"}>
|
||||
<div className={empty ? "packages-list empty" : "packages-list"}>
|
||||
{
|
||||
empty && <antd.Empty description="No packages installed" />
|
||||
loading && <antd.Skeleton active round />
|
||||
}
|
||||
|
||||
{
|
||||
packages.map((manifest) => {
|
||||
!loading && empty && <antd.Empty description="No packages installed" />
|
||||
}
|
||||
|
||||
{
|
||||
!loading && packages.map((manifest) => {
|
||||
return <PackageItem key={manifest.id} manifest={manifest} />
|
||||
})
|
||||
}
|
||||
@ -54,10 +59,10 @@ class InstallationsManager extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const InstallationsManagerPage = (props) => {
|
||||
const PackagesPage = (props) => {
|
||||
return <WithContext>
|
||||
<InstallationsManager {...props} />
|
||||
<Packages {...props} />
|
||||
</WithContext>
|
||||
}
|
||||
|
||||
export default InstallationsManagerPage
|
||||
export default PackagesPage
|
@ -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;
|
||||
}
|
||||
|
||||
.installations_list {
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.packages-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
@ -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 <div>Not found</div>
|
||||
}
|
||||
|
||||
const DefaultLoadingRender = () => {
|
||||
return <Skeleton active />
|
||||
return <SplashScreen />
|
||||
}
|
||||
|
||||
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 <div className="app_setup">
|
||||
<BarLoader
|
||||
className="app_loader"
|
||||
color={getRootCssVar("--primary-color")}
|
||||
/>
|
||||
|
||||
<h1>Setting up...</h1>
|
||||
|
||||
<code>
|
||||
<pre>{globalState.setup_step}</pre>
|
||||
</code>
|
||||
</div>
|
||||
if (globalState.initializing) {
|
||||
return <SplashScreen />
|
||||
}
|
||||
|
||||
return <Routes>
|
||||
|
@ -41,6 +41,12 @@ body {
|
||||
height: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
&.initializing {
|
||||
.app_header {
|
||||
height: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app_layout {
|
||||
|
Loading…
x
Reference in New Issue
Block a user