Add docker and WIP refactor front-end

This commit is contained in:
NGnius (Graham) 2022-12-24 10:22:26 -05:00
parent 8a7816f9f4
commit 1d17714274
16 changed files with 299 additions and 249 deletions

4
backend/Cargo.lock generated
View file

@ -1217,9 +1217,9 @@ dependencies = [
[[package]] [[package]]
name = "usdpl-back" name = "usdpl-back"
version = "0.7.2" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58928ed65332c30b9b9be5140fcdab97e45db679a5845d829aa26492765272e5" checksum = "e2ff8cc372a3b876bdbad212a398b06127bdb67603bce621d4148a50f1195372"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"async-trait", "async-trait",

View file

@ -8,7 +8,7 @@ license = "MIT"
repository = "https://github.com/NGnius/kaylon" repository = "https://github.com/NGnius/kaylon"
[dependencies] [dependencies]
usdpl-back = { version = "0.7.2"} usdpl-back = { version = "0.8.0"}
clap = { version = "3.2", features = ["derive", "std"], default-features = false } clap = { version = "3.2", features = ["derive", "std"], default-features = false }
@ -28,7 +28,7 @@ log = { version = "0.4" }
simplelog = { version = "0.12" } simplelog = { version = "0.12" }
[features] [features]
default = ["decky"] default = []
decky = ["usdpl-back/decky"] decky = ["usdpl-back/decky"]
encrypt = ["usdpl-back/encrypt"] encrypt = ["usdpl-back/encrypt"]

19
backend/build-docker-debug.sh Executable file
View file

@ -0,0 +1,19 @@
#!/bin/bash
echo " --- Rust version info ---"
rustup --version
rustc --version
cargo --version
echo " --- Building plugin backend ---"
cargo build --release --features decky
mkdir -p out
cp target/release/caylon out/backend
echo " --- Cleaning up backend ---"
# remove root-owned target folder
cargo clean
echo " --- Building plugin frontend WASM ---"
# TODO allow Dockerfile in root so that it can access src/usdpl_front and rebuild it
cd ../src/usdpl_front && ./rebuild.sh decky && cd ../../backend

View file

@ -8,7 +8,7 @@ rustc --version
cargo --version cargo --version
echo " --- Building plugin backend ---" echo " --- Building plugin backend ---"
cargo build --release --features encrypt cargo build --release --features decky,encrypt
mkdir -p out mkdir -p out
cp target/release/caylon out/backend cp target/release/caylon out/backend

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
#cargo build --release --target x86_64-unknown-linux-musl --features encrypt #cargo build --release --target x86_64-unknown-linux-musl --features encrypt
cargo build --target x86_64-unknown-linux-musl --features encrypt cargo build --target x86_64-unknown-linux-musl --features $1
#cross build --release --features encrypt #cross build --release --features encrypt
mkdir -p ../bin mkdir -p ../bin

8
backend/entrypoint-debug.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
set -e
echo "Container's IP address: `awk 'END{print $1}' /etc/hosts`"
cd /caylon/backend
sudo bash ./build-docker-debug.sh

7
backend/run_docker_debug.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/bash
# run docker container locally (for testing)
# assumes you're running in the backend/ dir of the project
docker run -i --entrypoint /caylon/backend/entrypoint-debug.sh -v $PWD/../:/caylon caylon_backend
mkdir -p ../bin
cp ./out/backend ../bin

9
build-debug.sh Executable file
View file

@ -0,0 +1,9 @@
#!/bin/bash
cd ./backend && ./build.sh decky && cd ..
cd ./src/usdpl_front && ./rebuild.sh decky && cd ../..
npm install && npm run build
unset USDPL_ENCRYPTION_KEY

View file

@ -3,7 +3,7 @@
export USDPL_ENCRYPTION_KEY=$(openssl enc -aes-256-cbc -k caylon -pbkdf2 -P -md sha1 | awk -F= '{if ($1 == "key") print $2}') export USDPL_ENCRYPTION_KEY=$(openssl enc -aes-256-cbc -k caylon -pbkdf2 -P -md sha1 | awk -F= '{if ($1 == "key") print $2}')
echo USDPL key: $USDPL_ENCRYPTION_KEY echo USDPL key: $USDPL_ENCRYPTION_KEY
cd ./backend && ./build.sh && cd .. cd ./backend && ./build.sh decky,encrypt && cd ..
cd ./src/usdpl_front && ./rebuild.sh decky encrypt && cd ../.. cd ./src/usdpl_front && ./rebuild.sh decky encrypt && cd ../..

View file

@ -11,6 +11,6 @@ class Plugin:
# Asyncio-compatible long-running code, executed in a task when the plugin is loaded # Asyncio-compatible long-running code, executed in a task when the plugin is loaded
async def _main(self): async def _main(self):
# startup # startup
self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend", "--config", ""]) self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend", "--config", "./caylon.json"])
while True: while True:
await asyncio.sleep(1) await asyncio.sleep(1)

