From 3ddb89544570fba81f8958b87250073568d3b3fd Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Sat, 30 Jul 2022 23:17:42 +0200 Subject: [PATCH 01/29] Support v2 patches in backend --- main.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index c55689a..8dce0d2 100644 --- a/main.py +++ b/main.py @@ -264,25 +264,43 @@ def __init__(self, theme : Theme, json : dict, name : str): self.json = json self.name = name self.default = json["default"] + self.type = json["type"] if type in json else "dropdown" self.theme = theme self.value = self.default self.injects = [] self.options = {} - for x in json: - if (x == "default"): - continue + self.patchVersion = None + + if "values" in json: # Do we have a v2 or a v1 format? + self.patchVersion = 2 + for x in json["values"]: + self.options[x] = [] + else: + self.patchVersion = 1 + for x in json: + if (x == "default"): + continue - self.options[x] = [] + self.options[x] = [] def check_value(self): if (self.value not in self.options): self.value = self.default + + if (self.type not in ["dropdown", "checkbox", "slider"]): + self.type = "dropdown" + + if (self.type == "checkbox"): + if not ("No" in self.options and "Yes" in self.options): + self.type = "dropdown" async def load(self) -> Result: self.theme.log("ThemePatch.load") for x in self.options: - for y in self.json[x]: - inject = Inject(self.theme.themePath + "/" + y, self.json[x][y], self.theme) + data = self.json[x] if self.patchVersion == 1 else self.json["values"][x] + + for y in data: + inject = Inject(self.theme.themePath + "/" + y, data[y], self.theme) self.injects.append(inject) self.options[x].append(inject) @@ -315,7 +333,8 @@ def to_dict(self) -> dict: "name": self.name, "default": self.default, "value": self.value, - "options": [x for x in self.options] + "options": [x for x in self.options], + "type": self.type, } class RemoteInstall: From 846cc726dfdbbd9a6ce9723db67c79e151ed036d Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Mon, 1 Aug 2022 22:59:01 +0200 Subject: [PATCH 02/29] oops --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 8dce0d2..33030ac 100644 --- a/main.py +++ b/main.py @@ -264,7 +264,7 @@ def __init__(self, theme : Theme, json : dict, name : str): self.json = json self.name = name self.default = json["default"] - self.type = json["type"] if type in json else "dropdown" + self.type = json["type"] if "type" in json else "dropdown" self.theme = theme self.value = self.default self.injects = [] From 991bdd42999c5c527dfff0f567e349ab40add321 Mon Sep 17 00:00:00 2001 From: beebls Date: Mon, 1 Aug 2022 16:00:32 -0600 Subject: [PATCH 03/29] V2 Patch support There is now seperate slider, checkbox, and dropdown patch options, all the functionality is there currently, it just looks a little ugly, I will style it further. --- src/components/ThemePatch.tsx | 99 ++++++++++++++++++++++++++++------- src/{theme.tsx => theme.ts} | 2 + 2 files changed, 81 insertions(+), 20 deletions(-) rename src/{theme.tsx => theme.ts} (94%) diff --git a/src/components/ThemePatch.tsx b/src/components/ThemePatch.tsx index 78e618c..884bd65 100644 --- a/src/components/ThemePatch.tsx +++ b/src/components/ThemePatch.tsx @@ -1,25 +1,84 @@ -import { DropdownItem, PanelSectionRow } from "decky-frontend-lib"; +import { + DropdownItem, + PanelSectionRow, + SliderField, + ToggleField, +} from "decky-frontend-lib"; import * as python from "../python"; -import { VFC } from "react"; +import { useState, VFC } from "react"; import { Patch } from "../theme"; export const ThemePatch: VFC<{ data: Patch }> = ({ data }) => { - return ( - - { - return { data: i, label: x }; - })} - label={`${data.name} of ${data.theme.name}`} - selectedOption={data.index} - onChange={(index) => { - data.index = index.data; - data.value = index.label; - python.execute( - python.setPatchOfTheme(data.theme.name, data.name, data.value) - ); - }} - /> - - ); + // For some reason, the other 2 don't require useStates, the slider does though. + const [sliderValue, setSlider] = useState(data.index); + + switch (data.type) { + case "slider": + return ( + + { + python.execute( + python.setPatchOfTheme( + data.theme.name, + data.name, + data.options[value] + ) + ); + setSlider(value); + + data.index = value; + data.value = data.options[value]; + }} + notchCount={data.options.length} + notchLabels={data.options.map((e, i) => ({ + notchIndex: i, + label: e, + value: i, + }))} + /> + + ); + case "checkbox": + return ( + + { + const newValue = bool ? "Yes" : "No"; + python.execute( + python.setPatchOfTheme(data.theme.name, data.name, newValue) + ); + data.index = data.options.findIndex((e) => e === newValue); + data.value = newValue; + }} + /> + + ); + default: + return ( + + { + return { data: i, label: x }; + })} + label={`${data.name} of ${data.theme.name}`} + selectedOption={data.index} + onChange={(index) => { + data.index = index.data; + data.value = index.label; + python.execute( + python.setPatchOfTheme(data.theme.name, data.name, data.value) + ); + }} + /> + + ); + } }; diff --git a/src/theme.tsx b/src/theme.ts similarity index 94% rename from src/theme.tsx rename to src/theme.ts index a214821..05ec15c 100644 --- a/src/theme.tsx +++ b/src/theme.ts @@ -33,6 +33,7 @@ export class Patch { value: string = ""; options: string[] = []; index: number = 0; + type: string = "dropdown"; constructor(theme: Theme) { this.theme = theme; @@ -43,6 +44,7 @@ export class Patch { this.default = this.data.default; this.value = this.data.value; this.options = this.data.options; + this.type = this.data.type; this.index = this.options.indexOf(this.value); } From 956ce41e65e488341cac114ca4fdaf02d5ae7243 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 2 Aug 2022 00:33:21 +0200 Subject: [PATCH 04/29] Improve error checking --- main.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 33030ac..070df56 100644 --- a/main.py +++ b/main.py @@ -145,7 +145,11 @@ async def load(self) -> Result: if "patches" in self.json: for x in self.json["patches"]: - patch = ThemePatch(self, self.json["patches"][x], x) + try: + patch = ThemePatch(self, self.json["patches"][x], x) + except Exception as e: + continue # Just don't load the patch if it's invalid + result = await patch.load() if not result.success: return result @@ -282,6 +286,9 @@ def __init__(self, theme : Theme, json : dict, name : str): continue self.options[x] = [] + + if self.default not in self.options: + raise Exception(f"In patch '{self.name}', '{self.default}' does not exist as a patch option") def check_value(self): if (self.value not in self.options): @@ -304,6 +311,7 @@ async def load(self) -> Result: self.injects.append(inject) self.options[x].append(inject) + self.check_value() return Result(True) async def inject(self) -> Result: From 7d29b34012f5821c47ec7f60ee53d0e550b6850e Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 2 Aug 2022 00:52:07 +0200 Subject: [PATCH 05/29] Check if patch is already injected before repatching --- main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.py b/main.py index 070df56..0651024 100644 --- a/main.py +++ b/main.py @@ -445,6 +445,9 @@ async def set_patch_of_theme(self, themeName : str, patchName : str, value : str if themePatch is None: return Result(False, f"Did not find patch '{patchName}' for theme '{themeName}'").to_dict() + if (themePatch.value == value): + return Result(True, "Already injected").to_dict() + if (value in themePatch.options): themePatch.value = value From ba455b516c4311fd45b89b4c711db606120d58be Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Tue, 2 Aug 2022 18:29:47 +0200 Subject: [PATCH 06/29] Add manifest_version, check theme.json in first round of load --- main.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/main.py b/main.py index 0651024..85478b1 100644 --- a/main.py +++ b/main.py @@ -7,7 +7,8 @@ from logging import getLogger, basicConfig, INFO, DEBUG pluginManagerUtils = Utilities(None) -Initialized = False +Initialized = False +CSS_LOADER_VER = 2 def createDir(dirPath : str): if (path.exists(dirPath)): @@ -120,6 +121,10 @@ def __init__(self, themePath : str, json : dict, configPath : str = None): self.name = json["name"] self.version = json["version"] if ("version" in json) else "v1.0" self.author = json["author"] if ("author" in json) else "" + self.require = int(json["manifest_version"]) if ("manifest_version" in json) else 1 + + if (CSS_LOADER_VER < self.require): + raise Exception("A newer version of the CssLoader is required to load this theme") self.patches = [] self.injects = [] @@ -133,29 +138,18 @@ def __init__(self, themePath : str, json : dict, configPath : str = None): self.json = json self.logobj = None + if "inject" in self.json: + self.injects = [Inject(self.themePath + "/" + x, self.json["inject"][x], self) for x in self.json["inject"]] + + if "patches" in self.json: + self.patches = [ThemePatch(self, self.json["patches"][x], x) for x in self.json["patches"]] + def log(self, text : str): if self.logobj is not None: self.logobj.info(text) async def load(self) -> Result: self.log("Theme.load") - if "inject" in self.json: - for x in self.json["inject"]: - self.injects.append(Inject(self.themePath + "/" + x, self.json["inject"][x], self)) - - if "patches" in self.json: - for x in self.json["patches"]: - try: - patch = ThemePatch(self, self.json["patches"][x], x) - except Exception as e: - continue # Just don't load the patch if it's invalid - - result = await patch.load() - if not result.success: - return result - - self.patches.append(patch) - if not path.exists(self.configJsonPath): return Result(True) @@ -260,6 +254,7 @@ def to_dict(self) -> dict: "enabled": self.enabled, "patches": [x.to_dict() for x in self.patches], "bundled": self.bundled, + "require": self.require, } @@ -289,6 +284,8 @@ def __init__(self, theme : Theme, json : dict, name : str): if self.default not in self.options: raise Exception(f"In patch '{self.name}', '{self.default}' does not exist as a patch option") + + self.load() def check_value(self): if (self.value not in self.options): @@ -301,8 +298,7 @@ def check_value(self): if not ("No" in self.options and "Yes" in self.options): self.type = "dropdown" - async def load(self) -> Result: - self.theme.log("ThemePatch.load") + def load(self): for x in self.options: data = self.json[x] if self.patchVersion == 1 else self.json["values"][x] @@ -312,7 +308,6 @@ async def load(self) -> Result: self.options[x].append(inject) self.check_value() - return Result(True) async def inject(self) -> Result: self.check_value() @@ -426,6 +421,9 @@ async def get_theme_db_data(self) -> list: async def reload_theme_db_data(self) -> dict: return (await self.remote.load(True)).to_dict() + async def get_backend_version(self) -> int: + return CSS_LOADER_VER + async def set_patch_of_theme(self, themeName : str, patchName : str, value : str) -> dict: theme = None for x in self.themes: From cebc4f587de6eaab326285e29b1a6e4860a92670 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Thu, 4 Aug 2022 17:06:23 +0200 Subject: [PATCH 07/29] Improve logging --- main.py | 78 ++++++++++++++++++++++++++------------------------------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/main.py b/main.py index 85478b1..0a70fce 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,10 @@ pluginManagerUtils = Utilities(None) Initialized = False CSS_LOADER_VER = 2 +Logger = getLogger("CSS_LOADER") + +def Log(text : str): + Logger.info(text) def createDir(dirPath : str): if (path.exists(dirPath)): @@ -23,6 +27,9 @@ class Result: def __init__(self, success : bool, message : str = "Success"): self.success = success self.message = message + + if not self.success: + Log(f"Result failed! {message}") def raise_on_failure(self): if not self.success: @@ -43,12 +50,11 @@ def __init__(self, cssPath : str, tabs : List[str], theme): self.uuids[x] = [] async def load(self) -> Result: - self.theme.log("Inject.load") try: with open(self.cssPath, "r") as fp: self.css = fp.read() - self.theme.log(f"Loaded css at {self.cssPath}") + Log(f"Loaded css at {self.cssPath}") self.css = self.css.replace("\\", "\\\\").replace("`", "\\`") return Result(True) @@ -56,7 +62,6 @@ async def load(self) -> Result: return Result(False, str(e)) async def inject(self, tab : str = None) -> Result: - self.theme.log("Inject.inject") if (tab is None): for x in self.tabs: await self.inject(x) @@ -79,7 +84,7 @@ async def inject(self, tab : str = None) -> Result: if not res["success"]: return Result(False, str(res["result"])) - self.theme.log(f"+{str(res['result'])} @ {tab}") + Log(f"+{str(res['result'])} @ {tab}") self.uuids[tab].append(str(res["result"])) except Exception as e: return Result(False, str(e)) @@ -88,7 +93,6 @@ async def inject(self, tab : str = None) -> Result: return Result(True) async def remove(self, tab : str = None) -> Result: - self.theme.log("Inject.remove") if (tab is None): for x in self.tabs: await self.remove(x) @@ -103,7 +107,7 @@ async def remove(self, tab : str = None) -> Result: try: for x in self.uuids[tab]: - self.theme.log(f"-{x} @ {tab}") + Log(f"-{x} @ {tab}") res = await pluginManagerUtils.remove_css_from_tab(tab, x) #if not res["success"]: # return Result(False, res["result"]) @@ -136,20 +140,14 @@ def __init__(self, themePath : str, json : dict, configPath : str = None): self.enabled = False self.json = json - self.logobj = None if "inject" in self.json: self.injects = [Inject(self.themePath + "/" + x, self.json["inject"][x], self) for x in self.json["inject"]] if "patches" in self.json: self.patches = [ThemePatch(self, self.json["patches"][x], x) for x in self.json["patches"]] - - def log(self, text : str): - if self.logobj is not None: - self.logobj.info(text) async def load(self) -> Result: - self.log("Theme.load") if not path.exists(self.configJsonPath): return Result(True) @@ -177,7 +175,6 @@ async def load(self) -> Result: return Result(True) async def save(self) -> Result: - self.log("Theme.save") createDir(self.configPath) try: @@ -194,7 +191,7 @@ async def save(self) -> Result: return Result(True) async def inject(self) -> Result: - self.log(f"Injecting theme '{self.name}'") + Log(f"Injecting theme '{self.name}'") for x in self.injects: result = await x.inject() if not result.success: @@ -210,7 +207,7 @@ async def inject(self) -> Result: return Result(True) async def remove(self) -> Result: - self.log("Theme.remove") + Log(f"Removing theme '{self.name}'") for x in self.get_all_injects(): result = await x.remove() if not result.success: @@ -221,8 +218,6 @@ async def remove(self) -> Result: return Result(True) async def delete(self) -> Result: - self.log("Theme.delete") - if (self.bundled): return Result(False, "Can't delete a bundled theme") @@ -238,7 +233,6 @@ async def delete(self) -> Result: return Result(True) def get_all_injects(self) -> List[Inject]: - self.log("Theme.get_all_injects") injects = [] injects.extend(self.injects) for x in self.patches: @@ -311,11 +305,9 @@ def load(self): async def inject(self) -> Result: self.check_value() - self.theme.log(f"Injecting patch '{self.name}' of theme '{self.theme.name}'") + Log(f"Injecting patch '{self.name}' of theme '{self.theme.name}'") for x in self.options[self.value]: - self.theme.log(x) result = await x.inject() - self.theme.log(result.message) if not result.success: return result @@ -323,7 +315,7 @@ async def inject(self) -> Result: async def remove(self) -> Result: self.check_value() - self.theme.log("ThemePatch.remove") + Log(f"Removing patch '{self.name}' of theme '{self.theme.name}'") for x in self.injects: result = await x.remove() if not result.success: @@ -362,7 +354,7 @@ async def load(self, force : bool = False) -> Result: if force or (self.themes == []): response = await self.run(f"curl {self.themeDb} -L") self.themes = json.loads(response) - self.plugin.log.info(self.themes) + Log(f"Got {len(self.themes)} from the themedb") except Exception as e: return Result(False, str(e)) @@ -386,11 +378,11 @@ async def install(self, uuid : str) -> Result: tempDir = tempfile.TemporaryDirectory() - print(f"Downloading {theme['download_url']} to {tempDir.name}...") + Log(f"Downloading {theme['download_url']} to {tempDir.name}...") themeZipPath = os.path.join(tempDir.name, 'theme.zip') await self.run(f"curl \"{theme['download_url']}\" -L -o \"{themeZipPath}\"") - print(f"Unzipping {themeZipPath}") + Log(f"Unzipping {themeZipPath}") await self.run(f"unzip -o \"{themeZipPath}\" -d /home/deck/homebrew/themes") tempDir.cleanup() @@ -484,6 +476,7 @@ async def delete_theme(self, themeName : str) -> dict: return Result(True).to_dict() async def _inject_test_element(self, tab : str) -> Result: + attempt = 0 while True: if await self._check_test_element(self, tab): return Result(True) @@ -500,6 +493,11 @@ async def _inject_test_element(self, tab : str) -> Result: except: pass + attempt += 1 + + if (attempt >= 6): + return Result(False, f"Inject into tab '{tab}' was attempted 6 times, stopping") + await asyncio.sleep(1) @@ -524,28 +522,26 @@ async def _parse_themes(self, themesDir : str, configDir : str = None): if not path.exists(themeDataPath): continue - self.log.info(f"Analyzing theme {x}") + Log(f"Analyzing theme {x}") try: with open(themeDataPath, "r") as fp: theme = json.load(fp) - self.log.info(theme) themeData = Theme(themePath, theme, configPath) if (themeData.name not in [x.name for x in self.themes]): self.themes.append(themeData) - self.log.info(f"Adding theme {themeData.name}") + Log(f"Adding theme {themeData.name}") except Exception as e: - self.log.warn(f"Exception while parsing a theme: {e}") # Couldn't properly parse everything + Log(f"Exception while parsing a theme: {e}") # Couldn't properly parse everything async def _cache_lists(self): self.injects = [] self.tabs = [] for x in self.themes: - x.logobj = self.log injects = x.get_all_injects() self.injects.extend(injects) for y in injects: @@ -558,19 +554,19 @@ async def _check_tabs(self): await asyncio.sleep(3) for x in self.tabs: try: - self.log.info(f"Checking if tab {x} is still injected...") + # Log(f"Checking if tab {x} is still injected...") if not await self._check_test_element(self, x): - self.log.info(f"Tab {x} is not injected, reloading...") + Log(f"Tab {x} is not injected, reloading...") await self._inject_test_element(self, x) for y in self.injects: if y.enabled: (await y.inject(x)).raise_on_failure() except Exception as e: - self.log.info(f":( {str(e)}") + Log(f":( {str(e)}") pass async def _load(self): - self.log.info("Loading themes...") + Log("Loading themes...") self.themes = [] themesPath = "/home/deck/homebrew/themes" @@ -585,10 +581,8 @@ async def _load(self): async def _load_stage_2(self): for x in self.themes: - self.log.info(f"Loading theme {x.name}") - res = await x.load() - if not res.success: - self.log(res.message) + Log(f"Loading theme {x.name}") + await x.load() await self._cache_lists(self) @@ -599,16 +593,14 @@ async def _main(self): Initialized = True - self.log = getLogger("CSS_LOADER") self.themes = [] - self.log.info("Hello world!") + Log("Initializing css loader...") self.remote = RemoteInstall(self) - response = await self.remote.load() - if not response.success: - self.log.info(f":( {response.message}") + await self.remote.load() await self._load(self) await self._inject_test_element(self, "SP") await self._load_stage_2(self) + Log(f"Initialised css loader. Found {len(self.themes)} themes, which inject into {len(self.tabs)} tabs ({self.tabs}). Total {len(self.injects)} injects") await self._check_tabs(self) \ No newline at end of file From dc0ba37ea63c9a0c1c3f1300acc4279d849cd398 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Fri, 5 Aug 2022 00:13:28 +0200 Subject: [PATCH 08/29] oops --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 0a70fce..b89824e 100644 --- a/main.py +++ b/main.py @@ -396,7 +396,7 @@ async def get_themes(self) -> list: return [x.to_dict() for x in self.themes] async def set_theme_state(self, name : str, state : bool) -> dict: - self.log.info(f"Setting state for {name} to {state}") + Log(f"Setting state for {name} to {state}") for x in self.themes: if (x.name == name): result = await x.inject() if state else await x.remove() From 0de882f5a69ae9438cbbf87ff60801767793bd9e Mon Sep 17 00:00:00 2001 From: beebls Date: Fri, 5 Aug 2022 14:03:21 -0600 Subject: [PATCH 09/29] Remove "of Theme Name" description for theme patches Simple visual change --- src/components/ThemePatch.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ThemePatch.tsx b/src/components/ThemePatch.tsx index 884bd65..2953b3f 100644 --- a/src/components/ThemePatch.tsx +++ b/src/components/ThemePatch.tsx @@ -17,7 +17,7 @@ export const ThemePatch: VFC<{ data: Patch }> = ({ data }) => { return ( = ({ data }) => { return ( { const newValue = bool ? "Yes" : "No"; @@ -68,7 +68,7 @@ export const ThemePatch: VFC<{ data: Patch }> = ({ data }) => { rgOptions={data.options.map((x, i) => { return { data: i, label: x }; })} - label={`${data.name} of ${data.theme.name}`} + label={`${data.name}`} selectedOption={data.index} onChange={(index) => { data.index = index.data; From 50e13baf6d67485a583ca8a315d58d6c5f3b0bb2 Mon Sep 17 00:00:00 2001 From: beebls Date: Fri, 5 Aug 2022 15:37:38 -0600 Subject: [PATCH 10/29] V2 Patch Formatting & Updates to filtering themes - Theme patches are now indented from their theme name - Theme names now no longer have a bottom separator diving them from their patches - Theme browser now has the option to filter by "Installed Only" - Theme browser search now also searches for author -Various wording tweaks --- src/components/ThemePatch.tsx | 18 +++++++++---- src/components/ThemeToggle.tsx | 8 +++++- src/theme-manager/ThemeBrowserPage.tsx | 37 +++++++++++++++++--------- 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/components/ThemePatch.tsx b/src/components/ThemePatch.tsx index 2953b3f..713cd28 100644 --- a/src/components/ThemePatch.tsx +++ b/src/components/ThemePatch.tsx @@ -8,16 +8,23 @@ import * as python from "../python"; import { useState, VFC } from "react"; import { Patch } from "../theme"; -export const ThemePatch: VFC<{ data: Patch }> = ({ data }) => { +export const ThemePatch: VFC<{ + data: Patch; + index: number; + fullArr: Patch[]; +}> = ({ data, index, fullArr }) => { // For some reason, the other 2 don't require useStates, the slider does though. const [sliderValue, setSlider] = useState(data.index); + const bottomSeparatorValue = fullArr.length - 1 === index ? undefined : false; + switch (data.type) { case "slider": return ( = ({ data }) => { return ( { const newValue = bool ? "Yes" : "No"; @@ -65,10 +72,11 @@ export const ThemePatch: VFC<{ data: Patch }> = ({ data }) => { return ( { return { data: i, label: x }; })} - label={`${data.name}`} selectedOption={data.index} onChange={(index) => { data.index = index.data; diff --git a/src/components/ThemeToggle.tsx b/src/components/ThemeToggle.tsx index 51d3597..0d5ae69 100644 --- a/src/components/ThemeToggle.tsx +++ b/src/components/ThemeToggle.tsx @@ -13,6 +13,9 @@ export const ThemeToggle: VFC<{ data: Theme; setThemeList: any }> = ({ <> 0 ? false : undefined + } checked={data.checked} label={data.name} description={data.description} @@ -23,7 +26,10 @@ export const ThemeToggle: VFC<{ data: Theme; setThemeList: any }> = ({ }} /> - {data.checked && data.patches.map((x) => )} + {data.checked && + data.patches.map((x, i, arr) => ( + + ))} ); }; diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 9cc48f0..6e9bff9 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -31,9 +31,12 @@ export const ThemeBrowserPage: VFC = () => { const searchFilter = (e: browseThemeEntry) => { // This filter just implements the search stuff if (searchFieldValue.length > 0) { + // Convert the theme and search to lowercase so that it's not case-sensitive if ( - // Convert the theme name and search to lowercase so that it's not case-sensitive - !e.name.toLowerCase().includes(searchFieldValue.toLowerCase()) + // This checks for the theme name + !e.name.toLowerCase().includes(searchFieldValue.toLowerCase()) && + // This checks for the author name + !e.author.toLowerCase().includes(searchFieldValue.toLowerCase()) ) { // return false just means it won't show in the list return false; @@ -55,15 +58,16 @@ export const ThemeBrowserPage: VFC = () => { const [selectedTarget, setTarget] = useState({ data: 1, - label: "Any", + label: "All", }); const targetOptions = useMemo((): DropdownOption[] => { const uniqueTargets = new Set( themeArr.filter(searchFilter).map((e) => e.target) ); return [ - { data: 1, label: "Any" }, - ...[...uniqueTargets].map((e, i) => ({ data: i + 2, label: e })), + { data: 1, label: "All" }, + { data: 2, label: "Installed Only" }, + ...[...uniqueTargets].map((e, i) => ({ data: i + 3, label: e })), ]; }, [themeArr, searchFilter]); @@ -152,14 +156,14 @@ export const ThemeBrowserPage: VFC = () => { <> setSort(e.data)} /> { {themeArr .filter(searchFilter) - .filter((e: browseThemeEntry) => - selectedTarget.label === "Any" - ? true - : e.target === selectedTarget.label - ) + .filter((e: browseThemeEntry) => { + if (selectedTarget.label === "All") { + return true; + } else if (selectedTarget.label === "Installed Only") { + const strValue = checkIfThemeInstalled(e); + return strValue === "installed" || strValue === "outdated"; + } else { + return e.target === selectedTarget.label; + } + }) .sort((a, b) => { // This handles the sort option the user has chosen - // 1: A-Z, 2: Z-A, 3: New-Old, 4: Old-New switch (selectedSort) { case 2: + // Z-A // localeCompare just sorts alphabetically return b.name.localeCompare(a.name); case 3: + // New-Old return ( new Date(b.last_changed).valueOf() - new Date(a.last_changed).valueOf() ); case 4: + // Old-New return ( new Date(a.last_changed).valueOf() - new Date(b.last_changed).valueOf() From 6d0170c64a5bb368a7b588fed075cb7a5b55bd76 Mon Sep 17 00:00:00 2001 From: beebls Date: Fri, 5 Aug 2022 16:13:07 -0600 Subject: [PATCH 11/29] Minor fixes & manifest_version compatibility check - Popup label of patch dropdown now doesn't show the arrow icon - Theme browser page now hides themes with an incompatible manifest version. - Renamed a couple things. --- src/components/ThemePatch.tsx | 1 + src/customTypes.ts | 1 + src/python.ts | 81 +++++++++++++++----------- src/theme-manager/ThemeBrowserPage.tsx | 18 +++++- 4 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/components/ThemePatch.tsx b/src/components/ThemePatch.tsx index 713cd28..618784d 100644 --- a/src/components/ThemePatch.tsx +++ b/src/components/ThemePatch.tsx @@ -74,6 +74,7 @@ export const ThemePatch: VFC<{ { return { data: i, label: x }; })} diff --git a/src/customTypes.ts b/src/customTypes.ts index 4e92201..38e134d 100644 --- a/src/customTypes.ts +++ b/src/customTypes.ts @@ -7,6 +7,7 @@ export interface browseThemeEntry { version: string; last_changed: string; target: string; + manifest_version: number; } export interface localThemeEntry { author: string; diff --git a/src/python.ts b/src/python.ts index f2ca584..5dfca45 100644 --- a/src/python.ts +++ b/src/python.ts @@ -4,60 +4,75 @@ import { ServerAPI } from "decky-frontend-lib"; var server: ServerAPI | undefined = undefined; export function resolve(promise: Promise, setter: any) { - (async function () { - let data = await promise; - if (data.success) { - console.debug("Got resolved", data, "promise", promise); - setter(data.result); - } else { - console.warn("Resolve failed:", data, "promise", promise); - } - })(); + (async function () { + let data = await promise; + if (data.success) { + console.debug("Got resolved", data, "promise", promise); + setter(data.result); + } else { + console.warn("Resolve failed:", data, "promise", promise); + } + })(); } export function execute(promise: Promise) { - (async function () { - let data = await promise; - if (data.success) { - console.debug("Got executed", data, "promise", promise); - } else { - console.warn("Execute failed:", data, "promise", promise); - } - })(); + (async function () { + let data = await promise; + if (data.success) { + console.debug("Got executed", data, "promise", promise); + } else { + console.warn("Execute failed:", data, "promise", promise); + } + })(); } export function setServer(s: ServerAPI) { - server = s; + server = s; } export function getThemes(): Promise { - return server!.callPluginMethod("get_themes", {}) + return server!.callPluginMethod("get_themes", {}); } -export function setThemeState(name : string, state : boolean): Promise { - return server!.callPluginMethod("set_theme_state", {"name": name, "state": state}) +export function setThemeState(name: string, state: boolean): Promise { + return server!.callPluginMethod("set_theme_state", { + name: name, + state: state, + }); } export function reset(): Promise { - return server!.callPluginMethod("reset", {}) + return server!.callPluginMethod("reset", {}); } -export function setPatchOfTheme(themeName : string, patchName : string, value : string) : Promise { - return server!.callPluginMethod("set_patch_of_theme", {"themeName": themeName, "patchName": patchName, "value": value}) +export function setPatchOfTheme( + themeName: string, + patchName: string, + value: string +): Promise { + return server!.callPluginMethod("set_patch_of_theme", { + themeName: themeName, + patchName: patchName, + value: value, + }); } -export function downloadTheme(uuid : string) : Promise { - return server!.callPluginMethod("download_theme", {"uuid": uuid}) +export function downloadTheme(uuid: string): Promise { + return server!.callPluginMethod("download_theme", { uuid: uuid }); } -export function getThemeDbData() : Promise { - return server!.callPluginMethod("get_theme_db_data", {}) +export function getThemeDbData(): Promise { + return server!.callPluginMethod("get_theme_db_data", {}); } -export function reloadThemeDbData() : Promise { - return server!.callPluginMethod("reload_theme_db_data", {}) +export function reloadThemeDbData(): Promise { + return server!.callPluginMethod("reload_theme_db_data", {}); } -export function deleteTheme(themeName : string) : Promise { - return server!.callPluginMethod("delete_theme", {"themeName": themeName}) -} \ No newline at end of file +export function deleteTheme(themeName: string): Promise { + return server!.callPluginMethod("delete_theme", { themeName: themeName }); +} + +export function getBackendVersion(): Promise { + return server!.callPluginMethod("get_backend_version", {}); +} diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 6e9bff9..59ebf5b 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -28,7 +28,16 @@ export const ThemeBrowserPage: VFC = () => { // This is used to disable buttons during a theme install const [isInstalling, setInstalling] = useState(false); + const [backendVersion, setBackendVer] = useState(2); + function reloadBackendVer() { + python.resolve(python.getBackendVersion(), setBackendVer); + } + const searchFilter = (e: browseThemeEntry) => { + // This means only compatible themes will show up, newer ones won't + if (e.manifest_version > backendVersion) { + return false; + } // This filter just implements the search stuff if (searchFieldValue.length > 0) { // Convert the theme and search to lowercase so that it's not case-sensitive @@ -72,6 +81,7 @@ export const ThemeBrowserPage: VFC = () => { }, [themeArr, searchFilter]); function reloadThemes() { + reloadBackendVer(); // Reloads the theme database python.resolve(python.reloadThemeDbData(), () => { python.resolve(python.getThemeDbData(), setThemeArr); @@ -148,6 +158,7 @@ export const ThemeBrowserPage: VFC = () => { // Runs upon opening the page useEffect(() => { + reloadBackendVer(); getThemeDb(); getInstalledThemes(); }, []); @@ -158,14 +169,14 @@ export const ThemeBrowserPage: VFC = () => { setSort(e.data)} /> setTarget(e)} /> @@ -180,6 +191,7 @@ export const ThemeBrowserPage: VFC = () => { {/* I wrap everything in a Focusable, because that ensures that the dpad/stick navigation works correctly */} {themeArr + // searchFilter also includes backend version check .filter(searchFilter) .filter((e: browseThemeEntry) => { if (selectedTarget.label === "All") { @@ -253,7 +265,7 @@ export const ThemeBrowserPage: VFC = () => { }}> {e.name} - {selectedTarget.label === "Any" && ( + {selectedTarget.label === "All" && ( Date: Sat, 6 Aug 2022 22:07:13 -0600 Subject: [PATCH 12/29] Visual tweaks to Theme Browser page to fix multi-line titles - Install button now forces itself to the bottom of a theme entry - Title text now truncates (blahbla...) if it's longer than 1 line - Title font size reduced from 1.5em (24px) to 1.25em (20px) --- src/theme-manager/ThemeBrowserPage.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 59ebf5b..378e4b6 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -259,9 +259,15 @@ export const ThemeBrowserPage: VFC = () => { {e.name} @@ -318,6 +324,7 @@ export const ThemeBrowserPage: VFC = () => {
From af9be328985b6b5c49b8d03b8ed179c32113c3d5 Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Mon, 8 Aug 2022 15:24:11 +0200 Subject: [PATCH 13/29] Attempt to fix reinjection issue --- main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index b89824e..068e397 100644 --- a/main.py +++ b/main.py @@ -73,6 +73,7 @@ async def inject(self, tab : str = None) -> Result: if (len(self.uuids[tab]) > 0): await self.remove(tab) + self.enabled = True # In case the below code fails, it will never be re-injected unless it's still enabled if (self.css is None): result = await self.load() @@ -495,8 +496,8 @@ async def _inject_test_element(self, tab : str) -> Result: attempt += 1 - if (attempt >= 6): - return Result(False, f"Inject into tab '{tab}' was attempted 6 times, stopping") + if (attempt >= 3): + return Result(False, f"Inject into tab '{tab}' was attempted 3 times, stopping") await asyncio.sleep(1) @@ -602,5 +603,5 @@ async def _main(self): await self._inject_test_element(self, "SP") await self._load_stage_2(self) - Log(f"Initialised css loader. Found {len(self.themes)} themes, which inject into {len(self.tabs)} tabs ({self.tabs}). Total {len(self.injects)} injects") + Log(f"Initialized css loader. Found {len(self.themes)} themes, which inject into {len(self.tabs)} tabs ({self.tabs}). Total {len(self.injects)} injects, {len([x for x in self.injects if x.enabled])} injected") await self._check_tabs(self) \ No newline at end of file From 09984185e8f3e3915178241ee65ef0563d011cbc Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Mon, 8 Aug 2022 23:49:26 +0200 Subject: [PATCH 14/29] Add dummy function to python backend --- main.py | 3 +++ src/python.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/main.py b/main.py index 068e397..378f413 100644 --- a/main.py +++ b/main.py @@ -339,6 +339,9 @@ def __init__(self, plugin): self.plugin = plugin self.themes = [] + async def dummy_function(self) -> bool: + return True + async def run(self, command : str) -> str: proc = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, diff --git a/src/python.ts b/src/python.ts index 5dfca45..49b790c 100644 --- a/src/python.ts +++ b/src/python.ts @@ -76,3 +76,7 @@ export function deleteTheme(themeName: string): Promise { export function getBackendVersion(): Promise { return server!.callPluginMethod("get_backend_version", {}); } + +export function dummyFunction(): Promise { + return server!.callPluginMethod("dummy_function", {}); +} \ No newline at end of file From a4f5db5a7296b4eaadfda86b1979bdaaec419de7 Mon Sep 17 00:00:00 2001 From: beebls Date: Tue, 9 Aug 2022 15:37:06 -0600 Subject: [PATCH 15/29] moved dummy_function to correct place in python --- main.py | 7 ++++--- src/index.tsx | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 378f413..50dbe5b 100644 --- a/main.py +++ b/main.py @@ -339,9 +339,6 @@ def __init__(self, plugin): self.plugin = plugin self.themes = [] - async def dummy_function(self) -> bool: - return True - async def run(self, command : str) -> str: proc = await asyncio.create_subprocess_shell(command, stdout=asyncio.subprocess.PIPE, @@ -396,6 +393,10 @@ async def install(self, uuid : str) -> Result: return Result(True) class Plugin: + + async def dummy_function(self) -> bool: + return True + async def get_themes(self) -> list: return [x.to_dict() for x in self.themes] diff --git a/src/index.tsx b/src/index.tsx index 6a9de6a..8fb67fd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -8,7 +8,7 @@ import { SidebarNavigation, Router, } from "decky-frontend-lib"; -import { VFC } from "react"; +import { useEffect, useState, VFC } from "react"; import * as python from "./python"; import { RiPaintFill } from "react-icons/ri"; @@ -31,6 +31,8 @@ const Content: VFC<{ serverAPI: ServerAPI }> = () => { // setThemeList is a function that takes the raw data from the python function and then formats it with init and generate functions // This still exists, it just has been moved into the CssLoaderState class' setter function, so it now happens automatically + const [dummyFuncResult, setDummyResult] = useState(false); + const reload = function () { python.resolve(python.getThemes(), setThemeList); }; @@ -40,6 +42,18 @@ const Content: VFC<{ serverAPI: ServerAPI }> = () => { reload(); } + function dummyFuncTest() { + python.resolve(python.dummyFunction(), setDummyResult); + } + + useEffect(() => { + dummyFuncTest(); + }, []); + + useEffect(() => { + console.log(dummyFuncResult); + }); + return ( From 0406233e9bda8701a012d92035d8f7823c2aa053 Mon Sep 17 00:00:00 2001 From: beebls Date: Wed, 10 Aug 2022 12:42:20 -0600 Subject: [PATCH 16/29] Add dummy function integrity check, and the really crappy beginnings of detailed view --- src/index.tsx | 43 ++-- src/theme-manager/ThemeBrowserPage.tsx | 298 ++++++++++++++++--------- 2 files changed, 218 insertions(+), 123 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 8fb67fd..7a3a0eb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -35,6 +35,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = () => { const reload = function () { python.resolve(python.getThemes(), setThemeList); + dummyFuncTest(); }; if (firstTime) { @@ -50,25 +51,33 @@ const Content: VFC<{ serverAPI: ServerAPI }> = () => { dummyFuncTest(); }, []); - useEffect(() => { - console.log(dummyFuncResult); - }); - return ( - - { - Router.CloseSideMenus(); - Router.Navigate("/theme-manager"); - }}> - Manage Themes - - - {themeList.map((x) => ( - - ))} + {dummyFuncResult ? ( + <> + + { + Router.CloseSideMenus(); + Router.Navigate("/theme-manager"); + }}> + Manage Themes + + + {themeList.map((x) => ( + + ))} + + ) : ( + + + CssLoader failed to initialize, try reloading, and if that doesn't + work, try restarting your deck. + + + )} + { localThemeList: installedThemes, setLocalThemeList: setInstalledThemes, } = useCssLoaderState(); + + const [currentExpandedTheme, setCurExpandedTheme] = useState< + browseThemeEntry | undefined + >(undefined); + const [searchFieldValue, setSearchValue] = useState(""); // This is used to disable buttons during a theme install @@ -163,6 +168,92 @@ export const ThemeBrowserPage: VFC = () => { getInstalledThemes(); }, []); + // if theres no theme in the detailed view + if (currentExpandedTheme) { + // This returns 'installed', 'outdated', or 'uninstalled' + const installStatus = checkIfThemeInstalled(currentExpandedTheme); + return ( + <> +
+
+
+
+ + {currentExpandedTheme.name} + +
+ {currentExpandedTheme.author} +
+ {currentExpandedTheme.target} +
+ {currentExpandedTheme.version} +
+ +
+ { + installTheme(currentExpandedTheme.id); + }}> + + {calcButtonText(installStatus)} + + +
+
+ +
+ { + setCurExpandedTheme(undefined); + }}> + + Back + + +
+
+
+
+
+
+ + {currentExpandedTheme?.description || "No Description Provided"} + +
+
+ + ); + } return ( <> @@ -228,134 +319,129 @@ export const ThemeBrowserPage: VFC = () => { } }) .map((e: browseThemeEntry) => { - const installStatus = checkIfThemeInstalled(e); return ( // The outer 2 most divs are the background darkened/blurred image, and everything inside is the text/image/buttons -
+ <>
- - {e.name} - - {selectedTarget.label === "All" && ( - - {e.target} - - )}
-
- {e.author} + {e.name} - + {e.target} + + )} +
+
- {e.version} - -
-
- -
- { - installTheme(e.id); + {e.author} + + + {e.version} + +
+
+ +
- - {calcButtonText(installStatus)} - - -
-
+ setCurExpandedTheme(e)}> + + See More + + +
+
+
-
+ ); })} From 65631310154d1f1458502f7a786d21a6cc79e703 Mon Sep 17 00:00:00 2001 From: EMERALD Date: Wed, 10 Aug 2022 15:00:57 -0500 Subject: [PATCH 17/29] Stylize theme detailed view --- src/theme-manager/ThemeBrowserPage.tsx | 132 ++++++++++++++----------- 1 file changed, 77 insertions(+), 55 deletions(-) diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 01aa913..cba39d4 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -175,33 +175,38 @@ export const ThemeBrowserPage: VFC = () => { return ( <>
-
+
-
- - {currentExpandedTheme.name} - -
- {currentExpandedTheme.author} -
- {currentExpandedTheme.target} -
- {currentExpandedTheme.version} +
+
+ + {currentExpandedTheme.name} + + {currentExpandedTheme.author} + {currentExpandedTheme.target} + {currentExpandedTheme.version} +
{ paddingBottom: "0px", // Filter is used to color the button blue for update filter: calcButtonColor(installStatus), - }}> + }} + > { installTheme(currentExpandedTheme.id); - }}> - + }} + > + {calcButtonText(installStatus)} @@ -224,19 +231,21 @@ export const ThemeBrowserPage: VFC = () => {
+ }} + > { setCurExpandedTheme(undefined); - }}> - + }} + > + Back @@ -245,9 +254,11 @@ export const ThemeBrowserPage: VFC = () => {
-
+
- {currentExpandedTheme?.description || "No Description Provided"} + {currentExpandedTheme?.description || ( + No description provided. + )}
@@ -258,23 +269,23 @@ export const ThemeBrowserPage: VFC = () => { <> setSort(e.data)} /> setTarget(e)} /> setSearchValue(e.target.value)} /> @@ -323,7 +334,7 @@ export const ThemeBrowserPage: VFC = () => { // The outer 2 most divs are the background darkened/blurred image, and everything inside is the text/image/buttons <>
{ marginLeft: "10px", marginRight: "10px", marginBottom: "20px", - }}> + }} + >
{ width: "100%", height: "100%", borderRadius: "3px", - }}> + }} + > { overflow: "hidden", textOverflow: "ellipsis", width: "90%", - }}> + }} + > {e.name} {selectedTarget.label === "All" && ( + }} + > {e.target} )}
{ }} />
+ }} + > + }} + > {e.author} + }} + > {e.version}
+ }} + >
+ }} + > setCurExpandedTheme(e)}> - + onClick={() => setCurExpandedTheme(e)} + > + See More @@ -447,10 +468,11 @@ export const ThemeBrowserPage: VFC = () => { { reloadThemes(); - }}> + }} + > Reload Themes From 317132a8150130a3f0f0088eaf7f5684711b91d8 Mon Sep 17 00:00:00 2001 From: EMERALD Date: Wed, 10 Aug 2022 15:11:20 -0500 Subject: [PATCH 18/29] Add description to browseThemeEntry --- src/customTypes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/customTypes.ts b/src/customTypes.ts index 38e134d..437bc15 100644 --- a/src/customTypes.ts +++ b/src/customTypes.ts @@ -3,6 +3,7 @@ export interface browseThemeEntry { download_url: string; id: string; name: string; + description: string; preview_image: string; version: string; last_changed: string; From c32cc0cc6d94319684235f938e853b2873f0c06f Mon Sep 17 00:00:00 2001 From: beebls Date: Wed, 10 Aug 2022 15:08:38 -0600 Subject: [PATCH 19/29] Move installing state & currentExpandedTheme to globalState, and add a prettierconfig --- .prettierrc | 7 + src/index.tsx | 20 ++- src/state/CssLoaderState.tsx | 31 +++- src/theme-manager/ExpandedView.tsx | 214 +++++++++++++++++++++++++ src/theme-manager/ThemeBrowserPage.tsx | 107 ++++++------- 5 files changed, 313 insertions(+), 66 deletions(-) create mode 100644 .prettierrc create mode 100644 src/theme-manager/ExpandedView.tsx diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..bcc5500 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": false, + "tabWidth": 2, + "semi": true, + "trailingComma": "es5" + "bracketSameLine": false +} diff --git a/src/index.tsx b/src/index.tsx index 7a3a0eb..bbdfffc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -52,16 +52,17 @@ const Content: VFC<{ serverAPI: ServerAPI }> = () => { }, []); return ( - + {dummyFuncResult ? ( <> { Router.CloseSideMenus(); Router.Navigate("/theme-manager"); - }}> + }} + > Manage Themes @@ -80,10 +81,11 @@ const Content: VFC<{ serverAPI: ServerAPI }> = () => { { python.resolve(python.reset(), () => reload()); - }}> + }} + > Reload themes @@ -94,7 +96,7 @@ const Content: VFC<{ serverAPI: ServerAPI }> = () => { const ThemeManagerRouter: VFC = () => { return ( { )); + // serverApi.routerHook.addRoute("theme-manager-expanded-view", () => ( + // + + // + // )) + return { title:
Css Loader
, content: ( diff --git a/src/state/CssLoaderState.tsx b/src/state/CssLoaderState.tsx index c7900c9..33b83d5 100644 --- a/src/state/CssLoaderState.tsx +++ b/src/state/CssLoaderState.tsx @@ -5,6 +5,8 @@ import { Theme } from "../theme"; interface PublicCssLoaderState { localThemeList: Theme[]; browseThemeList: browseThemeEntry[]; + isInstalling: boolean; + currentExpandedTheme: browseThemeEntry | undefined; } // The localThemeEntry interface refers to the theme data as given by the python function, the Theme class refers to a theme after it has been formatted and the generate function has been added @@ -12,12 +14,16 @@ interface PublicCssLoaderState { interface PublicCssLoaderContext extends PublicCssLoaderState { setLocalThemeList(listArr: localThemeEntry[]): void; setBrowseThemeList(listArr: browseThemeEntry[]): void; + setInstalling(bool: boolean): void; + setCurExpandedTheme(theme: browseThemeEntry | undefined): void; } // This class creates the getter and setter functions for all of the global state data. export class CssLoaderState { private localThemeList: Theme[] = []; private browseThemeList: browseThemeEntry[] = []; + private isInstalling: boolean = false; + private currentExpandedTheme: browseThemeEntry | undefined = undefined; // You can listen to this eventBus' 'stateUpdate' event and use that to trigger a useState or other function that causes a re-render public eventBus = new EventTarget(); @@ -26,6 +32,8 @@ export class CssLoaderState { return { localThemeList: this.localThemeList, browseThemeList: this.browseThemeList, + isInstalling: this.isInstalling, + currentExpandedTheme: this.currentExpandedTheme, }; } @@ -49,6 +57,16 @@ export class CssLoaderState { this.forceUpdate(); } + setInstalling(bool: boolean) { + this.isInstalling = bool; + this.forceUpdate(); + } + + setCurExpandedTheme(theme: browseThemeEntry | undefined) { + this.currentExpandedTheme = theme; + this.forceUpdate(); + } + private forceUpdate() { this.eventBus.dispatchEvent(new Event("stateUpdate")); } @@ -85,10 +103,21 @@ export const CssLoaderContextProvider: FC = ({ cssLoaderStateClass.setLocalThemeList(listArr); const setBrowseThemeList = (listArr: browseThemeEntry[]) => cssLoaderStateClass.setBrowseThemeList(listArr); + const setInstalling = (bool: boolean) => + cssLoaderStateClass.setInstalling(bool); + const setCurExpandedTheme = (theme: browseThemeEntry | undefined) => + cssLoaderStateClass.setCurExpandedTheme(theme); return ( + value={{ + ...publicState, + setLocalThemeList, + setBrowseThemeList, + setInstalling, + setCurExpandedTheme, + }} + > {children} ); diff --git a/src/theme-manager/ExpandedView.tsx b/src/theme-manager/ExpandedView.tsx new file mode 100644 index 0000000..f2b0610 --- /dev/null +++ b/src/theme-manager/ExpandedView.tsx @@ -0,0 +1,214 @@ +import { + ButtonItem, + PanelSectionRow, + Focusable, + TextField, + DropdownOption, + DropdownItem, + SingleDropdownOption, +} from "decky-frontend-lib"; +import { useEffect, useMemo, useState, VFC } from "react"; + +import * as python from "../python"; + +// Interfaces for the JSON objects the lists work with +import { browseThemeEntry } from "../customTypes"; +import { useCssLoaderState } from "../state"; +import { Theme } from "../theme"; + +export const ThemeBrowserPage: VFC = () => { + const { + browseThemeList: themeArr, + setBrowseThemeList: setThemeArr, + localThemeList: installedThemes, + setLocalThemeList: setInstalledThemes, + currentExpandedTheme, + setCurExpandedTheme, + isInstalling, + setInstalling, + } = useCssLoaderState(); + + const [backendVersion, setBackendVer] = useState(2); + function reloadBackendVer() { + python.resolve(python.getBackendVersion(), setBackendVer); + } + + function reloadThemes() { + reloadBackendVer(); + // Reloads the theme database + python.resolve(python.reloadThemeDbData(), () => { + python.resolve(python.getThemeDbData(), setThemeArr); + }); + // Reloads the local themes + python.resolve(python.reset(), () => { + python.resolve(python.getThemes(), setInstalledThemes); + }); + } + + // function getThemeDb() { + // python.resolve(python.getThemeDbData(), setThemeArr); + // } + // function getInstalledThemes() { + // python.resolve(python.getThemes(), setInstalledThemes); + // } + + // function installTheme(id: string) { + // // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it + // setInstalling(true); + // python.resolve(python.downloadTheme(id), () => { + // python.resolve(python.reset(), () => { + // python.resolve(python.getThemes(), setInstalledThemes); + // setInstalling(false); + // }); + // }); + // } + + function checkIfThemeInstalled(themeObj: browseThemeEntry) { + const filteredArr: Theme[] = installedThemes.filter( + (e: Theme) => + e.data.name === themeObj.name && e.data.author === themeObj.author + ); + if (filteredArr.length > 0) { + if (filteredArr[0].data.version === themeObj.version) { + return "installed"; + } else { + return "outdated"; + } + } else { + return "uninstalled"; + } + } + // These are just switch statements I use to determine text/css for the buttons + // I put them up here just because I find it clearer to read when they aren't inline + function calcButtonColor(installStatus: string) { + let filterCSS = ""; + switch (installStatus) { + case "outdated": + filterCSS = + "invert(6%) sepia(90%) saturate(200%) hue-rotate(160deg) contrast(122%)"; + break; + default: + filterCSS = ""; + break; + } + return filterCSS; + } + function calcButtonText(installStatus: string) { + let buttonText = ""; + switch (installStatus) { + case "installed": + buttonText = "Installed"; + break; + case "outdated": + buttonText = "Update"; + break; + default: + buttonText = "Install"; + break; + } + return buttonText; + } + + // if theres no theme in the detailed view + if (currentExpandedTheme) { + // This returns 'installed', 'outdated', or 'uninstalled' + const installStatus = checkIfThemeInstalled(currentExpandedTheme); + return ( + <> +
+
+
+
+
+ + {currentExpandedTheme.name} + + {currentExpandedTheme.author} + {currentExpandedTheme.target} + {currentExpandedTheme.version} +
+
+ +
+ { + installTheme(currentExpandedTheme.id); + }} + > + + {calcButtonText(installStatus)} + + +
+
+ +
+ { + setCurExpandedTheme(undefined); + }} + > + + Back + + +
+
+
+
+
+
+ + {currentExpandedTheme?.description || ( + No description provided. + )} + +
+
+ + ); + } + return ( + <> + Error fetching selected theme, please go back and retry. + + ); +}; diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index cba39d4..3c89924 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -22,6 +22,8 @@ export const ThemeBrowserPage: VFC = () => { setBrowseThemeList: setThemeArr, localThemeList: installedThemes, setLocalThemeList: setInstalledThemes, + isInstalling, + setInstalling, } = useCssLoaderState(); const [currentExpandedTheme, setCurExpandedTheme] = useState< @@ -31,7 +33,7 @@ export const ThemeBrowserPage: VFC = () => { const [searchFieldValue, setSearchValue] = useState(""); // This is used to disable buttons during a theme install - const [isInstalling, setInstalling] = useState(false); + // const [isInstalling, setInstalling] = useState(false); const [backendVersion, setBackendVer] = useState(2); function reloadBackendVer() { @@ -177,7 +179,7 @@ export const ThemeBrowserPage: VFC = () => {
{ padding: "0px 8px 0px 8px", display: "flex", flexDirection: "column", - }} - > + }}> {currentExpandedTheme.name} @@ -206,7 +207,7 @@ export const ThemeBrowserPage: VFC = () => {
{ paddingBottom: "0px", // Filter is used to color the button blue for update filter: calcButtonColor(installStatus), - }} - > + }}> { installTheme(currentExpandedTheme.id); - }} - > - + }}> + {calcButtonText(installStatus)} @@ -231,21 +230,19 @@ export const ThemeBrowserPage: VFC = () => {
+ }}> { setCurExpandedTheme(undefined); - }} - > - + }}> + Back @@ -269,23 +266,23 @@ export const ThemeBrowserPage: VFC = () => { <> setSort(e.data)} /> setTarget(e)} /> setSearchValue(e.target.value)} /> @@ -330,11 +327,12 @@ export const ThemeBrowserPage: VFC = () => { } }) .map((e: browseThemeEntry) => { + const installStatus = checkIfThemeInstalled(currentExpandedTheme); return ( // The outer 2 most divs are the background darkened/blurred image, and everything inside is the text/image/buttons <>
{ marginLeft: "10px", marginRight: "10px", marginBottom: "20px", - }} - > + }}>
{ width: "100%", height: "100%", borderRadius: "3px", - }} - > + }}> { overflow: "hidden", textOverflow: "ellipsis", width: "90%", - }} - > + }}> {e.name} {selectedTarget.label === "All" && ( + }}> {e.target} )}
{ }} />
+ }}> + }}> {e.author} + }}> {e.version}
+ }}>
+ }}> setCurExpandedTheme(e)} - > - - See More + onClick={() => setCurExpandedTheme(e)}> + + {installStatus === "outdated" + ? "Update Available" + : "See More"}
@@ -468,11 +458,10 @@ export const ThemeBrowserPage: VFC = () => { { reloadThemes(); - }} - > + }}> Reload Themes From 1ceeef1b92f365788879c5ff42b7c1220d2950f9 Mon Sep 17 00:00:00 2001 From: beebls Date: Wed, 10 Aug 2022 15:19:29 -0600 Subject: [PATCH 20/29] =?UTF-8?q?oopsie=20it=20was=20broken=20=F0=9F=98=B3?= =?UTF-8?q?=20and=20now=20its=20not?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.tsx | 11 ++--- src/theme-manager/ExpandedView.tsx | 58 +++++++++++--------------- src/theme-manager/ThemeBrowserPage.tsx | 16 ++++--- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index bbdfffc..ada142c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -19,6 +19,7 @@ import { useCssLoaderState, } from "./state"; import { ThemeToggle } from "./components"; +import { ExpandedViewPage } from "./theme-manager/ExpandedView"; var firstTime: boolean = true; @@ -125,11 +126,11 @@ export default definePlugin((serverApi: ServerAPI) => { )); - // serverApi.routerHook.addRoute("theme-manager-expanded-view", () => ( - // - - // - // )) + serverApi.routerHook.addRoute("theme-manager-expanded-view", () => ( + + + + )) return { title:
Css Loader
, diff --git a/src/theme-manager/ExpandedView.tsx b/src/theme-manager/ExpandedView.tsx index f2b0610..8d5df96 100644 --- a/src/theme-manager/ExpandedView.tsx +++ b/src/theme-manager/ExpandedView.tsx @@ -1,13 +1,9 @@ import { ButtonItem, PanelSectionRow, - Focusable, - TextField, - DropdownOption, - DropdownItem, - SingleDropdownOption, + Router, } from "decky-frontend-lib"; -import { useEffect, useMemo, useState, VFC } from "react"; +import { useState, VFC } from "react"; import * as python from "../python"; @@ -16,7 +12,7 @@ import { browseThemeEntry } from "../customTypes"; import { useCssLoaderState } from "../state"; import { Theme } from "../theme"; -export const ThemeBrowserPage: VFC = () => { +export const ExpandedViewPage: VFC = () => { const { browseThemeList: themeArr, setBrowseThemeList: setThemeArr, @@ -28,23 +24,16 @@ export const ThemeBrowserPage: VFC = () => { setInstalling, } = useCssLoaderState(); - const [backendVersion, setBackendVer] = useState(2); - function reloadBackendVer() { - python.resolve(python.getBackendVersion(), setBackendVer); - } - - function reloadThemes() { - reloadBackendVer(); - // Reloads the theme database - python.resolve(python.reloadThemeDbData(), () => { - python.resolve(python.getThemeDbData(), setThemeArr); - }); - // Reloads the local themes - python.resolve(python.reset(), () => { - python.resolve(python.getThemes(), setInstalledThemes); - }); - } - + // function reloadThemes() { + // // Reloads the theme database + // python.resolve(python.reloadThemeDbData(), () => { + // python.resolve(python.getThemeDbData(), setThemeArr); + // }); + // // Reloads the local themes + // python.resolve(python.reset(), () => { + // python.resolve(python.getThemes(), setInstalledThemes); + // }); + // } // function getThemeDb() { // python.resolve(python.getThemeDbData(), setThemeArr); // } @@ -52,16 +41,16 @@ export const ThemeBrowserPage: VFC = () => { // python.resolve(python.getThemes(), setInstalledThemes); // } - // function installTheme(id: string) { - // // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it - // setInstalling(true); - // python.resolve(python.downloadTheme(id), () => { - // python.resolve(python.reset(), () => { - // python.resolve(python.getThemes(), setInstalledThemes); - // setInstalling(false); - // }); - // }); - // } + function installTheme(id: string) { + // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it + setInstalling(true); + python.resolve(python.downloadTheme(id), () => { + python.resolve(python.reset(), () => { + python.resolve(python.getThemes(), setInstalledThemes); + setInstalling(false); + }); + }); + } function checkIfThemeInstalled(themeObj: browseThemeEntry) { const filteredArr: Theme[] = installedThemes.filter( @@ -184,6 +173,7 @@ export const ThemeBrowserPage: VFC = () => { layout="below" onClick={() => { setCurExpandedTheme(undefined); + Router.Navigate("/theme-manager"); }} > diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 3c89924..2f8a935 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -6,6 +6,7 @@ import { DropdownOption, DropdownItem, SingleDropdownOption, + Router, } from "decky-frontend-lib"; import { useEffect, useMemo, useState, VFC } from "react"; @@ -24,11 +25,13 @@ export const ThemeBrowserPage: VFC = () => { setLocalThemeList: setInstalledThemes, isInstalling, setInstalling, + currentExpandedTheme, + setCurExpandedTheme } = useCssLoaderState(); - const [currentExpandedTheme, setCurExpandedTheme] = useState< - browseThemeEntry | undefined - >(undefined); + // const [currentExpandedTheme, setCurExpandedTheme] = useState< + // browseThemeEntry | undefined + // >(undefined); const [searchFieldValue, setSearchValue] = useState(""); @@ -327,7 +330,7 @@ export const ThemeBrowserPage: VFC = () => { } }) .map((e: browseThemeEntry) => { - const installStatus = checkIfThemeInstalled(currentExpandedTheme); + const installStatus = checkIfThemeInstalled(e); return ( // The outer 2 most divs are the background darkened/blurred image, and everything inside is the text/image/buttons <> @@ -440,7 +443,10 @@ export const ThemeBrowserPage: VFC = () => { bottomSeparator={false} layout='below' disabled={isInstalling} - onClick={() => setCurExpandedTheme(e)}> + onClick={() => { + setCurExpandedTheme(e); + // Router.Navigate("/theme-manager-expanded-view"); + }}> {installStatus === "outdated" ? "Update Available" From ee3d2cd2f749a0fc6577fdc96ef61a8825be81e4 Mon Sep 17 00:00:00 2001 From: beebls Date: Wed, 10 Aug 2022 15:42:50 -0600 Subject: [PATCH 21/29] Expanded View implented --- src/index.tsx | 2 +- src/theme-manager/ExpandedView.tsx | 28 +----- src/theme-manager/ThemeBrowserPage.tsx | 126 ++++--------------------- 3 files changed, 23 insertions(+), 133 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index ada142c..c9c58e3 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -126,7 +126,7 @@ export default definePlugin((serverApi: ServerAPI) => { )); - serverApi.routerHook.addRoute("theme-manager-expanded-view", () => ( + serverApi.routerHook.addRoute("/theme-manager-expanded-view", () => ( diff --git a/src/theme-manager/ExpandedView.tsx b/src/theme-manager/ExpandedView.tsx index 8d5df96..83135ec 100644 --- a/src/theme-manager/ExpandedView.tsx +++ b/src/theme-manager/ExpandedView.tsx @@ -3,7 +3,7 @@ import { PanelSectionRow, Router, } from "decky-frontend-lib"; -import { useState, VFC } from "react"; +import { VFC } from "react"; import * as python from "../python"; @@ -14,8 +14,6 @@ import { Theme } from "../theme"; export const ExpandedViewPage: VFC = () => { const { - browseThemeList: themeArr, - setBrowseThemeList: setThemeArr, localThemeList: installedThemes, setLocalThemeList: setInstalledThemes, currentExpandedTheme, @@ -24,23 +22,6 @@ export const ExpandedViewPage: VFC = () => { setInstalling, } = useCssLoaderState(); - // function reloadThemes() { - // // Reloads the theme database - // python.resolve(python.reloadThemeDbData(), () => { - // python.resolve(python.getThemeDbData(), setThemeArr); - // }); - // // Reloads the local themes - // python.resolve(python.reset(), () => { - // python.resolve(python.getThemes(), setInstalledThemes); - // }); - // } - // function getThemeDb() { - // python.resolve(python.getThemeDbData(), setThemeArr); - // } - // function getInstalledThemes() { - // python.resolve(python.getThemes(), setInstalledThemes); - // } - function installTheme(id: string) { // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it setInstalling(true); @@ -103,7 +84,8 @@ export const ExpandedViewPage: VFC = () => { // This returns 'installed', 'outdated', or 'uninstalled' const installStatus = checkIfThemeInstalled(currentExpandedTheme); return ( - <> + // The outermost div is to push the content down into the visible area +
{ layout="below" onClick={() => { setCurExpandedTheme(undefined); - Router.Navigate("/theme-manager"); + Router.NavigateBackOrOpenMenu(); }} > @@ -193,7 +175,7 @@ export const ExpandedViewPage: VFC = () => {
- +
); } return ( diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 2f8a935..9451592 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -24,15 +24,11 @@ export const ThemeBrowserPage: VFC = () => { localThemeList: installedThemes, setLocalThemeList: setInstalledThemes, isInstalling, - setInstalling, - currentExpandedTheme, + // setInstalling, + // currentExpandedTheme, setCurExpandedTheme } = useCssLoaderState(); - // const [currentExpandedTheme, setCurExpandedTheme] = useState< - // browseThemeEntry | undefined - // >(undefined); - const [searchFieldValue, setSearchValue] = useState(""); // This is used to disable buttons during a theme install @@ -109,16 +105,18 @@ export const ThemeBrowserPage: VFC = () => { python.resolve(python.getThemes(), setInstalledThemes); } - function installTheme(id: string) { - // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it - setInstalling(true); - python.resolve(python.downloadTheme(id), () => { - python.resolve(python.reset(), () => { - python.resolve(python.getThemes(), setInstalledThemes); - setInstalling(false); - }); - }); - } + // Installing is now handled on the ExpandedView + + // function installTheme(id: string) { + // // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it + // setInstalling(true); + // python.resolve(python.downloadTheme(id), () => { + // python.resolve(python.reset(), () => { + // python.resolve(python.getThemes(), setInstalledThemes); + // setInstalling(false); + // }); + // }); + // } function checkIfThemeInstalled(themeObj: browseThemeEntry) { const filteredArr: Theme[] = installedThemes.filter( @@ -173,98 +171,6 @@ export const ThemeBrowserPage: VFC = () => { getInstalledThemes(); }, []); - // if theres no theme in the detailed view - if (currentExpandedTheme) { - // This returns 'installed', 'outdated', or 'uninstalled' - const installStatus = checkIfThemeInstalled(currentExpandedTheme); - return ( - <> -
-
-
-
-
- - {currentExpandedTheme.name} - - {currentExpandedTheme.author} - {currentExpandedTheme.target} - {currentExpandedTheme.version} -
-
- -
- { - installTheme(currentExpandedTheme.id); - }}> - - {calcButtonText(installStatus)} - - -
-
- -
- { - setCurExpandedTheme(undefined); - }}> - - Back - - -
-
-
-
-
-
- - {currentExpandedTheme?.description || ( - No description provided. - )} - -
-
- - ); - } return ( <> @@ -438,6 +344,8 @@ export const ThemeBrowserPage: VFC = () => { // Before this, I was using negative margin to "shrink" the element, but this is a much better solution paddingTop: "0px", paddingBottom: "0px", + + filter: calcButtonColor(installStatus) }}> { disabled={isInstalling} onClick={() => { setCurExpandedTheme(e); - // Router.Navigate("/theme-manager-expanded-view"); + Router.Navigate("/theme-manager-expanded-view"); }}> {installStatus === "outdated" From 5cb05f8ff838d32ce8a810b1469211a63f16fbbb Mon Sep 17 00:00:00 2001 From: EMERALD Date: Wed, 10 Aug 2022 21:12:42 -0500 Subject: [PATCH 22/29] Update theme details view --- src/theme-manager/ExpandedView.tsx | 19 ++++++---------- src/theme-manager/ThemeBrowserPage.tsx | 30 +++++++++++++------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/theme-manager/ExpandedView.tsx b/src/theme-manager/ExpandedView.tsx index 83135ec..b5fd880 100644 --- a/src/theme-manager/ExpandedView.tsx +++ b/src/theme-manager/ExpandedView.tsx @@ -86,24 +86,19 @@ export const ExpandedViewPage: VFC = () => { return ( // The outermost div is to push the content down into the visible area
-
-
-
+
+ -
+
{ } return filterCSS; } - function calcButtonText(installStatus: string) { - let buttonText = ""; - switch (installStatus) { - case "installed": - buttonText = "Installed"; - break; - case "outdated": - buttonText = "Update"; - break; - default: - buttonText = "Install"; - break; - } - return buttonText; - } + // function calcButtonText(installStatus: string) { + // let buttonText = ""; + // switch (installStatus) { + // case "installed": + // buttonText = "Installed"; + // break; + // case "outdated": + // buttonText = "Update"; + // break; + // default: + // buttonText = "Install"; + // break; + // } + // return buttonText; + // } // Runs upon opening the page useEffect(() => { From 49b87cb2aa9595cff3c77dd702b6335a54e5ed4a Mon Sep 17 00:00:00 2001 From: EMERALD Date: Wed, 10 Aug 2022 21:34:41 -0500 Subject: [PATCH 23/29] Update default sort and sort/filter names --- src/theme-manager/ThemeBrowserPage.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 3659f58..6fd8253 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -60,13 +60,13 @@ export const ThemeBrowserPage: VFC = () => { return true; }; - const [selectedSort, setSort] = useState(1); + const [selectedSort, setSort] = useState(3); const sortOptions = useMemo( (): DropdownOption[] => [ - { data: 1, label: "Name: A-Z" }, - { data: 2, label: "Name: Z-A" }, - { data: 3, label: "Date: Newest-Oldest" }, - { data: 4, label: "Date: Oldest-Newest" }, + { data: 1, label: "Alphabetical (A to Z)" }, + { data: 2, label: "Alphabetical (Z to A)" }, + { data: 3, label: "Last Updated (Newest)" }, + { data: 4, label: "Last Updated (Oldest)" }, ], [] ); @@ -81,7 +81,7 @@ export const ThemeBrowserPage: VFC = () => { ); return [ { data: 1, label: "All" }, - { data: 2, label: "Installed Only" }, + { data: 2, label: "Installed" }, ...[...uniqueTargets].map((e, i) => ({ data: i + 3, label: e })), ]; }, [themeArr, searchFilter]); @@ -175,14 +175,14 @@ export const ThemeBrowserPage: VFC = () => { <> setSort(e.data)} /> { .filter((e: browseThemeEntry) => { if (selectedTarget.label === "All") { return true; - } else if (selectedTarget.label === "Installed Only") { + } else if (selectedTarget.label === "Installed") { const strValue = checkIfThemeInstalled(e); return strValue === "installed" || strValue === "outdated"; } else { From 82d9216db6b2d0bb415525275385dbcfcd7a7eee Mon Sep 17 00:00:00 2001 From: beebls Date: Thu, 11 Aug 2022 19:02:58 -0600 Subject: [PATCH 24/29] Expanded View now focuses back button --- src/theme-manager/ExpandedView.tsx | 11 ++++++++++- src/theme-manager/ThemeBrowserPage.tsx | 17 +---------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/theme-manager/ExpandedView.tsx b/src/theme-manager/ExpandedView.tsx index b5fd880..b790eb0 100644 --- a/src/theme-manager/ExpandedView.tsx +++ b/src/theme-manager/ExpandedView.tsx @@ -3,7 +3,7 @@ import { PanelSectionRow, Router, } from "decky-frontend-lib"; -import { VFC } from "react"; +import { useEffect, useRef, VFC } from "react"; import * as python from "../python"; @@ -79,6 +79,12 @@ export const ExpandedViewPage: VFC = () => { return buttonText; } + const backButtonRef = useRef(null); + useEffect(() => { + // @ts-ignore + backButtonRef?.current.focus(); + }, []) + // if theres no theme in the detailed view if (currentExpandedTheme) { // This returns 'installed', 'outdated', or 'uninstalled' @@ -146,6 +152,9 @@ export const ExpandedViewPage: VFC = () => { }} > { diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 6fd8253..917e8a2 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -148,21 +148,6 @@ export const ThemeBrowserPage: VFC = () => { } return filterCSS; } - // function calcButtonText(installStatus: string) { - // let buttonText = ""; - // switch (installStatus) { - // case "installed": - // buttonText = "Installed"; - // break; - // case "outdated": - // buttonText = "Update"; - // break; - // default: - // buttonText = "Install"; - // break; - // } - // return buttonText; - // } // Runs upon opening the page useEffect(() => { @@ -358,7 +343,7 @@ export const ThemeBrowserPage: VFC = () => { {installStatus === "outdated" ? "Update Available" - : "See More"} + : "View Details"}
From d4095fe5af434b8503190b3c1eae63e6986a5c9e Mon Sep 17 00:00:00 2001 From: beebls Date: Thu, 11 Aug 2022 19:44:34 -0600 Subject: [PATCH 25/29] Fixed the prettier config, and added potential candidate for new theme card bgs --- .prettierrc | 2 +- src/index.tsx | 2 +- src/theme-manager/ExpandedView.tsx | 34 ++++----- src/theme-manager/ThemeBrowserPage.tsx | 95 ++++++++++++++++-------- src/theme-manager/UninstallThemePage.tsx | 3 +- 5 files changed, 82 insertions(+), 54 deletions(-) diff --git a/.prettierrc b/.prettierrc index bcc5500..2d08c14 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,6 +2,6 @@ "singleQuote": false, "tabWidth": 2, "semi": true, - "trailingComma": "es5" + "trailingComma": "es5", "bracketSameLine": false } diff --git a/src/index.tsx b/src/index.tsx index c9c58e3..e90cf27 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -130,7 +130,7 @@ export default definePlugin((serverApi: ServerAPI) => { - )) + )); return { title:
Css Loader
, diff --git a/src/theme-manager/ExpandedView.tsx b/src/theme-manager/ExpandedView.tsx index b790eb0..a371cf5 100644 --- a/src/theme-manager/ExpandedView.tsx +++ b/src/theme-manager/ExpandedView.tsx @@ -1,8 +1,4 @@ -import { - ButtonItem, - PanelSectionRow, - Router, -} from "decky-frontend-lib"; +import { ButtonItem, PanelSectionRow, Router } from "decky-frontend-lib"; import { useEffect, useRef, VFC } from "react"; import * as python from "../python"; @@ -22,16 +18,16 @@ export const ExpandedViewPage: VFC = () => { setInstalling, } = useCssLoaderState(); - function installTheme(id: string) { - // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it - setInstalling(true); - python.resolve(python.downloadTheme(id), () => { - python.resolve(python.reset(), () => { - python.resolve(python.getThemes(), setInstalledThemes); - setInstalling(false); - }); + function installTheme(id: string) { + // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it + setInstalling(true); + python.resolve(python.downloadTheme(id), () => { + python.resolve(python.reset(), () => { + python.resolve(python.getThemes(), setInstalledThemes); + setInstalling(false); }); - } + }); + } function checkIfThemeInstalled(themeObj: browseThemeEntry) { const filteredArr: Theme[] = installedThemes.filter( @@ -83,7 +79,7 @@ export const ExpandedViewPage: VFC = () => { useEffect(() => { // @ts-ignore backButtonRef?.current.focus(); - }, []) + }, []); // if theres no theme in the detailed view if (currentExpandedTheme) { @@ -91,8 +87,10 @@ export const ExpandedViewPage: VFC = () => { const installStatus = checkIfThemeInstalled(currentExpandedTheme); return ( // The outermost div is to push the content down into the visible area -
-
+
+
{ }} > { isInstalling, // setInstalling, // currentExpandedTheme, - setCurExpandedTheme + setCurExpandedTheme, } = useCssLoaderState(); const [searchFieldValue, setSearchValue] = useState(""); @@ -105,7 +107,7 @@ export const ThemeBrowserPage: VFC = () => { python.resolve(python.getThemes(), setInstalledThemes); } - // Installing is now handled on the ExpandedView + // Installing is now handled on the ExpandedView // function installTheme(id: string) { // // TODO: most of this is repeating code in other functions, I can probably refactor it to shorten it @@ -160,23 +162,23 @@ export const ThemeBrowserPage: VFC = () => { <> setSort(e.data)} /> setTarget(e)} /> setSearchValue(e.target.value)} /> @@ -226,8 +228,10 @@ export const ThemeBrowserPage: VFC = () => { // The outer 2 most divs are the background darkened/blurred image, and everything inside is the text/image/buttons <>
{ marginLeft: "10px", marginRight: "10px", marginBottom: "20px", - }}> + }} + >
+ }} + > { overflow: "hidden", textOverflow: "ellipsis", width: "90%", - }}> + }} + > {e.name} {selectedTarget.label === "All" && ( + }} + > {e.target} )}
{ backgroundRepeat: "no-repeat", height: "150px", display: "flex", + position: "relative", flexDirection: "column", alignItems: "center", }} - /> + > + +
+ }} + > + }} + > {e.author} + }} + > {e.version}
+ }} + >
+ filter: calcButtonColor(installStatus), + }} + > { setCurExpandedTheme(e); Router.Navigate("/theme-manager-expanded-view"); - }}> - + }} + > + {installStatus === "outdated" ? "Update Available" : "View Details"} @@ -357,10 +385,11 @@ export const ThemeBrowserPage: VFC = () => { { reloadThemes(); - }}> + }} + > Reload Themes diff --git a/src/theme-manager/UninstallThemePage.tsx b/src/theme-manager/UninstallThemePage.tsx index 0d9a3a4..d5e9895 100644 --- a/src/theme-manager/UninstallThemePage.tsx +++ b/src/theme-manager/UninstallThemePage.tsx @@ -40,7 +40,8 @@ export const UninstallThemePage: VFC = () => { handleUninstall(e)} - disabled={isUninstalling}> + disabled={isUninstalling} + > From 01301cb212e6c194568a6d3920278fb29ea58fb7 Mon Sep 17 00:00:00 2001 From: beebls Date: Thu, 11 Aug 2022 21:00:00 -0600 Subject: [PATCH 26/29] Remove the WIP installed icon --- src/theme-manager/ThemeBrowserPage.tsx | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 63fd73a..66f272c 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -10,8 +10,6 @@ import { } from "decky-frontend-lib"; import { useEffect, useMemo, useState, VFC } from "react"; -import { BiDownload } from "react-icons/bi"; - import * as python from "../python"; // Interfaces for the JSON objects the lists work with @@ -299,18 +297,7 @@ export const ThemeBrowserPage: VFC = () => { flexDirection: "column", alignItems: "center", }} - > - -
+ >
Date: Fri, 12 Aug 2022 13:28:46 -0600 Subject: [PATCH 27/29] Moved search, sort, and filter to global state These now persist through page changes. --- src/state/CssLoaderState.tsx | 39 ++++++++++++++++++++++++++ src/theme-manager/ThemeBrowserPage.tsx | 22 +++++++-------- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/state/CssLoaderState.tsx b/src/state/CssLoaderState.tsx index 33b83d5..531063d 100644 --- a/src/state/CssLoaderState.tsx +++ b/src/state/CssLoaderState.tsx @@ -1,3 +1,4 @@ +import { SingleDropdownOption } from "decky-frontend-lib"; import { createContext, FC, useContext, useEffect, useState } from "react"; import { localThemeEntry, browseThemeEntry } from "../customTypes"; import { Theme } from "../theme"; @@ -5,6 +6,9 @@ import { Theme } from "../theme"; interface PublicCssLoaderState { localThemeList: Theme[]; browseThemeList: browseThemeEntry[]; + searchFieldValue: string; + selectedSort: number; + selectedTarget: SingleDropdownOption; isInstalling: boolean; currentExpandedTheme: browseThemeEntry | undefined; } @@ -14,6 +18,9 @@ interface PublicCssLoaderState { interface PublicCssLoaderContext extends PublicCssLoaderState { setLocalThemeList(listArr: localThemeEntry[]): void; setBrowseThemeList(listArr: browseThemeEntry[]): void; + setSearchValue(value: string): void; + setSort(value: number): void; + setTarget(value: SingleDropdownOption): void; setInstalling(bool: boolean): void; setCurExpandedTheme(theme: browseThemeEntry | undefined): void; } @@ -22,6 +29,12 @@ interface PublicCssLoaderContext extends PublicCssLoaderState { export class CssLoaderState { private localThemeList: Theme[] = []; private browseThemeList: browseThemeEntry[] = []; + private searchFieldValue: string = ""; + private selectedSort: number = 3; + private selectedTarget: SingleDropdownOption = { + data: 1, + label: "All", + }; private isInstalling: boolean = false; private currentExpandedTheme: browseThemeEntry | undefined = undefined; @@ -32,6 +45,9 @@ export class CssLoaderState { return { localThemeList: this.localThemeList, browseThemeList: this.browseThemeList, + searchFieldValue: this.searchFieldValue, + selectedSort: this.selectedSort, + selectedTarget: this.selectedTarget, isInstalling: this.isInstalling, currentExpandedTheme: this.currentExpandedTheme, }; @@ -57,6 +73,21 @@ export class CssLoaderState { this.forceUpdate(); } + setSearchValue(value: string) { + this.searchFieldValue = value; + this.forceUpdate(); + } + + setSort(value: number) { + this.selectedSort = value; + this.forceUpdate(); + } + + setTarget(value: SingleDropdownOption) { + this.selectedTarget = value; + this.forceUpdate(); + } + setInstalling(bool: boolean) { this.isInstalling = bool; this.forceUpdate(); @@ -103,6 +134,11 @@ export const CssLoaderContextProvider: FC = ({ cssLoaderStateClass.setLocalThemeList(listArr); const setBrowseThemeList = (listArr: browseThemeEntry[]) => cssLoaderStateClass.setBrowseThemeList(listArr); + const setSearchValue = (value: string) => + cssLoaderStateClass.setSearchValue(value); + const setSort = (value: number) => cssLoaderStateClass.setSort(value); + const setTarget = (value: SingleDropdownOption) => + cssLoaderStateClass.setTarget(value); const setInstalling = (bool: boolean) => cssLoaderStateClass.setInstalling(bool); const setCurExpandedTheme = (theme: browseThemeEntry | undefined) => @@ -114,6 +150,9 @@ export const CssLoaderContextProvider: FC = ({ ...publicState, setLocalThemeList, setBrowseThemeList, + setSearchValue, + setSort, + setTarget, setInstalling, setCurExpandedTheme, }} diff --git a/src/theme-manager/ThemeBrowserPage.tsx b/src/theme-manager/ThemeBrowserPage.tsx index 66f272c..1ad1af5 100644 --- a/src/theme-manager/ThemeBrowserPage.tsx +++ b/src/theme-manager/ThemeBrowserPage.tsx @@ -5,7 +5,6 @@ import { TextField, DropdownOption, DropdownItem, - SingleDropdownOption, Router, } from "decky-frontend-lib"; import { useEffect, useMemo, useState, VFC } from "react"; @@ -23,16 +22,22 @@ export const ThemeBrowserPage: VFC = () => { setBrowseThemeList: setThemeArr, localThemeList: installedThemes, setLocalThemeList: setInstalledThemes, + searchFieldValue, + setSearchValue, + selectedSort, + setSort, + selectedTarget, + setTarget, isInstalling, - // setInstalling, - // currentExpandedTheme, setCurExpandedTheme, } = useCssLoaderState(); - const [searchFieldValue, setSearchValue] = useState(""); - - // This is used to disable buttons during a theme install + // THESE HAVE BEEN MOVED TO GLOBAL STATE + // These are the legacy "local state" versions of them, only uncomment if global state is broken and not working + // const [searchFieldValue, setSearchValue] = useState(""); // const [isInstalling, setInstalling] = useState(false); + // const [selectedTarget, setTarget] = useState({ data: 1, label: "All", }); + // const [selectedSort, setSort] = useState(3); const [backendVersion, setBackendVer] = useState(2); function reloadBackendVer() { @@ -60,7 +65,6 @@ export const ThemeBrowserPage: VFC = () => { return true; }; - const [selectedSort, setSort] = useState(3); const sortOptions = useMemo( (): DropdownOption[] => [ { data: 1, label: "Alphabetical (A to Z)" }, @@ -71,10 +75,6 @@ export const ThemeBrowserPage: VFC = () => { [] ); - const [selectedTarget, setTarget] = useState({ - data: 1, - label: "All", - }); const targetOptions = useMemo((): DropdownOption[] => { const uniqueTargets = new Set( themeArr.filter(searchFilter).map((e) => e.target) From 47714f26405abe0f41672924ef307f2a959804ac Mon Sep 17 00:00:00 2001 From: EMERALD Date: Fri, 12 Aug 2022 14:29:26 -0500 Subject: [PATCH 28/29] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fc1a5a4..a717b0a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# SDH-CssLoader -Dynamically loads css off storage. Also reloads whenever the steam ui reloads +# CSS Loader +Dynamically loads CSS files from storage and reloads alongside Steam UI. -## How it works -The loader reads all folders in `/home/deck/homebrew/themes`. In every folder, it looks for a file called `theme.json`. This json file stores in which tab which css should be injected. An example theme can be found in the themes folder of this repository +# Overview +The loader reads all folders in `/home/deck/homebrew/themes`. In every folder, it reads a `theme.json` file that stores how the CSS should be injected. -[More information about creating themes, and publishing themes to the theme browser can be found here](https://github.com/suchmememanyskill/CssLoader-ThemeDb) \ No newline at end of file +[Information about creating and publishing themes can be found here](https://github.com/suchmememanyskill/CssLoader-ThemeDb) From fadae7ec94b0f3d967e6ad4114ff4eef1c3b466a Mon Sep 17 00:00:00 2001 From: suchmememanyskill <38142618+suchmememanyskill@users.noreply.github.com> Date: Fri, 12 Aug 2022 22:12:06 +0200 Subject: [PATCH 29/29] Bump version --- main.py | 2 +- package.json | 2 +- plugin.json | 2 +- src/index.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 50dbe5b..91b7ca3 100644 --- a/main.py +++ b/main.py @@ -335,7 +335,7 @@ def to_dict(self) -> dict: class RemoteInstall: def __init__(self, plugin): - self.themeDb = "https://github.com/suchmememanyskill/CssLoader-ThemeDb/releases/download/1.0.0/themes.json" + self.themeDb = "https://github.com/suchmememanyskill/CssLoader-ThemeDb/releases/download/1.1.0/themes.json" self.plugin = plugin self.themes = [] diff --git a/package.json b/package.json index eb422a2..9408b8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "SDH-CssLoader", - "version": "1.0.0", + "version": "1.1.0", "description": "A css loader", "scripts": { "build": "shx rm -rf dist && rollup -c", diff --git a/plugin.json b/plugin.json index dd93fc4..3fc94f6 100644 --- a/plugin.json +++ b/plugin.json @@ -1,5 +1,5 @@ { - "name": "Css Loader", + "name": "CSS Loader", "author": "Such Meme, Many Skill", "flags": [], "publish": { diff --git a/src/index.tsx b/src/index.tsx index e90cf27..6e738c8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -133,7 +133,7 @@ export default definePlugin((serverApi: ServerAPI) => { )); return { - title:
Css Loader
, + title:
CSS Loader
, content: (