From 51bac4486f01ee7f0b97a82ade3a36d929a2682f Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Mon, 27 May 2024 21:14:10 -0400 Subject: [PATCH] Add class registry (first pass) In the first pass I add a `register` method and update `defineComponent` so it calls `register` as well. This made it possible to create a proof-of-concept for registered classes. Additionally ExportService was added to expose registered classes to service scripts. This first pass works, but it would be better if all types of classes (components or otherwise) were registered via the same method. --- .../src/services/PuterHomepageService.js | 22 ++++++++++ src/UI/Components/Button.js | 4 +- src/UI/Components/CodeEntryView.js | 4 +- src/UI/Components/ConfirmationsView.js | 4 +- src/UI/Components/Flexer.js | 4 +- src/UI/Components/JustHTML.js | 4 +- src/UI/Components/PasswordEntry.js | 4 +- src/UI/Components/QRCode.js | 4 +- src/UI/Components/RecoveryCodeEntryView.js | 3 +- src/UI/Components/RecoveryCodesView.js | 4 +- src/UI/Components/Slider.js | 4 +- src/UI/Components/StepHeading.js | 4 +- src/UI/Components/StepView.js | 4 +- src/UI/Components/StringView.js | 4 +- src/UI/Components/Table.js | 4 +- src/UI/Components/TestView.js | 4 +- src/UI/UIWindowTaskManager.js | 7 +++- src/definitions.js | 4 ++ src/index.js | 17 -------- src/initgui.js | 18 ++++++++- src/services/ExportService.js | 24 +++++++++++ src/util/Component.js | 40 ++++++++++++++++--- src/util/TeePromise.js | 4 ++ src/util/register.js | 17 ++++++++ 24 files changed, 171 insertions(+), 41 deletions(-) create mode 100644 src/services/ExportService.js create mode 100644 src/util/register.js diff --git a/packages/backend/src/services/PuterHomepageService.js b/packages/backend/src/services/PuterHomepageService.js index 8f8f1fbd..2c8f8fe7 100644 --- a/packages/backend/src/services/PuterHomepageService.js +++ b/packages/backend/src/services/PuterHomepageService.js @@ -161,6 +161,28 @@ class PuterHomepageService extends BaseService { + + ${ ((!bundled && manifest?.css_paths) diff --git a/src/UI/Components/Button.js b/src/UI/Components/Button.js index 9950f51e..4b7436a2 100644 --- a/src/UI/Components/Button.js +++ b/src/UI/Components/Button.js @@ -1,6 +1,8 @@ import { Component, defineComponent } from "../../util/Component.js"; export default class Button extends Component { + static ID = 'ui.component.Button'; + static PROPERTIES = { label: { value: 'Test Label' }, on_click: { value: null }, @@ -65,4 +67,4 @@ export default class Button extends Component { } } -defineComponent('c-button', Button); +defineComponent(Button); diff --git a/src/UI/Components/CodeEntryView.js b/src/UI/Components/CodeEntryView.js index a4d0c538..6d218353 100644 --- a/src/UI/Components/CodeEntryView.js +++ b/src/UI/Components/CodeEntryView.js @@ -1,6 +1,8 @@ import { Component, defineComponent } from "../../util/Component.js"; export default class CodeEntryView extends Component { + static ID = 'ui.component.CodeEntryView'; + static PROPERTIES = { value: {}, error: {}, @@ -215,4 +217,4 @@ export default class CodeEntryView extends Component { } } -defineComponent('c-code-entry-view', CodeEntryView); +defineComponent(CodeEntryView); diff --git a/src/UI/Components/ConfirmationsView.js b/src/UI/Components/ConfirmationsView.js index 19ec4d3e..73e17b0c 100644 --- a/src/UI/Components/ConfirmationsView.js +++ b/src/UI/Components/ConfirmationsView.js @@ -4,6 +4,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * Display a list of checkboxes for the user to confirm. */ export default class ConfirmationsView extends Component { + static ID = 'ui.component.ConfirmationsView'; + static PROPERTIES = { confirmations: { description: 'The list of confirmations to display', @@ -58,4 +60,4 @@ export default class ConfirmationsView extends Component { } } -defineComponent('c-confirmations-view', ConfirmationsView); +defineComponent(ConfirmationsView); diff --git a/src/UI/Components/Flexer.js b/src/UI/Components/Flexer.js index 838be891..cce4af7b 100644 --- a/src/UI/Components/Flexer.js +++ b/src/UI/Components/Flexer.js @@ -5,6 +5,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * treated as a component. */ export default class Flexer extends Component { + static ID = 'ui.component.Flexer'; + static PROPERTIES = { children: {}, gap: { value: '20pt' }, @@ -38,4 +40,4 @@ export default class Flexer extends Component { } } -defineComponent('c-flexer', Flexer); +defineComponent(Flexer); diff --git a/src/UI/Components/JustHTML.js b/src/UI/Components/JustHTML.js index 36e17db5..bc896583 100644 --- a/src/UI/Components/JustHTML.js +++ b/src/UI/Components/JustHTML.js @@ -4,6 +4,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * Allows using an HTML string as a component. */ export default class JustHTML extends Component { + static ID = 'ui.component.JustHTML'; + static PROPERTIES = { html: { value: '' } }; create_template ({ template }) { $(template).html(``); @@ -15,4 +17,4 @@ export default class JustHTML extends Component { } } -defineComponent('c-just-html', JustHTML); +defineComponent(JustHTML); diff --git a/src/UI/Components/PasswordEntry.js b/src/UI/Components/PasswordEntry.js index cbacdffe..7cd6afa3 100644 --- a/src/UI/Components/PasswordEntry.js +++ b/src/UI/Components/PasswordEntry.js @@ -1,6 +1,8 @@ import { Component, defineComponent } from "../../util/Component.js"; export default class PasswordEntry extends Component { + static ID = 'ui.component.PasswordEntry'; + static PROPERTIES = { spec: {}, value: {}, @@ -133,4 +135,4 @@ export default class PasswordEntry extends Component { } } -defineComponent('c-password-entry', PasswordEntry); +defineComponent(PasswordEntry); diff --git a/src/UI/Components/QRCode.js b/src/UI/Components/QRCode.js index bb974d99..b5c698dd 100644 --- a/src/UI/Components/QRCode.js +++ b/src/UI/Components/QRCode.js @@ -2,6 +2,8 @@ import { Component, defineComponent } from "../../util/Component.js"; import UIComponentWindow from "../UIComponentWindow.js"; export default class QRCodeView extends Component { + static ID = 'ui.component.QRCodeView'; + static PROPERTIES = { value: { description: 'The text to encode in the QR code', @@ -78,4 +80,4 @@ export default class QRCodeView extends Component { } } -defineComponent('c-qr-code', QRCodeView); +defineComponent(QRCodeView); diff --git a/src/UI/Components/RecoveryCodeEntryView.js b/src/UI/Components/RecoveryCodeEntryView.js index b17d27e1..7b63b63d 100644 --- a/src/UI/Components/RecoveryCodeEntryView.js +++ b/src/UI/Components/RecoveryCodeEntryView.js @@ -1,6 +1,7 @@ import { Component, defineComponent } from "../../util/Component.js"; export default class RecoveryCodeEntryView extends Component { + static ID = 'ui.component.RecoveryCodeEntryView'; static PROPERTIES = { value: {}, length: { value: 8 }, @@ -84,4 +85,4 @@ export default class RecoveryCodeEntryView extends Component { } } -defineComponent('c-recovery-code-entry', RecoveryCodeEntryView); +defineComponent(RecoveryCodeEntryView); diff --git a/src/UI/Components/RecoveryCodesView.js b/src/UI/Components/RecoveryCodesView.js index e72bd37f..89fac698 100644 --- a/src/UI/Components/RecoveryCodesView.js +++ b/src/UI/Components/RecoveryCodesView.js @@ -1,6 +1,8 @@ import { Component, defineComponent } from "../../util/Component.js"; export default class RecoveryCodesView extends Component { + static ID = 'ui.component.RecoveryCodesView'; + static PROPERTIES = { values: { description: 'The recovery codes to display', @@ -91,4 +93,4 @@ export default class RecoveryCodesView extends Component { } } -defineComponent('c-recovery-codes-view', RecoveryCodesView); +defineComponent(RecoveryCodesView); diff --git a/src/UI/Components/Slider.js b/src/UI/Components/Slider.js index 18281d80..64e68fe9 100644 --- a/src/UI/Components/Slider.js +++ b/src/UI/Components/Slider.js @@ -22,6 +22,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * Slider: A labeled slider input. */ export default class Slider extends Component { + static ID = 'ui.component.Slider'; + static PROPERTIES = { name: { value: null }, label: { value: null }, @@ -111,4 +113,4 @@ export default class Slider extends Component { } } -defineComponent('c-slider', Slider); +defineComponent(Slider); diff --git a/src/UI/Components/StepHeading.js b/src/UI/Components/StepHeading.js index b095d832..fbfcd4e6 100644 --- a/src/UI/Components/StepHeading.js +++ b/src/UI/Components/StepHeading.js @@ -6,6 +6,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * optimized for single-digit numbers. */ export default class StepHeading extends Component { + static ID = 'ui.component.StepHeading'; + static PROPERTIES = { symbol: { description: 'The symbol to display', @@ -58,4 +60,4 @@ export default class StepHeading extends Component { } } -defineComponent('c-step-heading', StepHeading); +defineComponent(StepHeading); diff --git a/src/UI/Components/StepView.js b/src/UI/Components/StepView.js index 4bc98e77..dc359686 100644 --- a/src/UI/Components/StepView.js +++ b/src/UI/Components/StepView.js @@ -1,6 +1,8 @@ import { Component, defineComponent } from "../../util/Component.js"; export default class StepView extends Component { + static ID = 'ui.component.StepView'; + static PROPERTIES = { children: {}, done: { value: false }, @@ -64,4 +66,4 @@ export default class StepView extends Component { } } -defineComponent('c-step-view', StepView); +defineComponent(StepView); diff --git a/src/UI/Components/StringView.js b/src/UI/Components/StringView.js index a41552c6..a4a67652 100644 --- a/src/UI/Components/StringView.js +++ b/src/UI/Components/StringView.js @@ -5,6 +5,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * specified style. */ export default class StringView extends Component { + static ID = 'ui.component.StringView'; + static PROPERTIES = { text: { value: '' }, heading: { value: 0 }, @@ -42,4 +44,4 @@ export default class StringView extends Component { } } -defineComponent('c-string-view', StringView); +defineComponent(StringView); diff --git a/src/UI/Components/Table.js b/src/UI/Components/Table.js index 865aa7c4..0b1ba6b3 100644 --- a/src/UI/Components/Table.js +++ b/src/UI/Components/Table.js @@ -4,6 +4,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * A table with a sticky header */ export default class Table extends Component { + static ID = 'ui.component.Table'; + static PROPERTIES = { headings: { value: [] }, scale: { value: '2pt' }, @@ -80,4 +82,4 @@ export default class Table extends Component { } } -defineComponent('c-table', Table); +defineComponent(Table); diff --git a/src/UI/Components/TestView.js b/src/UI/Components/TestView.js index 9fd76a3c..59121f46 100644 --- a/src/UI/Components/TestView.js +++ b/src/UI/Components/TestView.js @@ -4,6 +4,8 @@ import { Component, defineComponent } from "../../util/Component.js"; * A simple component when you just need to test something. */ export default class TestView extends Component { + static ID = 'ui.component.TestView'; + static CSS = ` div { background-color: lightblue; @@ -19,4 +21,4 @@ export default class TestView extends Component { } } -defineComponent('c-test-view', TestView); +defineComponent(TestView); diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index 7cb2b392..c92756e5 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -49,6 +49,7 @@ const end_process = async (uuid, force) => { }; class TaskManagerTable extends Component { + static ID = 'ui.component.TaskManagerTable'; static PROPERTIES = { tasks: { value: [] }, }; @@ -157,9 +158,11 @@ class TaskManagerTable extends Component { return rows; }; } -defineComponent('c-task-manager-table', TaskManagerTable); +defineComponent(TaskManagerTable); class TaskManagerRow extends Component { + static ID = 'ui.component.TaskManagerRow'; + static PROPERTIES = { name: {}, uuid: {}, @@ -291,7 +294,7 @@ class TaskManagerRow extends Component { }); } } -defineComponent('c-task-manager-row', TaskManagerRow); +defineComponent(TaskManagerRow); const UIWindowTaskManager = async function UIWindowTaskManager () { const svc_process = globalThis.services.get('process'); diff --git a/src/definitions.js b/src/definitions.js index 9a6a84c4..90bc50e1 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -17,6 +17,10 @@ * along with this program. If not, see . */ export class Service { + construct () { + if ( ! this._construct ) return; + return this._construct(); + } init (...a) { if ( ! this._init ) return; return this._init(...a) diff --git a/src/index.js b/src/index.js index 88719361..f3f3c0fe 100644 --- a/src/index.js +++ b/src/index.js @@ -16,23 +16,6 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -window.service_script_api_promise = (() => { - let resolve, reject; - const promise = new Promise((res, rej) => { - resolve = res; - reject = rej; - }); - promise.resolve = resolve; - promise.reject = reject; - return promise; -})(); -window.service_script = async fn => { - try { - await fn(await window.service_script_api_promise); - } catch (e) { - console.error('service_script(ERROR)', e); - } -}; window.puter_gui_enabled = true; /** diff --git a/src/initgui.js b/src/initgui.js index 9e079e71..9a9c654c 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -40,7 +40,9 @@ import { ProcessService } from './services/ProcessService.js'; import { PROCESS_RUNNING } from './definitions.js'; import { LocaleService } from './services/LocaleService.js'; import { SettingsService } from './services/SettingsService.js'; +import { ExportService } from './services/ExportService.js'; import UIComponentWindow from './UI/UIComponentWindow.js'; +import Spinner from './UI/Components/Spinner.js'; const launch_services = async function () { // === Services Data Structures === @@ -54,6 +56,9 @@ const launch_services = async function () { services_m_[name] = instance; } + const svc_export = new ExportService(); + svc_export.register('UIComponentWindow', UIComponentWindow); + // === Hooks for Service Scripts from Backend === const service_script_deferred = { services: [], on_ready: [] }; const service_script_api = { @@ -61,7 +66,9 @@ const launch_services = async function () { on_ready: fn => service_script_deferred.on_ready.push(fn), // Some files can't be imported by service scripts, // so this hack makes that possible. - use: name => ({ UIWindow, UIComponentWindow })[name], + use: svc_export.get.bind(svc_export), + exp: svc_export.register.bind(svc_export), + // use: name => ({ UIWindow, UIComponentWindow })[name], }; globalThis.service_script_api_promise.resolve(service_script_api); @@ -71,6 +78,7 @@ const launch_services = async function () { register('process', new ProcessService()); register('locale', new LocaleService()); register('settings', new SettingsService()); + register('export', svc_export); // === Service-Script Services === for (const [name, script] of service_script_deferred.services) { @@ -78,7 +86,13 @@ const launch_services = async function () { } for (const [_, instance] of services_l_) { - await instance.init(); + await instance.construct(); + } + + for (const [_, instance] of services_l_) { + await instance.init({ + services: globalThis.services, + }); } // === Service-Script Ready === diff --git a/src/services/ExportService.js b/src/services/ExportService.js new file mode 100644 index 00000000..f05f7e77 --- /dev/null +++ b/src/services/ExportService.js @@ -0,0 +1,24 @@ +import { Service } from "../definitions.js"; + +/** + * This service is responsible for exporting definitions to the + * service script SDK. This is the SDK that services provided by + * the backend will use. + */ +export class ExportService extends Service { + constructor () { + super(); + this.exports_ = {}; + } + + register (name, definition) { + this.exports_[name] = definition; + } + + get (name) { + if ( name ) { + return this.exports_[name]; + } + return this.exports_; + } +} diff --git a/src/util/Component.js b/src/util/Component.js index 40839c86..76ed132d 100644 --- a/src/util/Component.js +++ b/src/util/Component.js @@ -1,4 +1,5 @@ import ValueHolder from "./ValueHolder.js"; +import { register } from "./register.js"; export class Component extends HTMLElement { #has_created_element = false; @@ -146,10 +147,39 @@ export class Component extends HTMLElement { } } -export const defineComponent = (name, component) => { - // TODO: This is necessary because files can be loaded from - // both `/src/UI` and `/UI` in the URL; we need to fix that - if ( ! customElements.get(name) ) { - customElements.define(name, component); +// TODO: move this somewhere more useful +function is_subclass(subclass, superclass) { + if (subclass === superclass) return true; + + let proto = subclass.prototype; + while (proto) { + if (proto === superclass.prototype) return true; + proto = Object.getPrototypeOf(proto); } + + return false; +} + +export const defineComponent = (component) => { + // Web components need tags (despite that we never use the tags) + // because it was designed this way. + if ( is_subclass(component, HTMLElement) ) { + console.log('defining', component); + let name = component.ID; + name = 'c-' + name.split('.').pop().toLowerCase(); + // TODO: This is necessary because files can be loaded from + // both `/src/UI` and `/UI` in the URL; we need to fix that + console.log('[maybe] defining', name, 'as', component); + if ( customElements.get(name) ) return; + + console.log('[surely] defining', name, 'as', component); + + customElements.define(name, component); + component.defined_as = name; + } + + // Service scripts aren't able to import anything when the + // GUI code is bundled, so we need to use a custom export + // mechanism for them. + register(component); }; diff --git a/src/util/TeePromise.js b/src/util/TeePromise.js index 94f9b9d6..5192ee6a 100644 --- a/src/util/TeePromise.js +++ b/src/util/TeePromise.js @@ -1,3 +1,5 @@ +import { register } from "./register.js"; + export default class TeePromise { static STATUS_PENDING = {}; static STATUS_RUNNING = {}; @@ -41,3 +43,5 @@ export default class TeePromise { return this.then(fn); } } + +register(TeePromise, 'TeePromise'); \ No newline at end of file diff --git a/src/util/register.js b/src/util/register.js new file mode 100644 index 00000000..5da2d372 --- /dev/null +++ b/src/util/register.js @@ -0,0 +1,17 @@ +/** + * register registers a class with things that need classes + * to be registered. When in doubt, register your class. + * + * More specifically this function is here to handle such + * situations as service scripts not being able to import + * classes when the frontend is bundled. + * + * @param {*} cls + * @param {*} opt_name + */ +export const register = (cls, opt_name) => { + (async () => { + const api = await globalThis.service_script_api_promise; + api.exp(opt_name || cls.ID.split('.').pop(), cls); + })() +};