From 1a36af56aeecf0da3226da5d30e71cf38ba480f3 Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Fri, 17 Oct 2025 00:10:28 +0200 Subject: [PATCH] [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"