mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 10:34:18 +00:00
added settings
This commit is contained in:
parent
979a4fa515
commit
2c0533a16f
@ -1,32 +1,16 @@
|
||||
import lodash from "lodash"
|
||||
import sendToRender from "./utils/sendToRender"
|
||||
|
||||
global.sendToRenderer = (event, data) => {
|
||||
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]
|
||||
}
|
||||
global.SettingsStore = new Store({
|
||||
name: "settings",
|
||||
watch: true,
|
||||
})
|
||||
}
|
||||
|
||||
return copy
|
||||
}
|
||||
|
||||
global.win.webContents.send(event, serializeIpc(data))
|
||||
}
|
||||
|
||||
const { autoUpdater } = require("electron-differential-updater")
|
||||
const ProtocolRegistry = require("protocol-registry")
|
||||
|
||||
import path from "node:path"
|
||||
|
||||
import { app, shell, BrowserWindow, ipcMain } from "electron"
|
||||
import { electronApp, optimizer, is } from "@electron-toolkit/utils"
|
||||
import isDev from "electron-is-dev"
|
||||
import Store from "electron-store"
|
||||
|
||||
import open from "open"
|
||||
|
||||
@ -37,6 +21,11 @@ import setup from "./setup"
|
||||
import PkgManager from "./pkg_mng"
|
||||
import { readManifest } from "./utils/readManifest"
|
||||
|
||||
import GoogleDriveAPI from "./lib/google_drive"
|
||||
|
||||
const { autoUpdater } = require("electron-differential-updater")
|
||||
const ProtocolRegistry = require("protocol-registry")
|
||||
|
||||
const protocolRegistryNamespace = "rsbundle"
|
||||
|
||||
class ElectronApp {
|
||||
@ -46,9 +35,6 @@ class ElectronApp {
|
||||
}
|
||||
|
||||
handlers = {
|
||||
pkg: () => {
|
||||
return pkg
|
||||
},
|
||||
"get:installations": async () => {
|
||||
return await this.pkgManager.getInstallations()
|
||||
},
|
||||
@ -73,9 +59,6 @@ class ElectronApp {
|
||||
"pkg:apply_changes": (event, manifest_id, changes) => {
|
||||
this.pkgManager.applyChanges(manifest_id, changes)
|
||||
},
|
||||
"check:setup": async () => {
|
||||
return await setup()
|
||||
},
|
||||
"updater:check": () => {
|
||||
autoUpdater.checkForUpdates()
|
||||
},
|
||||
@ -83,7 +66,32 @@ class ElectronApp {
|
||||
setTimeout(() => {
|
||||
autoUpdater.quitAndInstall()
|
||||
}, 3000)
|
||||
},
|
||||
"settings:get": (e, key) => {
|
||||
return global.SettingsStore.get(key)
|
||||
},
|
||||
"settings:set": (e, key, value) => {
|
||||
return global.SettingsStore.set(key, value)
|
||||
},
|
||||
"settings:delete": (e, key) => {
|
||||
return global.SettingsStore.delete(key)
|
||||
},
|
||||
"settings:has": (e, key) => {
|
||||
return global.SettingsStore.has(key)
|
||||
},
|
||||
"app:init": async (event, data) => {
|
||||
await setup()
|
||||
|
||||
// check if can decode google drive token
|
||||
const googleDrive_enabled = !!await GoogleDriveAPI.readCredentials()
|
||||
|
||||
return {
|
||||
pkg: pkg,
|
||||
authorizedServices: {
|
||||
drive: googleDrive_enabled,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
events = {
|
||||
@ -92,11 +100,6 @@ class ElectronApp {
|
||||
},
|
||||
}
|
||||
|
||||
sendToRender(event, ...args) {
|
||||
console.log(`[sendToRender][${event}]`, ...args)
|
||||
this.win.webContents.send(event, ...args)
|
||||
}
|
||||
|
||||
createWindow() {
|
||||
this.win = global.win = new BrowserWindow({
|
||||
width: 450,
|
||||
@ -129,8 +132,6 @@ class ElectronApp {
|
||||
}
|
||||
|
||||
handleURLProtocol(url) {
|
||||
console.log(url)
|
||||
|
||||
const urlStarter = `${protocolRegistryNamespace}://`
|
||||
|
||||
if (url.startsWith(urlStarter)) {
|
||||
@ -143,18 +144,17 @@ class ElectronApp {
|
||||
|
||||
switch (action) {
|
||||
case "install": {
|
||||
return this.sendToRender("installation:invoked", value)
|
||||
return sendToRender("installation:invoked", value)
|
||||
}
|
||||
|
||||
default: {
|
||||
return this.sendToRender("new:message", {
|
||||
return sendToRender("new:message", {
|
||||
message: "Unrecognized URL action",
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// by default if no action is specified, assume is a install action
|
||||
return this.sendToRender("installation:invoked", urlValue)
|
||||
return sendToRender("installation:invoked", urlValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,7 +217,7 @@ class ElectronApp {
|
||||
autoUpdater.on("update-downloaded", (ev, info) => {
|
||||
console.log(info)
|
||||
|
||||
this.sendToRender("update-available", info)
|
||||
sendToRender("update-available", info)
|
||||
})
|
||||
|
||||
if (isDev) {
|
||||
@ -240,7 +240,9 @@ class ElectronApp {
|
||||
}
|
||||
}
|
||||
|
||||
this.createWindow()
|
||||
await GoogleDriveAPI.init()
|
||||
|
||||
await this.createWindow()
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
|
@ -1,7 +1,22 @@
|
||||
import { contextBridge, ipcRenderer } from "electron"
|
||||
import { electronAPI } from "@electron-toolkit/preload"
|
||||
|
||||
const api = {}
|
||||
const api = {
|
||||
settings: {
|
||||
get: (key) => {
|
||||
return ipcRenderer.invoke("settings:get", key)
|
||||
},
|
||||
set: (key, value) => {
|
||||
return ipcRenderer.invoke("settings:set", key, value)
|
||||
},
|
||||
delete: (key) => {
|
||||
return ipcRenderer.invoke("settings:delete", key)
|
||||
},
|
||||
has: (key) => {
|
||||
return ipcRenderer.invoke("settings:has", key)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (process.contextIsolated) {
|
||||
try {
|
||||
@ -21,8 +36,8 @@ if (process.contextIsolated) {
|
||||
ipcRenderer.removeListener(channel, listener)
|
||||
}
|
||||
},
|
||||
|
||||
)
|
||||
|
||||
contextBridge.exposeInMainWorld("electron", electronAPI)
|
||||
|
||||
contextBridge.exposeInMainWorld("api", api)
|
||||
|
@ -10,7 +10,7 @@ import ManifestInfo from "components/ManifestInfo"
|
||||
import AppHeader from "layout/components/Header"
|
||||
import AppModalDialog from "layout/components/ModalDialog"
|
||||
|
||||
import { PageRender } from "./router.jsx"
|
||||
import { InternalRouter, PageRender } from "./router.jsx"
|
||||
|
||||
globalThis.getRootCssVar = getRootCssVar
|
||||
globalThis.notification = antd.notification
|
||||
@ -32,7 +32,10 @@ window.app = {
|
||||
app.modal.close()
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
checkUpdates: () => {
|
||||
ipc.exec("updater:check")
|
||||
},
|
||||
}
|
||||
|
||||
class App extends React.Component {
|
||||
@ -41,6 +44,10 @@ class App extends React.Component {
|
||||
pkg: null,
|
||||
initializing: false,
|
||||
updateAvailable: false,
|
||||
|
||||
authorizedServices: {
|
||||
drive: false,
|
||||
},
|
||||
}
|
||||
|
||||
ipcEvents = {
|
||||
@ -50,14 +57,6 @@ class App extends React.Component {
|
||||
"runtime:info": (event, data) => {
|
||||
antd.message.info(data)
|
||||
},
|
||||
"initializing_text": (event, data) => {
|
||||
this.setState({
|
||||
initializing_text: data,
|
||||
})
|
||||
},
|
||||
"installation:invoked": (event, manifest) => {
|
||||
app.invokeInstall(manifest)
|
||||
},
|
||||
"new:notification": (event, data) => {
|
||||
antd.notification[data.type || "info"]({
|
||||
message: data.message,
|
||||
@ -71,6 +70,28 @@ class App extends React.Component {
|
||||
antd.message[data.type || "info"](data.message)
|
||||
},
|
||||
"update-available": (event, data) => {
|
||||
this.onUpdateAvailable(data)
|
||||
},
|
||||
"initializing_text": (event, data) => {
|
||||
this.setState({
|
||||
initializing_text: data,
|
||||
})
|
||||
},
|
||||
"installation:invoked": (event, manifest) => {
|
||||
app.invokeInstall(manifest)
|
||||
},
|
||||
"drive:authorized": (event, data) => {
|
||||
this.setState({
|
||||
authorizedServices: {
|
||||
drive: true,
|
||||
},
|
||||
})
|
||||
|
||||
message.success("Google Drive API authorized")
|
||||
},
|
||||
}
|
||||
|
||||
onUpdateAvailable = () => {
|
||||
this.setState({
|
||||
updateAvailable: true,
|
||||
})
|
||||
@ -91,20 +112,22 @@ class App extends React.Component {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount = async () => {
|
||||
for (const event in this.ipcEvents) {
|
||||
ipc.on(event, this.ipcEvents[event])
|
||||
}
|
||||
|
||||
const pkg = await ipc.exec("pkg")
|
||||
const initResult = await ipc.exec("app:init")
|
||||
|
||||
await ipc.exec("check:setup")
|
||||
console.log(`[INIT] >`, initResult)
|
||||
|
||||
this.setState({
|
||||
pkg: pkg,
|
||||
loading: false,
|
||||
pkg: initResult.pkg,
|
||||
authorizedServices: {
|
||||
drive: initResult.authorizedServices?.drive ?? false
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -125,6 +148,7 @@ class App extends React.Component {
|
||||
algorithm: antd.theme.darkAlgorithm
|
||||
}}
|
||||
>
|
||||
<InternalRouter>
|
||||
<GlobalStateContext.Provider value={this.state}>
|
||||
<AppModalDialog />
|
||||
|
||||
@ -136,6 +160,7 @@ class App extends React.Component {
|
||||
</antd.Layout.Content>
|
||||
</antd.Layout>
|
||||
</GlobalStateContext.Provider>
|
||||
</InternalRouter>
|
||||
</antd.ConfigProvider>
|
||||
}
|
||||
}
|
||||
|
19
src/renderer/src/components/Icons/index.jsx
Normal file
19
src/renderer/src/components/Icons/index.jsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from "react"
|
||||
|
||||
import * as MDIcons from "react-icons/md"
|
||||
import * as SIIcons from "react-icons/si"
|
||||
|
||||
export const Icons = {
|
||||
...MDIcons,
|
||||
...SIIcons,
|
||||
}
|
||||
|
||||
export const Icon = ({ icon }) => {
|
||||
if (icon && Icons[icon]) {
|
||||
return React.createElement(Icons[icon])
|
||||
}
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
export default Icons
|
@ -1,6 +1,6 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
import { MdFolder, MdSettings, MdDownload } from "react-icons/md"
|
||||
import { Icons } from "components/Icons"
|
||||
|
||||
import GlobalStateContext from "contexts/global"
|
||||
|
||||
@ -10,16 +10,24 @@ const Header = (props) => {
|
||||
const ctx = React.useContext(GlobalStateContext)
|
||||
|
||||
return <antd.Layout.Header className="app_header">
|
||||
<div className="branding">
|
||||
<div className="branding" onClick={() => app.location.push("/")}>
|
||||
<Icon />
|
||||
</div>
|
||||
|
||||
{
|
||||
!ctx.loading && <div className="menu">
|
||||
{
|
||||
ctx.authorizedServices?.drive && <Icons.SiGoogledrive
|
||||
style={{
|
||||
color: `var(--primary-color)`,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
ctx.updateAvailable && <antd.Button
|
||||
size="small"
|
||||
icon={<MdDownload />}
|
||||
icon={<Icons.MdDownload />}
|
||||
onClick={app.applyUpdate}
|
||||
>
|
||||
Update now
|
||||
@ -28,12 +36,13 @@ const Header = (props) => {
|
||||
|
||||
<antd.Button
|
||||
size="small"
|
||||
icon={<MdSettings />}
|
||||
icon={<Icons.MdSettings />}
|
||||
onClick={() => app.location.push("/settings")}
|
||||
/>
|
||||
|
||||
<antd.Button
|
||||
size="small"
|
||||
icon={<MdFolder />}
|
||||
icon={<Icons.MdFolder />}
|
||||
onClick={() => ipc.send("open-runtime-path")}
|
||||
/>
|
||||
|
||||
|
@ -1,15 +1,178 @@
|
||||
import React from "react"
|
||||
import * as antd from "antd"
|
||||
import { Icons, Icon } from "components/Icons"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
const settingsList = [
|
||||
{
|
||||
id: "drive_auth",
|
||||
name: "Google Drive Authorize",
|
||||
description: "Authorize your Google Drive account to be used for bundles installation.",
|
||||
icon: "SiGoogledrive",
|
||||
type: "button",
|
||||
value: async () => {
|
||||
return api.settings.get("drive_auth")
|
||||
},
|
||||
render: (props) => {
|
||||
return <antd.Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
if (!props.value) {
|
||||
message.info("Authorizing...")
|
||||
return ipc.exec("drive:authorize")
|
||||
}
|
||||
|
||||
return api.settings.delete("drive_auth")
|
||||
}}
|
||||
>
|
||||
{
|
||||
props.value ? "Deauthorize" : "Authorize"
|
||||
}
|
||||
</antd.Button>
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "check_update",
|
||||
name: "Check for updates",
|
||||
description: "Check for updates to the app.",
|
||||
icon: "MdUpdate",
|
||||
type: "button",
|
||||
props: {
|
||||
children: "Check",
|
||||
onClick: () => {
|
||||
message.info("Checking for updates...")
|
||||
app.checkUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const Settings = () => {
|
||||
const SettingTypeToComponent = {
|
||||
switch: antd.Switch,
|
||||
button: antd.Button,
|
||||
}
|
||||
|
||||
const SettingItem = (props) => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
icon,
|
||||
props: _props,
|
||||
render,
|
||||
} = props.setting
|
||||
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const [value, setValue] = React.useState(null)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (typeof props.setting.value === "function") {
|
||||
setLoading(true)
|
||||
|
||||
props.setting.value().then((value) => {
|
||||
setValue(value)
|
||||
setLoading(false)
|
||||
})
|
||||
} else {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [props.setting.value])
|
||||
|
||||
let componentProps = {
|
||||
value: value,
|
||||
..._props,
|
||||
}
|
||||
|
||||
async function handleChange(value) {
|
||||
console.log(`Setting [${id}] set to >`, value)
|
||||
setValue(value)
|
||||
api.settings.set(id, value)
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "switch": {
|
||||
componentProps.defaultChecked = defaultProps.defaultChecked ?? false
|
||||
componentProps.onChange = (e) => {
|
||||
handleChange(e)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const Component = SettingTypeToComponent[type.toLowerCase()]
|
||||
const Render = () => {
|
||||
if (typeof render === "function") {
|
||||
return render(componentProps)
|
||||
}
|
||||
|
||||
return React.createElement(Component, componentProps)
|
||||
}
|
||||
|
||||
return <div
|
||||
className="app_settings-list-item"
|
||||
>
|
||||
<div className="app_settings-list-item-info">
|
||||
<div className="app_settings-list-item-label">
|
||||
<Icon icon={icon} />
|
||||
|
||||
<h2>
|
||||
{name}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="app_settings-list-item-description">
|
||||
<p>
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app_settings-list-item-component">
|
||||
{
|
||||
loading && <antd.Spin />
|
||||
}
|
||||
{
|
||||
!loading && <Render />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
const Settings = () => {
|
||||
return <div className="app_settings">
|
||||
<div className="app_settings-header">
|
||||
<div className="app_settings-header-back">
|
||||
<Icons.MdChevronLeft
|
||||
onClick={() => {
|
||||
app.location.push("/")
|
||||
}}
|
||||
/>
|
||||
Back
|
||||
</div>
|
||||
|
||||
<div className="app_settings-header-title">
|
||||
<Icons.MdSettings />
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app_settings-list">
|
||||
{
|
||||
settingsList.map((setting, index) => {
|
||||
return <SettingItem
|
||||
key={index}
|
||||
setting={setting}
|
||||
/>
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="software_info">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Settings
|
100
src/renderer/src/pages/settings/index.less
Normal file
100
src/renderer/src/pages/settings/index.less
Normal file
@ -0,0 +1,100 @@
|
||||
@import "style/vars.less";
|
||||
|
||||
.app_settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
.app_settings-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 20px;
|
||||
|
||||
.app_settings-header-back {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
svg {
|
||||
color: var(--primary-color);
|
||||
border: 1px solid var(--primary-color);
|
||||
|
||||
border-radius: 100%;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.app_settings-header-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.app_settings-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: var(--background-color-secondary);
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
.app_settings-list-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
align-items: center;
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: mix(#fff, @var-background-color-secondary, 5%);
|
||||
}
|
||||
|
||||
border-radius: 12px;
|
||||
|
||||
padding: 10px;
|
||||
|
||||
.app_settings-list-item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 10px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
.app_settings-list-item-label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.app_settings-list-item-description {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
.app_settings-list-item-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,63 @@
|
||||
import React from "react"
|
||||
import BarLoader from "react-spinners/BarLoader"
|
||||
import { BrowserRouter, Route, Routes, useNavigate } from "react-router-dom"
|
||||
|
||||
import GlobalStateContext from "contexts/global"
|
||||
|
||||
import InstallationsManager from "pages/manager"
|
||||
import PackagesMangerPage from "pages/manager"
|
||||
import SettingsPage from "pages/settings"
|
||||
|
||||
const NavigationController = (props) => {
|
||||
if (!app.location) {
|
||||
app.location = Object()
|
||||
}
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
async function setLocation(to, state = {}) {
|
||||
// clean double slashes
|
||||
to = to.replace(/\/{2,}/g, "/")
|
||||
|
||||
// if state is a number, it's a delay
|
||||
if (typeof state !== "object") {
|
||||
state = {}
|
||||
}
|
||||
|
||||
app.location.last = window.location
|
||||
|
||||
return navigate(to, {
|
||||
state
|
||||
})
|
||||
}
|
||||
|
||||
async function backLocation() {
|
||||
app.location.last = window.location
|
||||
|
||||
if (transitionDuration >= 100) {
|
||||
await new Promise((resolve) => setTimeout(resolve, transitionDuration))
|
||||
}
|
||||
|
||||
return window.history.back()
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
app.location = {
|
||||
last: window.location,
|
||||
push: setLocation,
|
||||
back: backLocation,
|
||||
}
|
||||
}, [])
|
||||
|
||||
return props.children
|
||||
}
|
||||
|
||||
export const InternalRouter = (props) => {
|
||||
return <BrowserRouter>
|
||||
<NavigationController>
|
||||
{props.children}
|
||||
</NavigationController>
|
||||
</BrowserRouter>
|
||||
}
|
||||
|
||||
export const PageRender = () => {
|
||||
const globalState = React.useContext(GlobalStateContext)
|
||||
@ -23,5 +77,8 @@ export const PageRender = () => {
|
||||
</div>
|
||||
}
|
||||
|
||||
return <InstallationsManager />
|
||||
return <Routes>
|
||||
<Route path="/" element={<PackagesMangerPage />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
</Routes>
|
||||
}
|
@ -1,11 +1,7 @@
|
||||
@import "style/reset.css";
|
||||
@import "style/fix.less";
|
||||
|
||||
@var-text-color: #fff;
|
||||
@var-background-color-primary: #424549;
|
||||
@var-background-color-secondary: #1e2124;
|
||||
@var-primary-color: #36d7b7; //#F3B61F;
|
||||
@var-border-color: #a1a2a2;
|
||||
@import "style/vars.less";
|
||||
|
||||
:root {
|
||||
--background-color-primary: @var-background-color-primary;
|
||||
|
5
src/renderer/src/style/vars.less
Normal file
5
src/renderer/src/style/vars.less
Normal file
@ -0,0 +1,5 @@
|
||||
@var-text-color: #fff;
|
||||
@var-background-color-primary: #424549;
|
||||
@var-background-color-secondary: #1e2124;
|
||||
@var-primary-color: #36d7b7; //#F3B61F;
|
||||
@var-border-color: #a1a2a2;
|
Loading…
x
Reference in New Issue
Block a user