mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-06 17:59:54 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3700dd7437 | |||
| c6d48389c9 | |||
| 490fc18008 | |||
| 797c7ea3b0 | |||
| 0f06bc1ef0 | |||
| c774451ff4 | |||
| 62a5bdbbb0 | |||
| 7716c73014 | |||
| 8829adc5b6 | |||
| 62bd3e76bd | |||
| 9867d7bea0 | |||
| c4d6731401 | |||
| fded2fa8bf | |||
| 90c523ec45 | |||
| c5ccb4dfb8 | |||
| 8b1925bc53 | |||
| a8c7c2f18f | |||
| 463258febb | |||
| 304fc0f94c | |||
| b5b041fdee | |||
| 9d980618a7 | |||
| 6dad3f81e8 | |||
| adc1a792fb |
@@ -93,7 +93,7 @@ jobs:
|
||||
uses: rez0n/actions-github-release@main
|
||||
id: latest_release
|
||||
env:
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: "SteamDeckHomebrew/decky-loader"
|
||||
type: "nodraft"
|
||||
|
||||
@@ -159,14 +159,14 @@ jobs:
|
||||
uses: rez0n/actions-github-release@main
|
||||
id: latest_release
|
||||
env:
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: "SteamDeckHomebrew/decky-loader"
|
||||
type: "nodraft"
|
||||
|
||||
- name: Prepare tag ⚙️
|
||||
id: ready_tag
|
||||
run: |
|
||||
export VERSION="2.0.5"
|
||||
export VERSION=${{ steps.latest_release.outputs.release }}
|
||||
echo "VERS: $VERSION"
|
||||
OUT=""
|
||||
if [[ ! "$VERSION" =~ "-pre" ]]; then
|
||||
|
||||
Vendored
+21
-15
@@ -89,17 +89,6 @@
|
||||
"command": "rsync -azp --delete --rsh='ssh -p ${config:deckport} ${config:deckkey}' --exclude='.git/' --exclude='.github/' --exclude='.vscode/' --exclude='frontend/' --exclude='dist/' --exclude='contrib/' --exclude='*.log' --exclude='requirements.txt' --exclude='backend/__pycache__/' --exclude='.gitignore' . deck@${config:deckip}:${config:deckdir}/homebrew/dev/pluginloader",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "deployall",
|
||||
"dependsOrder": "sequence",
|
||||
"group": "none",
|
||||
"dependsOn": [
|
||||
"createfolders",
|
||||
"dependencies",
|
||||
"deploy"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
// RUN
|
||||
{
|
||||
"label": "runpydeck",
|
||||
@@ -107,7 +96,7 @@
|
||||
"type": "shell",
|
||||
"group": "none",
|
||||
"dependsOn" : ["checkforsettings"],
|
||||
"command": "ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PLUGIN_PATH=${config:deckdir}/homebrew/dev/plugins; export CHOWN_PLUGIN_PATH=0; echo '${config:deckpass}' | sudo -SE python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py'",
|
||||
"command": "ssh deck@${config:deckip} -p ${config:deckport} ${config:deckkey} 'export PLUGIN_PATH=${config:deckdir}/homebrew/dev/plugins; export CHOWN_PLUGIN_PATH=0; export LOG_LEVEL=DEBUG; cd ${config:deckdir}/homebrew/services; echo '${config:deckpass}' | sudo -SE python3 ${config:deckdir}/homebrew/dev/pluginloader/backend/main.py'",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
@@ -126,12 +115,12 @@
|
||||
"group": "none",
|
||||
"dependsOn": [
|
||||
"buildall",
|
||||
"deployall",
|
||||
"deploy",
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "allinone",
|
||||
"label": "updateandrun",
|
||||
"detail": "Build, deploy and run",
|
||||
"dependsOrder": "sequence",
|
||||
"group": {
|
||||
@@ -140,7 +129,24 @@
|
||||
},
|
||||
"dependsOn": [
|
||||
"buildall",
|
||||
"deployall",
|
||||
"deploy",
|
||||
"runpydeck"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "allinone",
|
||||
"detail": "Build, install dependencies, deploy and run",
|
||||
"dependsOrder": "sequence",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": false
|
||||
},
|
||||
"dependsOn": [
|
||||
"buildall",
|
||||
"createfolders",
|
||||
"dependencies",
|
||||
"deploy",
|
||||
"runpydeck"
|
||||
],
|
||||
"problemMatcher": []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Plugin Loader [](https://discord.gg/ZU74G2NJzk)
|
||||
|
||||

|
||||

|
||||
|
||||
Keep an eye on the [Wiki](https://deckbrew.xyz) for more information about Plugin Loader, documentation + tools for plugin development and more.
|
||||
|
||||
|
||||
+21
-5
@@ -3,7 +3,7 @@ import json
|
||||
|
||||
# Partial imports
|
||||
from aiohttp import ClientSession, web
|
||||
from asyncio import get_event_loop
|
||||
from asyncio import get_event_loop, sleep
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from hashlib import sha256
|
||||
from io import BytesIO
|
||||
@@ -28,9 +28,10 @@ class PluginInstallContext:
|
||||
self.hash = hash
|
||||
|
||||
class PluginBrowser:
|
||||
def __init__(self, plugin_path, plugins) -> None:
|
||||
def __init__(self, plugin_path, plugins, loader) -> None:
|
||||
self.plugin_path = plugin_path
|
||||
self.plugins = plugins
|
||||
self.loader = loader
|
||||
self.install_requests = {}
|
||||
|
||||
def _unzip_to_plugin_dir(self, zip, name, hash):
|
||||
@@ -39,8 +40,9 @@ class PluginBrowser:
|
||||
return False
|
||||
zip_file = ZipFile(zip)
|
||||
zip_file.extractall(self.plugin_path)
|
||||
code_chown = call(["chown", "-R", get_user()+":"+get_user_group(), self.plugin_path])
|
||||
code_chmod = call(["chmod", "-R", "555", self.plugin_path])
|
||||
plugin_dir = self.find_plugin_folder(name)
|
||||
code_chown = call(["chown", "-R", get_user()+":"+get_user_group(), plugin_dir])
|
||||
code_chmod = call(["chmod", "-R", "555", plugin_dir])
|
||||
if code_chown != 0 or code_chmod != 0:
|
||||
logger.error(f"chown/chmod exited with a non-zero exit code (chown: {code_chown}, chmod: {code_chmod})")
|
||||
return False
|
||||
@@ -58,6 +60,8 @@ class PluginBrowser:
|
||||
logger.debug(f"skipping {folder}")
|
||||
|
||||
async def uninstall_plugin(self, name):
|
||||
if self.loader.watcher:
|
||||
self.loader.watcher.disabled = True
|
||||
tab = await get_tab("SP")
|
||||
try:
|
||||
logger.info("uninstalling " + name)
|
||||
@@ -74,8 +78,12 @@ class PluginBrowser:
|
||||
except Exception as e:
|
||||
logger.error(f"Plugin {name} in {self.find_plugin_folder(name)} was not uninstalled")
|
||||
logger.error(f"Error at %s", exc_info=e)
|
||||
if self.loader.watcher:
|
||||
self.loader.watcher.disabled = False
|
||||
|
||||
async def _install(self, artifact, name, version, hash):
|
||||
if self.loader.watcher:
|
||||
self.loader.watcher.disabled = True
|
||||
try:
|
||||
await self.uninstall_plugin(name)
|
||||
except:
|
||||
@@ -93,9 +101,17 @@ class PluginBrowser:
|
||||
ret = self._unzip_to_plugin_dir(res_zip, name, hash)
|
||||
if ret:
|
||||
logger.info(f"Installed {name} (Version: {version})")
|
||||
await inject_to_tab("SP", "window.syncDeckyPlugins()")
|
||||
plugin_dir = self.find_plugin_folder(name)
|
||||
if name in self.loader.plugins:
|
||||
self.loader.plugins[name].stop()
|
||||
self.loader.plugins.pop(name, None)
|
||||
await sleep(1)
|
||||
self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_dir)
|
||||
# await inject_to_tab("SP", "window.syncDeckyPlugins()")
|
||||
else:
|
||||
self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
|
||||
if self.loader.watcher:
|
||||
self.loader.watcher.disabled = False
|
||||
else:
|
||||
logger.fatal(f"Could not fetch from URL. {await res.text()}")
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ def get_csrf_token():
|
||||
|
||||
@middleware
|
||||
async def csrf_middleware(request, handler):
|
||||
if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)):
|
||||
if str(request.method) == "OPTIONS" or request.headers.get('Authentication') == csrf_token or str(request.rel_url) == "/auth/token" or str(request.rel_url).startswith("/plugins/load_main/") or str(request.rel_url).startswith("/static/") or str(request.rel_url).startswith("/legacy/") or str(request.rel_url).startswith("/steam_resource/") or str(request.rel_url).startswith("/frontend/") or assets_regex.match(str(request.rel_url)) or frontend_regex.match(str(request.rel_url)):
|
||||
return await handler(request)
|
||||
return Response(text='Forbidden', status='403')
|
||||
|
||||
|
||||
+6
-1
@@ -25,8 +25,11 @@ class FileChangeHandler(RegexMatchingEventHandler):
|
||||
self.logger = getLogger("file-watcher")
|
||||
self.plugin_path = plugin_path
|
||||
self.queue = queue
|
||||
self.disabled = False
|
||||
|
||||
def maybe_reload(self, src_path):
|
||||
if self.disabled:
|
||||
return
|
||||
plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0]
|
||||
if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")):
|
||||
self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
|
||||
@@ -66,11 +69,13 @@ class Loader:
|
||||
self.plugin_path = plugin_path
|
||||
self.logger.info(f"plugin_path: {self.plugin_path}")
|
||||
self.plugins = {}
|
||||
self.watcher = None
|
||||
|
||||
if live_reload:
|
||||
self.reload_queue = Queue()
|
||||
self.observer = Observer()
|
||||
self.observer.schedule(FileChangeHandler(self.reload_queue, plugin_path), self.plugin_path, recursive=True)
|
||||
self.watcher = FileChangeHandler(self.reload_queue, plugin_path)
|
||||
self.observer.schedule(self.watcher, self.plugin_path, recursive=True)
|
||||
self.observer.start()
|
||||
self.loop.create_task(self.handle_reloads())
|
||||
|
||||
|
||||
+4
-3
@@ -4,6 +4,7 @@ from json import dumps, loads
|
||||
from logging import DEBUG, INFO, basicConfig, getLogger
|
||||
from os import getenv, path
|
||||
from subprocess import call
|
||||
from traceback import format_exc
|
||||
|
||||
import aiohttp_cors
|
||||
# Partial imports
|
||||
@@ -70,7 +71,7 @@ class PluginManager:
|
||||
)
|
||||
})
|
||||
self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
|
||||
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.plugin_loader.plugins)
|
||||
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.plugin_loader.plugins, self.plugin_loader)
|
||||
self.settings = SettingsManager("loader", path.join(HOMEBREW_PATH, "settings"))
|
||||
self.utilities = Utilities(self)
|
||||
self.updater = Updater(self)
|
||||
@@ -123,9 +124,9 @@ class PluginManager:
|
||||
|
||||
async def inject_javascript(self, request=None):
|
||||
try:
|
||||
await inject_to_tab("SP", "try{window.deckyHasLoaded = true;(async()=>{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')})();}catch(e){console.error(e)}", True)
|
||||
await inject_to_tab("SP", "try{if (window.deckyHasLoaded) location.reload();window.deckyHasLoaded = true;(async()=>{while(!window.SP_REACT){await new Promise(r => setTimeout(r, 10))};await import('http://localhost:1337/frontend/index.js')})();}catch(e){console.error(e)}", True)
|
||||
except:
|
||||
logger.info("Failed to inject JavaScript into tab")
|
||||
logger.info("Failed to inject JavaScript into tab\n" + format_exc())
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
|
||||
+22
-14
@@ -5,6 +5,8 @@ from asyncio import (Lock, get_event_loop, new_event_loop,
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from json import dumps, load, loads
|
||||
from logging import getLogger
|
||||
from traceback import format_exc
|
||||
from os import path, setgid, setuid
|
||||
from signal import SIGINT, signal
|
||||
from sys import exit
|
||||
@@ -40,28 +42,34 @@ class PluginWrapper:
|
||||
self.author = json["author"]
|
||||
self.flags = json["flags"]
|
||||
|
||||
self.log = getLogger("plugin")
|
||||
|
||||
self.passive = not path.isfile(self.file)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def _init(self):
|
||||
signal(SIGINT, lambda s, f: exit(0))
|
||||
try:
|
||||
signal(SIGINT, lambda s, f: exit(0))
|
||||
|
||||
set_event_loop(new_event_loop())
|
||||
if self.passive:
|
||||
return
|
||||
setgid(0 if "root" in self.flags else 1000)
|
||||
setuid(0 if "root" in self.flags else 1000)
|
||||
spec = spec_from_file_location("_", self.file)
|
||||
module = module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
self.Plugin = module.Plugin
|
||||
set_event_loop(new_event_loop())
|
||||
if self.passive:
|
||||
return
|
||||
setgid(0 if "root" in self.flags else 1000)
|
||||
setuid(0 if "root" in self.flags else 1000)
|
||||
spec = spec_from_file_location("_", self.file)
|
||||
module = module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
self.Plugin = module.Plugin
|
||||
|
||||
if hasattr(self.Plugin, "_main"):
|
||||
get_event_loop().create_task(self.Plugin._main(self.Plugin))
|
||||
get_event_loop().create_task(self._setup_socket())
|
||||
get_event_loop().run_forever()
|
||||
if hasattr(self.Plugin, "_main"):
|
||||
get_event_loop().create_task(self.Plugin._main(self.Plugin))
|
||||
get_event_loop().create_task(self._setup_socket())
|
||||
get_event_loop().run_forever()
|
||||
except:
|
||||
self.log.error("Failed to start " + self.name + "!\n" + format_exc())
|
||||
exit(0)
|
||||
|
||||
async def _setup_socket(self):
|
||||
self.socket = await start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT)
|
||||
|
||||
+24
-12
@@ -20,7 +20,7 @@ class Updater:
|
||||
self.settings = self.context.settings
|
||||
# Exposes updater methods to frontend
|
||||
self.updater_methods = {
|
||||
"get_branch": self.get_branch,
|
||||
"get_branch": self._get_branch,
|
||||
"get_version": self.get_version,
|
||||
"do_update": self.do_update,
|
||||
"do_restart": self.do_restart,
|
||||
@@ -29,18 +29,18 @@ class Updater:
|
||||
self.remoteVer = None
|
||||
self.allRemoteVers = None
|
||||
try:
|
||||
self.currentBranch = self.get_branch(self.context.settings)
|
||||
if int(self.currentBranch) == -1:
|
||||
raise ValueError("get_branch could not determine branch!")
|
||||
except:
|
||||
self.currentBranch = 0
|
||||
logger.error("Current branch could not be determined, defaulting to \"Stable\"")
|
||||
try:
|
||||
logger.info(getcwd())
|
||||
with open(path.join(getcwd(), ".loader.version"), 'r') as version_file:
|
||||
self.localVer = version_file.readline().replace("\n", "")
|
||||
except:
|
||||
self.localVer = False
|
||||
|
||||
try:
|
||||
self.currentBranch = self.get_branch(self.context.settings)
|
||||
except:
|
||||
self.currentBranch = 0
|
||||
logger.error("Current branch could not be determined, defaulting to \"Stable\"")
|
||||
|
||||
if context:
|
||||
context.web_app.add_routes([
|
||||
web.post("/updater/{method_name}", self._handle_server_method_call)
|
||||
@@ -63,9 +63,21 @@ class Updater:
|
||||
res["success"] = False
|
||||
return web.json_response(res)
|
||||
|
||||
async def get_branch(self, manager: SettingsManager):
|
||||
logger.debug("current branch: %i" % manager.getSetting("branch", -1))
|
||||
return manager.getSetting("branch", -1)
|
||||
def get_branch(self, manager: SettingsManager):
|
||||
ver = manager.getSetting("branch", -1)
|
||||
logger.debug("current branch: %i" % ver)
|
||||
if ver == -1:
|
||||
logger.info("Current branch is not set, determining branch from version...")
|
||||
if self.localVer.startswith("v") and self.localVer.find("-pre"):
|
||||
logger.info("Current version determined to be pre-release")
|
||||
return 1
|
||||
else:
|
||||
logger.info("Current version determined to be stable")
|
||||
return 0
|
||||
return ver
|
||||
|
||||
async def _get_branch(self, manager: SettingsManager):
|
||||
return self.get_branch(manager)
|
||||
|
||||
async def get_version(self):
|
||||
if self.localVer:
|
||||
@@ -80,7 +92,7 @@ class Updater:
|
||||
|
||||
async def check_for_updates(self):
|
||||
logger.debug("checking for updates")
|
||||
selectedBranch = await self.get_branch(self.context.settings)
|
||||
selectedBranch = self.get_branch(self.context.settings)
|
||||
async with ClientSession() as web:
|
||||
async with web.request("GET", "https://api.github.com/repos/SteamDeckHomebrew/decky-loader/releases", ssl=helpers.get_ssl_context()) as res:
|
||||
remoteVersions = await res.json()
|
||||
|
||||
+29
-1
@@ -1,4 +1,5 @@
|
||||
import uuid
|
||||
import os
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
from aiohttp import ClientSession, web
|
||||
@@ -24,7 +25,8 @@ class Utilities:
|
||||
"allow_remote_debugging": self.allow_remote_debugging,
|
||||
"disallow_remote_debugging": self.disallow_remote_debugging,
|
||||
"set_setting": self.set_setting,
|
||||
"get_setting": self.get_setting
|
||||
"get_setting": self.get_setting,
|
||||
"filepicker_ls": self.filepicker_ls
|
||||
}
|
||||
|
||||
if context:
|
||||
@@ -166,3 +168,29 @@ class Utilities:
|
||||
async def disallow_remote_debugging(self):
|
||||
await helpers.stop_systemd_unit(helpers.REMOTE_DEBUGGER_UNIT)
|
||||
return True
|
||||
|
||||
async def filepicker_ls(self, path, include_files=True):
|
||||
# def sorter(file): # Modification time
|
||||
# if os.path.isdir(os.path.join(path, file)) or os.path.isfile(os.path.join(path, file)):
|
||||
# return os.path.getmtime(os.path.join(path, file))
|
||||
# return 0
|
||||
# file_names = sorted(os.listdir(path), key=sorter, reverse=True) # TODO provide more sort options
|
||||
file_names = sorted(os.listdir(path)) # Alphabetical
|
||||
|
||||
files = []
|
||||
|
||||
for file in file_names:
|
||||
full_path = os.path.join(path, file)
|
||||
is_dir = os.path.isdir(full_path)
|
||||
|
||||
if is_dir or include_files:
|
||||
files.append({
|
||||
"isdir": is_dir,
|
||||
"name": file,
|
||||
"realpath": os.path.realpath(full_path)
|
||||
})
|
||||
|
||||
return {
|
||||
"realpath": os.path.realpath(path),
|
||||
"files": files
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"@rollup/plugin-replace": "^4.0.0",
|
||||
"@rollup/plugin-typescript": "^8.3.3",
|
||||
"@types/react": "16.14.0",
|
||||
"@types/react-file-icon": "^1.0.1",
|
||||
"@types/react-router": "5.1.18",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"husky": "^8.0.1",
|
||||
@@ -27,6 +28,7 @@
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"rollup": "^2.76.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-external-globals": "^0.6.1",
|
||||
"rollup-plugin-polyfill-node": "^0.10.2",
|
||||
"tslib": "^2.4.0",
|
||||
@@ -39,7 +41,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"decky-frontend-lib": "^2.0.0",
|
||||
"decky-frontend-lib": "^3.1.3",
|
||||
"react-file-icon": "^1.2.0",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"remark-gfm": "^3.0.1"
|
||||
|
||||
Generated
+263
-6
@@ -7,9 +7,10 @@ specifiers:
|
||||
'@rollup/plugin-replace': ^4.0.0
|
||||
'@rollup/plugin-typescript': ^8.3.3
|
||||
'@types/react': 16.14.0
|
||||
'@types/react-file-icon': ^1.0.1
|
||||
'@types/react-router': 5.1.18
|
||||
'@types/webpack': ^5.28.0
|
||||
decky-frontend-lib: ^2.0.0
|
||||
decky-frontend-lib: ^3.1.3
|
||||
husky: ^8.0.1
|
||||
import-sort-style-module: ^6.0.0
|
||||
inquirer: ^8.2.4
|
||||
@@ -17,17 +18,20 @@ specifiers:
|
||||
prettier-plugin-import-sort: ^0.0.7
|
||||
react: 16.14.0
|
||||
react-dom: 16.14.0
|
||||
react-file-icon: ^1.2.0
|
||||
react-icons: ^4.4.0
|
||||
react-markdown: ^8.0.3
|
||||
remark-gfm: ^3.0.1
|
||||
rollup: ^2.76.0
|
||||
rollup-plugin-delete: ^2.0.0
|
||||
rollup-plugin-external-globals: ^0.6.1
|
||||
rollup-plugin-polyfill-node: ^0.10.2
|
||||
tslib: ^2.4.0
|
||||
typescript: ^4.7.4
|
||||
|
||||
dependencies:
|
||||
decky-frontend-lib: 2.0.0
|
||||
decky-frontend-lib: 3.1.3
|
||||
react-file-icon: 1.2.0_wcqkhtmu7mswc6yz4uyexck3ty
|
||||
react-icons: 4.4.0_react@16.14.0
|
||||
react-markdown: 8.0.3_vshvapmxg47tngu7tvrsqpq55u
|
||||
remark-gfm: 3.0.1
|
||||
@@ -39,6 +43,7 @@ devDependencies:
|
||||
'@rollup/plugin-replace': 4.0.0_rollup@2.76.0
|
||||
'@rollup/plugin-typescript': 8.3.3_mrkdcqv53wzt2ybukxlrvz47fu
|
||||
'@types/react': 16.14.0
|
||||
'@types/react-file-icon': 1.0.1
|
||||
'@types/react-router': 5.1.18
|
||||
'@types/webpack': 5.28.0
|
||||
husky: 8.0.1
|
||||
@@ -49,6 +54,7 @@ devDependencies:
|
||||
react: 16.14.0
|
||||
react-dom: 16.14.0_react@16.14.0
|
||||
rollup: 2.76.0
|
||||
rollup-plugin-delete: 2.0.0
|
||||
rollup-plugin-external-globals: 0.6.1_rollup@2.76.0
|
||||
rollup-plugin-polyfill-node: 0.10.2_rollup@2.76.0
|
||||
tslib: 2.4.0
|
||||
@@ -296,6 +302,27 @@ packages:
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
run-parallel: 1.2.0
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.stat/2.0.5:
|
||||
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.walk/1.2.8:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
engines: {node: '>= 8'}
|
||||
dependencies:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.13.0
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-commonjs/21.1.0_rollup@2.76.0:
|
||||
resolution: {integrity: sha512-6ZtHx3VHIp2ReNNDxHjuUml6ur+WcQ28N1yHgCQwsbNkQg2suhxGMDQGJOn/KuDxKtd1xuZP5xSTwBA4GQ8hbA==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
@@ -427,6 +454,13 @@ packages:
|
||||
resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
|
||||
dev: true
|
||||
|
||||
/@types/glob/7.2.0:
|
||||
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
|
||||
dependencies:
|
||||
'@types/minimatch': 5.1.2
|
||||
'@types/node': 18.0.4
|
||||
dev: true
|
||||
|
||||
/@types/hast/2.3.4:
|
||||
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
|
||||
dependencies:
|
||||
@@ -451,6 +485,10 @@ packages:
|
||||
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
|
||||
dev: false
|
||||
|
||||
/@types/minimatch/5.1.2:
|
||||
resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
|
||||
dev: true
|
||||
|
||||
/@types/ms/0.7.31:
|
||||
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
|
||||
dev: false
|
||||
@@ -462,6 +500,12 @@ packages:
|
||||
/@types/prop-types/15.7.5:
|
||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
||||
|
||||
/@types/react-file-icon/1.0.1:
|
||||
resolution: {integrity: sha512-QTdYCkYXzh/PfKEIwcPxRdaPQkii5R4Ke7fcO+KB++IDPbYAG1jj+ulEcTA7pRf0gZ5jAvjWcTXBJJRtfYHjlw==}
|
||||
dependencies:
|
||||
'@types/react': 16.14.0
|
||||
dev: true
|
||||
|
||||
/@types/react-router/5.1.18:
|
||||
resolution: {integrity: sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==}
|
||||
dependencies:
|
||||
@@ -626,6 +670,14 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/aggregate-error/3.1.0:
|
||||
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
clean-stack: 2.2.0
|
||||
indent-string: 4.0.0
|
||||
dev: true
|
||||
|
||||
/ajv-keywords/3.5.2_ajv@6.12.6:
|
||||
resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==}
|
||||
peerDependencies:
|
||||
@@ -675,6 +727,11 @@ packages:
|
||||
sprintf-js: 1.0.3
|
||||
dev: true
|
||||
|
||||
/array-union/2.1.0:
|
||||
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/bail/2.0.2:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
dev: false
|
||||
@@ -702,6 +759,13 @@ packages:
|
||||
concat-map: 0.0.1
|
||||
dev: true
|
||||
|
||||
/braces/3.0.2:
|
||||
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
fill-range: 7.0.1
|
||||
dev: true
|
||||
|
||||
/browserslist/4.21.2:
|
||||
resolution: {integrity: sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==}
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
@@ -786,6 +850,11 @@ packages:
|
||||
engines: {node: '>=6.0'}
|
||||
dev: true
|
||||
|
||||
/clean-stack/2.2.0:
|
||||
resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/cli-cursor/3.1.0:
|
||||
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -875,8 +944,8 @@ packages:
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/decky-frontend-lib/2.0.0:
|
||||
resolution: {integrity: sha512-H7+JpKHlClECVpo+MCEwej7R9wDWk9M2uMSyTvuhTfLZe3RThsxWCiqY640Cjh/zIW2A7GyVRd4SjLtn6Isdeg==}
|
||||
/decky-frontend-lib/3.1.3:
|
||||
resolution: {integrity: sha512-X6DGi90VdXnyoQi8Q4jEYhvBiNQualMNwXWKwgFitdor2ktNj5xp3a4uIi1ijbnS/vdW2AGKjraTfOMXtHUNAg==}
|
||||
dependencies:
|
||||
minimist: 1.2.6
|
||||
dev: false
|
||||
@@ -898,6 +967,20 @@ packages:
|
||||
clone: 1.0.4
|
||||
dev: true
|
||||
|
||||
/del/5.1.0:
|
||||
resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
globby: 10.0.2
|
||||
graceful-fs: 4.2.10
|
||||
is-glob: 4.0.3
|
||||
is-path-cwd: 2.2.0
|
||||
is-path-inside: 3.0.3
|
||||
p-map: 3.0.0
|
||||
rimraf: 3.0.2
|
||||
slash: 3.0.0
|
||||
dev: true
|
||||
|
||||
/dequal/2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -913,6 +996,13 @@ packages:
|
||||
engines: {node: '>=0.3.1'}
|
||||
dev: false
|
||||
|
||||
/dir-glob/3.0.1:
|
||||
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
path-type: 4.0.0
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium/1.4.189:
|
||||
resolution: {integrity: sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==}
|
||||
dev: true
|
||||
@@ -1015,10 +1105,27 @@ packages:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
dev: true
|
||||
|
||||
/fast-glob/3.2.11:
|
||||
resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==}
|
||||
engines: {node: '>=8.6.0'}
|
||||
dependencies:
|
||||
'@nodelib/fs.stat': 2.0.5
|
||||
'@nodelib/fs.walk': 1.2.8
|
||||
glob-parent: 5.1.2
|
||||
merge2: 1.4.1
|
||||
micromatch: 4.0.5
|
||||
dev: true
|
||||
|
||||
/fast-json-stable-stringify/2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
dev: true
|
||||
|
||||
/fastq/1.13.0:
|
||||
resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==}
|
||||
dependencies:
|
||||
reusify: 1.0.4
|
||||
dev: true
|
||||
|
||||
/figures/3.2.0:
|
||||
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1026,6 +1133,13 @@ packages:
|
||||
escape-string-regexp: 1.0.5
|
||||
dev: true
|
||||
|
||||
/fill-range/7.0.1:
|
||||
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
dev: true
|
||||
|
||||
/find-line-column/0.5.2:
|
||||
resolution: {integrity: sha512-eNhNkDt5RbxY4X++JwyDURP62FYhV1bh9LF4dfOiwpVCTk5vvfEANhnui5ypUEELGR02QZSrWFtaTgd4ulW5tw==}
|
||||
dev: true
|
||||
@@ -1055,6 +1169,13 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/glob-parent/5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
dev: true
|
||||
|
||||
/glob-to-regexp/0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
dev: true
|
||||
@@ -1075,6 +1196,20 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/globby/10.0.2:
|
||||
resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
'@types/glob': 7.2.0
|
||||
array-union: 2.1.0
|
||||
dir-glob: 3.0.1
|
||||
fast-glob: 3.2.11
|
||||
glob: 7.2.3
|
||||
ignore: 5.2.0
|
||||
merge2: 1.4.1
|
||||
slash: 3.0.0
|
||||
dev: true
|
||||
|
||||
/graceful-fs/4.2.10:
|
||||
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
|
||||
dev: true
|
||||
@@ -1117,6 +1252,11 @@ packages:
|
||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||
dev: true
|
||||
|
||||
/ignore/5.2.0:
|
||||
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
|
||||
engines: {node: '>= 4'}
|
||||
dev: true
|
||||
|
||||
/import-fresh/2.0.0:
|
||||
resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1174,6 +1314,11 @@ packages:
|
||||
resolve: 1.22.1
|
||||
dev: true
|
||||
|
||||
/indent-string/4.0.0:
|
||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/inflight/1.0.6:
|
||||
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
|
||||
dependencies:
|
||||
@@ -1237,11 +1382,23 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-extglob/2.1.1:
|
||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/is-fullwidth-code-point/3.0.0:
|
||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/is-glob/4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
dev: true
|
||||
|
||||
/is-interactive/1.0.0:
|
||||
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1251,6 +1408,21 @@ packages:
|
||||
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
|
||||
dev: true
|
||||
|
||||
/is-number/7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
|
||||
/is-path-cwd/2.2.0:
|
||||
resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/is-path-inside/3.0.3:
|
||||
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/is-plain-obj/4.1.0:
|
||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -1321,6 +1493,10 @@ packages:
|
||||
engines: {node: '>=6.11.5'}
|
||||
dev: true
|
||||
|
||||
/lodash.uniqueid/4.0.1:
|
||||
resolution: {integrity: sha512-GQQWaIeGlL6DIIr06kj1j6sSmBxyNMwI8kaX9aKpHR/XsMTiaXDVPNPAkiboOTK9OJpTJF/dXT3xYoFQnj386Q==}
|
||||
dev: false
|
||||
|
||||
/lodash/4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: true
|
||||
@@ -1483,6 +1659,11 @@ packages:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
dev: true
|
||||
|
||||
/merge2/1.4.1:
|
||||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
dev: true
|
||||
|
||||
/micromark-core-commonmark/1.0.6:
|
||||
resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==}
|
||||
dependencies:
|
||||
@@ -1732,6 +1913,14 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/micromatch/4.0.5:
|
||||
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
|
||||
engines: {node: '>=8.6'}
|
||||
dependencies:
|
||||
braces: 3.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/mime-db/1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -1816,6 +2005,13 @@ packages:
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/p-map/3.0.0:
|
||||
resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
aggregate-error: 3.1.0
|
||||
dev: true
|
||||
|
||||
/parse-json/4.0.0:
|
||||
resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1833,6 +2029,11 @@ packages:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
|
||||
/path-type/4.0.0:
|
||||
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/picocolors/1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
@@ -1878,6 +2079,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/queue-microtask/1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
|
||||
/randombytes/2.1.0:
|
||||
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
|
||||
dependencies:
|
||||
@@ -1894,7 +2099,19 @@ packages:
|
||||
prop-types: 15.8.1
|
||||
react: 16.14.0
|
||||
scheduler: 0.19.1
|
||||
dev: true
|
||||
|
||||
/react-file-icon/1.2.0_wcqkhtmu7mswc6yz4uyexck3ty:
|
||||
resolution: {integrity: sha512-BI8CTyZu/k8AmhjGJiGYOqgjfp2si2Lt5PUNF6kfF31c7BFYJeerpfHnZBfpPjrb2M/DAdW1qNub17Rt+xuefQ==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^17.0.0 || ^16.2.0
|
||||
react-dom: ^18.0.0 || ^17.0.0 || ^16.2.0
|
||||
dependencies:
|
||||
lodash.uniqueid: 4.0.1
|
||||
prop-types: 15.8.1
|
||||
react: 16.14.0
|
||||
react-dom: 16.14.0_react@16.14.0
|
||||
tinycolor2: 1.4.2
|
||||
dev: false
|
||||
|
||||
/react-icons/4.4.0_react@16.14.0:
|
||||
resolution: {integrity: sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg==}
|
||||
@@ -2012,6 +2229,25 @@ packages:
|
||||
signal-exit: 3.0.7
|
||||
dev: true
|
||||
|
||||
/reusify/1.0.4:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/rimraf/3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-delete/2.0.0:
|
||||
resolution: {integrity: sha512-/VpLMtDy+8wwRlDANuYmDa9ss/knGsAgrDhM+tEwB1npHwNu4DYNmDfUL55csse/GHs9Q+SMT/rw9uiaZ3pnzA==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
del: 5.1.0
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-external-globals/0.6.1_rollup@2.76.0:
|
||||
resolution: {integrity: sha512-mlp3KNa5sE4Sp9UUR2rjBrxjG79OyZAh/QC18RHIjM+iYkbBwNXSo8DHRMZWtzJTrH8GxQ+SJvCTN3i14uMXIA==}
|
||||
peerDependencies:
|
||||
@@ -2046,6 +2282,12 @@ packages:
|
||||
engines: {node: '>=0.12.0'}
|
||||
dev: true
|
||||
|
||||
/run-parallel/1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
dev: true
|
||||
|
||||
/rxjs/7.5.6:
|
||||
resolution: {integrity: sha512-dnyv2/YsXhnm461G+R/Pe5bWP41Nm6LBXEYWI6eiFP4fiwx6WRI/CD0zbdVAudd9xwLEF2IDcKXLHit0FYjUzw==}
|
||||
dependencies:
|
||||
@@ -2076,7 +2318,6 @@ packages:
|
||||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
object-assign: 4.1.1
|
||||
dev: true
|
||||
|
||||
/schema-utils/3.1.1:
|
||||
resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==}
|
||||
@@ -2102,6 +2343,11 @@ packages:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
dev: true
|
||||
|
||||
/slash/3.0.0:
|
||||
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/source-map-support/0.5.21:
|
||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||
dependencies:
|
||||
@@ -2224,6 +2470,10 @@ packages:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
dev: true
|
||||
|
||||
/tinycolor2/1.4.2:
|
||||
resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==}
|
||||
dev: false
|
||||
|
||||
/tmp/0.0.33:
|
||||
resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
|
||||
engines: {node: '>=0.6.0'}
|
||||
@@ -2236,6 +2486,13 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/to-regex-range/5.0.1:
|
||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||
engines: {node: '>=8.0'}
|
||||
dependencies:
|
||||
is-number: 7.0.0
|
||||
dev: true
|
||||
|
||||
/trim-lines/3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
dev: false
|
||||
|
||||
@@ -2,6 +2,7 @@ import commonjs from '@rollup/plugin-commonjs';
|
||||
import json from '@rollup/plugin-json';
|
||||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import externalGlobals from "rollup-plugin-external-globals";
|
||||
import del from 'rollup-plugin-delete'
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { defineConfig } from 'rollup';
|
||||
@@ -9,6 +10,7 @@ import { defineConfig } from 'rollup';
|
||||
export default defineConfig({
|
||||
input: 'src/index.tsx',
|
||||
plugins: [
|
||||
del({ targets: "../backend/static/*", force: true }),
|
||||
commonjs(),
|
||||
nodeResolve(),
|
||||
externalGlobals({
|
||||
|
||||
@@ -1,9 +1,41 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import { Focusable } from 'decky-frontend-lib';
|
||||
import { FunctionComponent, useRef } from 'react';
|
||||
import ReactMarkdown, { Options as ReactMarkdownOptions } from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
const Markdown: FunctionComponent<ReactMarkdownOptions> = (props) => {
|
||||
return <ReactMarkdown remarkPlugins={[remarkGfm]} {...props} />;
|
||||
interface MarkdownProps extends ReactMarkdownOptions {
|
||||
onDismiss?: () => void;
|
||||
}
|
||||
|
||||
const Markdown: FunctionComponent<MarkdownProps> = (props) => {
|
||||
return (
|
||||
<Focusable>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
components={{
|
||||
div: (nodeProps) => <Focusable {...nodeProps.node.properties}>{nodeProps.children}</Focusable>,
|
||||
a: (nodeProps) => {
|
||||
const aRef = useRef<HTMLAnchorElement>(null);
|
||||
return (
|
||||
// TODO fix focus ring
|
||||
<Focusable
|
||||
onActivate={() => {}}
|
||||
onOKButton={() => {
|
||||
aRef?.current?.click();
|
||||
props.onDismiss?.();
|
||||
}}
|
||||
>
|
||||
<a ref={aRef} {...nodeProps.node.properties}>
|
||||
{nodeProps.children}
|
||||
</a>
|
||||
</Focusable>
|
||||
);
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</Focusable>
|
||||
);
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ToastData, findModule, findModuleChild, joinClassNames } from 'decky-frontend-lib';
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
import { ToastData, findModule, joinClassNames } from 'decky-frontend-lib';
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
interface ToastProps {
|
||||
toast: {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Focusable, SteamSpinner } from 'decky-frontend-lib';
|
||||
import { FunctionComponent, ReactElement, ReactNode, Suspense } from 'react';
|
||||
|
||||
interface WithSuspenseProps {
|
||||
children: ReactNode;
|
||||
route?: boolean;
|
||||
}
|
||||
|
||||
// Nice little wrapper around Suspense so we don't have to duplicate the styles and code for the loading spinner
|
||||
const WithSuspense: FunctionComponent<WithSuspenseProps> = (props) => {
|
||||
const propsCopy = { ...props };
|
||||
delete propsCopy.children;
|
||||
(props.children as ReactElement)?.props && Object.assign((props.children as ReactElement).props, propsCopy); // There is probably a better way to do this but valve does it this way so ¯\_(ツ)_/¯
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<Focusable
|
||||
// needed to enable focus ring so that the focus properly resets on load
|
||||
onActivate={() => {}}
|
||||
style={{
|
||||
overflowY: 'scroll',
|
||||
backgroundColor: 'transparent',
|
||||
...(props.route && {
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<SteamSpinner />
|
||||
</Focusable>
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
export default WithSuspense;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModalRoot, QuickAccessTab, Router, Spinner, staticClasses } from 'decky-frontend-lib';
|
||||
import { ConfirmModal, QuickAccessTab, Router, Spinner, staticClasses } from 'decky-frontend-lib';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
interface PluginInstallModalProps {
|
||||
@@ -14,7 +14,7 @@ interface PluginInstallModalProps {
|
||||
const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, hash, onOK, onCancel, closeModal }) => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
return (
|
||||
<ModalRoot
|
||||
<ConfirmModal
|
||||
bOKDisabled={loading}
|
||||
closeModal={closeModal}
|
||||
onOK={async () => {
|
||||
@@ -34,7 +34,7 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
|
||||
{!loading && '?'}
|
||||
</div>
|
||||
</div>
|
||||
</ModalRoot>
|
||||
</ConfirmModal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
// https://codesandbox.io/s/react-file-icon-colored-tmwut?file=/src/App.js
|
||||
import { FileIconProps } from 'react-file-icon';
|
||||
|
||||
type T_FileExtList = string[];
|
||||
|
||||
const styleDef: [FileIconProps, T_FileExtList][] = [];
|
||||
|
||||
// video ////////////////////////////////////
|
||||
const videoStyle = {
|
||||
color: '#f00f0f',
|
||||
};
|
||||
const videoExtList = [
|
||||
'avi',
|
||||
'3g2',
|
||||
'3gp',
|
||||
'aep',
|
||||
'asf',
|
||||
'flv',
|
||||
'm4v',
|
||||
'mkv',
|
||||
'mov',
|
||||
'mp4',
|
||||
'mpeg',
|
||||
'mpg',
|
||||
'ogv',
|
||||
'pr',
|
||||
'swfw',
|
||||
'webm',
|
||||
'wmv',
|
||||
'swf',
|
||||
'rm',
|
||||
];
|
||||
|
||||
styleDef.push([videoStyle, videoExtList]);
|
||||
|
||||
// image ////////////////////////////////////
|
||||
const imageStyle = {
|
||||
color: '#d18f00',
|
||||
};
|
||||
|
||||
const imageExtList = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'tif', 'tiff'];
|
||||
|
||||
styleDef.push([imageStyle, imageExtList]);
|
||||
|
||||
// zip ////////////////////////////////////
|
||||
const zipStyle = {
|
||||
color: '#f7b500',
|
||||
labelTextColor: '#000',
|
||||
// glyphColor: "#de9400"
|
||||
};
|
||||
|
||||
const zipExtList = ['zip', 'zipx', '7zip', 'tar', 'sitx', 'gz', 'rar'];
|
||||
|
||||
styleDef.push([zipStyle, zipExtList]);
|
||||
|
||||
// audio ////////////////////////////////////
|
||||
const audioStyle = {
|
||||
color: '#f00f0f',
|
||||
};
|
||||
|
||||
const audioExtList = ['aac', 'aif', 'aiff', 'flac', 'm4a', 'mid', 'mp3', 'ogg', 'wav'];
|
||||
|
||||
styleDef.push([audioStyle, audioExtList]);
|
||||
|
||||
// text ////////////////////////////////////
|
||||
const textStyle = {
|
||||
color: '#ffffff',
|
||||
glyphColor: '#787878',
|
||||
};
|
||||
|
||||
const textExtList = ['cue', 'odt', 'md', 'rtf', 'txt', 'tex', 'wpd', 'wps', 'xlr', 'fodt'];
|
||||
|
||||
styleDef.push([textStyle, textExtList]);
|
||||
|
||||
// system ////////////////////////////////////
|
||||
const systemStyle = {
|
||||
color: '#111',
|
||||
};
|
||||
|
||||
const systemExtList = ['exe', 'ini', 'dll', 'plist', 'sys'];
|
||||
|
||||
styleDef.push([systemStyle, systemExtList]);
|
||||
|
||||
// srcCode ////////////////////////////////////
|
||||
const srcCodeStyle = {
|
||||
glyphColor: '#787878',
|
||||
color: '#ffffff',
|
||||
};
|
||||
|
||||
const srcCodeExtList = [
|
||||
'asp',
|
||||
'aspx',
|
||||
'c',
|
||||
'cpp',
|
||||
'cs',
|
||||
'css',
|
||||
'scss',
|
||||
'py',
|
||||
'json',
|
||||
'htm',
|
||||
'html',
|
||||
'java',
|
||||
'yml',
|
||||
'php',
|
||||
'js',
|
||||
'ts',
|
||||
'rb',
|
||||
'jsx',
|
||||
'tsx',
|
||||
];
|
||||
|
||||
styleDef.push([srcCodeStyle, srcCodeExtList]);
|
||||
|
||||
// vector ////////////////////////////////////
|
||||
const vectorStyle = {
|
||||
color: '#ffe600',
|
||||
};
|
||||
|
||||
const vectorExtList = ['dwg', 'dxf', 'ps', 'svg', 'eps'];
|
||||
|
||||
styleDef.push([vectorStyle, vectorExtList]);
|
||||
|
||||
// font ////////////////////////////////////
|
||||
const fontStyle = {
|
||||
color: '#555',
|
||||
};
|
||||
|
||||
const fontExtList = ['fnt', 'ttf', 'otf', 'fon', 'eot', 'woff'];
|
||||
|
||||
styleDef.push([fontStyle, fontExtList]);
|
||||
|
||||
// objectModel ////////////////////////////////////
|
||||
const objectModelStyle = {
|
||||
color: '#bf6a02',
|
||||
glyphColor: '#bf6a02',
|
||||
};
|
||||
|
||||
const objectModelExtList = ['3dm', '3ds', 'max', 'obj', 'pkg'];
|
||||
|
||||
styleDef.push([objectModelStyle, objectModelExtList]);
|
||||
|
||||
// sheet ////////////////////////////////////
|
||||
const sheetStyle = {
|
||||
color: '#2a6e00',
|
||||
};
|
||||
|
||||
const sheetExtList = ['csv', 'fods', 'ods', 'xlr'];
|
||||
|
||||
styleDef.push([sheetStyle, sheetExtList]);
|
||||
|
||||
// const defaultStyle: Record<string, FileIconProps> = {
|
||||
// pdf: {
|
||||
// glyphColor: "white",
|
||||
// color: "#D93831"
|
||||
// }
|
||||
// };
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
function createStyleObj(extList: T_FileExtList, styleObj: Partial<FileIconProps>) {
|
||||
return Object.fromEntries(
|
||||
extList.map((ext) => {
|
||||
return [ext, { ...styleObj, glyphColor: 'white' }];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export const styleDefObj = styleDef.reduce((acc, [fileStyle, fileExtList]) => {
|
||||
return { ...acc, ...createStyleObj(fileExtList, fileStyle) };
|
||||
});
|
||||
@@ -0,0 +1,160 @@
|
||||
import { DialogButton, Focusable, SteamSpinner, TextField } from 'decky-frontend-lib';
|
||||
import { useEffect } from 'react';
|
||||
import { FunctionComponent, useState } from 'react';
|
||||
import { FileIcon, defaultStyles } from 'react-file-icon';
|
||||
import { FaArrowUp, FaFolder } from 'react-icons/fa';
|
||||
|
||||
import Logger from '../../../logger';
|
||||
import { styleDefObj } from './iconCustomizations';
|
||||
|
||||
const logger = new Logger('FilePicker');
|
||||
|
||||
export interface FilePickerProps {
|
||||
startPath: string;
|
||||
includeFiles?: boolean;
|
||||
regex?: RegExp;
|
||||
onSubmit: (val: { path: string; realpath: string }) => void;
|
||||
closeModal?: () => void;
|
||||
}
|
||||
|
||||
interface File {
|
||||
isdir: boolean;
|
||||
name: string;
|
||||
realpath: string;
|
||||
}
|
||||
|
||||
interface FileListing {
|
||||
realpath: string;
|
||||
files: File[];
|
||||
}
|
||||
|
||||
function getList(
|
||||
path: string,
|
||||
includeFiles: boolean = true,
|
||||
): Promise<{ result: FileListing | string; success: boolean }> {
|
||||
return window.DeckyPluginLoader.callServerMethod('filepicker_ls', { path, include_files: includeFiles });
|
||||
}
|
||||
|
||||
const iconStyles = {
|
||||
paddingRight: '10px',
|
||||
width: '1em',
|
||||
};
|
||||
|
||||
const FilePicker: FunctionComponent<FilePickerProps> = ({
|
||||
startPath,
|
||||
includeFiles = true,
|
||||
regex,
|
||||
onSubmit,
|
||||
closeModal,
|
||||
}) => {
|
||||
if (startPath.endsWith('/')) startPath = startPath.substring(0, startPath.length - 1); // remove trailing path
|
||||
const [path, setPath] = useState<string>(startPath);
|
||||
const [listing, setListing] = useState<FileListing>({ files: [], realpath: path });
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (error) setError(null);
|
||||
setLoading(true);
|
||||
const listing = await getList(path, includeFiles);
|
||||
if (!listing.success) {
|
||||
setListing({ files: [], realpath: path });
|
||||
setLoading(false);
|
||||
setError(listing.result as string);
|
||||
logger.error(listing.result);
|
||||
return;
|
||||
}
|
||||
setLoading(false);
|
||||
setListing(listing.result as FileListing);
|
||||
logger.log('reloaded', path, listing);
|
||||
})();
|
||||
}, [path]);
|
||||
|
||||
return (
|
||||
<div className="deckyFilePicker">
|
||||
<Focusable style={{ display: 'flex', flexDirection: 'row', paddingBottom: '10px' }}>
|
||||
<DialogButton
|
||||
style={{
|
||||
minWidth: 'unset',
|
||||
width: '40px',
|
||||
flexGrow: '0',
|
||||
borderRadius: 'unset',
|
||||
margin: '0',
|
||||
padding: '10px',
|
||||
}}
|
||||
onClick={() => {
|
||||
const newPathArr = path.split('/');
|
||||
newPathArr.pop();
|
||||
let newPath = newPathArr.join('/');
|
||||
if (newPath == '') newPath = '/';
|
||||
setPath(newPath);
|
||||
}}
|
||||
>
|
||||
<FaArrowUp />
|
||||
</DialogButton>
|
||||
<div style={{ flexGrow: '1', width: '100%' }}>
|
||||
<TextField
|
||||
value={path}
|
||||
onChange={(e) => {
|
||||
e.target.value && setPath(e.target.value);
|
||||
}}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
</Focusable>
|
||||
<Focusable style={{ display: 'flex', flexDirection: 'column', height: '60vh', overflow: 'scroll' }}>
|
||||
{loading && <SteamSpinner style={{ height: '100%' }} />}
|
||||
{!loading &&
|
||||
listing.files
|
||||
.filter((file) => (includeFiles || file.isdir) && (!regex || regex.test(file.name)))
|
||||
.map((file) => {
|
||||
let extension = file.realpath.split('.').pop() as string;
|
||||
return (
|
||||
<DialogButton
|
||||
style={{ borderRadius: 'unset', margin: '0', padding: '10px' }}
|
||||
onClick={() => {
|
||||
const fullPath = `${path}${path.endsWith('/') ? '' : '/'}${file.name}`;
|
||||
if (file.isdir) setPath(fullPath);
|
||||
else {
|
||||
onSubmit({ path: fullPath, realpath: file.realpath });
|
||||
closeModal?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'flex-start' }}>
|
||||
{file.isdir ? (
|
||||
<FaFolder style={iconStyles} />
|
||||
) : (
|
||||
<div style={iconStyles}>
|
||||
{file.realpath.includes('.') ? (
|
||||
<FileIcon {...defaultStyles[extension]} {...styleDefObj[extension]} extension={''} />
|
||||
) : (
|
||||
<FileIcon />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{file.name}
|
||||
</div>
|
||||
</DialogButton>
|
||||
);
|
||||
})}
|
||||
{error}
|
||||
</Focusable>
|
||||
{!loading && !error && !includeFiles && (
|
||||
<DialogButton
|
||||
className="Primary"
|
||||
style={{ marginTop: '10px', alignSelf: 'flex-end' }}
|
||||
onClick={() => {
|
||||
onSubmit({ path, realpath: listing.realpath });
|
||||
closeModal?.();
|
||||
}}
|
||||
>
|
||||
Use this folder
|
||||
</DialogButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilePicker;
|
||||
@@ -0,0 +1 @@
|
||||
This directory contains patches that replace Valve's broken file picker with ours.
|
||||
@@ -0,0 +1,10 @@
|
||||
import library from './library';
|
||||
let patches: Function[] = [];
|
||||
|
||||
export function deinitFilepickerPatches() {
|
||||
patches.forEach((unpatch) => unpatch());
|
||||
}
|
||||
|
||||
export async function initFilepickerPatches() {
|
||||
patches.push(await library());
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Patch, findModuleChild, replacePatch } from 'decky-frontend-lib';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
SteamClient: any;
|
||||
appDetailsStore: any;
|
||||
}
|
||||
}
|
||||
|
||||
let patch: Patch;
|
||||
|
||||
function rePatch() {
|
||||
// If you patch anything on SteamClient within the first few seconds of the client having loaded it will get redefined for some reason, so repatch any of these changes that occur within the first 20s of the last patch
|
||||
patch = replacePatch(window.SteamClient.Apps, 'PromptToChangeShortcut', async ([appid]: number[]) => {
|
||||
try {
|
||||
const details = window.appDetailsStore.GetAppDetails(appid);
|
||||
console.log(details);
|
||||
// strShortcutStartDir
|
||||
const file = await window.DeckyPluginLoader.openFilePicker(details.strShortcutStartDir.replaceAll('"', ''));
|
||||
console.log('user selected', file);
|
||||
window.SteamClient.Apps.SetShortcutExe(appid, JSON.stringify(file.path));
|
||||
const pathArr = file.path.split('/');
|
||||
pathArr.pop();
|
||||
const folder = pathArr.join('/');
|
||||
window.SteamClient.Apps.SetShortcutStartDir(appid, JSON.stringify(folder));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO type and add to frontend-lib
|
||||
const History = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (m[prop]?.m_history) return m[prop].m_history;
|
||||
}
|
||||
});
|
||||
|
||||
export default async function libraryPatch() {
|
||||
try {
|
||||
rePatch();
|
||||
const unlisten = History.listen(() => {
|
||||
if (window.SteamClient.Apps.PromptToChangeShortcut !== patch.patchedFunction) {
|
||||
rePatch();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
patch.unpatch();
|
||||
unlisten();
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('Error patching library file picker', e);
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Dropdown, Field } from 'decky-frontend-lib';
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
import Logger from '../../../../logger';
|
||||
import { callUpdaterMethod } from '../../../../updater';
|
||||
import { useSetting } from '../../../../utils/hooks/useSetting';
|
||||
|
||||
const logger = new Logger('BranchSelect');
|
||||
|
||||
enum UpdateBranch {
|
||||
Stable,
|
||||
Prerelease,
|
||||
@@ -28,7 +31,7 @@ const BranchSelect: FunctionComponent<{}> = () => {
|
||||
onChange={async (newVal) => {
|
||||
await setSelectedBranch(newVal.data);
|
||||
callUpdaterMethod('check_for_updates');
|
||||
console.log('switching branches!');
|
||||
logger.log('switching branches!');
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Carousel, DialogButton, Field, Focusable, ProgressBarWithInfo, Spinner, showModal } from 'decky-frontend-lib';
|
||||
import {
|
||||
Carousel,
|
||||
DialogButton,
|
||||
Field,
|
||||
FocusRing,
|
||||
Focusable,
|
||||
ProgressBarWithInfo,
|
||||
Spinner,
|
||||
showModal,
|
||||
} from 'decky-frontend-lib';
|
||||
import { useCallback } from 'react';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -7,49 +16,48 @@ import { FaArrowDown } from 'react-icons/fa';
|
||||
import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater';
|
||||
import { useDeckyState } from '../../../DeckyState';
|
||||
import InlinePatchNotes from '../../../patchnotes/InlinePatchNotes';
|
||||
import WithSuspense from '../../../WithSuspense';
|
||||
|
||||
const MarkdownRenderer = lazy(() => import('../../../Markdown'));
|
||||
|
||||
// import ReactMarkdown from 'react-markdown'
|
||||
// import remarkGfm from 'remark-gfm'
|
||||
|
||||
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
|
||||
return (
|
||||
<Focusable onCancelButton={closeModal}>
|
||||
<Carousel
|
||||
fnItemRenderer={(id: number) => (
|
||||
<Focusable
|
||||
onActivate={() => {}}
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: '40px',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1>{versionInfo?.all?.[id]?.name}</h1>
|
||||
{versionInfo?.all?.[id]?.body ? (
|
||||
<Suspense fallback={<Spinner style={{ width: '24', height: '24' }} />}>
|
||||
<MarkdownRenderer>{versionInfo.all[id].body}</MarkdownRenderer>
|
||||
</Suspense>
|
||||
) : (
|
||||
'no patch notes for this version'
|
||||
)}
|
||||
</div>
|
||||
</Focusable>
|
||||
)}
|
||||
fnGetId={(id) => id}
|
||||
nNumItems={versionInfo?.all?.length}
|
||||
nHeight={window.innerHeight - 150}
|
||||
nItemHeight={window.innerHeight - 200}
|
||||
nItemMarginX={0}
|
||||
initialColumn={0}
|
||||
autoFocus={true}
|
||||
fnGetColumnWidth={() => window.innerWidth}
|
||||
/>
|
||||
<FocusRing>
|
||||
<Carousel
|
||||
fnItemRenderer={(id: number) => (
|
||||
<Focusable
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: '40px',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1>{versionInfo?.all?.[id]?.name}</h1>
|
||||
{versionInfo?.all?.[id]?.body ? (
|
||||
<WithSuspense>
|
||||
<MarkdownRenderer onDismiss={closeModal}>{versionInfo.all[id].body}</MarkdownRenderer>
|
||||
</WithSuspense>
|
||||
) : (
|
||||
'no patch notes for this version'
|
||||
)}
|
||||
</div>
|
||||
</Focusable>
|
||||
)}
|
||||
fnGetId={(id) => id}
|
||||
nNumItems={versionInfo?.all?.length}
|
||||
nHeight={window.innerHeight - 40}
|
||||
nItemHeight={window.innerHeight - 40}
|
||||
nItemMarginX={0}
|
||||
initialColumn={0}
|
||||
autoFocus={true}
|
||||
fnGetColumnWidth={() => window.innerWidth}
|
||||
/>
|
||||
</FocusRing>
|
||||
</Focusable>
|
||||
);
|
||||
}
|
||||
@@ -126,7 +134,7 @@ export default function UpdaterSettings() {
|
||||
) : (
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator={false}
|
||||
bottomSeparator="none"
|
||||
nProgress={updateProgress}
|
||||
indeterminate={reloading}
|
||||
sOperationText={reloading ? 'Reloading' : 'Updating'}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { SteamSpinner } from 'decky-frontend-lib';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
|
||||
import Logger from '../../logger';
|
||||
import { LegacyStorePlugin, StorePlugin, getLegacyPluginList, getPluginList } from '../../store';
|
||||
import PluginCard from './PluginCard';
|
||||
|
||||
const logger = new Logger('FilePicker');
|
||||
|
||||
const StorePage: FC<{}> = () => {
|
||||
const [data, setData] = useState<StorePlugin[] | null>(null);
|
||||
const [legacyData, setLegacyData] = useState<LegacyStorePlugin[] | null>(null);
|
||||
@@ -11,12 +14,12 @@ const StorePage: FC<{}> = () => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = await getPluginList();
|
||||
console.log(res);
|
||||
logger.log('got data!', res);
|
||||
setData(res);
|
||||
})();
|
||||
(async () => {
|
||||
const res = await getLegacyPluginList();
|
||||
console.log(res);
|
||||
logger.log('got legacy data!', res);
|
||||
setLegacyData(res);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
@@ -19,7 +19,7 @@ export const debug = (name: string, ...args: any[]) => {
|
||||
};
|
||||
|
||||
export const error = (name: string, ...args: any[]) => {
|
||||
console.log(
|
||||
console.error(
|
||||
`%c Decky %c ${name} %c`,
|
||||
'background: #16a085; color: black;',
|
||||
'background: #FF0000;',
|
||||
@@ -40,6 +40,10 @@ class Logger {
|
||||
debug(...args: any[]) {
|
||||
debug(this.name, ...args);
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
error(this.name, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export default Logger;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { ModalRoot, QuickAccessTab, Router, SteamSpinner, showModal, sleep, staticClasses } from 'decky-frontend-lib';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { ConfirmModal, ModalRoot, QuickAccessTab, Router, showModal, sleep, staticClasses } from 'decky-frontend-lib';
|
||||
import { lazy } from 'react';
|
||||
import { FaPlug } from 'react-icons/fa';
|
||||
|
||||
import { DeckyState, DeckyStateContextProvider, useDeckyState } from './components/DeckyState';
|
||||
import LegacyPlugin from './components/LegacyPlugin';
|
||||
import { deinitFilepickerPatches, initFilepickerPatches } from './components/modals/filepicker/patches';
|
||||
import PluginInstallModal from './components/modals/PluginInstallModal';
|
||||
import NotificationBadge from './components/NotificationBadge';
|
||||
import PluginView from './components/PluginView';
|
||||
import TitleView from './components/TitleView';
|
||||
import WithSuspense from './components/WithSuspense';
|
||||
import Logger from './logger';
|
||||
import { Plugin } from './plugin';
|
||||
import RouterHook from './router-hook';
|
||||
@@ -16,6 +18,11 @@ import TabsHook from './tabs-hook';
|
||||
import Toaster from './toaster';
|
||||
import { VerInfo, callUpdaterMethod } from './updater';
|
||||
|
||||
const StorePage = lazy(() => import('./components/store/Store'));
|
||||
const SettingsPage = lazy(() => import('./components/settings'));
|
||||
|
||||
const FilePicker = lazy(() => import('./components/modals/filepicker'));
|
||||
|
||||
declare global {
|
||||
interface Window {}
|
||||
}
|
||||
@@ -36,7 +43,7 @@ class PluginLoader extends Logger {
|
||||
super(PluginLoader.name);
|
||||
this.log('Initialized');
|
||||
|
||||
const TabIcon = () => {
|
||||
const TabBadge = () => {
|
||||
const { updates, hasLoaderUpdate } = useDeckyState();
|
||||
return <NotificationBadge show={(updates && updates.size > 0) || hasLoaderUpdate} />;
|
||||
};
|
||||
@@ -53,57 +60,40 @@ class PluginLoader extends Logger {
|
||||
icon: (
|
||||
<DeckyStateContextProvider deckyState={this.deckyState}>
|
||||
<FaPlug />
|
||||
<TabIcon />
|
||||
<TabBadge />
|
||||
</DeckyStateContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
const StorePage = lazy(() => import('./components/store/Store'));
|
||||
const SettingsPage = lazy(() => import('./components/settings'));
|
||||
|
||||
this.routerHook.addRoute('/decky/store', () => (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
}}
|
||||
>
|
||||
<SteamSpinner />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<WithSuspense route={true}>
|
||||
<StorePage />
|
||||
</Suspense>
|
||||
</WithSuspense>
|
||||
));
|
||||
this.routerHook.addRoute('/decky/settings', () => {
|
||||
return (
|
||||
<DeckyStateContextProvider deckyState={this.deckyState}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
}}
|
||||
>
|
||||
<SteamSpinner />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<WithSuspense route={true}>
|
||||
<SettingsPage />
|
||||
</Suspense>
|
||||
</WithSuspense>
|
||||
</DeckyStateContextProvider>
|
||||
);
|
||||
});
|
||||
|
||||
initFilepickerPatches();
|
||||
|
||||
this.updateVersion();
|
||||
}
|
||||
|
||||
public async updateVersion() {
|
||||
const versionInfo = (await callUpdaterMethod('get_version')).result as VerInfo;
|
||||
this.deckyState.setVersionInfo(versionInfo);
|
||||
|
||||
return versionInfo;
|
||||
}
|
||||
|
||||
public async notifyUpdates() {
|
||||
const versionInfo = (await callUpdaterMethod('get_version')).result as VerInfo;
|
||||
this.deckyState.setVersionInfo(versionInfo);
|
||||
const versionInfo = await this.updateVersion();
|
||||
if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
|
||||
this.toaster.toast({
|
||||
title: 'Decky',
|
||||
@@ -147,7 +137,7 @@ class PluginLoader extends Logger {
|
||||
|
||||
public uninstallPlugin(name: string) {
|
||||
showModal(
|
||||
<ModalRoot
|
||||
<ConfirmModal
|
||||
onOK={async () => {
|
||||
await this.callServerMethod('uninstall_plugin', { name });
|
||||
}}
|
||||
@@ -158,7 +148,7 @@ class PluginLoader extends Logger {
|
||||
<div className={staticClasses.Title} style={{ flexDirection: 'column' }}>
|
||||
Uninstall {name}?
|
||||
</div>
|
||||
</ModalRoot>,
|
||||
</ConfirmModal>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -176,6 +166,7 @@ class PluginLoader extends Logger {
|
||||
public deinit() {
|
||||
this.routerHook.removeRoute('/decky/store');
|
||||
this.routerHook.removeRoute('/decky/settings');
|
||||
deinitFilepickerPatches();
|
||||
}
|
||||
|
||||
public unloadPlugin(name: string) {
|
||||
@@ -225,7 +216,8 @@ class PluginLoader extends Logger {
|
||||
},
|
||||
});
|
||||
if (res.ok) {
|
||||
let plugin = await eval(await res.text())(this.createPluginAPI(name));
|
||||
let plugin_export = await eval(await res.text());
|
||||
let plugin = plugin_export(this.createPluginAPI(name));
|
||||
this.plugins.push({
|
||||
...plugin,
|
||||
name: name,
|
||||
@@ -257,11 +249,41 @@ class PluginLoader extends Logger {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
openFilePicker(
|
||||
startPath: string,
|
||||
includeFiles?: boolean,
|
||||
regex?: RegExp,
|
||||
): Promise<{ path: string; realpath: string }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const Content = ({ closeModal }: { closeModal?: () => void }) => (
|
||||
// Purposely outside of the FilePicker component as lazy-loaded ModalRoots don't focus correctly
|
||||
<ModalRoot
|
||||
onCancel={() => {
|
||||
reject('User canceled');
|
||||
closeModal?.();
|
||||
}}
|
||||
>
|
||||
<WithSuspense>
|
||||
<FilePicker
|
||||
startPath={startPath}
|
||||
includeFiles={includeFiles}
|
||||
regex={regex}
|
||||
onSubmit={resolve}
|
||||
closeModal={closeModal}
|
||||
/>
|
||||
</WithSuspense>
|
||||
</ModalRoot>
|
||||
);
|
||||
showModal(<Content />);
|
||||
});
|
||||
}
|
||||
|
||||
createPluginAPI(pluginName: string) {
|
||||
return {
|
||||
routerHook: this.routerHook,
|
||||
toaster: this.toaster,
|
||||
callServerMethod: this.callServerMethod,
|
||||
openFilePicker: this.openFilePicker,
|
||||
async callPluginMethod(methodName: string, args = {}) {
|
||||
const response = await fetch(`http://127.0.0.1:1337/plugins/${pluginName}/methods/${methodName}`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModalRoot, showModal, staticClasses } from 'decky-frontend-lib';
|
||||
import { ConfirmModal, showModal, staticClasses } from 'decky-frontend-lib';
|
||||
|
||||
import { Plugin } from './plugin';
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function installFromURL(url: string) {
|
||||
|
||||
export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVer: string) {
|
||||
showModal(
|
||||
<ModalRoot
|
||||
<ConfirmModal
|
||||
onOK={() => {
|
||||
window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
||||
name: plugin.artifact,
|
||||
@@ -70,7 +70,7 @@ export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVe
|
||||
You are currently installing a <b>legacy</b> plugin. Legacy plugins are no longer supported and may have issues.
|
||||
Legacy plugins do not support gamepad input. To interact with a legacy plugin, you will need to use the
|
||||
touchscreen.
|
||||
</ModalRoot>,
|
||||
</ConfirmModal>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+15
-11
@@ -15,6 +15,7 @@ class Toaster extends Logger {
|
||||
private instanceRetPatch?: Patch;
|
||||
private node: any;
|
||||
private settingsModule: any;
|
||||
private ready: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super('Toaster');
|
||||
@@ -25,13 +26,6 @@ class Toaster extends Logger {
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.settingsModule = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (typeof m[prop]?.settings?.bDisableToastsInGame !== 'undefined') return m[prop];
|
||||
}
|
||||
});
|
||||
|
||||
let instance: any;
|
||||
while (true) {
|
||||
instance = findInReactTree(
|
||||
@@ -71,11 +65,21 @@ class Toaster extends Logger {
|
||||
return ret;
|
||||
};
|
||||
this.node.stateNode.forceUpdate();
|
||||
this.settingsModule = findModuleChild((m) => {
|
||||
if (typeof m !== 'object') return undefined;
|
||||
for (let prop in m) {
|
||||
if (typeof m[prop]?.settings && m[prop]?.communityPreferences) return m[prop];
|
||||
}
|
||||
});
|
||||
this.log('Initialized');
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
toast(toast: ToastData) {
|
||||
const settings = this.settingsModule.settings;
|
||||
async toast(toast: ToastData) {
|
||||
while (!this.ready) {
|
||||
await sleep(100);
|
||||
}
|
||||
const settings = this.settingsModule?.settings;
|
||||
let toastData = {
|
||||
nNotificationID: window.NotificationStore.m_nNextTestNotificationID++,
|
||||
rtCreated: Date.now(),
|
||||
@@ -87,8 +91,8 @@ class Toaster extends Logger {
|
||||
// @ts-ignore
|
||||
toastData.data.appid = () => 0;
|
||||
if (
|
||||
(settings.bDisableAllToasts && !toast.critical) ||
|
||||
(settings.bDisableToastsInGame && !toast.critical && window.NotificationStore.BIsUserInGame())
|
||||
(settings?.bDisableAllToasts && !toast.critical) ||
|
||||
(settings?.bDisableToastsInGame && !toast.critical && window.NotificationStore.BIsUserInGame())
|
||||
)
|
||||
return;
|
||||
window.NotificationStore.m_rgNotificationToasts.push(toastData);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { sleep } from 'decky-frontend-lib';
|
||||
|
||||
export enum Branches {
|
||||
Release,
|
||||
Prerelease,
|
||||
@@ -46,6 +44,4 @@ export async function callUpdaterMethod(methodName: string, args = {}) {
|
||||
|
||||
export async function finishUpdate() {
|
||||
callUpdaterMethod('do_restart');
|
||||
await sleep(3000);
|
||||
location.reload();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user