From dc6abdc2e0c3e771fc9e8596dc8b1604b8a16517 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Mon, 13 Oct 2025 13:29:19 +0200 Subject: [PATCH 01/11] [task-408] Widgets von Plankton --- lib/plankton/plankton.d.ts | 98 +++---- lib/plankton/plankton.js | 368 +++++++++++++------------- source/base/widget.ts | 18 -- source/pages/overview/logic.ts | 2 +- source/widgets/listview/logic.ts | 3 +- source/widgets/mode_switcher/logic.ts | 3 +- source/widgets/sources/logic.ts | 3 +- source/widgets/weekview/logic.ts | 3 +- tools/makefile | 1 - tools/update-plankton | 1 + 10 files changed, 239 insertions(+), 261 deletions(-) delete mode 100644 source/base/widget.ts diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index 91457ef..3302bcf 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -3809,55 +3809,6 @@ declare namespace lib_plankton.translate { */ function stance(str: string): string; } -declare namespace lib_plankton.zoo_page { - /** - */ - export type type_location = { - name: string; - parameters: Record; - }; - /** - */ - type type_handler = ((parameters: Record, target_element: Element) => void); - /** - */ - type type_nav_entry_definition = { - location: type_location; - label: string; - groups: Array; - }; - /** - */ - export let _pool: Record; - /** - */ - export function encode(location: type_location): string; - /** - * encodes a location in the URL and loads it - */ - export function set(location: type_location): void; - /** - */ - export function reload(): Promise; - /** - */ - export function register(location_name: string, handler: type_handler): void; - /** - */ - export function nav_set_groups(groups: (null | Array)): void; - /** - */ - export function init(target_element: Element, { "pool": pool, "fallback": fallback, "nav_entries": nav_entries, "nav_initial_groups": nav_initial_groups, }?: { - pool?: Record; - fallback?: (null | type_location); - nav_entries?: Array; - nav_initial_groups?: (null | Array); - }): void; - /** - */ - export function start(): void; - export {}; -} declare namespace lib_plankton.zoo_widget { /** */ @@ -4043,6 +3994,55 @@ declare namespace lib_plankton.zoo_widget { load(target_element: HTMLElement): Promise; } } +declare namespace lib_plankton.zoo_page { + /** + */ + export type type_location = { + name: string; + parameters: Record; + }; + /** + */ + type type_handler = ((parameters: Record, target_element: Element) => void); + /** + */ + type type_nav_entry_definition = { + location: type_location; + label: string; + groups: Array; + }; + /** + */ + export let _pool: Record; + /** + */ + export function encode(location: type_location): string; + /** + * encodes a location in the URL and loads it + */ + export function set(location: type_location): void; + /** + */ + export function reload(): Promise; + /** + */ + export function register(location_name: string, handler: type_handler): void; + /** + */ + export function nav_set_groups(groups: (null | Array)): void; + /** + */ + export function init(target_element: Element, { "pool": pool, "fallback": fallback, "nav_entries": nav_entries, "nav_initial_groups": nav_initial_groups, }?: { + pool?: Record; + fallback?: (null | type_location); + nav_entries?: Array; + nav_initial_groups?: (null | Array); + }): void; + /** + */ + export function start(): void; + export {}; +} declare namespace lib_plankton.zoo_input { /** * @author fenris diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index 6ff86ef..64e504d 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -11738,190 +11738,6 @@ var lib_plankton; })(translate = lib_plankton.translate || (lib_plankton.translate = {})); })(lib_plankton || (lib_plankton = {})); /* -This file is part of »bacterio-plankton:zoo-page«. - -Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' - - -»bacterio-plankton:zoo-page« 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-page« 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-page«. If not, see . - */ -var lib_plankton; -(function (lib_plankton) { - var zoo_page; - (function (zoo_page) { - /** - */ - zoo_page._pool = {}; - /** - */ - let _fallback = null; - /** - */ - let _current = null; - /** - */ - let _target_element = null; - /** - */ - let _nav_entries; - /** - */ - function encode(location) { - return ("#" - + - ([location.name] - .concat(Object.entries(location.parameters) - .filter(([key, value]) => (value !== null)) - .map(([key, value]) => (key + "=" + value))))); - } - zoo_page.encode = encode; - /** - */ - function decode(encoded) { - if (encoded === "") { - return null; - } - else { - if (!encoded.startsWith("#")) { - return null; - } - else { - const parts = encoded.slice(1).split(","); - return { - "name": parts[0], - "parameters": Object.fromEntries(parts.slice(1) - .map(part => { - const parts_ = part.split("="); - return [parts_[0], parts_[1]]; - })), - }; - } - } - } - /** - * renders a page to the main element - */ - async function load(location) { - // _target_element.innerHTML = "[loading …]"; - _target_element.innerHTML = ""; - if (location === null) { - // do nothing - } - else { - if (!(location.name in zoo_page._pool)) { - _target_element.innerHTML = "not found"; - } - else { - await zoo_page._pool[location.name](location.parameters, _target_element); - _current = location; - } - } - } - /** - * retrieves the location from the set URL - */ - function get() { - return decode(window.location.hash); - } - /** - * encodes a location in the URL and loads it - */ - function set(location) { - window.location.hash = encode(location); - } - zoo_page.set = set; - /** - */ - function reload() { - return load(get()); - } - zoo_page.reload = reload; - /** - */ - function register(location_name, handler) { - zoo_page._pool[location_name] = handler; - } - zoo_page.register = register; - /** - */ - function nav_set_groups(groups) { - _nav_entries.forEach(nav_entry => { - const active = ((groups === null) - || - groups.some(group => nav_entry.definition.groups.includes(group))); - nav_entry.element.classList.toggle("active", active); - }); - } - zoo_page.nav_set_groups = nav_set_groups; - /** - */ - function init(target_element, { "pool": pool = {}, "fallback": fallback = null, "nav_entries": nav_entries = [], "nav_initial_groups": nav_initial_groups = null, } = {}) { - _target_element = target_element; - _fallback = fallback; - Object.entries(pool).forEach(([location_name, handler]) => { - register(location_name, handler); - }); - window.addEventListener("hashchange", () => { - const location_old = _current; - const location_new = (get() ?? _fallback); - if (((location_old === null) - && - (location_new !== null)) - || - ((location_old !== null) - && - (location_new !== null) - && - (location_old.name !== location_new.name))) { - load(location_new); - } - else { - // do nothing - } - }); - // nav - { - let ul_element = document.querySelector("nav > ul"); - _nav_entries = nav_entries.map(nav_entry_definition => { - let li_element = document.createElement("li"); - { - let a_element = document.createElement("a"); - a_element.setAttribute("href", encode(nav_entry_definition.location)); - a_element.textContent = (nav_entry_definition.label ?? nav_entry_definition.location.name); - li_element.appendChild(a_element); - } - ul_element.appendChild(li_element); - return { - "definition": nav_entry_definition, - "element": li_element, - }; - }); - nav_set_groups(nav_initial_groups); - } - } - zoo_page.init = init; - /** - */ - function start() { - const location = (get() ?? _fallback); - set(location); - load(location); - } - zoo_page.start = start; - })(zoo_page = lib_plankton.zoo_page || (lib_plankton.zoo_page = {})); -})(lib_plankton || (lib_plankton = {})); -/* This file is part of »bacterio-plankton:zoo-widget«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' @@ -12441,6 +12257,190 @@ 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-page«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:zoo-page« 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-page« 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-page«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var zoo_page; + (function (zoo_page) { + /** + */ + zoo_page._pool = {}; + /** + */ + let _fallback = null; + /** + */ + let _current = null; + /** + */ + let _target_element = null; + /** + */ + let _nav_entries; + /** + */ + function encode(location) { + return ("#" + + + ([location.name] + .concat(Object.entries(location.parameters) + .filter(([key, value]) => (value !== null)) + .map(([key, value]) => (key + "=" + value))))); + } + zoo_page.encode = encode; + /** + */ + function decode(encoded) { + if (encoded === "") { + return null; + } + else { + if (!encoded.startsWith("#")) { + return null; + } + else { + const parts = encoded.slice(1).split(","); + return { + "name": parts[0], + "parameters": Object.fromEntries(parts.slice(1) + .map(part => { + const parts_ = part.split("="); + return [parts_[0], parts_[1]]; + })), + }; + } + } + } + /** + * renders a page to the main element + */ + async function load(location) { + // _target_element.innerHTML = "[loading …]"; + _target_element.innerHTML = ""; + if (location === null) { + // do nothing + } + else { + if (!(location.name in zoo_page._pool)) { + _target_element.innerHTML = "not found"; + } + else { + await zoo_page._pool[location.name](location.parameters, _target_element); + _current = location; + } + } + } + /** + * retrieves the location from the set URL + */ + function get() { + return decode(window.location.hash); + } + /** + * encodes a location in the URL and loads it + */ + function set(location) { + window.location.hash = encode(location); + } + zoo_page.set = set; + /** + */ + function reload() { + return load(get()); + } + zoo_page.reload = reload; + /** + */ + function register(location_name, handler) { + zoo_page._pool[location_name] = handler; + } + zoo_page.register = register; + /** + */ + function nav_set_groups(groups) { + _nav_entries.forEach(nav_entry => { + const active = ((groups === null) + || + groups.some(group => nav_entry.definition.groups.includes(group))); + nav_entry.element.classList.toggle("active", active); + }); + } + zoo_page.nav_set_groups = nav_set_groups; + /** + */ + function init(target_element, { "pool": pool = {}, "fallback": fallback = null, "nav_entries": nav_entries = [], "nav_initial_groups": nav_initial_groups = null, } = {}) { + _target_element = target_element; + _fallback = fallback; + Object.entries(pool).forEach(([location_name, handler]) => { + register(location_name, handler); + }); + window.addEventListener("hashchange", () => { + const location_old = _current; + const location_new = (get() ?? _fallback); + if (((location_old === null) + && + (location_new !== null)) + || + ((location_old !== null) + && + (location_new !== null) + && + (location_old.name !== location_new.name))) { + load(location_new); + } + else { + // do nothing + } + }); + // nav + { + let ul_element = document.querySelector("nav > ul"); + _nav_entries = nav_entries.map(nav_entry_definition => { + let li_element = document.createElement("li"); + { + let a_element = document.createElement("a"); + a_element.setAttribute("href", encode(nav_entry_definition.location)); + a_element.textContent = (nav_entry_definition.label ?? nav_entry_definition.location.name); + li_element.appendChild(a_element); + } + ul_element.appendChild(li_element); + return { + "definition": nav_entry_definition, + "element": li_element, + }; + }); + nav_set_groups(nav_initial_groups); + } + } + zoo_page.init = init; + /** + */ + function start() { + const location = (get() ?? _fallback); + set(location); + load(location); + } + zoo_page.start = start; + })(zoo_page = lib_plankton.zoo_page || (lib_plankton.zoo_page = {})); +})(lib_plankton || (lib_plankton = {})); +/* This file is part of »bacterio-plankton:zoo-input«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' diff --git a/source/base/widget.ts b/source/base/widget.ts deleted file mode 100644 index f971ca8..0000000 --- a/source/base/widget.ts +++ /dev/null @@ -1,18 +0,0 @@ -namespace _dali -{ - - /** - * @todo outsource - */ - export abstract class class_widget - { - - /** - */ - public abstract load( - target_element : Element - ) : Promise; - - } - -} diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index 7caeb15..e6dd23b 100644 --- a/source/pages/overview/logic.ts +++ b/source/pages/overview/logic.ts @@ -27,7 +27,7 @@ namespace _dali.pages.overview // mode switcher { - const widget_mode_switcher : _dali.class_widget = new _dali.widgets.mode_switcher.class_widget_mode_switcher( + const widget_mode_switcher : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.mode_switcher.class_widget_mode_switcher( [ { "mode": _dali.type.enum_view_mode.week, diff --git a/source/widgets/listview/logic.ts b/source/widgets/listview/logic.ts index 9523e4c..c79f673 100644 --- a/source/widgets/listview/logic.ts +++ b/source/widgets/listview/logic.ts @@ -28,7 +28,7 @@ namespace _dali.widgets.listview /** */ - export class class_widget_listview extends _dali.class_widget + export class class_widget_listview implements lib_plankton.zoo_widget.interface_widget { /** @@ -94,7 +94,6 @@ namespace _dali.widgets.listview }, options ); - super(); this.get_entries = get_entries; this.container = null; this.action_select_event = options.action_select_event; diff --git a/source/widgets/mode_switcher/logic.ts b/source/widgets/mode_switcher/logic.ts index 50a8e53..fd7c9b0 100644 --- a/source/widgets/mode_switcher/logic.ts +++ b/source/widgets/mode_switcher/logic.ts @@ -11,7 +11,7 @@ namespace _dali.widgets.mode_switcher /** */ - export class class_widget_mode_switcher extends _dali.class_widget + export class class_widget_mode_switcher implements lib_plankton.zoo_widget.interface_widget { /** @@ -47,7 +47,6 @@ namespace _dali.widgets.mode_switcher } ) { - super(); this.options = options; this.initial_selection = initial_selection; this.action_change = action_change; diff --git a/source/widgets/sources/logic.ts b/source/widgets/sources/logic.ts index e9b57da..01c082c 100644 --- a/source/widgets/sources/logic.ts +++ b/source/widgets/sources/logic.ts @@ -13,7 +13,7 @@ namespace _dali.widgets.sources /** */ - export class class_widget_sources extends _dali.class_widget + export class class_widget_sources implements lib_plankton.zoo_widget.interface_widget { /** @@ -53,7 +53,6 @@ namespace _dali.widgets.sources }, options ); - super(); this.keys = []; this.data = {}; entries.forEach( diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index 85686df..e788043 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -28,7 +28,7 @@ namespace _dali.widgets.weekview /** */ - export class class_widget_weekview extends _dali.class_widget + export class class_widget_weekview implements lib_plankton.zoo_widget.interface_widget { /** @@ -96,7 +96,6 @@ namespace _dali.widgets.weekview }, options ); - super(); this.get_entries = get_entries; this.container = null; this.action_select_day = options.action_select_day; diff --git a/tools/makefile b/tools/makefile index 3195a2b..dcc4413 100644 --- a/tools/makefile +++ b/tools/makefile @@ -134,7 +134,6 @@ logic: ${dir_build}/logic.js ${dir_temp}/logic-unlinked.js: \ ${dir_lib}/plankton/plankton.d.ts \ ${dir_source}/base/helpers.ts \ - ${dir_source}/base/widget.ts \ ${dir_source}/base/types.ts \ ${dir_source}/base/functions.ts \ ${dir_source}/resources/conf.ts \ diff --git a/tools/update-plankton b/tools/update-plankton index 7577b00..dad2426 100755 --- a/tools/update-plankton +++ b/tools/update-plankton @@ -23,6 +23,7 @@ modules="${modules} url" modules="${modules} pit" modules="${modules} www_form" modules="${modules} translate" +modules="${modules} zoo-widget" modules="${modules} zoo-page" modules="${modules} zoo-form" modules="${modules} zoo-input" From c97fd370407506949dfff4958cad8a868b30f1a8 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 00:16:22 +0200 Subject: [PATCH 02/11] [task-408] [int] --- lib/plankton/plankton.js | 2 +- misc/conf-example.json | 2 +- source/data/localization/deu.loc.json | 11 +- source/data/localization/eng.loc.json | 11 +- source/index.html.tpl | 4 + source/main.ts | 1 + source/overlay.ts | 75 +++++ source/pages/calendar_edit/logic.ts | 203 +++---------- source/pages/event_add/logic.ts | 235 --------------- .../event_add/templates/default.html.tpl | 5 - source/pages/event_edit/logic.ts | 195 ------------ .../event_edit/templates/default.html.tpl | 5 - source/pages/overview/logic.ts | 247 ++++++++++++--- source/resources/backend.ts | 1 + source/style/main.css | 33 ++ source/widgets/calendar_edit/logic.ts | 217 ++++++++++++++ source/widgets/event_edit/logic.ts | 282 ++++++++++++++++++ source/widgets/weekview/logic.ts | 51 +++- tools/makefile | 37 +-- 19 files changed, 928 insertions(+), 689 deletions(-) create mode 100644 source/overlay.ts delete mode 100644 source/pages/event_add/logic.ts delete mode 100644 source/pages/event_add/templates/default.html.tpl delete mode 100644 source/pages/event_edit/logic.ts delete mode 100644 source/pages/event_edit/templates/default.html.tpl create mode 100644 source/widgets/calendar_edit/logic.ts create mode 100644 source/widgets/event_edit/logic.ts diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index 64e504d..0a4b816 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -9940,7 +9940,7 @@ var lib_plankton; h = 0; } else if (q === r) { - h = ((0 / 3) + ((g - b) / (c * 6))); + h = ((0 / 3) + lib_plankton.math.mod((g - b) / (c * 6), 1)); } else if (q === g) { h = ((1 / 3) + ((b - r) / (c * 6))); diff --git a/misc/conf-example.json b/misc/conf-example.json index 7f8190c..cf5b37e 100644 --- a/misc/conf-example.json +++ b/misc/conf-example.json @@ -8,6 +8,6 @@ }, "misc": { "oidc_redirect_uri_template": "http://localhost:8888/#oidc_finish,session_key={{session_key}}", - "use_central_europe_specific_datetime_inputs": false + "use_central_europe_specific_datetime_inputs": true } } diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index 1ddbdb1..52bdc5b 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -18,6 +18,7 @@ "common.edit": "bearbeiten", "common.show": "zeigen", "common.hide": "ausblenden", + "common.cancel": "abbrechen", "access_level.none": "nichts", "access_level.view": "nur lesen", "access_level.edit": "lesen und bearbeiten", @@ -49,6 +50,11 @@ "widget.weekview.controls.week": "Woche", "widget.weekview.controls.count": "Anzahl", "widget.weekview.controls.apply": "Laden", + "widget.calendar_edit.actions.change": "ändern", + "widget.calendar_edit.actions.remove": "löschen", + "widget.event_edit.actions.add": "anlegen", + "widget.event_edit.actions.change": "ändern", + "widget.event_edit.actions.remove": "löschen", "page.login.title": "Anmelden", "page.login.internal.name": "Name", "page.login.internal.password": "Kennwort", @@ -70,14 +76,9 @@ "page.calendar_add.actions.do": "anlegen", "page.calendar_edit.title.regular": "Kalendar bearbeiten", "page.calendar_edit.title.read_only": "Kalendar-Details", - "page.calendar_edit.actions.change": "ändern", - "page.calendar_edit.actions.remove": "löschen", "page.event_add.title": "Termin anlegen", - "page.event_add.actions.do": "anlegen", "page.event_edit.title.regular": "Termin bearbeiten", "page.event_edit.title.read_only": "Termin-Details", - "page.event_edit.actions.change": "ändern", - "page.event_edit.actions.remove": "löschen", "page.overview.title": "Übersicht", "page.overview.login_hint": "anmelden um nicht-öffentliche Termine zu sehen", "page.overview.mode.week": "Wochen-Ansicht", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index 25ad7bf..023e8d2 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -18,6 +18,7 @@ "common.edit": "edit", "common.show": "show", "common.hide": "hide", + "common.cancel": "cancel", "access_level.none": "none", "access_level.view": "read only", "access_level.edit": "read and write", @@ -49,6 +50,11 @@ "widget.weekview.controls.week": "Week", "widget.weekview.controls.count": "Count", "widget.weekview.controls.apply": "Load", + "widget.calendar_edit.actions.change": "change", + "widget.calendar_edit.actions.remove": "delete", + "widget.event_edit.actions.add": "add", + "widget.event_edit.actions.change": "change", + "widget.event_edit.actions.remove": "delete", "page.login.title": "Login", "page.login.internal.name": "name", "page.login.internal.password": "password", @@ -69,15 +75,10 @@ "page.calendar_add.title": "Add calendar", "page.calendar_add.actions.do": "anlegen", "page.event_add.title": "Add event", - "page.event_add.actions.do": "add", "page.calendar_edit.title.regular": "Edit calendar", "page.calendar_edit.title.read_only": "Calendar details", - "page.calendar_edit.actions.change": "change", - "page.calendar_edit.actions.remove": "delete", "page.event_edit.title.regular": "Edit event", "page.event_edit.title.read_only": "Event details", - "page.event_edit.actions.change": "change", - "page.event_edit.actions.remove": "delete", "page.overview.title": "Overview", "page.overview.login_hint": "log in to view non-public events", "page.overview.mode.week": "week view", diff --git a/source/index.html.tpl b/source/index.html.tpl index e8eb5f4..d8151a6 100644 --- a/source/index.html.tpl +++ b/source/index.html.tpl @@ -31,6 +31,10 @@ document.addEventListener( +
+
+
+
diff --git a/source/main.ts b/source/main.ts index 1713e10..6998722 100644 --- a/source/main.ts +++ b/source/main.ts @@ -109,6 +109,7 @@ namespace _dali } ); await update(); + await _dali.overlay.initialize(); lib_plankton.call.loop( () => { update(); diff --git a/source/overlay.ts b/source/overlay.ts new file mode 100644 index 0000000..6195c85 --- /dev/null +++ b/source/overlay.ts @@ -0,0 +1,75 @@ +namespace _dali.overlay +{ + + /** + */ + function get_container_element( + ) : HTMLElement + { + return document.querySelector("#overlay"); + } + + + /** + */ + export function get_content_element( + ) : HTMLElement + { + return document.querySelector("#overlay_content"); + } + + + /** + */ + export function clear( + ) : void + { + get_content_element().innerHTML = ""; + } + + + /** + */ + export function toggle( + { + "mode": mode = null, + } + : + { + mode ?: (null | boolean); + } + = + { + } + ) : void + { + get_container_element().classList.toggle("overlay_active", mode ?? undefined); + } + + + /** + */ + export function initialize( + ) : Promise + { + clear(); + const container_element : HTMLElement = get_container_element(); + /* + container_element.addEventListener( + "click", + (event) => { + if (event.target == container_element) + { + toggle({"mode": false}); + } + else + { + // do nothing + } + } + ); + */ + return Promise.resolve(undefined); + } + +} diff --git a/source/pages/calendar_edit/logic.ts b/source/pages/calendar_edit/logic.ts index 0ed083d..8221f13 100644 --- a/source/pages/calendar_edit/logic.ts +++ b/source/pages/calendar_edit/logic.ts @@ -16,170 +16,51 @@ namespace _dali.pages "label": lib_plankton.translate.get("page.calendar_edit.title.regular") } ); - const form : lib_plankton.zoo_form.class_form< - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - }, - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - } - > = new lib_plankton.zoo_form.class_form< - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - }, - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - } - >( - (value) => value, - (raw) => raw, - new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text(), - "label": lib_plankton.translate.get("calendar.name") - }, - { - "name": "hue", - "input": new lib_plankton.zoo_input.class_input_hue( - ), - "label": lib_plankton.translate.get("calendar.hue"), - }, - { - "name": "access", - "input": new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "public", - "input": new lib_plankton.zoo_input.class_input_checkbox(), - "label": lib_plankton.translate.get("calendar.access.public"), - }, - { - "name": "default_level", - "input": _dali.helpers.input_access_level(), - "label": lib_plankton.translate.get("calendar.access.default_level"), - }, - { - "name": "attributed", - "input": await _dali.helpers.input_attributed_access(), - "label": lib_plankton.translate.get("calendar.access.attributed"), - }, - ] - ), - "label": lib_plankton.translate.get("calendar.access.access"), - }, - ] - ), - ( - read_only - ? - [ - ] - : - [ - { - "label": lib_plankton.translate.get("page.calendar_edit.actions.change"), - "procedure": async (get_value, get_representation) => { - const data : any = await get_value(); - try { - await _dali.backend.calendar_change( - calendar_id, - data - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - { - "label": lib_plankton.translate.get("page.calendar_edit.actions.remove"), - "procedure": async (get_value, get_representation) => { - try { - await _dali.backend.calendar_remove( - calendar_id - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - ] - ) - ); - await form.setup(document.querySelector("#calendar_edit_form")); const calendar_object : _dali.type.calendar_object = await _dali.backend.calendar_get( calendar_id ); - await form.input_write(calendar_object); + const widget = new _dali.widgets.calendar_edit.class_widget_calendar_edit( + { + "read_only": read_only, + "action_change": (data) => { + _dali.backend.calendar_change( + calendar_id, + data + ) + .then( + () => { + lib_plankton.zoo_page.set( + { + "name": "overview", + "parameters": {} + } + ); + } + ); + }, + "action_remove": (data) => { + _dali.backend.calendar_remove( + calendar_id + ) + .then( + () => { + lib_plankton.zoo_page.set( + { + "name": "overview", + "parameters": {} + } + ); + } + ); + }, + "initial_value": { + "name": calendar_object.name, + "hue": calendar_object.hue, + "access": calendar_object.access, + }, + } + ); + await widget.load(document.querySelector("#calendar_edit_form")); return Promise.resolve(undefined); } ); diff --git a/source/pages/event_add/logic.ts b/source/pages/event_add/logic.ts deleted file mode 100644 index f5b5d90..0000000 --- a/source/pages/event_add/logic.ts +++ /dev/null @@ -1,235 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "event_add", - async (parameters, target_element) => { - const calendar_id : (null | int) = ( - ("calendar_id" in parameters) - ? - parseInt(parameters["calendar_id"]) - : - null - ); - const year : (null | int) = ( - ("year" in parameters) - ? - parseInt(parameters["year"]) - : - null - ); - const month : (null | int) = ( - ("month" in parameters) - ? - parseInt(parameters["month"]) - : - null - ); - const day : (null | int) = ( - ("day" in parameters) - ? - parseInt(parameters["day"]) - : - null - ); - const date : lib_plankton.pit.type_date = ( - ( - (year !== null) - && - (month !== null) - && - (day !== null) - ) - ? - { - "year": year, - "month": month, - "day": day, - } - : - lib_plankton.pit.to_datetime(lib_plankton.pit.now()).date - ); - target_element.innerHTML = ""; - target_element.innerHTML = await _dali.helpers.template_coin( - "event_add", - "default", - { - "label": lib_plankton.translate.get("page.event_add.title") - } - ); - const form : lib_plankton.zoo_form.class_form< - { - calendar_id : _dali.type.calendar_id; - event_object : _dali.type.event_object; - }, - { - calendar_id : string; - name : string; - begin : lib_plankton.pit.type_datetime; - end : (null | lib_plankton.pit.type_datetime); - location : (null | string); - link : (null | string); - description : (null | string); - } - > = new lib_plankton.zoo_form.class_form< - { - calendar_id : _dali.type.calendar_id; - event_object : _dali.type.event_object; - }, - { - calendar_id : string; - name : string; - begin : lib_plankton.pit.type_datetime; - end : (null | lib_plankton.pit.type_datetime); - location : (null | string); - link : (null | string); - description : (null | string); - } - >( - (value) => ({ - "calendar_id": value.calendar_id.toFixed(0), - "name": value.event_object.name, - "begin": value.event_object.begin, - "end": value.event_object.end, - "location": value.event_object.location, - "link": value.event_object.link, - "description": value.event_object.description, - }), - (representation) => ({ - "calendar_id": parseInt(representation.calendar_id), - "event_object": { - "name": representation.name, - "begin": representation.begin, - "end": representation.end, - "location": representation.location, - "link": representation.link, - "description": representation.description, - } - }), - new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "calendar_id", - "input": new lib_plankton.zoo_input.class_input_selection( - ( - (await _dali.backend.calendar_list()) - .filter( - (entry) => ( - (entry.access_level === _dali.type.enum_access_level.edit) - || - (entry.access_level === _dali.type.enum_access_level.admin) - ) - ) - .map( - (entry) => ({ - "value": entry.id.toFixed(0), - "label": entry.name, - }) - ) - ) - ), - "label": lib_plankton.translate.get("calendar.calendar") - }, - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text( - ), - "label": lib_plankton.translate.get("event.name") - }, - { - "name": "begin", - "input": _dali.helpers.datetime_input(), - "label": lib_plankton.translate.get("event.begin") - }, - { - "name": "end", - "input": new lib_plankton.zoo_input.class_input_soft( - _dali.helpers.datetime_input() - ), - "label": lib_plankton.translate.get("event.end") - }, - { - "name": "location", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_text( - ) - ), - "label": lib_plankton.translate.get("event.location") - }, - { - "name": "link", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_text( - ) - ), - "label": lib_plankton.translate.get("event.link") - }, - { - "name": "description", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_textarea( - ) - ), - "label": lib_plankton.translate.get("event.description") - }, - ] - ), - [ - { - "label": lib_plankton.translate.get("page.event_add.actions.do"), - "target": "submit", - "procedure": async (get_value, get_representation) => { - const value : any = await get_value(); - try { - await _dali.backend.calendar_event_add( - value.calendar_id, - value.event_object - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - ] - ); - await form.setup(document.querySelector("#event_add_form")); - await form.input_write( - { - "calendar_id": (calendar_id ?? 0), - "event_object": { - "name": "", - "begin": { - "timezone_shift": 0, - "date": date, - "time": null - }, - "end": null, - "location": null, - "link": null, - "description": null, - } - } - ); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/event_add/templates/default.html.tpl b/source/pages/event_add/templates/default.html.tpl deleted file mode 100644 index 3cfad06..0000000 --- a/source/pages/event_add/templates/default.html.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{label}}

-
-
-
diff --git a/source/pages/event_edit/logic.ts b/source/pages/event_edit/logic.ts deleted file mode 100644 index 4e4bab6..0000000 --- a/source/pages/event_edit/logic.ts +++ /dev/null @@ -1,195 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "event_edit", - async (parameters, target_element) => { - const read_only : boolean = ((parameters["read_only"] ?? "yes") === "yes"); - const calendar_id : int = parseInt(parameters["calendar_id"]); - const event_id : int = parseInt(parameters["event_id"]); - target_element.innerHTML = ""; - target_element.innerHTML = await _dali.helpers.template_coin( - "event_edit", - "default", - { - "label": ( - read_only - ? - lib_plankton.translate.get("page.event_edit.title.read_only") - : - lib_plankton.translate.get("page.event_edit.title.regular") - ) - } - ); - const form : lib_plankton.zoo_form.class_form< - _dali.type.event_object, - { - name : string; - begin : lib_plankton.pit.type_datetime; - end : (null | lib_plankton.pit.type_datetime); - location : (null | string); - link : (null | string); - description : (null | string); - } - > = new lib_plankton.zoo_form.class_form< - _dali.type.event_object, - { - name : string; - begin : lib_plankton.pit.type_datetime; - end : (null | lib_plankton.pit.type_datetime); - location : (null | string); - link : (null | string); - description : (null | string); - } - >( - (value) => ({ - "name": value.name, - "begin": value.begin, - "end": value.end, - "location": value.location, - "link": value.link, - "description": value.description, - }), - (representation) => ({ - "name": representation.name, - "begin": representation.begin, - "end": representation.end, - "location": representation.location, - "link": representation.link, - "description": representation.description, - }), - new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text( - ), - "label": lib_plankton.translate.get("event.name") - }, - { - "name": "begin", - "input": _dali.helpers.datetime_input(), - "label": lib_plankton.translate.get("event.begin") - }, - { - "name": "end", - "input": new lib_plankton.zoo_input.class_input_soft( - _dali.helpers.datetime_input() - ), - "label": lib_plankton.translate.get("event.end") - }, - { - "name": "location", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_text( - ) - ), - "label": lib_plankton.translate.get("event.location") - }, - { - "name": "link", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_text( - ) - ), - "label": lib_plankton.translate.get("event.link") - }, - { - "name": "description", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_textarea( - ) - ), - "label": lib_plankton.translate.get("event.description") - }, - ] - ), - ( - read_only - ? - [ - ] - : - [ - { - "label": lib_plankton.translate.get("page.event_edit.actions.change"), - "target": "submit", - "procedure": async (get_value, get_representation) => { - const value : any = await get_value(); - try { - await _dali.backend.calendar_event_change( - calendar_id, - event_id, - value - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - lib_plankton.log.warning("page_event_edit_error", {"error": String(error)}); - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - { - "label": lib_plankton.translate.get("page.event_edit.actions.remove"), - "target": "submit", - "procedure": async (get_value, get_representation) => { - try { - await _dali.backend.calendar_event_remove( - calendar_id, - event_id - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - lib_plankton.log.warning("page_event_edit_error", {"error": String(error)}); - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - ] - ) - ); - await form.setup(document.querySelector("#event_edit_form")); - const event_object : _dali.type.event_object = await _dali.backend.calendar_event_get( - calendar_id, - event_id - ); - await form.input_write( - event_object - ); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/event_edit/templates/default.html.tpl b/source/pages/event_edit/templates/default.html.tpl deleted file mode 100644 index 2d47742..0000000 --- a/source/pages/event_edit/templates/default.html.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{label}}

-
-
-
diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index e6dd23b..e798974 100644 --- a/source/pages/overview/logic.ts +++ b/source/pages/overview/logic.ts @@ -17,6 +17,13 @@ namespace _dali.pages.overview target_element.querySelector("#overview").classList.toggle("overview-compact", compact); }; + /** + * @todo geschickter bauen (MVC, MVVM, …) + */ + const update = () => { + lib_plankton.zoo_page.reload(); + }; + // exec target_element.innerHTML = await _dali.helpers.template_coin( "overview", @@ -85,6 +92,7 @@ namespace _dali.pages.overview data, { "action_open": (entry) => { + let read_only : boolean; switch (entry.access_level) { case _dali.type.enum_access_level.none: @@ -95,31 +103,60 @@ namespace _dali.pages.overview case _dali.type.enum_access_level.edit: case _dali.type.enum_access_level.view: { - lib_plankton.zoo_page.set( - { - "name": "calendar_edit", - "parameters": { - "read_only": "yes", - "calendar_id": entry.id, - } - } - ); + read_only = true; break; } case _dali.type.enum_access_level.admin: { - lib_plankton.zoo_page.set( - { - "name": "calendar_edit", - "parameters": { - "read_only": "no", - "calendar_id": entry.id, - } - } - ); + read_only = false; break; } } + (async () => { + const calendar_id : _dali.type.calendar_id = entry.id; + const calendar_object : _dali.type.calendar_object = await _dali.backend.calendar_get( + calendar_id + ); + const widget = new _dali.widgets.calendar_edit.class_widget_calendar_edit( + { + "read_only": read_only, + "action_cancel": () => { + _dali.overlay.toggle({"mode": false}); + }, + "action_change": (data) => { + _dali.backend.calendar_change( + calendar_id, + data + ) + .then( + () => { + update(); + _dali.overlay.toggle({"mode": false}); + } + ); + }, + "action_remove": (data) => { + _dali.backend.calendar_remove( + calendar_id + ) + .then( + () => { + update(); + _dali.overlay.toggle({"mode": false}); + } + ); + }, + "initial_value": { + "name": calendar_object.name, + "hue": calendar_object.hue, + "access": calendar_object.access, + }, + } + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); }, "action_toggle_visibility": (entry) => { widget_weekview.toggle_visibility(entry.id); @@ -139,6 +176,7 @@ namespace _dali.pages.overview } ); const action_select_event = (calendar_id, access_level, event_id) => { + let read_only : boolean; switch (access_level) { case _dali.type.enum_access_level.none: @@ -148,34 +186,90 @@ namespace _dali.pages.overview } case _dali.type.enum_access_level.view: { - lib_plankton.zoo_page.set( - { - "name": "event_edit", - "parameters": { - "read_only": "yes", - "calendar_id": calendar_id, - "event_id": event_id, - } - } - ); + read_only = true; break; } case _dali.type.enum_access_level.edit: case _dali.type.enum_access_level.admin: { - lib_plankton.zoo_page.set( - { - "name": "event_edit", - "parameters": { - "read_only": "no", - "calendar_id": calendar_id, - "event_id": event_id, - } - } - ); + read_only = false; break; } } + (async () => { + const event_object : _dali.type.event_object = await _dali.backend.calendar_event_get( + calendar_id, + event_id + ); + const widget = new _dali.widgets.event_edit.class_widget_event_edit( + { + "calendar_id": calendar_id, + "event_name": event_object.name, + "event_begin": event_object.begin, + "event_end": event_object.end, + "event_location": event_object.location, + "event_link": event_object.link, + "event_description": event_object.description, + }, + { + "read_only": read_only, + "action_cancel": () => { + _dali.overlay.toggle({"mode": false}); + }, + "action_change": (data) => { + _dali.backend.calendar_event_change( + calendar_id, + event_id, + { + "name": data.event_name, + "begin": data.event_begin, + "end": data.event_end, + "location": data.event_location, + "link": data.event_link, + "description": data.event_description, + } + ) + .then( + () => { + update(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + lib_plankton.log.warning( + "dali.overview.event_change.error", + {"details": String(reason)} + ); + } + ); + }, + "action_remove": () => { + _dali.backend.calendar_event_remove( + calendar_id, + event_id + ) + .then( + () => { + update(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + lib_plankton.log.warning( + "dali.overview.event_remove_error", + {"details": String(reason)} + ); + } + ); + }, + } + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); }; // listview { @@ -210,17 +304,76 @@ namespace _dali.pages.overview { "action_select_event": action_select_event, "action_select_day": (date) => { - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { + (async () => { + const widget = new _dali.widgets.event_edit.class_widget_event_edit( + { "calendar_id": null, - "year": date.year, - "month": date.month, - "day": date.day, + "event_name": "", + "event_begin": lib_plankton.call.convey( + date, + [ + x => ({ + "timezone_shift": 0, + "date": date, + "time": {"hour": 12, "minute": 0, "second": 0} + }), + lib_plankton.pit.from_datetime, + x => lib_plankton.pit.shift_hour(x, 0), + lib_plankton.pit.to_datetime, + ] + ), + "event_end": lib_plankton.call.convey( + date, + [ + x => ({ + "timezone_shift": 0, + "date": date, + "time": {"hour": 12, "minute": 0, "second": 0} + }), + lib_plankton.pit.from_datetime, + x => lib_plankton.pit.shift_hour(x, +1), + lib_plankton.pit.to_datetime, + ] + ), + "event_location": null, + "event_link": null, + "event_description": null, + }, + { + "read_only": false, + "action_cancel": () => { + _dali.overlay.toggle({"mode": false}); + }, + "action_add": (data) => { + _dali.backend.calendar_event_add( + data.calendar_id, + { + "name": data.event_name, + "begin": data.event_begin, + "end": data.event_end, + "location": data.event_location, + "link": data.event_link, + "description": data.event_description, + } + ) + .then( + () => { + update(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + // todo + } + ); + }, } - } - ); + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); }, } ) diff --git a/source/resources/backend.ts b/source/resources/backend.ts index 1164246..9991f92 100644 --- a/source/resources/backend.ts +++ b/source/resources/backend.ts @@ -547,6 +547,7 @@ namespace _dali.backend /** + * @todo Möglichkeit den Kalender zu ändern */ export async function calendar_event_change( calendar_id : _dali.type.calendar_id, diff --git a/source/style/main.css b/source/style/main.css index e1d417e..ea384f0 100644 --- a/source/style/main.css +++ b/source/style/main.css @@ -20,6 +20,39 @@ header margin-bottom: 16px; } +#overlay +{ + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.75); + z-index: 2; +} + +#overlay_content +{ + position: absolute; + top: 50%; + left: 50%; + + transform: translate(-50%,-50%); + -ms-transform: translate(-50%,-50%); + + padding: 32px; + + background-color: hsl(0, 0%, 12.5%); + color: hsl(0, 0%, 100%); +} + +#overlay:not(.overlay_active) +{ + display: none; +} + nav > ul { list-style-type: none; diff --git a/source/widgets/calendar_edit/logic.ts b/source/widgets/calendar_edit/logic.ts new file mode 100644 index 0000000..ac45f84 --- /dev/null +++ b/source/widgets/calendar_edit/logic.ts @@ -0,0 +1,217 @@ +namespace _dali.widgets.calendar_edit +{ + + /** + */ + export type type_value = { + name : string; + hue : float; + access : { + public : boolean; + default_level : _dali.type.enum_access_level; + attributed : lib_plankton.map.type_map< + _dali.type.user_id, + _dali.type.enum_access_level + >; + }; + }; + + + /** + */ + export class class_widget_calendar_edit + implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private read_only : boolean; + + + /** + */ + private action_cancel ?: (null | (() => void)); + + + /** + */ + private action_change ?: (null | ((value : type_value) => void)); + + + /** + */ + private action_remove ?: (null | ((value : type_value) => void)); + + + /** + */ + private initial_value : (null | type_value); + + + /** + */ + public constructor( + { + "read_only": read_only = false, + "action_cancel": action_cancel = null, + "action_change": action_change = null, + "action_remove": action_remove = null, + "initial_value": initial_value = null, + } + : + { + read_only ?: boolean; + action_cancel ?: (null | (() => void)); + action_change ?: (null | ((value : type_value) => void)); + action_remove ?: (null | ((value : type_value) => void)); + initial_value ?: (null | type_value); + } + = + { + } + ) + { + this.read_only = read_only; + this.action_cancel = action_cancel; + this.action_change = action_change; + this.action_remove = action_remove; + this.initial_value = initial_value; + } + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) : Promise + { + const form : lib_plankton.zoo_form.class_form< + type_value, + type_value + > = new lib_plankton.zoo_form.class_form< + type_value, + type_value + >( + (value) => value, + (raw) => raw, + new lib_plankton.zoo_input.class_input_group( + [ + { + "name": "name", + "input": new lib_plankton.zoo_input.class_input_text(), + "label": lib_plankton.translate.get("calendar.name") + }, + { + "name": "hue", + "input": new lib_plankton.zoo_input.class_input_hue( + ), + "label": lib_plankton.translate.get("calendar.hue"), + }, + { + "name": "access", + "input": new lib_plankton.zoo_input.class_input_group( + [ + { + "name": "public", + "input": new lib_plankton.zoo_input.class_input_checkbox(), + "label": lib_plankton.translate.get("calendar.access.public"), + }, + { + "name": "default_level", + "input": _dali.helpers.input_access_level(), + "label": lib_plankton.translate.get("calendar.access.default_level"), + }, + { + "name": "attributed", + "input": await _dali.helpers.input_attributed_access(), + "label": lib_plankton.translate.get("calendar.access.attributed"), + }, + ] + ), + "label": lib_plankton.translate.get("calendar.access.access"), + }, + ] + ), + ( + [] + // cancel + .concat( + (! (this.action_cancel === null)) + ? + [ + { + "label": lib_plankton.translate.get("common.cancel"), + "procedure": async () => { + this.action_cancel(); + } + }, + ] + : + [] + ) + // change + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.calendar_edit.actions.change"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_change(value); + } + }, + ] + : + [] + ) + // remove + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.calendar_edit.actions.remove"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_remove(value); + } + }, + ] + : + [] + ) + ) + ); + await form.setup(target_element); + await form.input_write( + (! (this.initial_value === null)) + ? + this.initial_value + : + { + "name": "", + "hue": lib_plankton.random.generate_unit(), + "access": { + "public": false, + "default_level": _dali.type.enum_access_level.view, + "attributed": lib_plankton.map.hashmap.implementation_map< + _dali.type.user_id, + _dali.type.enum_access_level + >( + lib_plankton.map.hashmap.make< + _dali.type.user_id, + _dali.type.enum_access_level + >( + user_id => user_id.toFixed(0), + ) + ), + }, + } + ); + } + + } + +} diff --git a/source/widgets/event_edit/logic.ts b/source/widgets/event_edit/logic.ts new file mode 100644 index 0000000..7d1b942 --- /dev/null +++ b/source/widgets/event_edit/logic.ts @@ -0,0 +1,282 @@ +namespace _dali.widgets.event_edit +{ + + /** + */ + export type type_value = { + calendar_id : (null | _dali.type.calendar_id); + event_name : string; + event_begin : lib_plankton.pit.type_datetime; + event_end : (null | lib_plankton.pit.type_datetime); + event_location : (null | string); + event_link : (null | string); + event_description : (null | string); + }; + + + /** + */ + export type type_representation = { + calendar_id : string; + event_name : string; + event_begin : lib_plankton.pit.type_datetime; + event_end : (null | lib_plankton.pit.type_datetime); + event_location : (null | string); + event_link : (null | string); + event_description : (null | string); + }; + + + /** + */ + export class class_widget_event_edit + implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private read_only : boolean; + + + /** + */ + private action_cancel ?: (null | (() => void)); + + + /** + */ + private action_add ?: (null | ((value : type_value) => void)); + + + /** + */ + private action_change ?: (null | ((value : type_value) => void)); + + + /** + */ + private action_remove ?: (null | ((value : type_value) => void)); + + + /** + */ + private initial_value : type_value; + + + /** + */ + public constructor( + initial_value : type_value, + { + "read_only": read_only = false, + "action_cancel": action_cancel = null, + "action_add": action_add = null, + "action_change": action_change = null, + "action_remove": action_remove = null, + } + : + { + read_only ?: boolean; + action_cancel ?: (null | (() => void)); + action_add ?: (null | ((value : type_value) => void)); + action_change ?: (null | ((value : type_value) => void)); + action_remove ?: (null | ((value : type_value) => void)); + } + = + { + } + ) + { + this.read_only = read_only; + this.action_cancel = action_cancel; + this.action_add = action_add; + this.action_change = action_change; + this.action_remove = action_remove; + this.initial_value = initial_value; + } + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) : Promise + { + const available_calendars : Array< + { + id : int; + name : string; + hue : float; + access_level : _dali.type.enum_access_level; + } + > = ( + (await _dali.backend.calendar_list()) + .filter( + (entry) => ( + (entry.access_level === _dali.type.enum_access_level.edit) + || + (entry.access_level === _dali.type.enum_access_level.admin) + ) + ) + ); + const form : lib_plankton.zoo_form.class_form< + type_value, + type_representation + > = new lib_plankton.zoo_form.class_form< + type_value, + type_representation + >( + (value) => ({ + "calendar_id": (value.calendar_id ?? available_calendars[0].id).toFixed(0), + "event_name": value.event_name, + "event_begin": value.event_begin, + "event_end": value.event_end, + "event_location": value.event_location, + "event_link": value.event_link, + "event_description": value.event_description, + }), + (representation) => ({ + "calendar_id": parseInt(representation.calendar_id), + "event_name": representation.event_name, + "event_begin": representation.event_begin, + "event_end": representation.event_end, + "event_location": representation.event_location, + "event_link": representation.event_link, + "event_description": representation.event_description, + }), + new lib_plankton.zoo_input.class_input_group( + [ + { + "name": "calendar_id", + "input": new lib_plankton.zoo_input.class_input_selection( + ( + available_calendars + .map( + (entry) => ({ + "value": entry.id.toFixed(0), + "label": entry.name, + }) + ) + ) + ), + "label": lib_plankton.translate.get("calendar.calendar") + }, + { + "name": "event_name", + "input": new lib_plankton.zoo_input.class_input_text( + ), + "label": lib_plankton.translate.get("event.name") + }, + { + "name": "event_begin", + "input": _dali.helpers.datetime_input(), + "label": lib_plankton.translate.get("event.begin") + }, + { + "name": "event_end", + "input": new lib_plankton.zoo_input.class_input_soft( + _dali.helpers.datetime_input() + ), + "label": lib_plankton.translate.get("event.end") + }, + { + "name": "event_location", + "input": new lib_plankton.zoo_input.class_input_soft( + new lib_plankton.zoo_input.class_input_text( + ) + ), + "label": lib_plankton.translate.get("event.location") + }, + { + "name": "event_link", + "input": new lib_plankton.zoo_input.class_input_soft( + new lib_plankton.zoo_input.class_input_text( + ) + ), + "label": lib_plankton.translate.get("event.link") + }, + { + "name": "event_description", + "input": new lib_plankton.zoo_input.class_input_soft( + new lib_plankton.zoo_input.class_input_textarea( + ) + ), + "label": lib_plankton.translate.get("event.description") + }, + ] + ), + ( + [] + // cancel + .concat( + (! (this.action_cancel === null)) + ? + [ + { + "label": lib_plankton.translate.get("common.cancel"), + "procedure": async () => { + this.action_cancel(); + } + }, + ] + : + [] + ) + // add + .concat( + ((! this.read_only) && (! (this.action_add === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.event_edit.actions.add"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_add(value); + } + }, + ] + : + [] + ) + // change + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.event_edit.actions.change"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_change(value); + } + }, + ] + : + [] + ) + // remove + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.event_edit.actions.remove"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_remove(value); + } + }, + ] + : + [] + ) + ) + ); + await form.setup(target_element); + await form.input_write(this.initial_value); + } + + } + +} diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index e788043..c6a4484 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -833,6 +833,26 @@ namespace _dali.widgets.weekview } + /** + */ + private react_on_control_change( + dom_context : Element + ) : void + { + const year : int = parseInt((dom_context.querySelector(".weekview-control-year > input") as HTMLInputElement).value); + const week : int = parseInt((dom_context.querySelector(".weekview-control-week > input") as HTMLInputElement).value); + const count : int = parseInt((dom_context.querySelector(".weekview-control-count > input") as HTMLInputElement).value); + this.update( + year, + week, + count, + { + "update_controls": false, + } + ); + } + + /** */ public toggle_visibility( @@ -886,19 +906,28 @@ namespace _dali.widgets.weekview "click", (event) => { event.preventDefault(); - const year : int = parseInt((target_element.querySelector(".weekview-control-year > input") as HTMLInputElement).value); - const week : int = parseInt((target_element.querySelector(".weekview-control-week > input") as HTMLInputElement).value); - const count : int = parseInt((target_element.querySelector(".weekview-control-count > input") as HTMLInputElement).value); - this.update( - year, - week, - count, - { - "update_controls": false, - } - ); + this.react_on_control_change(target_element); } ); + if (false) + { + [ + "year", + "week", + "count", + ].forEach( + (name) => { + const selector : string = (".weekview-control-" + name + " > input"); + (target_element.querySelector(selector) as HTMLInputElement).addEventListener( + "change", + (event) => { + event.preventDefault(); + this.react_on_control_change(target_element); + } + ); + } + ); + } } // table { diff --git a/tools/makefile b/tools/makefile index dcc4413..89727f0 100644 --- a/tools/makefile +++ b/tools/makefile @@ -36,11 +36,11 @@ templates: \ templates-widgets-listview \ templates-widgets-weekview \ templates-widgets-mode_switcher \ + templates-widgets-calendar_edit \ + templates-widgets-event_edit \ templates-pages-caldav \ templates-pages-calendar_add \ templates-pages-calendar_edit \ - templates-pages-event_add \ - templates-pages-event_edit \ templates-pages-overview \ templates-pages-login @@ -72,6 +72,20 @@ 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-widgets-calendar_edit +templates-widgets-calendar_edit: \ + $(wildcard ${dir_source}/widgets/calendar_edit/templates/*) + @ ${cmd_log} "templates:widgets:calendar_edit …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-calendar_edit + # @ ${cmd_cp} -r -u -v ${dir_source}/widgets/calendar_edit/templates/* ${dir_build}/templates/widget-calendar_edit/ + +.PHONY: templates-widgets-event_edit +templates-widgets-event_edit: \ + $(wildcard ${dir_source}/pages/event_edit/templates/*) + @ ${cmd_log} "templates:widgets:event_edit …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-event_edit + # @ ${cmd_cp} -r -u -v ${dir_source}/pages/event_edit/templates/* ${dir_build}/templates/widget-uevent_edit/ + .PHONY: templates-pages-caldav templates-pages-caldav: \ $(wildcard ${dir_source}/pages/caldav/templates/*) @@ -93,20 +107,6 @@ templates-pages-calendar_edit: \ @ ${cmd_mkdir} ${dir_build}/templates/calendar_edit @ ${cmd_cp} -r -u -v ${dir_source}/pages/calendar_edit/templates/* ${dir_build}/templates/calendar_edit/ -.PHONY: templates-pages-event_add -templates-pages-event_add: \ - $(wildcard ${dir_source}/pages/event_add/templates/*) - @ ${cmd_log} "templates:event_add …" - @ ${cmd_mkdir} ${dir_build}/templates/event_add - @ ${cmd_cp} -r -u -v ${dir_source}/pages/event_add/templates/* ${dir_build}/templates/event_add/ - -.PHONY: templates-pages-event_edit -templates-pages-event_edit: \ - $(wildcard ${dir_source}/pages/event_edit/templates/*) - @ ${cmd_log} "templates:event_edit …" - @ ${cmd_mkdir} ${dir_build}/templates/event_edit - @ ${cmd_cp} -r -u -v ${dir_source}/pages/event_edit/templates/* ${dir_build}/templates/event_edit/ - .PHONY: templates-pages-overview templates-pages-overview: \ $(wildcard ${dir_source}/pages/overview/templates/*) @@ -142,14 +142,15 @@ ${dir_temp}/logic-unlinked.js: \ ${dir_source}/widgets/listview/logic.ts \ ${dir_source}/widgets/weekview/logic.ts \ ${dir_source}/widgets/mode_switcher/logic.ts \ + ${dir_source}/widgets/calendar_edit/logic.ts \ + ${dir_source}/widgets/event_edit/logic.ts \ + ${dir_source}/overlay.ts \ ${dir_source}/pages/login/logic.ts \ ${dir_source}/pages/logout/logic.ts \ ${dir_source}/pages/caldav/logic.ts \ ${dir_source}/pages/oidc_finish/logic.ts \ ${dir_source}/pages/calendar_add/logic.ts \ ${dir_source}/pages/calendar_edit/logic.ts \ - ${dir_source}/pages/event_add/logic.ts \ - ${dir_source}/pages/event_edit/logic.ts \ ${dir_source}/pages/overview/logic.ts \ ${dir_source}/main.ts @ ${cmd_log} "logic | compile …" From 057b682bdc524a18316041ec2c266fb415fc5b28 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 15:56:29 +0200 Subject: [PATCH 03/11] [task-408] [int] --- source/base/model.ts | 8 + source/base/types.ts | 21 ++- source/widgets/weekview/logic.ts | 270 +++++++++++++++++++++++++++++-- tools/makefile | 1 + 4 files changed, 284 insertions(+), 16 deletions(-) create mode 100644 source/base/model.ts diff --git a/source/base/model.ts b/source/base/model.ts new file mode 100644 index 0000000..26847c6 --- /dev/null +++ b/source/base/model.ts @@ -0,0 +1,8 @@ +namespace _dali.model +{ + + /** + */ + let events : Array<_dali.type.event_entry> = []; + +} diff --git a/source/base/types.ts b/source/base/types.ts index da79c50..e97b1d9 100644 --- a/source/base/types.ts +++ b/source/base/types.ts @@ -31,6 +31,22 @@ namespace _dali.type }; + /** + * @todo deprecate? + */ + export type local_resource_event_id = int; + + + /** + * info: das ist nicht deckungsgleich mit der Event-ID aus dem Backend; hiermit werden sowohl lokale als auch + * extern eingebundene Events kodiert + * + * @example "local:1234" + * @example "ics:2345" + */ + export type event_id = string; + + /** */ export type event_object = { @@ -61,7 +77,10 @@ namespace _dali.type /** */ - export type local_resource_event_id = int; + export type event_entry = { + id : event_id; + object : event_object; + }; /** diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index c6a4484..a6265a5 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -41,6 +41,14 @@ namespace _dali.widgets.weekview private container : (null | Element); + /** + */ + private event_map : lib_plankton.map.type_map< + _dali.type.event_id, + HTMLElement + >; + + /** */ private action_select_event : ( @@ -98,6 +106,14 @@ namespace _dali.widgets.weekview ); this.get_entries = get_entries; this.container = null; + this.event_map = lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make< + _dali.type.event_id, + HTMLElement + >( + event_id => event_id + ) + ); this.action_select_day = options.action_select_day; this.action_select_event = options.action_select_event; } @@ -198,6 +214,215 @@ namespace _dali.widgets.weekview } + /** + */ + private create_dom_entry( + sources : lib_plankton.map.type_map< + _dali.type.calendar_id, + { + name : string; + access_level : _dali.type.enum_access_level; + /** + * @todo replace with hue? + */ + color : lib_plankton.color.type_color; + } + >, + // event_entry : _dali.type.event_entry + entry : type_entry + ) : HTMLElement + { + return _dali.helpers.template_coin( + "widget-weekview", + "tableview-cell-entry", + { + "color": lib_plankton.color.output_hex( + sources.get( + entry.calendar_id + ).color + ), + "title": class_widget_weekview.event_generate_tooltip( + sources.get( + entry.calendar_id + ).name, + entry.event_object + ), + "name": entry.event_object.name, + "rel": lib_plankton.string.coin( + "{{calendar_id}}/{{event_id}}/{{access_level}}", + { + "calendar_id": entry.calendar_id.toFixed(0), + "event_id": ( + (entry.event_id === null) + ? + "-" + : + entry.event_id.toFixed(0) + ), + "access_level": (() => { + const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; + switch (access_level) + { + case _dali.type.enum_access_level.none: return "none"; + case _dali.type.enum_access_level.view: return "view"; + case _dali.type.enum_access_level.edit: return "edit"; + case _dali.type.enum_access_level.admin: return "admin"; + } + }) (), + } + ), + "additional_classes": lib_plankton.string.coin( + " access_level-{{access_level}}", + { + "access_level": (() => { + const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; + switch (access_level) + { + case _dali.type.enum_access_level.none: return "none"; + case _dali.type.enum_access_level.view: return "view"; + case _dali.type.enum_access_level.edit: return "edit"; + case _dali.type.enum_access_level.admin: return "admin"; + } + }) (), + } + ), + } + ); + } + + + /** + */ + private add_entry( + event_entry : _dali.type.event_entry + ) : void + { + const dom_entry : HTMLElement = this.create_dom_entry(event_entry); + /** + * @todo insert dom_entry into DOM + */ + this.event_map.set( + event_entry.id, + dom_entry + ); + } + + + /** + */ + private update_entry( + event_id : _dali.type.event_id, + event_entry : _dali.type.event_entry + ) : void + { + const dom_entry_old : HTMLElement = lib_plankton.map.get( + this.event_map, + event_id + ); + /** + * @todo remove dom_entry_old from DOM + */ + const dom_entry_new : HTMLElement = this.create_dom_entry(event_entry); + /** + * @todo insert dom_entry_new into DOM + */ + this.event_map.set( + event_id, + dom_entry_new + ); + } + + + /** + */ + private remove_entry( + event_id : _dali.type.event_id + ) : void + { + const dom_entry : HTMLElement = lib_plankton.map.get( + this.event_map, + event_id + ); + /** + * @todo remove dom_entry from DOM + */ + this.event_map.delete( + event_id + ); + } + + + /** + */ + private sync_events( + events : Array<_dali.type.event_entry> + ) : void + { + const track : Record< + string, + { + external : (null | _dali.type.event_entry); + internal : (null | _dali.type.event_id); + } + > = {}; + events.forEach( + event => { + const key : string = event.id; + if (! (key in track)) track[key] = {"external": null, "internal": null}; + track[key].external = event; + } + ); + lib_plankton.map.dump(this.event_map).forEach( + pair => { + const key : string = pair.key; + if (! (key in track)) track[key] = {"external": null, "internal": null}; + track[key].internal = pair.key; + } + ); + + Object.entries(track).forEach( + ([key, value]) => { + if ( + ! (value.external !== null) + && + ! (value.internal !== null) + ) + { + throw (new Error("impossible!")); + } + else if ( + (value.external !== null) + && + ! (value.internal !== null) + ) + { + this.add_entry(value.external); + } + else if ( + (value.external !== null) + && + (value.internal !== null) + ) + { + this.update_entry(value.internal, value.external); + } + else if ( + ! (value.external !== null) + && + (value.internal !== null) + ) + { + this.remove_entry(value.external.id); + } + else + { + throw (new Error("impossible!")); + } + } + ); + } + + /** * @todo kein "while" */ @@ -341,7 +566,8 @@ namespace _dali.widgets.weekview } > = []; let day : int = 0; - while (true) { + while (true) + { const pit_current : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day( from_pit, day @@ -351,7 +577,8 @@ namespace _dali.widgets.weekview pit_current, to_pit ) - ) { + ) + { day += 1; row.push( { @@ -360,7 +587,8 @@ namespace _dali.widgets.weekview "today": false, // TODO } ); - if (day % 7 === 0) { + if (day % 7 === 0) + { result.rows.push( { "week": ( @@ -375,11 +603,13 @@ namespace _dali.widgets.weekview ); row = []; } - else { + else + { // do nothing } } - else { + else + { break; } } @@ -731,10 +961,12 @@ namespace _dali.widgets.weekview const context : Element = this.container; // controls { - if (! options.update_controls) { + if (! options.update_controls) + { // do nothing } - else { + else + { (context.querySelector(".weekview-control-year > input") as HTMLInputElement).value = year.toFixed(0); (context.querySelector(".weekview-control-week > input") as HTMLInputElement).value = week.toFixed(0); (context.querySelector(".weekview-control-count > input") as HTMLInputElement).value = count.toFixed(0); @@ -760,19 +992,23 @@ namespace _dali.widgets.weekview ); // cells { - if (! await _dali.backend.is_logged_in()) { + if (! await _dali.backend.is_logged_in()) + { // do nothing } - else { + else + { context.querySelectorAll(".weekview-cell-regular").forEach( (element) => { element.addEventListener( "click", (event) => { - if (! (element === event.target)) { + if (! (element === event.target)) + { // do nothing } - else { + else + { const rel : string = element.getAttribute("rel"); const parts : Array = rel.split("-"); const date : lib_plankton.pit.type_date = { @@ -790,10 +1026,12 @@ namespace _dali.widgets.weekview } // events { - if (! await _dali.backend.is_logged_in()) { + if (! await _dali.backend.is_logged_in()) + { // do nothing } - else { + else + { context.querySelectorAll(".weekview-event_entry").forEach( (element) => { element.addEventListener( @@ -864,10 +1102,12 @@ namespace _dali.widgets.weekview const rel : string = element.getAttribute("rel"); const parts : Array = rel.split("/"); const calendar_id_ : _dali.type.calendar_id = parseInt(parts[0]); - if (! (calendar_id === calendar_id_)) { + if (! (calendar_id === calendar_id_)) + { // do nothing } - else { + else + { element.classList.toggle("weekview-cell-hidden"); } } diff --git a/tools/makefile b/tools/makefile index 89727f0..9788fe7 100644 --- a/tools/makefile +++ b/tools/makefile @@ -136,6 +136,7 @@ ${dir_temp}/logic-unlinked.js: \ ${dir_source}/base/helpers.ts \ ${dir_source}/base/types.ts \ ${dir_source}/base/functions.ts \ + ${dir_source}/base/model.ts \ ${dir_source}/resources/conf.ts \ ${dir_source}/resources/backend.ts \ ${dir_source}/widgets/sources/logic.ts \ From c53837ebfe1bdffddd2f7edfee51c9366c08c6c5 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 22:09:49 +0200 Subject: [PATCH 04/11] [upd] plankton --- lib/plankton/plankton.d.ts | 53 ++++++++++++-- lib/plankton/plankton.js | 144 +++++++++++++++++++++++++++++++++++-- 2 files changed, 186 insertions(+), 11 deletions(-) diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index 3302bcf..a72512e 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -1,11 +1,11 @@ /** * @author fenris */ -declare type int = number; +type int = number; /** * @author fenris */ -declare type float = number; +type float = number; declare class Buffer { constructor(x: string, modifier?: string); toString(modifier?: string): string; @@ -19,7 +19,7 @@ declare namespace lib_plankton.base { /** * @author fenris */ -declare type type_pseudopointer = { +type type_pseudopointer = { value: type_value; }; /** @@ -1969,7 +1969,7 @@ declare namespace lib_plankton.storage.memory { clear(): Promise; write(key: any, value: any): Promise; delete(key: any): Promise; - read(key: any): Promise; + read(key: any): Promise>; search(term: any): Promise<{ key: string; preview: string; @@ -2693,6 +2693,51 @@ declare namespace lib_plankton.map.collatemap { export function implementation_map(subject: type_subject): type_map; export {}; } +declare namespace lib_plankton.cache { + /** + */ + type type_result = { + retrieved: boolean; + value: type_value; + }; + /** + */ + type type_entry = { + value: type_value; + expiry: (null | float); + }; + /** + */ + type type_subject = lib_plankton.storage.type_chest, void, any, any>; +} +declare namespace lib_plankton.cache { + /** + */ + function make({ "chest": chest, }?: { + chest?: lib_plankton.storage.type_chest, void, any, any>; + }): type_subject; + /** + */ + function init(subject: type_subject): Promise; + /** + */ + function clear(subject: type_subject): Promise; + /** + */ + function invalidate(subject: type_subject, key: string): Promise; + /** + */ + function query(subject: type_subject, key: string, lifetime: (null | float), retrieve: (() => Promise)): Promise>; + /** + * syntactic sugar: if the information, whether the value was retrieved, is irrelevant + */ + function get(subject: type_subject, key: string, lifetime: (null | float), retrieve: (() => Promise)): Promise; + /** + */ + function get_complex(cache: type_subject, group: string, input: type_input, lifetime: (null | float), retrieve: ((input: type_input) => Promise), { "encode_input": encode_input, }?: { + encode_input?: ((input: type_input) => string); + }): Promise; +} declare namespace lib_plankton.complex { /** * @author fenris diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index 0a4b816..fa6a73c 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -2151,7 +2151,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) { function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); - while (_) try { + while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { @@ -8710,6 +8710,136 @@ var lib_plankton; })(map = lib_plankton.map || (lib_plankton.map = {})); })(lib_plankton || (lib_plankton = {})); /* +This file is part of »bacterio-plankton:cache«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:cache« 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:cache« 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:cache«. If not, see . + */ +/* +This file is part of »bacterio-plankton:cache«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:cache« 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:cache« 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:cache«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var cache; + (function (cache_1) { + /** + */ + function make({ "chest": chest = lib_plankton.storage.memory.implementation_chest({}), } = {}) { + return chest; + } + cache_1.make = make; + /** + */ + function init(subject) { + return subject.setup(undefined); + } + cache_1.init = init; + /** + */ + function clear(subject) { + return subject.clear(); + } + cache_1.clear = clear; + /** + */ + function invalidate(subject, key) { + return subject.delete(key); + } + cache_1.invalidate = invalidate; + /** + */ + async function query(subject, key, lifetime, retrieve) { + const now = lib_plankton.base.get_current_timestamp(); + return ((subject.read(key) + .then((entry) => Promise.resolve({ + "found": true, + "entry": entry, + })) + .catch(() => Promise.resolve({ + "found": false, + "entry": null, + }))) + .then((item) => { + if ((!item.found) + || + ((item.entry.expiry !== null) + && + (item.entry.expiry <= now))) { + lib_plankton.log.info("plankton.cache.unknown_or_expired", { + "key": key, + }); + return (retrieve() + .then((value) => (subject.write(key, { + "value": value, + "expiry": ((lifetime === null) + ? + null + : + (now + lifetime)) + }) + .then(() => Promise.resolve({ + "retrieved": true, + "value": value + }))))); + } + else { + lib_plankton.log.info("plankton.cache.known_and_valid", { + "key": key, + }); + return Promise.resolve({ + "retrieved": false, + "value": item.entry.value + }); + } + })); + } + cache_1.query = query; + /** + * syntactic sugar: if the information, whether the value was retrieved, is irrelevant + */ + function get(subject, key, lifetime, retrieve) { + return (query(subject, key, lifetime, retrieve) + .then(result => Promise.resolve(result.value))); + } + cache_1.get = get; + /** + */ + function get_complex(cache, group, input, lifetime, retrieve, { "encode_input": encode_input = (input => JSON.stringify(input)), } = {}) { + return get(cache, (group + "." + encode_input(input)), lifetime, () => retrieve(input)); + } + cache_1.get_complex = get_complex; + })(cache = lib_plankton.cache || (lib_plankton.cache = {})); +})(lib_plankton || (lib_plankton = {})); +/* This file is part of »bacterio-plankton:complex«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' @@ -9449,6 +9579,12 @@ var lib_plankton; * @author fenris */ class class_relation { + /** + * @author fenris + */ + check(value, reference) { + return this.predicate(value, reference); + } /** * @author fenris */ @@ -9458,12 +9594,6 @@ var lib_plankton; this.name = name; this.predicate = predicate; } - /** - * @author fenris - */ - check(value, reference) { - return this.predicate(value, reference); - } /** * @author fenris */ From 7e1f585f34771aab78d4f40b6a5524c1738fc6ba Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 22:09:57 +0200 Subject: [PATCH 05/11] [upd] plankton --- tools/update-plankton | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/update-plankton b/tools/update-plankton index dad2426..b652c8d 100755 --- a/tools/update-plankton +++ b/tools/update-plankton @@ -14,6 +14,7 @@ modules="${modules} json" modules="${modules} string" modules="${modules} random" modules="${modules} map" +modules="${modules} cache" modules="${modules} color" # modules="${modules} xml" modules="${modules} map" From 4612128134084da9ef55f1cf69b8a1eafd8f9d9d Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 22:10:56 +0200 Subject: [PATCH 06/11] [task-408] [mod] Wochenansich-Steuerelement umgestellt --- source/base/functions.ts | 35 +- source/base/types.ts | 7 +- source/pages/overview/logic.ts | 31 +- source/resources/backend.ts | 67 +- source/widgets/weekview/logic.ts | 1468 +++++++---------- .../widgets/weekview/templates/main.html.tpl | 1 - 6 files changed, 676 insertions(+), 933 deletions(-) diff --git a/source/base/functions.ts b/source/base/functions.ts index 274127d..a4721c9 100644 --- a/source/base/functions.ts +++ b/source/base/functions.ts @@ -1,7 +1,38 @@ namespace _dali { - + /** + */ + export function access_level_encode( + access_level : _dali.type.enum_access_level + ) : ("none" | "view" | "edit" | "admin") + { + switch (access_level) { + case _dali.type.enum_access_level.none: return "none"; + case _dali.type.enum_access_level.view: return "view"; + case _dali.type.enum_access_level.edit: return "edit"; + case _dali.type.enum_access_level.admin: return "admin"; + } + } + + + /** + */ + export function access_level_decode( + representation : /*("none" | "view" | "edit" | "admin")*/string + ) : _dali.type.enum_access_level + { + switch (representation) + { + case "none": return _dali.type.enum_access_level.none; + case "view": return _dali.type.enum_access_level.view; + case "edit": return _dali.type.enum_access_level.edit; + case "admin": return _dali.type.enum_access_level.admin; + default: throw (new Error("invalid access level representation: " + representation)); + } + } + + /** */ export function view_mode_encode( @@ -58,6 +89,6 @@ namespace _dali { return view_mode_decode(mode_descriptor); } - } + } } diff --git a/source/base/types.ts b/source/base/types.ts index e97b1d9..ac35b84 100644 --- a/source/base/types.ts +++ b/source/base/types.ts @@ -42,9 +42,9 @@ namespace _dali.type * extern eingebundene Events kodiert * * @example "local:1234" - * @example "ics:2345" + * @example "ics~2345" */ - export type event_id = string; + export type event_key = string; /** @@ -78,7 +78,8 @@ namespace _dali.type /** */ export type event_entry = { - id : event_id; + id : (null | local_resource_event_id); + key : event_key; object : event_object; }; diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index e798974..94bbb20 100644 --- a/source/pages/overview/logic.ts +++ b/source/pages/overview/logic.ts @@ -17,13 +17,6 @@ namespace _dali.pages.overview target_element.querySelector("#overview").classList.toggle("overview-compact", compact); }; - /** - * @todo geschickter bauen (MVC, MVVM, …) - */ - const update = () => { - lib_plankton.zoo_page.reload(); - }; - // exec target_element.innerHTML = await _dali.helpers.template_coin( "overview", @@ -66,6 +59,12 @@ namespace _dali.pages.overview let widget_weekview : _dali.widgets.weekview.class_widget_weekview; let widget_listview : _dali.widgets.listview.class_widget_listview; + /** + * @todo update sources + */ + const update = () => { + widget_weekview.update_entries(); + }; // hint { if (! await _dali.backend.is_logged_in()) @@ -176,6 +175,15 @@ namespace _dali.pages.overview } ); const action_select_event = (calendar_id, access_level, event_id) => { + /* + if (! await _dali.backend.is_logged_in()) + { + // do nothing + } + else + { + } + */ let read_only : boolean; switch (access_level) { @@ -304,6 +312,15 @@ namespace _dali.pages.overview { "action_select_event": action_select_event, "action_select_day": (date) => { + /* + if (! await _dali.backend.is_logged_in()) + { + // do nothing + } + else + { + } + */ (async () => { const widget = new _dali.widgets.event_edit.class_widget_event_edit( { diff --git a/source/resources/backend.ts b/source/resources/backend.ts index 9991f92..c0f6377 100644 --- a/source/resources/backend.ts +++ b/source/resources/backend.ts @@ -14,6 +14,16 @@ namespace _dali.backend /** */ + var _cache : ( + null + | + lib_plankton.cache.type_subject + ); + + + /** + * meant for translation of the API values + */ function access_level_encode( access_level : _dali.type.enum_access_level ) : ("none" | "view" | "edit" | "admin") @@ -28,6 +38,7 @@ namespace _dali.backend /** + * meant for translation of the API values */ function access_level_decode( access_level_encoded : ("none" | "view" | "edit" | "admin") @@ -52,6 +63,14 @@ namespace _dali.backend "corner": "zeitbild", } ); + _cache = lib_plankton.cache.make( + /* + lib_plankton.storage.memory.implementation_chest( + { + } + ) + */ + ); return Promise.resolve(undefined); } @@ -162,19 +181,27 @@ namespace _dali.backend /** + * @todo mneh … */ export async function is_logged_in( ) : Promise { - // return ((await get_session_key()) !== null); - const result : { - logged_in : boolean; - } = await call( - lib_plankton.http.enum_method.get, - "/session/status", - null + return lib_plankton.cache.get( + _cache, + "status", + 5, + async () => { + // return ((await get_session_key()) !== null); + const result : { + logged_in : boolean; + } = await call( + lib_plankton.http.enum_method.get, + "/session/status", + null + ); + return result.logged_in; + } ); - return result.logged_in; } @@ -596,12 +623,20 @@ namespace _dali.backend export async function events( from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, - options : { + { + "calendar_ids": calendar_ids = null, + } + : + { calendar_ids ?: (null | Array<_dali.type.calendar_id>); - } = {} + } + = + { + } ) : Promise< Array< { + key : _dali.type.event_key; calendar_id : _dali.type.calendar_id; calendar_name : string; hue : float; @@ -612,13 +647,6 @@ namespace _dali.backend > > { - options = Object.assign( - { - "calendar_ids": null, - }, - options - ); - return ( call( lib_plankton.http.enum_method.get, @@ -629,11 +657,11 @@ namespace _dali.backend "to": to_pit, }, ( - (options.calendar_ids === null) + (calendar_ids === null) ? {} : - {"calendar_ids": options.calendar_ids.join(",")} + {"calendar_ids": calendar_ids.join(",")} ) ) ) @@ -642,6 +670,7 @@ namespace _dali.backend data .map( (entry) => ({ + "key": entry.hash, "calendar_id": entry.calendar_id, "calendar_name": entry.calendar_name, "hue": entry.hue, diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index a6265a5..457d9e9 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -4,6 +4,7 @@ namespace _dali.widgets.weekview /** */ type type_entry = { + key : _dali.type.event_key; calendar_id : _dali.type.calendar_id; calendar_name : string; hue : float; @@ -32,24 +33,13 @@ namespace _dali.widgets.weekview { /** + * [dependency] */ private get_entries : type_get_entries; /** - */ - private container : (null | Element); - - - /** - */ - private event_map : lib_plankton.map.type_map< - _dali.type.event_id, - HTMLElement - >; - - - /** + * [hook] */ private action_select_event : ( ( @@ -63,6 +53,7 @@ namespace _dali.widgets.weekview /** + * [hook] */ private action_select_day : ( ( @@ -73,11 +64,55 @@ namespace _dali.widgets.weekview ); + /** + * [state] + */ + private year : int; + + + /** + * [state] + */ + private week : int; + + + /** + * [state] + */ + private count : int; + + + /** + * [state] + */ + private event_map : lib_plankton.map.type_map< + _dali.type.event_key, + { + element : HTMLElement; + hash : string; + } + >; + + + /** + * [state] + */ + private container : (null | Element); + + /** */ public constructor( get_entries : type_get_entries, - options : { + { + "action_select_day": action_select_day = ((date) => {}), + "action_select_event": action_select_event = ((calendar_id, access_level, event_id) => {}), + "initial_year": initial_year = null, + "initial_week": initial_week = null, + "initial_count": initial_count = 5, + } + : + { action_select_event ?: ( ( calendar_id : _dali.type.calendar_id, @@ -94,28 +129,70 @@ namespace _dali.widgets.weekview => void ); - } = {} + initial_year ?: (null | int); + initial_week ?: (null | int); + initial_count ?: int; + } + = + {} ) { - options = Object.assign( - { - "action_select_day": (date) => {}, - "action_select_event": (calendar_id, access_level, event_id) => {}, - }, - options - ); + // dependencies this.get_entries = get_entries; - this.container = null; + + // hooks + this.action_select_day = action_select_day; + this.action_select_event = action_select_event; + + // state + const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now()); + this.year = ( + initial_year + ?? + ywd_now.year + ); + this.week = ( + initial_week + ?? + Math.max(0, (ywd_now.week - 1)) + ); + this.count = initial_count; this.event_map = lib_plankton.map.hashmap.implementation_map( lib_plankton.map.hashmap.make< - _dali.type.event_id, - HTMLElement + _dali.type.event_key, + { + element : HTMLElement; + hash : string; + } >( - event_id => event_id + event_key => event_key ) ); - this.action_select_day = options.action_select_day; - this.action_select_event = options.action_select_event; + this.container = null; + } + + + /** + * some kind of checksum for comparing entries + * @todo base64 encode? + * @todo sha256 hash? + */ + private static entry_hash( + entry : type_entry + ) : string + { + return lib_plankton.call.convey( + { + "calendar_id": entry.calendar_id, + "calendar_name": entry.calendar_name, + "hue": Math.floor(entry.hue * 0xFFFF), + "access_level": entry.access_level, + "event_object": entry.event_object, + }, + [ + x => lib_plankton.json.encode(x), + ] + ); } @@ -216,885 +293,479 @@ namespace _dali.widgets.weekview /** */ - private create_dom_entry( - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - /** - * @todo replace with hue? - */ - color : lib_plankton.color.type_color; - } - >, - // event_entry : _dali.type.event_entry - entry : type_entry - ) : HTMLElement - { - return _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell-entry", - { - "color": lib_plankton.color.output_hex( - sources.get( - entry.calendar_id - ).color - ), - "title": class_widget_weekview.event_generate_tooltip( - sources.get( - entry.calendar_id - ).name, - entry.event_object - ), - "name": entry.event_object.name, - "rel": lib_plankton.string.coin( - "{{calendar_id}}/{{event_id}}/{{access_level}}", - { - "calendar_id": entry.calendar_id.toFixed(0), - "event_id": ( - (entry.event_id === null) - ? - "-" - : - entry.event_id.toFixed(0) - ), - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) - { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - "additional_classes": lib_plankton.string.coin( - " access_level-{{access_level}}", - { - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) - { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - } - ); - } - - - /** - */ - private add_entry( - event_entry : _dali.type.event_entry - ) : void - { - const dom_entry : HTMLElement = this.create_dom_entry(event_entry); - /** - * @todo insert dom_entry into DOM - */ - this.event_map.set( - event_entry.id, - dom_entry - ); - } - - - /** - */ - private update_entry( - event_id : _dali.type.event_id, - event_entry : _dali.type.event_entry - ) : void - { - const dom_entry_old : HTMLElement = lib_plankton.map.get( - this.event_map, - event_id - ); - /** - * @todo remove dom_entry_old from DOM - */ - const dom_entry_new : HTMLElement = this.create_dom_entry(event_entry); - /** - * @todo insert dom_entry_new into DOM - */ - this.event_map.set( - event_id, - dom_entry_new - ); - } - - - /** - */ - private remove_entry( - event_id : _dali.type.event_id - ) : void - { - const dom_entry : HTMLElement = lib_plankton.map.get( - this.event_map, - event_id - ); - /** - * @todo remove dom_entry from DOM - */ - this.event_map.delete( - event_id - ); - } - - - /** - */ - private sync_events( - events : Array<_dali.type.event_entry> - ) : void - { - const track : Record< - string, - { - external : (null | _dali.type.event_entry); - internal : (null | _dali.type.event_id); - } - > = {}; - events.forEach( - event => { - const key : string = event.id; - if (! (key in track)) track[key] = {"external": null, "internal": null}; - track[key].external = event; - } - ); - lib_plankton.map.dump(this.event_map).forEach( - pair => { - const key : string = pair.key; - if (! (key in track)) track[key] = {"external": null, "internal": null}; - track[key].internal = pair.key; - } - ); - - Object.entries(track).forEach( - ([key, value]) => { - if ( - ! (value.external !== null) - && - ! (value.internal !== null) - ) - { - throw (new Error("impossible!")); - } - else if ( - (value.external !== null) - && - ! (value.internal !== null) - ) - { - this.add_entry(value.external); - } - else if ( - (value.external !== null) - && - (value.internal !== null) - ) - { - this.update_entry(value.internal, value.external); - } - else if ( - ! (value.external !== null) - && - (value.internal !== null) - ) - { - this.remove_entry(value.external.id); - } - else - { - throw (new Error("impossible!")); - } - } - ); - } - - - /** - * @todo kein "while" - */ - private async calendar_view_table_data( - calendar_ids : ( - null - | - Array<_dali.type.calendar_id> - ), - from : { - year : int; - week : int; - }, - to : { - year : int; - week : int; - }, - timezone_shift : int - ) : Promise< + private get_entries_wrapped( { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - > + "calendar_ids": calendar_ids = null, + "timezone_shift": timezone_shift = 0, } - > + : + { + calendar_ids ?: (null | Array<_dali.type.calendar_id>); + timezone_shift ?: int; + } + = + { + } + ) + : Promise> { - const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); - const from_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( - { - "year": (from as {year : int; week : int}).year, - "week": (from as {year : int; week : int}).week, - "day": 1, - }, - { - "timezone_shift": (timezone_shift as int), - } - ); - const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( - { - "year": (to as {year : int; week : int}).year, - "week": (to as {year : int; week : int}).week, - "day": 1, - }, - { - "timezone_shift": (timezone_shift as int), - } - ); - - // prepare - const entries : Array = await this.get_entries( - from_pit, - to_pit, + return this.get_entries( + lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": this.week, + "day": 1, + }, + { + "timezone_shift": timezone_shift, + } + ), + lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": (this.week + this.count), + "day": 1, + }, + { + "timezone_shift": timezone_shift, + } + ), calendar_ids ); - let result : { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - >; - } = { - "sources": lib_plankton.map.hashmap.implementation_map( - lib_plankton.map.hashmap.make( - x => x.toFixed(0), - { - "pairs": ( - entries - .map( - (entry) => ( - { - "key": entry.calendar_id, - "value": { - "name": entry.calendar_name, - "access_level": entry.access_level, - "hue": entry.hue, - } - } - ) - ) - ) - } - ) - ), - "rows": [], - }; - let row : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - > = []; - let day : int = 0; - while (true) - { - const pit_current : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day( - from_pit, - day - ); - if ( - lib_plankton.pit.is_before( - pit_current, - to_pit - ) - ) - { - day += 1; - row.push( - { - "pit": pit_current, - "entries": [], - "today": false, // TODO - } - ); - if (day % 7 === 0) - { - result.rows.push( - { - "week": ( - (from as {year : int; week : int}).week - + - Math.floor(day / 7) - - - 1 // TODO - ), - "data": row - } - ); - row = []; - } - else - { - // do nothing - } - } - else - { - break; - } - } - - // fill - { - // events - ( - entries - .forEach( - (entry) => { - const distance_seconds : int = ( - lib_plankton.pit.from_datetime( - /** - * so that events without a start time will be put in the correct box - */ - { - "timezone_shift": entry.event_object.begin.timezone_shift, - "date": entry.event_object.begin.date, - "time": ( - entry.event_object.begin.time - ?? - { - "hour": 12, - "minute": 0, - "second": 0 - } - ), - } - ) - - - from_pit - ); - const distance_days : int = (distance_seconds / (60 * 60 * 24)); - - const week : int = Math.floor(Math.floor(distance_days) / 7); - const day : int = (Math.floor(distance_days) % 7); - - if ((week >= 0) && (week < result.rows.length)) { - result.rows[week].data[day].entries.push(entry); - } - else { - // do nothing - } - } - ) - ); - // today - { - const distance_seconds : int = ( - now_pit - - - from_pit - ); - const distance_days : int = (distance_seconds / (60 * 60 * 24)); - - const week : int = Math.floor(Math.floor(distance_days) / 7); - const day : int = (Math.floor(distance_days) % 7); - - if ((week >= 0) && (week < result.rows.length)) { - result.rows[week].data[day].today = true; - } - else { - // do nothing - } - } - } - - return Promise.resolve(result); } /** */ - private async table_rows( - options : { - calendar_ids ?: ( - null - | - Array<_dali.type.calendar_id> - ); - from ?: { - year : int; - week : int; - }; - to ?: { - year : int; - week : int; - }; - timezone_shift ?: int; - action_select ?: ( - ( - calendar_id : _dali.type.calendar_id, - event_id : _dali.type.local_resource_event_id - ) - => - void - ) - } = {} - ) : Promise + private async entry_insert( + entry : type_entry + ) : Promise<(null | HTMLElement)> { - const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); - options = Object.assign( + const selector : string = lib_plankton.string.coin( + ".weekview-cell[rel=\"{{rel}}\"] > .weekview-events", { - "calendar_ids": null, - "from": lib_plankton.call.convey( - now_pit, - [ - (x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, -1), - lib_plankton.pit.to_ywd, - x => ({"year": x.year, "week": x.week}), - ] - ), - "to": lib_plankton.call.convey( - now_pit, - [ - (x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, +4), - lib_plankton.pit.to_ywd, - x => ({"year": x.year, "week": x.week}), - ] - ), - "timezone_shift": 0, - }, - options - ); - const stuff : { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - >; - } = await this.calendar_view_table_data( - options.calendar_ids, - options.from, - options.to, - options.timezone_shift - ); - const sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - color : lib_plankton.color.type_color; + "rel": lib_plankton.pit.date_format(entry.event_object.begin.date), } - > = lib_plankton.map.hashmap.implementation_map( - lib_plankton.map.hashmap.make( - (x => x.toFixed(0)), - { - "pairs": ( - lib_plankton.map.dump( - stuff.sources - ) - .map( - (pair) => ({ - "key": pair.key, - "value": { - "name": pair.value.name, - "access_level": pair.value.access_level, - "color": lib_plankton.color.make_hsv( - { - "hue": pair.value.hue, - "saturation": 0.375, - "value": 0.375, - } - ), - } - }) - ) - ) - } - ) ); - return ( - await _dali.helpers.promise_row( - stuff.rows - .map( - (row) => async () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-row", + const dom_cell = this.container.querySelector(selector); + if (dom_cell === null) + { + console.warn("entry out of scope"); + return null; + } + else + { + let dom_dummy : HTMLElement = document.createElement("div"); + dom_dummy.innerHTML = await _dali.helpers.template_coin( + "widget-weekview", + "tableview-cell-entry", + { + "color": lib_plankton.color.output_hex( + lib_plankton.color.make_hsv( + { + "hue": entry.hue, + /** + * @todo as constant + */ + "saturation": 0.375, + /** + * @todo as constant + */ + "value": 0.375, + } + ) + ), + "title": class_widget_weekview.event_generate_tooltip( + entry.calendar_name, + entry.event_object + ), + "name": entry.event_object.name, + "rel": lib_plankton.string.coin( + "{{calendar_id}}/{{event_id}}/{{access_level}}", { - "week": row.week.toFixed(0).padStart(2, "0"), - "cells": ( - await _dali.helpers.promise_row( - row.data - .map( - (cell) => async () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell", - { - "extra_classes": ( - [""] - .concat(cell.today ? ["weekview-cell-today"] : []) - .join(" ") - ), - "title": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{year}}-{{month}}-{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ), - ] - ), - "day": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ), - ] - ), - "rel": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{year}}-{{month}}-{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ) - ] - ), - "entries": ( - await _dali.helpers.promise_row( - cell.entries - .map( - (entry) => () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell-entry", - { - "color": lib_plankton.color.output_hex( - sources.get( - entry.calendar_id - ).color - ), - "title": class_widget_weekview.event_generate_tooltip( - sources.get( - entry.calendar_id - ).name, - entry.event_object - ), - "name": entry.event_object.name, - "rel": lib_plankton.string.coin( - "{{calendar_id}}/{{event_id}}/{{access_level}}", - { - "calendar_id": entry.calendar_id.toFixed(0), - "event_id": ( - (entry.event_id === null) - ? - "-" - : - entry.event_id.toFixed(0) - ), - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - "additional_classes": lib_plankton.string.coin( - " access_level-{{access_level}}", - { - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - } - ) - ) - ) - ).join(""), - } - ) - ) - ) - ).join(""), + "calendar_id": entry.calendar_id.toFixed(0), + "event_id": ( + (entry.event_id === null) + ? + "-" + : + entry.event_id.toFixed(0) + ), + "access_level": _dali.access_level_encode(entry.access_level), } - ) - ) - ) - ).join(""); + ), + "additional_classes": lib_plankton.string.coin( + " access_level-{{access_level}}", + { + "access_level": _dali.access_level_encode(entry.access_level), + } + ), + } + ); + const dom_entry : HTMLElement = dom_dummy.querySelector(".weekview-event_entry"); + + // listener + dom_entry.addEventListener( + "click", + (event) => { + const rel : string = dom_entry.getAttribute("rel"); + const parts : Array = rel.split("/"); + const calendar_id : _dali.type.calendar_id = parseInt(parts[0]); + const event_id : (null | _dali.type.local_resource_event_id) = ( + (parts[1] === "-") + ? + null + : + parseInt(parts[1]) + ); + const access_level : _dali.type.enum_access_level = _dali.access_level_decode(parts[2]); + this.action_select_event( + calendar_id, + access_level, + event_id + ); + } + ); + + // emplace + dom_cell.appendChild(dom_entry); + + return dom_entry; + } } /** */ - private async update( - year : int, - week : int, - count : int, - options : { - update_controls ?: boolean; - } = {} + private async entry_add( + entry : type_entry ) : Promise { - options = Object.assign( - { - "update_controls": true, - }, - options - ); - const context : Element = this.container; - // controls + const dom_entry : (null | HTMLElement) = await this.entry_insert(entry); + if (dom_entry === null) { - if (! options.update_controls) - { - // do nothing - } - else - { - (context.querySelector(".weekview-control-year > input") as HTMLInputElement).value = year.toFixed(0); - (context.querySelector(".weekview-control-week > input") as HTMLInputElement).value = week.toFixed(0); - (context.querySelector(".weekview-control-count > input") as HTMLInputElement).value = count.toFixed(0); - } + // do nothing } - // table + else { - context.querySelector(".weekview-table tbody").innerHTML = await this.table_rows( + this.event_map.set( + entry.key, { - "calendar_ids": null, - // TODO - "from": { - "year": year, - "week": week - }, - // TODO - "to": { - "year": year, - "week": (week + count) - }, - "timezone_shift": /*conf.timezone_shift*/0, + "element": dom_entry, + "hash": class_widget_weekview.entry_hash(entry), } ); - // cells - { - if (! await _dali.backend.is_logged_in()) - { - // do nothing - } - else - { - context.querySelectorAll(".weekview-cell-regular").forEach( - (element) => { - element.addEventListener( - "click", - (event) => { - if (! (element === event.target)) - { - // do nothing - } - else - { - const rel : string = element.getAttribute("rel"); - const parts : Array = rel.split("-"); - const date : lib_plankton.pit.type_date = { - "year": parseInt(parts[0]), - "month": parseInt(parts[1]), - "day": parseInt(parts[2]), - }; - this.action_select_day(date); - } - } - ); - } - ); - } - } - // events - { - if (! await _dali.backend.is_logged_in()) - { - // do nothing - } - else - { - context.querySelectorAll(".weekview-event_entry").forEach( - (element) => { - element.addEventListener( - "click", - () => { - const rel : string = element.getAttribute("rel"); - const parts : Array = rel.split("/"); - const calendar_id : _dali.type.calendar_id = parseInt(parts[0]); - const event_id : (null | _dali.type.local_resource_event_id) = ( - (parts[1] === "-") - ? - null - : - parseInt(parts[1]) - ); - const access_level : _dali.type.enum_access_level = (() => { - switch (parts[2]) { - case "none": return _dali.type.enum_access_level.none; - case "view": return _dali.type.enum_access_level.view; - case "edit": return _dali.type.enum_access_level.edit; - case "admin": return _dali.type.enum_access_level.admin; - } - }) (); - this.action_select_event( - calendar_id, - access_level, - event_id - ); - } - ); - } - ); - } - } } - return Promise.resolve(undefined); } /** */ - private react_on_control_change( - dom_context : Element - ) : void + private async entry_update( + key : _dali.type.event_key, + entry : type_entry + ) : Promise { - const year : int = parseInt((dom_context.querySelector(".weekview-control-year > input") as HTMLInputElement).value); - const week : int = parseInt((dom_context.querySelector(".weekview-control-week > input") as HTMLInputElement).value); - const count : int = parseInt((dom_context.querySelector(".weekview-control-count > input") as HTMLInputElement).value); - this.update( - year, - week, - count, + if (! this.event_map.has(key)) + { + console.warn("event missing: " + key); + } + else + { + const value = this.event_map.get(key); + const hash_old : string = value.hash; + const hash_new : string = class_widget_weekview.entry_hash(entry); + if (hash_old === hash_new) { - "update_controls": false, + // do nothing + // console.info("nothing to update", {"key": key, "entry": entry, "element": value.element}); } + else + { + const dom_entry_old : HTMLElement = value.element; + dom_entry_old.remove(); + const dom_entry_new : (null | HTMLElement) = await this.entry_insert(entry); + if (dom_entry_new === null) + { + // do nothing + } + else + { + this.event_map.set( + entry.key, + { + "element": dom_entry_new, + "hash": hash_new, + } + ); + } + } + } + } + + + /** + */ + private async entry_remove( + key : _dali.type.event_key + ) : Promise + { + if (! this.event_map.has(key)) + { + // do nothing + // console.warn("not in map", {"key": key, "map": lib_plankton.map.dump(this.event_map)}); + } + else + { + const value = this.event_map.get( + key + ); + this.event_map.delete( + key + ); + value.element.remove(); + } + } + + + /** + */ + public async update_entries( + ) : Promise + { + const entries : Array = await this.get_entries_wrapped( ); + + const contrast = lib_plankton.list.contrast< + any, + type_entry + >( + lib_plankton.map.dump(this.event_map), + pair => pair.key, + entries, + event => event.key + ); + await Promise.all( + [] + // remove + .concat( + contrast.only_left.map( + ({"key": key, "left": left}) => this.entry_remove(key) + ) + ) + // update + .concat( + contrast.both.map( + ({"key": key, "left": left, "right": right}) => this.entry_update(key, right) + ) + ) + // add + .concat( + contrast.only_right.map( + ({"key": key, "right": right}) => this.entry_add(right) + ) + ) + ); + } + + + /** + */ + 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); + } + + + /** + */ + private async update_table( + ) : Promise + { + /** + * @todo avoid? + */ + lib_plankton.map.clear(this.event_map); + const context : Element = this.container; + // structure + { + /** + * @todo als Variable? + */ + const timezone_shift : int = 0; + const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); + const today_begin_pit : lib_plankton.pit.type_pit = lib_plankton.pit.trunc_day(now_pit); + const today_end_pit : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day(today_begin_pit, 1); + const row_data : Array< + { + week : int; + data : Array< + { + pit : lib_plankton.pit.type_pit; + today : boolean; + } + >; + } + > = ( + lib_plankton.list.sequence(this.count) + .map( + offset => { + const week : int = (this.week + offset); + return { + "week": week, + "data": ( + lib_plankton.list.sequence(7) + .map( + day => { + const day_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": week, + "day": (day + 1), + }, + { + "timezone_shift": timezone_shift, + } + ); + return { + "pit": day_pit, + "today": lib_plankton.pit.is_between( + day_pit, + today_begin_pit, + today_end_pit + ), + }; + } + ) + ), + }; + } + ) + ); + context.querySelector(".weekview-table tbody").innerHTML = ( + await _dali.helpers.promise_row( + row_data + .map( + (row) => async () => _dali.helpers.template_coin( + "widget-weekview", + "tableview-row", + { + "week": row.week.toFixed(0).padStart(2, "0"), + "cells": ( + await _dali.helpers.promise_row( + row.data + .map( + (cell) => async () => _dali.helpers.template_coin( + "widget-weekview", + "tableview-cell", + { + "extra_classes": ( + [""] + .concat(cell.today ? ["weekview-cell-today"] : []) + .join(" ") + ), + "title": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{year}}-{{month}}-{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ), + ] + ), + "day": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ), + ] + ), + "rel": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{year}}-{{month}}-{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ) + ] + ), + "entries": "" + } + ) + ) + ) + ).join(""), + } + ) + ) + ) + ).join(""); + } + // listeners + { + context.querySelectorAll(".weekview-cell-regular").forEach( + (element) => { + element.addEventListener( + "click", + (event) => { + if (! (element === event.target)) + { + // do nothing + } + else + { + const rel : string = element.getAttribute("rel"); + const parts : Array = rel.split("-"); + const date : lib_plankton.pit.type_date = { + "year": parseInt(parts[0]), + "month": parseInt(parts[1]), + "day": parseInt(parts[2]), + }; + this.action_select_day(date); + } + } + ); + } + ); + } } /** */ public toggle_visibility( - calendar_id : _dali.type.calendar_id + calendar_id : _dali.type.calendar_id, + { + "mode": mode = null, + } + : + { + mode ?: (null | boolean); + } + = + { + } ) : void { this.container.querySelectorAll(".weekview-event_entry").forEach( @@ -1108,7 +779,7 @@ namespace _dali.widgets.weekview } else { - element.classList.toggle("weekview-cell-hidden"); + element.classList.toggle("weekview-cell-hidden", mode ?? undefined); } } ); @@ -1142,48 +813,43 @@ namespace _dali.widgets.weekview this.container = target_element.querySelector(".weekview"); // controls { - target_element.querySelector(".weekview-control-apply").addEventListener( - "click", - (event) => { - event.preventDefault(); - this.react_on_control_change(target_element); - } - ); - if (false) - { - [ - "year", - "week", - "count", - ].forEach( - (name) => { - const selector : string = (".weekview-control-" + name + " > input"); - (target_element.querySelector(selector) as HTMLInputElement).addEventListener( - "change", - (event) => { - event.preventDefault(); - this.react_on_control_change(target_element); - } - ); - } - ); - } - } - // table - { - const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now()); - let year : int = ywd_now.year; - let week : int = Math.max(0, (ywd_now.week - 1)); - let count : int = 5; - await this.update( - year, - week, - count, + [ { - "update_controls": true, + "name": "year", + "transform": parseInt, + "write": x => {this.year = x;} + }, + { + "name": "week", + "transform": parseInt, + "write": x => {this.week = x;} + }, + { + "name": "count", + "transform": parseInt, + "write": x => {this.count = x;} + }, + ].forEach( + (entry) => { + const selector : string = (".weekview-control-" + entry.name + " > input"); + const element : HTMLInputElement = (target_element.querySelector(selector) as HTMLInputElement); + element.addEventListener( + "change", + async (event) => { + event.preventDefault(); + const value : int = entry.transform(element.value); + entry.write(value); + await this.update_table(); + await this.update_entries(); + } + ); } ); } + await this.update_controls(); + await this.update_table(); + await this.update_entries(); + return Promise.resolve(undefined); } diff --git a/source/widgets/weekview/templates/main.html.tpl b/source/widgets/weekview/templates/main.html.tpl index 0ebb5f0..30a6ee5 100644 --- a/source/widgets/weekview/templates/main.html.tpl +++ b/source/widgets/weekview/templates/main.html.tpl @@ -12,7 +12,6 @@ {{label_control_count}} -
From c6d868b2438054cb2d25aa31c0c1696ea88ce6aa Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 23:04:31 +0200 Subject: [PATCH 07/11] [task-408] [mod] Quellen-Steuerelement umgestellt --- source/data/localization/deu.loc.json | 1 + source/data/localization/eng.loc.json | 1 + source/pages/overview/logic.ts | 22 +- source/style/main.css | 6 +- source/style/widget-sources.css | 6 +- source/widgets/sources/logic.ts | 326 +++++++++++------- .../widgets/sources/templates/main.html.tpl | 9 +- 7 files changed, 233 insertions(+), 138 deletions(-) diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index 52bdc5b..dfc7be8 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -55,6 +55,7 @@ "widget.event_edit.actions.add": "anlegen", "widget.event_edit.actions.change": "ändern", "widget.event_edit.actions.remove": "löschen", + "widget.sources.create": "anlegen", "page.login.title": "Anmelden", "page.login.internal.name": "Name", "page.login.internal.password": "Kennwort", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index 023e8d2..4daaa92 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -55,6 +55,7 @@ "widget.event_edit.actions.add": "add", "widget.event_edit.actions.change": "change", "widget.event_edit.actions.remove": "delete", + "widget.sources.create": "create", "page.login.title": "Login", "page.login.internal.name": "name", "page.login.internal.password": "password", diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index 94bbb20..7a6aa6c 100644 --- a/source/pages/overview/logic.ts +++ b/source/pages/overview/logic.ts @@ -57,13 +57,15 @@ namespace _dali.pages.overview await widget_mode_switcher.load(target_element.querySelector("#overview-mode")); } + 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 update sources */ - const update = () => { - widget_weekview.update_entries(); + const update = async () => { + await widget_weekview.update_entries(); + await widget_sources.update(); }; // hint { @@ -78,17 +80,8 @@ namespace _dali.pages.overview } // sources { - const data : Array< - { - id : _dali.type.calendar_id; - name : string; - hue : float; - access_level : _dali.type.enum_access_level; - } - > = await _dali.backend.calendar_list( - ); - const widget_sources = new _dali.widgets.sources.class_widget_sources( - data, + widget_sources = new _dali.widgets.sources.class_widget_sources( + _dali.backend.calendar_list, { "action_open": (entry) => { let read_only : boolean; @@ -161,6 +154,9 @@ namespace _dali.pages.overview widget_weekview.toggle_visibility(entry.id); widget_listview.toggle_visibility(entry.id); }, + "action_create": () => { + console.warn("not implemented: calendar_add widget"); + }, } ); await widget_sources.load(target_element.querySelector("#overview-pane-left")); diff --git a/source/style/main.css b/source/style/main.css index ea384f0..2f85e01 100644 --- a/source/style/main.css +++ b/source/style/main.css @@ -89,12 +89,14 @@ nav a:hover a { text-decoration: none; - color: hsl(var(--hue), 50%, 50%); + color: hsl(var(--hue), 0%, 87.5%); } a:hover { - color: hsl(var(--hue), 50%, 75%); + color: hsl(var(--hue), 0%, 100%); + border-bottom: 2px solid hsl(0, 0%, 100%); + transition: 1s ease color; } input,select,textarea diff --git a/source/style/widget-sources.css b/source/style/widget-sources.css index bfcb618..efc6e46 100644 --- a/source/style/widget-sources.css +++ b/source/style/widget-sources.css @@ -1,9 +1,13 @@ .sources +{ + font-size: 0.75em; +} + +.sources-entries { margin: 0; padding: 0; list-style-type: none; - font-size: 0.75em; } .sources-entry diff --git a/source/widgets/sources/logic.ts b/source/widgets/sources/logic.ts index 01c082c..53a4931 100644 --- a/source/widgets/sources/logic.ts +++ b/source/widgets/sources/logic.ts @@ -17,53 +17,230 @@ namespace _dali.widgets.sources { /** + * [dependency] */ - private keys : Array; - - - /** - */ - private data : Record; + private get_entries : (() => Promise>); /** + * [hook] */ private action_open : ((entry : type_entry) => void); /** + * [hook] */ private action_toggle_visibility : ((entry : type_entry) => void); + /** + * [hook] + */ + private action_create : (() => void); + + + /** + * [state] + */ + private container : (null | Element); + + /** */ public constructor( - entries : Array, - options : { + get_entries : (() => Promise>), + { + "action_open": action_open = ((calendar_id) => {}), + "action_toggle_visibility": action_toggle_visibility = ((calendar_id) => {}), + "action_create": action_create = (() => {}), + } + : + { action_open ?: ((entry : type_entry) => void); action_toggle_visibility ?: ((entry : type_entry) => void); - } = {} + action_create ?: (() => void); + } + = + { + } ) { - options = Object.assign( - { - "action_open": (calendar_id) => {}, - "action_toggle_visibility": (calendar_id) => {}, - }, - options + // dependencies + this.get_entries = get_entries; + + // hooks + this.action_open = action_open; + this.action_toggle_visibility = action_toggle_visibility; + this.action_create = action_create; + + // state + this.container = null; + } + + + /** + */ + private static id_encode( + id : _dali.type.calendar_id + ) + : string + { + return id.toFixed(0); + } + + + /** + */ + private static id_decode( + representation : string + ) + : _dali.type.calendar_id + { + return parseInt(representation); + } + + + /** + */ + public async update( + ) + : Promise + { + const data : lib_plankton.map.type_map<_dali.type.calendar_id, type_entry> = lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make( + calendar_id => class_widget_sources.id_encode(calendar_id), + { + "pairs": ( + (await this.get_entries()) + .map( + entry => ({ + "key": entry.id, + "value": entry, + }) + ) + ), + } + ) ); - this.keys = []; - this.data = {}; - entries.forEach( - (entry) => { - const key : string = entry.id.toFixed(0); - this.keys.push(key); - this.data[key] = entry; - } - ); - this.action_open = options.action_open; - this.action_toggle_visibility = options.action_toggle_visibility; + // structure + { + this.container.innerHTML = await _dali.helpers.template_coin( + "widget-sources", + "main", + { + "label_create": lib_plankton.translate.get("widget.sources.create"), + "entries": ( + ( + await _dali.helpers.promise_row( + lib_plankton.map.dump(data) + .map( + (pair) => () => { + return _dali.helpers.template_coin( + "widget-sources", + "entry", + { + "name": pair.value.name, + "label_toggle": lib_plankton.string.coin( + "{{show}}/{{hide}}", + { + "show": lib_plankton.translate.get("common.show"), + "hide": lib_plankton.translate.get("common.hide"), + } + ), + "label_edit": lib_plankton.translate.get("common.edit"), + // "access_level": entry.access_level, // TODO + // TODO centralize + "color_head": lib_plankton.color.output_hex( + lib_plankton.color.make_hsv( + { + "hue": pair.value.hue, + /** + * @todo const and outsource + */ + "saturation": 0.375, + /** + * @todo const and outsource + */ + "value": 0.375, + } + ), + ), + "color_body": lib_plankton.color.output_hex( + lib_plankton.color.make_hsv( + { + "hue": pair.value.hue, + /** + * @todo const and outsource + */ + "saturation": 0.375, + /** + * @todo const and outsource + */ + "value": 0.25, + } + ), + ), + "rel": class_widget_sources.id_encode(pair.key), + } + ); + } + ) + ) + ) + .join("") + ), + } + ); + } + // listeners + { + this.container.querySelector(".sources-create").addEventListener( + "click", + (event) => { + event.preventDefault(); + this.action_create(); + } + ); + this.container.querySelectorAll(".sources-entry-head").forEach( + (element) => { + element.addEventListener( + "click", + (event) => { + element.parentElement.classList.toggle("sources-entry-open"); + } + ); + } + ); + this.container.querySelectorAll(".sources-entry-toggle").forEach( + (element) => { + element.addEventListener( + "click", + () => { + const key_encoded : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); + const calendar_id : _dali.type.calendar_id = class_widget_sources.id_decode(key_encoded); + const entry : type_entry = data.get(calendar_id); + element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-hidden"); + element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-open", false); + this.action_toggle_visibility(entry); + } + ); + } + ); + this.container.querySelectorAll(".sources-entry-edit").forEach( + (element) => { + element.addEventListener( + "click", + (event) => { + const key_encoded : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); + const calendar_id : _dali.type.calendar_id = class_widget_sources.id_decode(key_encoded); + const entry : type_entry = data.get(calendar_id); + this.action_open(entry); + } + ); + } + ); + } } @@ -72,100 +249,11 @@ namespace _dali.widgets.sources */ public async load( target_element : Element - ) : Promise + ) + : Promise { - target_element.innerHTML = await _dali.helpers.template_coin( - "widget-sources", - "main", - { - "entries": ( - ( - await _dali.helpers.promise_row( - this.keys - .map( - (key) => () => { - const entry : type_entry = this.data[key]; - return _dali.helpers.template_coin( - "widget-sources", - "entry", - { - "name": entry.name, - "label_toggle": lib_plankton.string.coin( - "{{show}}/{{hide}}", - { - "show": lib_plankton.translate.get("common.show"), - "hide": lib_plankton.translate.get("common.hide"), - } - ), - "label_edit": lib_plankton.translate.get("common.edit"), - // "access_level": entry.access_level, // TODO - // TODO centralize - "color_head": lib_plankton.color.output_hex( - lib_plankton.color.make_hsv( - { - "hue": entry.hue, - "saturation": 0.375, - "value": 0.375, - } - ), - ), - "color_body": lib_plankton.color.output_hex( - lib_plankton.color.make_hsv( - { - "hue": entry.hue, - "saturation": 0.375, - "value": 0.25, - } - ), - ), - "rel": key, - } - ); - } - ) - ) - ) - .join("") - ), - } - ); - target_element.querySelectorAll(".sources-entry-head").forEach( - (element) => { - element.addEventListener( - "click", - (event) => { - element.parentElement.classList.toggle("sources-entry-open"); - } - ); - } - ); - target_element.querySelectorAll(".sources-entry-toggle").forEach( - (element) => { - element.addEventListener( - "click", - () => { - const key : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); - const entry : type_entry = this.data[key]; - element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-hidden"); - element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-open", false); - this.action_toggle_visibility(entry); - } - ); - } - ); - target_element.querySelectorAll(".sources-entry-edit").forEach( - (element) => { - element.addEventListener( - "click", - (event) => { - const key : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); - const entry : type_entry = this.data[key]; - this.action_open(entry); - } - ); - } - ); - return Promise.resolve(undefined); + this.container = target_element; + await this.update(); } } diff --git a/source/widgets/sources/templates/main.html.tpl b/source/widgets/sources/templates/main.html.tpl index 3f9a9a7..ba432fd 100644 --- a/source/widgets/sources/templates/main.html.tpl +++ b/source/widgets/sources/templates/main.html.tpl @@ -1,3 +1,6 @@ -
    - {{entries}} -
+
+
    + {{entries}} +
+ {{label_create}} +
From c6f3d9fd578374d8eb685078f9040104cb129246 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 23:18:30 +0200 Subject: [PATCH 08/11] =?UTF-8?q?[task-408]=20[mod]=20Kalender-Hinzuf?= =?UTF-8?q?=C3=BCgen-Seite=20entfernt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/data/localization/deu.loc.json | 1 + source/data/localization/eng.loc.json | 1 + source/main.ts | 12 - source/pages/calendar_add/logic.ts | 228 ------------------ .../calendar_add/templates/default.html.tpl | 5 - source/pages/calendar_edit/logic.ts | 68 ------ .../calendar_edit/templates/default.html.tpl | 5 - source/pages/overview/logic.ts | 48 +++- source/widgets/calendar_edit/logic.ts | 24 ++ tools/makefile | 18 -- 10 files changed, 71 insertions(+), 339 deletions(-) delete mode 100644 source/pages/calendar_add/logic.ts delete mode 100644 source/pages/calendar_add/templates/default.html.tpl delete mode 100644 source/pages/calendar_edit/logic.ts delete mode 100644 source/pages/calendar_edit/templates/default.html.tpl diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index dfc7be8..957d735 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -50,6 +50,7 @@ "widget.weekview.controls.week": "Woche", "widget.weekview.controls.count": "Anzahl", "widget.weekview.controls.apply": "Laden", + "widget.calendar_edit.actions.add": "anlegen", "widget.calendar_edit.actions.change": "ändern", "widget.calendar_edit.actions.remove": "löschen", "widget.event_edit.actions.add": "anlegen", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index 4daaa92..ab7151c 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -50,6 +50,7 @@ "widget.weekview.controls.week": "Week", "widget.weekview.controls.count": "Count", "widget.weekview.controls.apply": "Load", + "widget.calendar_edit.actions.add": "add", "widget.calendar_edit.actions.change": "change", "widget.calendar_edit.actions.remove": "delete", "widget.event_edit.actions.add": "add", diff --git a/source/main.ts b/source/main.ts index 6998722..df3090d 100644 --- a/source/main.ts +++ b/source/main.ts @@ -82,23 +82,11 @@ namespace _dali "label": lib_plankton.translate.get("page.overview.title"), "groups": ["logged_out", "logged_in"], }, - { - "location": {"name": "calendar_add", "parameters": {}}, - "label": lib_plankton.translate.get("page.calendar_add.title"), - "groups": ["logged_in"], - }, { "location": {"name": "caldav", "parameters": {}}, "label": lib_plankton.translate.get("page.caldav.title"), "groups": ["logged_in"], }, - /* - { - "location": {"name": "event_add", "parameters": {}}, - "label": lib_plankton.translate.get("page.event_add.title"), - "groups": ["logged_in"], - } - */ { "location": {"name": "logout", "parameters": {}}, "label": lib_plankton.translate.get("page.logout.title"), diff --git a/source/pages/calendar_add/logic.ts b/source/pages/calendar_add/logic.ts deleted file mode 100644 index 7438bb4..0000000 --- a/source/pages/calendar_add/logic.ts +++ /dev/null @@ -1,228 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "calendar_add", - async (parameters, target_element) => { - target_element.innerHTML = ""; - target_element.innerHTML = await _dali.helpers.template_coin( - "calendar_add", - "default", - { - "label": lib_plankton.translate.get("page.calendar_add.title") - } - ); - const form : lib_plankton.zoo_form.class_form< - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - }, - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - } - > = new lib_plankton.zoo_form.class_form< - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - }, - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - } - >( - (value) => value, - (raw) => raw, - new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text(), - "label": lib_plankton.translate.get("calendar.name") - }, - { - "name": "hue", - "input": new lib_plankton.zoo_input.class_input_hue( - ), - "label": lib_plankton.translate.get("calendar.hue"), - }, - { - "name": "access", - "input": new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "public", - "input": new lib_plankton.zoo_input.class_input_checkbox(), - "label": lib_plankton.translate.get("calendar.access.public"), - }, - { - "name": "default_level", - "input": _dali.helpers.input_access_level(), - "label": lib_plankton.translate.get("calendar.access.default_level"), - }, - { - "name": "attributed", - "input": await _dali.helpers.input_attributed_access(), - "label": lib_plankton.translate.get("calendar.access.attributed"), - }, - ] - ), - "label": lib_plankton.translate.get("calendar.access.access"), - }, - { - "name": "resource_kind", - "input": new lib_plankton.zoo_input.class_input_selection( - [ - { - "value": "local", - "label": lib_plankton.translate.get("resource.kinds.local.title") - }, - { - "value": "caldav", - "label": lib_plankton.translate.get("resource.kinds.caldav.title") - }, - ] - ), - "label": lib_plankton.translate.get("resource.kind") - }, - ] - ), - [ - { - "label": lib_plankton.translate.get("page.calendar_add.actions.do"), - "procedure": async (get_value, get_representation) => { - const value : any = await get_value(); - const calendar_object : _dali.type.calendar_object = { - "name": value.name, - "access": { - "public": value.access.public, - "default_level": value.access.default_level, - "attributed": value.access.attributed, - }, - "resource": (() => { - switch (value.resource_kind) { - case "local": - { - return { - "kind": "local", - "data": { - "events": [], - } - }; - break; - } - case "caldav": - { - return { - "kind": "caldav", - "data": { - "url": "", // TODO - "read_only": true, // TODO - } - }; - break; - } - default: - { - throw (new Error("invalid resource kind: " + value.resource_kind)); - break; - } - } - }) (), - "hue": value.hue, - }; - try - { - await _dali.backend.calendar_add( - calendar_object - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) - { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - ] - ); - await form.setup(document.querySelector("#calendar_add_form")); - await form.input_write( - { - "name": "", - "access": { - "public": false, - "default_level": _dali.type.enum_access_level.view, - "attributed": lib_plankton.map.hashmap.implementation_map< - _dali.type.user_id, - _dali.type.enum_access_level - >( - lib_plankton.map.hashmap.make< - _dali.type.user_id, - _dali.type.enum_access_level - >( - user_id => user_id.toFixed(0), - ) - ), - }, - "resource_kind": "local", - "hue": lib_plankton.random.generate_unit(), - } - ); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/calendar_add/templates/default.html.tpl b/source/pages/calendar_add/templates/default.html.tpl deleted file mode 100644 index 3b573e7..0000000 --- a/source/pages/calendar_add/templates/default.html.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{label}}

-
-
-
diff --git a/source/pages/calendar_edit/logic.ts b/source/pages/calendar_edit/logic.ts deleted file mode 100644 index 8221f13..0000000 --- a/source/pages/calendar_edit/logic.ts +++ /dev/null @@ -1,68 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "calendar_edit", - async (parameters, target_element) => { - const read_only : boolean = ((parameters["read_only"] ?? "yes") === "yes"); - const calendar_id : int = parseInt(parameters["calendar_id"]); - target_element.innerHTML = ""; - target_element.innerHTML = await _dali.helpers.template_coin( - "calendar_edit", - "default", - { - "label": lib_plankton.translate.get("page.calendar_edit.title.regular") - } - ); - const calendar_object : _dali.type.calendar_object = await _dali.backend.calendar_get( - calendar_id - ); - const widget = new _dali.widgets.calendar_edit.class_widget_calendar_edit( - { - "read_only": read_only, - "action_change": (data) => { - _dali.backend.calendar_change( - calendar_id, - data - ) - .then( - () => { - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - ); - }, - "action_remove": (data) => { - _dali.backend.calendar_remove( - calendar_id - ) - .then( - () => { - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - ); - }, - "initial_value": { - "name": calendar_object.name, - "hue": calendar_object.hue, - "access": calendar_object.access, - }, - } - ); - await widget.load(document.querySelector("#calendar_edit_form")); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/calendar_edit/templates/default.html.tpl b/source/pages/calendar_edit/templates/default.html.tpl deleted file mode 100644 index 62d2f1a..0000000 --- a/source/pages/calendar_edit/templates/default.html.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{label}}

-
-
-
diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index 7a6aa6c..d2f2b15 100644 --- a/source/pages/overview/logic.ts +++ b/source/pages/overview/logic.ts @@ -83,6 +83,51 @@ namespace _dali.pages.overview widget_sources = new _dali.widgets.sources.class_widget_sources( _dali.backend.calendar_list, { + "action_create": () => { + (async () => { + const widget = new _dali.widgets.calendar_edit.class_widget_calendar_edit( + { + "read_only": false, + "action_cancel": () => { + _dali.overlay.toggle({"mode": false}); + }, + "action_add": (data) => { + _dali.backend.calendar_add( + { + "name": data.name, + "hue": data.hue, + "access": data.access, + "resource": { + "kind": "local", + "data": { + "events": [], + } + } + } + ) + .then( + () => { + update(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + lib_plankton.log.warning( + "dali.overview.calendar_add_error", + {"details": String(reason)} + ); + } + ); + }, + "initial_value": null, + } + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); + }, "action_open": (entry) => { let read_only : boolean; switch (entry.access_level) @@ -154,9 +199,6 @@ namespace _dali.pages.overview widget_weekview.toggle_visibility(entry.id); widget_listview.toggle_visibility(entry.id); }, - "action_create": () => { - console.warn("not implemented: calendar_add widget"); - }, } ); await widget_sources.load(target_element.querySelector("#overview-pane-left")); diff --git a/source/widgets/calendar_edit/logic.ts b/source/widgets/calendar_edit/logic.ts index ac45f84..f4327eb 100644 --- a/source/widgets/calendar_edit/logic.ts +++ b/source/widgets/calendar_edit/logic.ts @@ -33,6 +33,11 @@ namespace _dali.widgets.calendar_edit private action_cancel ?: (null | (() => void)); + /** + */ + private action_add ?: (null | ((value : type_value) => void)); + + /** */ private action_change ?: (null | ((value : type_value) => void)); @@ -54,6 +59,7 @@ namespace _dali.widgets.calendar_edit { "read_only": read_only = false, "action_cancel": action_cancel = null, + "action_add": action_add = null, "action_change": action_change = null, "action_remove": action_remove = null, "initial_value": initial_value = null, @@ -62,6 +68,7 @@ namespace _dali.widgets.calendar_edit { read_only ?: boolean; action_cancel ?: (null | (() => void)); + action_add ?: (null | ((value : type_value) => void)) action_change ?: (null | ((value : type_value) => void)); action_remove ?: (null | ((value : type_value) => void)); initial_value ?: (null | type_value); @@ -73,6 +80,7 @@ namespace _dali.widgets.calendar_edit { this.read_only = read_only; this.action_cancel = action_cancel; + this.action_add = action_add; this.action_change = action_change; this.action_remove = action_remove; this.initial_value = initial_value; @@ -150,6 +158,22 @@ namespace _dali.widgets.calendar_edit : [] ) + // add + .concat( + ((! this.read_only) && (! (this.action_add === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.calendar_edit.actions.add"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_add(value); + } + }, + ] + : + [] + ) // change .concat( ((! this.read_only) && (! (this.action_change === null))) diff --git a/tools/makefile b/tools/makefile index 9788fe7..2ca4fa3 100644 --- a/tools/makefile +++ b/tools/makefile @@ -39,8 +39,6 @@ templates: \ templates-widgets-calendar_edit \ templates-widgets-event_edit \ templates-pages-caldav \ - templates-pages-calendar_add \ - templates-pages-calendar_edit \ templates-pages-overview \ templates-pages-login @@ -93,20 +91,6 @@ templates-pages-caldav: \ @ ${cmd_mkdir} ${dir_build}/templates/caldav @ ${cmd_cp} -r -u -v ${dir_source}/pages/caldav/templates/* ${dir_build}/templates/caldav/ -.PHONY: templates-pages-calendar_add -templates-pages-calendar_add: \ - $(wildcard ${dir_source}/pages/calendar_add/templates/*) - @ ${cmd_log} "templates:calendar_add …" - @ ${cmd_mkdir} ${dir_build}/templates/calendar_add - @ ${cmd_cp} -r -u -v ${dir_source}/pages/calendar_add/templates/* ${dir_build}/templates/calendar_add/ - -.PHONY: templates-pages-calendar_edit -templates-pages-calendar_edit: \ - $(wildcard ${dir_source}/pages/calendar_edit/templates/*) - @ ${cmd_log} "templates:calendar_edit …" - @ ${cmd_mkdir} ${dir_build}/templates/calendar_edit - @ ${cmd_cp} -r -u -v ${dir_source}/pages/calendar_edit/templates/* ${dir_build}/templates/calendar_edit/ - .PHONY: templates-pages-overview templates-pages-overview: \ $(wildcard ${dir_source}/pages/overview/templates/*) @@ -150,8 +134,6 @@ ${dir_temp}/logic-unlinked.js: \ ${dir_source}/pages/logout/logic.ts \ ${dir_source}/pages/caldav/logic.ts \ ${dir_source}/pages/oidc_finish/logic.ts \ - ${dir_source}/pages/calendar_add/logic.ts \ - ${dir_source}/pages/calendar_edit/logic.ts \ ${dir_source}/pages/overview/logic.ts \ ${dir_source}/main.ts @ ${cmd_log} "logic | compile …" From 1990c047c2cec79d47098dfce2b52627e911832c Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 23:34:06 +0200 Subject: [PATCH 09/11] [task-408] [mod] loc --- source/data/localization/deu.loc.json | 2 +- source/data/localization/eng.loc.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index 957d735..81f9572 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -50,7 +50,7 @@ "widget.weekview.controls.week": "Woche", "widget.weekview.controls.count": "Anzahl", "widget.weekview.controls.apply": "Laden", - "widget.calendar_edit.actions.add": "anlegen", + "widget.calendar_edit.actions.add": "Kalender anlegen", "widget.calendar_edit.actions.change": "ändern", "widget.calendar_edit.actions.remove": "löschen", "widget.event_edit.actions.add": "anlegen", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index ab7151c..63bee86 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -50,7 +50,7 @@ "widget.weekview.controls.week": "Week", "widget.weekview.controls.count": "Count", "widget.weekview.controls.apply": "Load", - "widget.calendar_edit.actions.add": "add", + "widget.calendar_edit.actions.add": "add calendar", "widget.calendar_edit.actions.change": "change", "widget.calendar_edit.actions.remove": "delete", "widget.event_edit.actions.add": "add", From 1a36af56aeecf0da3226da5d30e71cf38ba480f3 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Fri, 17 Oct 2025 00:10:28 +0200 Subject: [PATCH 10/11] [mod] vieeeele Verbesserungen --- lib/plankton/plankton.d.ts | 122 ++++ lib/plankton/plankton.js | 364 +++++++++- source/base.ts | 102 +++ source/base/functions.ts | 94 --- source/base/model.ts | 8 - source/base/types.ts | 144 ---- source/data/localization/deu.loc.json | 14 +- source/data/localization/eng.loc.json | 16 +- source/{base => }/helpers.ts | 58 +- source/index.html.tpl | 12 +- source/main.ts | 131 +++- source/model.ts | 604 +++++++++++++++ source/pages/login/logic.ts | 118 --- source/pages/login/templates/default.html.tpl | 2 - source/pages/logout/logic.ts | 36 - source/pages/oidc_finish/logic.ts | 1 + source/pages/overview/logic.ts | 170 +++-- source/resources/backend.ts | 686 ++++++++++-------- source/style/main.css | 22 +- source/style/page-calendar_add.css | 13 - source/style/page-event_add.css | 8 - source/style/page-event_edit.css | 8 - source/style/page-overview.css | 5 + source/style/plankton.css | 24 +- source/style/widget-menu.css | 74 ++ source/style/widget-sources.css | 10 + source/types.ts | 245 +++++++ source/widgets/calendar_edit/logic.ts | 105 +-- source/widgets/event_edit/logic.ts | 96 +-- source/widgets/listview/logic.ts | 50 +- source/widgets/login/logic.ts | 195 +++++ .../widgets/login/templates/default.html.tpl | 2 + source/widgets/menu/logic.ts | 165 +++++ source/widgets/mode_switcher/logic.ts | 12 +- source/widgets/sources/logic.ts | 53 +- source/widgets/weekview/logic.ts | 111 +-- tools/makefile | 30 +- tools/update-plankton | 1 + 38 files changed, 2830 insertions(+), 1081 deletions(-) create mode 100644 source/base.ts delete mode 100644 source/base/functions.ts delete mode 100644 source/base/model.ts delete mode 100644 source/base/types.ts rename source/{base => }/helpers.ts (72%) create mode 100644 source/model.ts delete mode 100644 source/pages/login/logic.ts delete mode 100644 source/pages/login/templates/default.html.tpl delete mode 100644 source/pages/logout/logic.ts delete mode 100644 source/style/page-calendar_add.css delete mode 100644 source/style/page-event_add.css delete mode 100644 source/style/page-event_edit.css create mode 100644 source/style/widget-menu.css create mode 100644 source/types.ts create mode 100644 source/widgets/login/logic.ts create mode 100644 source/widgets/login/templates/default.html.tpl create mode 100644 source/widgets/menu/logic.ts diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index a72512e..09b58dd 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -2693,6 +2693,128 @@ declare namespace lib_plankton.map.collatemap { export function implementation_map(subject: type_subject): type_map; export {}; } +declare namespace lib_plankton.set { + /** + */ + type type_set = { + size: (() => int); + has: ((element: type_element) => boolean); + add: ((element: type_element) => void); + remove: ((element: type_element) => void); + iterate: ((procedure: ((element: type_element) => void)) => void); + }; +} +declare namespace lib_plankton.set { + /** + */ + function clear(set: type_set): void; + /** + */ + function dump(set: type_set): Array; + /** + */ + function show(set: type_set, { "show_element": show_element, }?: { + show_element?: ((element: type_element) => string); + }): string; + /** + */ + function map(construct: (() => type_set), set: type_set, transformator: ((element: type_element_from) => type_element_to)): type_set; + /** + */ + function filter(construct: (() => type_set), set: type_set, predicate: ((element: type_element) => boolean)): type_set; + /** + */ + function subset(set1: type_set, set2: type_set): boolean; + /** + */ + function superset(set1: type_set, set2: type_set): boolean; + /** + */ + function equal(set1: type_set, set2: type_set): boolean; + /** + */ + function empty(set: type_set): boolean; + /** + */ + function union(construct: (() => type_set), set1: type_set, set2: type_set): type_set; + /** + */ + function intersection(construct: (() => type_set), set1: type_set, set2: type_set): type_set; + /** + */ + function difference(construct: (() => type_set), set1: type_set, set2: type_set): type_set; + /** + */ + function symmetric_difference(construct: (() => type_set), set1: type_set, set2: type_set): type_set; +} +declare namespace lib_plankton.set.hashset { + /** + */ + type type_hashing = ((element: type_element) => string); + /** + */ + export type type_subject = { + core: lib_plankton.map.hashmap.type_subject; + }; + /** + */ + export function make(hashing: type_hashing, options?: { + elements?: Array; + }): type_subject; + /** + */ + export function size(subject: type_subject): int; + /** + */ + export function has(subject: type_subject, element: type_element): boolean; + /** + */ + export function add(subject: type_subject, element: type_element): void; + /** + */ + export function remove(subject: type_subject, element: type_element): void; + /** + */ + export function iterate(subject: type_subject, procedure: ((element: type_element) => void)): void; + /** + */ + export function implementation_set(subject: type_subject): type_set; + export {}; +} +declare namespace lib_plankton.set.collateset { + /** + */ + type type_collation = ((x: type_element, y: type_element) => boolean); + /** + */ + export type type_subject = { + core: lib_plankton.map.collatemap.type_subject; + }; + /** + */ + export function make(collation: type_collation, options?: { + elements?: Array; + }): type_subject; + /** + */ + export function size(subject: type_subject): int; + /** + */ + export function has(subject: type_subject, element: type_element): boolean; + /** + */ + export function add(subject: type_subject, element: type_element): void; + /** + */ + export function remove(subject: type_subject, element: type_element): void; + /** + */ + export function iterate(subject: type_subject, procedure: ((element: type_element) => void)): void; + /** + */ + export function implementation_set(subject: type_subject): type_set; + export {}; +} declare namespace lib_plankton.cache { /** */ diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index fa6a73c..4cd8cd9 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -8570,7 +8570,7 @@ the Free Software Foundation, either version 3 of the License, or »bacterio-plankton:map« 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 -3GNU Lesser General Public License for more details. +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:map«. If not, see . @@ -8710,6 +8710,368 @@ var lib_plankton; })(map = lib_plankton.map || (lib_plankton.map = {})); })(lib_plankton || (lib_plankton = {})); /* +This file is part of »bacterio-plankton:set«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:set« 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:set« 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:set«. If not, see . + */ +/* +This file is part of »bacterio-plankton:set«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:set« 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:set« 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:set«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var set; + (function (set_1) { + /** + */ + function clear(set) { + set.iterate(function (element) { + set.remove(element); + }); + } + set_1.clear = clear; + /** + */ + function dump(set) { + var elements = []; + set.iterate(function (element) { + elements.push(element); + }); + return elements; + } + set_1.dump = dump; + /** + */ + function show(set, _a) { + var _b = _a === void 0 ? {} : _a, _c = _b["show_element"], show_element = _c === void 0 ? instance_show : _c; + return ("{" + + + (dump(set) + .map(function (element) { return (show_element(element)); }) + .join(", ")) + + + "}"); + } + set_1.show = show; + /** + */ + function map(construct, set, transformator) { + var result = construct(); + set.iterate(function (element_from) { + var element_to = transformator(element_from); + result.add(element_to); + }); + return result; + } + set_1.map = map; + /** + */ + function filter(construct, set, predicate) { + var result = construct(); + set.iterate(function (element) { + if (!predicate(element)) { + // do nothing + } + else { + result.add(element); + } + }); + return result; + } + set_1.filter = filter; + /** + */ + function subset(set1, set2) { + var result; + set1.iterate(function (element) { + if (!set2.has(element)) { + result = false; + } + else { + // do nothing + } + }); + return result; + } + set_1.subset = subset; + /** + */ + function superset(set1, set2) { + return subset(set2, set1); + } + set_1.superset = superset; + /** + */ + function equal(set1, set2) { + return (subset(set1, set2) + && + superset(set1, set2)); + } + set_1.equal = equal; + /** + */ + function empty(set) { + return (set.size() <= 0); + } + set_1.empty = empty; + /** + */ + function union(construct, set1, set2) { + var result = construct(); + set1.iterate(function (element) { + result.add(element); + }); + set2.iterate(function (element) { + result.add(element); + }); + return result; + } + set_1.union = union; + /** + */ + function intersection(construct, set1, set2) { + var result = construct(); + set1.iterate(function (element) { + if (!set2.has(element)) { + // do nothing + } + else { + result.add(element); + } + }); + return result; + } + set_1.intersection = intersection; + /** + */ + function difference(construct, set1, set2) { + var result = construct(); + set1.iterate(function (element) { + if (set2.has(element)) { + // do nothing + } + else { + result.add(element); + } + }); + return result; + } + set_1.difference = difference; + /** + */ + function symmetric_difference(construct, set1, set2) { + // X $ Y = (X ∪ Y) \ (X ∩ Y) + return (difference(construct, union(construct, set1, set2), intersection(construct, set1, set2))); + } + set_1.symmetric_difference = symmetric_difference; + })(set = lib_plankton.set || (lib_plankton.set = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:set«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:set« 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:set« 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:set«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var set; + (function (set) { + var hashset; + (function (hashset) { + /** + */ + function make(hashing, options) { + if (options === void 0) { options = {}; } + options = Object.assign({ + "elements": [] + }, options); + return { + "core": lib_plankton.map.hashmap.make(hashing, { + "pairs": (options.elements + .map(function (element) { return ({ + "key": element, + "value": null + }); })) + }) + }; + } + hashset.make = make; + /** + */ + function size(subject) { + return lib_plankton.map.hashmap.size(subject.core); + } + hashset.size = size; + /** + */ + function has(subject, element) { + return lib_plankton.map.hashmap.has(subject.core, element); + } + hashset.has = has; + /** + */ + function add(subject, element) { + return lib_plankton.map.hashmap.set(subject.core, element, null); + } + hashset.add = add; + /** + */ + function remove(subject, element) { + lib_plankton.map.hashmap.delete_(subject.core, element); + } + hashset.remove = remove; + /** + */ + function iterate(subject, procedure) { + lib_plankton.map.hashmap.iterate(subject.core, function (value, key) { procedure(key); }); + } + hashset.iterate = iterate; + /** + */ + function implementation_set(subject) { + return { + "size": function () { return size(subject); }, + "has": function (element) { return has(subject, element); }, + "add": function (element) { return add(subject, element); }, + "remove": function (element) { return remove(subject, element); }, + "iterate": function (procedure) { return iterate(subject, procedure); } + }; + } + hashset.implementation_set = implementation_set; + })(hashset = set.hashset || (set.hashset = {})); + })(set = lib_plankton.set || (lib_plankton.set = {})); +})(lib_plankton || (lib_plankton = {})); +/* +This file is part of »bacterio-plankton:set«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:set« 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:set« 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:set«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var set; + (function (set) { + var collateset; + (function (collateset) { + /** + */ + function make(collation, options) { + if (options === void 0) { options = {}; } + options = Object.assign({ + "elements": [] + }, options); + return { + "core": lib_plankton.map.collatemap.make(collation, { + "pairs": (options.elements + .map(function (element) { return ({ + "key": element, + "value": null + }); })) + }) + }; + } + collateset.make = make; + /** + */ + function size(subject) { + return lib_plankton.map.collatemap.size(subject.core); + } + collateset.size = size; + /** + */ + function has(subject, element) { + return lib_plankton.map.collatemap.has(subject.core, element); + } + collateset.has = has; + /** + */ + function add(subject, element) { + return lib_plankton.map.collatemap.set(subject.core, element, null); + } + collateset.add = add; + /** + */ + function remove(subject, element) { + lib_plankton.map.collatemap.delete_(subject.core, element); + } + collateset.remove = remove; + /** + */ + function iterate(subject, procedure) { + lib_plankton.map.collatemap.iterate(subject.core, function (key, value) { procedure(key); }); + } + collateset.iterate = iterate; + /** + */ + function implementation_set(subject) { + return { + "size": function () { return size(subject); }, + "has": function (element) { return has(subject, element); }, + "add": function (element) { return add(subject, element); }, + "remove": function (element) { return remove(subject, element); }, + "iterate": function (procedure) { return iterate(subject, procedure); } + }; + } + collateset.implementation_set = implementation_set; + })(collateset = set.collateset || (set.collateset = {})); + })(set = lib_plankton.set || (lib_plankton.set = {})); +})(lib_plankton || (lib_plankton = {})); +/* This file is part of »bacterio-plankton:cache«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' diff --git a/source/base.ts b/source/base.ts new file mode 100644 index 0000000..5f3aa65 --- /dev/null +++ b/source/base.ts @@ -0,0 +1,102 @@ +namespace _dali +{ + + /** + */ + let _actions_login : Array<(() => Promise)> = []; + + + /** + */ + let _actions_logout : Array<(() => Promise)> = []; + + + /** + */ + let _is_logged_in : boolean = false; + + + /** + */ + export function listen_login( + action : (() => Promise) + ) + : void + { + _actions_login.push(action); + } + + + /** + */ + export function listen_logout( + action : (() => Promise) + ) + : void + { + _actions_logout.push(action); + } + + + /** + */ + export async function notify_login( + ) + : Promise + { + _is_logged_in = true; + for (const action of _actions_login) + { + await action(); + } + } + + + /** + */ + export async function notify_logout( + ) + : Promise + { + _is_logged_in = false; + for (const action of _actions_logout) + { + await action(); + } + } + + + /** + */ + export function is_logged_in( + ) + : boolean + { + return _is_logged_in; + } + + + /** + */ + export async function logout( + ) + : Promise + { + try + { + await _dali.backend.session_end( + ); + notify_logout(); + } + catch (error) + { + lib_plankton.log.notice( + "dali.logout_failed", + { + "reason": String(error), + } + ); + } + } + +} diff --git a/source/base/functions.ts b/source/base/functions.ts deleted file mode 100644 index a4721c9..0000000 --- a/source/base/functions.ts +++ /dev/null @@ -1,94 +0,0 @@ -namespace _dali -{ - - /** - */ - export function access_level_encode( - access_level : _dali.type.enum_access_level - ) : ("none" | "view" | "edit" | "admin") - { - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - } - - - /** - */ - export function access_level_decode( - representation : /*("none" | "view" | "edit" | "admin")*/string - ) : _dali.type.enum_access_level - { - switch (representation) - { - case "none": return _dali.type.enum_access_level.none; - case "view": return _dali.type.enum_access_level.view; - case "edit": return _dali.type.enum_access_level.edit; - case "admin": return _dali.type.enum_access_level.admin; - default: throw (new Error("invalid access level representation: " + representation)); - } - } - - - /** - */ - export function view_mode_encode( - mode : _dali.type.enum_view_mode - ) : string - { - switch (mode) - { - case _dali.type.enum_view_mode.week: {return "week"; break;} - case _dali.type.enum_view_mode.list: {return "list"; break;} - default: {throw (new Error("invalid mode"));} - } - } - - - /** - */ - export function view_mode_decode( - view_mode_encoded : string - ) : _dali.type.enum_view_mode - { - const map : Record = { - "week": _dali.type.enum_view_mode.week, - "list": _dali.type.enum_view_mode.list, - }; - if (! (view_mode_encoded in map)) - { - throw (new Error("invalid mode: " + view_mode_encoded)); - } - else - { - return map[view_mode_encoded]; - } - } - - - /** - */ - export function view_mode_determine( - mode_descriptor : string - ) : _dali.type.enum_view_mode - { - if (mode_descriptor === "auto") - { - return ( - (window.innerWidth >= 800) - ? - _dali.type.enum_view_mode.week - : - _dali.type.enum_view_mode.list - ); - } - else - { - return view_mode_decode(mode_descriptor); - } - } - -} diff --git a/source/base/model.ts b/source/base/model.ts deleted file mode 100644 index 26847c6..0000000 --- a/source/base/model.ts +++ /dev/null @@ -1,8 +0,0 @@ -namespace _dali.model -{ - - /** - */ - let events : Array<_dali.type.event_entry> = []; - -} diff --git a/source/base/types.ts b/source/base/types.ts deleted file mode 100644 index ac35b84..0000000 --- a/source/base/types.ts +++ /dev/null @@ -1,144 +0,0 @@ - -/** - */ -namespace _dali.type -{ - - /** - */ - export enum enum_access_level { - none, - view, - edit, - admin - } - - - /** - */ - export type user_id = int; - - - /** - */ - export type user_object = { - name : string; - email_address : ( - null - | - string - ); - }; - - - /** - * @todo deprecate? - */ - export type local_resource_event_id = int; - - - /** - * info: das ist nicht deckungsgleich mit der Event-ID aus dem Backend; hiermit werden sowohl lokale als auch - * extern eingebundene Events kodiert - * - * @example "local:1234" - * @example "ics~2345" - */ - export type event_key = string; - - - /** - */ - export type event_object = { - name : string; - begin : lib_plankton.pit.type_datetime; - end : ( - null - | - lib_plankton.pit.type_datetime - ); - location : ( - null - | - string - ); - link : ( - null - | - string - ); - description : ( - null - | - string - ); - }; - - - /** - */ - export type event_entry = { - id : (null | local_resource_event_id); - key : event_key; - object : event_object; - }; - - - /** - */ - export type resource_id = int; - - - /** - */ - export type resource_object = ( - { - kind : "local"; - data : { - events : Array< - event_object - >; - }; - } - | - { - kind : "caldav"; - data : { - read_only : boolean; - url : string; - }; - } - ); - - - /** - */ - export type calendar_id = int; - - - /** - */ - export type calendar_object = { - name : string; - hue : float; - access : { - public : boolean; - default_level : enum_access_level; - attributed : lib_plankton.map.type_map< - user_id, - enum_access_level - >; - }; - resource : resource_object; - }; - - - /** - */ - export enum enum_view_mode - { - week, - list, - } - -} diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index 81f9572..a19e24e 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -19,6 +19,8 @@ "common.show": "zeigen", "common.hide": "ausblenden", "common.cancel": "abbrechen", + "common.login": "anmelden", + "common.logout": "abmelden", "access_level.none": "nichts", "access_level.view": "nur lesen", "access_level.edit": "lesen und bearbeiten", @@ -56,13 +58,11 @@ "widget.event_edit.actions.add": "anlegen", "widget.event_edit.actions.change": "ändern", "widget.event_edit.actions.remove": "löschen", - "widget.sources.create": "anlegen", - "page.login.title": "Anmelden", - "page.login.internal.name": "Name", - "page.login.internal.password": "Kennwort", - "page.login.internal.do": "Anmelden", - "page.login.oidc.via": "via {{title}}", - "page.logout.title": "Abmelden", + "widget.sources.create": "Kalender anlegen", + "widget.login.internal.name": "Name", + "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", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index 63bee86..2e14e24 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -19,6 +19,8 @@ "common.show": "show", "common.hide": "hide", "common.cancel": "cancel", + "common.login": "login", + "common.logout": "logout", "access_level.none": "none", "access_level.view": "read only", "access_level.edit": "read and write", @@ -37,7 +39,7 @@ "resource.kinds.caldav.title": "CalDAV", "resource.kinds.caldav.url": "URL", "resource.kinds.caldav.read_only": "read-only", - "calendar.calendar": "Kalendar", + "calendar.calendar": "calendar", "calendar.name": "name", "calendar.hue": "hue", "calendar.resource": "resource", @@ -56,13 +58,11 @@ "widget.event_edit.actions.add": "add", "widget.event_edit.actions.change": "change", "widget.event_edit.actions.remove": "delete", - "widget.sources.create": "create", - "page.login.title": "Login", - "page.login.internal.name": "name", - "page.login.internal.password": "password", - "page.login.internal.do": "login", - "page.login.oidc.via": "via {{title}}", - "page.logout.title": "Logout", + "widget.sources.create": "create calendar", + "widget.login.internal.name": "name", + "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", diff --git a/source/base/helpers.ts b/source/helpers.ts similarity index 72% rename from source/base/helpers.ts rename to source/helpers.ts index 0bc7bd6..47dfee4 100644 --- a/source/base/helpers.ts +++ b/source/helpers.ts @@ -9,6 +9,29 @@ namespace _dali.helpers var _template_cache : Record = {}; + /** + */ + export function view_mode_determine( + mode_descriptor : string + ) : _dali.enum_view_mode + { + if (mode_descriptor === "auto") + { + return ( + (window.innerWidth >= 800) + ? + _dali.enum_view_mode.week + : + _dali.enum_view_mode.list + ); + } + else + { + return view_mode_decode(mode_descriptor); + } + } + + /** */ export async function template_coin( @@ -78,12 +101,12 @@ namespace _dali.helpers /** */ export function input_access_level( - ) : lib_plankton.zoo_input.interface_input<_dali.type.enum_access_level> + ) : lib_plankton.zoo_input.interface_input<_dali.enum_access_level> { return ( new lib_plankton.zoo_input.class_input_wrapped< /*("none" | "view" | "edit" | "admin")*/string, - _dali.type.enum_access_level + _dali.enum_access_level >( new lib_plankton.zoo_input.class_input_selection( [ @@ -107,18 +130,18 @@ namespace _dali.helpers ), (raw) => { switch (raw) { - case "none": return _dali.type.enum_access_level.none; - case "view": return _dali.type.enum_access_level.view; - case "edit": return _dali.type.enum_access_level.edit; - case "admin": return _dali.type.enum_access_level.admin; + case "none": return _dali.enum_access_level.none; + case "view": return _dali.enum_access_level.view; + case "edit": return _dali.enum_access_level.edit; + case "admin": return _dali.enum_access_level.admin; } }, (access_level) => { switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; + case _dali.enum_access_level.none: return "none"; + case _dali.enum_access_level.view: return "view"; + case _dali.enum_access_level.edit: return "edit"; + case _dali.enum_access_level.admin: return "admin"; } }, ) @@ -128,13 +151,16 @@ namespace _dali.helpers /** */ - export async function input_attributed_access( - ) : Promise> + export function input_attributed_access( + users : Array<{id : _dali.type_user_id; name : string;}> + ) + : lib_plankton.zoo_input.class_input_hashmap< + _dali.type_user_id, + _dali.enum_access_level + > { - const users : Array<{id : _dali.type.user_id; name : string;}> = await _dali.backend.user_list( - ); - return Promise.resolve( - new lib_plankton.zoo_input.class_input_hashmap<_dali.type.user_id, _dali.type.enum_access_level>( + return ( + new lib_plankton.zoo_input.class_input_hashmap<_dali.type_user_id, _dali.enum_access_level>( // hash_key (user_id) => user_id.toFixed(0), // key_input_factory diff --git a/source/index.html.tpl b/source/index.html.tpl index d8151a6..c2374e0 100644 --- a/source/index.html.tpl +++ b/source/index.html.tpl @@ -24,17 +24,13 @@ document.addEventListener( {{templates}} +
+
+
+
-
-
-
-
-
diff --git a/source/main.ts b/source/main.ts index df3090d..c316c10 100644 --- a/source/main.ts +++ b/source/main.ts @@ -8,7 +8,8 @@ namespace _dali */ function nav_groups( logged_in : boolean - ) : Array + ) + : Array { return ( logged_in @@ -24,12 +25,13 @@ namespace _dali * @todo reload page when switching to "logged_out" */ async function update( - ) : Promise + ) + : Promise { lib_plankton.log.debug( "dali.update" ); - const logged_in : boolean = await _dali.backend.is_logged_in(); + const logged_in : boolean = _dali.is_logged_in(); lib_plankton.zoo_page.nav_set_groups(nav_groups(logged_in)); // lib_plankton.zoo_page.reload(); } @@ -38,7 +40,8 @@ namespace _dali /** */ export async function main( - ) : Promise + ) + : Promise { // conf await _dali.conf.init( @@ -51,8 +54,6 @@ namespace _dali {"kind": "console", "data": {"threshold": "info"}}, ] ); - await _dali.backend.init( - ); await lib_plankton.translate.initialize( { "verbosity": 1, @@ -64,6 +65,11 @@ namespace _dali "autopromote": false, } ); + await _dali.backend.initialize( + _dali.conf.get()["backend"] + ); + await _dali.model.initialize( + ); lib_plankton.zoo_page.init( document.querySelector("main"), { @@ -71,41 +77,112 @@ namespace _dali "name": "overview", "parameters": {} }, + /* "nav_entries": [ - { - "location": {"name": "login", "parameters": {}}, - "label": lib_plankton.translate.get("page.login.title"), - "groups": ["logged_out"], - }, - { - "location": {"name": "overview", "parameters": {}}, - "label": lib_plankton.translate.get("page.overview.title"), - "groups": ["logged_out", "logged_in"], - }, - { - "location": {"name": "caldav", "parameters": {}}, - "label": lib_plankton.translate.get("page.caldav.title"), - "groups": ["logged_in"], - }, - { - "location": {"name": "logout", "parameters": {}}, - "label": lib_plankton.translate.get("page.logout.title"), - "groups": ["logged_in"], - }, ], + */ "nav_initial_groups": [], } ); + // menu widget + { + const widget_menu : _dali.widgets.menu.class_widget_menu = new _dali.widgets.menu.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_cancel": () => { + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": false}); + }, + "action_success": () => { + _dali.notify_login(); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": false}); + }, + } + ); + _dali.overlay.clear(); + _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"), + "groups": ["logged_in"], + "action": () => { + lib_plankton.zoo_page.set( + { + "name": "caldav", + "parameters": {} + } + ); + }, + }, + { + "label": lib_plankton.translate.get("common.logout"), + "groups": ["logged_in"], + "action": () => { + _dali.logout(); + }, + }, + ] + ); + await widget_menu.load(document.querySelector("header")); + _dali.listen_login( + async () => { + widget_menu.set_groups(["logged_in"]); + } + ); + _dali.listen_logout( + async () => { + widget_menu.set_groups(["logged_out"]); + } + ); + } await update(); await _dali.overlay.initialize(); + /* lib_plankton.call.loop( () => { update(); }, _dali.conf.get().misc.update_interval ); + */ + + // check if logged_in + { + const status = await _dali.backend.status(); + lib_plankton.log.info( + "dali.status", + status + ); + if (status.logged_in) + { + _dali.notify_login(); + } + else + { + _dali.notify_logout(); + } + } - // exec lib_plankton.zoo_page.start(); return Promise.resolve(undefined); diff --git a/source/model.ts b/source/model.ts new file mode 100644 index 0000000..0409e94 --- /dev/null +++ b/source/model.ts @@ -0,0 +1,604 @@ +namespace _dali.model +{ + + /** + */ + type type_state = { + users : Array< + { + id : _dali.type_user_id; + name : string; + } + >; + calendars : ( + null + | + lib_plankton.map.type_map< + _dali.type_calendar_id, + { + reduced : _dali.type_calendar_object_reduced; + complete : (null | _dali.type_calendar_object); + } + > + ); + events : lib_plankton.map.type_map< + _dali.type_event_key, + _dali.type_event_object_extended + >; + covered_dates : lib_plankton.set.type_set< + lib_plankton.pit.type_date + >; + }; + + + /** + */ + let _state : (null | type_state) = null; + + + /** + */ + let _listeners_reset : Array<((priviliged ?: boolean) => Promise)> = []; + + + /** + */ + function make_date_set( + { + "elements": elements = [], + } + : + { + elements ?: Array; + } + = + { + } + ) + : lib_plankton.set.type_set + { + return lib_plankton.set.hashset.implementation_set( + lib_plankton.set.hashset.make( + x => lib_plankton.pit.date_format(x), + { + /** + * @todo häääh!? + */ + "elements": elements.filter(x => (x !== null)), + } + ) + ); + } + + + /** + */ + function timeframe_to_date_set( + timeframe : { + from : lib_plankton.pit.type_pit; + to : lib_plankton.pit.type_pit; + } + ) + : lib_plankton.set.type_set + { + if (! lib_plankton.pit.is_before(timeframe.from, timeframe.to)) + { + throw (new Error("invalid timeframe")); + } + else + { + const heuristic : int = Math.ceil((timeframe.to - timeframe.from) / (60 * 60 * 24)); + let pit_current : lib_plankton.pit.type_pit = timeframe.from; + return make_date_set( + { + "elements": ( + lib_plankton.list.sequence(heuristic) + .map( + (offset) => lib_plankton.call.convey( + timeframe.from, + [ + x => lib_plankton.pit.shift_day(x, offset), + x => lib_plankton.pit.to_datetime(x), + x => x.date, + ] + ) + ) + ), + } + ); + } + } + + + /** + */ + export async function user_list( + ) + : Promise< + Array< + { + id : _dali.type_user_id; + name : string; + } + > + > + { + return Promise.resolve(_state.users); + } + + + /** + * @todo clear after login/logout + * @todo do not export + */ + export async function sync_calendars( + ) + : Promise + { + const data = await _dali.backend.calendar_list(); + lib_plankton.map.clear(_state.calendars); + data.forEach( + entry => { + _state.calendars.set( + entry.id, + { + "reduced": { + "name": entry.name, + "hue": entry.hue, + "access_level": _dali.access_level_decode(entry.access_level), + }, + "complete": null, + } + ); + } + ); + } + + + /** + */ + export function calendar_list( + ) + : Promise< + Array< + _dali.type_calendar_object_reduced_with_id + > + > + { + return Promise.resolve( + lib_plankton.map.dump(_state.calendars) + .map( + pair => ( + { + "id": pair.key, + "name": pair.value.reduced.name, + "hue": pair.value.reduced.hue, + "access_level": pair.value.reduced.access_level, + } + ) + ) + ); + } + + + /** + */ + export async function calendar_get( + calendar_id : _dali.type_calendar_id + ) + : Promise<_dali.type_calendar_object> + { + const value = _state.calendars.get(calendar_id); + if (value.complete === null) + { + const data = await _dali.backend.calendar_get(calendar_id); + const calendar_object : _dali.type_calendar_object = { + "name": data.name, + "hue": data.hue, + "access": { + "public": data.access.public, + "default_level": _dali.access_level_decode(data.access.default_level), + "attributed": lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make( + x => x.toFixed(0), + { + "pairs": ( + data.access.attributed + .map( + (entry) => ( + { + "key": entry.user_id, + "value": _dali.access_level_decode(entry.level), + } + ) + ) + ), + } + ) + ), + }, + "resource_id": data.resource_id, + }; + value.complete = calendar_object; + /** + * @todo set in map? + */ + return calendar_object; + } + else + { + return value.complete; + } + } + + + /** + */ + export async function calendar_add( + calendar_object : _dali.type_calendar_object + ) + : Promise + { + const calendar_id : _dali.type_calendar_id = await _dali.backend.calendar_add( + { + "name": calendar_object.name, + "hue": calendar_object.hue, + "access": { + "public": calendar_object.access.public, + "default_level": _dali.access_level_encode(calendar_object.access.default_level), + "attributed": ( + lib_plankton.map.dump(calendar_object.access.attributed) + .map( + (pair) => ( + { + "user_id": pair.key, + "level": _dali.access_level_encode(pair.value), + } + ) + ) + ) + }, + /** + * @todo + */ + "resource": { + "kind": "local", + "data": { + "events": [], + } + }, + } + ); + const calendar_object_reduced : _dali.type_calendar_object_reduced = { + "name": calendar_object.name, + "hue": calendar_object.hue, + "access_level": _dali.enum_access_level.admin, + }; + _state.calendars.set( + calendar_id, + { + "reduced": calendar_object_reduced, + "complete": calendar_object, + } + ); + } + + + /** + */ + export async function calendar_change( + calendar_id : _dali.type_calendar_id, + calendar_object : _dali.type_calendar_object + ) + : Promise + { + /*await */ _dali.backend.calendar_change( + calendar_id, + { + "name": calendar_object.name, + "hue": calendar_object.hue, + "access": { + "public": calendar_object.access.public, + "default_level": _dali.access_level_encode(calendar_object.access.default_level), + "attributed": ( + lib_plankton.map.dump(calendar_object.access.attributed) + .map( + (pair) => ({ + "user_id": pair.key, + "level": _dali.access_level_encode(pair.value), + }) + ) + ) + }, + } + ); + const calendar_object_reduced : _dali.type_calendar_object_reduced = { + "name": calendar_object.name, + "hue": calendar_object.hue, + /** + * @todo + */ + "access_level": _dali.enum_access_level.admin, + }; + _state.calendars.set( + calendar_id, + { + "reduced": calendar_object_reduced, + "complete": calendar_object, + } + ); + { + lib_plankton.map.clear(_state.events); + lib_plankton.set.clear(_state.covered_dates); + notify_reset(); + } + } + + + /** + */ + export async function calendar_remove( + calendar_id : _dali.type_calendar_id + ) + : Promise + { + /*await */ _dali.backend.calendar_remove(calendar_id); + _state.calendars.delete(calendar_id); + { + lib_plankton.map.clear(_state.events); + lib_plankton.set.clear(_state.covered_dates); + notify_reset(); + } + } + + + /** + * @todo do NOT export? + * @todo clear? + * @todo heed calendar_ids + * @todo mutex + * @todo only update outside timeframe + * @todo clear after login/logout + */ + export async function sync_events( + timeframe : { + from : lib_plankton.pit.type_pit; + to : lib_plankton.pit.type_pit; + }, + { + "calendar_ids": calendar_ids = null, + } + : + { + calendar_ids ?: (null | Array<_dali.type_calendar_id>); + } + = + { + } + ) + : Promise + { + const queried_dates : lib_plankton.set.type_set = timeframe_to_date_set( + timeframe + ); + const new_dates : lib_plankton.set.type_set = lib_plankton.set.difference( + () => make_date_set(), + queried_dates, + _state.covered_dates + ); + if (lib_plankton.set.empty(new_dates)) + { + // do nothing + } + else + { + const result = await _dali.backend.events( + timeframe.from, + timeframe.to, + { + "calendar_ids": calendar_ids, + } + ); + result.forEach( + entry => { + _state.events.set( + entry.hash, + { + "key": entry.hash, + "calendar_id": entry.calendar_id, + "calendar_name": entry.calendar_name, + "hue": entry.hue, + "access_level": _dali.access_level_decode(entry.access_level), + "event_id": entry.event_id, + "event_object": entry.event_object + } + ); + } + ); + _state.covered_dates = lib_plankton.set.union( + () => make_date_set(), + _state.covered_dates, + new_dates + ); + } + } + + + /** + */ + export function event_list( + ) + : Promise> + { + return Promise.resolve(lib_plankton.map.values(_state.events)); + } + + + /** + */ + export async function event_get( + event_key : _dali.type_event_key + ) + : Promise<_dali.type_event_object_extended> + { + return Promise.resolve(_state.events.get(event_key)); + } + + + /** + */ + export async function event_add( + calendar_id : _dali.type_calendar_id, + event_object : _dali.type_event_object + ) + : Promise + { + /** + * @todo do NOT wait? + */ + const result = await _dali.backend.calendar_event_add( + calendar_id, + event_object + ); + const event_id : _dali.type_local_resource_event_id = result.local_resource_event_id; + const event_key : _dali.type_event_key = result.hash; + const value = _state.calendars.get(calendar_id); + const calendar_object_reduced : _dali.type_calendar_object_reduced = value.reduced; + _state.events.set( + event_key, + { + "key": event_key, + "calendar_id": calendar_id, + "calendar_name": calendar_object_reduced.name, + "hue": calendar_object_reduced.hue, + "access_level": calendar_object_reduced.access_level, + "event_id": event_id, + "event_object": event_object, + } + ); + } + + + /** + */ + export async function event_change( + event_key : _dali.type_event_key, + event_object : _dali.type_event_object + ) + : Promise + { + const event_object_extended_old : _dali.type_event_object_extended = _state.events.get(event_key); + const event_object_extended_new : _dali.type_event_object_extended = { + "key": event_object_extended_old.key, + "calendar_id": event_object_extended_old.calendar_id, + "calendar_name": event_object_extended_old.calendar_name, + "hue": event_object_extended_old.hue, + "access_level": event_object_extended_old.access_level, + "event_id": event_object_extended_old.event_id, + "event_object": event_object, + }; + _state.events.set( + event_key, + event_object_extended_new + ); + /*await */_dali.backend.calendar_event_change( + event_object_extended_old.calendar_id, + event_object_extended_old.event_id, + event_object + ); + } + + + /** + */ + export async function event_remove( + event_key : _dali.type_event_key + ) + : Promise + { + const event_object_extended : _dali.type_event_object_extended = _state.events.get(event_key); + _state.events.delete(event_key); + /*await */_dali.backend.calendar_event_remove( + event_object_extended.calendar_id, + event_object_extended.event_id + ); + } + + + /** + */ + export function listen_reset( + action : ((priviliged ?: boolean) => Promise) + ) + : void + { + _listeners_reset.push(action); + } + + + /** + */ + async function notify_reset( + priviliged ?: boolean + ) + : Promise + { + for (const action of _listeners_reset) + { + await action(priviliged); + } + } + + + /** + */ + export async function initialize( + ) + : Promise + { + _state = { + "users": ( + _dali.is_logged_in() + ? + (await _dali.backend.user_list()) + : + [] + ), + "calendars": lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make( + calendar_id => calendar_id.toFixed(0) + ) + ), + "events": lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make( + event_key => event_key + ) + ), + "covered_dates": make_date_set( + ), + }; + + _dali.listen_login( + async () => { + _state.users = await _dali.backend.user_list(); + await sync_calendars(); + lib_plankton.map.clear(_state.events); + lib_plankton.set.clear(_state.covered_dates); + notify_reset(true); + } + ); + _dali.listen_logout( + async () => { + _state.users = []; + await sync_calendars(); + lib_plankton.map.clear(_state.events); + lib_plankton.set.clear(_state.covered_dates); + notify_reset(false); + } + ); + + await sync_calendars(); + // await sync_events(); + } + +} diff --git a/source/pages/login/logic.ts b/source/pages/login/logic.ts deleted file mode 100644 index 3c98c3c..0000000 --- a/source/pages/login/logic.ts +++ /dev/null @@ -1,118 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "login", - async (parameters, target_element) => { - target_element.innerHTML = ""; - const preparation : {kind : string; data : any;} = await _dali.backend.session_prepare( - { - "oidc_redirect_uri_template": _dali.conf.get()["misc"]["oidc_redirect_uri_template"], - } - ); - switch (preparation.kind) - { - case "internal": - { - target_element.innerHTML = await _dali.helpers.template_coin( - "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("page.login.internal.name"), - }, - { - "name": "password", - "input": new lib_plankton.zoo_input.class_input_password(), - "label": lib_plankton.translate.get("page.login.internal.password"), - }, - ] - ), - [ - { - "label": lib_plankton.translate.get("page.login.internal.do"), - "target": "submit", - "procedure": async (get_value, get_representation) => { - const value : any = await get_value(); - try - { - await _dali.backend.session_begin( - value.name, - value.password - ); - lib_plankton.zoo_page.nav_set_groups(["logged_in"]); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) - { - lib_plankton.zoo_page.set( - { - "name": "login", - "parameters": { - "name": value.name, - } - } - ); - } - } - }, - ] - ); - await form.setup(document.querySelector("#login")); - await form.input_write( - { - "name": (parameters.name ?? ""), - "password": "", - } - ); - break; - } - case "oidc": - { - let element_a : HTMLElement = document.createElement("a");; - element_a.textContent = lib_plankton.string.coin( - lib_plankton.translate.get("page.login.oidc.via"), - { - "title": preparation.data.label, - } - ); - element_a.setAttribute("href", preparation.data.url); - target_element.innerHTML = ""; - target_element.appendChild(element_a); - break; - } - default: - { - break; - } - } - return Promise.resolve(undefined); - } - ); - -} - diff --git a/source/pages/login/templates/default.html.tpl b/source/pages/login/templates/default.html.tpl deleted file mode 100644 index d386a39..0000000 --- a/source/pages/login/templates/default.html.tpl +++ /dev/null @@ -1,2 +0,0 @@ -
-
diff --git a/source/pages/logout/logic.ts b/source/pages/logout/logic.ts deleted file mode 100644 index 43388d1..0000000 --- a/source/pages/logout/logic.ts +++ /dev/null @@ -1,36 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "logout", - async (parameters, target_element) => { - target_element.innerHTML = ""; - try - { - await _dali.backend.session_end( - ); - } - catch (error) - { - lib_plankton.log.notice( - "dali.logout_failed", - { - "details": String(error), - } - ); - } - lib_plankton.zoo_page.nav_set_groups(["logged_out"]); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": { - } - } - ); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/oidc_finish/logic.ts b/source/pages/oidc_finish/logic.ts index 7ac4a14..d6a3838 100644 --- a/source/pages/oidc_finish/logic.ts +++ b/source/pages/oidc_finish/logic.ts @@ -8,6 +8,7 @@ namespace _dali.pages async (parameters, target_element) => { target_element.innerHTML = ""; await _dali.backend.set_session_key(parameters["session_key"]); + await _dali.notify_login(); lib_plankton.zoo_page.set( { "name": "overview", diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index d2f2b15..e10ecd0 100644 --- a/source/pages/overview/logic.ts +++ b/source/pages/overview/logic.ts @@ -7,13 +7,13 @@ namespace _dali.pages.overview "overview", async (parameters, target_element) => { // params - const view_mode : _dali.type.enum_view_mode = view_mode_determine(parameters["mode"] ?? "auto"); + const view_mode : _dali.enum_view_mode = _dali.helpers.view_mode_determine(parameters["mode"] ?? "auto"); /** * @todo ordentlich machen (nicht nur week und list) */ const set_view_mode = (view_mode) => { - const compact : boolean = (view_mode !== _dali.type.enum_view_mode.week); + const compact : boolean = (view_mode !== _dali.enum_view_mode.week); target_element.querySelector("#overview").classList.toggle("overview-compact", compact); }; @@ -30,11 +30,11 @@ namespace _dali.pages.overview const widget_mode_switcher : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.mode_switcher.class_widget_mode_switcher( [ { - "mode": _dali.type.enum_view_mode.week, + "mode": _dali.enum_view_mode.week, "label": lib_plankton.translate.get("page.overview.mode.week"), }, { - "mode": _dali.type.enum_view_mode.list, + "mode": _dali.enum_view_mode.list, "label": lib_plankton.translate.get("page.overview.mode.list"), }, ], @@ -60,54 +60,60 @@ namespace _dali.pages.overview 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; + + const get_available_calendars = async () => { + 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 update sources + * @todo update listview */ - const update = async () => { + const update_sources_and_entries = async (priviliged = null) => { + await widget_sources.update({"priviliged": priviliged}); + await widget_weekview.update_entries(); + }; + /** + * @todo update listview + */ + const update_entries = async (priviliged = null) => { await widget_weekview.update_entries(); - await widget_sources.update(); }; // hint { - if (! await _dali.backend.is_logged_in()) - { - target_element.querySelector("#overview-hint").textContent = lib_plankton.translate.get("page.overview.login_hint"); - } - else - { - // do nothing - } + 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()); } // sources { widget_sources = new _dali.widgets.sources.class_widget_sources( - _dali.backend.calendar_list, + _dali.model.calendar_list, { + "initial_priviliged": _dali.is_logged_in(), "action_create": () => { (async () => { const widget = new _dali.widgets.calendar_edit.class_widget_calendar_edit( + await _dali.model.user_list(), { "read_only": false, "action_cancel": () => { _dali.overlay.toggle({"mode": false}); }, - "action_add": (data) => { - _dali.backend.calendar_add( - { - "name": data.name, - "hue": data.hue, - "access": data.access, - "resource": { - "kind": "local", - "data": { - "events": [], - } - } - } + "action_add": (calendar_object) => { + _dali.model.calendar_add( + calendar_object ) .then( () => { - update(); + update_sources_and_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -115,7 +121,7 @@ namespace _dali.pages.overview (reason) => { lib_plankton.log.warning( "dali.overview.calendar_add_error", - {"details": String(reason)} + {"reason": String(reason)} ); } ); @@ -132,62 +138,59 @@ namespace _dali.pages.overview let read_only : boolean; switch (entry.access_level) { - case _dali.type.enum_access_level.none: + case _dali.enum_access_level.none: { throw (new Error("this event should not be visible")); break; } - case _dali.type.enum_access_level.edit: - case _dali.type.enum_access_level.view: + case _dali.enum_access_level.edit: + case _dali.enum_access_level.view: { read_only = true; break; } - case _dali.type.enum_access_level.admin: + case _dali.enum_access_level.admin: { read_only = false; break; } } (async () => { - const calendar_id : _dali.type.calendar_id = entry.id; - const calendar_object : _dali.type.calendar_object = await _dali.backend.calendar_get( + const calendar_id : _dali.type_calendar_id = entry.id; + 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( + await _dali.model.user_list(), { "read_only": read_only, "action_cancel": () => { _dali.overlay.toggle({"mode": false}); }, "action_change": (data) => { - _dali.backend.calendar_change( + _dali.model.calendar_change( calendar_id, data ) .then( () => { - update(); + update_sources_and_entries(); _dali.overlay.toggle({"mode": false}); } ); }, "action_remove": (data) => { - _dali.backend.calendar_remove( + _dali.model.calendar_remove( calendar_id ) .then( () => { - update(); + update_sources_and_entries(); _dali.overlay.toggle({"mode": false}); } ); }, - "initial_value": { - "name": calendar_object.name, - "hue": calendar_object.hue, - "access": calendar_object.access, - }, + "initial_value": calendar_object, } ); _dali.overlay.clear(); @@ -205,16 +208,31 @@ namespace _dali.pages.overview } // events { - const get_entries = (from_pit, to_pit, calendar_ids) => _dali.backend.events( - from_pit, - to_pit, - { - "calendar_ids": calendar_ids, - } - ); - const action_select_event = (calendar_id, access_level, event_id) => { + const get_entries = async (from_pit, to_pit, calendar_ids) => { + /** + * @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(); + }; + const action_select_event = async (event_key) => { + const event_object_extended : _dali.type_event_object_extended = await _dali.model.event_get(event_key); + const calendar_id = event_object_extended.calendar_id; + const access_level = event_object_extended.access_level; + const event_id = event_object_extended.event_id; /* - if (! await _dali.backend.is_logged_in()) + if (! _dali.is_logged_in()) { // do nothing } @@ -225,29 +243,30 @@ namespace _dali.pages.overview let read_only : boolean; switch (access_level) { - case _dali.type.enum_access_level.none: + case _dali.enum_access_level.none: { throw (new Error("this event should not be visible")); break; } - case _dali.type.enum_access_level.view: + case _dali.enum_access_level.view: { read_only = true; break; } - case _dali.type.enum_access_level.edit: - case _dali.type.enum_access_level.admin: + case _dali.enum_access_level.edit: + case _dali.enum_access_level.admin: { read_only = false; break; } } (async () => { - const event_object : _dali.type.event_object = await _dali.backend.calendar_event_get( + const event_object : _dali.type_event_object = await _dali.backend.calendar_event_get( calendar_id, event_id ); const widget = new _dali.widgets.event_edit.class_widget_event_edit( + (await get_available_calendars()), { "calendar_id": calendar_id, "event_name": event_object.name, @@ -263,9 +282,8 @@ namespace _dali.pages.overview _dali.overlay.toggle({"mode": false}); }, "action_change": (data) => { - _dali.backend.calendar_event_change( - calendar_id, - event_id, + _dali.model.event_change( + event_key, { "name": data.event_name, "begin": data.event_begin, @@ -277,7 +295,7 @@ namespace _dali.pages.overview ) .then( () => { - update(); + update_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -285,19 +303,18 @@ namespace _dali.pages.overview (reason) => { lib_plankton.log.warning( "dali.overview.event_change.error", - {"details": String(reason)} + {"reason": String(reason)} ); } ); }, "action_remove": () => { - _dali.backend.calendar_event_remove( - calendar_id, - event_id + _dali.model.event_remove( + event_key ) .then( () => { - update(); + update_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -305,7 +322,7 @@ namespace _dali.pages.overview (reason) => { lib_plankton.log.warning( "dali.overview.event_remove_error", - {"details": String(reason)} + {"reason": String(reason)} ); } ); @@ -351,7 +368,7 @@ namespace _dali.pages.overview "action_select_event": action_select_event, "action_select_day": (date) => { /* - if (! await _dali.backend.is_logged_in()) + if (! _dali.is_logged_in()) { // do nothing } @@ -361,6 +378,7 @@ namespace _dali.pages.overview */ (async () => { const widget = new _dali.widgets.event_edit.class_widget_event_edit( + (await get_available_calendars()), { "calendar_id": null, "event_name": "", @@ -400,7 +418,7 @@ namespace _dali.pages.overview _dali.overlay.toggle({"mode": false}); }, "action_add": (data) => { - _dali.backend.calendar_event_add( + _dali.model.event_add( data.calendar_id, { "name": data.event_name, @@ -413,7 +431,7 @@ namespace _dali.pages.overview ) .then( () => { - update(); + update_entries(); _dali.overlay.toggle({"mode": false}); } ) @@ -435,6 +453,12 @@ namespace _dali.pages.overview ); await widget_weekview.load(target_element.querySelector("#overview-pane-right-weekview")); } + _dali.model.listen_reset( + async (priviliged) => { + update_sources_and_entries(priviliged); + target_element.querySelector("#overview-hint").classList.toggle("overview-hint-hidden", priviliged); + } + ); } return Promise.resolve(undefined); }, diff --git a/source/resources/backend.ts b/source/resources/backend.ts index c0f6377..bb5cef7 100644 --- a/source/resources/backend.ts +++ b/source/resources/backend.ts @@ -3,6 +3,90 @@ namespace _dali.backend { + /** + */ + type type_conf = { + scheme : string; + host : string; + port : int; + path : string; + }; + + + /** + */ + type type_request = { + method : lib_plankton.http.enum_method; + action : string; + input : (null | any); + }; + + + /** + */ + type type_event_object = { + name : string; + begin : { + timezone_shift: int; + date: { + year: int; + month: int; + day: int; + }; + time: ( + null + | + { + hour: int; + minute: int; + second: int; + } + ); + }; + end : ( + null + | + { + timezone_shift: int; + date: { + year: int; + month: int; + day: int; + }; + time: ( + null + | + { + hour: int; + minute: int; + second: int; + } + ); + } + ); + location : ( + null + | + string + ); + link : ( + null + | + string + ); + description : ( + null + | + string + ); + }; + + + /** + */ + var _conf : type_conf; + + /** */ var _data_chest : ( @@ -22,42 +106,27 @@ namespace _dali.backend /** - * meant for translation of the API values */ - function access_level_encode( - access_level : _dali.type.enum_access_level - ) : ("none" | "view" | "edit" | "admin") - { - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - } - - - /** - * meant for translation of the API values - */ - function access_level_decode( - access_level_encoded : ("none" | "view" | "edit" | "admin") - ) : _dali.type.enum_access_level - { - switch (access_level_encoded) { - case "none": return _dali.type.enum_access_level.none; - case "view": return _dali.type.enum_access_level.view; - case "edit": return _dali.type.enum_access_level.edit; - case "admin": return _dali.type.enum_access_level.admin; - } - } + var _queue : { + items : Array< + { + request : type_request; + resolve : ((result : any) => void); + reject : ((reason : any) => void); + } + >; + busy : boolean; + }; /** */ - export async function init( - ) : Promise + export async function initialize( + conf : type_conf + ) + : Promise { + _conf = conf; _data_chest = lib_plankton.storage.localstorage.implementation_chest( { "corner": "zeitbild", @@ -71,6 +140,10 @@ namespace _dali.backend ) */ ); + _queue = { + "items": [], + "busy": false, + }; return Promise.resolve(undefined); } @@ -78,7 +151,8 @@ namespace _dali.backend /** */ async function get_session_key( - ) : Promise<(null | string)> + ) + : Promise<(null | string)> { try { @@ -93,24 +167,23 @@ namespace _dali.backend /** */ - async function call( - method : lib_plankton.http.enum_method, - action : string, - input : (null | any) - ) : Promise + async function call_real( + request : type_request + ) + : Promise { const with_body : boolean = ( [ lib_plankton.http.enum_method.post, lib_plankton.http.enum_method.put, lib_plankton.http.enum_method.patch, - ].includes(method) + ].includes(request.method) ); const session_key : (null | string) = await get_session_key(); const http_request : lib_plankton.http.type_request = { "version": "HTTP/2", "scheme": ( - (_dali.conf.get()["backend"]["scheme"] === "http") + (_conf.scheme === "http") ? "http" : @@ -119,24 +192,24 @@ namespace _dali.backend "host": lib_plankton.string.coin( "{{host}}:{{port}}", { - "host": _dali.conf.get()["backend"]["host"], - "port": _dali.conf.get()["backend"]["port"].toFixed(0), + "host": _conf.host, + "port": _conf.port.toFixed(0), } ), "path": lib_plankton.string.coin( "{{base}}{{action}}", { - "base": _dali.conf.get()["backend"]["path"], - "action": action, + "base": _conf.path, + "action": request.action, } ), - "method": method, + "method": request.method, "query": ( - (with_body || (input === null)) + (with_body || (request.input === null)) ? null : - ("?" + lib_plankton.www_form.encode(input)) + ("?" + lib_plankton.www_form.encode(request.input)) ), "headers": Object.assign( {}, @@ -156,11 +229,11 @@ namespace _dali.backend ) ), "body": ( - ((! with_body) || (input === null)) + ((! with_body) || (request.input === null)) ? null : - /*Buffer.from*/(lib_plankton.json.encode(input)) + /*Buffer.from*/(lib_plankton.json.encode(request.input)) ), }; const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(http_request); @@ -178,30 +251,97 @@ namespace _dali.backend return Promise.resolve(output); } } - + /** - * @todo mneh … */ - export async function is_logged_in( - ) : Promise + async function process( + ) + : Promise { - return lib_plankton.cache.get( - _cache, - "status", - 5, - async () => { - // return ((await get_session_key()) !== null); - const result : { - logged_in : boolean; - } = await call( - lib_plankton.http.enum_method.get, - "/session/status", - null + if (_queue.busy) + { + // do nothing + } + else + { + _queue.busy = true; + while (_queue.items.length > 0) + { + const entry = _queue.items.shift(); + let successful : boolean; + let reason : any; + let result : any; + try + { + result = await call_real(entry.request); + successful = true; + } + catch (error) + { + reason = error; + successful = false; + } + if (successful) + { + entry.resolve(result); + } + else + { + entry.reject(reason); + } + } + _queue.busy = false; + // process(); + } + } + + + /** + */ + async function call( + method : lib_plankton.http.enum_method, + action : string, + input : (null | any) + ) + : Promise + { + const request : type_request = { + "method": method, + "action": action, + "input": input, + }; + const promise : Promise = new Promise( + (resolve, reject) => { + _queue.items.push( + { + "request": request, + "resolve": resolve, + "reject": reject, + } ); - return result.logged_in; } ); + process(); + return promise; + } + + + /** + */ + export function status( + ) + : Promise< + { + logged_in : boolean; + } + > + { + return call( + lib_plankton.http.enum_method.get, + "/session/status", + null + ) } @@ -209,7 +349,8 @@ namespace _dali.backend */ export async function session_prepare( input : any - ) : Promise<{kind : string; data : any;}> + ) + : Promise<{kind : string; data : any;}> { return call( lib_plankton.http.enum_method.post, @@ -223,7 +364,8 @@ namespace _dali.backend */ export function set_session_key( session_key : string - ) : Promise + ) + : Promise { return ( _data_chest.write("session_key", session_key) @@ -236,7 +378,8 @@ namespace _dali.backend export async function session_begin( name : string, password : string - ) : Promise + ) + : Promise { const session_key : string = await call( lib_plankton.http.enum_method.post, @@ -254,7 +397,8 @@ namespace _dali.backend /** */ export async function session_end( - ) : Promise + ) + : Promise { await call( lib_plankton.http.enum_method.delete, @@ -269,7 +413,8 @@ namespace _dali.backend /** */ export function user_list( - ) : Promise< + ) + : Promise< Array< { id : int; @@ -289,7 +434,8 @@ namespace _dali.backend /** */ export function user_dav_conf( - ) : Promise< + ) + : Promise< ( null | @@ -319,7 +465,8 @@ namespace _dali.backend /** */ export function user_dav_token( - ) : Promise + ) + : Promise { return call( lib_plankton.http.enum_method.patch, @@ -332,36 +479,22 @@ namespace _dali.backend /** */ export async function calendar_list( - ) : Promise< + ) + : Promise< Array< { id : int; name : string; hue : float; - access_level : _dali.type.enum_access_level; + access_level : string; } > > { - return ( - call( - lib_plankton.http.enum_method.get, - "/calendar", - null - ) - .then( - (entries) => Promise.resolve( - entries - .map( - (entry) => ({ - "id": entry.id, - "name": entry.name, - "hue": entry.hue, - "access_level": access_level_decode(entry.access_level), - }) - ) - ) - ) + return call( + lib_plankton.http.enum_method.get, + "/calendar", + null ); } @@ -369,158 +502,28 @@ namespace _dali.backend /** */ export async function calendar_get( - calendar_id : _dali.type.calendar_id - ) : Promise< - _dali.type.calendar_object - > - { - return ( - call( - lib_plankton.http.enum_method.get, - lib_plankton.string.coin( - "/calendar/{{calendar_id}}", - { - "calendar_id": calendar_id.toFixed(0), - } - ), - null - ) - .then( - (raw) => Promise.resolve( - { - "name": raw.name, - "hue": raw.hue, - "access": { - "public": raw.access.public, - "default_level": access_level_decode(raw.access.default_level), - "attributed": lib_plankton.map.hashmap.implementation_map( - lib_plankton.map.hashmap.make( - x => x.toFixed(0), - { - "pairs": ( - raw.access.attributed - .map( - (entry) => ({ - "key": entry.user_id, - "value": access_level_decode(entry.level), - }) - ) - ), - } - ) - ), - }, - // "resource_id": raw.resource_id - // TODO - "resource": { - "kind": "local", - "data": { - "events": [] - } - }, - } - ) - ) - ); - } - - - /** - */ - export async function calendar_add( - calendar_object : _dali.type.calendar_object - ) : Promise< - _dali.type.calendar_id - > - { - return call( - lib_plankton.http.enum_method.post, - lib_plankton.string.coin( - "/calendar", - { - } - ), - { - "name": calendar_object.name, - "hue": calendar_object.hue, - "access": { - "public": calendar_object.access.public, - "default_level": access_level_encode(calendar_object.access.default_level), - "attributed": ( - lib_plankton.map.dump(calendar_object.access.attributed) - .map( - (pair) => ({ - "user_id": pair.key, - "level": access_level_encode(pair.value), - }) - ) - ) - }, - "resource": calendar_object.resource, - } - ); - } - - - /** - */ - export async function calendar_change( - calendar_id : _dali.type.calendar_id, - data : { + calendar_id : int + ) + : Promise< + { name : string; hue : float; access : { public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; + default_level : string; + attributed : Array< + { + user_id : int; + level : string; + } + > }; - } - ) : Promise< - void + resource_id : int; + } > { return call( - lib_plankton.http.enum_method.put, - lib_plankton.string.coin( - "/calendar/{{calendar_id}}", - { - "calendar_id": calendar_id.toFixed(0), - } - ), - { - "name": data.name, - "hue": data.hue, - "access": { - "public": data.access.public, - "default_level": access_level_encode(data.access.default_level), - "attributed": ( - lib_plankton.map.dump(data.access.attributed) - .map( - (pair) => ({ - "user_id": pair.key, - "level": access_level_encode(pair.value), - }) - ) - ) - }, - } - ); - } - - - /** - */ - export async function calendar_remove( - calendar_id : _dali.type.calendar_id - ) : Promise< - void - > - { - return call( - lib_plankton.http.enum_method.delete, + lib_plankton.http.enum_method.get, lib_plankton.string.coin( "/calendar/{{calendar_id}}", { @@ -532,12 +535,122 @@ namespace _dali.backend } + /** + */ + export async function calendar_add( + data : { + name : string; + access : { + public : boolean; + default_level : string; + attributed : Array< + { + user_id : int; + level : string; + } + >; + }; + resource : ( + { + kind : "local"; + data : { + }; + } + | + { + kind : "ics_feed"; + data : { + url : string; + from_fucked_up_wordpress : boolean; + }; + } + ); + hue : float; + } + ) + : Promise< + int + > + { + return call( + lib_plankton.http.enum_method.post, + lib_plankton.string.coin( + "/calendar", + { + } + ), + data + ); + } + + + /** + */ + export async function calendar_change( + id : int, + data : { + name : string; + hue : float; + access : { + public : boolean; + default_level : string; + attributed : Array< + { + user_id : int; + level : string; + } + >; + }; + } + ) + : Promise< + void + > + { + return call( + lib_plankton.http.enum_method.put, + lib_plankton.string.coin( + "/calendar/{{id}}", + { + "id": id.toFixed(0), + } + ), + data + ); + } + + + /** + */ + export async function calendar_remove( + id : int + ) + : Promise< + void + > + { + return call( + lib_plankton.http.enum_method.delete, + lib_plankton.string.coin( + "/calendar/{{id}}", + { + "id": id.toFixed(0), + } + ), + null + ); + } + + /** */ export async function calendar_event_get( - calendar_id : _dali.type.calendar_id, - event_id : _dali.type.local_resource_event_id - ) : Promise<_dali.type.event_object> + calendar_id : int, + event_id : int + ) + : Promise< + type_event_object + > { return call( lib_plankton.http.enum_method.get, @@ -556,9 +669,15 @@ namespace _dali.backend /** */ export async function calendar_event_add( - calendar_id : _dali.type.calendar_id, - event_object : _dali.type.event_object - ) : Promise + calendar_id : int, + event_data : type_event_object + ) + : Promise< + { + local_resource_event_id : (null | int); + hash : string; + } + > { return call( lib_plankton.http.enum_method.post, @@ -568,7 +687,7 @@ namespace _dali.backend "calendar_id": calendar_id.toFixed(0), } ), - event_object + event_data ); } @@ -577,10 +696,11 @@ namespace _dali.backend * @todo Möglichkeit den Kalender zu ändern */ export async function calendar_event_change( - calendar_id : _dali.type.calendar_id, - event_id : _dali.type.local_resource_event_id, - event_object : _dali.type.event_object - ) : Promise + calendar_id : int, + event_id : int, + event_object : type_event_object + ) + : Promise { return call( lib_plankton.http.enum_method.put, @@ -599,9 +719,10 @@ namespace _dali.backend /** */ export async function calendar_event_remove( - calendar_id : _dali.type.calendar_id, - event_id : _dali.type.local_resource_event_id - ) : Promise + calendar_id : int, + event_id : int + ) + : Promise { return call( lib_plankton.http.enum_method.delete, @@ -621,64 +742,47 @@ namespace _dali.backend * @todo prevent loops */ export async function events( - from_pit : lib_plankton.pit.type_pit, - to_pit : lib_plankton.pit.type_pit, + from_timestamp : int, + to_timestamp : int, { "calendar_ids": calendar_ids = null, } : { - calendar_ids ?: (null | Array<_dali.type.calendar_id>); + calendar_ids ?: (null | Array); } = { } - ) : Promise< + ) + : Promise< Array< { - key : _dali.type.event_key; - calendar_id : _dali.type.calendar_id; + hash : string; + calendar_id : int; calendar_name : string; hue : float; - access_level : _dali.type.enum_access_level; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; + access_level : string; + event_id : (null | int); + event_object : type_event_object; } > > { - return ( - call( - lib_plankton.http.enum_method.get, - "/events", - Object.assign( - { - "from": from_pit, - "to": to_pit, - }, - ( - (calendar_ids === null) - ? - {} - : - {"calendar_ids": calendar_ids.join(",")} - ) - ) - ) - .then( - (data) => Promise.resolve( - data - .map( - (entry) => ({ - "key": entry.hash, - "calendar_id": entry.calendar_id, - "calendar_name": entry.calendar_name, - "hue": entry.hue, - "access_level": access_level_decode(entry.access_level), - "event_id": entry.event_id, - "event_object": entry.event_object, - }) - ) + return call( + lib_plankton.http.enum_method.get, + "/events", + Object.assign( + { + "from": from_timestamp, + "to": to_timestamp, + }, + ( + (calendar_ids === null) + ? + {} + : + {"calendar_ids": calendar_ids.join(",")} ) ) ); diff --git a/source/style/main.css b/source/style/main.css index 2f85e01..49bbfc4 100644 --- a/source/style/main.css +++ b/source/style/main.css @@ -5,15 +5,15 @@ html { - background-color: hsl(0, 0%, 12.5%); - color: hsl(0, 0%, 100%); + background-color: hsl(var(--hue), 0%, 12.5%); + color: hsl(var(--hue), 0%, 100%); font-family: sans-serif; } header { - background-color: hsl(0, 0%, 25%); /* + background-color: hsl(0, 0%, 25%); border-bottom: 2px solid #888; padding-bottom: 16px; */ @@ -29,7 +29,7 @@ header left: 0; right: 0; bottom: 0; - background-color: rgba(0, 0, 0, 0.75); + background-color: hsla(var(--hue), 0%, 0%, 0.75); z-index: 2; } @@ -109,13 +109,19 @@ button padding: 8px; text-transform: uppercase; cursor: pointer; + + background-color: hsl(var(--hue), 0%, 6.125%); + border: 1px solid hsl(var(--hue), 0%, 6.125%); + color: hsl(0, 0%, 87.5%); + margin: 4px; + border-radius: 4px; } -input,select,textarea,button +input,select,textarea { - background-color: hsl(0, 0%, 0%); + background-color: hsl(0, 0%, 25%); + border: 1px solid hsl(0, 0%, 25%); color: hsl(0, 0%, 100%); - border: 1px solid hsl(0, 0%, 75%); margin: 4px; - border-radius: 2px; + border-radius: 4px; } diff --git a/source/style/page-calendar_add.css b/source/style/page-calendar_add.css deleted file mode 100644 index 369de40..0000000 --- a/source/style/page-calendar_add.css +++ /dev/null @@ -1,13 +0,0 @@ -#calendar_add .plankton_input_group_field[rel="attributed"] > .plankton_input_list > .plankton_input_list_elements > .plankton_input_list_element -{ - display: flex; - flex-direction: row; - flex-wrap: wrap; -} - -#calendar_add .plankton_input_group_field[rel="attributed"] > .plankton_input_list > .plankton_input_list_elements > .plankton_input_list_element > .plankton_input_list_element_input > .plankton_input_group -{ - display: flex; - flex-direction: row; - flex-wrap: wrap; -} diff --git a/source/style/page-event_add.css b/source/style/page-event_add.css deleted file mode 100644 index c0e2d44..0000000 --- a/source/style/page-event_add.css +++ /dev/null @@ -1,8 +0,0 @@ -#event_add .plankton_input_group_field[rel="begin"] > .plankton_input_group -, -#event_add .plankton_input_group_field[rel="end"] > .plankton_input_soft_container > .plankton_input_soft_core_wrapper > .plankton_input_group -{ - display: flex; - flex-direction: row; - flex-wrap: wrap; -} diff --git a/source/style/page-event_edit.css b/source/style/page-event_edit.css deleted file mode 100644 index c3ac020..0000000 --- a/source/style/page-event_edit.css +++ /dev/null @@ -1,8 +0,0 @@ -#event_edit .plankton_input_group_field[rel="begin"] > .plankton_input_group -, -#event_edit .plankton_input_group_field[rel="end"] > .plankton_input_soft_container > .plankton_input_soft_core_wrapper > .plankton_input_group -{ - display: flex; - flex-direction: row; - flex-wrap: wrap; -} diff --git a/source/style/page-overview.css b/source/style/page-overview.css index fa2c45c..cd010d7 100644 --- a/source/style/page-overview.css +++ b/source/style/page-overview.css @@ -11,6 +11,11 @@ font-weight: bold; } +#overview-hint.overview-hint-hidden +{ + display: none; +} + #overview-body { display: flex; diff --git a/source/style/plankton.css b/source/style/plankton.css index 7c0a407..84ef684 100644 --- a/source/style/plankton.css +++ b/source/style/plankton.css @@ -1,26 +1,33 @@ -.plankton_input_group_field { +.plankton_input_group_field +{ margin-bottom: 8px; } -.plankton_input_group_field_label { +.plankton_input_group_field_label +{ display: block; font-weight: bold; font-size: 0.8em; + text-transform: uppercase; } -.plankton_input_soft_container > * { +.plankton_input_soft_container > * +{ display: inline-block; } -.plankton_input_soft_setter { +.plankton_input_soft_setter +{ margin-right: 8px; } -.plankton_input_soft_inactive { +.plankton_input_soft_inactive +{ display: none !important; } -.plankton_input_group { +.plankton_input_group +{ margin-left: 24px; } @@ -44,3 +51,8 @@ { vertical-align: top; } + +.plankton_input_password_exhibit +{ + display: none; +} diff --git a/source/style/widget-menu.css b/source/style/widget-menu.css new file mode 100644 index 0000000..501f1d4 --- /dev/null +++ b/source/style/widget-menu.css @@ -0,0 +1,74 @@ +.widget-menu +{ + margin-left: auto; + margin-right: 8px; + max-width: 40px; +} + +.widget-menu.widget-menu-collapsed > .widget-menu-platform +{ + display: none; +} + +.widget-menu-button +{ +} + +.widget-menu-entry +{ + cursor: pointer; +} + +.widget-menu-platform +{ + background-color: hsl(var(--hue), 0%, 25%); + + border-radius: 2px; + border: 1px solid hsl(var(--hue), 0%, 0%); + + padding: 8px; + + min-width: 200px; +} + +.widget-menu-platform:not(.widget-menu-platform-collapsed) +{ + position: fixed; + top: 32px; + right: 32px; + z-index: 2; +} + +.widget-menu-entries +{ + padding: 0; + margin: 0; + list-style-type: none; +} + +.widget-menu-entry +{ + margin: 12px 16px; + padding: 8px; + text-transform: capitalize; +} + +.widget-menu-entry:not(:hover) +{ + background-color: hsl(var(--hue), 0%, 25%); + color: hsl(var(--hue), 0%, 100%); +} + +.widget-menu-entry:hover::after +{ + content: " «"; + /* + background-color: hsl(var(--hue), 0%, 50%); + color: hsl(var(--hue), 0%, 100%); + */ +} + +.widget-menu-entry.widget-menu-entry-hidden +{ + display: none; +} diff --git a/source/style/widget-sources.css b/source/style/widget-sources.css index efc6e46..47dfbb5 100644 --- a/source/style/widget-sources.css +++ b/source/style/widget-sources.css @@ -3,6 +3,16 @@ font-size: 0.75em; } +.sources:not(.sources-priviliged) > .sources-create +{ + display: none; +} + +.sources-create +{ + margin-left: 8px; +} + .sources-entries { margin: 0; diff --git a/source/types.ts b/source/types.ts new file mode 100644 index 0000000..1f7b695 --- /dev/null +++ b/source/types.ts @@ -0,0 +1,245 @@ + +/** + */ +namespace _dali +{ + + /** + */ + export enum enum_access_level { + none, + view, + edit, + admin + } + + + /** + */ + export type type_user_id = int; + + + /** + */ + export type type_user_object = { + name : string; + email_address : ( + null + | + string + ); + }; + + + /** + * @todo deprecate? + */ + export type type_local_resource_event_id = int; + + + /** + * info: das ist nicht deckungsgleich mit der Event-ID aus dem Backend; hiermit werden sowohl lokale als auch + * extern eingebundene Events kodiert + * + * @example "local:1234" + * @example "ics~2345" + */ + export type type_event_key = string; + + + /** + */ + export type type_event_object = { + name : string; + begin : lib_plankton.pit.type_datetime; + end : ( + null + | + lib_plankton.pit.type_datetime + ); + location : ( + null + | + string + ); + link : ( + null + | + string + ); + description : ( + null + | + string + ); + }; + + + /** + */ + export type type_event_entry = { + id : (null | type_local_resource_event_id); + key : type_event_key; + object : type_event_object; + }; + + + /** + */ + export type type_event_object_extended = { + key : type_event_key; + calendar_id : type_calendar_id; + calendar_name : string; + hue : float; + access_level : enum_access_level; + event_id : (null | type_local_resource_event_id); + event_object : type_event_object; + }; + + + /** + */ + export type type_resource_id = int; + + + /** + */ + export type type_resource_object = ( + { + kind : "local"; + data : { + events : Array< + type_event_object + >; + }; + } + | + { + kind : "caldav"; + data : { + read_only : boolean; + url : string; + }; + } + ); + + + /** + */ + export type type_calendar_id = int; + + + /** + */ + export type type_calendar_object = { + name : string; + hue : float; + access : { + public : boolean; + default_level : enum_access_level; + attributed : lib_plankton.map.type_map< + type_user_id, + enum_access_level + >; + }; + resource_id : type_resource_id; + }; + + + /** + */ + export type type_calendar_object_reduced = { + name : string; + hue : float; + access_level : enum_access_level; + }; + + + /** + */ + export type type_calendar_object_reduced_with_id = { + id : type_calendar_id; + name : string; + hue : float; + access_level : enum_access_level; + }; + + + /** + */ + export enum enum_view_mode + { + week, + list, + } + + + /** + */ + export function access_level_encode( + access_level : _dali.enum_access_level + ) : ("none" | "view" | "edit" | "admin") + { + switch (access_level) + { + case _dali.enum_access_level.none: return "none"; + case _dali.enum_access_level.view: return "view"; + case _dali.enum_access_level.edit: return "edit"; + case _dali.enum_access_level.admin: return "admin"; + } + } + + + /** + */ + export function access_level_decode( + representation : /*("none" | "view" | "edit" | "admin")*/string + ) : _dali.enum_access_level + { + switch (representation) + { + case "none": return _dali.enum_access_level.none; + case "view": return _dali.enum_access_level.view; + case "edit": return _dali.enum_access_level.edit; + case "admin": return _dali.enum_access_level.admin; + default: throw (new Error("invalid access level representation: " + representation)); + } + } + + + /** + */ + export function view_mode_encode( + mode : _dali.enum_view_mode + ) : string + { + switch (mode) + { + case _dali.enum_view_mode.week: {return "week"; break;} + case _dali.enum_view_mode.list: {return "list"; break;} + default: {throw (new Error("invalid mode"));} + } + } + + + /** + */ + export function view_mode_decode( + view_mode_encoded : string + ) : _dali.enum_view_mode + { + const map : Record = { + "week": _dali.enum_view_mode.week, + "list": _dali.enum_view_mode.list, + }; + if (! (view_mode_encoded in map)) + { + throw (new Error("invalid mode: " + view_mode_encoded)); + } + else + { + return map[view_mode_encoded]; + } + } + +} diff --git a/source/widgets/calendar_edit/logic.ts b/source/widgets/calendar_edit/logic.ts index f4327eb..455670e 100644 --- a/source/widgets/calendar_edit/logic.ts +++ b/source/widgets/calendar_edit/logic.ts @@ -1,28 +1,17 @@ namespace _dali.widgets.calendar_edit { - /** - */ - export type type_value = { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - }; - - /** */ export class class_widget_calendar_edit implements lib_plankton.zoo_widget.interface_widget { + /** + */ + private users : Array<{id : _dali.type_user_id; name : string;}>; + + /** */ private read_only : boolean; @@ -35,27 +24,28 @@ namespace _dali.widgets.calendar_edit /** */ - private action_add ?: (null | ((value : type_value) => void)); + private action_add ?: (null | ((value : _dali.type_calendar_object) => void)); /** */ - private action_change ?: (null | ((value : type_value) => void)); + private action_change ?: (null | ((value : _dali.type_calendar_object) => void)); /** */ - private action_remove ?: (null | ((value : type_value) => void)); + private action_remove ?: (null | ((value : _dali.type_calendar_object) => void)); /** */ - private initial_value : (null | type_value); + private initial_value : (null | _dali.type_calendar_object); /** */ public constructor( + users : Array<{id : _dali.type_user_id; name : string;}>, { "read_only": read_only = false, "action_cancel": action_cancel = null, @@ -68,16 +58,17 @@ namespace _dali.widgets.calendar_edit { read_only ?: boolean; action_cancel ?: (null | (() => void)); - action_add ?: (null | ((value : type_value) => void)) - action_change ?: (null | ((value : type_value) => void)); - action_remove ?: (null | ((value : type_value) => void)); - initial_value ?: (null | type_value); + action_add ?: (null | ((value : _dali.type_calendar_object) => void)) + action_change ?: (null | ((value : _dali.type_calendar_object) => void)); + action_remove ?: (null | ((value : _dali.type_calendar_object) => void)); + initial_value ?: (null | _dali.type_calendar_object); } = { } ) { + this.users = users; this.read_only = read_only; this.action_cancel = action_cancel; this.action_add = action_add; @@ -95,11 +86,11 @@ namespace _dali.widgets.calendar_edit ) : Promise { const form : lib_plankton.zoo_form.class_form< - type_value, - type_value + _dali.type_calendar_object, + _dali.type_calendar_object > = new lib_plankton.zoo_form.class_form< - type_value, - type_value + _dali.type_calendar_object, + _dali.type_calendar_object >( (value) => value, (raw) => raw, @@ -132,32 +123,23 @@ namespace _dali.widgets.calendar_edit }, { "name": "attributed", - "input": await _dali.helpers.input_attributed_access(), + "input": _dali.helpers.input_attributed_access(this.users), "label": lib_plankton.translate.get("calendar.access.attributed"), }, ] ), "label": lib_plankton.translate.get("calendar.access.access"), }, + { + "name": "resource", + "input": new lib_plankton.zoo_input.class_input_hidden( + ), + "label": lib_plankton.translate.get("calendar.resource"), + }, ] ), ( [] - // cancel - .concat( - (! (this.action_cancel === null)) - ? - [ - { - "label": lib_plankton.translate.get("common.cancel"), - "procedure": async () => { - this.action_cancel(); - } - }, - ] - : - [] - ) // add .concat( ((! this.read_only) && (! (this.action_add === null))) @@ -166,7 +148,7 @@ namespace _dali.widgets.calendar_edit { "label": lib_plankton.translate.get("widget.calendar_edit.actions.add"), "procedure": async (get_value, get_representation) => { - const value : type_value = await get_value(); + const value : _dali.type_calendar_object = await get_value(); this.action_add(value); } }, @@ -182,7 +164,7 @@ namespace _dali.widgets.calendar_edit { "label": lib_plankton.translate.get("widget.calendar_edit.actions.change"), "procedure": async (get_value, get_representation) => { - const value : type_value = await get_value(); + const value : _dali.type_calendar_object = await get_value(); this.action_change(value); } }, @@ -198,7 +180,7 @@ namespace _dali.widgets.calendar_edit { "label": lib_plankton.translate.get("widget.calendar_edit.actions.remove"), "procedure": async (get_value, get_representation) => { - const value : type_value = await get_value(); + const value : _dali.type_calendar_object = await get_value(); this.action_remove(value); } }, @@ -206,6 +188,21 @@ namespace _dali.widgets.calendar_edit : [] ) + // cancel + .concat( + (! (this.action_cancel === null)) + ? + [ + { + "label": lib_plankton.translate.get("common.cancel"), + "procedure": async () => { + this.action_cancel(); + } + }, + ] + : + [] + ) ) ); await form.setup(target_element); @@ -219,19 +216,23 @@ namespace _dali.widgets.calendar_edit "hue": lib_plankton.random.generate_unit(), "access": { "public": false, - "default_level": _dali.type.enum_access_level.view, + "default_level": _dali.enum_access_level.view, "attributed": lib_plankton.map.hashmap.implementation_map< - _dali.type.user_id, - _dali.type.enum_access_level + _dali.type_user_id, + _dali.enum_access_level >( lib_plankton.map.hashmap.make< - _dali.type.user_id, - _dali.type.enum_access_level + _dali.type_user_id, + _dali.enum_access_level >( user_id => user_id.toFixed(0), ) ), }, + /** + * @todo + */ + "resource_id": 0, } ); } diff --git a/source/widgets/event_edit/logic.ts b/source/widgets/event_edit/logic.ts index 7d1b942..093d204 100644 --- a/source/widgets/event_edit/logic.ts +++ b/source/widgets/event_edit/logic.ts @@ -4,7 +4,7 @@ namespace _dali.widgets.event_edit /** */ export type type_value = { - calendar_id : (null | _dali.type.calendar_id); + calendar_id : (null | _dali.type_calendar_id); event_name : string; event_begin : lib_plankton.pit.type_datetime; event_end : (null | lib_plankton.pit.type_datetime); @@ -34,38 +34,65 @@ namespace _dali.widgets.event_edit { /** + * [data] + */ + private available_calendars : Array< + { + id : int; + name : string; + hue : float; + access_level : _dali.enum_access_level; + } + >; + + + /** + * [data] */ private read_only : boolean; /** + * [data] + */ + private initial_value : type_value; + + + /** + * [hook] */ private action_cancel ?: (null | (() => void)); /** + * [hook] */ private action_add ?: (null | ((value : type_value) => void)); /** + * [hook] */ private action_change ?: (null | ((value : type_value) => void)); /** + * [hook] */ private action_remove ?: (null | ((value : type_value) => void)); - /** - */ - private initial_value : type_value; - - /** */ public constructor( + available_calendars : Array< + { + id : int; + name : string; + hue : float; + access_level : _dali.enum_access_level; + } + >, initial_value : type_value, { "read_only": read_only = false, @@ -87,12 +114,16 @@ namespace _dali.widgets.event_edit } ) { + // data this.read_only = read_only; + this.available_calendars = available_calendars; + this.initial_value = initial_value; + + // hooks this.action_cancel = action_cancel; this.action_add = action_add; this.action_change = action_change; this.action_remove = action_remove; - this.initial_value = initial_value; } @@ -103,23 +134,6 @@ namespace _dali.widgets.event_edit target_element : HTMLElement ) : Promise { - const available_calendars : Array< - { - id : int; - name : string; - hue : float; - access_level : _dali.type.enum_access_level; - } - > = ( - (await _dali.backend.calendar_list()) - .filter( - (entry) => ( - (entry.access_level === _dali.type.enum_access_level.edit) - || - (entry.access_level === _dali.type.enum_access_level.admin) - ) - ) - ); const form : lib_plankton.zoo_form.class_form< type_value, type_representation @@ -128,7 +142,7 @@ namespace _dali.widgets.event_edit type_representation >( (value) => ({ - "calendar_id": (value.calendar_id ?? available_calendars[0].id).toFixed(0), + "calendar_id": (value.calendar_id ?? this.available_calendars[0].id).toFixed(0), "event_name": value.event_name, "event_begin": value.event_begin, "event_end": value.event_end, @@ -151,7 +165,7 @@ namespace _dali.widgets.event_edit "name": "calendar_id", "input": new lib_plankton.zoo_input.class_input_selection( ( - available_calendars + this.available_calendars .map( (entry) => ({ "value": entry.id.toFixed(0), @@ -208,21 +222,6 @@ namespace _dali.widgets.event_edit ), ( [] - // cancel - .concat( - (! (this.action_cancel === null)) - ? - [ - { - "label": lib_plankton.translate.get("common.cancel"), - "procedure": async () => { - this.action_cancel(); - } - }, - ] - : - [] - ) // add .concat( ((! this.read_only) && (! (this.action_add === null))) @@ -271,6 +270,21 @@ namespace _dali.widgets.event_edit : [] ) + // cancel + .concat( + (! (this.action_cancel === null)) + ? + [ + { + "label": lib_plankton.translate.get("common.cancel"), + "procedure": async () => { + this.action_cancel(); + } + }, + ] + : + [] + ) ) ); await form.setup(target_element); diff --git a/source/widgets/listview/logic.ts b/source/widgets/listview/logic.ts index c79f673..47db5ce 100644 --- a/source/widgets/listview/logic.ts +++ b/source/widgets/listview/logic.ts @@ -4,12 +4,12 @@ namespace _dali.widgets.listview /** */ type type_entry = { - calendar_id : _dali.type.calendar_id; + calendar_id : _dali.type_calendar_id; calendar_name : string; hue : float; - access_level : _dali.type.enum_access_level; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; + access_level : _dali.enum_access_level; + event_id : (null | _dali.type_local_resource_event_id); + event_object : _dali.type_event_object; }; @@ -19,7 +19,7 @@ namespace _dali.widgets.listview ( from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, - calendar_ids : Array<_dali.type.calendar_id> + calendar_ids : Array<_dali.type_calendar_id> ) => Promise> @@ -45,9 +45,9 @@ namespace _dali.widgets.listview */ private action_select_event : ( ( - calendar_id : _dali.type.calendar_id, - access_level : _dali.type.enum_access_level, - event_id : _dali.type.local_resource_event_id + calendar_id : _dali.type_calendar_id, + access_level : _dali.enum_access_level, + event_id : _dali.type_local_resource_event_id ) => void @@ -71,9 +71,9 @@ namespace _dali.widgets.listview options : { action_select_event ?: ( ( - calendar_id : _dali.type.calendar_id, - access_level : _dali.type.enum_access_level, - event_id : _dali.type.local_resource_event_id + calendar_id : _dali.type_calendar_id, + access_level : _dali.enum_access_level, + event_id : _dali.type_local_resource_event_id ) => void @@ -104,14 +104,14 @@ namespace _dali.widgets.listview /** */ public toggle_visibility( - calendar_id : _dali.type.calendar_id + calendar_id : _dali.type_calendar_id ) : void { this.container.querySelectorAll(".listview-entry").forEach( (element) => { const rel : string = element.getAttribute("rel"); const parts : Array = rel.split("/"); - const calendar_id_ : _dali.type.calendar_id = parseInt(parts[0]); + const calendar_id_ : _dali.type_calendar_id = parseInt(parts[0]); if (! (calendar_id === calendar_id_)) { // do nothing } @@ -155,7 +155,7 @@ namespace _dali.widgets.listview "add_href": "", "add_label": lib_plankton.translate.get("widget.listview.add"), "add_extra_classes": ( - (! await _dali.backend.is_logged_in()) + (! await _dali.is_logged_in()) ? " listview-add-hidden" : @@ -250,10 +250,10 @@ namespace _dali.widgets.listview ), "access_level": (() => { switch (entry.access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; + case _dali.enum_access_level.none: return "none"; + case _dali.enum_access_level.view: return "view"; + case _dali.enum_access_level.edit: return "edit"; + case _dali.enum_access_level.admin: return "admin"; } }) (), } @@ -289,20 +289,20 @@ namespace _dali.widgets.listview else { const rel : string = element.getAttribute("rel"); const parts : Array = rel.split("/"); - const calendar_id : _dali.type.calendar_id = parseInt(parts[0]); - const event_id : (null | _dali.type.local_resource_event_id) = ( + const calendar_id : _dali.type_calendar_id = parseInt(parts[0]); + const event_id : (null | _dali.type_local_resource_event_id) = ( parts[1] === "-" ? null : parseInt(parts[1]) ); - const access_level : _dali.type.enum_access_level = (() => { + const access_level : _dali.enum_access_level = (() => { switch (parts[2]) { - case "none": return _dali.type.enum_access_level.none; - case "view": return _dali.type.enum_access_level.view; - case "edit": return _dali.type.enum_access_level.edit; - case "admin": return _dali.type.enum_access_level.admin; + case "none": return _dali.enum_access_level.none; + case "view": return _dali.enum_access_level.view; + case "edit": return _dali.enum_access_level.edit; + case "admin": return _dali.enum_access_level.admin; } }) (); this.action_select_event( diff --git a/source/widgets/login/logic.ts b/source/widgets/login/logic.ts new file mode 100644 index 0000000..367b0c4 --- /dev/null +++ b/source/widgets/login/logic.ts @@ -0,0 +1,195 @@ +namespace _dali.widgets.login +{ + + /** + */ + export class class_widget_login implements lib_plankton.zoo_widget.interface_widget + { + + /** + * [data] + */ + private initial_name : string; + + + /** + * [hook] + */ + private action_cancel : (null | (() => void)); + + + /** + * [hook] + */ + private action_success : (null | (() => void)); + + + /** + */ + public constructor( + { + "initial_name": initial_name = "", + "action_cancel": action_cancel = null, + "action_success": action_success = null, + } + : + { + initial_name ?: string; + action_cancel ?: (null | (() => void)); + action_success ?: (null | (() => void)); + } + = + { + } + ) + { + this.initial_name = initial_name; + this.action_cancel = action_cancel; + this.action_success = action_success; + } + + + /** + * [implementation] + */ + public async load( + target_element : Element + ) + : Promise + { + const preparation : {kind : string; data : any;} = await _dali.backend.session_prepare( + { + "oidc_redirect_uri_template": _dali.conf.get()["misc"]["oidc_redirect_uri_template"], + } + ); + switch (preparation.kind) + { + 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(); + } + } + ] + ) + ) + ); + await form.setup(document.querySelector(".widget-login")); + await form.input_write( + { + "name": this.initial_name, + "password": "", + } + ); + 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"), + { + "title": preparation.data.label, + } + ); + 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); + } + break; + } + default: + { + // todo + break; + } + } + } + + } + +} diff --git a/source/widgets/login/templates/default.html.tpl b/source/widgets/login/templates/default.html.tpl new file mode 100644 index 0000000..9ac6980 --- /dev/null +++ b/source/widgets/login/templates/default.html.tpl @@ -0,0 +1,2 @@ + diff --git a/source/widgets/menu/logic.ts b/source/widgets/menu/logic.ts new file mode 100644 index 0000000..da48fe4 --- /dev/null +++ b/source/widgets/menu/logic.ts @@ -0,0 +1,165 @@ +namespace _dali.widgets.menu +{ + + /** + */ + type type_entry_data = { + label : string; + groups : Array; + action : (() => void); + } + + + /** + */ + export class class_widget_menu implements lib_plankton.zoo_widget.interface_widget + { + + /** + */ + private entries : Array< + { + data : type_entry_data; + element : (null | HTMLElement); + } + >; + + + /** + */ + private initial_groups : Array; + + + /** + */ + private container : (null | HTMLElement); + + + /** + */ + public constructor( + entry_data_list : Array, + { + "initial_groups": initial_groups = [], + } + : + { + initial_groups ?: Array; + } + = + { + } + ) + { + this.entries = entry_data_list.map( + entry_data => ( + { + "data": entry_data, + "element": null, + } + ) + ); + this.initial_groups = initial_groups; + this.container = null; + } + + + /** + */ + public set_groups( + groups : Array + ) + : void + { + this.entries.forEach( + entry => { + const active : boolean = groups.some(group => entry.data.groups.includes(group)); + entry.element.classList.toggle("widget-menu-entry-hidden", (! active)); + } + ); + } + + + /** + */ + private toggle_collapsed( + { + "mode": mode = null, + } + : + { + mode ?: (null | boolean); + } + = + { + } + ) + : void + { + this.container.classList.toggle("widget-menu-collapsed", mode ?? undefined); + } + + + /** + * [implementation] + */ + public async load( + target_element : Element + ) + : Promise + { + // container + { + const dom_container : HTMLElement = document.createElement("div"); + dom_container.classList.add("widget-menu"); + // button + { + const dom_button : HTMLElement = document.createElement("button"); + dom_button.textContent = "[=]"; + dom_button.classList.add("widget-menu-button"); + dom_button.addEventListener( + "click", + () => { + this.toggle_collapsed(); + } + ); + dom_container.classList.toggle("widget-menu-collapsed", true); + dom_container.appendChild(dom_button); + } + // platform + { + const dom_platform : HTMLElement = document.createElement("div"); + dom_platform.classList.add("widget-menu-platform"); + { + const dom_list : HTMLElement = document.createElement("ul"); + dom_list.classList.add("widget-menu-entries"); + this.entries.forEach( + entry => { + const dom_entry : HTMLElement = document.createElement("li"); + dom_entry.classList.add("widget-menu-entry"); + dom_entry.textContent = entry.data.label; + dom_entry.addEventListener( + "click", + () => { + this.toggle_collapsed({"mode": true}); + entry.data.action(); + } + ); + dom_list.appendChild(dom_entry); + entry.element = dom_entry; + } + ); + dom_platform.appendChild(dom_list); + } + dom_container.appendChild(dom_platform); + } + target_element.appendChild(dom_container); + this.container = dom_container; + } + + this.set_groups(this.initial_groups); + } + + } + +} diff --git a/source/widgets/mode_switcher/logic.ts b/source/widgets/mode_switcher/logic.ts index fd7c9b0..ca966d2 100644 --- a/source/widgets/mode_switcher/logic.ts +++ b/source/widgets/mode_switcher/logic.ts @@ -4,7 +4,7 @@ namespace _dali.widgets.mode_switcher /** */ type type_option = { - mode : _dali.type.enum_view_mode; + mode : _dali.enum_view_mode; label : string, }; @@ -21,12 +21,12 @@ namespace _dali.widgets.mode_switcher /** */ - private initial_selection : (null | _dali.type.enum_view_mode); + private initial_selection : (null | _dali.enum_view_mode); /** */ - private action_change : (null | ((mode : _dali.type.enum_view_mode) => void)); + private action_change : (null | ((mode : _dali.enum_view_mode) => void)); /** @@ -39,8 +39,8 @@ namespace _dali.widgets.mode_switcher } : { - initial_selection ?: (null | _dali.type.enum_view_mode), - action_change ?: (null | ((mode : _dali.type.enum_view_mode) => void)) + initial_selection ?: (null | _dali.enum_view_mode), + action_change ?: (null | ((mode : _dali.enum_view_mode) => void)) } = { @@ -99,7 +99,7 @@ namespace _dali.widgets.mode_switcher target_element.querySelectorAll(".widget-mode_switcher-option").forEach( element => { const view_mode_encoded : string = element.getAttribute("rel"); - const mode : _dali.type.enum_view_mode = _dali.view_mode_decode(view_mode_encoded); + const mode : _dali.enum_view_mode = _dali.view_mode_decode(view_mode_encoded); element.querySelector("input").addEventListener( "change", (event) => { diff --git a/source/widgets/sources/logic.ts b/source/widgets/sources/logic.ts index 53a4931..b8cd927 100644 --- a/source/widgets/sources/logic.ts +++ b/source/widgets/sources/logic.ts @@ -4,10 +4,10 @@ namespace _dali.widgets.sources /** */ type type_entry = { - id : _dali.type.calendar_id; + id : _dali.type_calendar_id; name : string; hue : float; - access_level : _dali.type.enum_access_level; + access_level : _dali.enum_access_level; }; @@ -40,6 +40,11 @@ namespace _dali.widgets.sources private action_create : (() => void); + /** + */ + private priviliged : boolean; + + /** * [state] */ @@ -54,12 +59,14 @@ namespace _dali.widgets.sources "action_open": action_open = ((calendar_id) => {}), "action_toggle_visibility": action_toggle_visibility = ((calendar_id) => {}), "action_create": action_create = (() => {}), + "initial_priviliged": initial_priviliged = false, } : { action_open ?: ((entry : type_entry) => void); action_toggle_visibility ?: ((entry : type_entry) => void); action_create ?: (() => void); + initial_priviliged ?: boolean; } = { @@ -75,6 +82,7 @@ namespace _dali.widgets.sources this.action_create = action_create; // state + this.priviliged = initial_priviliged; this.container = null; } @@ -82,7 +90,7 @@ namespace _dali.widgets.sources /** */ private static id_encode( - id : _dali.type.calendar_id + id : _dali.type_calendar_id ) : string { @@ -95,7 +103,7 @@ namespace _dali.widgets.sources private static id_decode( representation : string ) - : _dali.type.calendar_id + : _dali.type_calendar_id { return parseInt(representation); } @@ -104,20 +112,40 @@ namespace _dali.widgets.sources /** */ public async update( + { + "priviliged": priviliged = null, + } + : + { + priviliged ?: boolean; + } + = + { + } ) : Promise { - const data : lib_plankton.map.type_map<_dali.type.calendar_id, type_entry> = lib_plankton.map.hashmap.implementation_map( + if (priviliged === null) + { + // do nothing + } + else + { + this.priviliged = priviliged; + } + const data : lib_plankton.map.type_map<_dali.type_calendar_id, type_entry> = lib_plankton.map.hashmap.implementation_map( lib_plankton.map.hashmap.make( calendar_id => class_widget_sources.id_encode(calendar_id), { "pairs": ( (await this.get_entries()) .map( - entry => ({ - "key": entry.id, - "value": entry, - }) + entry => ( + { + "key": entry.id, + "value": entry, + } + ) ) ), } @@ -192,6 +220,7 @@ namespace _dali.widgets.sources ), } ); + this.container.querySelector(".sources").classList.toggle("sources-priviliged", this.priviliged); } // listeners { @@ -218,7 +247,7 @@ namespace _dali.widgets.sources "click", () => { const key_encoded : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); - const calendar_id : _dali.type.calendar_id = class_widget_sources.id_decode(key_encoded); + const calendar_id : _dali.type_calendar_id = class_widget_sources.id_decode(key_encoded); const entry : type_entry = data.get(calendar_id); element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-hidden"); element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-open", false); @@ -233,7 +262,7 @@ namespace _dali.widgets.sources "click", (event) => { const key_encoded : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); - const calendar_id : _dali.type.calendar_id = class_widget_sources.id_decode(key_encoded); + const calendar_id : _dali.type_calendar_id = class_widget_sources.id_decode(key_encoded); const entry : type_entry = data.get(calendar_id); this.action_open(entry); } @@ -255,7 +284,7 @@ namespace _dali.widgets.sources this.container = target_element; await this.update(); } - + } } diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index 457d9e9..1d20719 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -4,13 +4,13 @@ namespace _dali.widgets.weekview /** */ type type_entry = { - key : _dali.type.event_key; - calendar_id : _dali.type.calendar_id; + key : _dali.type_event_key; + calendar_id : _dali.type_calendar_id; calendar_name : string; hue : float; - access_level : _dali.type.enum_access_level; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; + access_level : _dali.enum_access_level; + event_id : (null | _dali.type_local_resource_event_id); + event_object : _dali.type_event_object; }; @@ -20,7 +20,7 @@ namespace _dali.widgets.weekview ( from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, - calendar_ids : Array<_dali.type.calendar_id> + calendar_ids : Array<_dali.type_calendar_id> ) => Promise> @@ -43,9 +43,7 @@ namespace _dali.widgets.weekview */ private action_select_event : ( ( - calendar_id : _dali.type.calendar_id, - access_level : _dali.type.enum_access_level, - event_id : _dali.type.local_resource_event_id + event_key : _dali.type_event_key ) => void @@ -86,7 +84,7 @@ namespace _dali.widgets.weekview * [state] */ private event_map : lib_plankton.map.type_map< - _dali.type.event_key, + _dali.type_event_key, { element : HTMLElement; hash : string; @@ -106,7 +104,7 @@ namespace _dali.widgets.weekview get_entries : type_get_entries, { "action_select_day": action_select_day = ((date) => {}), - "action_select_event": action_select_event = ((calendar_id, access_level, event_id) => {}), + "action_select_event": action_select_event = ((event_key) => {}), "initial_year": initial_year = null, "initial_week": initial_week = null, "initial_count": initial_count = 5, @@ -115,9 +113,7 @@ namespace _dali.widgets.weekview { action_select_event ?: ( ( - calendar_id : _dali.type.calendar_id, - access_level : _dali.type.enum_access_level, - event_id : _dali.type.local_resource_event_id + event_key : _dali.type_event_key ) => void @@ -159,7 +155,7 @@ namespace _dali.widgets.weekview this.count = initial_count; this.event_map = lib_plankton.map.hashmap.implementation_map( lib_plankton.map.hashmap.make< - _dali.type.event_key, + _dali.type_event_key, { element : HTMLElement; hash : string; @@ -200,7 +196,7 @@ namespace _dali.widgets.weekview */ private static event_generate_tooltip( calendar_name : string, - event_object : _dali.type.event_object + event_object : _dali.type_event_object ) : string { return ( @@ -293,14 +289,14 @@ namespace _dali.widgets.weekview /** */ - private get_entries_wrapped( + private async get_entries_wrapped( { "calendar_ids": calendar_ids = null, "timezone_shift": timezone_shift = 0, } : { - calendar_ids ?: (null | Array<_dali.type.calendar_id>); + calendar_ids ?: (null | Array<_dali.type_calendar_id>); timezone_shift ?: int; } = @@ -309,7 +305,7 @@ namespace _dali.widgets.weekview ) : Promise> { - return this.get_entries( + const entries = await this.get_entries( lib_plankton.pit.from_ywd( { "year": this.year, @@ -332,6 +328,14 @@ namespace _dali.widgets.weekview ), calendar_ids ); + entries.sort( + (entry1, entry2) => { + const b1 : string = lib_plankton.pit.datetime_format(entry1.event_object.begin); + const b2 : string = lib_plankton.pit.datetime_format(entry2.event_object.begin); + return ((b1 <= b2) ? -1 : +1); + } + ); + return entries; } @@ -350,7 +354,12 @@ namespace _dali.widgets.weekview const dom_cell = this.container.querySelector(selector); if (dom_cell === null) { - console.warn("entry out of scope"); + lib_plankton.log.debug( + "dali.widget.weekview.entry_insert.out_of_scope", + { + "entry": entry, + } + ); return null; } else @@ -380,20 +389,7 @@ namespace _dali.widgets.weekview entry.event_object ), "name": entry.event_object.name, - "rel": lib_plankton.string.coin( - "{{calendar_id}}/{{event_id}}/{{access_level}}", - { - "calendar_id": entry.calendar_id.toFixed(0), - "event_id": ( - (entry.event_id === null) - ? - "-" - : - entry.event_id.toFixed(0) - ), - "access_level": _dali.access_level_encode(entry.access_level), - } - ), + "rel": entry.key, "additional_classes": lib_plankton.string.coin( " access_level-{{access_level}}", { @@ -409,20 +405,9 @@ namespace _dali.widgets.weekview "click", (event) => { const rel : string = dom_entry.getAttribute("rel"); - const parts : Array = rel.split("/"); - const calendar_id : _dali.type.calendar_id = parseInt(parts[0]); - const event_id : (null | _dali.type.local_resource_event_id) = ( - (parts[1] === "-") - ? - null - : - parseInt(parts[1]) - ); - const access_level : _dali.type.enum_access_level = _dali.access_level_decode(parts[2]); + const event_key : _dali.type_event_key = rel; this.action_select_event( - calendar_id, - access_level, - event_id + event_key ); } ); @@ -462,13 +447,18 @@ namespace _dali.widgets.weekview /** */ private async entry_update( - key : _dali.type.event_key, + key : _dali.type_event_key, entry : type_entry ) : Promise { if (! this.event_map.has(key)) { - console.warn("event missing: " + key); + lib_plankton.log.warning( + "dali.widget.weekview.entry_update.event_missing", + { + "key": key, + } + ); } else { @@ -478,7 +468,14 @@ namespace _dali.widgets.weekview if (hash_old === hash_new) { // do nothing - // console.info("nothing to update", {"key": key, "entry": entry, "element": value.element}); + lib_plankton.log.debug( + "dali.widget.weekview.entry_update.nothing_to_update", + { + "key": key, + "entry": entry, + "element": value.element, + } + ); } else { @@ -507,13 +504,19 @@ namespace _dali.widgets.weekview /** */ private async entry_remove( - key : _dali.type.event_key + key : _dali.type_event_key ) : Promise { if (! this.event_map.has(key)) { // do nothing - // console.warn("not in map", {"key": key, "map": lib_plankton.map.dump(this.event_map)}); + lib_plankton.log.warning( + "dali.widget.weekview.entry_remove.not_in_map", + { + "key": key, + "pairs": lib_plankton.map.dump(this.event_map), + } + ); } else { @@ -755,7 +758,7 @@ namespace _dali.widgets.weekview /** */ public toggle_visibility( - calendar_id : _dali.type.calendar_id, + calendar_id : _dali.type_calendar_id, { "mode": mode = null, } @@ -772,7 +775,7 @@ namespace _dali.widgets.weekview (element) => { const rel : string = element.getAttribute("rel"); const parts : Array = rel.split("/"); - const calendar_id_ : _dali.type.calendar_id = parseInt(parts[0]); + const calendar_id_ : _dali.type_calendar_id = parseInt(parts[0]); if (! (calendar_id === calendar_id_)) { // do nothing diff --git a/tools/makefile b/tools/makefile index 2ca4fa3..45634f4 100644 --- a/tools/makefile +++ b/tools/makefile @@ -32,6 +32,7 @@ ${dir_build}/index.html: \ .PHONY: templates templates: \ + templates-widgets-login \ templates-widgets-sources \ templates-widgets-listview \ templates-widgets-weekview \ @@ -39,8 +40,14 @@ templates: \ templates-widgets-calendar_edit \ templates-widgets-event_edit \ templates-pages-caldav \ - templates-pages-overview \ - templates-pages-login + templates-pages-overview + +.PHONY: templates-widgets-login +templates-widgets-login: \ + $(wildcard ${dir_source}/widgets/login/templates/*) + @ ${cmd_log} "templates:widgets: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-sources templates-widgets-sources: \ @@ -98,13 +105,6 @@ templates-pages-overview: \ @ ${cmd_mkdir} ${dir_build}/templates/overview @ ${cmd_cp} -r -u -v ${dir_source}/pages/overview/templates/* ${dir_build}/templates/overview/ -.PHONY: templates-pages-login -templates-pages-login: \ - $(wildcard ${dir_source}/pages/login/templates/*) - @ ${cmd_log} "templates:login …" - @ ${cmd_mkdir} ${dir_build}/templates/login - @ ${cmd_cp} -r -u -v ${dir_source}/pages/login/templates/* ${dir_build}/templates/login/ - .PHONY: style style: \ $(wildcard ${dir_source}/style/*) @@ -117,12 +117,14 @@ logic: ${dir_build}/logic.js ${dir_temp}/logic-unlinked.js: \ ${dir_lib}/plankton/plankton.d.ts \ - ${dir_source}/base/helpers.ts \ - ${dir_source}/base/types.ts \ - ${dir_source}/base/functions.ts \ - ${dir_source}/base/model.ts \ ${dir_source}/resources/conf.ts \ ${dir_source}/resources/backend.ts \ + ${dir_source}/base.ts \ + ${dir_source}/types.ts \ + ${dir_source}/model.ts \ + ${dir_source}/helpers.ts \ + ${dir_source}/widgets/login/logic.ts \ + ${dir_source}/widgets/menu/logic.ts \ ${dir_source}/widgets/sources/logic.ts \ ${dir_source}/widgets/listview/logic.ts \ ${dir_source}/widgets/weekview/logic.ts \ @@ -130,8 +132,6 @@ ${dir_temp}/logic-unlinked.js: \ ${dir_source}/widgets/calendar_edit/logic.ts \ ${dir_source}/widgets/event_edit/logic.ts \ ${dir_source}/overlay.ts \ - ${dir_source}/pages/login/logic.ts \ - ${dir_source}/pages/logout/logic.ts \ ${dir_source}/pages/caldav/logic.ts \ ${dir_source}/pages/oidc_finish/logic.ts \ ${dir_source}/pages/overview/logic.ts \ diff --git a/tools/update-plankton b/tools/update-plankton index b652c8d..665794d 100755 --- a/tools/update-plankton +++ b/tools/update-plankton @@ -14,6 +14,7 @@ modules="${modules} json" modules="${modules} string" modules="${modules} random" modules="${modules} map" +modules="${modules} set" modules="${modules} cache" modules="${modules} color" # modules="${modules} xml" From b1c8bf5806d95dd54adaf16e200a64f607f7121c Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Fri, 17 Oct 2025 00:43:10 +0200 Subject: [PATCH 11/11] =?UTF-8?q?[task-408]=20[mod]=20Nutzername=20im=20Me?= =?UTF-8?q?n=C3=BC-Knopf=20anzeigen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/base.ts | 21 ++++++++++++++++++--- source/main.ts | 11 +++++++---- source/pages/oidc_finish/logic.ts | 3 +-- source/resources/backend.ts | 1 + source/style/widget-menu.css | 3 ++- source/widgets/menu/logic.ts | 27 ++++++++++++++++++++++++++- 6 files changed, 55 insertions(+), 11 deletions(-) diff --git a/source/base.ts b/source/base.ts index 5f3aa65..3dd2bb3 100644 --- a/source/base.ts +++ b/source/base.ts @@ -3,7 +3,7 @@ namespace _dali /** */ - let _actions_login : Array<(() => Promise)> = []; + let _actions_login : Array<((name ?: (null | string)) => Promise)> = []; /** @@ -19,7 +19,7 @@ namespace _dali /** */ export function listen_login( - action : (() => Promise) + action : ((name ?: (null | string)) => Promise) ) : void { @@ -41,13 +41,14 @@ namespace _dali /** */ export async function notify_login( + name : (null | string) ) : Promise { _is_logged_in = true; for (const action of _actions_login) { - await action(); + await action(name); } } @@ -76,6 +77,20 @@ namespace _dali } + /** + */ + export async function oidc_finish( + session_key : string + ) + : Promise + { + await _dali.backend.set_session_key(session_key); + const status = await _dali.backend.status(); + await _dali.notify_login(status.name); + return Promise.resolve(undefined); + } + + /** */ export async function logout( diff --git a/source/main.ts b/source/main.ts index c316c10..73e60f9 100644 --- a/source/main.ts +++ b/source/main.ts @@ -98,8 +98,9 @@ namespace _dali _dali.overlay.clear(); _dali.overlay.toggle({"mode": false}); }, - "action_success": () => { - _dali.notify_login(); + "action_success": async () => { + const status = await _dali.backend.status(); + _dali.notify_login(status.name); _dali.overlay.clear(); _dali.overlay.toggle({"mode": false}); }, @@ -145,13 +146,15 @@ namespace _dali ); await widget_menu.load(document.querySelector("header")); _dali.listen_login( - async () => { + async (name) => { widget_menu.set_groups(["logged_in"]); + widget_menu.set_label(name); } ); _dali.listen_logout( async () => { widget_menu.set_groups(["logged_out"]); + widget_menu.set_label(null); } ); } @@ -175,7 +178,7 @@ namespace _dali ); if (status.logged_in) { - _dali.notify_login(); + _dali.notify_login(status.name); } else { diff --git a/source/pages/oidc_finish/logic.ts b/source/pages/oidc_finish/logic.ts index d6a3838..1c3f42a 100644 --- a/source/pages/oidc_finish/logic.ts +++ b/source/pages/oidc_finish/logic.ts @@ -7,8 +7,7 @@ namespace _dali.pages "oidc_finish", async (parameters, target_element) => { target_element.innerHTML = ""; - await _dali.backend.set_session_key(parameters["session_key"]); - await _dali.notify_login(); + await _dali.oidc_finish(parameters["session_key"]); lib_plankton.zoo_page.set( { "name": "overview", diff --git a/source/resources/backend.ts b/source/resources/backend.ts index bb5cef7..3fc73d1 100644 --- a/source/resources/backend.ts +++ b/source/resources/backend.ts @@ -334,6 +334,7 @@ namespace _dali.backend : Promise< { logged_in : boolean; + name : (null | string); } > { diff --git a/source/style/widget-menu.css b/source/style/widget-menu.css index 501f1d4..d8b4b40 100644 --- a/source/style/widget-menu.css +++ b/source/style/widget-menu.css @@ -2,7 +2,7 @@ { margin-left: auto; margin-right: 8px; - max-width: 40px; + width: fit-content; } .widget-menu.widget-menu-collapsed > .widget-menu-platform @@ -12,6 +12,7 @@ .widget-menu-button { + text-transform: initial; } .widget-menu-entry diff --git a/source/widgets/menu/logic.ts b/source/widgets/menu/logic.ts index da48fe4..68c486a 100644 --- a/source/widgets/menu/logic.ts +++ b/source/widgets/menu/logic.ts @@ -30,6 +30,11 @@ namespace _dali.widgets.menu private initial_groups : Array; + /** + */ + private label : (null | string); + + /** */ private container : (null | HTMLElement); @@ -41,10 +46,12 @@ namespace _dali.widgets.menu entry_data_list : Array, { "initial_groups": initial_groups = [], + "initial_label": initial_label = "=", } : { initial_groups ?: Array; + initial_label ?: string; } = { @@ -60,6 +67,7 @@ namespace _dali.widgets.menu ) ); this.initial_groups = initial_groups; + this.label = initial_label; this.container = null; } @@ -79,6 +87,23 @@ namespace _dali.widgets.menu ); } + /** + */ + public set_label( + label ?: string + ) + : void + { + this.label = label; + this.container.querySelector(".widget-menu-button").textContent = ( + (this.label === null) + ? + "[=]" + : + ("[" + this.label + "]") + ); + } + /** */ @@ -115,7 +140,7 @@ namespace _dali.widgets.menu // button { const dom_button : HTMLElement = document.createElement("button"); - dom_button.textContent = "[=]"; + dom_button.textContent = "[" + this.label + "]"; dom_button.classList.add("widget-menu-button"); dom_button.addEventListener( "click",