diff --git a/src/UI/Components/ActionCard.js b/src/UI/Components/ActionCard.js new file mode 100644 index 000000000..8c65b9118 --- /dev/null +++ b/src/UI/Components/ActionCard.js @@ -0,0 +1,39 @@ +const Component = use('util.Component'); + +export default def(class ActionCard extends Component { + static ID = 'ui.component.ActionCard'; + static RENDER_MODE = Component.NO_SHADOW; + + static PROPERTIES = { + title: { + value: 'Title' + }, + info: {}, + button_text: {}, + button_style: {}, + on_click: {}, + style: {}, + } + + create_template ({ template }) { + $(template).html(/*html*/` +
+
+ ${ this.get('title') } + ${ + this.get('info') + } +
+
+ +
+
+ `); + } + + on_ready ({ listen }) { + $(this.dom_).find('button').on('click', this.get('on_click') || (() => {})); + } +}); diff --git a/src/UI/Components/Frame.js b/src/UI/Components/Frame.js new file mode 100644 index 000000000..446f4f166 --- /dev/null +++ b/src/UI/Components/Frame.js @@ -0,0 +1,20 @@ +const Component = use('util.Component'); + +export default def(class Frame extends Component { + static ID = 'ui.component.Frame'; + static RENDER_MODE = Component.NO_SHADOW; + + static PROPERTIES = { + component: {}, + } + + on_ready ({ listen }) { + listen('component', component => { + this.dom_.innerHTML = ''; + if ( ! component ) { + return; + } + component.attach(this.dom_); + }); + } +}); diff --git a/src/UI/Components/Glyph.js b/src/UI/Components/Glyph.js new file mode 100644 index 000000000..2d58dadfc --- /dev/null +++ b/src/UI/Components/Glyph.js @@ -0,0 +1,28 @@ +import { Component } from "../../util/Component.js"; + +export default def(class Glyph extends Component { + static ID = 'ui.component.Glyph'; + + static PROPERTIES = { + size: { + value: 24, + }, + codepoint: { + value: '✅', + }, + } + + static CSS = ` + div { + text-align: center; + } + `; + + create_template ({ template }) { + template.innerHTML = /*html*/` +
+ ${this.get('codepoint')} +
+ `; + } +}); diff --git a/src/UI/Components/JustHTML.js b/src/UI/Components/JustHTML.js index b1233d695..ace8548f9 100644 --- a/src/UI/Components/JustHTML.js +++ b/src/UI/Components/JustHTML.js @@ -15,4 +15,13 @@ export default def(class JustHTML extends Component { $(this.dom_).find('span').html(html); }); } + + _set_dom_based_on_render_mode({ property_values }) { + if ( property_values.no_shadow ) { + this.dom_ = this; + return; + } + + return super._set_dom_based_on_render_mode(); + } }); diff --git a/src/UI/Components/NotifCard.js b/src/UI/Components/NotifCard.js new file mode 100644 index 000000000..737904e21 --- /dev/null +++ b/src/UI/Components/NotifCard.js @@ -0,0 +1,25 @@ +const Component = use('util.Component'); + +export default def(class NotifCard extends Component { + static ID = 'ui.component.NotifCard'; + static RENDER_MODE = Component.NO_SHADOW; + + static PROPERTIES = { + text: { value: 'no text' }, + style: {}, + } + + create_template ({ template }) { + $(template).html(/*html*/` +
+
+ ${ this.get('text') } +
+
+ `); + } + + on_ready ({ listen }) { + $(this.dom_).find('button').on('click', this.get('on_click') || (() => {})); + } +}); diff --git a/src/UI/Components/Spinner.js b/src/UI/Components/Spinner.js index 05d1e20d9..ffae866ab 100644 --- a/src/UI/Components/Spinner.js +++ b/src/UI/Components/Spinner.js @@ -3,15 +3,19 @@ const Component = use('util.Component'); export default def(class Spinner extends Component { static ID = 'ui.component.Spinner'; - static PROPERTIES = {} + static PROPERTIES = { + size: { + value: 24, + }, + } // static RENDER_MODE = Component.NO_SHADOW; create_template ({ template }) { - console.log('template?', template); + const size = '' + Number(this.get('size')); template.innerHTML = /*html*/`
- + circle anim diff --git a/src/UI/Settings/UIWindowSettings.js b/src/UI/Settings/UIWindowSettings.js index 832ea8f6a..e50999511 100644 --- a/src/UI/Settings/UIWindowSettings.js +++ b/src/UI/Settings/UIWindowSettings.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ +import Placeholder from '../../util/Placeholder.js'; import UIWindow from '../UIWindow.js' async function UIWindowSettings(options){ @@ -26,6 +27,7 @@ async function UIWindowSettings(options){ const svc_settings = globalThis.services.get('settings'); const tabs = svc_settings.get_tabs(); + const tab_placeholders = []; let h = ''; @@ -42,9 +44,14 @@ async function UIWindowSettings(options){ h += `
`; tabs.forEach((tab, i) => { - h += `
- ${tab.html()} -
`; + h += `
`; + if ( tab.factory ) { + tab_placeholders[i] = Placeholder(); + h += tab_placeholders[i].html; + } else { + h += tab.html(); + } + h += `
`; }); h += `
`; @@ -85,7 +92,13 @@ async function UIWindowSettings(options){ } }); const $el_window = $(el_window); - tabs.forEach(tab => tab.init($el_window)); + tabs.forEach((tab, i) => { + tab.init && tab.init($el_window); + if ( tab.factory ) { + const component = tab.factory(); + component.attach(tab_placeholders[i]); + } + }); $(el_window).on('click', '.settings-sidebar-item', function(){ const $this = $(this); diff --git a/src/css/style.css b/src/css/style.css index 0913d78b9..37fd04c0f 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -3709,6 +3709,10 @@ fieldset[name=number-code] { height: 45px; } +.thin-card { + padding: 0 15px; +} + .settings-card strong { font-weight: 500; } diff --git a/src/init_async.js b/src/init_async.js index 212310431..40aca629c 100644 --- a/src/init_async.js +++ b/src/init_async.js @@ -3,6 +3,10 @@ logger.info('start -> async initialization'); import './util/TeePromise.js'; import './util/Component.js'; +import './UI/Components/Frame.js'; +import './UI/Components/Glyph.js'; +import './UI/Components/ActionCard.js'; +import './UI/Components/NotifCard.js'; logger.info('end -> async initialization'); globalThis.init_promise.resolve(); diff --git a/src/init_sync.js b/src/init_sync.js index 44277a2f6..cea2e5347 100644 --- a/src/init_sync.js +++ b/src/init_sync.js @@ -84,7 +84,8 @@ logger.info('start -> blocking initialization'); } if ( registry_.classes_m[id] ) { - throw new Error(`Class with ID ${id} already registered`); + // throw new Error(`Class with ID ${id} already registered`); + return; } registry_.classes_m[id] = cls; diff --git a/src/util/Component.js b/src/util/Component.js index 9b08ba12e..813ce00b7 100644 --- a/src/util/Component.js +++ b/src/util/Component.js @@ -34,14 +34,23 @@ export const Component = def(class Component extends HTMLElement { }); } - constructor (property_values) { - super(); - + _set_dom_based_on_render_mode () { if ( this.constructor.RENDER_MODE === Component.NO_SHADOW ) { this.dom_ = this; } else { this.dom_ = this.attachShadow({ mode: 'open' }); } + } + + constructor (property_values) { + super(); + + property_values = property_values || {}; + + // We allow a subclass of component to define custom behavior + // for the `RENDER_MODE` static property. This is so JustHTML + // can have ths `no_shadow: true` option. + this._set_dom_based_on_render_mode({ property_values }); this.values_ = {}; @@ -104,6 +113,10 @@ export const Component = def(class Component extends HTMLElement { } get (key) { + if ( ! this.values_.hasOwnProperty(key) ) { + throw new Error(`Unknown property \`${key}\` in ${ + this.constructor.ID || this.constructor.name}`); + } return this.values_[key].get(); } @@ -130,6 +143,11 @@ export const Component = def(class Component extends HTMLElement { return; } + if ( destination instanceof ShadowRoot ) { + destination.appendChild(this); + return; + } + if ( destination.$ === 'placeholder' ) { destination.replaceWith(this); return; @@ -162,6 +180,14 @@ export const Component = def(class Component extends HTMLElement { get_api_ () { return { listen: (name, callback) => { + if ( Array.isArray(name) ) { + const names = name; + for ( const name of names ) { + this.values_[name].sub((_, more) => { + callback(this, { ...more, name }); + }); + } + } this.values_[name].sub(callback); callback(this.values_[name].get(), {}); } diff --git a/src/util/Placeholder.js b/src/util/Placeholder.js index 48bf60197..5515a9f7b 100644 --- a/src/util/Placeholder.js +++ b/src/util/Placeholder.js @@ -18,7 +18,7 @@ * * @returns {PlaceholderReturn} */ -const Placeholder = () => { +const Placeholder = def(() => { const id = Placeholder.get_next_id_(); return { $: 'placeholder', @@ -29,7 +29,7 @@ const Placeholder = () => { place.replaceWith(el); } }; -}; +}, 'util.Placeholder'); const anti_collision = `94d2cb6b85a1`; // Arbitrary random string Placeholder.next_id_ = 0;