mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 10:34:18 +00:00
init
This commit is contained in:
commit
2f8f735710
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
4
.eslintignore
Normal file
4
.eslintignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
out
|
||||||
|
.gitignore
|
9
.eslintrc.cjs
Normal file
9
.eslintrc.cjs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
'@electron-toolkit',
|
||||||
|
'@electron-toolkit/eslint-config-prettier'
|
||||||
|
]
|
||||||
|
}
|
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Secrets
|
||||||
|
/**/**/.env
|
||||||
|
/**/**/origin.server
|
||||||
|
/**/**/server.manifest
|
||||||
|
/**/**/server.registry
|
||||||
|
|
||||||
|
/**/**/_shared
|
||||||
|
|
||||||
|
# Trash
|
||||||
|
/**/**/*.log
|
||||||
|
/**/**/dumps.log
|
||||||
|
/**/**/.crash.log
|
||||||
|
/**/**/.tmp
|
||||||
|
/**/**/.cache
|
||||||
|
/**/**/cache
|
||||||
|
/**/**/out
|
||||||
|
/**/**/.out
|
||||||
|
/**/**/dist
|
||||||
|
/**/**/node_modules
|
||||||
|
/**/**/corenode_modules
|
||||||
|
/**/**/.DS_Store
|
||||||
|
/**/**/package-lock.json
|
||||||
|
/**/**/yarn.lock
|
||||||
|
/**/**/.evite
|
||||||
|
/**/**/build
|
||||||
|
/**/**/uploads
|
||||||
|
/**/**/d_data
|
||||||
|
/**/**/*.tar
|
||||||
|
/**/**/*.7z
|
||||||
|
/**/**/*.zip
|
||||||
|
/**/**/*.env
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
/**/**/npm-debug.log*
|
||||||
|
/**/**/yarn-error.log
|
||||||
|
/**/**/dumps.log
|
||||||
|
/**/**/corenode.log
|
||||||
|
|
||||||
|
# Temporal configurations
|
||||||
|
/**/**/.aliaser
|
6
.prettierignore
Normal file
6
.prettierignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
out
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
||||||
|
LICENSE.md
|
||||||
|
tsconfig.json
|
||||||
|
tsconfig.*.json
|
4
.prettierrc.yaml
Normal file
4
.prettierrc.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
singleQuote: true
|
||||||
|
semi: false
|
||||||
|
printWidth: 100
|
||||||
|
trailingComma: none
|
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["dbaeumer.vscode-eslint"]
|
||||||
|
}
|
39
.vscode/launch.json
vendored
Normal file
39
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug Main Process",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
|
||||||
|
},
|
||||||
|
"runtimeArgs": ["--sourcemap"],
|
||||||
|
"env": {
|
||||||
|
"REMOTE_DEBUGGING_PORT": "9222"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Renderer Process",
|
||||||
|
"port": 9222,
|
||||||
|
"request": "attach",
|
||||||
|
"type": "chrome",
|
||||||
|
"webRoot": "${workspaceFolder}/src/renderer",
|
||||||
|
"timeout": 60000,
|
||||||
|
"presentation": {
|
||||||
|
"hidden": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Debug All",
|
||||||
|
"configurations": ["Debug Main Process", "Debug Renderer Process"],
|
||||||
|
"presentation": {
|
||||||
|
"order": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
11
.vscode/settings.json
vendored
Normal file
11
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
}
|
||||||
|
}
|
34
README.md
Normal file
34
README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# rs-bundler
|
||||||
|
|
||||||
|
An Electron application with React
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
- [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For windows
|
||||||
|
$ npm run build:win
|
||||||
|
|
||||||
|
# For macOS
|
||||||
|
$ npm run build:mac
|
||||||
|
|
||||||
|
# For Linux
|
||||||
|
$ npm run build:linux
|
||||||
|
```
|
3
dev-app-update.yml
Normal file
3
dev-app-update.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
provider: generic
|
||||||
|
url: https://example.com/auto-updates
|
||||||
|
updaterCacheDirName: rs-bundler-updater
|
42
electron-builder.yml
Normal file
42
electron-builder.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
appId: com.electron.app
|
||||||
|
productName: rs-bundler
|
||||||
|
directories:
|
||||||
|
buildResources: build
|
||||||
|
files:
|
||||||
|
- '!**/.vscode/*'
|
||||||
|
- '!src/*'
|
||||||
|
- '!electron.vite.config.{js,ts,mjs,cjs}'
|
||||||
|
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
|
||||||
|
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
|
||||||
|
asarUnpack:
|
||||||
|
- resources/**
|
||||||
|
win:
|
||||||
|
executableName: rs-bundler
|
||||||
|
nsis:
|
||||||
|
artifactName: ${name}-${version}-setup.${ext}
|
||||||
|
shortcutName: ${productName}
|
||||||
|
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.
|
||||||
|
notarize: false
|
||||||
|
dmg:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
linux:
|
||||||
|
target:
|
||||||
|
- AppImage
|
||||||
|
- snap
|
||||||
|
- deb
|
||||||
|
maintainer: electronjs.org
|
||||||
|
category: Utility
|
||||||
|
appImage:
|
||||||
|
artifactName: ${name}-${version}.${ext}
|
||||||
|
npmRebuild: false
|
||||||
|
publish:
|
||||||
|
provider: generic
|
||||||
|
url: https://example.com/auto-updates
|
34
electron.vite.config.js
Normal file
34
electron.vite.config.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { resolve } from 'path'
|
||||||
|
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
main: {
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
plugins: [externalizeDepsPlugin()]
|
||||||
|
},
|
||||||
|
renderer: {
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"style": resolve('src/renderer/src/style'),
|
||||||
|
"components": resolve('src/renderer/src/components'),
|
||||||
|
"utils": resolve('src/renderer/src/utils'),
|
||||||
|
"contexts": resolve('src/renderer/src/contexts'),
|
||||||
|
"pages": resolve('src/renderer/src/pages'),
|
||||||
|
"hooks": resolve('src/renderer/src/hooks'),
|
||||||
|
"services": resolve('src/renderer/src/services'),
|
||||||
|
'@renderer': resolve('src/renderer/src')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
less: {
|
||||||
|
javascriptEnabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
47
package.json
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "rs-bundler",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "An Electron application with React",
|
||||||
|
"main": "./out/main/index.js",
|
||||||
|
"author": "RageStudio",
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
|
||||||
|
"start": "electron-vite preview",
|
||||||
|
"dev": "electron-vite dev",
|
||||||
|
"build": "electron-vite build",
|
||||||
|
"postinstall": "electron-builder install-app-deps",
|
||||||
|
"build:win": "npm run build && electron-builder --win --config",
|
||||||
|
"build:mac": "npm run build && electron-builder --mac --config",
|
||||||
|
"build:linux": "npm run build && electron-builder --linux --config"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@electron-toolkit/preload": "^2.0.0",
|
||||||
|
"@electron-toolkit/utils": "^2.0.0",
|
||||||
|
"antd": "^5.10.2",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
|
"electron-updater": "^6.1.1",
|
||||||
|
"got": "11.8.3",
|
||||||
|
"less": "^4.2.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"node-7z": "^3.0.0",
|
||||||
|
"open": "8.4.2",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-spinners": "^0.13.8",
|
||||||
|
"rimraf": "^5.0.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@electron-toolkit/eslint-config": "^1.0.1",
|
||||||
|
"@electron-toolkit/eslint-config-prettier": "^1.0.1",
|
||||||
|
"@vitejs/plugin-react": "^4.0.4",
|
||||||
|
"electron": "^25.6.0",
|
||||||
|
"electron-builder": "^24.6.3",
|
||||||
|
"electron-vite": "^1.0.27",
|
||||||
|
"eslint": "^8.47.0",
|
||||||
|
"eslint-plugin-react": "^7.33.2",
|
||||||
|
"prettier": "^3.0.2",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"vite": "^4.4.9"
|
||||||
|
}
|
||||||
|
}
|
BIN
resources/icon.png
Normal file
BIN
resources/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
116
src/main/index.js
Normal file
116
src/main/index.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
import { app, shell, BrowserWindow, ipcMain } from "electron"
|
||||||
|
import { electronApp, optimizer, is } from "@electron-toolkit/utils"
|
||||||
|
|
||||||
|
import open from "open"
|
||||||
|
|
||||||
|
import icon from "../../resources/icon.png?asset"
|
||||||
|
import pkg from "../../package.json"
|
||||||
|
|
||||||
|
import setup from "./setup"
|
||||||
|
|
||||||
|
import PkgManager from "./pkgManager"
|
||||||
|
|
||||||
|
class ElectronApp {
|
||||||
|
constructor() {
|
||||||
|
this.pkgManager = new PkgManager()
|
||||||
|
this.win = null
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers = {
|
||||||
|
pkg: () => {
|
||||||
|
return pkg
|
||||||
|
},
|
||||||
|
"get:installations": async () => {
|
||||||
|
return await this.pkgManager.getInstallations()
|
||||||
|
},
|
||||||
|
"bundle:update": (event, manifest_id) => {
|
||||||
|
this.pkgManager.update(manifest_id)
|
||||||
|
},
|
||||||
|
"bundle:exec": (event, manifest_id) => {
|
||||||
|
this.pkgManager.exec(manifest_id)
|
||||||
|
},
|
||||||
|
"bundle:install": async (event, manifest) => {
|
||||||
|
this.pkgManager.install(manifest)
|
||||||
|
},
|
||||||
|
"bundle:uninstall": (event, manifest_id) => {
|
||||||
|
this.pkgManager.uninstall(manifest_id)
|
||||||
|
},
|
||||||
|
"bundle:open": (event, manifest_id) => {
|
||||||
|
this.pkgManager.openBundleFolder(manifest_id)
|
||||||
|
},
|
||||||
|
"check:setup": async () => {
|
||||||
|
return await setup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
events = {
|
||||||
|
"open-runtime-path": () => {
|
||||||
|
return open(this.pkgManager.runtimePath)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createWindow() {
|
||||||
|
this.win = global.win = new BrowserWindow({
|
||||||
|
width: 450,
|
||||||
|
height: 670,
|
||||||
|
show: false,
|
||||||
|
autoHideMenuBar: true,
|
||||||
|
...(process.platform === "linux" ? { icon } : {}),
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, "../preload/index.js"),
|
||||||
|
sandbox: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.win.on("ready-to-show", () => {
|
||||||
|
this.win.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.win.webContents.setWindowOpenHandler((details) => {
|
||||||
|
shell.openExternal(details.url)
|
||||||
|
|
||||||
|
return { action: "deny" }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||||
|
this.win.loadURL(process.env["ELECTRON_RENDERER_URL"])
|
||||||
|
} else {
|
||||||
|
this.win.loadFile(path.join(__dirname, "../renderer/index.html"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
for (const key in this.handlers) {
|
||||||
|
ipcMain.handle(key, this.handlers[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in this.events) {
|
||||||
|
ipcMain.on(key, this.events[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
await app.whenReady()
|
||||||
|
|
||||||
|
// Set app user model id for windows
|
||||||
|
electronApp.setAppUserModelId("com.electron")
|
||||||
|
|
||||||
|
app.on("browser-window-created", (_, window) => {
|
||||||
|
optimizer.watchWindowShortcuts(window)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.createWindow()
|
||||||
|
|
||||||
|
app.on("activate", function () {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on("window-all-closed", () => {
|
||||||
|
if (process.platform !== "darwin") {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new ElectronApp().initialize()
|
601
src/main/pkgManager.js
Normal file
601
src/main/pkgManager.js
Normal file
@ -0,0 +1,601 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
import { pipeline as streamPipeline } from "node:stream/promises"
|
||||||
|
import ChildProcess from "node:child_process"
|
||||||
|
import fs from "node:fs"
|
||||||
|
import os from "node:os"
|
||||||
|
|
||||||
|
import open from "open"
|
||||||
|
import got from "got"
|
||||||
|
import { extractFull } from "node-7z"
|
||||||
|
import { rimraf } from "rimraf"
|
||||||
|
import lodash from "lodash"
|
||||||
|
|
||||||
|
import pkg from "../../package.json"
|
||||||
|
|
||||||
|
global.OS_USERDATA_PATH = path.resolve(
|
||||||
|
process.env.APPDATA ||
|
||||||
|
(process.platform == "darwin" ? process.env.HOME + "/Library/Preferences" : process.env.HOME + "/.local/share"),
|
||||||
|
)
|
||||||
|
|
||||||
|
global.RUNTIME_PATH = path.join(global.OS_USERDATA_PATH, "rs-bundler")
|
||||||
|
|
||||||
|
const TMP_PATH = path.resolve(os.tmpdir(), "RS-MCPacks")
|
||||||
|
const INSTALLERS_PATH = path.join(global.RUNTIME_PATH, "installers")
|
||||||
|
const MANIFEST_PATH = path.join(global.RUNTIME_PATH, "manifests")
|
||||||
|
|
||||||
|
const RealmDBDefault = {
|
||||||
|
created_at_version: pkg.version,
|
||||||
|
installations: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeIpc(data) {
|
||||||
|
const copy = lodash.cloneDeep(data)
|
||||||
|
|
||||||
|
// remove fns
|
||||||
|
if (!Array.isArray(copy)) {
|
||||||
|
Object.keys(copy).forEach((key) => {
|
||||||
|
if (typeof copy[key] === "function") {
|
||||||
|
delete copy[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToRenderer(event, data) {
|
||||||
|
global.win.webContents.send(event, serializeIpc(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndCreateModule(manifest) {
|
||||||
|
console.log(`Fetching ${manifest}...`)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await got.get(manifest)
|
||||||
|
const moduleCode = response.body
|
||||||
|
|
||||||
|
const newModule = new module.constructor()
|
||||||
|
newModule._compile(moduleCode, manifest)
|
||||||
|
|
||||||
|
return newModule
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readManifest(manifest, { just_read = false } = {}) {
|
||||||
|
// check if manifest is a directory or a url
|
||||||
|
const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/gi
|
||||||
|
|
||||||
|
if (urlRegex.test(manifest)) {
|
||||||
|
const _module = await fetchAndCreateModule(manifest)
|
||||||
|
const remoteUrl = lodash.clone(manifest)
|
||||||
|
|
||||||
|
manifest = _module.exports
|
||||||
|
|
||||||
|
manifest.remote_url = remoteUrl
|
||||||
|
} else {
|
||||||
|
if (!fs.existsSync(manifest)) {
|
||||||
|
throw new Error(`Manifest not found: ${manifest}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.statSync(manifest).isFile()) {
|
||||||
|
throw new Error(`Manifest is not a file: ${manifest}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifestFilePath = lodash.clone(manifest)
|
||||||
|
|
||||||
|
manifest = require(manifest)
|
||||||
|
|
||||||
|
if (!just_read) {
|
||||||
|
// copy manifest
|
||||||
|
fs.copyFileSync(manifestFilePath, path.resolve(MANIFEST_PATH, path.basename(manifest)))
|
||||||
|
|
||||||
|
manifest.remote_url = manifestFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PkgManager {
|
||||||
|
constructor() {
|
||||||
|
this.initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
get realmDbPath() {
|
||||||
|
return path.join(RUNTIME_PATH, "local_realm.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
get runtimePath() {
|
||||||
|
return RUNTIME_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
if (!fs.existsSync(RUNTIME_PATH)) {
|
||||||
|
fs.mkdirSync(RUNTIME_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(INSTALLERS_PATH)) {
|
||||||
|
fs.mkdirSync(INSTALLERS_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(MANIFEST_PATH)) {
|
||||||
|
fs.mkdirSync(MANIFEST_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(TMP_PATH)) {
|
||||||
|
fs.mkdirSync(TMP_PATH, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(this.realmDbPath)) {
|
||||||
|
console.log(`Creating default realm db...`, this.realmDbPath)
|
||||||
|
|
||||||
|
await this.writeDb(RealmDBDefault)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DB Operations
|
||||||
|
async readDb() {
|
||||||
|
return JSON.parse(await fs.promises.readFile(this.realmDbPath, "utf8"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async writeDb(data) {
|
||||||
|
return fs.promises.writeFile(this.realmDbPath, JSON.stringify(data, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
async appendInstallation(manifest) {
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
const prevIndex = db.installations.findIndex((i) => i.id === manifest.id)
|
||||||
|
|
||||||
|
if (prevIndex !== -1) {
|
||||||
|
db.installations[prevIndex] = manifest
|
||||||
|
} else {
|
||||||
|
db.installations.push(manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.writeDb(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRUD Operations
|
||||||
|
async getInstallations() {
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
return db.installations
|
||||||
|
}
|
||||||
|
|
||||||
|
async openBundleFolder(manifest_id) {
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
const index = db.installations.findIndex((i) => i.id === manifest_id)
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
const manifest = db.installations[index]
|
||||||
|
|
||||||
|
open(manifest.install_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async install(manifest) {
|
||||||
|
let pendingTasks = []
|
||||||
|
|
||||||
|
manifest = await readManifest(manifest).catch((error) => {
|
||||||
|
sendToRenderer("runtime:error", "Cannot fetch this manifest")
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const packPath = path.resolve(INSTALLERS_PATH, manifest.id)
|
||||||
|
|
||||||
|
if (typeof manifest.init === "function") {
|
||||||
|
const init_result = await manifest.init({
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
...manifest,
|
||||||
|
...init_result,
|
||||||
|
}
|
||||||
|
|
||||||
|
delete manifest.init
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.status = "installing"
|
||||||
|
|
||||||
|
console.log(`Starting to install ${manifest.pack_name}...`)
|
||||||
|
console.log(`Installing at >`, packPath)
|
||||||
|
|
||||||
|
sendToRenderer("new:installation", manifest)
|
||||||
|
|
||||||
|
fs.mkdirSync(packPath, { recursive: true })
|
||||||
|
|
||||||
|
await this.appendInstallation(manifest)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof manifest.git_clones_steps !== "undefined" && Array.isArray(manifest.git_clones_steps)) {
|
||||||
|
for await (const step of manifest.git_clones_steps) {
|
||||||
|
const _path = path.resolve(packPath, step.path)
|
||||||
|
|
||||||
|
console.log(`Cloning ${step.url}...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Cloning ${step.url}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.mkdirSync(_path, { recursive: true })
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const process = ChildProcess.exec(`git clone --recurse-submodules --remote-submodules ${step.url} ${_path}`, {
|
||||||
|
shell: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("exit", resolve)
|
||||||
|
process.on("error", reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.http_downloads !== "undefined" && Array.isArray(manifest.http_downloads)) {
|
||||||
|
for await (const step of manifest.http_downloads) {
|
||||||
|
let _path = path.resolve(packPath, step.path ?? ".")
|
||||||
|
|
||||||
|
console.log(`Downloading ${step.url}...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Downloading ${step.url}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (step.tmp) {
|
||||||
|
_path = path.resolve(TMP_PATH, String(new Date().getTime()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(path.resolve(_path, ".."), { recursive: true })
|
||||||
|
|
||||||
|
await streamPipeline(
|
||||||
|
got.stream(step.url),
|
||||||
|
fs.createWriteStream(_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (step.execute) {
|
||||||
|
pendingTasks.push(async () => {
|
||||||
|
await new Promise(async (resolve, reject) => {
|
||||||
|
const process = ChildProcess.execFile(_path, {
|
||||||
|
shell: true,
|
||||||
|
}, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("exit", resolve)
|
||||||
|
process.on("error", reject)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.extract) {
|
||||||
|
console.log(`Extracting ${step.extract}...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Extracting bundle ${step.extract}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const extract = extractFull(_path, step.extract, {
|
||||||
|
$bin: global.SEVENZIP_PATH
|
||||||
|
})
|
||||||
|
|
||||||
|
extract.on("error", reject)
|
||||||
|
extract.on("end", resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingTasks.length > 0) {
|
||||||
|
console.log(`Performing pending tasks...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing pending tasks...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
for await (const task of pendingTasks) {
|
||||||
|
await task()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.after_install === "function") {
|
||||||
|
console.log(`Performing after_install hook...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing after_install hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await manifest.after_install({
|
||||||
|
manifest,
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.status = "installed"
|
||||||
|
manifest.install_path = packPath
|
||||||
|
manifest.installed_at = new Date()
|
||||||
|
manifest.last_update = null
|
||||||
|
|
||||||
|
await this.appendInstallation(manifest)
|
||||||
|
|
||||||
|
console.log(`Successfully installed ${manifest.pack_name}!`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:done`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: "Successfully installed",
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
manifest.status = "failed"
|
||||||
|
|
||||||
|
sendToRenderer(`installation:error`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: error.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
console.error(error)
|
||||||
|
|
||||||
|
fs.rmdirSync(packPath, { recursive: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uninstall(manifest_id) {
|
||||||
|
console.log(`Uninstalling >`, manifest_id)
|
||||||
|
|
||||||
|
sendToRenderer("installation:status", {
|
||||||
|
status: "uninstalling",
|
||||||
|
id: manifest_id,
|
||||||
|
statusText: `Uninstalling ${manifest_id}...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
const manifest = db.installations.find((i) => i.id === manifest_id)
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
sendToRenderer("runtime:error", "Manifest not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.remote_url) {
|
||||||
|
const remoteManifest = await readManifest(manifest.remote_url, { just_read: true })
|
||||||
|
|
||||||
|
if (typeof remoteManifest.uninstall === "function") {
|
||||||
|
console.log(`Performing uninstall hook...`)
|
||||||
|
|
||||||
|
await remoteManifest.uninstall({
|
||||||
|
manifest: remoteManifest,
|
||||||
|
pack_dir: remoteManifest.install_path,
|
||||||
|
tmp_dir: TMP_PATH,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await rimraf(manifest.install_path)
|
||||||
|
|
||||||
|
db.installations = db.installations.filter((i) => i.id !== manifest_id)
|
||||||
|
|
||||||
|
await this.writeDb(db)
|
||||||
|
|
||||||
|
sendToRenderer("installation:uninstalled", {
|
||||||
|
id: manifest_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(manifest_id) {
|
||||||
|
try {
|
||||||
|
let pendingTasks = []
|
||||||
|
|
||||||
|
console.log(`Updating >`, manifest_id)
|
||||||
|
|
||||||
|
sendToRenderer("installation:status", {
|
||||||
|
status: "updating",
|
||||||
|
id: manifest_id,
|
||||||
|
statusText: `Updating ${manifest_id}...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const db = await this.readDb()
|
||||||
|
|
||||||
|
let manifest = db.installations.find((i) => i.id === manifest_id)
|
||||||
|
|
||||||
|
if (!manifest) {
|
||||||
|
sendToRenderer("runtime:error", "Manifest not found")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(manifest)
|
||||||
|
|
||||||
|
const packPath = manifest.install_path
|
||||||
|
|
||||||
|
if (manifest.remote_url) {
|
||||||
|
manifest = await readManifest(manifest.remote_url, { just_read: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.status = "updating"
|
||||||
|
|
||||||
|
if (typeof manifest.init === "function") {
|
||||||
|
const init_result = await manifest.init({
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
...manifest,
|
||||||
|
...init_result,
|
||||||
|
}
|
||||||
|
|
||||||
|
delete manifest.init
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(manifest)
|
||||||
|
|
||||||
|
if (typeof manifest.update === "function") {
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing update hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Performing update hook...`)
|
||||||
|
|
||||||
|
await manifest.update({
|
||||||
|
manifest,
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.git_update !== "undefined" && Array.isArray(manifest.git_update)) {
|
||||||
|
for await (const step of manifest.git_update) {
|
||||||
|
const _path = path.resolve(packPath, step.path)
|
||||||
|
|
||||||
|
console.log(`GIT Pulling ${step.url}`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `GIT Pulling ${step.url}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const process = ChildProcess.exec(`git pull`, {
|
||||||
|
cwd: _path,
|
||||||
|
shell: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("exit", resolve)
|
||||||
|
process.on("error", reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.http_downloads !== "undefined" && Array.isArray(manifest.http_downloads)) {
|
||||||
|
for await (const step of manifest.http_downloads) {
|
||||||
|
let _path = path.resolve(packPath, step.path ?? ".")
|
||||||
|
|
||||||
|
console.log(`Downloading ${step.url}...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Downloading ${step.url}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (step.tmp) {
|
||||||
|
_path = path.resolve(TMP_PATH, String(new Date().getTime()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(path.resolve(_path, ".."), { recursive: true })
|
||||||
|
|
||||||
|
await streamPipeline(
|
||||||
|
got.stream(step.url),
|
||||||
|
fs.createWriteStream(_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (step.execute) {
|
||||||
|
pendingTasks.push(async () => {
|
||||||
|
await new Promise(async (resolve, reject) => {
|
||||||
|
const process = ChildProcess.execFile(_path, {
|
||||||
|
shell: true,
|
||||||
|
}, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
process.on("exit", resolve)
|
||||||
|
process.on("error", reject)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.extract) {
|
||||||
|
console.log(`Extracting ${step.extract}...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Extracting bundle ${step.extract}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const extract = extractFull(_path, step.extract, {
|
||||||
|
$bin: global.SEVENZIP_PATH
|
||||||
|
})
|
||||||
|
|
||||||
|
extract.on("error", reject)
|
||||||
|
extract.on("end", resolve)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingTasks.length > 0) {
|
||||||
|
console.log(`Performing pending tasks...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing pending tasks...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
for await (const task of pendingTasks) {
|
||||||
|
await task()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof manifest.after_install === "function") {
|
||||||
|
console.log(`Performing after_install hook...`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:status`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: `Performing after_install hook...`,
|
||||||
|
})
|
||||||
|
|
||||||
|
await manifest.after_install({
|
||||||
|
manifest,
|
||||||
|
pack_dir: packPath,
|
||||||
|
tmp_dir: TMP_PATH
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.status = "installed"
|
||||||
|
manifest.install_path = packPath
|
||||||
|
manifest.last_update = new Date()
|
||||||
|
|
||||||
|
await this.appendInstallation(manifest)
|
||||||
|
|
||||||
|
console.log(`Successfully updated ${manifest.pack_name}!`)
|
||||||
|
|
||||||
|
sendToRenderer(`installation:done`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: "Successfully updated",
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
manifest.status = "failed"
|
||||||
|
|
||||||
|
sendToRenderer(`installation:error`, {
|
||||||
|
...manifest,
|
||||||
|
statusText: error.toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
src/main/setup.js
Normal file
74
src/main/setup.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import path from "node:path"
|
||||||
|
import fs from "node:fs"
|
||||||
|
import os from "node:os"
|
||||||
|
import ChildProcess from "node:child_process"
|
||||||
|
import { pipeline as streamPipeline } from "node:stream/promises"
|
||||||
|
|
||||||
|
import got from "got"
|
||||||
|
|
||||||
|
function resolveDestBin(pre, post) {
|
||||||
|
let url = null
|
||||||
|
|
||||||
|
if (process.platform === "darwin") {
|
||||||
|
url = `${pre}/mac/${process.arch}/${post}`
|
||||||
|
}
|
||||||
|
else if (process.platform === "win32") {
|
||||||
|
url = `${pre}/win/${process.arch}/${post}`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
url = `${pre}/linux/${process.arch}/${post}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const sevenzip_exec = path.resolve(global.RUNTIME_PATH, "7z-bin", process.platform === "win32" ? "7za.exe" : "7za")
|
||||||
|
const git_exec = path.resolve(global.RUNTIME_PATH, "git", process.platform === "win32" ? "git.exe" : "git")
|
||||||
|
|
||||||
|
if (!fs.existsSync(sevenzip_exec)) {
|
||||||
|
global.win.webContents.send("initializing_text", "Downloading 7z binaries...")
|
||||||
|
|
||||||
|
console.log(`Downloading 7z binaries...`)
|
||||||
|
|
||||||
|
fs.mkdirSync(path.resolve(global.RUNTIME_PATH, "7z-bin"), { recursive: true })
|
||||||
|
|
||||||
|
let url = resolveDestBin(`https://storage.ragestudio.net/rstudio/binaries/7zip-bin`, process.platform === "win32" ? "7za.exe" : "7za")
|
||||||
|
|
||||||
|
await streamPipeline(
|
||||||
|
got.stream(url),
|
||||||
|
fs.createWriteStream(sevenzip_exec)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (os.platform() !== "win32") {
|
||||||
|
ChildProcess.execSync("chmod +x " + sevenzip_exec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(git_exec) && process.platform === "win32") {
|
||||||
|
global.win.webContents.send("initializing_text", "Downloading GIT binaries...")
|
||||||
|
|
||||||
|
console.log(`Downloading git binaries...`)
|
||||||
|
|
||||||
|
fs.mkdirSync(path.resolve(global.RUNTIME_PATH, "git"), { recursive: true })
|
||||||
|
|
||||||
|
let url = resolveDestBin(`https://storage.ragestudio.net/rstudio/binaries/git`, "git.7z")
|
||||||
|
|
||||||
|
await streamPipeline(
|
||||||
|
got.stream(url),
|
||||||
|
fs.createWriteStream(git_exec)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (os.platform() !== "win32") {
|
||||||
|
ChildProcess.execSync("chmod +x " + git_exec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.SEVENZIP_PATH = sevenzip_exec
|
||||||
|
global.GIT_PATH = git_exec
|
||||||
|
|
||||||
|
console.log(`7z binaries: ${sevenzip_exec}`)
|
||||||
|
console.log(`GIT binaries: ${git_exec}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default main
|
33
src/preload/index.js
Normal file
33
src/preload/index.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { contextBridge, ipcRenderer } from "electron"
|
||||||
|
import { electronAPI } from "@electron-toolkit/preload"
|
||||||
|
|
||||||
|
const api = {}
|
||||||
|
|
||||||
|
if (process.contextIsolated) {
|
||||||
|
try {
|
||||||
|
contextBridge.exposeInMainWorld(
|
||||||
|
"ipc",
|
||||||
|
{
|
||||||
|
exec: (channel, ...args) => {
|
||||||
|
return ipcRenderer.invoke(channel, ...args)
|
||||||
|
},
|
||||||
|
send: (channel, args) => {
|
||||||
|
ipcRenderer.send(channel, args)
|
||||||
|
},
|
||||||
|
on: (channel, listener) => {
|
||||||
|
ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
|
||||||
|
},
|
||||||
|
off: (channel, listener) => {
|
||||||
|
ipcRenderer.removeListener(channel, listener)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||||
|
contextBridge.exposeInMainWorld('api', api)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.electron = electronAPI
|
||||||
|
window.api = api
|
||||||
|
}
|
16
src/renderer/index.html
Normal file
16
src/renderer/index.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>RageStudio Bundler</title>
|
||||||
|
<!-- <meta
|
||||||
|
http-equiv="Content-Security-Policy"
|
||||||
|
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
|
||||||
|
/> -->
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
125
src/renderer/src/App.jsx
Normal file
125
src/renderer/src/App.jsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
|
||||||
|
import BarLoader from "react-spinners/BarLoader"
|
||||||
|
|
||||||
|
import GlobalStateContext from "contexts/global"
|
||||||
|
|
||||||
|
import getRootCssVar from "utils/getRootCssVar"
|
||||||
|
|
||||||
|
import InstallationsManager from "pages/manager"
|
||||||
|
|
||||||
|
import { MdFolder } from "react-icons/md"
|
||||||
|
|
||||||
|
globalThis.getRootCssVar = getRootCssVar
|
||||||
|
|
||||||
|
const PageRender = () => {
|
||||||
|
const globalState = React.useContext(GlobalStateContext)
|
||||||
|
|
||||||
|
if (globalState.initializing_text && globalState.loading) {
|
||||||
|
return <div className="app_setup">
|
||||||
|
<BarLoader
|
||||||
|
className="app_loader"
|
||||||
|
color={getRootCssVar("--primary-color")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h1>Setting up...</h1>
|
||||||
|
|
||||||
|
<code>
|
||||||
|
<pre>{globalState.initializing_text}</pre>
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return <InstallationsManager />
|
||||||
|
}
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
state = {
|
||||||
|
loading: true,
|
||||||
|
pkg: null,
|
||||||
|
initializing: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcEvents = {
|
||||||
|
"runtime:error": (event, data) => {
|
||||||
|
antd.message.error(data)
|
||||||
|
},
|
||||||
|
"runtime:info": (event, data) => {
|
||||||
|
antd.message.info(data)
|
||||||
|
},
|
||||||
|
"initializing_text": (event, data) => {
|
||||||
|
this.setState({
|
||||||
|
initializing_text: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
for (const event in this.ipcEvents) {
|
||||||
|
ipc.on(event, this.ipcEvents[event])
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkg = await ipc.exec("pkg")
|
||||||
|
|
||||||
|
await ipc.exec("check:setup")
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
pkg: pkg,
|
||||||
|
loading: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
for (const event in this.ipcEvents) {
|
||||||
|
ipc.off(event, this.ipcEvents[event])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, pkg } = this.state
|
||||||
|
|
||||||
|
return <antd.ConfigProvider
|
||||||
|
theme={{
|
||||||
|
token: {
|
||||||
|
colorPrimary: getRootCssVar("--primary-color"),
|
||||||
|
colorBgContainer: getRootCssVar("--background-color-primary"),
|
||||||
|
colorPrimaryBg: getRootCssVar("--background-color-primary"),
|
||||||
|
},
|
||||||
|
algorithm: antd.theme.darkAlgorithm
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GlobalStateContext.Provider value={this.state}>
|
||||||
|
<antd.Layout className="app_layout">
|
||||||
|
<antd.Layout.Header className="app_header">
|
||||||
|
<h1>RageStudio Bundler</h1>
|
||||||
|
</antd.Layout.Header>
|
||||||
|
|
||||||
|
<antd.Layout.Content className="app_content">
|
||||||
|
<PageRender />
|
||||||
|
</antd.Layout.Content>
|
||||||
|
|
||||||
|
{
|
||||||
|
!loading && <antd.Layout.Footer className="app_footer">
|
||||||
|
<span>
|
||||||
|
{pkg.name}
|
||||||
|
|
||||||
|
<antd.Tag>
|
||||||
|
v{pkg.version}
|
||||||
|
</antd.Tag>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<antd.Button
|
||||||
|
size="small"
|
||||||
|
icon={<MdFolder />}
|
||||||
|
onClick={() => ipc.send("open-runtime-path")}
|
||||||
|
/>
|
||||||
|
</antd.Layout.Footer>
|
||||||
|
}
|
||||||
|
</antd.Layout>
|
||||||
|
</GlobalStateContext.Provider>
|
||||||
|
</antd.ConfigProvider>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
16
src/renderer/src/components/Versions.jsx
Normal file
16
src/renderer/src/components/Versions.jsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
function Versions() {
|
||||||
|
const [versions] = useState(window.electron.process.versions)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="versions">
|
||||||
|
<li className="electron-version">Electron v{versions.electron}</li>
|
||||||
|
<li className="chrome-version">Chromium v{versions.chrome}</li>
|
||||||
|
<li className="node-version">Node v{versions.node}</li>
|
||||||
|
<li className="v8-version">V8 v{versions.v8}</li>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Versions
|
8
src/renderer/src/contexts/global.js
Normal file
8
src/renderer/src/contexts/global.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
const GlobalStateContext = React.createContext({
|
||||||
|
pkg: {},
|
||||||
|
installations: [],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default GlobalStateContext
|
108
src/renderer/src/contexts/installations.jsx
Normal file
108
src/renderer/src/contexts/installations.jsx
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
|
||||||
|
export const Context = React.createContext([])
|
||||||
|
|
||||||
|
export class WithContext extends React.Component {
|
||||||
|
state = {
|
||||||
|
installations: []
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcEvents = {
|
||||||
|
"new:installation": (event, data) => {
|
||||||
|
antd.message.loading(`Installing ${data.id}`)
|
||||||
|
|
||||||
|
let newData = this.state.installations
|
||||||
|
|
||||||
|
// search if installation already exists
|
||||||
|
const prev = this.state.installations.findIndex((item) => item.id === data.id)
|
||||||
|
|
||||||
|
if (prev !== -1) {
|
||||||
|
newData[prev] = data
|
||||||
|
} else {
|
||||||
|
newData.push(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
installations: newData,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
"installation:status": (event, data) => {
|
||||||
|
console.log(`INSTALLATION STATUS: ${data.id} >`, data)
|
||||||
|
|
||||||
|
const { id } = data
|
||||||
|
|
||||||
|
let newData = this.state.installations
|
||||||
|
|
||||||
|
const index = newData.findIndex((item) => item.id === id)
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
newData[index] = {
|
||||||
|
...newData[index],
|
||||||
|
...data,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
installations: newData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"installation:error": (event, data) => {
|
||||||
|
antd.message.error(`Failed to install ${data.id}`)
|
||||||
|
|
||||||
|
this.ipcEvents["installation:status"](event, data)
|
||||||
|
},
|
||||||
|
"installation:done": (event, data) => {
|
||||||
|
antd.message.success(`Successfully installed ${data.id}`)
|
||||||
|
|
||||||
|
this.ipcEvents["installation:status"](event, data)
|
||||||
|
},
|
||||||
|
"installation:uninstalled": (event, data) => {
|
||||||
|
antd.message.success(`Successfully uninstalled ${data.id}`)
|
||||||
|
|
||||||
|
const index = this.state.installations.findIndex((item) => item.id === data.id)
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
this.setState({
|
||||||
|
installations: [
|
||||||
|
...this.state.installations.slice(0, index),
|
||||||
|
...this.state.installations.slice(index + 1),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
const installations = await ipc.exec("get:installations")
|
||||||
|
|
||||||
|
for (const event in this.ipcEvents) {
|
||||||
|
ipc.on(event, this.ipcEvents[event])
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
installations: [
|
||||||
|
...this.state.installations,
|
||||||
|
...installations,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
for (const event in this.ipcEvents) {
|
||||||
|
ipc.off(event, this.ipcEvents[event])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <Context.Provider
|
||||||
|
value={{
|
||||||
|
installations: this.state.installations
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</Context.Provider>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Context
|
8
src/renderer/src/main.jsx
Normal file
8
src/renderer/src/main.jsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import "./style/index.less"
|
||||||
|
|
||||||
|
import React from "react"
|
||||||
|
import ReactDOM from "react-dom"
|
||||||
|
|
||||||
|
import App from "./App"
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById("root"))
|
211
src/renderer/src/pages/manager/index.jsx
Normal file
211
src/renderer/src/pages/manager/index.jsx
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import React from "react"
|
||||||
|
import * as antd from "antd"
|
||||||
|
import classnames from "classnames"
|
||||||
|
|
||||||
|
import BarLoader from "react-spinners/BarLoader"
|
||||||
|
|
||||||
|
import { MdAdd, MdUploadFile, MdFolder, MdDelete, MdPlayArrow, MdUpdate } from "react-icons/md"
|
||||||
|
|
||||||
|
import { Context as InstallationsContext, WithContext } from "contexts/installations"
|
||||||
|
|
||||||
|
import "./index.less"
|
||||||
|
|
||||||
|
const NewInstallation = (props) => {
|
||||||
|
const [manifestUrl, setManifestUrl] = React.useState("")
|
||||||
|
|
||||||
|
const handleInstall = (manifest) => {
|
||||||
|
ipc.exec("bundle:install", manifest)
|
||||||
|
.then(() => {
|
||||||
|
props.close()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
antd.message.error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="new_installation_prompt">
|
||||||
|
<antd.Input
|
||||||
|
placeholder="Manifest URL"
|
||||||
|
value={manifestUrl}
|
||||||
|
onChange={(e) => setManifestUrl(e.target.value)}
|
||||||
|
onPressEnter={() => handleInstall(manifestUrl)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
or
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<antd.Button
|
||||||
|
icon={<MdUploadFile />}
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
Local file
|
||||||
|
</antd.Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
const InstallationItem = (props) => {
|
||||||
|
const { manifest } = props
|
||||||
|
|
||||||
|
const isLoading = manifest.status === "installing" || manifest.status === "uninstalling" || manifest.status === "updating"
|
||||||
|
const isInstalled = manifest.status === "installed"
|
||||||
|
const isFailed = manifest.status === "failed"
|
||||||
|
|
||||||
|
const onClickUpdate = () => {
|
||||||
|
ipc.exec("bundle:update", manifest.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickPlay = () => {
|
||||||
|
ipc.exec("bundle:exec", manifest.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickFolder = () => {
|
||||||
|
ipc.exec("bundle:open", manifest.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClickDelete = () => {
|
||||||
|
ipc.exec("bundle:uninstall", manifest.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div
|
||||||
|
className={classnames(
|
||||||
|
"installation_item_wrapper",
|
||||||
|
{
|
||||||
|
["status_visible"]: !isInstalled
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="installation_item">
|
||||||
|
<img src={manifest.icon} className="installation_item_icon" />
|
||||||
|
|
||||||
|
<div className="installation_item_info">
|
||||||
|
<h2>
|
||||||
|
{
|
||||||
|
manifest.pack_name
|
||||||
|
}
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
{
|
||||||
|
isLoading ? manifest.status : manifest.version ?? "N/A"
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="installation_item_actions">
|
||||||
|
{
|
||||||
|
isFailed && <antd.Button
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
Retry
|
||||||
|
</antd.Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isInstalled && <antd.Button
|
||||||
|
type="primary"
|
||||||
|
icon={<MdUpdate />}
|
||||||
|
onClick={onClickUpdate}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isInstalled && manifest.exec_path && <antd.Button
|
||||||
|
type="primary"
|
||||||
|
icon={<MdPlayArrow />}
|
||||||
|
onClick={onClickPlay}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isInstalled && <antd.Button
|
||||||
|
type="primary"
|
||||||
|
icon={<MdFolder />}
|
||||||
|
onClick={onClickFolder}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isInstalled && <antd.Popconfirm
|
||||||
|
title="Delete Installation"
|
||||||
|
onConfirm={onClickDelete}
|
||||||
|
>
|
||||||
|
<antd.Button
|
||||||
|
type="ghost"
|
||||||
|
icon={<MdDelete />}
|
||||||
|
/>
|
||||||
|
</antd.Popconfirm>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="installation_status"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
isLoading && <BarLoader color={getRootCssVar("--primary-color")} className="app_loader" />
|
||||||
|
}
|
||||||
|
<p>{manifest.statusText ?? "Unknown status"}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstallationsManager extends React.Component {
|
||||||
|
static contextType = InstallationsContext
|
||||||
|
|
||||||
|
state = {
|
||||||
|
drawerVisible: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDrawer = (to) => {
|
||||||
|
this.setState({
|
||||||
|
drawerVisible: to ?? !this.state.drawerVisible,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { installations } = this.context
|
||||||
|
|
||||||
|
const empty = installations.length == 0
|
||||||
|
|
||||||
|
return <div className="installations_manager">
|
||||||
|
<antd.Button
|
||||||
|
type="primary"
|
||||||
|
icon={<MdAdd />}
|
||||||
|
onClick={() => this.toggleDrawer(true)}
|
||||||
|
>
|
||||||
|
Add new installation
|
||||||
|
</antd.Button>
|
||||||
|
|
||||||
|
<div className={empty ? "installations_list empty" : "installations_list"}>
|
||||||
|
{
|
||||||
|
empty && <antd.Empty description="No installations" />
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
installations.map((manifest) => {
|
||||||
|
return <InstallationItem key={manifest.id} manifest={manifest} />
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<antd.Drawer
|
||||||
|
title="Add new installation"
|
||||||
|
placement="bottom"
|
||||||
|
open={this.state.drawerVisible}
|
||||||
|
onClose={() => this.toggleDrawer(false)}
|
||||||
|
>
|
||||||
|
<NewInstallation
|
||||||
|
close={() => this.toggleDrawer(false)}
|
||||||
|
/>
|
||||||
|
</antd.Drawer>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const InstallationsManagerPage = (props) => {
|
||||||
|
return <WithContext>
|
||||||
|
<InstallationsManager {...props} />
|
||||||
|
</WithContext>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default InstallationsManagerPage
|
157
src/renderer/src/pages/manager/index.less
Normal file
157
src/renderer/src/pages/manager/index.less
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
.installations_manager {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.installations_list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@installation-item-borderRadius: 12px;
|
||||||
|
|
||||||
|
.installation_item_wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.status_visible {
|
||||||
|
.installation_item {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.installation_status {
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
padding: 10px 20px;
|
||||||
|
padding-top: calc(8px + 10px);
|
||||||
|
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
.installation_item {
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.installation_status {
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.installation_item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
border-radius: @installation-item-borderRadius;
|
||||||
|
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
|
||||||
|
z-index: 50;
|
||||||
|
|
||||||
|
.installation_item_info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.installation_item_icon {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
|
||||||
|
min-width: 50px;
|
||||||
|
min-height: 50px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.installation_item_actions {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.installation_status {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
z-index: 49;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
border-radius: 0 0 12px 12px;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
opacity: 0;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
line-height: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.new_installation_prompt {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
gap: 20px;
|
||||||
|
}
|
153
src/renderer/src/style/index.less
Normal file
153
src/renderer/src/style/index.less
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
@import "style/reset.css";
|
||||||
|
|
||||||
|
@var-text-color: #fff;
|
||||||
|
@var-background-color-primary: #424549;
|
||||||
|
@var-background-color-secondary: #1e2124;
|
||||||
|
@var-primary-color: #36d7b7; //#F3B61F;
|
||||||
|
@var-border-color: #a1a2a2;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background-color-primary: @var-background-color-primary;
|
||||||
|
--background-color-secondary: @var-background-color-secondary;
|
||||||
|
--primary-color: @var-primary-color;
|
||||||
|
--text-color: @var-text-color;
|
||||||
|
--border-color: @var-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: var(--background-color-primary);
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen;
|
||||||
|
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_layout {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_header {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background-color: darken(@var-background-color-primary, 5%);
|
||||||
|
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_footer {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
gap: 30px;
|
||||||
|
|
||||||
|
background-color: darken(@var-background-color-primary, 5%);
|
||||||
|
|
||||||
|
border: 1px solid @var-border-color;
|
||||||
|
|
||||||
|
padding: 10px 40px;
|
||||||
|
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
background-color: var(--background-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
span {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: var(--text-color);
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
* svg {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-message-notice-wrapper {
|
||||||
|
.ant-message-notice-content {
|
||||||
|
color: var(--text-color) !important;
|
||||||
|
background-color: var(--background-color-primary) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_setup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_loader {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
48
src/renderer/src/style/reset.css
Normal file
48
src/renderer/src/style/reset.css
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/* http://meyerweb.com/eric/tools/css/reset/
|
||||||
|
v2.0 | 20110126
|
||||||
|
License: none (public domain)
|
||||||
|
*/
|
||||||
|
|
||||||
|
html, body, div, span, applet, object, iframe,
|
||||||
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||||
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
|
small, strike, strong, sub, sup, tt, var,
|
||||||
|
b, u, i, center,
|
||||||
|
dl, dt, dd, ol, ul, li,
|
||||||
|
fieldset, form, label, legend,
|
||||||
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
|
article, aside, canvas, details, embed,
|
||||||
|
figure, figcaption, footer, header, hgroup,
|
||||||
|
menu, nav, output, ruby, section, summary,
|
||||||
|
time, mark, audio, video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
/* HTML5 display-role reset for older browsers */
|
||||||
|
article, aside, details, figcaption, figure,
|
||||||
|
footer, header, hgroup, menu, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
blockquote, q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
blockquote:before, blockquote:after,
|
||||||
|
q:before, q:after {
|
||||||
|
content: '';
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
6
src/renderer/src/utils/getRootCssVar/index.js
Normal file
6
src/renderer/src/utils/getRootCssVar/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
function getRootCssVar(key) {
|
||||||
|
const root = document.querySelector(':root')
|
||||||
|
return window.getComputedStyle(root).getPropertyValue(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getRootCssVar
|
Loading…
x
Reference in New Issue
Block a user