mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
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.
This commit is contained in:
parent
146ce659e2
commit
51bac4486f
@ -161,6 +161,28 @@ class PuterHomepageService extends BaseService {
|
||||
<!-- Preload images when applicable -->
|
||||
<link rel="preload" as="image" href="${asset_dir}/images/wallpaper.webp">
|
||||
|
||||
<script>
|
||||
if ( ! window.service_script ) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Files from JSON (may be empty) -->
|
||||
${
|
||||
((!bundled && manifest?.css_paths)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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(`<span></span>`);
|
||||
@ -15,4 +17,4 @@ export default class JustHTML extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
defineComponent('c-just-html', JustHTML);
|
||||
defineComponent(JustHTML);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
|
@ -17,6 +17,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
export class Service {
|
||||
construct () {
|
||||
if ( ! this._construct ) return;
|
||||
return this._construct();
|
||||
}
|
||||
init (...a) {
|
||||
if ( ! this._init ) return;
|
||||
return this._init(...a)
|
||||
|
17
src/index.js
17
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
|
@ -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 ===
|
||||
|
24
src/services/ExportService.js
Normal file
24
src/services/ExportService.js
Normal file
@ -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_;
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
|
@ -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');
|
17
src/util/register.js
Normal file
17
src/util/register.js
Normal file
@ -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);
|
||||
})()
|
||||
};
|
Loading…
Reference in New Issue
Block a user