92
src/components/about.tsx Normal file
View file

@ -0,0 +1,92 @@
import {Component} from "react";
import {
Field,
PanelSectionRow,
staticClasses,
} from "decky-frontend-lib";
import * as backend from "../backend";
export class About extends Component<{about: backend.CAbout | null}> {
render() {
return buildAbout(this.props.about);
}
}
function buildAbout(about: backend.CAbout | null) {
if (about == null) {
return [];
} else {
let elements = [
<div className={staticClasses.PanelSectionTitle}>
About
</div>,
<PanelSectionRow>
<Field label="Name">
{about.name}
</Field>
</PanelSectionRow>,
<PanelSectionRow>
<Field label="Version">
{about.version}
</Field>
</PanelSectionRow>,
<PanelSectionRow>
<Field label="Description">
{about.description}
</Field>
</PanelSectionRow>
];
if (about.url != null) {
elements.push(
<PanelSectionRow>
<Field label="URL">
{about.url}
</Field>
</PanelSectionRow>
);
}
if (about.authors.length > 1) {
let authors = about.authors.map((elem, i) => {
if (i == about!.authors.length - 1) {
return <p>{elem}</p>;
} else {
return <span>{elem}</span>;
}
});
elements.push(
<PanelSectionRow>
<Field label="Authors">
{authors}
</Field>
</PanelSectionRow>
);
} else if (about.authors.length == 1) {
elements.push(
<PanelSectionRow>
<Field label="Author">
{about.authors[0]}
</Field>
</PanelSectionRow>
);
} else {
elements.push(
<PanelSectionRow>
<Field label="Author">
NGnius
</Field>
</PanelSectionRow>
);
}
if (about.license != null) {
elements.push(
<PanelSectionRow>
<Field label="License">
{about.license}
</Field>
</PanelSectionRow>
);
}
return elements;
}
}

125
src/components/elements.tsx Normal file
View file

@ -0,0 +1,125 @@
import { Component, useState } from "react";
import {
ButtonItem,
PanelSectionRow,
SliderField,
ToggleField,
Field,
} from "decky-frontend-lib";
import { get_value, set_value } from "usdpl-front";
import {DISPLAY_KEY, VALUE_KEY} from "../consts";
import * as backend from "../backend";
export class Elements extends Component<{items: backend.CElement[]}> {
render() {
const [triggerInternal, updateInternal] = useState<boolean>(false);
const update = () => {
updateInternal(!triggerInternal);
}
function updateIdc(_: any) {
update();
}
return this.props.items.map(
(elem, i) => {
return (<PanelSectionRow>{buildHtmlElement(elem, i, updateIdc)}</PanelSectionRow>);
}
);
}
}
function buildHtmlElement(element: backend.CElement, index: number, updateIdc: any) {
switch (element.element) {
case "button":
return buildButton(element as backend.CButton, index, updateIdc);
case "slider":
return buildSlider(element as backend.CSlider, index, updateIdc);
case "toggle":
return buildToggle(element as backend.CToggle, index, updateIdc);
case "reading":
return buildReading(element as backend.CReading, index, updateIdc);
case "result-display":
return buildResultDisplay(element as backend.CResultDisplay, index, updateIdc);
case "event-display":
return buildEventDisplay(element as backend.CEventDisplay, index, updateIdc);
}
console.error("CAYLON: Unsupported element", element);
backend.log(backend.CLogLevel.ERROR, "Unsupported element " + element.element);
return (<div>Unsupported</div>);
}
function buildButton(element: backend.CButton, index: number, updateIdc: any) {
return (
<ButtonItem
layout="below"
onClick={() => {backend.resolve(backend.onUpdate(index, null), updateIdc)}}>
{element.title}
</ButtonItem>
);
}
function buildSlider(element: backend.CSlider, index: number, updateIdc: any) {
const KEY = VALUE_KEY + index.toString();
if (get_value(KEY) == null) {
set_value(KEY, element.min);
}
return (
<SliderField
label={element.title}
value={get_value(KEY)}
max={element.max}
min={element.min}
showValue={true}
onChange={(value: number) => {
backend.resolve(backend.onUpdate(index, value), updateIdc);
set_value(KEY, value);
}}
/>
);
}
function buildToggle(element: backend.CToggle, index: number, updateIdc: any) {
const KEY = VALUE_KEY + index.toString();
if (get_value(KEY) == null) {
set_value(KEY, false);
}
return (
<ToggleField
checked={get_value(KEY)}
label={element.title}
description={element.description!}
onChange={(value: boolean) => {
backend.resolve(backend.onUpdate(index, value), updateIdc);
set_value(KEY, value);
}}
/>
);
}
function buildReading(element: backend.CReading, index: number, _updateIdc: any) {
return (
<Field label={element.title}>
{get_value(DISPLAY_KEY + index.toString())}
</Field>
);
}
function buildResultDisplay(element: backend.CResultDisplay, index: number, _updateIdc: any) {
return (
<Field label={element.title}>
{get_value(DISPLAY_KEY + index.toString())}
</Field>
);
}
function buildEventDisplay(element: backend.CEventDisplay, index: number, _updateIdc: any) {
return (
<Field label={element.title}>
{get_value(DISPLAY_KEY + index.toString())}
</Field>
);
}

