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); + })() +};