mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-01 23:39:55 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6347ad0856 | |||
| 1377d83023 | |||
| 43d36d2b35 | |||
| 591c58330c | |||
| 501145a210 | |||
| 843e03b42c | |||
| 5f469bfb16 | |||
| acaf6c72e4 | |||
| eb439574be | |||
| 16a6e9b6a9 | |||
| 6f84cf94b5 | |||
| 7c06db5ece | |||
| aeb2decfc1 | |||
| b7d7ca04e1 | |||
| d4d1c2bbab | |||
| effc4ab0f5 | |||
| 79db0c779d | |||
| fe2b6b0283 | |||
| b9a87cd785 | |||
| 98e9ce881f | |||
| e49bdd9c05 | |||
| d0fd2ac674 | |||
| de1c89af21 | |||
| 8b3f569a09 | |||
| 1930400032 | |||
| 43dee863cd |
+103
-115
@@ -54,13 +54,20 @@ jobs:
|
||||
run: pyinstaller --noconfirm --onefile --name "PluginLoader" --add-data ./backend/static:/static --add-data ./backend/legacy:/legacy ./backend/*.py
|
||||
|
||||
- name: Upload package artifact ⬆️
|
||||
if: ${{ !env.ACT }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: PluginLoader
|
||||
path: ./dist/PluginLoader
|
||||
|
||||
- name: Download package artifact locally
|
||||
if: ${{ env.ACT }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ./dist/PluginLoader
|
||||
|
||||
release:
|
||||
name: Release the package
|
||||
name: Release stable version of the package
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'release' }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
@@ -69,144 +76,125 @@ jobs:
|
||||
- name: Checkout 🧰
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Fetch package artifact ⬇️
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: PluginLoader
|
||||
path: dist
|
||||
|
||||
- name: Release 📦
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
name: Release ${{ steps.tag_version.outputs.new_tag }}
|
||||
tag_name: ${{ steps.tag_version.outputs.new_tag }}
|
||||
files: ./dist/PluginLoader
|
||||
generate_release_notes: true
|
||||
|
||||
prerelease:
|
||||
name: Release the pre-release version of the package
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'prerelease' }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout 🧰
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Fetch package artifact ⬇️
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: PluginLoader
|
||||
path: dist
|
||||
|
||||
- name: Install semver-tool asdf
|
||||
uses: asdf-vm/actions/install@v1
|
||||
with:
|
||||
tool_versions: |
|
||||
semver 3.3.0
|
||||
|
||||
- name: Get tag 🏷️
|
||||
id: old_tag
|
||||
uses: rafarlopes/get-latest-pre-release-tag-action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Fetch package artifact ⬇️
|
||||
uses: actions/download-artifact@v3
|
||||
if: ${{ !env.ACT }}
|
||||
with:
|
||||
repository: 'decky-loader'
|
||||
name: PluginLoader
|
||||
path: dist
|
||||
|
||||
- name: Get latest release
|
||||
uses: rez0n/actions-github-release@main
|
||||
id: latest_release
|
||||
env:
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: "SteamDeckHomebrew/decky-loader"
|
||||
type: "nodraft"
|
||||
|
||||
- name: Prepare tag ⚙️
|
||||
id: ready_tag
|
||||
run: |
|
||||
export VERSION=${{ steps.old_tag.outputs.tag }}
|
||||
export VERSION=${{ steps.latest_release.outputs.release }}
|
||||
echo "VERS: $VERSION"
|
||||
OUT=$(semver bump prerel "$VERSION")
|
||||
echo "OUT: $OUT"
|
||||
OUT="notsemver"
|
||||
if [[ "$VERSION" =~ "-pre" ]]; then
|
||||
printf "is prerelease, bumping to release\n"
|
||||
OUT=$(semver bump release "$VERSION")
|
||||
printf "OUT: ${OUT}\n"
|
||||
elif [[ ! "$VERSION" =~ "-pre" ]]; then
|
||||
printf "previous tag is a release, bumping by a patch\n"
|
||||
OUT=$(semver bump minor "$VERSION")
|
||||
printf "OUT: ${OUT}\n"
|
||||
fi
|
||||
echo "vOUT: v$OUT"
|
||||
echo ::set-output name=tag_name::v$OUT
|
||||
|
||||
- name: Push tag 📤
|
||||
uses: rickstaa/action-create-tag@v1.3.2
|
||||
if: ${{ steps.ready_tag.outputs.tag_name && github.event_name == 'workflow_dispatch' }}
|
||||
if: ${{ steps.ready_tag.outputs.tag_name && github.event_name == 'workflow_dispatch' && !env.ACT }}
|
||||
with:
|
||||
tag: ${{ steps.ready_tag.outputs.tag_name }}
|
||||
message: Pre-release ${{ steps.ready_tag.outputs.tag_name }}
|
||||
|
||||
- name: Release 📦
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && !env.ACT }}
|
||||
with:
|
||||
name: Prerelease ${{ steps.ready_tag.outputs.tag_name }}
|
||||
tag_name: ${{ steps.ready_tag.outputs.tag_name }}
|
||||
files: ./dist/PluginLoader
|
||||
prerelease: false
|
||||
generate_release_notes: true
|
||||
|
||||
prerelease:
|
||||
name: Release the pre-release version of the package
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'prerelease' }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout 🧰
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install semver-tool asdf
|
||||
uses: asdf-vm/actions/install@v1
|
||||
with:
|
||||
tool_versions: |
|
||||
semver 3.3.0
|
||||
|
||||
- name: Fetch package artifact ⬇️
|
||||
uses: actions/download-artifact@v3
|
||||
if: ${{ !env.ACT }}
|
||||
with:
|
||||
name: PluginLoader
|
||||
path: dist
|
||||
|
||||
- name: Get latest release
|
||||
uses: rez0n/actions-github-release@main
|
||||
id: latest_release
|
||||
env:
|
||||
# token: ${{ secrets.GITHUB_TOKEN }}
|
||||
repository: "SteamDeckHomebrew/decky-loader"
|
||||
type: "nodraft"
|
||||
|
||||
- name: Prepare tag ⚙️
|
||||
id: ready_tag
|
||||
run: |
|
||||
export VERSION="2.0.5"
|
||||
echo "VERS: $VERSION"
|
||||
OUT=""
|
||||
if [[ ! "$VERSION" =~ "-pre" ]]; then
|
||||
printf "is release, bumping minor version and prerel\n"
|
||||
OUT=$(semver bump minor "$VERSION")
|
||||
OUT="$OUT-pre"
|
||||
OUT=$(semver bump prerel "$OUT")
|
||||
printf "OUT: ${OUT}\n"
|
||||
elif [[ "$VERSION" =~ "-pre" ]]; then
|
||||
printf "is a prerelease, bumping prerel\n"
|
||||
OUT=$(semver bump prerel "$VERSION")
|
||||
printf "OUT: ${OUT}\n"
|
||||
fi
|
||||
echo ::set-output name=tag_name::v$OUT
|
||||
|
||||
- name: Push tag 📤
|
||||
uses: rickstaa/action-create-tag@v1.3.2
|
||||
if: ${{ steps.ready_tag.outputs.tag_name && github.event_name == 'workflow_dispatch' && !env.ACT }}
|
||||
with:
|
||||
tag: ${{ steps.ready_tag.outputs.tag_name }}
|
||||
message: Pre-release ${{ steps.ready_tag.outputs.tag_name }}
|
||||
|
||||
- name: Release 📦
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && !env.ACT }}
|
||||
with:
|
||||
name: Prerelease ${{ steps.ready_tag.outputs.tag_name }}
|
||||
tag_name: ${{ steps.ready_tag.outputs.tag_name }}
|
||||
files: ./dist/PluginLoader
|
||||
prerelease: true
|
||||
generate_release_notes: true
|
||||
|
||||
# nightly:
|
||||
# name: Release the nightly version of the package
|
||||
# if: ${{ github.event_name == 'schedule' }}
|
||||
# needs: build
|
||||
# runs-on: ubuntu-latest
|
||||
|
||||
# steps:
|
||||
# - name: Checkout 🧰
|
||||
# uses: actions/checkout@v3
|
||||
|
||||
# - name: Fetch package artifact ⬇️
|
||||
# uses: actions/download-artifact@v3
|
||||
# with:
|
||||
# name: PluginLoader
|
||||
# path: dist
|
||||
|
||||
# - name: Get tag 🏷️
|
||||
# id: old_tag
|
||||
# uses: rafarlopes/get-latest-pre-release-tag-action@v1
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# with:
|
||||
# repository: 'decky-loader'
|
||||
|
||||
# - name: Prepare tag ⚙️
|
||||
# id: ready_tag
|
||||
# run: |
|
||||
# export VERSION=${{ steps.old_tag.outputs.tag }}
|
||||
# export COMMIT=$(git log -1 --pretty=format:%h)
|
||||
# echo ::set-output name=tag_name::$(sed -r 's/(-.*)?-pre$//' <<< $VERSION)-$COMMIT-nightly
|
||||
|
||||
# - name: Push tag 📤
|
||||
# uses: rickstaa/action-create-tag@v1.3.2
|
||||
# if: ${{ steps.ready_tag.outputs.tag_name && github.event_name == 'workflow_dispatch' }}
|
||||
# with:
|
||||
# tag: ${{ steps.ready_tag.outputs.tag_name }}
|
||||
# message: Nightly ${{ steps.ready_tag.outputs.tag_name }}
|
||||
|
||||
# - name: Release 📦
|
||||
# uses: softprops/action-gh-release@v1
|
||||
# if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
# with:
|
||||
# name: Prerelease ${{ steps.ready_tag.outputs.tag_name }}
|
||||
# tag_name: ${{ steps.ready_tag.outputs.tag_name }}
|
||||
# files: ./dist/PluginLoader
|
||||
# prerelease: true
|
||||
# generate_release_notes: true
|
||||
|
||||
# - name: Bump prerelease ⏫
|
||||
# id: bump
|
||||
# if: ${{ github.event_name == 'schedule' }}
|
||||
# run: |
|
||||
# git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
||||
# echo ::set-output new_tag="nightly-$git_hash"
|
||||
|
||||
# - name: Push tag 📤
|
||||
# uses: rickstaa/action-create-tag@v1.3.2
|
||||
# if: ${{ github.event_name == 'schedule' }}
|
||||
# with:
|
||||
# tag: ${{ steps.bump.outputs.new_tag }}
|
||||
# message: Nightly ${{ steps.bump.outputs.new_tag }}
|
||||
|
||||
# - name: Release 📦
|
||||
# uses: softprops/action-gh-release@v1
|
||||
# if: ${{ github.event_name == 'schedule' }}
|
||||
# with:
|
||||
# name: Nightly ${{ steps.bump.outputs.new_tag }}
|
||||
# tag_name: ${{ steps.bump.outputs.new_tag }}
|
||||
# files: ./dist/PluginLoader
|
||||
# prerelease: true
|
||||
# generate_release_notes: true
|
||||
|
||||
+3
-1
@@ -159,4 +159,6 @@ backend/static
|
||||
.vscode/settings.json
|
||||
|
||||
# plugins folder for local launches
|
||||
plugins/*
|
||||
plugins/*
|
||||
act/.directory
|
||||
act/artifacts/*
|
||||
@@ -0,0 +1,10 @@
|
||||
this directory contains artifacts generated by invocations of https://github.com/nektos/act in order to do local testing of binary builds
|
||||
|
||||
how to?
|
||||
run:
|
||||
|
||||
./act/run-act.sh prerelease
|
||||
|
||||
or
|
||||
|
||||
./act/run-act.sh release
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"inputs": {
|
||||
"release": "prerelease"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"inputs": {
|
||||
"release": "release"
|
||||
}
|
||||
}
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
type=$1
|
||||
|
||||
oldartifactsdir="old"
|
||||
|
||||
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
|
||||
cd "$parent_path"
|
||||
|
||||
artifactfolders=$(find artifacts/ -maxdepth 1 -mindepth 1 -type d)
|
||||
if [[ ${#artifactfolders[@]} > 0 ]]; then
|
||||
for i in ${artifactfolders[@]}; do
|
||||
foldername=$(dirname $i)
|
||||
subfoldername=$(basename $i)
|
||||
out=$foldername/$oldartifactsdir/$subfoldername-$(date +'%s')
|
||||
if [[ ! "$subfoldername" =~ "$oldartifactsdir" ]]; then
|
||||
mkdir -p $out
|
||||
mv $i $out
|
||||
printf "Moved "${foldername}"/"${subfoldername}" to "${out}" \n"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
cd ..
|
||||
|
||||
if [[ "$type" == "release" ]]; then
|
||||
printf "release!\n"
|
||||
act workflow_dispatch -e act/release.json --artifact-server-path act/artifacts
|
||||
elif [[ "$type" == "prerelease" ]]; then
|
||||
printf "prerelease!\n"
|
||||
act workflow_dispatch -e act/prerelease.json --artifact-server-path act/artifacts
|
||||
else
|
||||
printf "Release type unspecified/badly specified.\n"
|
||||
printf "Options: 'release' or 'prerelease'\n"
|
||||
fi
|
||||
|
||||
cd act/artifacts
|
||||
|
||||
if [[ -d "1" ]]; then
|
||||
cd "1/artifact"
|
||||
cp "PluginLoader.gz__" "PluginLoader.gz"
|
||||
gzip -d "PluginLoader.gz"
|
||||
chmod +x PluginLoader
|
||||
fi
|
||||
+16
-28
@@ -28,16 +28,11 @@ class PluginInstallContext:
|
||||
self.hash = hash
|
||||
|
||||
class PluginBrowser:
|
||||
def __init__(self, plugin_path, server_instance, plugins) -> None:
|
||||
def __init__(self, plugin_path, plugins) -> None:
|
||||
self.plugin_path = plugin_path
|
||||
self.plugins = plugins
|
||||
self.install_requests = {}
|
||||
|
||||
server_instance.add_routes([
|
||||
web.post("/browser/install_plugin", self.install_plugin),
|
||||
web.post("/browser/uninstall_plugin", self.uninstall_plugin)
|
||||
])
|
||||
|
||||
def _unzip_to_plugin_dir(self, zip, name, hash):
|
||||
zip_hash = sha256(zip.getbuffer()).hexdigest()
|
||||
if hash and (zip_hash != hash):
|
||||
@@ -64,25 +59,24 @@ class PluginBrowser:
|
||||
|
||||
async def uninstall_plugin(self, name):
|
||||
tab = await get_tab("SP")
|
||||
|
||||
try:
|
||||
if type(name) != str:
|
||||
data = await name.post()
|
||||
name = data.get("name", "undefined")
|
||||
logger.info("uninstalling " + name)
|
||||
logger.info(" at dir " + self.find_plugin_folder(name))
|
||||
logger.debug("unloading %s" % str(name))
|
||||
await tab.evaluate_js(f"DeckyPluginLoader.unloadPlugin('{name}')")
|
||||
if self.plugins[name]:
|
||||
self.plugins[name].stop()
|
||||
self.plugins.pop(name, None)
|
||||
del self.plugins[name]
|
||||
logger.debug("removing files %s" % str(name))
|
||||
rmtree(self.find_plugin_folder(name))
|
||||
except FileNotFoundError:
|
||||
logger.warning(f"Plugin {name} not installed, skipping uninstallation")
|
||||
|
||||
return web.Response(text="Requested plugin uninstall")
|
||||
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)
|
||||
|
||||
async def _install(self, artifact, name, version, hash):
|
||||
try:
|
||||
try:
|
||||
await self.uninstall_plugin(name)
|
||||
except:
|
||||
logger.error(f"Plugin {name} not installed, skipping uninstallation")
|
||||
@@ -95,29 +89,23 @@ class PluginBrowser:
|
||||
data = await res.read()
|
||||
logger.debug(f"Read {len(data)} bytes")
|
||||
res_zip = BytesIO(data)
|
||||
with ProcessPoolExecutor() as executor:
|
||||
logger.debug("Unzipping...")
|
||||
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()")
|
||||
else:
|
||||
logger.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
|
||||
logger.debug("Unzipping...")
|
||||
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()")
|
||||
else:
|
||||
self.log.fatal(f"SHA-256 Mismatch!!!! {name} (Version: {version})")
|
||||
else:
|
||||
logger.fatal(f"Could not fetch from URL. {await res.text()}")
|
||||
|
||||
async def install_plugin(self, request):
|
||||
data = await request.post()
|
||||
get_event_loop().create_task(self.request_plugin_install(data.get("artifact", ""), data.get("name", "No name"), data.get("version", "dev"), data.get("hash", False)))
|
||||
return web.Response(text="Requested plugin install")
|
||||
|
||||
async def request_plugin_install(self, artifact, name, version, hash):
|
||||
request_id = str(time())
|
||||
self.install_requests[request_id] = PluginInstallContext(artifact, name, version, hash)
|
||||
tab = await get_tab("SP")
|
||||
await tab.open_websocket()
|
||||
await tab.evaluate_js(f"DeckyPluginLoader.addPluginInstallPrompt('{name}', '{version}', '{request_id}', '{hash}')")
|
||||
|
||||
|
||||
async def confirm_plugin_install(self, request_id):
|
||||
request = self.install_requests.pop(request_id)
|
||||
await self._install(request.artifact, request.name, request.version, request.hash)
|
||||
|
||||
+39
-6
@@ -1,12 +1,15 @@
|
||||
import certifi
|
||||
import ssl
|
||||
import uuid
|
||||
import re
|
||||
|
||||
from aiohttp.web import middleware, Response
|
||||
import ssl
|
||||
import subprocess
|
||||
import uuid
|
||||
from subprocess import check_output
|
||||
from time import sleep
|
||||
|
||||
import certifi
|
||||
from aiohttp.web import Response, middleware
|
||||
|
||||
REMOTE_DEBUGGER_UNIT = "steam-web-debug-portforward.service"
|
||||
|
||||
# global vars
|
||||
csrf_token = str(uuid.uuid4())
|
||||
ssl_ctx = ssl.create_default_context(cafile=certifi.where())
|
||||
@@ -14,6 +17,7 @@ user = None
|
||||
group = None
|
||||
|
||||
assets_regex = re.compile("^/plugins/.*/assets/.*")
|
||||
frontend_regex = re.compile("^/frontend/.*")
|
||||
|
||||
def get_ssl_context():
|
||||
return ssl_ctx
|
||||
@@ -23,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)):
|
||||
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)):
|
||||
return await handler(request)
|
||||
return Response(text='Forbidden', status='403')
|
||||
|
||||
@@ -63,3 +67,32 @@ def get_user_group() -> str:
|
||||
if group == None:
|
||||
raise ValueError("helpers.get_user_group method called before group variable was set. Run helpers.set_user_group first.")
|
||||
return group
|
||||
|
||||
# Get the default home path unless a user is specified
|
||||
def get_home_path(username = None) -> str:
|
||||
if username == None:
|
||||
raise ValueError("Username not defined, no home path can be found.")
|
||||
else:
|
||||
return str("/home/"+username)
|
||||
|
||||
# Get the default homebrew path unless a user is specified
|
||||
def get_homebrew_path(home_path = None) -> str:
|
||||
if home_path == None:
|
||||
raise ValueError("Home path not defined, homebrew dir cannot be determined.")
|
||||
else:
|
||||
return str(home_path+"/homebrew")
|
||||
# return str(home_path+"/homebrew")
|
||||
|
||||
async def is_systemd_unit_active(unit_name: str) -> bool:
|
||||
res = subprocess.run(["systemctl", "is-active", unit_name], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
return res.returncode == 0
|
||||
|
||||
async def stop_systemd_unit(unit_name: str) -> subprocess.CompletedProcess:
|
||||
cmd = ["systemctl", "stop", unit_name]
|
||||
|
||||
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
async def start_systemd_unit(unit_name: str) -> subprocess.CompletedProcess:
|
||||
cmd = ["systemctl", "start", unit_name]
|
||||
|
||||
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ async def get_tabs():
|
||||
except ClientConnectorError:
|
||||
logger.debug("ClientConnectorError excepted.")
|
||||
logger.debug("Steam isn't available yet. Wait for a moment...")
|
||||
logger.debug(format_exc())
|
||||
logger.error(format_exc())
|
||||
await sleep(5)
|
||||
else:
|
||||
break
|
||||
|
||||
+14
-8
@@ -1,4 +1,4 @@
|
||||
from asyncio import Queue
|
||||
from asyncio import Queue, sleep
|
||||
from json.decoder import JSONDecodeError
|
||||
from logging import getLogger
|
||||
from os import listdir, path
|
||||
@@ -75,10 +75,11 @@ class Loader:
|
||||
self.loop.create_task(self.handle_reloads())
|
||||
|
||||
server_instance.add_routes([
|
||||
web.get("/frontend/{path:.*}", self.handle_frontend_assets),
|
||||
web.get("/plugins", self.get_plugins),
|
||||
web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
|
||||
web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call),
|
||||
web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_frontend_assets),
|
||||
web.get("/plugins/{plugin_name}/assets/{path:.*}", self.handle_plugin_frontend_assets),
|
||||
|
||||
# The following is legacy plugin code.
|
||||
web.get("/plugins/load_main/{name}", self.load_plugin_main_view),
|
||||
@@ -86,15 +87,20 @@ class Loader:
|
||||
web.get("/steam_resource/{path:.+}", self.get_steam_resource)
|
||||
])
|
||||
|
||||
async def handle_frontend_assets(self, request):
|
||||
file = path.join(path.dirname(__file__), "static", request.match_info["path"])
|
||||
|
||||
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
|
||||
|
||||
async def get_plugins(self, request):
|
||||
plugins = list(self.plugins.values())
|
||||
return web.json_response([str(i) if not i.legacy else "$LEGACY_"+str(i) for i in plugins])
|
||||
return web.json_response([{"name": str(i) if not i.legacy else "$LEGACY_"+str(i), "version": i.version} for i in plugins])
|
||||
|
||||
def handle_frontend_assets(self, request):
|
||||
def handle_plugin_frontend_assets(self, request):
|
||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||
file = path.join(self.plugin_path, plugin.plugin_directory, "dist/assets", request.match_info["path"])
|
||||
|
||||
return web.FileResponse(file)
|
||||
return web.FileResponse(file, headers={"Cache-Control": "no-cache"})
|
||||
|
||||
def handle_frontend_bundle(self, request):
|
||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||
@@ -116,13 +122,13 @@ class Loader:
|
||||
self.logger.info(f"Plugin {plugin.name} is passive")
|
||||
self.plugins[plugin.name] = plugin.start()
|
||||
self.logger.info(f"Loaded {plugin.name}")
|
||||
self.loop.create_task(self.dispatch_plugin(plugin.name if not plugin.legacy else "$LEGACY_" + plugin.name))
|
||||
self.loop.create_task(self.dispatch_plugin(plugin.name if not plugin.legacy else "$LEGACY_" + plugin.name, plugin.version))
|
||||
except Exception as e:
|
||||
self.logger.error(f"Could not load {file}. {e}")
|
||||
print_exc()
|
||||
|
||||
async def dispatch_plugin(self, name):
|
||||
await inject_to_tab("SP", f"window.importDeckyPlugin('{name}')")
|
||||
async def dispatch_plugin(self, name, version):
|
||||
await inject_to_tab("SP", f"window.importDeckyPlugin('{name}', '{version}')")
|
||||
|
||||
def import_plugins(self):
|
||||
self.logger.info(f"import plugins from {self.plugin_path}")
|
||||
|
||||
+32
-16
@@ -1,21 +1,25 @@
|
||||
# Full imports
|
||||
import aiohttp_cors
|
||||
|
||||
# Partial imports
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp.web import Application, run_app, static, get, Response
|
||||
from aiohttp_jinja2 import setup as jinja_setup
|
||||
from asyncio import get_event_loop, sleep
|
||||
from json import dumps, loads
|
||||
from logging import DEBUG, INFO, basicConfig, getLogger
|
||||
from os import getenv, path
|
||||
from subprocess import call
|
||||
|
||||
import aiohttp_cors
|
||||
# Partial imports
|
||||
from aiohttp import ClientSession
|
||||
from aiohttp.web import Application, Response, get, run_app, static
|
||||
from aiohttp_jinja2 import setup as jinja_setup
|
||||
|
||||
# local modules
|
||||
from browser import PluginBrowser
|
||||
from helpers import csrf_middleware, get_csrf_token, get_user, get_user_group, set_user, set_user_group
|
||||
from helpers import (REMOTE_DEBUGGER_UNIT, csrf_middleware, get_csrf_token,
|
||||
get_home_path, get_homebrew_path, get_user,
|
||||
get_user_group, set_user, set_user_group,
|
||||
stop_systemd_unit)
|
||||
from injector import inject_to_tab, tab_has_global_var
|
||||
from loader import Loader
|
||||
from settings import SettingsManager
|
||||
from updater import Updater
|
||||
from utilities import Utilities
|
||||
|
||||
@@ -28,16 +32,22 @@ set_user_group()
|
||||
USER = get_user()
|
||||
GROUP = get_user_group()
|
||||
HOME_PATH = "/home/"+USER
|
||||
HOMEBREW_PATH = HOME_PATH+"/homebrew"
|
||||
CONFIG = {
|
||||
"plugin_path": getenv("PLUGIN_PATH", HOME_PATH+"/homebrew/plugins"),
|
||||
"plugin_path": getenv("PLUGIN_PATH", HOMEBREW_PATH+"/plugins"),
|
||||
"chown_plugin_path": getenv("CHOWN_PLUGIN_PATH", "1") == "1",
|
||||
"server_host": getenv("SERVER_HOST", "127.0.0.1"),
|
||||
"server_port": int(getenv("SERVER_PORT", "1337")),
|
||||
"live_reload": getenv("LIVE_RELOAD", "1") == "1",
|
||||
"log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")]
|
||||
"log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING": 30, "INFO": 20, "DEBUG": 10}[
|
||||
getenv("LOG_LEVEL", "INFO")
|
||||
],
|
||||
}
|
||||
|
||||
basicConfig(level=CONFIG["log_level"], format="[%(module)s][%(levelname)s]: %(message)s")
|
||||
basicConfig(
|
||||
level=CONFIG["log_level"],
|
||||
format="[%(module)s][%(levelname)s]: %(message)s"
|
||||
)
|
||||
|
||||
logger = getLogger("Main")
|
||||
|
||||
@@ -53,11 +63,15 @@ class PluginManager:
|
||||
self.web_app = Application()
|
||||
self.web_app.middlewares.append(csrf_middleware)
|
||||
self.cors = aiohttp_cors.setup(self.web_app, defaults={
|
||||
"https://steamloopback.host": aiohttp_cors.ResourceOptions(expose_headers="*",
|
||||
allow_headers="*", allow_credentials=True)
|
||||
"https://steamloopback.host": aiohttp_cors.ResourceOptions(
|
||||
expose_headers="*",
|
||||
allow_headers="*",
|
||||
allow_credentials=True
|
||||
)
|
||||
})
|
||||
self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
|
||||
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, self.plugin_loader.plugins)
|
||||
self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.plugin_loader.plugins)
|
||||
self.settings = SettingsManager("loader", path.join(HOMEBREW_PATH, "settings"))
|
||||
self.utilities = Utilities(self)
|
||||
self.updater = Updater(self)
|
||||
|
||||
@@ -66,11 +80,13 @@ class PluginManager:
|
||||
self.web_app.on_startup.append(chown_plugin_dir)
|
||||
self.loop.create_task(self.loader_reinjector())
|
||||
self.loop.create_task(self.load_plugins())
|
||||
if not self.settings.getSetting("cef_forward", False):
|
||||
self.loop.create_task(stop_systemd_unit(REMOTE_DEBUGGER_UNIT))
|
||||
self.loop.set_exception_handler(self.exception_handler)
|
||||
self.web_app.add_routes([get("/auth/token", self.get_auth_token)])
|
||||
|
||||
for route in list(self.web_app.router.routes()):
|
||||
self.cors.add(route)
|
||||
self.cors.add(route)
|
||||
self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
|
||||
self.web_app.add_routes([static("/legacy", path.join(path.dirname(__file__), 'legacy'))])
|
||||
|
||||
@@ -94,7 +110,7 @@ class PluginManager:
|
||||
async def load_plugins(self):
|
||||
await self.wait_for_server()
|
||||
self.plugin_loader.import_plugins()
|
||||
#await inject_to_tab("SP", "window.syncDeckyPlugins();")
|
||||
# await inject_to_tab("SP", "window.syncDeckyPlugins();")
|
||||
|
||||
async def loader_reinjector(self):
|
||||
await sleep(2)
|
||||
@@ -107,7 +123,7 @@ class PluginManager:
|
||||
|
||||
async def inject_javascript(self, request=None):
|
||||
try:
|
||||
await inject_to_tab("SP", "try{" + open(path.join(path.dirname(__file__), "./static/plugin-loader.iife.js"), "r").read() + "}catch(e){console.error(e)}", True)
|
||||
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)
|
||||
except:
|
||||
logger.info("Failed to inject JavaScript into tab")
|
||||
pass
|
||||
|
||||
+39
-6
@@ -1,17 +1,19 @@
|
||||
import multiprocessing
|
||||
from asyncio import (Lock, get_event_loop, new_event_loop,
|
||||
open_unix_connection, set_event_loop, sleep,
|
||||
start_unix_server)
|
||||
start_unix_server, IncompleteReadError, LimitOverrunError)
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from json import dumps, load, loads
|
||||
from os import path, setuid
|
||||
from os import path, setgid, setuid
|
||||
from signal import SIGINT, signal
|
||||
from sys import exit
|
||||
from time import time
|
||||
|
||||
multiprocessing.set_start_method("fork")
|
||||
|
||||
BUFFER_LIMIT = 2 ** 20 # 1 MiB
|
||||
|
||||
class PluginWrapper:
|
||||
def __init__(self, file, plugin_directory, plugin_path) -> None:
|
||||
self.file = file
|
||||
@@ -21,7 +23,13 @@ class PluginWrapper:
|
||||
self.socket_addr = f"/tmp/plugin_socket_{time()}"
|
||||
self.method_call_lock = Lock()
|
||||
|
||||
self.version = None
|
||||
|
||||
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r"))
|
||||
if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
|
||||
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r"))
|
||||
self.version = package_json["version"]
|
||||
|
||||
|
||||
self.legacy = False
|
||||
self.main_view_html = json["main_view_html"] if "main_view_html" in json else ""
|
||||
@@ -43,6 +51,7 @@ class PluginWrapper:
|
||||
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)
|
||||
@@ -55,11 +64,23 @@ class PluginWrapper:
|
||||
get_event_loop().run_forever()
|
||||
|
||||
async def _setup_socket(self):
|
||||
self.socket = await start_unix_server(self._listen_for_method_call, path=self.socket_addr)
|
||||
self.socket = await start_unix_server(self._listen_for_method_call, path=self.socket_addr, limit=BUFFER_LIMIT)
|
||||
|
||||
async def _listen_for_method_call(self, reader, writer):
|
||||
while True:
|
||||
data = loads((await reader.readline()).decode("utf-8"))
|
||||
line = bytearray()
|
||||
while True:
|
||||
try:
|
||||
line.extend(await reader.readuntil())
|
||||
except LimitOverrunError:
|
||||
line.extend(await reader.read(reader._limit))
|
||||
continue
|
||||
except IncompleteReadError as err:
|
||||
line.extend(err.partial)
|
||||
break
|
||||
else:
|
||||
break
|
||||
data = loads(line.decode("utf-8"))
|
||||
if "stop" in data:
|
||||
get_event_loop().stop()
|
||||
while get_event_loop().is_running():
|
||||
@@ -81,7 +102,7 @@ class PluginWrapper:
|
||||
retries = 0
|
||||
while retries < 10:
|
||||
try:
|
||||
self.reader, self.writer = await open_unix_connection(self.socket_addr)
|
||||
self.reader, self.writer = await open_unix_connection(self.socket_addr, limit=BUFFER_LIMIT)
|
||||
return True
|
||||
except:
|
||||
await sleep(2)
|
||||
@@ -114,7 +135,19 @@ class PluginWrapper:
|
||||
self.writer.write(
|
||||
(dumps({"method": method_name, "args": kwargs})+"\n").encode("utf-8"))
|
||||
await self.writer.drain()
|
||||
res = loads((await self.reader.readline()).decode("utf-8"))
|
||||
line = bytearray()
|
||||
while True:
|
||||
try:
|
||||
line.extend(await self.reader.readuntil())
|
||||
except LimitOverrunError:
|
||||
line.extend(await self.reader.read(self.reader._limit))
|
||||
continue
|
||||
except IncompleteReadError as err:
|
||||
line.extend(err.partial)
|
||||
break
|
||||
else:
|
||||
break
|
||||
res = loads(line.decode("utf-8"))
|
||||
if not res["success"]:
|
||||
raise Exception(res["res"])
|
||||
return res["res"]
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import imp
|
||||
from json import dump, load
|
||||
from os import mkdir, path
|
||||
|
||||
from helpers import get_home_path, get_homebrew_path, get_user, set_user
|
||||
|
||||
|
||||
class SettingsManager:
|
||||
def __init__(self, name, settings_directory = None) -> None:
|
||||
set_user()
|
||||
USER = get_user()
|
||||
if settings_directory == None:
|
||||
settings_directory = get_homebrew_path(get_home_path(USER))
|
||||
self.path = path.join(settings_directory, name + ".json")
|
||||
|
||||
if not path.exists(settings_directory):
|
||||
mkdir(settings_directory)
|
||||
|
||||
self.settings = {}
|
||||
|
||||
try:
|
||||
open(self.path, "x")
|
||||
except FileExistsError as e:
|
||||
self.read()
|
||||
pass
|
||||
|
||||
def read(self):
|
||||
try:
|
||||
with open(self.path, "r") as file:
|
||||
self.settings = load(file)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
def commit(self):
|
||||
with open(self.path, "w+") as file:
|
||||
dump(self.settings, file, indent=4)
|
||||
|
||||
def getSetting(self, key, default):
|
||||
return self.settings.get(key, default)
|
||||
|
||||
def setSetting(self, key, value):
|
||||
self.settings[key] = value
|
||||
self.commit()
|
||||
+42
-12
@@ -1,31 +1,40 @@
|
||||
import uuid
|
||||
from logging import getLogger
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
from asyncio import sleep
|
||||
from ensurepip import version
|
||||
from json.decoder import JSONDecodeError
|
||||
from logging import getLogger
|
||||
from os import getcwd, path, remove
|
||||
from subprocess import call
|
||||
|
||||
from aiohttp import ClientSession, web
|
||||
|
||||
from injector import inject_to_tab, get_tab
|
||||
|
||||
from os import getcwd, path, remove
|
||||
|
||||
from subprocess import call
|
||||
|
||||
import helpers
|
||||
from injector import get_tab, inject_to_tab
|
||||
from settings import SettingsManager
|
||||
|
||||
logger = getLogger("Updater")
|
||||
|
||||
class Updater:
|
||||
def __init__(self, context) -> None:
|
||||
self.context = context
|
||||
self.settings = self.context.settings
|
||||
# Exposes updater methods to frontend
|
||||
self.updater_methods = {
|
||||
"get_branch": self.get_branch,
|
||||
"get_version": self.get_version,
|
||||
"do_update": self.do_update,
|
||||
"do_restart": self.do_restart,
|
||||
"check_for_updates": self.check_for_updates
|
||||
}
|
||||
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:
|
||||
with open(path.join(getcwd(), ".loader.version"), 'r') as version_file:
|
||||
self.localVer = version_file.readline().replace("\n", "")
|
||||
@@ -54,21 +63,43 @@ 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)
|
||||
|
||||
async def get_version(self):
|
||||
if self.localVer:
|
||||
return {
|
||||
"current": self.localVer,
|
||||
"remote": self.remoteVer,
|
||||
"all": self.allRemoteVers,
|
||||
"updatable": self.localVer != None
|
||||
}
|
||||
else:
|
||||
return {"current": "unknown", "remote": self.remoteVer, "updatable": False}
|
||||
return {"current": "unknown", "remote": self.remoteVer, "all": self.allRemoteVers, "updatable": False}
|
||||
|
||||
async def check_for_updates(self):
|
||||
logger.debug("checking for updates")
|
||||
selectedBranch = await 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()
|
||||
self.remoteVer = next(filter(lambda ver: ver["prerelease"] and ver["tag_name"].startswith("v") and ver["tag_name"].find("-pre"), remoteVersions), None)
|
||||
self.allRemoteVers = remoteVersions
|
||||
logger.debug("determining release type to find, branch is %i" % selectedBranch)
|
||||
if selectedBranch == 0:
|
||||
logger.debug("release type: release")
|
||||
self.remoteVer = next(filter(lambda ver: ver["tag_name"].startswith("v") and not ver["prerelease"] and ver["tag_name"], remoteVersions), None)
|
||||
elif selectedBranch == 1:
|
||||
logger.debug("release type: pre-release")
|
||||
self.remoteVer = next(filter(lambda ver: ver["prerelease"] and ver["tag_name"].startswith("v") and ver["tag_name"].find("-pre"), remoteVersions), None)
|
||||
# elif selectedBranch == 2:
|
||||
# logger.debug("release type: nightly")
|
||||
# self.remoteVer = next(filter(lambda ver: ver["prerelease"] and ver["tag_name"].startswith("v") and ver["tag_name"].find("nightly"), remoteVersions), None)
|
||||
else:
|
||||
logger.error("release type: NOT FOUND")
|
||||
raise ValueError("no valid branch found")
|
||||
# doesn't make it to this line below or farther
|
||||
# logger.debug("Remote Version: %s" % self.remoteVer.find("name"))
|
||||
logger.info("Updated remote version information")
|
||||
tab = await get_tab("SP")
|
||||
await tab.evaluate_js(f"window.DeckyPluginLoader.notifyUpdates()", False, True, False)
|
||||
@@ -85,7 +116,6 @@ class Updater:
|
||||
|
||||
async def do_update(self):
|
||||
version = self.remoteVer["tag_name"]
|
||||
#TODO don't hardcode this
|
||||
download_url = self.remoteVer["assets"][0]["browser_download_url"]
|
||||
|
||||
tab = await get_tab("SP")
|
||||
|
||||
+38
-5
@@ -5,6 +5,8 @@ from aiohttp import ClientSession, web
|
||||
|
||||
from injector import inject_to_tab
|
||||
import helpers
|
||||
import subprocess
|
||||
|
||||
|
||||
class Utilities:
|
||||
def __init__(self, context) -> None:
|
||||
@@ -12,11 +14,17 @@ class Utilities:
|
||||
self.util_methods = {
|
||||
"ping": self.ping,
|
||||
"http_request": self.http_request,
|
||||
"install_plugin": self.install_plugin,
|
||||
"cancel_plugin_install": self.cancel_plugin_install,
|
||||
"confirm_plugin_install": self.confirm_plugin_install,
|
||||
"uninstall_plugin": self.uninstall_plugin,
|
||||
"execute_in_tab": self.execute_in_tab,
|
||||
"inject_css_into_tab": self.inject_css_into_tab,
|
||||
"remove_css_from_tab": self.remove_css_from_tab
|
||||
"remove_css_from_tab": self.remove_css_from_tab,
|
||||
"allow_remote_debugging": self.allow_remote_debugging,
|
||||
"disallow_remote_debugging": self.disallow_remote_debugging,
|
||||
"set_setting": self.set_setting,
|
||||
"get_setting": self.get_setting
|
||||
}
|
||||
|
||||
if context:
|
||||
@@ -40,12 +48,23 @@ class Utilities:
|
||||
res["success"] = False
|
||||
return web.json_response(res)
|
||||
|
||||
async def install_plugin(self, artifact="", name="No name", version="dev", hash=False):
|
||||
return await self.context.plugin_browser.request_plugin_install(
|
||||
artifact=artifact,
|
||||
name=name,
|
||||
version=version,
|
||||
hash=hash
|
||||
)
|
||||
|
||||
async def confirm_plugin_install(self, request_id):
|
||||
return await self.context.plugin_browser.confirm_plugin_install(request_id)
|
||||
|
||||
def cancel_plugin_install(self, request_id):
|
||||
return self.context.plugin_browser.cancel_plugin_install(request_id)
|
||||
|
||||
async def uninstall_plugin(self, name):
|
||||
return await self.context.plugin_browser.uninstall_plugin(name)
|
||||
|
||||
async def http_request(self, method="", url="", **kwargs):
|
||||
async with ClientSession() as web:
|
||||
async with web.request(method, url, ssl=helpers.get_ssl_context(), **kwargs) as res:
|
||||
@@ -69,12 +88,12 @@ class Utilities:
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"result" : result["result"]["result"].get("value")
|
||||
"result": result["result"]["result"].get("value")
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"result": e
|
||||
"success": False,
|
||||
"result": e
|
||||
}
|
||||
|
||||
async def inject_css_into_tab(self, tab, style):
|
||||
@@ -99,7 +118,7 @@ class Utilities:
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"result" : css_id
|
||||
"result": css_id
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
@@ -133,3 +152,17 @@ class Utilities:
|
||||
"success": False,
|
||||
"result": e
|
||||
}
|
||||
|
||||
async def get_setting(self, key, default):
|
||||
return self.context.settings.getSetting(key, default)
|
||||
|
||||
async def set_setting(self, key, value):
|
||||
return self.context.settings.setSetting(key, value)
|
||||
|
||||
async def allow_remote_debugging(self):
|
||||
await helpers.start_systemd_unit(helpers.REMOTE_DEBUGGER_UNIT)
|
||||
return True
|
||||
|
||||
async def disallow_remote_debugging(self):
|
||||
await helpers.stop_systemd_unit(helpers.REMOTE_DEBUGGER_UNIT)
|
||||
return True
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
"react": "16.14.0",
|
||||
"react-dom": "16.14.0",
|
||||
"rollup": "^2.76.0",
|
||||
"rollup-plugin-external-globals": "^0.6.1",
|
||||
"rollup-plugin-polyfill-node": "^0.10.2",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
@@ -37,7 +39,9 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"decky-frontend-lib": "^1.7.5",
|
||||
"react-icons": "^4.4.0"
|
||||
"decky-frontend-lib": "^2.0.0",
|
||||
"react-icons": "^4.4.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"remark-gfm": "^3.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+708
-9
@@ -9,7 +9,7 @@ specifiers:
|
||||
'@types/react': 16.14.0
|
||||
'@types/react-router': 5.1.18
|
||||
'@types/webpack': ^5.28.0
|
||||
decky-frontend-lib: ^1.7.5
|
||||
decky-frontend-lib: ^2.0.0
|
||||
husky: ^8.0.1
|
||||
import-sort-style-module: ^6.0.0
|
||||
inquirer: ^8.2.4
|
||||
@@ -18,13 +18,19 @@ specifiers:
|
||||
react: 16.14.0
|
||||
react-dom: 16.14.0
|
||||
react-icons: ^4.4.0
|
||||
react-markdown: ^8.0.3
|
||||
remark-gfm: ^3.0.1
|
||||
rollup: ^2.76.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: 1.7.5
|
||||
decky-frontend-lib: 2.0.0
|
||||
react-icons: 4.4.0_react@16.14.0
|
||||
react-markdown: 8.0.3_vshvapmxg47tngu7tvrsqpq55u
|
||||
remark-gfm: 3.0.1
|
||||
|
||||
devDependencies:
|
||||
'@rollup/plugin-commonjs': 21.1.0_rollup@2.76.0
|
||||
@@ -43,6 +49,8 @@ devDependencies:
|
||||
react: 16.14.0
|
||||
react-dom: 16.14.0_react@16.14.0
|
||||
rollup: 2.76.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
|
||||
typescript: 4.7.4
|
||||
|
||||
@@ -304,6 +312,17 @@ packages:
|
||||
rollup: 2.76.0
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-inject/4.0.4_rollup@2.76.0:
|
||||
resolution: {integrity: sha512-4pbcU4J/nS+zuHk+c+OL3WtmEQhqxlZ9uqfjQMQDOHOPld7PsCd8k5LWs8h5wjwJN7MgnAn768F2sDxEP4eNFQ==}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0 || ^2.0.0
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 3.1.0_rollup@2.76.0
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.25.9
|
||||
rollup: 2.76.0
|
||||
dev: true
|
||||
|
||||
/@rollup/plugin-json/4.1.0_rollup@2.76.0:
|
||||
resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==}
|
||||
peerDependencies:
|
||||
@@ -368,6 +387,20 @@ packages:
|
||||
rollup: 2.76.0
|
||||
dev: true
|
||||
|
||||
/@rollup/pluginutils/4.2.1:
|
||||
resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==}
|
||||
engines: {node: '>= 8.0.0'}
|
||||
dependencies:
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/@types/debug/4.1.7:
|
||||
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
|
||||
dependencies:
|
||||
'@types/ms': 0.7.31
|
||||
dev: false
|
||||
|
||||
/@types/eslint-scope/3.7.4:
|
||||
resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
|
||||
dependencies:
|
||||
@@ -394,6 +427,12 @@ packages:
|
||||
resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
|
||||
dev: true
|
||||
|
||||
/@types/hast/2.3.4:
|
||||
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
dev: false
|
||||
|
||||
/@types/history/4.7.11:
|
||||
resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
|
||||
dev: true
|
||||
@@ -402,13 +441,26 @@ packages:
|
||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||
dev: true
|
||||
|
||||
/@types/mdast/3.0.10:
|
||||
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
dev: false
|
||||
|
||||
/@types/mdurl/1.0.2:
|
||||
resolution: {integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==}
|
||||
dev: false
|
||||
|
||||
/@types/ms/0.7.31:
|
||||
resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==}
|
||||
dev: false
|
||||
|
||||
/@types/node/18.0.4:
|
||||
resolution: {integrity: sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==}
|
||||
dev: true
|
||||
|
||||
/@types/prop-types/15.7.5:
|
||||
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
|
||||
dev: true
|
||||
|
||||
/@types/react-router/5.1.18:
|
||||
resolution: {integrity: sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==}
|
||||
@@ -422,7 +474,6 @@ packages:
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.5
|
||||
csstype: 3.1.0
|
||||
dev: true
|
||||
|
||||
/@types/resolve/1.17.1:
|
||||
resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==}
|
||||
@@ -430,6 +481,10 @@ packages:
|
||||
'@types/node': 18.0.4
|
||||
dev: true
|
||||
|
||||
/@types/unist/2.0.6:
|
||||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: false
|
||||
|
||||
/@types/webpack/5.28.0:
|
||||
resolution: {integrity: sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==}
|
||||
dependencies:
|
||||
@@ -620,6 +675,10 @@ packages:
|
||||
sprintf-js: 1.0.3
|
||||
dev: true
|
||||
|
||||
/bail/2.0.2:
|
||||
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
|
||||
dev: false
|
||||
|
||||
/balanced-match/1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
dev: true
|
||||
@@ -693,6 +752,10 @@ packages:
|
||||
resolution: {integrity: sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==}
|
||||
dev: true
|
||||
|
||||
/ccount/2.0.1:
|
||||
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
|
||||
dev: false
|
||||
|
||||
/chalk/2.4.2:
|
||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -710,6 +773,10 @@ packages:
|
||||
supports-color: 7.2.0
|
||||
dev: true
|
||||
|
||||
/character-entities/2.0.2:
|
||||
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
|
||||
dev: false
|
||||
|
||||
/chardet/0.7.0:
|
||||
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
|
||||
dev: true
|
||||
@@ -762,6 +829,10 @@ packages:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
dev: true
|
||||
|
||||
/comma-separated-tokens/2.0.2:
|
||||
resolution: {integrity: sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==}
|
||||
dev: false
|
||||
|
||||
/commander/2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
dev: true
|
||||
@@ -792,7 +863,6 @@ packages:
|
||||
|
||||
/csstype/3.1.0:
|
||||
resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==}
|
||||
dev: true
|
||||
|
||||
/debug/4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
@@ -804,14 +874,19 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/decky-frontend-lib/1.7.5:
|
||||
resolution: {integrity: sha512-1OX/Ix9W76gF0NJjfm0k/01LYPmC2k/k+k/qqH8JJPlPHh5+W5P8ZG2T8m5wKsqoP7jx2W3k7RNZBh9vAqFoFw==}
|
||||
/decky-frontend-lib/2.0.0:
|
||||
resolution: {integrity: sha512-H7+JpKHlClECVpo+MCEwej7R9wDWk9M2uMSyTvuhTfLZe3RThsxWCiqY640Cjh/zIW2A7GyVRd4SjLtn6Isdeg==}
|
||||
dependencies:
|
||||
minimist: 1.2.6
|
||||
dev: false
|
||||
|
||||
/decode-named-character-reference/1.0.2:
|
||||
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
|
||||
dependencies:
|
||||
character-entities: 2.0.2
|
||||
dev: false
|
||||
|
||||
/deepmerge/4.2.2:
|
||||
resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -823,11 +898,21 @@ packages:
|
||||
clone: 1.0.4
|
||||
dev: true
|
||||
|
||||
/dequal/2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/detect-newline/2.1.0:
|
||||
resolution: {integrity: sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/diff/5.1.0:
|
||||
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium/1.4.189:
|
||||
resolution: {integrity: sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==}
|
||||
dev: true
|
||||
@@ -864,6 +949,11 @@ packages:
|
||||
engines: {node: '>=0.8.0'}
|
||||
dev: true
|
||||
|
||||
/escape-string-regexp/5.0.0:
|
||||
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/eslint-scope/5.1.1:
|
||||
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
@@ -908,6 +998,10 @@ packages:
|
||||
engines: {node: '>=0.8.x'}
|
||||
dev: true
|
||||
|
||||
/extend/3.0.2:
|
||||
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
|
||||
dev: false
|
||||
|
||||
/external-editor/3.1.0:
|
||||
resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1002,6 +1096,10 @@ packages:
|
||||
function-bind: 1.1.1
|
||||
dev: true
|
||||
|
||||
/hast-util-whitespace/2.0.0:
|
||||
resolution: {integrity: sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==}
|
||||
dev: false
|
||||
|
||||
/husky/8.0.1:
|
||||
resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1087,6 +1185,10 @@ packages:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
dev: true
|
||||
|
||||
/inline-style-parser/0.1.1:
|
||||
resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
|
||||
dev: false
|
||||
|
||||
/inquirer/8.2.4:
|
||||
resolution: {integrity: sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -1112,6 +1214,11 @@ packages:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
dev: true
|
||||
|
||||
/is-buffer/2.0.5:
|
||||
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/is-builtin-module/3.1.0:
|
||||
resolution: {integrity: sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1144,6 +1251,11 @@ packages:
|
||||
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
|
||||
dev: true
|
||||
|
||||
/is-plain-obj/4.1.0:
|
||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/is-reference/1.2.1:
|
||||
resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==}
|
||||
dependencies:
|
||||
@@ -1199,6 +1311,11 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/kleur/4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/loader-runner/4.3.0:
|
||||
resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==}
|
||||
engines: {node: '>=6.11.5'}
|
||||
@@ -1216,6 +1333,10 @@ packages:
|
||||
is-unicode-supported: 0.1.0
|
||||
dev: true
|
||||
|
||||
/longest-streak/3.0.1:
|
||||
resolution: {integrity: sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==}
|
||||
dev: false
|
||||
|
||||
/loose-envify/1.4.0:
|
||||
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
|
||||
hasBin: true
|
||||
@@ -1228,10 +1349,389 @@ packages:
|
||||
sourcemap-codec: 1.4.8
|
||||
dev: true
|
||||
|
||||
/markdown-table/3.0.2:
|
||||
resolution: {integrity: sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==}
|
||||
dev: false
|
||||
|
||||
/mdast-util-definitions/5.1.1:
|
||||
resolution: {integrity: sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
'@types/unist': 2.0.6
|
||||
unist-util-visit: 4.1.1
|
||||
dev: false
|
||||
|
||||
/mdast-util-find-and-replace/2.2.1:
|
||||
resolution: {integrity: sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==}
|
||||
dependencies:
|
||||
escape-string-regexp: 5.0.0
|
||||
unist-util-is: 5.1.1
|
||||
unist-util-visit-parents: 5.1.1
|
||||
dev: false
|
||||
|
||||
/mdast-util-from-markdown/1.2.0:
|
||||
resolution: {integrity: sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
'@types/unist': 2.0.6
|
||||
decode-named-character-reference: 1.0.2
|
||||
mdast-util-to-string: 3.1.0
|
||||
micromark: 3.0.10
|
||||
micromark-util-decode-numeric-character-reference: 1.0.0
|
||||
micromark-util-decode-string: 1.0.2
|
||||
micromark-util-normalize-identifier: 1.0.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
unist-util-stringify-position: 3.0.2
|
||||
uvu: 0.5.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-autolink-literal/1.0.2:
|
||||
resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
ccount: 2.0.1
|
||||
mdast-util-find-and-replace: 2.2.1
|
||||
micromark-util-character: 1.1.0
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-footnote/1.0.1:
|
||||
resolution: {integrity: sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-to-markdown: 1.3.0
|
||||
micromark-util-normalize-identifier: 1.0.0
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-strikethrough/1.0.1:
|
||||
resolution: {integrity: sha512-zKJbEPe+JP6EUv0mZ0tQUyLQOC+FADt0bARldONot/nefuISkaZFlmVK4tU6JgfyZGrky02m/I6PmehgAgZgqg==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-to-markdown: 1.3.0
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-table/1.0.4:
|
||||
resolution: {integrity: sha512-aEuoPwZyP4iIMkf2cLWXxx3EQ6Bmh2yKy9MVCg4i6Sd3cX80dcLEfXO/V4ul3pGH9czBK4kp+FAl+ZHmSUt9/w==}
|
||||
dependencies:
|
||||
markdown-table: 3.0.2
|
||||
mdast-util-from-markdown: 1.2.0
|
||||
mdast-util-to-markdown: 1.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-task-list-item/1.0.1:
|
||||
resolution: {integrity: sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-to-markdown: 1.3.0
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm/2.0.1:
|
||||
resolution: {integrity: sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==}
|
||||
dependencies:
|
||||
mdast-util-from-markdown: 1.2.0
|
||||
mdast-util-gfm-autolink-literal: 1.0.2
|
||||
mdast-util-gfm-footnote: 1.0.1
|
||||
mdast-util-gfm-strikethrough: 1.0.1
|
||||
mdast-util-gfm-table: 1.0.4
|
||||
mdast-util-gfm-task-list-item: 1.0.1
|
||||
mdast-util-to-markdown: 1.3.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-to-hast/12.2.1:
|
||||
resolution: {integrity: sha512-dyindR2P7qOqXO1hQirZeGtVbiX7xlNQbw7gGaAwN4A1dh4+X8xU/JyYmRoyB8Fu1uPXzp7mlL5QwW7k+knvgA==}
|
||||
dependencies:
|
||||
'@types/hast': 2.3.4
|
||||
'@types/mdast': 3.0.10
|
||||
'@types/mdurl': 1.0.2
|
||||
mdast-util-definitions: 5.1.1
|
||||
mdurl: 1.0.1
|
||||
micromark-util-sanitize-uri: 1.0.0
|
||||
trim-lines: 3.0.1
|
||||
unist-builder: 3.0.0
|
||||
unist-util-generated: 2.0.0
|
||||
unist-util-position: 4.0.3
|
||||
unist-util-visit: 4.1.1
|
||||
dev: false
|
||||
|
||||
/mdast-util-to-markdown/1.3.0:
|
||||
resolution: {integrity: sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
'@types/unist': 2.0.6
|
||||
longest-streak: 3.0.1
|
||||
mdast-util-to-string: 3.1.0
|
||||
micromark-util-decode-string: 1.0.2
|
||||
unist-util-visit: 4.1.1
|
||||
zwitch: 2.0.2
|
||||
dev: false
|
||||
|
||||
/mdast-util-to-string/3.1.0:
|
||||
resolution: {integrity: sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA==}
|
||||
dev: false
|
||||
|
||||
/mdurl/1.0.1:
|
||||
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
|
||||
dev: false
|
||||
|
||||
/merge-stream/2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
dev: true
|
||||
|
||||
/micromark-core-commonmark/1.0.6:
|
||||
resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==}
|
||||
dependencies:
|
||||
decode-named-character-reference: 1.0.2
|
||||
micromark-factory-destination: 1.0.0
|
||||
micromark-factory-label: 1.0.2
|
||||
micromark-factory-space: 1.0.0
|
||||
micromark-factory-title: 1.0.2
|
||||
micromark-factory-whitespace: 1.0.0
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-chunked: 1.0.0
|
||||
micromark-util-classify-character: 1.0.0
|
||||
micromark-util-html-tag-name: 1.1.0
|
||||
micromark-util-normalize-identifier: 1.0.0
|
||||
micromark-util-resolve-all: 1.0.0
|
||||
micromark-util-subtokenize: 1.0.2
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-autolink-literal/1.0.3:
|
||||
resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==}
|
||||
dependencies:
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-sanitize-uri: 1.0.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-footnote/1.0.4:
|
||||
resolution: {integrity: sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==}
|
||||
dependencies:
|
||||
micromark-core-commonmark: 1.0.6
|
||||
micromark-factory-space: 1.0.0
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-normalize-identifier: 1.0.0
|
||||
micromark-util-sanitize-uri: 1.0.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-strikethrough/1.0.4:
|
||||
resolution: {integrity: sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==}
|
||||
dependencies:
|
||||
micromark-util-chunked: 1.0.0
|
||||
micromark-util-classify-character: 1.0.0
|
||||
micromark-util-resolve-all: 1.0.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-table/1.0.5:
|
||||
resolution: {integrity: sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==}
|
||||
dependencies:
|
||||
micromark-factory-space: 1.0.0
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-tagfilter/1.0.1:
|
||||
resolution: {integrity: sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==}
|
||||
dependencies:
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-task-list-item/1.0.3:
|
||||
resolution: {integrity: sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==}
|
||||
dependencies:
|
||||
micromark-factory-space: 1.0.0
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm/2.0.1:
|
||||
resolution: {integrity: sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==}
|
||||
dependencies:
|
||||
micromark-extension-gfm-autolink-literal: 1.0.3
|
||||
micromark-extension-gfm-footnote: 1.0.4
|
||||
micromark-extension-gfm-strikethrough: 1.0.4
|
||||
micromark-extension-gfm-table: 1.0.5
|
||||
micromark-extension-gfm-tagfilter: 1.0.1
|
||||
micromark-extension-gfm-task-list-item: 1.0.3
|
||||
micromark-util-combine-extensions: 1.0.0
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-factory-destination/1.0.0:
|
||||
resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==}
|
||||
dependencies:
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-factory-label/1.0.2:
|
||||
resolution: {integrity: sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==}
|
||||
dependencies:
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-factory-space/1.0.0:
|
||||
resolution: {integrity: sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==}
|
||||
dependencies:
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-factory-title/1.0.2:
|
||||
resolution: {integrity: sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==}
|
||||
dependencies:
|
||||
micromark-factory-space: 1.0.0
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-factory-whitespace/1.0.0:
|
||||
resolution: {integrity: sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==}
|
||||
dependencies:
|
||||
micromark-factory-space: 1.0.0
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-util-character/1.1.0:
|
||||
resolution: {integrity: sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==}
|
||||
dependencies:
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-util-chunked/1.0.0:
|
||||
resolution: {integrity: sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==}
|
||||
dependencies:
|
||||
micromark-util-symbol: 1.0.1
|
||||
dev: false
|
||||
|
||||
/micromark-util-classify-character/1.0.0:
|
||||
resolution: {integrity: sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==}
|
||||
dependencies:
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-util-combine-extensions/1.0.0:
|
||||
resolution: {integrity: sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==}
|
||||
dependencies:
|
||||
micromark-util-chunked: 1.0.0
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-util-decode-numeric-character-reference/1.0.0:
|
||||
resolution: {integrity: sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==}
|
||||
dependencies:
|
||||
micromark-util-symbol: 1.0.1
|
||||
dev: false
|
||||
|
||||
/micromark-util-decode-string/1.0.2:
|
||||
resolution: {integrity: sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==}
|
||||
dependencies:
|
||||
decode-named-character-reference: 1.0.2
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-decode-numeric-character-reference: 1.0.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
dev: false
|
||||
|
||||
/micromark-util-encode/1.0.1:
|
||||
resolution: {integrity: sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==}
|
||||
dev: false
|
||||
|
||||
/micromark-util-html-tag-name/1.1.0:
|
||||
resolution: {integrity: sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==}
|
||||
dev: false
|
||||
|
||||
/micromark-util-normalize-identifier/1.0.0:
|
||||
resolution: {integrity: sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==}
|
||||
dependencies:
|
||||
micromark-util-symbol: 1.0.1
|
||||
dev: false
|
||||
|
||||
/micromark-util-resolve-all/1.0.0:
|
||||
resolution: {integrity: sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==}
|
||||
dependencies:
|
||||
micromark-util-types: 1.0.2
|
||||
dev: false
|
||||
|
||||
/micromark-util-sanitize-uri/1.0.0:
|
||||
resolution: {integrity: sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg==}
|
||||
dependencies:
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-encode: 1.0.1
|
||||
micromark-util-symbol: 1.0.1
|
||||
dev: false
|
||||
|
||||
/micromark-util-subtokenize/1.0.2:
|
||||
resolution: {integrity: sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==}
|
||||
dependencies:
|
||||
micromark-util-chunked: 1.0.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
dev: false
|
||||
|
||||
/micromark-util-symbol/1.0.1:
|
||||
resolution: {integrity: sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==}
|
||||
dev: false
|
||||
|
||||
/micromark-util-types/1.0.2:
|
||||
resolution: {integrity: sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==}
|
||||
dev: false
|
||||
|
||||
/micromark/3.0.10:
|
||||
resolution: {integrity: sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg==}
|
||||
dependencies:
|
||||
'@types/debug': 4.1.7
|
||||
debug: 4.3.4
|
||||
decode-named-character-reference: 1.0.2
|
||||
micromark-core-commonmark: 1.0.6
|
||||
micromark-factory-space: 1.0.0
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-chunked: 1.0.0
|
||||
micromark-util-combine-extensions: 1.0.0
|
||||
micromark-util-decode-numeric-character-reference: 1.0.0
|
||||
micromark-util-encode: 1.0.1
|
||||
micromark-util-normalize-identifier: 1.0.0
|
||||
micromark-util-resolve-all: 1.0.0
|
||||
micromark-util-sanitize-uri: 1.0.0
|
||||
micromark-util-subtokenize: 1.0.2
|
||||
micromark-util-symbol: 1.0.1
|
||||
micromark-util-types: 1.0.2
|
||||
uvu: 0.5.6
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mime-db/1.52.0:
|
||||
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -1259,9 +1759,13 @@ packages:
|
||||
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
|
||||
dev: false
|
||||
|
||||
/mri/1.2.0:
|
||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/ms/2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/mute-stream/0.0.8:
|
||||
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
|
||||
@@ -1365,6 +1869,10 @@ packages:
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
/property-information/6.1.1:
|
||||
resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==}
|
||||
dev: false
|
||||
|
||||
/punycode/2.1.1:
|
||||
resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1399,6 +1907,37 @@ packages:
|
||||
/react-is/16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
/react-is/18.2.0:
|
||||
resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
|
||||
dev: false
|
||||
|
||||
/react-markdown/8.0.3_vshvapmxg47tngu7tvrsqpq55u:
|
||||
resolution: {integrity: sha512-We36SfqaKoVNpN1QqsZwWSv/OZt5J15LNgTLWynwAN5b265hrQrsjMtlRNwUvS+YyR3yDM8HpTNc4pK9H/Gc0A==}
|
||||
peerDependencies:
|
||||
'@types/react': '>=16'
|
||||
react: '>=16'
|
||||
dependencies:
|
||||
'@types/hast': 2.3.4
|
||||
'@types/prop-types': 15.7.5
|
||||
'@types/react': 16.14.0
|
||||
'@types/unist': 2.0.6
|
||||
comma-separated-tokens: 2.0.2
|
||||
hast-util-whitespace: 2.0.0
|
||||
prop-types: 15.8.1
|
||||
property-information: 6.1.1
|
||||
react: 16.14.0
|
||||
react-is: 18.2.0
|
||||
remark-parse: 10.0.1
|
||||
remark-rehype: 10.1.0
|
||||
space-separated-tokens: 2.0.1
|
||||
style-to-object: 0.3.0
|
||||
unified: 10.1.2
|
||||
unist-util-visit: 4.1.1
|
||||
vfile: 5.3.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/react/16.14.0:
|
||||
resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1416,6 +1955,36 @@ packages:
|
||||
util-deprecate: 1.0.2
|
||||
dev: true
|
||||
|
||||
/remark-gfm/3.0.1:
|
||||
resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-gfm: 2.0.1
|
||||
micromark-extension-gfm: 2.0.1
|
||||
unified: 10.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/remark-parse/10.0.1:
|
||||
resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-from-markdown: 1.2.0
|
||||
unified: 10.1.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/remark-rehype/10.1.0:
|
||||
resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==}
|
||||
dependencies:
|
||||
'@types/hast': 2.3.4
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-to-hast: 12.2.1
|
||||
unified: 10.1.2
|
||||
dev: false
|
||||
|
||||
/resolve-from/3.0.0:
|
||||
resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1443,6 +2012,27 @@ packages:
|
||||
signal-exit: 3.0.7
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-external-globals/0.6.1_rollup@2.76.0:
|
||||
resolution: {integrity: sha512-mlp3KNa5sE4Sp9UUR2rjBrxjG79OyZAh/QC18RHIjM+iYkbBwNXSo8DHRMZWtzJTrH8GxQ+SJvCTN3i14uMXIA==}
|
||||
peerDependencies:
|
||||
rollup: ^2.25.0
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 4.2.1
|
||||
estree-walker: 2.0.2
|
||||
is-reference: 1.2.1
|
||||
magic-string: 0.25.9
|
||||
rollup: 2.76.0
|
||||
dev: true
|
||||
|
||||
/rollup-plugin-polyfill-node/0.10.2_rollup@2.76.0:
|
||||
resolution: {integrity: sha512-5GMywXiLiuQP6ZzED/LO/Q0HyDi2W6b8VN+Zd3oB0opIjyRs494Me2ZMaqKWDNbGiW4jvvzl6L2n4zRgxS9cSQ==}
|
||||
peerDependencies:
|
||||
rollup: ^1.20.0 || ^2.0.0
|
||||
dependencies:
|
||||
'@rollup/plugin-inject': 4.0.4_rollup@2.76.0
|
||||
rollup: 2.76.0
|
||||
dev: true
|
||||
|
||||
/rollup/2.76.0:
|
||||
resolution: {integrity: sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -1462,6 +2052,13 @@ packages:
|
||||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/sade/1.8.1:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
dev: false
|
||||
|
||||
/safe-buffer/5.1.2:
|
||||
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
|
||||
dev: true
|
||||
@@ -1521,6 +2118,10 @@ packages:
|
||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||
dev: true
|
||||
|
||||
/space-separated-tokens/2.0.1:
|
||||
resolution: {integrity: sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==}
|
||||
dev: false
|
||||
|
||||
/sprintf-js/1.0.3:
|
||||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
dev: true
|
||||
@@ -1547,6 +2148,12 @@ packages:
|
||||
ansi-regex: 5.0.1
|
||||
dev: true
|
||||
|
||||
/style-to-object/0.3.0:
|
||||
resolution: {integrity: sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==}
|
||||
dependencies:
|
||||
inline-style-parser: 0.1.1
|
||||
dev: false
|
||||
|
||||
/supports-color/5.5.0:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -1629,6 +2236,14 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
dev: true
|
||||
|
||||
/trim-lines/3.0.1:
|
||||
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
|
||||
dev: false
|
||||
|
||||
/trough/2.1.0:
|
||||
resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==}
|
||||
dev: false
|
||||
|
||||
/tslib/2.4.0:
|
||||
resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==}
|
||||
dev: true
|
||||
@@ -1650,6 +2265,59 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/unified/10.1.2:
|
||||
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
bail: 2.0.2
|
||||
extend: 3.0.2
|
||||
is-buffer: 2.0.5
|
||||
is-plain-obj: 4.1.0
|
||||
trough: 2.1.0
|
||||
vfile: 5.3.4
|
||||
dev: false
|
||||
|
||||
/unist-builder/3.0.0:
|
||||
resolution: {integrity: sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
dev: false
|
||||
|
||||
/unist-util-generated/2.0.0:
|
||||
resolution: {integrity: sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw==}
|
||||
dev: false
|
||||
|
||||
/unist-util-is/5.1.1:
|
||||
resolution: {integrity: sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ==}
|
||||
dev: false
|
||||
|
||||
/unist-util-position/4.0.3:
|
||||
resolution: {integrity: sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
dev: false
|
||||
|
||||
/unist-util-stringify-position/3.0.2:
|
||||
resolution: {integrity: sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
dev: false
|
||||
|
||||
/unist-util-visit-parents/5.1.1:
|
||||
resolution: {integrity: sha512-gks4baapT/kNRaWxuGkl5BIhoanZo7sC/cUT/JToSRNL1dYoXRFl75d++NkjYk4TAu2uv2Px+l8guMajogeuiw==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
unist-util-is: 5.1.1
|
||||
dev: false
|
||||
|
||||
/unist-util-visit/4.1.1:
|
||||
resolution: {integrity: sha512-n9KN3WV9k4h1DxYR1LoajgN93wpEi/7ZplVe02IoB4gH5ctI1AaF2670BLHQYbwj+pY83gFtyeySFiyMHJklrg==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
unist-util-is: 5.1.1
|
||||
unist-util-visit-parents: 5.1.1
|
||||
dev: false
|
||||
|
||||
/update-browserslist-db/1.0.4_browserslist@4.21.2:
|
||||
resolution: {integrity: sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==}
|
||||
hasBin: true
|
||||
@@ -1671,6 +2339,33 @@ packages:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: true
|
||||
|
||||
/uvu/0.5.6:
|
||||
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
diff: 5.1.0
|
||||
kleur: 4.1.5
|
||||
sade: 1.8.1
|
||||
dev: false
|
||||
|
||||
/vfile-message/3.1.2:
|
||||
resolution: {integrity: sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
unist-util-stringify-position: 3.0.2
|
||||
dev: false
|
||||
|
||||
/vfile/5.3.4:
|
||||
resolution: {integrity: sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw==}
|
||||
dependencies:
|
||||
'@types/unist': 2.0.6
|
||||
is-buffer: 2.0.5
|
||||
unist-util-stringify-position: 3.0.2
|
||||
vfile-message: 3.1.2
|
||||
dev: false
|
||||
|
||||
/watchpack/2.4.0:
|
||||
resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
@@ -1742,3 +2437,7 @@ packages:
|
||||
/wrappy/1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
/zwitch/2.0.2:
|
||||
resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==}
|
||||
dev: false
|
||||
|
||||
@@ -1,6 +1,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 replace from '@rollup/plugin-replace';
|
||||
import typescript from '@rollup/plugin-typescript';
|
||||
import { defineConfig } from 'rollup';
|
||||
@@ -10,6 +11,14 @@ export default defineConfig({
|
||||
plugins: [
|
||||
commonjs(),
|
||||
nodeResolve(),
|
||||
externalGlobals({
|
||||
react: 'SP_REACT',
|
||||
'react-dom': 'SP_REACTDOM',
|
||||
// hack to shut up react-markdown
|
||||
'process': '{cwd: () => {}}',
|
||||
'path': '{dirname: () => {}, join: () => {}, basename: () => {}, extname: () => {}}',
|
||||
'url': '{fileURLToPath: (f) => f}'
|
||||
}),
|
||||
typescript(),
|
||||
json(),
|
||||
replace({
|
||||
@@ -17,13 +26,12 @@ export default defineConfig({
|
||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||
}),
|
||||
],
|
||||
external: ["react", "react-dom"],
|
||||
preserveEntrySignatures: false,
|
||||
output: {
|
||||
file: '../backend/static/plugin-loader.iife.js',
|
||||
globals: {
|
||||
react: 'SP_REACT',
|
||||
'react-dom': 'SP_REACTDOM',
|
||||
},
|
||||
format: 'iife',
|
||||
},
|
||||
dir: '../backend/static',
|
||||
format: 'esm',
|
||||
chunkFileNames: (chunkInfo) => {
|
||||
return 'chunk-[hash].js'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,20 +1,42 @@
|
||||
import { FC, createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
import { Plugin } from '../plugin';
|
||||
import { PluginUpdateMapping } from '../store';
|
||||
import { VerInfo } from '../updater';
|
||||
|
||||
interface PublicDeckyState {
|
||||
plugins: Plugin[];
|
||||
activePlugin: Plugin | null;
|
||||
updates: PluginUpdateMapping | null;
|
||||
hasLoaderUpdate?: boolean;
|
||||
isLoaderUpdating: boolean;
|
||||
versionInfo: VerInfo | null;
|
||||
}
|
||||
|
||||
export class DeckyState {
|
||||
private _plugins: Plugin[] = [];
|
||||
private _activePlugin: Plugin | null = null;
|
||||
private _updates: PluginUpdateMapping | null = null;
|
||||
private _hasLoaderUpdate: boolean = false;
|
||||
private _isLoaderUpdating: boolean = false;
|
||||
private _versionInfo: VerInfo | null = null;
|
||||
|
||||
public eventBus = new EventTarget();
|
||||
|
||||
publicState(): PublicDeckyState {
|
||||
return { plugins: this._plugins, activePlugin: this._activePlugin };
|
||||
return {
|
||||
plugins: this._plugins,
|
||||
activePlugin: this._activePlugin,
|
||||
updates: this._updates,
|
||||
hasLoaderUpdate: this._hasLoaderUpdate,
|
||||
isLoaderUpdating: this._isLoaderUpdating,
|
||||
versionInfo: this._versionInfo,
|
||||
};
|
||||
}
|
||||
|
||||
setVersionInfo(versionInfo: VerInfo) {
|
||||
this._versionInfo = versionInfo;
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
setPlugins(plugins: Plugin[]) {
|
||||
@@ -32,12 +54,29 @@ export class DeckyState {
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
setUpdates(updates: PluginUpdateMapping) {
|
||||
this._updates = updates;
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
setHasLoaderUpdate(hasUpdate: boolean) {
|
||||
this._hasLoaderUpdate = hasUpdate;
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
setIsLoaderUpdating(isUpdating: boolean) {
|
||||
this._isLoaderUpdating = isUpdating;
|
||||
this.notifyUpdate();
|
||||
}
|
||||
|
||||
private notifyUpdate() {
|
||||
this.eventBus.dispatchEvent(new Event('update'));
|
||||
}
|
||||
}
|
||||
|
||||
interface DeckyStateContext extends PublicDeckyState {
|
||||
setVersionInfo(versionInfo: VerInfo): void;
|
||||
setIsLoaderUpdating(hasUpdate: boolean): void;
|
||||
setActivePlugin(name: string): void;
|
||||
closeActivePlugin(): void;
|
||||
}
|
||||
@@ -63,11 +102,15 @@ export const DeckyStateContextProvider: FC<Props> = ({ children, deckyState }) =
|
||||
return () => deckyState.eventBus.removeEventListener('update', onUpdate);
|
||||
}, []);
|
||||
|
||||
const setIsLoaderUpdating = (hasUpdate: boolean) => deckyState.setIsLoaderUpdating(hasUpdate);
|
||||
const setVersionInfo = (versionInfo: VerInfo) => deckyState.setVersionInfo(versionInfo);
|
||||
const setActivePlugin = (name: string) => deckyState.setActivePlugin(name);
|
||||
const closeActivePlugin = () => deckyState.closeActivePlugin();
|
||||
|
||||
return (
|
||||
<DeckyStateContext.Provider value={{ ...publicDeckyState, setActivePlugin, closeActivePlugin }}>
|
||||
<DeckyStateContext.Provider
|
||||
value={{ ...publicDeckyState, setIsLoaderUpdating, setVersionInfo, setActivePlugin, closeActivePlugin }}
|
||||
>
|
||||
{children}
|
||||
</DeckyStateContext.Provider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { FunctionComponent } 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} />;
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { CSSProperties, FunctionComponent } from 'react';
|
||||
|
||||
interface NotificationBadgeProps {
|
||||
show?: boolean;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const NotificationBadge: FunctionComponent<NotificationBadgeProps> = ({ show, style }) => {
|
||||
return show ? (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
right: '8px',
|
||||
height: '10px',
|
||||
width: '10px',
|
||||
background: 'orange',
|
||||
borderRadius: '50%',
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default NotificationBadge;
|
||||
@@ -9,9 +9,10 @@ import {
|
||||
import { VFC } from 'react';
|
||||
|
||||
import { useDeckyState } from './DeckyState';
|
||||
import NotificationBadge from './NotificationBadge';
|
||||
|
||||
const PluginView: VFC = () => {
|
||||
const { plugins, activePlugin, setActivePlugin } = useDeckyState();
|
||||
const { plugins, updates, activePlugin, setActivePlugin } = useDeckyState();
|
||||
|
||||
if (activePlugin) {
|
||||
return (
|
||||
@@ -23,7 +24,6 @@ const PluginView: VFC = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={joinClassNames(staticClasses.TabGroupPanel, scrollClasses.ScrollPanel, scrollClasses.ScrollY)}>
|
||||
<PanelSection>
|
||||
@@ -35,6 +35,7 @@ const PluginView: VFC = () => {
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div>{icon}</div>
|
||||
<div>{name}</div>
|
||||
<NotificationBadge show={updates?.has(name)} style={{ top: '-5px', right: '-5px' }} />
|
||||
</div>
|
||||
</ButtonItem>
|
||||
</PanelSectionRow>
|
||||
|
||||
@@ -8,7 +8,6 @@ const titleStyles: CSSProperties = {
|
||||
display: 'flex',
|
||||
paddingTop: '3px',
|
||||
paddingRight: '16px',
|
||||
boxShadow: 'unset',
|
||||
};
|
||||
|
||||
const TitleView: VFC = () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ToastData, findModule, joinClassNames } from 'decky-frontend-lib';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { ToastData, findModule, findModuleChild, joinClassNames } from 'decky-frontend-lib';
|
||||
import { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
interface ToastProps {
|
||||
toast: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ModalRoot, QuickAccessTab, Router, Spinner, sleep, staticClasses } from 'decky-frontend-lib';
|
||||
import { ModalRoot, QuickAccessTab, Router, Spinner, staticClasses } from 'decky-frontend-lib';
|
||||
import { FC, useState } from 'react';
|
||||
|
||||
interface PluginInstallModalProps {
|
||||
@@ -20,9 +20,7 @@ const PluginInstallModal: FC<PluginInstallModalProps> = ({ artifact, version, ha
|
||||
onOK={async () => {
|
||||
setLoading(true);
|
||||
await onOK();
|
||||
Router.NavigateBackOrOpenMenu();
|
||||
await sleep(250);
|
||||
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 1000);
|
||||
setTimeout(() => Router.OpenQuickAccessMenu(QuickAccessTab.Decky), 250);
|
||||
}}
|
||||
onCancel={async () => {
|
||||
await onCancel();
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Focusable, updaterFieldClasses } from 'decky-frontend-lib';
|
||||
import { FunctionComponent, ReactNode } from 'react';
|
||||
|
||||
interface InlinePatchNotesProps {
|
||||
date: ReactNode;
|
||||
title: string;
|
||||
children: ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const InlinePatchNotes: FunctionComponent<InlinePatchNotesProps> = ({ date, title, children, onClick }) => {
|
||||
return (
|
||||
<Focusable className={updaterFieldClasses.PatchNotes} onActivate={onClick}>
|
||||
<div className={updaterFieldClasses.PostedTime}>{date}</div>
|
||||
<div className={updaterFieldClasses.EventDetailTitle}>{title}</div>
|
||||
<div className={updaterFieldClasses.EventDetailsBody}>{children}</div>
|
||||
</Focusable>
|
||||
);
|
||||
};
|
||||
|
||||
export default InlinePatchNotes;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Dropdown, Field } from 'decky-frontend-lib';
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
import { callUpdaterMethod } from '../../../../updater';
|
||||
import { useSetting } from '../../../../utils/hooks/useSetting';
|
||||
|
||||
enum UpdateBranch {
|
||||
Stable,
|
||||
Prerelease,
|
||||
// Nightly,
|
||||
}
|
||||
|
||||
const BranchSelect: FunctionComponent<{}> = () => {
|
||||
const [selectedBranch, setSelectedBranch] = useSetting<UpdateBranch>('branch', UpdateBranch.Prerelease);
|
||||
|
||||
return (
|
||||
// Returns numerical values from 0 to 2 (with current branch setup as of 8/28/22)
|
||||
// 0 being stable, 1 being pre-release and 2 being nightly
|
||||
<Field label="Update Channel">
|
||||
<Dropdown
|
||||
rgOptions={Object.values(UpdateBranch)
|
||||
.filter((branch) => typeof branch == 'string')
|
||||
.map((branch) => ({
|
||||
label: branch,
|
||||
data: UpdateBranch[branch],
|
||||
}))}
|
||||
selectedOption={selectedBranch}
|
||||
onChange={async (newVal) => {
|
||||
await setSelectedBranch(newVal.data);
|
||||
callUpdaterMethod('check_for_updates');
|
||||
console.log('switching branches!');
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
};
|
||||
|
||||
export default BranchSelect;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Field, Toggle } from 'decky-frontend-lib';
|
||||
import { FaBug } from 'react-icons/fa';
|
||||
|
||||
import { useSetting } from '../../../../utils/hooks/useSetting';
|
||||
|
||||
export default function RemoteDebuggingSettings() {
|
||||
const [allowRemoteDebugging, setAllowRemoteDebugging] = useSetting<boolean>('cef_forward', false);
|
||||
|
||||
return (
|
||||
<Field
|
||||
label="Allow Remote CEF Debugging"
|
||||
description={
|
||||
<span style={{ whiteSpace: 'pre-line' }}>
|
||||
Allow unauthenticated access to the CEF debugger to anyone in your network
|
||||
</span>
|
||||
}
|
||||
icon={<FaBug style={{ display: 'block' }} />}
|
||||
>
|
||||
<Toggle
|
||||
value={allowRemoteDebugging || false}
|
||||
onChange={(toggleValue) => {
|
||||
setAllowRemoteDebugging(toggleValue);
|
||||
if (toggleValue) window.DeckyPluginLoader.callServerMethod('allow_remote_debugging');
|
||||
else window.DeckyPluginLoader.callServerMethod('disallow_remote_debugging');
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
);
|
||||
}
|
||||
@@ -1,81 +1,154 @@
|
||||
import { DialogButton, Field, ProgressBarWithInfo, Spinner } from 'decky-frontend-lib';
|
||||
import { Carousel, DialogButton, Field, Focusable, ProgressBarWithInfo, Spinner, showModal } from 'decky-frontend-lib';
|
||||
import { useCallback } from 'react';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaArrowDown } from 'react-icons/fa';
|
||||
|
||||
import { VerInfo, callUpdaterMethod, finishUpdate } from '../../../../updater';
|
||||
import { useDeckyState } from '../../../DeckyState';
|
||||
import InlinePatchNotes from '../../../patchnotes/InlinePatchNotes';
|
||||
|
||||
export default function UpdaterSettings() {
|
||||
const [versionInfo, setVersionInfo] = useState<VerInfo | null>(null);
|
||||
const [updateProgress, setUpdateProgress] = useState<number>(-1);
|
||||
const [reloading, setReloading] = useState<boolean>(false);
|
||||
const [checkingForUpdates, setCheckingForUpdates] = useState<boolean>(false);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = (await callUpdaterMethod('get_version')) as { result: VerInfo };
|
||||
setVersionInfo(res.result);
|
||||
})();
|
||||
}, []);
|
||||
const MarkdownRenderer = lazy(() => import('../../../Markdown'));
|
||||
|
||||
// import ReactMarkdown from 'react-markdown'
|
||||
// import remarkGfm from 'remark-gfm'
|
||||
|
||||
function PatchNotesModal({ versionInfo, closeModal }: { versionInfo: VerInfo | null; closeModal?: () => {} }) {
|
||||
return (
|
||||
<Field
|
||||
label="Updates"
|
||||
description={
|
||||
versionInfo && (
|
||||
<span style={{ whiteSpace: 'pre-line' }}>{`Current version: ${versionInfo.current}\n${
|
||||
versionInfo.updatable ? `Latest version: ${versionInfo.remote?.tag_name}` : ''
|
||||
}`}</span>
|
||||
)
|
||||
}
|
||||
icon={
|
||||
!versionInfo ? (
|
||||
<Spinner style={{ width: '1em', height: 20, display: 'block' }} />
|
||||
) : (
|
||||
<FaArrowDown style={{ display: 'block' }} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{updateProgress == -1 ? (
|
||||
<DialogButton
|
||||
disabled={!versionInfo?.updatable || checkingForUpdates}
|
||||
onClick={
|
||||
!versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? async () => {
|
||||
setCheckingForUpdates(true);
|
||||
const res = (await callUpdaterMethod('check_for_updates')) as { result: VerInfo };
|
||||
setVersionInfo(res.result);
|
||||
setCheckingForUpdates(false);
|
||||
}
|
||||
: async () => {
|
||||
window.DeckyUpdater = {
|
||||
updateProgress: (i) => {
|
||||
setUpdateProgress(i);
|
||||
},
|
||||
finish: async () => {
|
||||
setUpdateProgress(0);
|
||||
setReloading(true);
|
||||
await finishUpdate();
|
||||
},
|
||||
};
|
||||
setUpdateProgress(0);
|
||||
callUpdaterMethod('do_update');
|
||||
}
|
||||
}
|
||||
>
|
||||
{checkingForUpdates
|
||||
? 'Checking'
|
||||
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? 'Check For Updates'
|
||||
: 'Install Update'}
|
||||
</DialogButton>
|
||||
) : (
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator={false}
|
||||
nProgress={updateProgress}
|
||||
indeterminate={reloading}
|
||||
sOperationText={reloading ? 'Reloading' : 'Updating'}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
<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}
|
||||
/>
|
||||
</Focusable>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UpdaterSettings() {
|
||||
const { isLoaderUpdating, setIsLoaderUpdating, versionInfo, setVersionInfo } = useDeckyState();
|
||||
|
||||
const [checkingForUpdates, setCheckingForUpdates] = useState<boolean>(false);
|
||||
const [updateProgress, setUpdateProgress] = useState<number>(-1);
|
||||
const [reloading, setReloading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
window.DeckyUpdater = {
|
||||
updateProgress: (i) => {
|
||||
setUpdateProgress(i);
|
||||
setIsLoaderUpdating(true);
|
||||
},
|
||||
finish: async () => {
|
||||
setUpdateProgress(0);
|
||||
setReloading(true);
|
||||
await finishUpdate();
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
const showPatchNotes = useCallback(() => {
|
||||
showModal(<PatchNotesModal versionInfo={versionInfo} />);
|
||||
}, [versionInfo]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
onOptionsActionDescription={versionInfo?.all ? 'Patch Notes' : undefined}
|
||||
onOptionsButton={versionInfo?.all ? showPatchNotes : undefined}
|
||||
label="Updates"
|
||||
description={
|
||||
versionInfo && (
|
||||
<span style={{ whiteSpace: 'pre-line' }}>{`Current version: ${versionInfo.current}\n${
|
||||
versionInfo.updatable ? `Latest version: ${versionInfo.remote?.tag_name}` : ''
|
||||
}`}</span>
|
||||
)
|
||||
}
|
||||
icon={
|
||||
!versionInfo ? (
|
||||
<Spinner style={{ width: '1em', height: 20, display: 'block' }} />
|
||||
) : (
|
||||
<FaArrowDown style={{ display: 'block' }} />
|
||||
)
|
||||
}
|
||||
>
|
||||
{updateProgress == -1 && !isLoaderUpdating ? (
|
||||
<DialogButton
|
||||
disabled={!versionInfo?.updatable || checkingForUpdates}
|
||||
onClick={
|
||||
!versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? async () => {
|
||||
setCheckingForUpdates(true);
|
||||
const res = (await callUpdaterMethod('check_for_updates')) as { result: VerInfo };
|
||||
setVersionInfo(res.result);
|
||||
setCheckingForUpdates(false);
|
||||
}
|
||||
: async () => {
|
||||
setUpdateProgress(0);
|
||||
callUpdaterMethod('do_update');
|
||||
}
|
||||
}
|
||||
>
|
||||
{checkingForUpdates
|
||||
? 'Checking'
|
||||
: !versionInfo?.remote || versionInfo?.remote?.tag_name == versionInfo?.current
|
||||
? 'Check For Updates'
|
||||
: 'Install Update'}
|
||||
</DialogButton>
|
||||
) : (
|
||||
<ProgressBarWithInfo
|
||||
layout="inline"
|
||||
bottomSeparator={false}
|
||||
nProgress={updateProgress}
|
||||
indeterminate={reloading}
|
||||
sOperationText={reloading ? 'Reloading' : 'Updating'}
|
||||
/>
|
||||
)}
|
||||
</Field>
|
||||
{versionInfo?.remote && (
|
||||
<InlinePatchNotes
|
||||
title={versionInfo?.remote.name}
|
||||
date={new Intl.RelativeTimeFormat('en-US', {
|
||||
numeric: 'auto',
|
||||
}).format(
|
||||
Math.ceil((new Date(versionInfo.remote.published_at).getTime() - new Date().getTime()) / 86400000),
|
||||
'day',
|
||||
)}
|
||||
onClick={showPatchNotes}
|
||||
>
|
||||
<Suspense fallback={<Spinner style={{ width: '24', height: '24' }} />}>
|
||||
<MarkdownRenderer>{versionInfo?.remote.body}</MarkdownRenderer>
|
||||
</Suspense>
|
||||
</InlinePatchNotes>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ import { DialogButton, Field, TextField } from 'decky-frontend-lib';
|
||||
import { useState } from 'react';
|
||||
import { FaShapes } from 'react-icons/fa';
|
||||
|
||||
import { installFromURL } from '../../../store/Store';
|
||||
import { installFromURL } from '../../../../store';
|
||||
import BranchSelect from './BranchSelect';
|
||||
import RemoteDebuggingSettings from './RemoteDebugging';
|
||||
import UpdaterSettings from './Updater';
|
||||
|
||||
export default function GeneralSettings() {
|
||||
@@ -20,6 +22,8 @@ export default function GeneralSettings() {
|
||||
/>
|
||||
</Field> */}
|
||||
<UpdaterSettings />
|
||||
<BranchSelect />
|
||||
<RemoteDebuggingSettings />
|
||||
<Field
|
||||
label="Manual plugin install"
|
||||
description={<TextField label={'URL'} value={pluginURL} onChange={(e) => setPluginURL(e?.target.value)} />}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { DialogButton, Menu, MenuItem, showContextMenu, staticClasses } from 'decky-frontend-lib';
|
||||
import { FaEllipsisH } from 'react-icons/fa';
|
||||
import { DialogButton, Focusable, Menu, MenuItem, showContextMenu } from 'decky-frontend-lib';
|
||||
import { useEffect } from 'react';
|
||||
import { FaDownload, FaEllipsisH } from 'react-icons/fa';
|
||||
|
||||
import { requestPluginInstall } from '../../../../store';
|
||||
import { useDeckyState } from '../../../DeckyState';
|
||||
|
||||
export default function PluginList() {
|
||||
const { plugins } = useDeckyState();
|
||||
const { plugins, updates } = useDeckyState();
|
||||
|
||||
useEffect(() => {
|
||||
window.DeckyPluginLoader.checkPluginUpdates();
|
||||
}, []);
|
||||
|
||||
if (plugins.length === 0) {
|
||||
return (
|
||||
@@ -16,27 +22,45 @@ export default function PluginList() {
|
||||
|
||||
return (
|
||||
<ul style={{ listStyleType: 'none' }}>
|
||||
{plugins.map(({ name }) => (
|
||||
<li style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<span>{name}</span>
|
||||
<div className={staticClasses.Title} style={{ marginLeft: 'auto', boxShadow: 'none' }}>
|
||||
<DialogButton
|
||||
style={{ height: '40px', width: '40px', padding: '10px 12px' }}
|
||||
onClick={(e: MouseEvent) =>
|
||||
showContextMenu(
|
||||
<Menu label="Plugin Actions">
|
||||
<MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(name)}>Reload</MenuItem>
|
||||
<MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(name)}>Uninstall</MenuItem>
|
||||
</Menu>,
|
||||
e.currentTarget ?? window,
|
||||
)
|
||||
}
|
||||
>
|
||||
<FaEllipsisH />
|
||||
</DialogButton>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
{plugins.map(({ name, version }) => {
|
||||
const update = updates?.get(name);
|
||||
return (
|
||||
<li style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', paddingBottom: '10px' }}>
|
||||
<span>
|
||||
{name} {version}
|
||||
</span>
|
||||
<Focusable style={{ marginLeft: 'auto', boxShadow: 'none', display: 'flex', justifyContent: 'right' }}>
|
||||
{update && (
|
||||
<DialogButton
|
||||
style={{ height: '40px', minWidth: '60px', marginRight: '10px' }}
|
||||
onClick={() => requestPluginInstall(name, update)}
|
||||
>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
Update to {update.name}
|
||||
<FaDownload style={{ paddingLeft: '2rem' }} />
|
||||
</div>
|
||||
</DialogButton>
|
||||
)}
|
||||
<DialogButton
|
||||
style={{ height: '40px', width: '40px', padding: '10px 12px', minWidth: '40px' }}
|
||||
onClick={(e: MouseEvent) =>
|
||||
showContextMenu(
|
||||
<Menu label="Plugin Actions">
|
||||
<MenuItem onSelected={() => window.DeckyPluginLoader.importPlugin(name, version)}>
|
||||
Reload
|
||||
</MenuItem>
|
||||
<MenuItem onSelected={() => window.DeckyPluginLoader.uninstallPlugin(name)}>Uninstall</MenuItem>
|
||||
</Menu>,
|
||||
e.currentTarget ?? window,
|
||||
)
|
||||
}
|
||||
>
|
||||
<FaEllipsisH />
|
||||
</DialogButton>
|
||||
</Focusable>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,18 +15,15 @@ import {
|
||||
LegacyStorePlugin,
|
||||
StorePlugin,
|
||||
StorePluginVersion,
|
||||
isLegacyPlugin,
|
||||
requestLegacyPluginInstall,
|
||||
requestPluginInstall,
|
||||
} from './Store';
|
||||
} from '../../store';
|
||||
|
||||
interface PluginCardProps {
|
||||
plugin: StorePlugin | LegacyStorePlugin;
|
||||
}
|
||||
|
||||
function isLegacyPlugin(plugin: LegacyStorePlugin | StorePlugin): plugin is LegacyStorePlugin {
|
||||
return 'artifact' in plugin;
|
||||
}
|
||||
|
||||
const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
const [selectedOption, setSelectedOption] = useState<number>(0);
|
||||
const buttonRef = useRef<HTMLDivElement>(null);
|
||||
@@ -119,14 +116,21 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
<p className={joinClassNames(staticClasses.PanelSectionRow)}>
|
||||
<span>Author: {plugin.author}</span>
|
||||
</p>
|
||||
<p className={joinClassNames('deckyStoreCardTagsContainer', staticClasses.PanelSectionRow)}>
|
||||
<span>Tags:</span>
|
||||
<p
|
||||
className={joinClassNames('deckyStoreCardTagsContainer', staticClasses.PanelSectionRow)}
|
||||
style={{
|
||||
padding: '0 16px',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: '5px 10px',
|
||||
}}
|
||||
>
|
||||
<span style={{ padding: '5px 0' }}>Tags:</span>
|
||||
{plugin.tags.map((tag: string) => (
|
||||
<span
|
||||
className="deckyStoreCardTag"
|
||||
style={{
|
||||
padding: '5px',
|
||||
marginRight: '10px',
|
||||
borderRadius: '5px',
|
||||
background: tag == 'root' ? '#842029' : '#ACB2C947',
|
||||
}}
|
||||
@@ -140,7 +144,6 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
style={{
|
||||
color: '#232120',
|
||||
padding: '5px',
|
||||
marginRight: '10px',
|
||||
borderRadius: '5px',
|
||||
background: '#EDE841',
|
||||
}}
|
||||
@@ -180,7 +183,7 @@ const PluginCard: FC<PluginCardProps> = ({ plugin }) => {
|
||||
onClick={() =>
|
||||
isLegacyPlugin(plugin)
|
||||
? requestLegacyPluginInstall(plugin, Object.keys(plugin.versions)[selectedOption])
|
||||
: requestPluginInstall(plugin, plugin.versions[selectedOption])
|
||||
: requestPluginInstall(plugin.name, plugin.versions[selectedOption])
|
||||
}
|
||||
>
|
||||
Install
|
||||
|
||||
@@ -1,111 +1,21 @@
|
||||
import { ModalRoot, SteamSpinner, showModal, staticClasses } from 'decky-frontend-lib';
|
||||
import { SteamSpinner } from 'decky-frontend-lib';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
|
||||
import { LegacyStorePlugin, StorePlugin, getLegacyPluginList, getPluginList } from '../../store';
|
||||
import PluginCard from './PluginCard';
|
||||
|
||||
export interface StorePluginVersion {
|
||||
name: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export interface StorePlugin {
|
||||
id: number;
|
||||
name: string;
|
||||
versions: StorePluginVersion[];
|
||||
author: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface LegacyStorePlugin {
|
||||
artifact: string;
|
||||
versions: {
|
||||
[version: string]: string;
|
||||
};
|
||||
author: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export async function installFromURL(url: string) {
|
||||
const formData = new FormData();
|
||||
const splitURL = url.split('/');
|
||||
formData.append('name', splitURL[splitURL.length - 1].replace('.zip', ''));
|
||||
formData.append('artifact', url);
|
||||
await fetch('http://localhost:1337/browser/install_plugin', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Authentication: window.deckyAuthToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVer: string) {
|
||||
showModal(
|
||||
<ModalRoot
|
||||
onOK={() => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', plugin.artifact);
|
||||
formData.append('artifact', `https://github.com/${plugin.artifact}/archive/refs/tags/${selectedVer}.zip`);
|
||||
formData.append('version', selectedVer);
|
||||
formData.append('hash', plugin.versions[selectedVer]);
|
||||
fetch('http://localhost:1337/browser/install_plugin', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Authentication: window.deckyAuthToken,
|
||||
},
|
||||
});
|
||||
}}
|
||||
onCancel={() => {
|
||||
// do nothing
|
||||
}}
|
||||
>
|
||||
<div className={staticClasses.Title} style={{ flexDirection: 'column', boxShadow: 'unset' }}>
|
||||
Using legacy plugins
|
||||
</div>
|
||||
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>,
|
||||
);
|
||||
}
|
||||
|
||||
export async function requestPluginInstall(plugin: StorePlugin, selectedVer: StorePluginVersion) {
|
||||
const formData = new FormData();
|
||||
formData.append('name', plugin.name);
|
||||
formData.append('artifact', `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`);
|
||||
formData.append('version', selectedVer.name);
|
||||
formData.append('hash', selectedVer.hash);
|
||||
await fetch('http://localhost:1337/browser/install_plugin', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Authentication: window.deckyAuthToken,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const StorePage: FC<{}> = () => {
|
||||
const [data, setData] = useState<StorePlugin[] | null>(null);
|
||||
const [legacyData, setLegacyData] = useState<LegacyStorePlugin[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = await fetch('https://beta.deckbrew.xyz/plugins', {
|
||||
method: 'GET',
|
||||
}).then((r) => r.json());
|
||||
const res = await getPluginList();
|
||||
console.log(res);
|
||||
setData(res.filter((x: StorePlugin) => x.name !== 'Example Plugin'));
|
||||
setData(res);
|
||||
})();
|
||||
(async () => {
|
||||
const res = await fetch('https://plugins.deckbrew.xyz/get_plugins', {
|
||||
method: 'GET',
|
||||
}).then((r) => r.json());
|
||||
const res = await getLegacyPluginList();
|
||||
console.log(res);
|
||||
setLegacyData(res);
|
||||
})();
|
||||
|
||||
@@ -38,15 +38,14 @@ if (!window.webpackJsonp || window.webpackJsonp.deckyShimmed) {
|
||||
}
|
||||
|
||||
(async () => {
|
||||
window.deckyHasLoaded = true;
|
||||
window.deckyAuthToken = await fetch('http://127.0.0.1:1337/auth/token').then((r) => r.text());
|
||||
|
||||
window.DeckyPluginLoader?.dismountAll();
|
||||
window.DeckyPluginLoader?.deinit();
|
||||
|
||||
window.DeckyPluginLoader = new PluginLoader();
|
||||
window.importDeckyPlugin = function (name: string) {
|
||||
window.DeckyPluginLoader?.importPlugin(name);
|
||||
window.importDeckyPlugin = function (name: string, version: string) {
|
||||
window.DeckyPluginLoader?.importPlugin(name, version);
|
||||
};
|
||||
|
||||
window.syncDeckyPlugins = async function () {
|
||||
@@ -57,8 +56,10 @@ if (!window.webpackJsonp || window.webpackJsonp.deckyShimmed) {
|
||||
})
|
||||
).json();
|
||||
for (const plugin of plugins) {
|
||||
if (!window.DeckyPluginLoader.hasPlugin(plugin)) window.DeckyPluginLoader?.importPlugin(plugin);
|
||||
if (!window.DeckyPluginLoader.hasPlugin(plugin.name))
|
||||
window.DeckyPluginLoader?.importPlugin(plugin.name, plugin.version);
|
||||
}
|
||||
window.DeckyPluginLoader.checkPluginUpdates();
|
||||
};
|
||||
|
||||
setTimeout(() => window.syncDeckyPlugins(), 5000);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { ModalRoot, QuickAccessTab, showModal, staticClasses } from 'decky-frontend-lib';
|
||||
import { ModalRoot, QuickAccessTab, Router, SteamSpinner, showModal, sleep, staticClasses } from 'decky-frontend-lib';
|
||||
import { Suspense, lazy } from 'react';
|
||||
import { FaPlug } from 'react-icons/fa';
|
||||
|
||||
import { DeckyState, DeckyStateContextProvider } from './components/DeckyState';
|
||||
import { DeckyState, DeckyStateContextProvider, useDeckyState } from './components/DeckyState';
|
||||
import LegacyPlugin from './components/LegacyPlugin';
|
||||
import PluginInstallModal from './components/modals/PluginInstallModal';
|
||||
import NotificationBadge from './components/NotificationBadge';
|
||||
import PluginView from './components/PluginView';
|
||||
import SettingsPage from './components/settings';
|
||||
import StorePage from './components/store/Store';
|
||||
import TitleView from './components/TitleView';
|
||||
import Logger from './logger';
|
||||
import { Plugin } from './plugin';
|
||||
import RouterHook from './router-hook';
|
||||
import { checkForUpdates } from './store';
|
||||
import TabsHook from './tabs-hook';
|
||||
import Toaster from './toaster';
|
||||
import { VerInfo, callUpdaterMethod } from './updater';
|
||||
@@ -29,12 +30,17 @@ class PluginLoader extends Logger {
|
||||
|
||||
private reloadLock: boolean = false;
|
||||
// stores a list of plugin names which requested to be reloaded
|
||||
private pluginReloadQueue: string[] = [];
|
||||
private pluginReloadQueue: { name: string; version?: string }[] = [];
|
||||
|
||||
constructor() {
|
||||
super(PluginLoader.name);
|
||||
this.log('Initialized');
|
||||
|
||||
const TabIcon = () => {
|
||||
const { updates, hasLoaderUpdate } = useDeckyState();
|
||||
return <NotificationBadge show={(updates && updates.size > 0) || hasLoaderUpdate} />;
|
||||
};
|
||||
|
||||
this.tabsHook.add({
|
||||
id: QuickAccessTab.Decky,
|
||||
title: null,
|
||||
@@ -44,14 +50,52 @@ class PluginLoader extends Logger {
|
||||
<PluginView />
|
||||
</DeckyStateContextProvider>
|
||||
),
|
||||
icon: <FaPlug />,
|
||||
icon: (
|
||||
<DeckyStateContextProvider deckyState={this.deckyState}>
|
||||
<FaPlug />
|
||||
<TabIcon />
|
||||
</DeckyStateContextProvider>
|
||||
),
|
||||
});
|
||||
|
||||
this.routerHook.addRoute('/decky/store', () => <StorePage />);
|
||||
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>
|
||||
}
|
||||
>
|
||||
<StorePage />
|
||||
</Suspense>
|
||||
));
|
||||
this.routerHook.addRoute('/decky/settings', () => {
|
||||
return (
|
||||
<DeckyStateContextProvider deckyState={this.deckyState}>
|
||||
<SettingsPage />
|
||||
<Suspense
|
||||
fallback={
|
||||
<div
|
||||
style={{
|
||||
marginTop: '40px',
|
||||
height: 'calc( 100% - 40px )',
|
||||
overflowY: 'scroll',
|
||||
}}
|
||||
>
|
||||
<SteamSpinner />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<SettingsPage />
|
||||
</Suspense>
|
||||
</DeckyStateContextProvider>
|
||||
);
|
||||
});
|
||||
@@ -59,10 +103,32 @@ class PluginLoader extends Logger {
|
||||
|
||||
public async notifyUpdates() {
|
||||
const versionInfo = (await callUpdaterMethod('get_version')).result as VerInfo;
|
||||
this.deckyState.setVersionInfo(versionInfo);
|
||||
if (versionInfo?.remote && versionInfo?.remote?.tag_name != versionInfo?.current) {
|
||||
this.toaster.toast({
|
||||
title: 'Decky',
|
||||
body: `Update to ${versionInfo?.remote?.tag_name} availiable!`,
|
||||
body: `Update to ${versionInfo?.remote?.tag_name} available!`,
|
||||
onClick: () => Router.Navigate('/decky/settings'),
|
||||
});
|
||||
this.deckyState.setHasLoaderUpdate(true);
|
||||
}
|
||||
await sleep(7000);
|
||||
await this.notifyPluginUpdates();
|
||||
}
|
||||
|
||||
public async checkPluginUpdates() {
|
||||
const updates = await checkForUpdates(this.plugins);
|
||||
this.deckyState.setUpdates(updates);
|
||||
return updates;
|
||||
}
|
||||
|
||||
public async notifyPluginUpdates() {
|
||||
const updates = await this.checkPluginUpdates();
|
||||
if (updates?.size > 0) {
|
||||
this.toaster.toast({
|
||||
title: 'Decky',
|
||||
body: `Updates available for ${updates.size} plugin${updates.size > 1 ? 's' : ''}!`,
|
||||
onClick: () => Router.Navigate('/decky/settings/plugins'),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -83,16 +149,7 @@ class PluginLoader extends Logger {
|
||||
showModal(
|
||||
<ModalRoot
|
||||
onOK={async () => {
|
||||
const formData = new FormData();
|
||||
formData.append('name', name);
|
||||
await fetch('http://localhost:1337/browser/uninstall_plugin', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
Authentication: window.deckyAuthToken,
|
||||
},
|
||||
});
|
||||
await this.callServerMethod('uninstall_plugin', { name });
|
||||
}}
|
||||
onCancel={() => {
|
||||
// do nothing
|
||||
@@ -128,10 +185,10 @@ class PluginLoader extends Logger {
|
||||
this.deckyState.setPlugins(this.plugins);
|
||||
}
|
||||
|
||||
public async importPlugin(name: string) {
|
||||
public async importPlugin(name: string, version?: string | undefined) {
|
||||
if (this.reloadLock) {
|
||||
this.log('Reload currently in progress, adding to queue', name);
|
||||
this.pluginReloadQueue.push(name);
|
||||
this.pluginReloadQueue.push({ name, version: version });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -144,7 +201,7 @@ class PluginLoader extends Logger {
|
||||
if (name.startsWith('$LEGACY_')) {
|
||||
await this.importLegacyPlugin(name.replace('$LEGACY_', ''));
|
||||
} else {
|
||||
await this.importReactPlugin(name);
|
||||
await this.importReactPlugin(name, version);
|
||||
}
|
||||
|
||||
this.deckyState.setPlugins(this.plugins);
|
||||
@@ -155,12 +212,12 @@ class PluginLoader extends Logger {
|
||||
this.reloadLock = false;
|
||||
const nextPlugin = this.pluginReloadQueue.shift();
|
||||
if (nextPlugin) {
|
||||
this.importPlugin(nextPlugin);
|
||||
this.importPlugin(nextPlugin.name, nextPlugin.version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async importReactPlugin(name: string) {
|
||||
private async importReactPlugin(name: string, version?: string) {
|
||||
let res = await fetch(`http://127.0.0.1:1337/plugins/${name}/frontend_bundle`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
@@ -172,6 +229,7 @@ class PluginLoader extends Logger {
|
||||
this.plugins.push({
|
||||
...plugin,
|
||||
name: name,
|
||||
version: version,
|
||||
});
|
||||
} else throw new Error(`${name} frontend_bundle not OK`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface Plugin {
|
||||
name: string;
|
||||
version?: string;
|
||||
icon: JSX.Element;
|
||||
content?: JSX.Element;
|
||||
onDismount?(): void;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterPatch, findModuleChild, unpatch } from 'decky-frontend-lib';
|
||||
import { Patch, afterPatch, findModuleChild } from 'decky-frontend-lib';
|
||||
import { ReactElement, ReactNode, cloneElement, createElement, memo } from 'react';
|
||||
import type { Route } from 'react-router';
|
||||
|
||||
@@ -22,6 +22,8 @@ class RouterHook extends Logger {
|
||||
private memoizedRouter: any;
|
||||
private gamepadWrapper: any;
|
||||
private routerState: DeckyRouterState = new DeckyRouterState();
|
||||
private wrapperPatch: Patch;
|
||||
private routerPatch?: Patch;
|
||||
|
||||
constructor() {
|
||||
super('RouterHook');
|
||||
@@ -87,7 +89,7 @@ class RouterHook extends Logger {
|
||||
return children;
|
||||
};
|
||||
|
||||
afterPatch(this.gamepadWrapper, 'render', (_: any, ret: any) => {
|
||||
this.wrapperPatch = afterPatch(this.gamepadWrapper, 'render', (_: any, ret: any) => {
|
||||
if (ret?.props?.children?.props?.children?.length == 5) {
|
||||
if (
|
||||
ret.props.children.props.children[2]?.props?.children?.[0]?.type?.type
|
||||
@@ -96,7 +98,7 @@ class RouterHook extends Logger {
|
||||
) {
|
||||
if (!this.router) {
|
||||
this.router = ret.props.children.props.children[2]?.props?.children?.[0]?.type;
|
||||
afterPatch(this.router, 'type', (_: any, ret: any) => {
|
||||
this.routerPatch = afterPatch(this.router, 'type', (_: any, ret: any) => {
|
||||
if (!Route)
|
||||
Route = ret.props.children[0].props.children.find((x: any) => x.props.path == '/createaccount').type;
|
||||
const returnVal = (
|
||||
@@ -133,8 +135,8 @@ class RouterHook extends Logger {
|
||||
}
|
||||
|
||||
deinit() {
|
||||
unpatch(this.gamepadWrapper, 'render');
|
||||
this.router && unpatch(this.router, 'type');
|
||||
this.wrapperPatch.unpatch();
|
||||
this.routerPatch?.unpatch();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import { ModalRoot, showModal, staticClasses } from 'decky-frontend-lib';
|
||||
|
||||
import { Plugin } from './plugin';
|
||||
|
||||
export interface StorePluginVersion {
|
||||
name: string;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export interface StorePlugin {
|
||||
id: number;
|
||||
name: string;
|
||||
versions: StorePluginVersion[];
|
||||
author: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface LegacyStorePlugin {
|
||||
artifact: string;
|
||||
versions: {
|
||||
[version: string]: string;
|
||||
};
|
||||
author: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
// name: version
|
||||
export type PluginUpdateMapping = Map<string, StorePluginVersion>;
|
||||
|
||||
export function getPluginList(): Promise<StorePlugin[]> {
|
||||
return fetch('https://beta.deckbrew.xyz/plugins', {
|
||||
method: 'GET',
|
||||
}).then((r) => r.json());
|
||||
}
|
||||
|
||||
export function getLegacyPluginList(): Promise<LegacyStorePlugin[]> {
|
||||
return fetch('https://plugins.deckbrew.xyz/get_plugins', {
|
||||
method: 'GET',
|
||||
}).then((r) => r.json());
|
||||
}
|
||||
|
||||
export async function installFromURL(url: string) {
|
||||
const splitURL = url.split('/');
|
||||
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
||||
name: splitURL[splitURL.length - 1].replace('.zip', ''),
|
||||
artifact: url,
|
||||
});
|
||||
}
|
||||
|
||||
export function requestLegacyPluginInstall(plugin: LegacyStorePlugin, selectedVer: string) {
|
||||
showModal(
|
||||
<ModalRoot
|
||||
onOK={() => {
|
||||
window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
||||
name: plugin.artifact,
|
||||
artifact: `https://github.com/${plugin.artifact}/archive/refs/tags/${selectedVer}.zip`,
|
||||
version: selectedVer,
|
||||
hash: plugin.versions[selectedVer],
|
||||
});
|
||||
}}
|
||||
onCancel={() => {
|
||||
// do nothing
|
||||
}}
|
||||
>
|
||||
<div className={staticClasses.Title} style={{ flexDirection: 'column', boxShadow: 'unset' }}>
|
||||
Using legacy plugins
|
||||
</div>
|
||||
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>,
|
||||
);
|
||||
}
|
||||
|
||||
export async function requestPluginInstall(plugin: string, selectedVer: StorePluginVersion) {
|
||||
await window.DeckyPluginLoader.callServerMethod('install_plugin', {
|
||||
name: plugin,
|
||||
artifact: `https://cdn.tzatzikiweeb.moe/file/steam-deck-homebrew/versions/${selectedVer.hash}.zip`,
|
||||
version: selectedVer.name,
|
||||
hash: selectedVer.hash,
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkForUpdates(plugins: Plugin[]): Promise<PluginUpdateMapping> {
|
||||
const serverData = await getPluginList();
|
||||
const updateMap = new Map<string, StorePluginVersion>();
|
||||
for (let plugin of plugins) {
|
||||
const remotePlugin = serverData?.find((x) => x.name == plugin.name);
|
||||
if (remotePlugin && remotePlugin.versions?.length > 0 && plugin.version != remotePlugin?.versions?.[0]?.name) {
|
||||
updateMap.set(plugin.name, remotePlugin.versions[0]);
|
||||
}
|
||||
}
|
||||
return updateMap;
|
||||
}
|
||||
|
||||
export function isLegacyPlugin(plugin: LegacyStorePlugin | StorePlugin): plugin is LegacyStorePlugin {
|
||||
return 'artifact' in plugin;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { QuickAccessTab, afterPatch, sleep, unpatch } from 'decky-frontend-lib';
|
||||
import { Patch, QuickAccessTab, afterPatch, sleep } from 'decky-frontend-lib';
|
||||
import { memo } from 'react';
|
||||
|
||||
import Logger from './logger';
|
||||
@@ -35,6 +35,8 @@ class TabsHook extends Logger {
|
||||
private qAPTree: any;
|
||||
private rendererTree: any;
|
||||
|
||||
private cNodePatch?: Patch;
|
||||
|
||||
constructor() {
|
||||
super('TabsHook');
|
||||
|
||||
@@ -60,7 +62,7 @@ class TabsHook extends Logger {
|
||||
}
|
||||
let newQA: any;
|
||||
let newQATabRenderer: any;
|
||||
afterPatch(scrollRoot.stateNode, 'render', (_: any, ret: any) => {
|
||||
this.cNodePatch = afterPatch(scrollRoot.stateNode, 'render', (_: any, ret: any) => {
|
||||
if (!this.quickAccess && ret.props.children.props.children[4]) {
|
||||
this.quickAccess = ret?.props?.children?.props?.children[4].type;
|
||||
newQA = (...args: any) => {
|
||||
@@ -103,7 +105,7 @@ class TabsHook extends Logger {
|
||||
}
|
||||
|
||||
deinit() {
|
||||
if (this.cNode) unpatch(this.cNode.stateNode, 'render');
|
||||
this.cNodePatch?.unpatch();
|
||||
if (this.qAPTree) this.qAPTree.type = this.quickAccess;
|
||||
if (this.rendererTree) this.rendererTree.type = this.tabRenderer;
|
||||
if (this.cNode) this.cNode.stateNode.forceUpdate();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ToastData, afterPatch, findInReactTree, findModuleChild, sleep, unpatch } from 'decky-frontend-lib';
|
||||
import { Patch, ToastData, afterPatch, findInReactTree, findModuleChild, sleep } from 'decky-frontend-lib';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import Toast from './components/Toast';
|
||||
import Logger from './logger';
|
||||
@@ -11,7 +12,7 @@ declare global {
|
||||
}
|
||||
|
||||
class Toaster extends Logger {
|
||||
private instanceRet: any;
|
||||
private instanceRetPatch?: Patch;
|
||||
private node: any;
|
||||
private settingsModule: any;
|
||||
|
||||
@@ -43,14 +44,26 @@ class Toaster extends Logger {
|
||||
}
|
||||
|
||||
this.node = instance.return.return;
|
||||
let toast: any;
|
||||
let renderedToast: ReactNode = null;
|
||||
this.node.stateNode.render = (...args: any[]) => {
|
||||
const ret = this.node.stateNode.__proto__.render.call(this.node.stateNode, ...args);
|
||||
if (ret) {
|
||||
this.instanceRet = ret;
|
||||
afterPatch(ret, 'type', (_: any, ret: any) => {
|
||||
if (ret?.props?.children[1]?.children?.props?.notification?.decky) {
|
||||
const toast = ret.props.children[1].children.props.notification;
|
||||
ret.props.children[1].children.type = () => <Toast toast={toast} />;
|
||||
this.instanceRetPatch = afterPatch(ret, 'type', (_: any, ret: any) => {
|
||||
if (ret?.props?.children[1]?.children?.props) {
|
||||
const currentToast = ret.props.children[1].children.props.notification;
|
||||
if (currentToast?.decky) {
|
||||
if (currentToast == toast) {
|
||||
ret.props.children[1].children = renderedToast;
|
||||
} else {
|
||||
toast = currentToast;
|
||||
renderedToast = <Toast toast={toast} />;
|
||||
ret.props.children[1].children = renderedToast;
|
||||
}
|
||||
} else {
|
||||
toast = null;
|
||||
renderedToast = null;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
@@ -80,11 +93,10 @@ class Toaster extends Logger {
|
||||
return;
|
||||
window.NotificationStore.m_rgNotificationToasts.push(toastData);
|
||||
window.NotificationStore.DispatchNextToast();
|
||||
window.NotificationStore.m_rgNotificationToasts.pop();
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.instanceRet && unpatch(this.instanceRet, 'type');
|
||||
this.instanceRetPatch?.unpatch();
|
||||
this.node && delete this.node.stateNode.render;
|
||||
this.node && this.node.stateNode.forceUpdate();
|
||||
}
|
||||
|
||||
+14
-11
@@ -11,19 +11,22 @@ export interface DeckyUpdater {
|
||||
finish: () => void;
|
||||
}
|
||||
|
||||
export interface RemoteVerInfo {
|
||||
assets: {
|
||||
browser_download_url: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
name: string;
|
||||
body: string;
|
||||
prerelease: boolean;
|
||||
published_at: string;
|
||||
tag_name: string;
|
||||
}
|
||||
|
||||
export interface VerInfo {
|
||||
current: string;
|
||||
remote: {
|
||||
assets: {
|
||||
browser_download_url: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
name: string;
|
||||
body: string;
|
||||
prerelease: boolean;
|
||||
published_at: string;
|
||||
tag_name: string;
|
||||
} | null;
|
||||
remote: RemoteVerInfo | null;
|
||||
all: RemoteVerInfo[] | null;
|
||||
updatable: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface GetSettingArgs<T> {
|
||||
key: string;
|
||||
default: T;
|
||||
}
|
||||
|
||||
interface SetSettingArgs<T> {
|
||||
key: string;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export function useSetting<T>(key: string, def: T): [value: T | null, setValue: (value: T) => Promise<void>] {
|
||||
const [value, setValue] = useState(def);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = (await window.DeckyPluginLoader.callServerMethod('get_setting', {
|
||||
key,
|
||||
default: def,
|
||||
} as GetSettingArgs<T>)) as { result: T };
|
||||
setValue(res.result);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return [
|
||||
value,
|
||||
async (val: T) => {
|
||||
setValue(val);
|
||||
await window.DeckyPluginLoader.callServerMethod('set_setting', {
|
||||
key,
|
||||
value: val,
|
||||
} as SetSettingArgs<T>);
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"module": "ESNext",
|
||||
"target": "ES2020",
|
||||
"jsx": "react",
|
||||
|
||||
Reference in New Issue
Block a user