9
src/consts.ts Normal file
View file

@ -0,0 +1,9 @@
import {
gamepadDialogClasses,
joinClassNames,
} from "decky-frontend-lib";
export const DISPLAY_KEY = "display";
export const VALUE_KEY = "value";
export const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard);

View file

@ -10,23 +10,16 @@ import {
ServerAPI, ServerAPI,
//showContextMenu, //showContextMenu,
staticClasses, staticClasses,
SliderField,
ToggleField,
//NotchLabel
gamepadDialogClasses,
joinClassNames,
} from "decky-frontend-lib"; } from "decky-frontend-lib";
import { VFC, useState } from "react"; import { VFC, useState } from "react";
import { GiWashingMachine } from "react-icons/gi"; import { GiWashingMachine } from "react-icons/gi";
import { get_value, set_value } from "usdpl-front"; import { set_value } from "usdpl-front";
import * as backend from "./backend"; import * as backend from "./backend";
import {register_for_steam_events, unregister_for_steam_events} from "./steam_events"; import {register_for_steam_events, unregister_for_steam_events} from "./steam_events";
import {DISPLAY_KEY} from "./consts";
const FieldWithSeparator = joinClassNames(gamepadDialogClasses.Field, gamepadDialogClasses.WithBottomSeparatorStandard); import {Elements} from "./components/elements";
import {About} from "./components/about";
const DISPLAY_KEY = "display";
const VALUE_KEY = "value";
let items: backend.CElement[] = []; let items: backend.CElement[] = [];
let about: backend.CAbout | null = null; let about: backend.CAbout | null = null;
@ -167,10 +160,6 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
updateInternal(!triggerInternal); updateInternal(!triggerInternal);
} }
function updateIdc(_: any) {
update();
}
// perform tasks (like updating display elements) only while rendering the plugin // perform tasks (like updating display elements) only while rendering the plugin
let taskItem = updateTasks.pop(); let taskItem = updateTasks.pop();
while (taskItem != undefined) { while (taskItem != undefined) {
@ -180,12 +169,8 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
return ( return (
<PanelSection> <PanelSection>
{items.map( <Elements items={items}/>
(elem, i) => { <About about={about}/>
return <PanelSectionRow>{buildHtmlElement(elem, i, updateIdc)}</PanelSectionRow>
})
}
{ about != null && buildAbout() }
<PanelSectionRow> <PanelSectionRow>
<ButtonItem <ButtonItem
layout="below" layout="below"
@ -219,210 +204,6 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
); );
}; };
function buildHtmlElement(element: backend.CElement, index: number, updateIdc: any) {
switch (element.element) {
case "button":
return buildButton(element as backend.CButton, index, updateIdc);
case "slider":
return buildSlider(element as backend.CSlider, index, updateIdc);
case "toggle":
return buildToggle(element as backend.CToggle, index, updateIdc);
case "reading":
return buildReading(element as backend.CReading, index, updateIdc);
case "result-display":
return buildResultDisplay(element as backend.CResultDisplay, index, updateIdc);
case "event-display":
return buildEventDisplay(element as backend.CEventDisplay, index, updateIdc);
}
console.error("CAYLON: Unsupported element", element);
backend.log(backend.CLogLevel.ERROR, "Unsupported element " + element.element);
return <div>Unsupported</div>;
}
function buildButton(element: backend.CButton, index: number, updateIdc: any) {
return (
<ButtonItem
layout="below"
onClick={() => {backend.resolve(backend.onUpdate(index, null), updateIdc)}}>
{element.title}
</ButtonItem>
);
}
function buildSlider(element: backend.CSlider, index: number, updateIdc: any) {
const KEY = VALUE_KEY + index.toString();
if (get_value(KEY) == null) {
set_value(KEY, element.min);
}
return (
<SliderField
label={element.title}
value={get_value(KEY)}
max={element.max}
min={element.min}
showValue={true}
onChange={(value: number) => {
backend.resolve(backend.onUpdate(index, value), updateIdc);
set_value(KEY, value);
}}
/>
);
}
function buildToggle(element: backend.CToggle, index: number, updateIdc: any) {
const KEY = VALUE_KEY + index.toString();
if (get_value(KEY) == null) {
set_value(KEY, false);
}
return (
<ToggleField
checked={get_value(KEY)}
label={element.title}
description={element.description!}
onChange={(value: boolean) => {
backend.resolve(backend.onUpdate(index, value), updateIdc);
set_value(KEY, value);
}}
/>
);
}
function buildReading(element: backend.CReading, index: number, _updateIdc: any) {
return (
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>{element.title}</div>
<div className={gamepadDialogClasses.FieldChildren}>{get_value(DISPLAY_KEY + index.toString())}</div>
</div>
</div>
);
}
function buildResultDisplay(element: backend.CResultDisplay, index: number, _updateIdc: any) {
return (
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>{element.title}</div>
<div className={gamepadDialogClasses.FieldChildren}>{get_value(DISPLAY_KEY + index.toString())}</div>
</div>
</div>
);
}
function buildEventDisplay(element: backend.CEventDisplay, index: number, _updateIdc: any) {
return (
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>{element.title}</div>
<div className={gamepadDialogClasses.FieldChildren}>{get_value(DISPLAY_KEY + index.toString())}</div>
</div>
</div>
);
}
function buildAbout() {
if (about == null) {
return [];
} else {
let elements = [
<div className={staticClasses.PanelSectionTitle}>
About
</div>,
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>Name</div>
<div className={gamepadDialogClasses.FieldChildren}>{about.name}</div>
</div>
</div>
</PanelSectionRow>,
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>Version</div>
<div className={gamepadDialogClasses.FieldChildren}>{about.version}</div>
</div>
</div>
</PanelSectionRow>,
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>Description</div>
<div className={gamepadDialogClasses.FieldDescription}>{about.description}</div>
</div>
</div>
</PanelSectionRow>
];
if (about.url != null) {
elements.push(
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>URL</div>
<div className={gamepadDialogClasses.FieldDescription}>{about.url}</div>
</div>
</div>
</PanelSectionRow>
);
}
if (about.authors.length > 1) {
let authors = about.authors.map((elem, i) => {
if (i == about!.authors.length - 1) {
return <p>{elem}</p>;
} else {
return <span>{elem}</span>;
}
});
elements.push(
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>Authors</div>
<div className={gamepadDialogClasses.FieldDescription}>{authors}</div>
</div>
</div>
</PanelSectionRow>
);
} else if (about.authors.length == 1) {
elements.push(
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>Author</div>
<div className={gamepadDialogClasses.FieldDescription}>{about.authors[0]}</div>
</div>
</div>
</PanelSectionRow>
);
} else {
elements.push(
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>Author</div>
<div className={gamepadDialogClasses.FieldDescription}>NGnius</div>
</div>
</div>
</PanelSectionRow>
);
}
if (about.license != null) {
elements.push(
<PanelSectionRow>
<div className={FieldWithSeparator}>
<div className={gamepadDialogClasses.FieldLabelRow}>
<div className={gamepadDialogClasses.FieldLabel}>License</div>
<div className={gamepadDialogClasses.FieldChildren}>{about.license}</div>
</div>
</div>
</PanelSectionRow>
);
}
return elements;
}
}
export default definePlugin((serverApi: ServerAPI) => { export default definePlugin((serverApi: ServerAPI) => {
return { return {
title: <div className={staticClasses.Title}>{about == null? "Caylon": about.name}</div>, title: <div className={staticClasses.Title}>{about == null? "Caylon": about.name}</div>,

File diff suppressed because one or more lines are too long

Binary file not shown.