From c1a41bec6500d622082e5d4ce36c5d3f80e4bdef Mon Sep 17 00:00:00 2001 From: KingRainbow44 Date: Wed, 6 Jul 2022 01:38:34 -0400 Subject: [PATCH] Push `custom-options` --- .eslintrc.json | 3 +- public/index.html | 2 + public/theme-engine.js | 14 +++ src-tauri/Cargo.lock | 11 +- src-tauri/Cargo.toml | 6 +- src-tauri/tauri.conf.json | 2 +- src/index.tsx | 8 +- src/resources/example-theme/index.json | 34 ++++++ src/ui/components/ServerLaunchSection.css | 2 +- src/ui/components/common/ThemeOptionValue.tsx | 105 ++++++++++++++++++ src/ui/components/menu/Options.tsx | 36 +++++- src/utils/dom.ts | 31 ++++++ src/utils/themes.ts | 28 ++++- 13 files changed, 265 insertions(+), 17 deletions(-) create mode 100644 public/theme-engine.js create mode 100644 src/resources/example-theme/index.json create mode 100644 src/ui/components/common/ThemeOptionValue.tsx create mode 100644 src/utils/dom.ts diff --git a/.eslintrc.json b/.eslintrc.json index 638c08f..1f792fa 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -33,6 +33,7 @@ "semi": [ "error", "never" - ] + ], + "no-explicit-any": "off" } } diff --git a/public/index.html b/public/index.html index 030b8e7..89e366f 100644 --- a/public/index.html +++ b/public/index.html @@ -12,7 +12,9 @@ Cultivation + +
diff --git a/public/theme-engine.js b/public/theme-engine.js new file mode 100644 index 0000000..bfc7eb0 --- /dev/null +++ b/public/theme-engine.js @@ -0,0 +1,14 @@ +/** + * Passes a message through to the React backend. + * @param type The message type. + * @param data The message data. + */ +function passthrough(type, data) { + document.dispatchEvent(new CustomEvent('domMessage', { + type, msg: data + })) +} + +function setConfigValue(key, value) { + passthrough('updateConfig', {setting: key, value}) +} \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f2d5257..4837726 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -740,7 +740,7 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" [[package]] name = "cultivation" -version = "0.1.0" +version = "1.0.1" dependencies = [ "duct", "futures-util", @@ -2103,6 +2103,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "minisign-verify" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" + [[package]] name = "miniz_oxide" version = "0.5.1" @@ -3886,6 +3892,7 @@ checksum = "a34cef4a0ebee0230baaa319b1709c4336f4add550149d2b005a9a5dc5d33617" dependencies = [ "anyhow", "attohttpc", + "base64", "bincode", "cocoa", "dirs-next", @@ -3899,6 +3906,7 @@ dependencies = [ "heck 0.4.0", "http", "ignore", + "minisign-verify", "notify-rust", "objc", "once_cell", @@ -3930,6 +3938,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "windows 0.30.0", + "zip 0.6.2", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 7c90f77..8b84fa8 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "cultivation" -version = "0.1.0" +version = "1.0.1" description = "A custom launcher for anime game." authors = ["KingRainbow44", "SpikeHD"] -license = "" +license = "Apache-2.0" repository = "https://github.com/Grasscutters/Cultivation.git" default-run = "cultivation" edition = "2021" @@ -16,7 +16,7 @@ tauri-build = { version = "1.0.0-rc.8", features = [] } [dependencies] serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.0.0-rc.9", features = ["api-all"] } +tauri = { version = "1.0.0-rc.9", features = ["api-all", "updater"] } # Access system process info. sysinfo = "0.23.12" diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 58af5c8..5ab5241 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -72,7 +72,7 @@ "csp": "default-src 'self' https://asset.localhost; img-src 'self'; img-src https://* asset: https://asset.localhost" }, "updater": { - "active": false, + "active": true, "dialog": true, "endpoints": [ "https://api.grasscutter.io/cultivation/updater?version={{current_version}}", diff --git a/src/index.tsx b/src/index.tsx index e2b5cab..b049f99 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -17,6 +17,7 @@ let isDebug = false; isDebug = await getConfigOption('debug_enabled') }) +// Render the app. root.render( { @@ -25,5 +26,10 @@ root.render( ) +// Enable web vitals if needed. import reportWebVitals from './utils/reportWebVitals' -isDebug && reportWebVitals(console.log) \ No newline at end of file +isDebug && reportWebVitals(console.log) + +// Setup DOM message passing. +import { parseMessageFromDOM } from './utils/dom' +document.addEventListener('domMessage', parseMessageFromDOM) \ No newline at end of file diff --git a/src/resources/example-theme/index.json b/src/resources/example-theme/index.json new file mode 100644 index 0000000..b73503d --- /dev/null +++ b/src/resources/example-theme/index.json @@ -0,0 +1,34 @@ +{ + "name": "Example Theme", + "version": "420.69", + "description": "Show off some of the abilities of the Cultivation theme system", + + "includes": { + "_README": "You can include any amount of CSS and JS files here. Paths are relative to the theme directory.", + + "css": ["/index.css"], + "js": ["/index.js"] + }, + + "settings": [ + { + "label": "Example Setting", + "type": "input", + "className": "Input", + + "data": { + "placeholder": "Enter a value", + "initialValue": "Change this value" + } + }, + { + "label": "Example Setting", + "type": "checkbox", + "className": "Checkbox" + } + ], + + "_README": "These are optional. Including neither will result in the launcher simply using the default background choice.", + "customBackgroundPath": "/background/bg.png", + "customBackgroundURL": "" +} \ No newline at end of file diff --git a/src/ui/components/ServerLaunchSection.css b/src/ui/components/ServerLaunchSection.css index 1ed8762..a5f6850 100644 --- a/src/ui/components/ServerLaunchSection.css +++ b/src/ui/components/ServerLaunchSection.css @@ -33,7 +33,7 @@ background: #fff; } -.BottomSection .CheckboxDisplay { +.BottomSection .CheckboxDisplay { margin-right: 6px; box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.2); } diff --git a/src/ui/components/common/ThemeOptionValue.tsx b/src/ui/components/common/ThemeOptionValue.tsx new file mode 100644 index 0000000..d8bcb4d --- /dev/null +++ b/src/ui/components/common/ThemeOptionValue.tsx @@ -0,0 +1,105 @@ +import React from 'react' +import TextInput from './TextInput' +import Checkbox from './Checkbox' + +/* + * Valid types for the theme option value. + * - input: A text input. + * - dropdown: A select/dropdown input. + * - checkbox: A toggle. + * - button: A button. + */ + +interface IProps { + type: string; + className?: string; + jsCallback?: string; + data: InputSettings; +} + +interface IState { + toggled: boolean +} + +export interface InputSettings { + /* Input. */ + placeholder?: string; + initialValue?: string; + + /* Dropdown. */ + options?: string[]; + + /* Checkbox. */ + toggled?: boolean + id?: string; + + /* Button. */ + text?: string; +} + +export default class ThemeOptionValue extends React.Component { + constructor(props: IProps) { + super(props) + + this.state = { + toggled: false + } + } + + static getDerivedStateFromProps(props: IProps, state: IState) { + return { toggled: props.data.toggled || state.toggled } + } + + async componentDidMount() { + const data = this.props.data + + if(this.props.type == 'checkbox') + this.setState({ toggled: data.toggled || false }) + } + + async onChange() { + // Change toggled state if needed. + if(this.props.type == 'checkbox') + this.setState({ + toggled: !this.state.toggled + }) + + if(!this.props.jsCallback) + return + } + + render() { + const data = this.props.data + + switch(this.props.type) { + case 'input': + return ( +
+ +
+ ) + case 'dropdown': + return ( +
+ +
+ ) + case 'button': + return ( +
+ +
+ ) + default: + return ( +
+ +
+ ) + } + } +} \ No newline at end of file diff --git a/src/ui/components/menu/Options.tsx b/src/ui/components/menu/Options.tsx index 4341fc5..50e6af5 100644 --- a/src/ui/components/menu/Options.tsx +++ b/src/ui/components/menu/Options.tsx @@ -7,11 +7,12 @@ import Tr, { getLanguages, translate } from '../../../utils/language' import { setConfigOption, getConfig, getConfigOption } from '../../../utils/configuration' import Checkbox from '../common/Checkbox' import Divider from './Divider' -import { getThemeList } from '../../../utils/themes' +import { getTheme, getThemeList, ThemeList } from '../../../utils/themes' import * as server from '../../../utils/server' import './Options.css' import BigButton from '../common/BigButton' +import ThemeOptionValue from '../common/ThemeOptionValue' interface IProps { closeFn: () => void; @@ -28,6 +29,8 @@ interface IState { themes: string[] theme: string encryption: boolean + + theme_object: ThemeList|null; } export default class Options extends React.Component { @@ -44,7 +47,9 @@ export default class Options extends React.Component { bg_url_or_path: '', themes: ['default'], theme: '', - encryption: false + encryption: false, + + theme_object: null } this.setGameExec = this.setGameExec.bind(this) @@ -74,7 +79,9 @@ export default class Options extends React.Component { bg_url_or_path: config.customBackground || '', themes: (await getThemeList()).map(t => t.name), theme: config.theme || 'default', - encryption: await translate(encEnabled ? 'options.enabled' : 'options.disabled') + encryption: await translate(encEnabled ? 'options.enabled' : 'options.disabled'), + + theme_object: (await getTheme(config.theme)) }) this.forceUpdate() @@ -124,7 +131,7 @@ export default class Options extends React.Component { } async setCustomBackground(value: string) { - const isUrl = /^(?:http(s)?:\/\/)/gm.test(value) + const isUrl = /^http(s)?:\/\//gm.test(value) if (!value) return await setConfigOption('customBackground', '') @@ -168,6 +175,8 @@ export default class Options extends React.Component { } render() { + const themeSettings = this.state.theme_object?.settings + return ( + + + + + + { + themeSettings ? themeSettings.map((settings, index) => { + return ( +
+
+ {settings.label} +
+
+ +
+
+ ) + }) : null + }
) } diff --git a/src/utils/dom.ts b/src/utils/dom.ts new file mode 100644 index 0000000..f8ba56d --- /dev/null +++ b/src/utils/dom.ts @@ -0,0 +1,31 @@ +import { setConfigOption } from './configuration' + +interface DOMMessage { + type: string + data: ConfigUpdate +} + +interface ConfigUpdate { + setting: string + value: any +} + +/** + * Parses a message received from the DOM. + * @param document The document. + * @param msg The message received from the DOM. + */ +export function parseMessageFromDOM(document: Document, msg: any): void { + msg = msg.detail + + if(!msg || !msg.type || !msg.data) + return + + switch(msg.type) { + case 'updateConfig': + if(!msg.data.setting || !msg.data.value) + return + setConfigOption(msg.data.setting, msg.data.value) + return + } +} \ No newline at end of file diff --git a/src/utils/themes.ts b/src/utils/themes.ts index 6e78587..8562ebf 100644 --- a/src/utils/themes.ts +++ b/src/utils/themes.ts @@ -1,7 +1,9 @@ -import { invoke } from '@tauri-apps/api' -import { dataDir } from '@tauri-apps/api/path' -import { convertFileSrc } from '@tauri-apps/api/tauri' -import { getConfig, setConfigOption } from './configuration' +import {invoke} from '@tauri-apps/api' +import {dataDir} from '@tauri-apps/api/path' +import {convertFileSrc} from '@tauri-apps/api/tauri' +import {getConfig, setConfigOption} from './configuration' + +import {InputSettings} from '../ui/components/common/ThemeOptionValue' interface Theme { name: string @@ -13,6 +15,16 @@ interface Theme { css: string[] js: string[] } + + // Custom settings. + settings?: { + label: string // The setting's label. + type: string // The setting's type. + data: InputSettings // The data for the setting. + + className?: string // The name of the class this setting should take. + jsCallback?: string // The name of the callback method that should be invoked. + }[] customBackgroundURL?: string customBackgroundPath?: string @@ -23,7 +35,7 @@ interface BackendThemeList { path: string } -interface ThemeList extends Theme { +export interface ThemeList extends Theme { path: string } @@ -37,6 +49,7 @@ const defaultTheme = { }, path: 'default' } + export async function getThemeList() { // Do some invoke to backend to get the theme list const themes = await invoke('get_theme_list', { @@ -77,6 +90,11 @@ export async function getTheme(name: string) { return themes.find(t => t.name === name) || defaultTheme } +export async function getSelectedTheme() { + const config = await getConfig() + return await getTheme(config.theme) +} + export async function loadTheme(theme: ThemeList, document: Document) { // Get config, since we will set the custom background in there const config = await getConfig()