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