diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index 2b7018d..c6ea0f0 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -4194,6 +4194,49 @@ declare namespace lib_plankton.zoo_widget { load(target_element: HTMLElement): Promise; } } +declare namespace lib_plankton.zoo_widget { + /** + */ + class class_slider implements interface_widget { + /** + */ + private conf; + /** + */ + private state; + /** + */ + constructor(content: Array, { "threshold": threshold, "acceleration_function": acceleration_function, "initial_index": initial_index, }?: { + threshold?: float; + acceleration_function?: ((float: any) => float); + initial_index?: int; + }); + /** + */ + private static cap; + /** + */ + private static position_subtract; + /** + */ + private static touch_event_position; + /** + */ + private set_translation; + /** + */ + private start; + /** + */ + private update; + /** + */ + private finish; + /** + */ + load(target_element: HTMLElement): Promise; + } +} declare namespace lib_plankton.zoo_page { /** */ diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index f94ef73..dfa0424 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -12743,9 +12743,10 @@ var lib_plankton; const dom_container = document.createElement("div"); dom_container.classList.add("widget-bunch"); for (const element of this.elements) { - const dom_li = document.createElement("li"); - await element.load(dom_li); - dom_container.appendChild(dom_li); + const dom_element = document.createElement("div"); + dom_element.classList.add("widget-bunch-element"); + await element.load(dom_element); + dom_container.appendChild(dom_element); } target_element.appendChild(dom_container); } @@ -12754,6 +12755,259 @@ var lib_plankton; })(zoo_widget = lib_plankton.zoo_widget || (lib_plankton.zoo_widget = {})); })(lib_plankton || (lib_plankton = {})); /* +This file is part of »bacterio-plankton:zoo-widget«. + +Copyright 2016-2025 'kcf' + +»bacterio-plankton:zoo-widget« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:zoo-widget« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:zoo-widget«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var zoo_widget; + (function (zoo_widget) { + /** + */ + class class_slider { + /** + */ + constructor(content, { "threshold": threshold = 200, "acceleration_function": acceleration_function = (x => (x * 3)), "initial_index": initial_index = 1, } = {}) { + this.conf = { + "content": content, + "threshold": threshold, + "acceleration_function": acceleration_function, + }; + this.state = { + "dom_context": null, + "index": initial_index, + "touch_start_position": null, + }; + } + /** + */ + static cap(minimum, maximum, value) { + let result = value; + if (minimum !== null) { + result = Math.max(minimum, result); + } + if (maximum !== null) { + result = Math.min(maximum, result); + } + return result; + } + /** + */ + static position_subtract(position1, position2) { + return { + "x": (position1.x - position2.x), + "y": (position1.y - position2.y), + }; + } + /** + */ + static touch_event_position(touch_event) { + return ((touch_event.changedTouches.length <= 0) + ? + null + : + { + "x": touch_event.changedTouches[0].pageX, + "y": touch_event.changedTouches[0].pageY, + }); + } + /** + */ + set_translation(position) { + const difference = (((this.state.touch_start_position === null) || (position === null)) + ? + null + : + class_slider.position_subtract(position, this.state.touch_start_position)); + this.state.dom_context.querySelector(".widget-slider-slides").style.transform = (([ + lib_plankton.string.coin("translateX({{value}}%)", { + "value": ((this.state.index - 1) * (-100)).toFixed(0) + }) + ] + .concat((difference === null) + ? + [] + : + [ + lib_plankton.string.coin("translateX({{value}}px)", { + "value": class_slider.cap(((this.state.index >= this.conf.content.length) + ? + (-this.conf.threshold) + : + null), ((this.state.index <= 1) + ? + this.conf.threshold + : + null), this.conf.acceleration_function(difference.x)).toFixed(0), + }) + ])) + .join(" ")); + } + /** + */ + start(position) { + if (position === null) { + // do nothing + } + else { + if (this.state.touch_start_position === null) { + this.state.touch_start_position = position; + } + else { + // do nothing + } + } + } + /** + */ + update(position) { + if (position === null) { + // do nothing + } + else { + if (this.state.touch_start_position === null) { + // do nothing + } + else { + this.set_translation(position); + } + } + } + /** + */ + finish(position) { + if (position === null) { + this.state.touch_start_position = null; + // do nothing + } + else { + if (this.state.touch_start_position === null) { + // do nothing + } + else { + const difference = class_slider.position_subtract(position, this.state.touch_start_position); + const offset = this.conf.acceleration_function(difference.x); + const index_increment = (() => { + if (offset <= -this.conf.threshold) { + return ((this.state.index >= this.conf.content.length) ? 0 : (+1)); + } + else if ((offset > -this.conf.threshold) && (offset < this.conf.threshold)) { + return 0; + } + else if (offset >= this.conf.threshold) { + return ((this.state.index <= 1) ? 0 : (-1)); + } + else { + // not possible + } + })(); + this.state.index += index_increment; + this.set_translation(null); + this.state.touch_start_position = null; + } + } + } + /** + */ + async load(target_element) { + this.state.dom_context = target_element; + // style + { + const dom_style = document.createElement("style"); + dom_style.textContent = ([ + { + "selector": ".widget-slider", + "settings": { + "overflow": "hidden", + "white-space": "nowrap", + "width": "100%", + }, + }, + { + "selector": ".widget-slider-slides", + "settings": { + "width": "100%", + "height": "100%", + "transition": "transform 0.25s ease-in-out", + } + }, + { + "selector": ".widget-slider-slide", + "settings": { + "display": "inline-block", + "height": "100%", + "width": "100%;", + "vertical-align": "top", + "white-space": "normal", + "transition": "width 0.25s ease-in-out", + } + } + ] + .map(rule => lib_plankton.string.coin("{{selector}} {{{settings}}}", { + "selector": rule.selector, + "settings": (Object.entries(rule.settings) + .map(([key, value]) => lib_plankton.string.coin("{{key}}: {{value}};", { + "key": key, + "value": value, + })) + .join(" ")) + })) + .join("\n")); + this.state.dom_context.appendChild(dom_style); + } + // structure + { + const dom_container = document.createElement("div"); + dom_container.classList.add("widget-slider"); + { + const dom_content = document.createElement("div"); + dom_content.classList.add("widget-slider-slides"); + for (const slide of this.conf.content) { + const dom_slide = document.createElement("div"); + dom_slide.classList.add("widget-slider-slide"); + await slide.load(dom_slide); + dom_content.appendChild(dom_slide); + } + dom_container.appendChild(dom_content); + } + this.state.dom_context.appendChild(dom_container); + } + // listeners + { + this.state.dom_context.addEventListener("touchstart", (event) => { + this.start(class_slider.touch_event_position(event)); + }); + this.state.dom_context.addEventListener("touchmove", (event) => { + this.update(class_slider.touch_event_position(event)); + }); + this.state.dom_context.addEventListener("touchend", (event) => { + this.finish(class_slider.touch_event_position(event)); + }); + this.state.dom_context.addEventListener("touchcancel", (event) => { + this.finish(null); + }); + } + this.set_translation(null); + } + } + zoo_widget.class_slider = class_slider; + })(zoo_widget = lib_plankton.zoo_widget || (lib_plankton.zoo_widget = {})); +})(lib_plankton || (lib_plankton = {})); +/* This file is part of »bacterio-plankton:zoo-page«. Copyright 2016-2025 'kcf' diff --git a/misc/conf-example.json b/misc/conf-example.json index cf5b37e..330b798 100644 --- a/misc/conf-example.json +++ b/misc/conf-example.json @@ -7,7 +7,7 @@ "path": "" }, "misc": { - "oidc_redirect_uri_template": "http://localhost:8888/#oidc_finish,session_key={{session_key}}", + "oidc_redirect_uri_template": "http://localhost:8888/?action=oidc_finish&session_key={{session_key}}", "use_central_europe_specific_datetime_inputs": true } } diff --git a/source/base.ts b/source/base.ts index e4de274..dab47b3 100644 --- a/source/base.ts +++ b/source/base.ts @@ -106,8 +106,19 @@ namespace _dali { await _dali.backend.set_session_key(session_key); const status = await _dali.backend.status(); - await _dali.notify_login(status.name); - return Promise.resolve(undefined); + if (! status.logged_in) + { + lib_plankton.log.error( + "dali.oidc_login_failed" + ); + await _dali.notify_logout(); + return Promise.reject(new Error("oidc login failed")); + } + else + { + await _dali.notify_login(status.name); + return Promise.resolve(undefined); + } } diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index abaf730..af4a35a 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -27,6 +27,7 @@ "common.monthname.november": "Nov", "common.monthname.december": "Dez", "common.open": "öffnen", + "common.close": "schließen", "common.edit": "bearbeiten", "common.show": "zeigen", "common.hide": "ausblenden", @@ -78,27 +79,20 @@ "widget.login.internal.password": "Kennwort", "widget.login.internal.do": "Anmelden", "widget.login.oidc.via": "via {{title}}", - "page.caldav.title": "CalDAV", - "page.caldav.unavailable": "CalDAV nicht verfügbar", - "page.caldav.conf.title": "Zugangsdaten", - "page.caldav.conf.address": "Adresse (URL)", - "page.caldav.conf.username": "Nutzername", - "page.caldav.conf.password": "Kennwort", - "page.caldav.conf.setup_hints": "Einrichtungs-Hinweise", - "page.caldav.conf.token_unset": "es muss zunächst ein Token gesetzt werden", - "page.caldav.set_token.title": "Token setzen", - "page.caldav.set_token.action.set": "setzen", - "page.caldav.set_token.action.overwrite": "überschreiben", - "page.calendar_add.title": "Kalendar anlegen", - "page.calendar_add.actions.do": "anlegen", - "page.calendar_edit.title.regular": "Kalendar bearbeiten", - "page.calendar_edit.title.read_only": "Kalendar-Details", - "page.event_add.title": "Termin anlegen", - "page.event_edit.title.regular": "Termin bearbeiten", - "page.event_edit.title.read_only": "Termin-Details", - "page.overview.title": "Übersicht", - "page.overview.login_hint": "anmelden um nicht-öffentliche Termine zu sehen", - "page.overview.mode.week": "Wochen-Ansicht", - "page.overview.mode.list": "Listen-Ansicht" + "widget.caldav.title": "CalDAV", + "widget.caldav.unavailable": "CalDAV nicht verfügbar", + "widget.caldav.conf.title": "Zugangsdaten", + "widget.caldav.conf.address": "Adresse (URL)", + "widget.caldav.conf.username": "Nutzername", + "widget.caldav.conf.password": "Kennwort", + "widget.caldav.conf.setup_hints": "Einrichtungs-Hinweise", + "widget.caldav.conf.token_unset": "es muss zunächst ein Token gesetzt werden", + "widget.caldav.set_token.title": "Token setzen", + "widget.caldav.set_token.action.set": "setzen", + "widget.caldav.set_token.action.overwrite": "überschreiben", + "widget.overview.title": "Übersicht", + "widget.overview.login_hint": "anmelden um nicht-öffentliche Termine zu sehen", + "widget.overview.mode.week": "Wochen-Ansicht", + "widget.overview.mode.list": "Listen-Ansicht" } } diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index 1436b7d..c63e674 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -27,6 +27,7 @@ "common.monthname.november": "nov", "common.monthname.december": "dec", "common.open": "open", + "common.close": "close", "common.edit": "edit", "common.show": "show", "common.hide": "hide", @@ -78,27 +79,20 @@ "widget.login.internal.password": "password", "widget.login.internal.do": "login", "widget.login.oidc.via": "via {{title}}", - "page.caldav.title": "CalDAV", - "page.caldav.unavailable": "CalDAV not available", - "page.caldav.conf.title": "credentials", - "page.caldav.conf.address": "address (URL)", - "page.caldav.conf.username": "username", - "page.caldav.conf.password": "password", - "page.caldav.conf.setup_hints": "setup hints", - "page.caldav.conf.token_unset": "a token has to be set", - "page.caldav.set_token.title": "set token", - "page.caldav.set_token.action.set": "set", - "page.caldav.set_token.action.overwrite": "overwrite", - "page.calendar_add.title": "Add calendar", - "page.calendar_add.actions.do": "anlegen", - "page.event_add.title": "Add event", - "page.calendar_edit.title.regular": "Edit calendar", - "page.calendar_edit.title.read_only": "Calendar details", - "page.event_edit.title.regular": "Edit event", - "page.event_edit.title.read_only": "Event details", - "page.overview.title": "Overview", - "page.overview.login_hint": "log in to view non-public events", - "page.overview.mode.week": "week view", - "page.overview.mode.list": "list view" + "widget.caldav.title": "CalDAV", + "widget.caldav.unavailable": "CalDAV not available", + "widget.caldav.conf.title": "credentials", + "widget.caldav.conf.address": "address (URL)", + "widget.caldav.conf.username": "username", + "widget.caldav.conf.password": "password", + "widget.caldav.conf.setup_hints": "setup hints", + "widget.caldav.conf.token_unset": "a token has to be set", + "widget.caldav.set_token.title": "set token", + "widget.caldav.set_token.action.set": "set", + "widget.caldav.set_token.action.overwrite": "overwrite", + "widget.overview.title": "Overview", + "widget.overview.login_hint": "log in to view non-public events", + "widget.overview.mode.week": "week view", + "widget.overview.mode.list": "list view" } } diff --git a/source/helpers.ts b/source/helpers.ts index 48b1a3f..e955bae 100644 --- a/source/helpers.ts +++ b/source/helpers.ts @@ -23,6 +23,23 @@ along with »dali«. If not, see . namespace _dali.helpers { + /** + * @todo outsource + */ + function is_touch_device( + ) + : boolean + { + return ( + ("ontouchstart" in window) + || + (navigator.maxTouchPoints > 0) + || + (navigator["msMaxTouchPoints"] > 0) + ); + } + + /** */ var _template_cache : Record = {}; @@ -32,12 +49,13 @@ namespace _dali.helpers */ export function view_mode_determine( mode_descriptor : string - ) : _dali.enum_view_mode + ) + : _dali.enum_view_mode { if (mode_descriptor === "auto") { return ( - (window.innerWidth >= 800) + (window.innerWidth >= 1000) ? _dali.enum_view_mode.week : @@ -51,6 +69,30 @@ namespace _dali.helpers } + /** + */ + export function view_kind_determine( + mode_descriptor : string + ) + : _dali.enum_view_kind + { + if (mode_descriptor === "auto") + { + return ( + is_touch_device() + ? + _dali.enum_view_kind.touch + : + _dali.enum_view_kind.regular + ); + } + else + { + return view_kind_decode(mode_descriptor); + } + } + + /** */ export async function template_coin( @@ -308,4 +350,23 @@ namespace _dali.helpers return lib_plankton.translate.get(keys[month-1]); } + + /** + */ + export function loading( + mode : boolean + ) + : void + { + if (! mode) + { + _dali.overlay.toggle({"mode": false}); + } + else + { + _dali.overlay.get_content_element().innerHTML = "[...]"; + _dali.overlay.toggle({"mode": true}); + } + } + } diff --git a/source/main.ts b/source/main.ts index 97cb9d8..af55d02 100644 --- a/source/main.ts +++ b/source/main.ts @@ -23,24 +23,6 @@ along with »dali«. If not, see . namespace _dali { - /** - */ - function nav_groups( - logged_in : boolean - ) - : Array - { - return ( - logged_in - ? - ["logged_in"] - : - ["logged_out"] - ); - } - - - /** */ export async function main( @@ -52,7 +34,7 @@ namespace _dali "conf.json" ); - // init + // init:logger lib_plankton.log.set_main_logger( [ { @@ -68,6 +50,11 @@ namespace _dali }, ] ); + // init:overlay + { + await _dali.overlay.initialize(); + _dali.helpers.loading(true); + } // init:localization { const order : Array = ["deu", "eng"]; @@ -76,10 +63,11 @@ namespace _dali "verbosity": 1, "packages": await Promise.all( order.map( - async (code) => JSON.parse( - await lib_plankton.file.read( - "data/localization/" + code + ".loc.json" - ) + code => ( + Promise.resolve(code) + .then(code => Promise.resolve(lib_plankton.string.coin("data/localization/{{code}}.loc.json", {"code": code}))) + .then(lib_plankton.file.read) + .then(content => Promise.resolve(JSON.parse(content))) ) ) ), @@ -88,11 +76,14 @@ namespace _dali } ); } + // init:backend await _dali.backend.initialize( _dali.conf.get()["backend"] ); + // init:model await _dali.model.initialize( ); + // init:page lib_plankton.zoo_page.init( document.querySelector("main"), { @@ -100,69 +91,55 @@ namespace _dali "name": "overview", "parameters": {} }, - /* - "nav_entries": [ - ], - */ - "nav_initial_groups": [], } ); - // menu widget + // init:menu { - const widget_menu : _dali.widgets.menu.class_widget_menu = new _dali.widgets.menu.class_widget_menu( + const widget_menu : _dali.widgets.class_widget_menu = new _dali.widgets.class_widget_menu( [ { "label": lib_plankton.translate.get("common.login"), "groups": ["logged_out"], - "action": () => { - const widget_login = new _dali.widgets.login.class_widget_login( + "action": async () => { + _dali.helpers.loading(true); + const widget : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.class_widget_login( { "action_cancel": () => { _dali.overlay.clear(); _dali.overlay.toggle({"mode": false}); }, "action_success": async () => { + _dali.helpers.loading(true); const status = await _dali.backend.status(); _dali.notify_login(status.name); - _dali.overlay.clear(); - _dali.overlay.toggle({"mode": false}); + // _dali.overlay.clear(); + _dali.helpers.loading(false); }, } ); - _dali.overlay.clear(); + await widget.load(_dali.overlay.get_content_element()); + // _dali.helpers.loading(false); _dali.overlay.toggle({"mode": true}); - widget_login.load(_dali.overlay.get_content_element()); }, }, { - "label": lib_plankton.translate.get("page.overview.title"), - "groups": ["logged_out", "logged_in"], - "action": () => { - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - }, - }, - { - "label": lib_plankton.translate.get("page.caldav.title"), + "label": lib_plankton.translate.get("widget.caldav.title"), "groups": ["logged_in"], - "action": () => { - lib_plankton.zoo_page.set( - { - "name": "caldav", - "parameters": {} - } - ); + "action": async () => { + _dali.helpers.loading(true); + const widget : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.class_widget_caldav(); + await widget.load(_dali.overlay.get_content_element()); + // _dali.helpers.loading(false); + _dali.overlay.toggle({"mode": true}); }, }, { "label": lib_plankton.translate.get("common.logout"), "groups": ["logged_in"], - "action": () => { - _dali.logout(); + "action": async () => { + _dali.helpers.loading(true); + await _dali.logout(); + _dali.helpers.loading(false); }, }, ] @@ -181,9 +158,36 @@ namespace _dali } ); } - await _dali.overlay.initialize(); - // check if logged_in + // process actions + { + let url_search_params : URLSearchParams = new URLSearchParams(window.location.search); + const action : (null | string) = url_search_params.get("action"); + switch (action) + { + case "oidc_finish": + { + try + { + await _dali.oidc_finish(url_search_params.get("session_key")); + } + catch (error_) + { + // do nothing + } + document.location = "/"; + break; + } + default: + case null: + { + // do nothing + break; + } + } + } + + // process status { const status = await _dali.backend.status(); lib_plankton.log.info( @@ -200,7 +204,15 @@ namespace _dali } } - lib_plankton.zoo_page.start(); + // load overview + { + const widget : lib_plankton.zoo_widget.interface_widget = ( + new _dali.widgets.class_widget_overview( + ) + ); + await widget.load(document.querySelector("main")); + } + _dali.helpers.loading(false); return Promise.resolve(undefined); } diff --git a/source/overlay.ts b/source/overlay.ts index 8db482b..890a42d 100644 --- a/source/overlay.ts +++ b/source/overlay.ts @@ -24,7 +24,8 @@ namespace _dali.overlay /** */ function get_container_element( - ) : HTMLElement + ) + : HTMLElement { return document.querySelector("#overlay"); } @@ -33,7 +34,8 @@ namespace _dali.overlay /** */ export function get_content_element( - ) : HTMLElement + ) + : HTMLElement { return document.querySelector("#overlay_content"); } @@ -42,7 +44,8 @@ namespace _dali.overlay /** */ export function clear( - ) : void + ) + : void { get_content_element().innerHTML = ""; } @@ -61,7 +64,8 @@ namespace _dali.overlay = { } - ) : void + ) + : void { get_container_element().classList.toggle("overlay_active", mode ?? undefined); } @@ -70,7 +74,8 @@ namespace _dali.overlay /** */ export function initialize( - ) : Promise + ) + : Promise { clear(); const container_element : HTMLElement = get_container_element(); diff --git a/source/pages/caldav/templates/conf-token_set.html.tpl b/source/pages/caldav/templates/conf-token_set.html.tpl deleted file mode 100644 index 0a8b003..0000000 --- a/source/pages/caldav/templates/conf-token_set.html.tpl +++ /dev/null @@ -1,18 +0,0 @@ -
- - {{address_value}} -
-
- - {{username_value}} -
-
- - {{password_value}} -
-
- -
    -{{setup_hint_entries}} -
-
diff --git a/source/pages/caldav/templates/conf-token_unset.html.tpl b/source/pages/caldav/templates/conf-token_unset.html.tpl deleted file mode 100644 index 1d78bf3..0000000 --- a/source/pages/caldav/templates/conf-token_unset.html.tpl +++ /dev/null @@ -1,4 +0,0 @@ -
- ({{text}}) -
- diff --git a/source/pages/caldav/templates/main.html.tpl b/source/pages/caldav/templates/main.html.tpl deleted file mode 100644 index da98a2b..0000000 --- a/source/pages/caldav/templates/main.html.tpl +++ /dev/null @@ -1,6 +0,0 @@ -
-

{{label}}

-
-{{content}} -
-
diff --git a/source/pages/caldav/templates/unavailable.html.tpl b/source/pages/caldav/templates/unavailable.html.tpl deleted file mode 100644 index d6d75fb..0000000 --- a/source/pages/caldav/templates/unavailable.html.tpl +++ /dev/null @@ -1,3 +0,0 @@ -
- {{text}} -
diff --git a/source/pages/oidc_finish/logic.ts b/source/pages/oidc_finish/logic.ts deleted file mode 100644 index 925f93e..0000000 --- a/source/pages/oidc_finish/logic.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* -This file is part of »dali«. - -Copyright 2025 'kcf' - -»dali« is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -»dali« is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with »dali«. If not, see . - */ - - -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "oidc_finish", - async (parameters, target_element) => { - target_element.innerHTML = ""; - await _dali.oidc_finish(parameters["session_key"]); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/overview/style.css b/source/pages/overview/style.css deleted file mode 100644 index f60b623..0000000 --- a/source/pages/overview/style.css +++ /dev/null @@ -1,46 +0,0 @@ -#overview-head -{ - padding-bottom: 12px; - margin-bottom: 12px; - border-bottom: 1px solid; -} - -#overview-hint -{ - text-align: center; - font-weight: bold; -} - -#overview-hint.overview-hint-hidden -{ - display: none; -} - -#overview-body -{ - display: flex; - flex-direction: row; - flex-wrap: nowrap; -} - -#overview-body #overview-pane-left -{ - flex-grow: 0; - flex-shrink: 1; -} - -#overview-body #overview-pane-right -{ - flex-grow: 1; - flex-shrink: 1; -} - -#overview.overview-compact #overview-pane-left {/*flex-basis: 25%;*/display: none;} -#overview.overview-compact #overview-pane-right {flex-basis: 75%;} -#overview.overview-compact #overview-pane-right-listview {} -#overview.overview-compact #overview-pane-right-weekview {display: none;} - -#overview:not(.overview-compact) #overview-pane-left {flex-basis: 12.5%;} -#overview:not(.overview-compact) #overview-pane-right {flex-basis: 87.5%;} -#overview:not(.overview-compact) #overview-pane-right-listview {display: none;} -#overview:not(.overview-compact) #overview-pane-right-weekview {} diff --git a/source/pages/overview/templates/default.html.tpl b/source/pages/overview/templates/default.html.tpl deleted file mode 100644 index aeb85a8..0000000 --- a/source/pages/overview/templates/default.html.tpl +++ /dev/null @@ -1,18 +0,0 @@ -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/source/style/main.css b/source/style/main.css index 6feb245..dc06abf 100644 --- a/source/style/main.css +++ b/source/style/main.css @@ -101,7 +101,7 @@ a:hover button { - padding: 8px; + padding: 4px 8px; text-transform: uppercase; cursor: pointer; diff --git a/source/types.ts b/source/types.ts index 925bdb4..5767175 100644 --- a/source/types.ts +++ b/source/types.ts @@ -25,7 +25,8 @@ namespace _dali /** */ - export enum enum_access_level { + export enum enum_access_level + { none, view, edit, @@ -202,20 +203,12 @@ namespace _dali }; - /** - */ - export enum enum_view_mode - { - week, - list, - } - - /** */ export function access_level_encode( access_level : _dali.enum_access_level - ) : ("none" | "view" | "edit" | "admin") + ) + : ("none" | "view" | "edit" | "admin") { switch (access_level) { @@ -231,7 +224,8 @@ namespace _dali */ export function access_level_decode( representation : /*("none" | "view" | "edit" | "admin")*/string - ) : _dali.enum_access_level + ) + : _dali.enum_access_level { switch (representation) { @@ -244,11 +238,21 @@ namespace _dali } + /** + */ + export enum enum_view_mode + { + week, + list, + } + + /** */ export function view_mode_encode( mode : _dali.enum_view_mode - ) : string + ) + : string { switch (mode) { @@ -263,7 +267,8 @@ namespace _dali */ export function view_mode_decode( view_mode_encoded : string - ) : _dali.enum_view_mode + ) + : _dali.enum_view_mode { const map : Record = { "week": _dali.enum_view_mode.week, @@ -279,4 +284,51 @@ namespace _dali } } + + /** + */ + export enum enum_view_kind + { + regular, + touch, + } + + + /** + */ + export function view_kind_encode( + kind : _dali.enum_view_kind + ) + : string + { + switch (kind) + { + case _dali.enum_view_kind.regular: {return "regular"; break;} + case _dali.enum_view_kind.touch: {return "touch"; break;} + default: {throw (new Error("invalid kind"));} + } + } + + + /** + */ + export function view_kind_decode( + view_kind_encoded : string + ) + : _dali.enum_view_kind + { + const map : Record = { + "regular": _dali.enum_view_kind.regular, + "touch": _dali.enum_view_kind.touch, + }; + if (! (view_kind_encoded in map)) + { + throw (new Error("invalid kind: " + view_kind_encoded)); + } + else + { + return map[view_kind_encoded]; + } + } + } diff --git a/source/pages/caldav/logic.ts b/source/widgets/caldav/logic.ts similarity index 58% rename from source/pages/caldav/logic.ts rename to source/widgets/caldav/logic.ts index 028108b..d382238 100644 --- a/source/pages/caldav/logic.ts +++ b/source/widgets/caldav/logic.ts @@ -18,65 +18,80 @@ along with »dali«. If not, see . */ -namespace _dali.pages +namespace _dali.widgets { /** */ - lib_plankton.zoo_page.register( - "caldav", - async (parameters, target_element) => { + export class class_widget_caldav + { + + /** + */ + public constructor( + ) + { + } + + + /** + */ + public async load( + target_element : HTMLElement + ) + : Promise + { target_element.innerHTML = ""; const conf = await _dali.backend.user_dav_conf(); target_element.innerHTML = await _dali.helpers.template_coin( - "caldav", + "widget-caldav", "main", { - "label": lib_plankton.translate.get("page.caldav.title"), + "title": lib_plankton.translate.get("widget.caldav.title"), "content": ( (conf === null) ? await _dali.helpers.template_coin( - "caldav", + "widget-caldav", "unavailable", { - "text": lib_plankton.translate.get("page.caldav.unavailable"), + "text": lib_plankton.translate.get("widget.caldav.unavailable"), } ) : await _dali.helpers.template_coin( - "caldav", + "widget-caldav", "available", { - "conf_title": lib_plankton.translate.get("page.caldav.conf.title"), + "conf_title": lib_plankton.translate.get("widget.caldav.conf.title"), "conf_content": ( (conf.password === null) ? await _dali.helpers.template_coin( - "caldav", + "widget-caldav", "conf-token_unset", { - "text": lib_plankton.translate.get("page.caldav.conf.token_unset") + "text": lib_plankton.translate.get("widget.caldav.conf.token_unset") } ) : await _dali.helpers.template_coin( - "caldav", + "widget-caldav", "conf-token_set", { - "address_label": lib_plankton.translate.get("page.caldav.conf.address"), + "address_label": lib_plankton.translate.get("widget.caldav.conf.address"), "address_value": conf.address, - "username_label": lib_plankton.translate.get("page.caldav.conf.username"), + "username_label": lib_plankton.translate.get("widget.caldav.conf.username"), "username_value": conf.username, - "password_label": lib_plankton.translate.get("page.caldav.conf.password"), + "password_label": lib_plankton.translate.get("widget.caldav.conf.password"), "password_value": conf.password, - "setup_hints_label": lib_plankton.translate.get("page.caldav.conf.setup_hints"), + "setup_hints_label": lib_plankton.translate.get("widget.caldav.conf.setup_hints"), "setup_hint_entries": ( await lib_plankton.call.promise_condense( conf.setup_hints .map( entry => () => _dali.helpers.template_coin( - "caldav", + "widget-caldav", "conf-setup_hint_entry", { "text": entry.label, @@ -101,31 +116,45 @@ namespace _dali.pages } ) ), - "set_token_title": lib_plankton.translate.get("page.caldav.set_token.title"), + "set_token_title": lib_plankton.translate.get("widget.caldav.set_token.title"), "set_token_action": ( (conf.password === null) ? - lib_plankton.translate.get("page.caldav.set_token.action.set") + lib_plankton.translate.get("widget.caldav.set_token.action.set") : - lib_plankton.translate.get("page.caldav.set_token.action.overwrite") + lib_plankton.translate.get("widget.caldav.set_token.action.overwrite") ), } ) - ) + ), + "close": lib_plankton.translate.get("common.close"), } ); - - /** - * logic: set token - */ + // logic { - if (conf !== null) + // set token { - document.querySelector("#caldav-set_token > button").addEventListener( + if (conf === null) + { + // do nothing + } + else + { + target_element.querySelector(".widget-caldav-set_token > button").addEventListener( + "click", + async () => { + await _dali.backend.user_dav_token(); + await this.load(target_element); + } + ); + } + } + // close + { + target_element.querySelector(".widget-caldav-close").addEventListener( "click", - async () => { - await _dali.backend.user_dav_token(); - lib_plankton.zoo_page.reload(); + () => { + _dali.overlay.toggle({"mode": false}); } ); } @@ -133,7 +162,8 @@ namespace _dali.pages return Promise.resolve(undefined); } - ); - + + } + } diff --git a/source/pages/caldav/style.css b/source/widgets/caldav/style.css similarity index 55% rename from source/pages/caldav/style.css rename to source/widgets/caldav/style.css index 47e293b..e053072 100644 --- a/source/pages/caldav/style.css +++ b/source/widgets/caldav/style.css @@ -1,9 +1,9 @@ -.caldav-conf-section +.widget-caldav-conf-section { margin-bottom: 16px; } -.caldav-conf-section-label +.widget-caldav-conf-section-label { margin-left: 16px; display: block; @@ -11,12 +11,12 @@ text-transform: capitalize; } -.caldav-conf-section-value +.widget-caldav-conf-section-value { margin-left: 32px; } -.caldav-conf-section-value-regular +.widget-caldav-conf-section-value-regular { font-family: monospace; } diff --git a/source/pages/caldav/templates/available.html.tpl b/source/widgets/caldav/templates/available.html.tpl similarity index 64% rename from source/pages/caldav/templates/available.html.tpl rename to source/widgets/caldav/templates/available.html.tpl index c8bb50b..56b3cde 100644 --- a/source/pages/caldav/templates/available.html.tpl +++ b/source/widgets/caldav/templates/available.html.tpl @@ -1,8 +1,8 @@ -
+

{{conf_title}}

{{conf_content}}
-
+

{{set_token_title}}

diff --git a/source/pages/caldav/templates/conf-setup_hint_entry.html.tpl b/source/widgets/caldav/templates/conf-setup_hint_entry.html.tpl similarity index 56% rename from source/pages/caldav/templates/conf-setup_hint_entry.html.tpl rename to source/widgets/caldav/templates/conf-setup_hint_entry.html.tpl index d4338e6..737f0e8 100644 --- a/source/pages/caldav/templates/conf-setup_hint_entry.html.tpl +++ b/source/widgets/caldav/templates/conf-setup_hint_entry.html.tpl @@ -1,3 +1,3 @@ -
  • +
  • {{text}}{{remark}}
  • diff --git a/source/widgets/caldav/templates/conf-token_set.html.tpl b/source/widgets/caldav/templates/conf-token_set.html.tpl new file mode 100644 index 0000000..5b5441a --- /dev/null +++ b/source/widgets/caldav/templates/conf-token_set.html.tpl @@ -0,0 +1,18 @@ +
    + + {{address_value}} +
    +
    + + {{username_value}} +
    +
    + + {{password_value}} +
    +
    + +
      +{{setup_hint_entries}} +
    +
    diff --git a/source/widgets/caldav/templates/conf-token_unset.html.tpl b/source/widgets/caldav/templates/conf-token_unset.html.tpl new file mode 100644 index 0000000..beb63a5 --- /dev/null +++ b/source/widgets/caldav/templates/conf-token_unset.html.tpl @@ -0,0 +1,4 @@ +
    + ({{text}}) +
    + diff --git a/source/widgets/caldav/templates/main.html.tpl b/source/widgets/caldav/templates/main.html.tpl new file mode 100644 index 0000000..c4dfd7d --- /dev/null +++ b/source/widgets/caldav/templates/main.html.tpl @@ -0,0 +1,8 @@ +
    +

    {{title}}

    +
    +{{content}} +
    +
    + +
    diff --git a/source/widgets/caldav/templates/unavailable.html.tpl b/source/widgets/caldav/templates/unavailable.html.tpl new file mode 100644 index 0000000..28f7bf8 --- /dev/null +++ b/source/widgets/caldav/templates/unavailable.html.tpl @@ -0,0 +1,3 @@ +
    + {{text}} +
    diff --git a/source/widgets/calendar_edit/logic.ts b/source/widgets/calendar_edit/logic.ts index 4c30a1d..a3464ba 100644 --- a/source/widgets/calendar_edit/logic.ts +++ b/source/widgets/calendar_edit/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.calendar_edit +namespace _dali.widgets { /** diff --git a/source/widgets/event_edit/logic.ts b/source/widgets/event_edit/logic.ts index d2f76cc..4eecdb7 100644 --- a/source/widgets/event_edit/logic.ts +++ b/source/widgets/event_edit/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.event_edit +namespace _dali.widgets { /** diff --git a/source/widgets/listview/logic.ts b/source/widgets/listview/logic.ts index 34c68e6..53fbba0 100644 --- a/source/widgets/listview/logic.ts +++ b/source/widgets/listview/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.listview +namespace _dali.widgets { /** @@ -162,146 +162,121 @@ namespace _dali.widgets.listview /** - * [implementation] */ - public async load( - target_element : Element + public async update_entries( ) : Promise { - const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); - const from_pit : lib_plankton.pit.type_pit = now_pit; - const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.shift_week(now_pit, +4); - const entries : Array<_dali.type_event_object_extended> = await this.get_entries( - from_pit, - to_pit, - null - ); - entries.sort( - (x, y) => ( - lib_plankton.pit.from_datetime(x.event_object.begin) - - - lib_plankton.pit.from_datetime(y.event_object.begin) - ) - ); - - // view + // structure { - target_element.innerHTML = await _dali.helpers.template_coin( - "widget-listview", - "main", - { - "add_href": "", - "add_label": lib_plankton.translate.get("widget.listview.add"), - "add_extra_classes": ( - (! await _dali.is_logged_in()) - ? - " listview-add-hidden" - : - "" - ), - "entries": ( - ( - await _dali.helpers.promise_row( - entries - .filter( - (entry) => ( - this.include_passed - ? - true - : - lib_plankton.pit.is_after( - lib_plankton.pit.from_datetime(entry.event_object.begin), - now_pit - ) - ) - ) - .map( - (entry) => () => _dali.helpers.template_coin( - "widget-listview", - "entry", - { - "name_value": entry.event_object.name, - "calendar_value": entry.calendar_name, - "when_value": lib_plankton.pit.timespan_format( - entry.event_object.begin, - entry.event_object.end, - { - "timezone_indicator": lib_plankton.translate.get("common.timezone_indicator"), - "adjust_to_ce": true, - "show_timezone": false, - } - ), - "location_label": lib_plankton.translate.get("event.location"), - "location_extra_classes": ( - (entry.event_object.location === null) - ? - " listview-entry-field-empty" - : - "" - ), - "location_value": ( - (entry.event_object.location === null) - ? - "?" - : - entry.event_object.location - ), - "link_label": lib_plankton.translate.get("event.link"), - "link_extra_classes": ( - (entry.event_object.link === null) - ? - " listview-entry-field-empty" - : - "" - ), - "link_value": ( - (entry.event_object.link === null) - ? - "?" - : - entry.event_object.link - ), - "link_action": lib_plankton.translate.get("common.open"), - "description_label": lib_plankton.translate.get("event.description"), - "description_extra_classes": ( - (entry.event_object.description === null) - ? - " listview-entry-field-empty" - : - "" - ), - "description_value": ( - (entry.event_object.description === null) - ? - "?" - : - entry.event_object.description - ), - "raw": JSON.stringify(entry), - "color": _dali.helpers.event_color(entry.hue), - "rel": entry.key, - }, - ) + const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); + const from_pit : lib_plankton.pit.type_pit = now_pit; + const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.shift_week(now_pit, +4); + const entries : Array<_dali.type_event_object_extended> = await this.get_entries( + from_pit, + to_pit, + null + ); + entries.sort( + (x, y) => ( + lib_plankton.pit.from_datetime(x.event_object.begin) + - + lib_plankton.pit.from_datetime(y.event_object.begin) + ) + ); + const dom_list : HTMLElement = this.container.querySelector(".listview-entries"); + dom_list.innerHTML = ( + ( + await _dali.helpers.promise_row( + entries + .filter( + (entry) => ( + this.include_passed + ? + true + : + lib_plankton.pit.is_after( + lib_plankton.pit.from_datetime(entry.event_object.begin), + now_pit ) ) ) - .join("") - ), - } + .map( + (entry) => () => _dali.helpers.template_coin( + "widget-listview", + "entry", + { + "name_value": entry.event_object.name, + "calendar_value": entry.calendar_name, + "when_value": lib_plankton.pit.timespan_format( + entry.event_object.begin, + entry.event_object.end, + { + "timezone_indicator": lib_plankton.translate.get("common.timezone_indicator"), + "adjust_to_ce": true, + "show_timezone": false, + } + ), + "location_label": lib_plankton.translate.get("event.location"), + "location_extra_classes": ( + (entry.event_object.location === null) + ? + " listview-entry-field-empty" + : + "" + ), + "location_value": ( + (entry.event_object.location === null) + ? + "?" + : + entry.event_object.location + ), + "link_label": lib_plankton.translate.get("event.link"), + "link_extra_classes": ( + (entry.event_object.link === null) + ? + " listview-entry-field-empty" + : + "" + ), + "link_value": ( + (entry.event_object.link === null) + ? + "?" + : + entry.event_object.link + ), + "link_action": lib_plankton.translate.get("common.open"), + "description_label": lib_plankton.translate.get("event.description"), + "description_extra_classes": ( + (entry.event_object.description === null) + ? + " listview-entry-field-empty" + : + "" + ), + "description_value": ( + (entry.event_object.description === null) + ? + "?" + : + entry.event_object.description + ), + "raw": JSON.stringify(entry), + "color": _dali.helpers.event_color(entry.hue), + "rel": entry.key, + }, + ) + ) + ) + ) + .join("") ); } - - // control + // listeners { - target_element.querySelector(".listview-add").addEventListener( - "click", - (event) => { - event.preventDefault(); - this.action_add(); - } - ); - target_element.querySelectorAll(".listview-entry").forEach( + this.container.querySelectorAll(".listview-entry").forEach( (element) => { element.addEventListener( "click", @@ -320,8 +295,51 @@ namespace _dali.widgets.listview } ); } + } + + + /** + * [implementation] + */ + public async load( + target_element : Element + ) + : Promise + { + this.container = target_element; - this.container = target_element.querySelector(".listview"); + // view + { + target_element.innerHTML = await _dali.helpers.template_coin( + "widget-listview", + "main", + { + "add_href": "", + "add_label": lib_plankton.translate.get("widget.listview.add"), + "add_extra_classes": ( + (! await _dali.is_logged_in()) + ? + " listview-add-hidden" + : + "" + ), + "entries": "", + } + ); + } + + // control + { + target_element.querySelector(".listview-add").addEventListener( + "click", + (event) => { + event.preventDefault(); + this.action_add(); + } + ); + } + + await this.update_entries(); return Promise.resolve(undefined); } diff --git a/source/widgets/login/logic.ts b/source/widgets/login/logic.ts index 4838588..ba90d34 100644 --- a/source/widgets/login/logic.ts +++ b/source/widgets/login/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.login +namespace _dali.widgets { /** @@ -73,7 +73,7 @@ namespace _dali.widgets.login * [implementation] */ public async load( - target_element : Element + target_element : HTMLElement ) : Promise { @@ -86,125 +86,36 @@ namespace _dali.widgets.login { case "internal": { - target_element.innerHTML = await _dali.helpers.template_coin( - "widget-login", - "default", - { - } - ); - const form : lib_plankton.zoo_form.class_form< - {name : string; password : string;}, - {name : string; password : string;} - > = new lib_plankton.zoo_form.class_form< - {name : string; password : string;}, - {name : string; password : string;} - >( - x => x, - x => x, - new lib_plankton.zoo_input.class_input_group< - {name : string; password : string;} - >( - [ - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text(), - "label": lib_plankton.translate.get("widget.login.internal.name"), - }, - { - "name": "password", - "input": new lib_plankton.zoo_input.class_input_password(), - "label": lib_plankton.translate.get("widget.login.internal.password"), - }, - ] - ), - ( - [] - .concat( - [ - { - "label": lib_plankton.translate.get("widget.login.internal.do"), - "procedure": async (get_value, get_representation) => { - const value : any = await get_value(); - try - { - await _dali.backend.session_begin( - value.name, - value.password - ); - if (this.action_success !== null) this.action_success(); - } - catch (error) - { - // todo - } - } - } - ] - ) - .concat( - (this.action_cancel === null) - ? - [] - : - [ - { - "label": lib_plankton.translate.get("common.cancel"), - "procedure": () => { - this.action_cancel(); - } - } - ] - ) + const sub_widget : lib_plankton.zoo_widget.interface_widget = ( + new class_widget_login_internal( + preparation.data, + { + "initial_name": this.initial_name, + "action_cancel": this.action_cancel, + "action_success": this.action_success, + } ) ); - await form.setup(document.querySelector(".widget-login")); - await form.input_write( - { - "name": this.initial_name, - "password": "", - } - ); + await sub_widget.load(target_element); break; } case "oidc": { - // link - { - let element_a : HTMLElement = document.createElement("a");; - element_a.textContent = lib_plankton.string.coin( - lib_plankton.translate.get("widget.login.oidc.via"), + const sub_widget : lib_plankton.zoo_widget.interface_widget = ( + new class_widget_login_oidc( + preparation.data, { - "title": preparation.data.label, + "action_cancel": this.action_cancel, + "action_success": this.action_success, } - ); - element_a.setAttribute("href", preparation.data.url); - target_element.appendChild(element_a); - } - { - let dom_br : HTMLElement = document.createElement("br"); - target_element.appendChild(dom_br); - } - { - let dom_br : HTMLElement = document.createElement("br"); - target_element.appendChild(dom_br); - } - // cancel - { - let dom_cancel : HTMLElement = document.createElement("button"); - dom_cancel.textContent = lib_plankton.translate.get("common.cancel"); - dom_cancel.addEventListener( - "click", - () => { - this.action_cancel(); - } - ); - target_element.appendChild(dom_cancel); - } + ) + ); + await sub_widget.load(target_element); break; } default: { - // todo + throw (new Error("unhandled login kind: " + preparation.kind)); break; } } diff --git a/source/widgets/login/templates/default.html.tpl b/source/widgets/login/templates/default.html.tpl deleted file mode 100644 index 9ac6980..0000000 --- a/source/widgets/login/templates/default.html.tpl +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/source/widgets/login_internal/logic.ts b/source/widgets/login_internal/logic.ts new file mode 100644 index 0000000..5e85b46 --- /dev/null +++ b/source/widgets/login_internal/logic.ts @@ -0,0 +1,164 @@ +/* +This file is part of »dali«. + +Copyright 2025 'kcf' + +»dali« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»dali« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »dali«. If not, see . + */ + + +namespace _dali.widgets +{ + + /** + */ + export class class_widget_login_internal + implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private preparation_data : any; + + + /** + */ + private initial_name : (null | string); + + + /** + * [hook] + */ + private action_cancel : (null | (() => void)); + + + /** + * [hook] + */ + private action_success : (null | (() => void)); + + + /** + */ + public constructor( + preparation_data : any, + { + "initial_name": initial_name = null, + "action_cancel": action_cancel = null, + "action_success": action_success = null, + } + : + { + initial_name ?: (null | string); + action_cancel ?: (null | (() => void)); + action_success ?: (null | (() => void)); + } + = + { + } + ) + { + this.preparation_data = preparation_data; + this.initial_name = initial_name; + this.action_cancel = action_cancel; + this.action_success = action_success; + } + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) + : Promise + { + const form : lib_plankton.zoo_form.class_form< + {name : string; password : string;}, + {name : string; password : string;} + > = new lib_plankton.zoo_form.class_form< + {name : string; password : string;}, + {name : string; password : string;} + >( + x => x, + x => x, + new lib_plankton.zoo_input.class_input_group< + {name : string; password : string;} + >( + [ + { + "name": "name", + "input": new lib_plankton.zoo_input.class_input_text(), + "label": lib_plankton.translate.get("widget.login.internal.name"), + }, + { + "name": "password", + "input": new lib_plankton.zoo_input.class_input_password(), + "label": lib_plankton.translate.get("widget.login.internal.password"), + }, + ] + ), + ( + [] + .concat( + [ + { + "label": lib_plankton.translate.get("widget.login.internal.do"), + "procedure": async (get_value, get_representation) => { + const value : any = await get_value(); + try + { + await _dali.backend.session_begin( + value.name, + value.password + ); + if (this.action_success !== null) this.action_success(); + } + catch (error) + { + // todo + } + } + } + ] + ) + .concat( + (this.action_cancel === null) + ? + [] + : + [ + { + "label": lib_plankton.translate.get("common.cancel"), + "procedure": () => { + this.action_cancel(); + } + } + ] + ) + ) + ); + target_element.innerHTML = ""; + await form.setup(target_element); + await form.input_write( + { + "name": this.initial_name, + "password": "", + } + ); + } + + } + +} diff --git a/source/widgets/login_oidc/logic.ts b/source/widgets/login_oidc/logic.ts new file mode 100644 index 0000000..b5a4500 --- /dev/null +++ b/source/widgets/login_oidc/logic.ts @@ -0,0 +1,110 @@ +/* +This file is part of »dali«. + +Copyright 2025 'kcf' + +»dali« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»dali« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »dali«. If not, see . + */ + + +namespace _dali.widgets +{ + + /** + */ + export class class_widget_login_oidc + implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private preparation_data : any; + + + /** + * [hook] + */ + private action_cancel : (null | (() => void)); + + + /** + * [hook] + */ + private action_success : (null | (() => void)); + + + /** + */ + public constructor( + preparation_data : any, + { + "action_cancel": action_cancel = null, + "action_success": action_success = null, + } + : + { + action_cancel ?: (null | (() => void)); + action_success ?: (null | (() => void)); + } + = + { + } + ) + { + this.preparation_data = preparation_data; + this.action_cancel = action_cancel; + this.action_success = action_success; + } + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) + : Promise + { + // structure + { + target_element.innerHTML = await _dali.helpers.template_coin( + "widget-login_oidc", + "main", + { + "label": lib_plankton.string.coin( + lib_plankton.translate.get("widget.login.oidc.via"), + { + "title": this.preparation_data.label, + } + ), + "href": this.preparation_data.url, + "cancel": lib_plankton.translate.get("common.cancel"), + } + ); + } + + // controls + { + target_element.querySelector(".widget-login_oidc-cancel").addEventListener( + "click", + () => { + this.action_cancel(); + } + ); + } + } + + } + +} diff --git a/source/widgets/login_oidc/style.css b/source/widgets/login_oidc/style.css new file mode 100644 index 0000000..ab5f060 --- /dev/null +++ b/source/widgets/login_oidc/style.css @@ -0,0 +1,5 @@ +.widget-login_oidc-cancel +{ + display: block; + margin-top: 32px; +} diff --git a/source/widgets/login_oidc/templates/main.html.tpl b/source/widgets/login_oidc/templates/main.html.tpl new file mode 100644 index 0000000..57cdffb --- /dev/null +++ b/source/widgets/login_oidc/templates/main.html.tpl @@ -0,0 +1,4 @@ + diff --git a/source/widgets/menu/logic.ts b/source/widgets/menu/logic.ts index fd9d540..617a2f0 100644 --- a/source/widgets/menu/logic.ts +++ b/source/widgets/menu/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.menu +namespace _dali.widgets { /** diff --git a/source/widgets/mode_switcher/logic.ts b/source/widgets/mode_switcher/logic.ts index 691782b..6a40a29 100644 --- a/source/widgets/mode_switcher/logic.ts +++ b/source/widgets/mode_switcher/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.mode_switcher +namespace _dali.widgets { /** diff --git a/source/widgets/mode_switcher/style.css b/source/widgets/mode_switcher/style.css new file mode 100644 index 0000000..4261776 --- /dev/null +++ b/source/widgets/mode_switcher/style.css @@ -0,0 +1,4 @@ +.widget-mode_switcher-option +{ + margin-left: 16px; +} diff --git a/source/widgets/multiview/logic.ts b/source/widgets/multiview/logic.ts new file mode 100644 index 0000000..414c8d6 --- /dev/null +++ b/source/widgets/multiview/logic.ts @@ -0,0 +1,278 @@ +/* +This file is part of »dali«. + +Copyright 2025 'kcf' + +»dali« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»dali« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »dali«. If not, see . + */ + + +namespace _dali.widgets +{ + + /** + */ + type type_get_entries = ( + ( + from_pit : lib_plankton.pit.type_pit, + to_pit : lib_plankton.pit.type_pit, + calendar_ids : Array<_dali.type_calendar_id> + ) + => + Promise> + ); + + + /** + */ + type type_create_event = ( + ( + stuff ?: { + date ?: lib_plankton.pit.type_date; + } + ) + => + Promise + ); + + + /** + */ + type type_edit_event = ( + ( + event_key : _dali.type_event_key + ) + => + Promise + ); + + + /** + */ + export class class_widget_multiview + implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private initial_view_mode : _dali.enum_view_mode; + + + /** + */ + private weekview_initial_vertical : boolean; + + + /** + */ + private get_entries : type_get_entries; + + + /** + */ + private action_create_event : type_create_event; + + + /** + */ + private action_edit_event : type_edit_event; + + + /** + */ + private dom_context : (null | HTMLElement); + + + /** + */ + private widget_mode_switcher : _dali.widgets.class_widget_mode_switcher; + + + /** + */ + private widget_weekview : _dali.widgets.class_widget_weekview; + + + /** + */ + private widget_listview : _dali.widgets.class_widget_listview; + + + /** + */ + public constructor( + get_entries : type_get_entries, + { + "initial_view_mode": initial_view_mode = _dali.enum_view_mode.week, + "weekview_initial_vertical": weekview_initial_vertical = false, + "action_create_event": action_create_event = ((stuff) => Promise.resolve(undefined)), + "action_edit_event": action_edit_event = ((event_key) => Promise.resolve(undefined)), + } + : + { + initial_view_mode ?: _dali.enum_view_mode; + weekview_initial_vertical ?: boolean; + action_create_event ?: type_create_event; + action_edit_event ?: type_edit_event; + } + = + { + } + ) + { + this.get_entries = get_entries; + this.initial_view_mode = initial_view_mode; + this.weekview_initial_vertical = weekview_initial_vertical; + this.action_create_event = action_create_event; + this.action_edit_event = action_edit_event; + this.dom_context = null; + } + + + /** + */ + public toggle_calendar_visibilty( + calendar_id : _dali.type_calendar_id, + { + "mode": mode = null, + } + : + { + mode ?: (null | boolean); + } + = + { + } + ) + : void + { + this.widget_weekview.toggle_visibility(calendar_id, {"mode": mode}); + this.widget_listview.toggle_visibility(calendar_id, {"mode": mode}); + } + + + /** + */ + private set_view_mode + ( + view_mode : _dali.enum_view_mode + ) + : void + { + this.dom_context.setAttribute("rel", _dali.view_mode_encode(view_mode)); + } + + + /** + */ + public async update_entries( + ) + : + Promise + { + await Promise.all( + [ + this.widget_weekview.update_entries(), + this.widget_listview.update_entries(), + ] + ); + } + + + /** + * [implementation] + */ + public async load( + target_element : Element + ) + : Promise + { + this.dom_context = (target_element as HTMLElement); + + this.dom_context.classList.add("widget-multiview"); + + // mode switcher + { + this.widget_mode_switcher = ( + new _dali.widgets.class_widget_mode_switcher( + [ + { + "mode": _dali.enum_view_mode.week, + /** + * @todo as dependency + */ + "label": lib_plankton.translate.get("widget.overview.mode.week"), + }, + { + "mode": _dali.enum_view_mode.list, + /** + * @todo as dependency + */ + "label": lib_plankton.translate.get("widget.overview.mode.list"), + }, + ], + { + "initial_selection": this.initial_view_mode, + "action_change": (view_mode) => { + this.set_view_mode(view_mode); + } + } + ) + ); + let dom_wrapper = document.createElement("div"); + dom_wrapper.classList.add("widget-multiview-mode_switcher"); + await this.widget_mode_switcher.load(dom_wrapper); + this.dom_context.appendChild(dom_wrapper); + } + // weekview + { + this.widget_weekview = ( + new _dali.widgets.class_widget_weekview( + this.get_entries, + { + "action_select_event": (event_key) => this.action_edit_event(event_key), + "action_select_day": (date) => this.action_create_event({"date": date}), + "vertical": this.weekview_initial_vertical, + } + ) + ); + let dom_wrapper = document.createElement("div"); + dom_wrapper.classList.add("widget-multiview-weekview"); + await this.widget_weekview.load(dom_wrapper); + this.dom_context.appendChild(dom_wrapper); + } + // listview + { + this.widget_listview = ( + new _dali.widgets.class_widget_listview( + this.get_entries, + { + "action_select": (event_key) => this.action_edit_event(event_key), + "action_add": () => this.action_create_event(), + } + ) + ); + let dom_wrapper = document.createElement("div"); + dom_wrapper.classList.add("widget-multiview-listview"); + await this.widget_listview.load(dom_wrapper); + this.dom_context.appendChild(dom_wrapper); + } + this.set_view_mode(this.initial_view_mode); + + return Promise.resolve(undefined); + } + + } + +} diff --git a/source/widgets/multiview/style.css b/source/widgets/multiview/style.css new file mode 100644 index 0000000..a9fbf06 --- /dev/null +++ b/source/widgets/multiview/style.css @@ -0,0 +1,10 @@ +.widget-multiview-mode_switcher +{ + margin-bottom: 12px; +} + +.widget-multiview[rel="week"] > .widget-multiview-weekview {} +.widget-multiview[rel="week"] > .widget-multiview-listview {display: none;} + +.widget-multiview[rel="list"] > .widget-multiview-weekview {display: none;} +.widget-multiview[rel="list"] > .widget-multiview-listview {} diff --git a/source/pages/overview/logic.ts b/source/widgets/overview/logic.ts similarity index 60% rename from source/pages/overview/logic.ts rename to source/widgets/overview/logic.ts index 2266acc..a3ad008 100644 --- a/source/pages/overview/logic.ts +++ b/source/widgets/overview/logic.ts @@ -18,144 +18,98 @@ along with »dali«. If not, see . */ -namespace _dali.pages.overview +namespace _dali.widgets { /** */ - lib_plankton.zoo_page.register( - "overview", - async (parameters, target_element) => { - // params - const view_mode : _dali.enum_view_mode = _dali.helpers.view_mode_determine(parameters["mode"] ?? "auto"); - - // exec - target_element.innerHTML = await _dali.helpers.template_coin( - "overview", - "default", - { - } - ); - - let widget_mode_switcher : lib_plankton.zoo_widget.interface_widget; - let widget_sources : _dali.widgets.sources.class_widget_sources; - let widget_weekview : _dali.widgets.weekview.class_widget_weekview; - let widget_listview : _dali.widgets.listview.class_widget_listview; - - /** - * @todo ordentlich machen (nicht nur week und list) - */ - function set_view_mode - ( - view_mode : _dali.enum_view_mode - ) - : void - { - const compact : boolean = (view_mode !== _dali.enum_view_mode.week); - target_element.querySelector("#overview").classList.toggle("overview-compact", compact); + type type_action_create_calendar = ( + ( + ) + => + Promise + ); + + + /** + */ + type type_action_edit_calendar = ( + ( + type_calendar_object_reduced_with_id + ) + => + Promise + ); + + + /** + */ + type type_action_create_event = ( + ( + stuff ?: { + date ?: lib_plankton.pit.type_date; } - - /** - */ - async function get_available_calendars( - ) - : Promise< - Array< - _dali.type_calendar_object_reduced_with_id - > - > - { - return ( - (await _dali.model.calendar_list()) - /* - .filter( - (entry) => ( - (entry.access_level === _dali.enum_access_level.edit) - || - (entry.access_level === _dali.enum_access_level.admin) - ) - ) - */ - ); - } - - /** - * @todo add return type - */ - async function get_entries( - from_pit : lib_plankton.pit.type_pit, - to_pit : lib_plankton.pit.type_pit, - calendar_ids : Array<_dali.type_calendar_id> - ) - : Promise> - { - /** - * @todo do NOT wait? - */ - await _dali.model.sync_events( - { - "from": from_pit, - "to": to_pit, - }, - { - "calendar_ids": calendar_ids, - } - ); - /** - * @todo filter - */ - return _dali.model.event_list(); - } - - /** - * @todo update listview - */ - async function update_sources_and_entries( - { - "priviliged": priviliged = null, - } - : - { - priviliged ?: (null | boolean); - } - = - { - } - ) - : Promise - { - await widget_sources.update({"priviliged": priviliged}); - await widget_weekview.update_entries(); - } - - /** - * @todo use priviliged? - * @todo update listview - */ - async function update_entries( - { - "priviliged": priviliged = null, - } - : - { - priviliged ?: (null | boolean); - } - = - { - } - ) - : Promise - { - await widget_weekview.update_entries(); - } - - /** - */ - async function action_create_calendar( - ) - : Promise - { - const widget = new _dali.widgets.calendar_edit.class_widget_calendar_edit( + ) + => + Promise + ); + + + /** + */ + type type_action_edit_event = ( + ( + event_key : _dali.type_event_key + ) + => + Promise + ); + + + /** + */ + export class class_widget_overview + implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private widget_sources : _dali.widgets.class_widget_sources; + + + /** + */ + private widget_multiview : _dali.widgets.class_widget_multiview; + + + /** + */ + private action_create_calendar : type_action_create_calendar; + + + /** + */ + private action_edit_calendar : type_action_edit_calendar; + + + /** + */ + private action_create_event : type_action_create_event; + + + /** + */ + private action_edit_event : type_action_edit_event; + + + /** + */ + public constructor( + ) + { + this.action_create_calendar = async ( + ) => { + const widget = new _dali.widgets.class_widget_calendar_edit( await _dali.model.group_list(), await _dali.model.user_list(), { @@ -163,7 +117,7 @@ namespace _dali.pages.overview "hue": lib_plankton.random.generate_unit(), "access": { "public": false, - "default_level": _dali.enum_access_level.view, + "default_level": _dali.enum_access_level.none, "attributed_group": lib_plankton.map.hashmap.implementation_map< _dali.type_group_id, _dali.enum_access_level @@ -203,7 +157,7 @@ namespace _dali.pages.overview ) .then( () => { - update_sources_and_entries(); + this.update_sources_and_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -221,15 +175,10 @@ namespace _dali.pages.overview _dali.overlay.clear(); _dali.overlay.toggle({"mode": true}); await widget.load(_dali.overlay.get_content_element()); - } - - /** - */ - async function action_edit_calendar( - calendar_object_reduced_with_id : type_calendar_object_reduced_with_id - ) - : Promise - { + }; + this.action_edit_calendar = async ( + calendar_object_reduced_with_id + ) => { const read_only : boolean = (() => { switch (calendar_object_reduced_with_id.access_level) { @@ -266,7 +215,7 @@ namespace _dali.pages.overview const calendar_object : _dali.type_calendar_object = await _dali.model.calendar_get( calendar_id ); - const widget = new _dali.widgets.calendar_edit.class_widget_calendar_edit( + const widget = new _dali.widgets.class_widget_calendar_edit( await _dali.model.group_list(), await _dali.model.user_list(), calendar_object, @@ -282,7 +231,7 @@ namespace _dali.pages.overview ) .then( () => { - update_sources_and_entries(); + this.update_sources_and_entries(); _dali.overlay.toggle({"mode": false}); } ); @@ -293,7 +242,7 @@ namespace _dali.pages.overview ) .then( () => { - update_sources_and_entries(); + this.update_sources_and_entries(); _dali.overlay.toggle({"mode": false}); } ); @@ -305,11 +254,10 @@ namespace _dali.pages.overview await widget.load(_dali.overlay.get_content_element()); } } - /** * @todo unterschiedliches Verhalten bei Anmeldung? */ - async function action_create_event( + this.action_create_event = async ( { "date": date = lib_plankton.pit.to_datetime(lib_plankton.pit.now()).date, } @@ -320,17 +268,15 @@ namespace _dali.pages.overview = { } - ) - : Promise - { + ) => { if (! _dali.is_logged_in()) { // do nothing } else { - const widget = new _dali.widgets.event_edit.class_widget_event_edit( - (await get_available_calendars()), + const widget = new _dali.widgets.class_widget_event_edit( + (await this.get_available_calendars()), { "calendar_id": null, "event_name": "", @@ -383,7 +329,7 @@ namespace _dali.pages.overview ) .then( () => { - update_entries(); + this.update_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -400,15 +346,11 @@ namespace _dali.pages.overview await widget.load(_dali.overlay.get_content_element()); } } - /** */ - async function action_edit_event - ( - event_key : _dali.type_event_key - ) - : Promise - { + this.action_edit_event = async ( + event_key + ) => { const event_object_extended : _dali.type_event_object_extended = await _dali.model.event_get(event_key); const read_only : boolean = (() => { switch (event_object_extended.access_level) @@ -431,8 +373,8 @@ namespace _dali.pages.overview } } }) (); - const widget = new _dali.widgets.event_edit.class_widget_event_edit( - (await get_available_calendars()), + const widget = new _dali.widgets.class_widget_event_edit( + (await this.get_available_calendars()), { "calendar_id": event_object_extended.calendar_id, "event_name": event_object_extended.event_object.name, @@ -461,7 +403,7 @@ namespace _dali.pages.overview ) .then( () => { - update_entries(); + this.update_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -480,7 +422,7 @@ namespace _dali.pages.overview ) .then( () => { - update_entries(); + this.update_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -495,105 +437,212 @@ namespace _dali.pages.overview }, } ); - _dali.overlay.clear(); _dali.overlay.toggle({"mode": true}); await widget.load(_dali.overlay.get_content_element()); } - - // hint + } + + + /** + */ + private async get_available_calendars( + ) + : Promise< + Array< + _dali.type_calendar_object_reduced_with_id + > + > + { + return ( + (await _dali.model.calendar_list()) + /* + .filter( + (entry) => ( + (entry.access_level === _dali.enum_access_level.edit) + || + (entry.access_level === _dali.enum_access_level.admin) + ) + ) + */ + ); + } + + + /** + */ + private async get_entries( + from_pit : lib_plankton.pit.type_pit, + to_pit : lib_plankton.pit.type_pit, + calendar_ids : Array<_dali.type_calendar_id> + ) + : Promise> + { + /** + * @todo do NOT wait? + */ + await _dali.model.sync_events( + { + "from": from_pit, + "to": to_pit, + }, + { + "calendar_ids": calendar_ids, + } + ); + /** + * @todo filter + */ + return _dali.model.event_list(); + } + + + /** + */ + private async update_sources_and_entries( { - const dom_hint = target_element.querySelector("#overview-hint"); - dom_hint.textContent = lib_plankton.translate.get("page.overview.login_hint"); - dom_hint.classList.toggle("overview-hint-hidden", _dali.is_logged_in()); + "priviliged": priviliged = null, } - - // mode switcher + : { - widget_mode_switcher = new _dali.widgets.mode_switcher.class_widget_mode_switcher( - [ - { - "mode": _dali.enum_view_mode.week, - "label": lib_plankton.translate.get("page.overview.mode.week"), - }, - { - "mode": _dali.enum_view_mode.list, - "label": lib_plankton.translate.get("page.overview.mode.list"), - }, - ], - { - "initial_selection": view_mode, - "action_change": (view_mode) => { - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": { - "mode": _dali.view_mode_encode(view_mode), - } - } - ); - set_view_mode(view_mode); - } - } - ); - set_view_mode(view_mode); - await widget_mode_switcher.load(target_element.querySelector("#overview-mode")); + priviliged ?: (null | boolean); } - - // sources + = { - widget_sources = new _dali.widgets.sources.class_widget_sources( + } + ) + : Promise + { + await this.widget_sources.update({"priviliged": priviliged}); + await this.widget_multiview.update_entries(); + } + + + /** + * @todo use priviliged? + */ + private async update_entries( + { + "priviliged": priviliged = null, + } + : + { + priviliged ?: (null | boolean); + } + = + { + } + ) + : Promise + { + await this.widget_multiview.update_entries(); + } + + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) + : Promise + { + const view_mode : _dali.enum_view_mode = _dali.helpers.view_mode_determine("auto"); + const view_kind : _dali.enum_view_kind = _dali.helpers.view_kind_determine("auto"); + + this.widget_sources = ( + new _dali.widgets.class_widget_sources( _dali.model.calendar_list, { "initial_priviliged": _dali.is_logged_in(), - "action_add": action_create_calendar, - "action_select": (entry) => action_edit_calendar(entry), + "action_add": this.action_create_calendar, + "action_select": this.action_edit_calendar, "action_toggle": (entry, mode) => { - widget_weekview.toggle_visibility(entry.id, {"mode": mode}); - widget_listview.toggle_visibility(entry.id, {"mode": mode}); + this.widget_multiview.toggle_calendar_visibilty(entry.id, {"mode": mode}); }, } - ); - await widget_sources.load(target_element.querySelector("#overview-pane-left")); - } + ) + ); + this.widget_multiview = ( + new _dali.widgets.class_widget_multiview( + this.get_entries, + { + "initial_view_mode": view_mode, + "weekview_initial_vertical": (view_kind === _dali.enum_view_kind.touch), + "action_create_event": this.action_create_event, + "action_edit_event": this.action_edit_event, + } + ) + ); - // weekview + target_element.innerHTML = await _dali.helpers.template_coin( + "widget-overview", + "main", + { + } + ); + + // head { - widget_weekview = ( - new _dali.widgets.weekview.class_widget_weekview( - get_entries, - { - "action_select_event": (event_key) => action_edit_event(event_key), - "action_select_day": (date) => action_create_event({"date": date}), - } - ) - ); - await widget_weekview.load(target_element.querySelector("#overview-pane-right-weekview")); + // hint + { + const dom_hint = target_element.querySelector(".widget-overview-hint"); + dom_hint.textContent = lib_plankton.translate.get("widget.overview.login_hint"); + dom_hint.classList.toggle("widget-overview-hint-hidden", _dali.is_logged_in()); + } } - - // listview + // body { - widget_listview = ( - new _dali.widgets.listview.class_widget_listview( - get_entries, - { - "action_select": (event_key) => action_edit_event(event_key), - "action_add": () => action_create_event(), - } - ) - ); - await widget_listview.load(target_element.querySelector("#overview-pane-right-listview")); + let widget_slider : lib_plankton.zoo_widget.interface_widget; + switch (view_kind) + { + case _dali.enum_view_kind.regular: + { + widget_slider = new lib_plankton.zoo_widget.class_slider( + [ + new lib_plankton.zoo_widget.class_bunch( + [ + this.widget_sources, + this.widget_multiview, + ] + ), + ], + { + "threshold": 100, + "initial_index": 1, + } + ); + break; + } + case _dali.enum_view_kind.touch: + { + widget_slider = new lib_plankton.zoo_widget.class_slider( + [ + this.widget_sources, + this.widget_multiview, + ], + { + "threshold": 100, + "initial_index": 2, + } + ); + break; + } + } + await widget_slider.load(target_element.querySelector(".widget-overview-body")); } _dali.model.listen_reset( async (priviliged) => { - update_sources_and_entries({"priviliged": priviliged}); - target_element.querySelector("#overview-hint").classList.toggle("overview-hint-hidden", priviliged); + this.update_sources_and_entries({"priviliged": priviliged}); + target_element.querySelector(".widget-overview-hint").classList.toggle("widget-overview-hint-hidden", priviliged); } ); return Promise.resolve(undefined); - }, - ); - + } + + } + } diff --git a/source/widgets/overview/style.css b/source/widgets/overview/style.css new file mode 100644 index 0000000..813ec16 --- /dev/null +++ b/source/widgets/overview/style.css @@ -0,0 +1,59 @@ +.widget-overview-head +{ + padding-bottom: 12px; + margin-bottom: 12px; + border-bottom: 1px solid; +} + +.widget-overview-hint +{ + text-align: center; + font-weight: bold; +} + +.widget-overview-hint.widget-overview-hint-hidden +{ + display: none; +} + +.widget-overview-body +{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.widget-overview-pane-left +{ + flex-basis: 12.5%; + flex-grow: 0; + flex-shrink: 1; +} + +.widget-overview-pane-right +{ + flex-basis: 87.5%; + flex-grow: 1; + flex-shrink: 1; +} + +.widget-overview-body .widget-slider-slide .widget-bunch +{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} + +.widget-overview-body .widget-slider-slide .widget-bunch-element:nth-child(1) +{ + flex-grow: 0; + flex-shrink: 1; + flex-basis: 12.5%; +} + +.widget-overview-body .widget-slider-slide .widget-bunch-element:nth-child(2) +{ + flex-grow: 1; + flex-shrink: 1; + flex-basis: 87.5%; +} diff --git a/source/widgets/overview/templates/main.html.tpl b/source/widgets/overview/templates/main.html.tpl new file mode 100644 index 0000000..d4d3054 --- /dev/null +++ b/source/widgets/overview/templates/main.html.tpl @@ -0,0 +1,8 @@ +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/source/widgets/sources/logic.ts b/source/widgets/sources/logic.ts index 04bbc3b..355927a 100644 --- a/source/widgets/sources/logic.ts +++ b/source/widgets/sources/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.sources +namespace _dali.widgets { /** diff --git a/source/widgets/special_number_input/logic.ts b/source/widgets/special_number_input/logic.ts new file mode 100644 index 0000000..d593f89 --- /dev/null +++ b/source/widgets/special_number_input/logic.ts @@ -0,0 +1,164 @@ +/* +This file is part of »dali«. + +Copyright 2025 'kcf' + +»dali« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»dali« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »dali«. If not, see . + */ + + +namespace _dali.widgets +{ + + /** + */ + export class class_widget_special_number_input + implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private action_change : ((int) => Promise); + + + /** + */ + private label : (null | string); + + + /** + */ + private minimum : (null | int); + + + /** + */ + private maximum : (null | int); + + + /** + */ + private value : int; + + + /** + */ + public constructor( + { + "label": label = null, + "minimum": minimum = null, + "maximum": maximum = null, + "initial_value": initial_value = 0, + "action_change": action_change = ((value) => Promise.resolve(undefined)), + } + : + { + label ?: (null | string); + minimum ?: (null | int); + maximum ?: (null | int); + initial_value ?: int; + action_change ?: ((int) => Promise); + } + = + { + } + ) + { + this.label = label; + this.minimum = minimum; + this.maximum = maximum; + this.value = initial_value; + this.action_change = action_change; + } + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) + : Promise + { + const dom_dummy = document.createElement("div"); + dom_dummy.innerHTML = await _dali.helpers.template_coin( + "widget-special_number_input", + "main", + { + "label": this.label, + } + ); + + const dom_input : HTMLInputElement = (dom_dummy.querySelector(".widget-special_number_input-input") as HTMLInputElement); + + // listeners + { + dom_input.addEventListener( + "change", + () => { + this.value = parseInt(dom_input.value); + if ( + ((this.minimum === null) || (this.value >= this.minimum)) + && + ((this.maximum === null) || (this.value <= this.maximum)) + ) + { + this.action_change(this.value); + } + else + { + // do nothing + } + } + ); + dom_dummy.querySelector(".widget-special_number_input-prev").addEventListener( + "click", + () => { + if ((this.minimum === null) || (this.value > this.minimum)) + { + this.value -= 1; + dom_input.value = this.value.toFixed(0); + this.action_change(this.value); + } + else + { + // do nothing + } + } + ); + dom_dummy.querySelector(".widget-special_number_input-next").addEventListener( + "click", + () => { + if ((this.maximum === null) || (this.value < this.maximum)) + { + this.value += 1; + dom_input.value = this.value.toFixed(0); + this.action_change(this.value); + } + else + { + // do nothing + } + } + ); + } + + dom_input.value = this.value.toFixed(0); + + target_element.appendChild(dom_dummy.querySelector(".widget-special_number_input")); + } + + } + +} diff --git a/source/widgets/special_number_input/style.css b/source/widgets/special_number_input/style.css new file mode 100644 index 0000000..a76ee8d --- /dev/null +++ b/source/widgets/special_number_input/style.css @@ -0,0 +1,34 @@ +.widget-special_number_input-controls +{ + white-space: nowrap; +} + +.widget-special_number_input-controls > * +{ + display: inline-block; +} + +.widget-special_number_input-button +{ + padding: 4px 4px; + margin: 0; + + cursor: pointer; +} + +.widget-special_number_input-label +{ + display: block; + + font-size: 0.75em; + /* + text-transform: uppercase; + */ +} + +.widget-special_number_input-input +{ + max-width: 40px; + margin: 0; + text-align: center; +} diff --git a/source/widgets/special_number_input/templates/main.html.tpl b/source/widgets/special_number_input/templates/main.html.tpl new file mode 100644 index 0000000..f8590b6 --- /dev/null +++ b/source/widgets/special_number_input/templates/main.html.tpl @@ -0,0 +1,8 @@ +
    + +
    +
    + +
    +
    +
    diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index fec5824..ac939de 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -18,7 +18,7 @@ along with »dali«. If not, see . */ -namespace _dali.widgets.weekview +namespace _dali.widgets { /** @@ -36,7 +36,8 @@ namespace _dali.widgets.weekview /** */ - export class class_widget_weekview implements lib_plankton.zoo_widget.interface_widget + export class class_widget_weekview + implements lib_plankton.zoo_widget.interface_widget { /** @@ -191,7 +192,8 @@ namespace _dali.widgets.weekview */ private static entry_hash( entry : _dali.type_event_object_extended - ) : string + ) + : string { return lib_plankton.call.convey( { @@ -213,7 +215,8 @@ namespace _dali.widgets.weekview private static event_generate_tooltip( calendar_name : string, event_object : _dali.type_event_object - ) : string + ) + : string { return ( lib_plankton.string.coin( @@ -359,7 +362,8 @@ namespace _dali.widgets.weekview */ private async entry_insert( entry : _dali.type_event_object_extended - ) : Promise<(null | HTMLElement)> + ) + : Promise<(null | HTMLElement)> { const selector : string = lib_plankton.string.coin( ".weekview-cell[rel=\"{{rel}}\"] > .weekview-events", @@ -422,7 +426,8 @@ namespace _dali.widgets.weekview */ private async entry_add( entry : _dali.type_event_object_extended - ) : Promise + ) + : Promise { const dom_entry : (null | HTMLElement) = await this.entry_insert(entry); if (dom_entry === null) @@ -447,7 +452,8 @@ namespace _dali.widgets.weekview private async entry_update( key : _dali.type_event_key, entry : _dali.type_event_object_extended - ) : Promise + ) + : Promise { if (! this.event_map.has(key)) { @@ -503,7 +509,8 @@ namespace _dali.widgets.weekview */ private async entry_remove( key : _dali.type_event_key - ) : Promise + ) + : Promise { if (! this.event_map.has(key)) { @@ -532,7 +539,8 @@ namespace _dali.widgets.weekview /** */ public async update_entries( - ) : Promise + ) + : Promise { const entries : Array<_dali.type_event_object_extended> = await this.get_entries_wrapped( ); @@ -570,23 +578,11 @@ namespace _dali.widgets.weekview } - /** - */ - private async update_controls( - ) : Promise - { - const context : Element = this.container; - (context.querySelector(".weekview-control-year input") as HTMLInputElement).value = this.year.toFixed(0); - (context.querySelector(".weekview-control-week input") as HTMLInputElement).value = this.week.toFixed(0); - (context.querySelector(".weekview-control-count input") as HTMLInputElement).value = this.count.toFixed(0); - (context.querySelector(".weekview-control-vertical input") as HTMLInputElement).checked = this.vertical; - } - - /** */ private async update_table( - ) : Promise + ) + : Promise { /** * @todo avoid? @@ -846,7 +842,8 @@ namespace _dali.widgets.weekview = { } - ) : void + ) + : void { this.container.querySelectorAll(".weekview-event_entry").forEach( (element) => { @@ -874,175 +871,98 @@ namespace _dali.widgets.weekview */ public async load( target_element : Element - ) : Promise + ) + : Promise { target_element.innerHTML = await _dali.helpers.template_coin( "widget-weekview", "main", { - "label_control_year": lib_plankton.translate.get("widget.weekview.controls.year"), - "label_control_week": lib_plankton.translate.get("widget.weekview.controls.week"), - "label_control_count": lib_plankton.translate.get("widget.weekview.controls.count"), - "label_control_vertical": lib_plankton.translate.get("widget.weekview.controls.vertical"), - "label_control_apply": lib_plankton.translate.get("widget.weekview.controls.apply"), } ); - this.container = target_element.querySelector(".weekview"); - // controls + // control:year { - // direct inputs - { - [ - { - "name": "year", - "selector": ".weekview-control-year input", - "retrieve": element => parseInt(element.value), - "write": x => {this.year = x;} - }, - { - "name": "week", - "selector": ".weekview-control-week input", - "retrieve": element => parseInt(element.value), - "write": x => {this.week = x;} - }, - { - "name": "count", - "selector": ".weekview-control-count input", - "retrieve": element => parseInt(element.value), - "write": x => {this.count = x;} - }, - { - "name": "vertical", - "selector": ".weekview-control-vertical input", - "retrieve": element => element.checked, - "write": x => {this.vertical = x;} - }, - ].forEach( - (entry) => { - const element : HTMLInputElement = (target_element.querySelector(entry.selector) as HTMLInputElement); - element.addEventListener( - "change", - async (event) => { - event.preventDefault(); - const value : unknown = entry.retrieve(element); - entry.write(value); - await this.update_table(); - await this.update_entries(); - } - ); - } - ); - } - // buttons - { - // year + const widget : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.class_widget_special_number_input( { - /** - * @todo limit - */ - target_element.querySelector(".weekview-control-year-prev").addEventListener( - "click", - async () => { - this.year -= 1; - await this.update_controls(); - await this.update_table(); - await this.update_entries(); - } - ); - /** - * @todo limit - */ - target_element.querySelector(".weekview-control-year-next").addEventListener( - "click", - async () => { - this.year += 1; - await this.update_controls(); - await this.update_table(); - await this.update_entries(); - } - ); + "label": lib_plankton.translate.get("widget.weekview.controls.year"), + "minimum": 1900, + "maximum": 2500, + "initial_value": this.year, + "action_change": async (value) => { + this.year = value; + await this.update_table(); + await this.update_entries(); + }, } - // week - { - target_element.querySelector(".weekview-control-week-prev").addEventListener( - "click", - async () => { - if (this.week >= 1) - { - this.week -= 1; - } - else - { - this.year -= 1; - /** - * @todo get correct week - */ - this.week = 51; - } - await this.update_controls(); - await this.update_table(); - await this.update_entries(); - } - ); - target_element.querySelector(".weekview-control-week-next").addEventListener( - "click", - async () => { - /** - * @todo correct limit - */ - if (this.week <= 51) - { - this.week += 1; - } - else - { - this.year += 1; - this.week = 1; - } - await this.update_controls(); - await this.update_table(); - await this.update_entries(); - } - ); - } - // count - { - target_element.querySelector(".weekview-control-count-prev").addEventListener( - "click", - async () => { - if (this.count >= 2) - { - this.count -= 1; - await this.update_controls(); - await this.update_table(); - await this.update_entries(); - } - else - { - // do nothing - } - } - ); - target_element.querySelector(".weekview-control-count-next").addEventListener( - "click", - async () => { - if (this.count <= 6) - { - this.count += 1; - await this.update_controls(); - await this.update_table(); - await this.update_entries(); - } - else - { - // do nothing - } - } - ); - } - } + ); + await widget.load(target_element.querySelector(".weekview-controls")); } - await this.update_controls(); + // control:week + { + const widget : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.class_widget_special_number_input( + { + "label": lib_plankton.translate.get("widget.weekview.controls.week"), + "minimum": 1, + /** + * @todo correct + */ + "maximum": 53, + "initial_value": this.week, + "action_change": async (value) => { + this.week = value; + await this.update_table(); + await this.update_entries(); + }, + } + ); + await widget.load(target_element.querySelector(".weekview-controls")); + } + // control:count + /* + { + const widget : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.class_widget_special_number_input( + { + "label": lib_plankton.translate.get("widget.weekview.controls.count"), + "minimum": 2, + "maximum": 6, + "initial_value": this.count, + "action_change": async (value) => { + this.count = value; + await this.update_table(); + await this.update_entries(); + }, + } + ); + await widget.load(target_element.querySelector(".weekview-controls")); + } + */ + // control:vertical + { + const dom_dummy : HTMLElement = document.createElement("div"); + dom_dummy.innerHTML = await _dali.helpers.template_coin( + "widget-weekview", + "control-vertical", + { + "label": lib_plankton.translate.get("widget.weekview.controls.vertical"), + } + ); + const dom_input : HTMLInputElement = (dom_dummy.querySelector("input") as HTMLInputElement); + dom_input.addEventListener( + "change", + async () => { + event.preventDefault(); + const value : boolean = dom_input.checked; + this.vertical = value; + await this.update_table(); + await this.update_entries(); + } + ); + dom_input.checked = this.vertical; + target_element.querySelector(".weekview-controls").appendChild(dom_dummy.querySelector(".weekview-control-vertical")); + } + + this.container = target_element.querySelector(".weekview"); + await this.update_table(); await this.update_entries(); diff --git a/source/widgets/weekview/style.css b/source/widgets/weekview/style.css index 93b07bc..a5b88d2 100644 --- a/source/widgets/weekview/style.css +++ b/source/widgets/weekview/style.css @@ -2,7 +2,7 @@ { display: flex; flex-direction: row; - flex-wrap: wrap; + flex-wrap: nowrap; justify-content: right; margin-bottom: 12px; @@ -10,14 +10,13 @@ text-align: center; } -.weekview-control +.weekview-controls > * { flex-basis: 0; flex-grow: 0; flex-shrink: 1; - min-width: 140px; - margin: 0 12px; + margin: 0 0 0 16px; } .weekview-control-label @@ -30,16 +29,6 @@ */ } -.weekview-control-input > * -{ - display: inline-block; -} - -.weekview-control input -{ - max-width: 40px; -} - .weekview-table table { width: 100%; diff --git a/source/widgets/weekview/templates/control-vertical.html.tpl b/source/widgets/weekview/templates/control-vertical.html.tpl new file mode 100644 index 0000000..b19eb83 --- /dev/null +++ b/source/widgets/weekview/templates/control-vertical.html.tpl @@ -0,0 +1,6 @@ +
    + +
    + +
    +
    diff --git a/source/widgets/weekview/templates/main.html.tpl b/source/widgets/weekview/templates/main.html.tpl index 7d4be90..0bf066d 100644 --- a/source/widgets/weekview/templates/main.html.tpl +++ b/source/widgets/weekview/templates/main.html.tpl @@ -1,35 +1,5 @@
    -
    - -
    - - - -
    -
    -
    - -
    - - - -
    -
    -
    - -
    - - - -
    -
    -
    - -
    - -
    -
    diff --git a/tools/makefile b/tools/makefile index 42319cc..c2b20c8 100644 --- a/tools/makefile +++ b/tools/makefile @@ -30,20 +30,28 @@ ${dir_build}/index.html: \ .PHONY: templates templates: \ - templates-widgets-login \ + templates-widgets-special_number_input \ + templates-widgets-login_oidc \ templates-widgets-sources \ + templates-widgets-caldav \ templates-widgets-listview \ templates-widgets-weekview \ - templates-widgets-mode_switcher \ - templates-pages-caldav \ - templates-pages-overview + templates-widgets-overview \ + templates-widgets-mode_switcher -.PHONY: templates-widgets-login -templates-widgets-login: \ - $(wildcard ${dir_source}/widgets/login/templates/*) - @ ${cmd_log} "templates:widget:login …" - @ ${cmd_mkdir} ${dir_build}/templates/widget-login - @ ${cmd_cp} -r -u -v ${dir_source}/widgets/login/templates/* ${dir_build}/templates/widget-login/ +.PHONY: templates-widgets-special_number_input +templates-widgets-special_number_input: \ + $(wildcard ${dir_source}/widgets/special_number_input/templates/*) + @ ${cmd_log} "templates:widget:special_number_input …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-special_number_input + @ ${cmd_cp} -r -u -v ${dir_source}/widgets/special_number_input/templates/* ${dir_build}/templates/widget-special_number_input/ + +.PHONY: templates-widgets-login_oidc +templates-widgets-login_oidc: \ + $(wildcard ${dir_source}/widgets/login_oidc/templates/*) + @ ${cmd_log} "templates:widget:login_oidc …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-login_oidc + @ ${cmd_cp} -r -u -v ${dir_source}/widgets/login_oidc/templates/* ${dir_build}/templates/widget-login_oidc/ .PHONY: templates-widgets-sources templates-widgets-sources: \ @@ -52,6 +60,13 @@ templates-widgets-sources: \ @ ${cmd_mkdir} ${dir_build}/templates/widget-sources @ ${cmd_cp} -r -u -v ${dir_source}/widgets/sources/templates/* ${dir_build}/templates/widget-sources/ +.PHONY: templates-widgets-caldav +templates-widgets-caldav: \ + $(wildcard ${dir_source}/widgets/caldav/templates/*) + @ ${cmd_log} "templates:widget:caldav …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-caldav + @ ${cmd_cp} -r -u -v ${dir_source}/widgets/caldav/templates/* ${dir_build}/templates/widget-caldav/ + .PHONY: templates-widgets-listview templates-widgets-listview: \ $(wildcard ${dir_source}/widgets/listview/templates/*) @@ -66,6 +81,13 @@ templates-widgets-weekview: \ @ ${cmd_mkdir} ${dir_build}/templates/widget-weekview @ ${cmd_cp} -r -u -v ${dir_source}/widgets/weekview/templates/* ${dir_build}/templates/widget-weekview/ +.PHONY: templates-widgets-overview +templates-widgets-overview: \ + $(wildcard ${dir_source}/widgets/overview/templates/*) + @ ${cmd_log} "templates:widget:overview …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-overview + @ ${cmd_cp} -r -u -v ${dir_source}/widgets/overview/templates/* ${dir_build}/templates/widget-overview/ + .PHONY: templates-widgets-mode_switcher templates-widgets-mode_switcher: \ $(wildcard ${dir_source}/widgets/mode_switcher/templates/*) @@ -73,27 +95,12 @@ templates-widgets-mode_switcher: \ @ ${cmd_mkdir} ${dir_build}/templates/widget-mode_switcher @ ${cmd_cp} -r -u -v ${dir_source}/widgets/mode_switcher/templates/* ${dir_build}/templates/widget-mode_switcher/ -.PHONY: templates-pages-caldav -templates-pages-caldav: \ - $(wildcard ${dir_source}/pages/caldav/templates/*) - @ ${cmd_log} "templates:page:caldav …" - @ ${cmd_mkdir} ${dir_build}/templates/caldav - @ ${cmd_cp} -r -u -v ${dir_source}/pages/caldav/templates/* ${dir_build}/templates/caldav/ - -.PHONY: templates-pages-overview -templates-pages-overview: \ - $(wildcard ${dir_source}/pages/overview/templates/*) - @ ${cmd_log} "templates:page:overview …" - @ ${cmd_mkdir} ${dir_build}/templates/overview - @ ${cmd_cp} -r -u -v ${dir_source}/pages/overview/templates/* ${dir_build}/templates/overview/ - .PHONY: style style: ${dir_build}/style.css ${dir_build}/style.css: \ $(wildcard ${dir_source}/style/*.css) \ - $(wildcard ${dir_source}/widgets/*/style.css) \ - $(wildcard ${dir_source}/pages/*/style.css) + $(wildcard ${dir_source}/widgets/*/style.css) @ ${cmd_log} "style …" @ ${cmd_mkdir} ${dir_build} @ ${cmd_cat} $^ > $@ @@ -108,10 +115,9 @@ ${dir_temp}/logic-unlinked.js: \ ${dir_source}/base.ts \ ${dir_source}/types.ts \ ${dir_source}/model.ts \ - ${dir_source}/helpers.ts \ ${dir_source}/overlay.ts \ + ${dir_source}/helpers.ts \ $(wildcard ${dir_source}/widgets/*/logic.ts) \ - $(wildcard ${dir_source}/pages/*/logic.ts) \ ${dir_source}/main.ts @ ${cmd_log} "logic | compile …" @ ${cmd_mkdir} $(dir $@)