diff --git a/lib/plankton/plankton.d.ts b/lib/plankton/plankton.d.ts index 91457ef..09b58dd 100644 --- a/lib/plankton/plankton.d.ts +++ b/lib/plankton/plankton.d.ts @@ -1,11 +1,11 @@ /** * @author fenris */ -declare type int = number; +type int = number; /** * @author fenris */ -declare type float = number; +type float = number; declare class Buffer { constructor(x: string, modifier?: string); toString(modifier?: string): string; @@ -19,7 +19,7 @@ declare namespace lib_plankton.base { /** * @author fenris */ -declare type type_pseudopointer = { +type type_pseudopointer = { value: type_value; }; /** @@ -1969,7 +1969,7 @@ declare namespace lib_plankton.storage.memory { clear(): Promise; write(key: any, value: any): Promise; delete(key: any): Promise; - read(key: any): Promise; + read(key: any): Promise>; search(term: any): Promise<{ key: string; preview: string; @@ -2693,6 +2693,173 @@ 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 { + /** + */ + type type_result = { + retrieved: boolean; + value: type_value; + }; + /** + */ + type type_entry = { + value: type_value; + expiry: (null | float); + }; + /** + */ + type type_subject = lib_plankton.storage.type_chest, void, any, any>; +} +declare namespace lib_plankton.cache { + /** + */ + function make({ "chest": chest, }?: { + chest?: lib_plankton.storage.type_chest, void, any, any>; + }): type_subject; + /** + */ + function init(subject: type_subject): Promise; + /** + */ + function clear(subject: type_subject): Promise; + /** + */ + function invalidate(subject: type_subject, key: string): Promise; + /** + */ + function query(subject: type_subject, key: string, lifetime: (null | float), retrieve: (() => Promise)): Promise>; + /** + * syntactic sugar: if the information, whether the value was retrieved, is irrelevant + */ + function get(subject: type_subject, key: string, lifetime: (null | float), retrieve: (() => Promise)): Promise; + /** + */ + function get_complex(cache: type_subject, group: string, input: type_input, lifetime: (null | float), retrieve: ((input: type_input) => Promise), { "encode_input": encode_input, }?: { + encode_input?: ((input: type_input) => string); + }): Promise; +} declare namespace lib_plankton.complex { /** * @author fenris @@ -3809,55 +3976,6 @@ declare namespace lib_plankton.translate { */ function stance(str: string): string; } -declare namespace lib_plankton.zoo_page { - /** - */ - export type type_location = { - name: string; - parameters: Record; - }; - /** - */ - type type_handler = ((parameters: Record, target_element: Element) => void); - /** - */ - type type_nav_entry_definition = { - location: type_location; - label: string; - groups: Array; - }; - /** - */ - export let _pool: Record; - /** - */ - export function encode(location: type_location): string; - /** - * encodes a location in the URL and loads it - */ - export function set(location: type_location): void; - /** - */ - export function reload(): Promise; - /** - */ - export function register(location_name: string, handler: type_handler): void; - /** - */ - export function nav_set_groups(groups: (null | Array)): void; - /** - */ - export function init(target_element: Element, { "pool": pool, "fallback": fallback, "nav_entries": nav_entries, "nav_initial_groups": nav_initial_groups, }?: { - pool?: Record; - fallback?: (null | type_location); - nav_entries?: Array; - nav_initial_groups?: (null | Array); - }): void; - /** - */ - export function start(): void; - export {}; -} declare namespace lib_plankton.zoo_widget { /** */ @@ -4043,6 +4161,55 @@ declare namespace lib_plankton.zoo_widget { load(target_element: HTMLElement): Promise; } } +declare namespace lib_plankton.zoo_page { + /** + */ + export type type_location = { + name: string; + parameters: Record; + }; + /** + */ + type type_handler = ((parameters: Record, target_element: Element) => void); + /** + */ + type type_nav_entry_definition = { + location: type_location; + label: string; + groups: Array; + }; + /** + */ + export let _pool: Record; + /** + */ + export function encode(location: type_location): string; + /** + * encodes a location in the URL and loads it + */ + export function set(location: type_location): void; + /** + */ + export function reload(): Promise; + /** + */ + export function register(location_name: string, handler: type_handler): void; + /** + */ + export function nav_set_groups(groups: (null | Array)): void; + /** + */ + export function init(target_element: Element, { "pool": pool, "fallback": fallback, "nav_entries": nav_entries, "nav_initial_groups": nav_initial_groups, }?: { + pool?: Record; + fallback?: (null | type_location); + nav_entries?: Array; + nav_initial_groups?: (null | Array); + }): void; + /** + */ + export function start(): void; + export {}; +} declare namespace lib_plankton.zoo_input { /** * @author fenris diff --git a/lib/plankton/plankton.js b/lib/plankton/plankton.js index 6ff86ef..4cd8cd9 100644 --- a/lib/plankton/plankton.js +++ b/lib/plankton/plankton.js @@ -2151,7 +2151,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) { function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); - while (_) try { + while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { @@ -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,498 @@ 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' + + +»bacterio-plankton:cache« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:cache« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:cache«. If not, see . + */ +/* +This file is part of »bacterio-plankton:cache«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:cache« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:cache« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:cache«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var cache; + (function (cache_1) { + /** + */ + function make({ "chest": chest = lib_plankton.storage.memory.implementation_chest({}), } = {}) { + return chest; + } + cache_1.make = make; + /** + */ + function init(subject) { + return subject.setup(undefined); + } + cache_1.init = init; + /** + */ + function clear(subject) { + return subject.clear(); + } + cache_1.clear = clear; + /** + */ + function invalidate(subject, key) { + return subject.delete(key); + } + cache_1.invalidate = invalidate; + /** + */ + async function query(subject, key, lifetime, retrieve) { + const now = lib_plankton.base.get_current_timestamp(); + return ((subject.read(key) + .then((entry) => Promise.resolve({ + "found": true, + "entry": entry, + })) + .catch(() => Promise.resolve({ + "found": false, + "entry": null, + }))) + .then((item) => { + if ((!item.found) + || + ((item.entry.expiry !== null) + && + (item.entry.expiry <= now))) { + lib_plankton.log.info("plankton.cache.unknown_or_expired", { + "key": key, + }); + return (retrieve() + .then((value) => (subject.write(key, { + "value": value, + "expiry": ((lifetime === null) + ? + null + : + (now + lifetime)) + }) + .then(() => Promise.resolve({ + "retrieved": true, + "value": value + }))))); + } + else { + lib_plankton.log.info("plankton.cache.known_and_valid", { + "key": key, + }); + return Promise.resolve({ + "retrieved": false, + "value": item.entry.value + }); + } + })); + } + cache_1.query = query; + /** + * syntactic sugar: if the information, whether the value was retrieved, is irrelevant + */ + function get(subject, key, lifetime, retrieve) { + return (query(subject, key, lifetime, retrieve) + .then(result => Promise.resolve(result.value))); + } + cache_1.get = get; + /** + */ + function get_complex(cache, group, input, lifetime, retrieve, { "encode_input": encode_input = (input => JSON.stringify(input)), } = {}) { + return get(cache, (group + "." + encode_input(input)), lifetime, () => retrieve(input)); + } + cache_1.get_complex = get_complex; + })(cache = lib_plankton.cache || (lib_plankton.cache = {})); +})(lib_plankton || (lib_plankton = {})); +/* This file is part of »bacterio-plankton:complex«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' @@ -9449,6 +9941,12 @@ var lib_plankton; * @author fenris */ class class_relation { + /** + * @author fenris + */ + check(value, reference) { + return this.predicate(value, reference); + } /** * @author fenris */ @@ -9458,12 +9956,6 @@ var lib_plankton; this.name = name; this.predicate = predicate; } - /** - * @author fenris - */ - check(value, reference) { - return this.predicate(value, reference); - } /** * @author fenris */ @@ -9940,7 +10432,7 @@ var lib_plankton; h = 0; } else if (q === r) { - h = ((0 / 3) + ((g - b) / (c * 6))); + h = ((0 / 3) + lib_plankton.math.mod((g - b) / (c * 6), 1)); } else if (q === g) { h = ((1 / 3) + ((b - r) / (c * 6))); @@ -11738,190 +12230,6 @@ var lib_plankton; })(translate = lib_plankton.translate || (lib_plankton.translate = {})); })(lib_plankton || (lib_plankton = {})); /* -This file is part of »bacterio-plankton:zoo-page«. - -Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' - - -»bacterio-plankton:zoo-page« is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -»bacterio-plankton:zoo-page« is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with »bacterio-plankton:zoo-page«. If not, see . - */ -var lib_plankton; -(function (lib_plankton) { - var zoo_page; - (function (zoo_page) { - /** - */ - zoo_page._pool = {}; - /** - */ - let _fallback = null; - /** - */ - let _current = null; - /** - */ - let _target_element = null; - /** - */ - let _nav_entries; - /** - */ - function encode(location) { - return ("#" - + - ([location.name] - .concat(Object.entries(location.parameters) - .filter(([key, value]) => (value !== null)) - .map(([key, value]) => (key + "=" + value))))); - } - zoo_page.encode = encode; - /** - */ - function decode(encoded) { - if (encoded === "") { - return null; - } - else { - if (!encoded.startsWith("#")) { - return null; - } - else { - const parts = encoded.slice(1).split(","); - return { - "name": parts[0], - "parameters": Object.fromEntries(parts.slice(1) - .map(part => { - const parts_ = part.split("="); - return [parts_[0], parts_[1]]; - })), - }; - } - } - } - /** - * renders a page to the main element - */ - async function load(location) { - // _target_element.innerHTML = "[loading …]"; - _target_element.innerHTML = ""; - if (location === null) { - // do nothing - } - else { - if (!(location.name in zoo_page._pool)) { - _target_element.innerHTML = "not found"; - } - else { - await zoo_page._pool[location.name](location.parameters, _target_element); - _current = location; - } - } - } - /** - * retrieves the location from the set URL - */ - function get() { - return decode(window.location.hash); - } - /** - * encodes a location in the URL and loads it - */ - function set(location) { - window.location.hash = encode(location); - } - zoo_page.set = set; - /** - */ - function reload() { - return load(get()); - } - zoo_page.reload = reload; - /** - */ - function register(location_name, handler) { - zoo_page._pool[location_name] = handler; - } - zoo_page.register = register; - /** - */ - function nav_set_groups(groups) { - _nav_entries.forEach(nav_entry => { - const active = ((groups === null) - || - groups.some(group => nav_entry.definition.groups.includes(group))); - nav_entry.element.classList.toggle("active", active); - }); - } - zoo_page.nav_set_groups = nav_set_groups; - /** - */ - function init(target_element, { "pool": pool = {}, "fallback": fallback = null, "nav_entries": nav_entries = [], "nav_initial_groups": nav_initial_groups = null, } = {}) { - _target_element = target_element; - _fallback = fallback; - Object.entries(pool).forEach(([location_name, handler]) => { - register(location_name, handler); - }); - window.addEventListener("hashchange", () => { - const location_old = _current; - const location_new = (get() ?? _fallback); - if (((location_old === null) - && - (location_new !== null)) - || - ((location_old !== null) - && - (location_new !== null) - && - (location_old.name !== location_new.name))) { - load(location_new); - } - else { - // do nothing - } - }); - // nav - { - let ul_element = document.querySelector("nav > ul"); - _nav_entries = nav_entries.map(nav_entry_definition => { - let li_element = document.createElement("li"); - { - let a_element = document.createElement("a"); - a_element.setAttribute("href", encode(nav_entry_definition.location)); - a_element.textContent = (nav_entry_definition.label ?? nav_entry_definition.location.name); - li_element.appendChild(a_element); - } - ul_element.appendChild(li_element); - return { - "definition": nav_entry_definition, - "element": li_element, - }; - }); - nav_set_groups(nav_initial_groups); - } - } - zoo_page.init = init; - /** - */ - function start() { - const location = (get() ?? _fallback); - set(location); - load(location); - } - zoo_page.start = start; - })(zoo_page = lib_plankton.zoo_page || (lib_plankton.zoo_page = {})); -})(lib_plankton || (lib_plankton = {})); -/* This file is part of »bacterio-plankton:zoo-widget«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' @@ -12441,6 +12749,190 @@ var lib_plankton; })(zoo_widget = lib_plankton.zoo_widget || (lib_plankton.zoo_widget = {})); })(lib_plankton || (lib_plankton = {})); /* +This file is part of »bacterio-plankton:zoo-page«. + +Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' + + +»bacterio-plankton:zoo-page« is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +»bacterio-plankton:zoo-page« is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with »bacterio-plankton:zoo-page«. If not, see . + */ +var lib_plankton; +(function (lib_plankton) { + var zoo_page; + (function (zoo_page) { + /** + */ + zoo_page._pool = {}; + /** + */ + let _fallback = null; + /** + */ + let _current = null; + /** + */ + let _target_element = null; + /** + */ + let _nav_entries; + /** + */ + function encode(location) { + return ("#" + + + ([location.name] + .concat(Object.entries(location.parameters) + .filter(([key, value]) => (value !== null)) + .map(([key, value]) => (key + "=" + value))))); + } + zoo_page.encode = encode; + /** + */ + function decode(encoded) { + if (encoded === "") { + return null; + } + else { + if (!encoded.startsWith("#")) { + return null; + } + else { + const parts = encoded.slice(1).split(","); + return { + "name": parts[0], + "parameters": Object.fromEntries(parts.slice(1) + .map(part => { + const parts_ = part.split("="); + return [parts_[0], parts_[1]]; + })), + }; + } + } + } + /** + * renders a page to the main element + */ + async function load(location) { + // _target_element.innerHTML = "[loading …]"; + _target_element.innerHTML = ""; + if (location === null) { + // do nothing + } + else { + if (!(location.name in zoo_page._pool)) { + _target_element.innerHTML = "not found"; + } + else { + await zoo_page._pool[location.name](location.parameters, _target_element); + _current = location; + } + } + } + /** + * retrieves the location from the set URL + */ + function get() { + return decode(window.location.hash); + } + /** + * encodes a location in the URL and loads it + */ + function set(location) { + window.location.hash = encode(location); + } + zoo_page.set = set; + /** + */ + function reload() { + return load(get()); + } + zoo_page.reload = reload; + /** + */ + function register(location_name, handler) { + zoo_page._pool[location_name] = handler; + } + zoo_page.register = register; + /** + */ + function nav_set_groups(groups) { + _nav_entries.forEach(nav_entry => { + const active = ((groups === null) + || + groups.some(group => nav_entry.definition.groups.includes(group))); + nav_entry.element.classList.toggle("active", active); + }); + } + zoo_page.nav_set_groups = nav_set_groups; + /** + */ + function init(target_element, { "pool": pool = {}, "fallback": fallback = null, "nav_entries": nav_entries = [], "nav_initial_groups": nav_initial_groups = null, } = {}) { + _target_element = target_element; + _fallback = fallback; + Object.entries(pool).forEach(([location_name, handler]) => { + register(location_name, handler); + }); + window.addEventListener("hashchange", () => { + const location_old = _current; + const location_new = (get() ?? _fallback); + if (((location_old === null) + && + (location_new !== null)) + || + ((location_old !== null) + && + (location_new !== null) + && + (location_old.name !== location_new.name))) { + load(location_new); + } + else { + // do nothing + } + }); + // nav + { + let ul_element = document.querySelector("nav > ul"); + _nav_entries = nav_entries.map(nav_entry_definition => { + let li_element = document.createElement("li"); + { + let a_element = document.createElement("a"); + a_element.setAttribute("href", encode(nav_entry_definition.location)); + a_element.textContent = (nav_entry_definition.label ?? nav_entry_definition.location.name); + li_element.appendChild(a_element); + } + ul_element.appendChild(li_element); + return { + "definition": nav_entry_definition, + "element": li_element, + }; + }); + nav_set_groups(nav_initial_groups); + } + } + zoo_page.init = init; + /** + */ + function start() { + const location = (get() ?? _fallback); + set(location); + load(location); + } + zoo_page.start = start; + })(zoo_page = lib_plankton.zoo_page || (lib_plankton.zoo_page = {})); +})(lib_plankton || (lib_plankton = {})); +/* This file is part of »bacterio-plankton:zoo-input«. Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR' diff --git a/misc/conf-example.json b/misc/conf-example.json index 7f8190c..cf5b37e 100644 --- a/misc/conf-example.json +++ b/misc/conf-example.json @@ -8,6 +8,6 @@ }, "misc": { "oidc_redirect_uri_template": "http://localhost:8888/#oidc_finish,session_key={{session_key}}", - "use_central_europe_specific_datetime_inputs": false + "use_central_europe_specific_datetime_inputs": true } } diff --git a/source/base.ts b/source/base.ts new file mode 100644 index 0000000..3dd2bb3 --- /dev/null +++ b/source/base.ts @@ -0,0 +1,117 @@ +namespace _dali +{ + + /** + */ + let _actions_login : Array<((name ?: (null | string)) => Promise)> = []; + + + /** + */ + let _actions_logout : Array<(() => Promise)> = []; + + + /** + */ + let _is_logged_in : boolean = false; + + + /** + */ + export function listen_login( + action : ((name ?: (null | string)) => Promise) + ) + : void + { + _actions_login.push(action); + } + + + /** + */ + export function listen_logout( + action : (() => Promise) + ) + : void + { + _actions_logout.push(action); + } + + + /** + */ + export async function notify_login( + name : (null | string) + ) + : Promise + { + _is_logged_in = true; + for (const action of _actions_login) + { + await action(name); + } + } + + + /** + */ + 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 oidc_finish( + session_key : string + ) + : Promise + { + await _dali.backend.set_session_key(session_key); + const status = await _dali.backend.status(); + await _dali.notify_login(status.name); + return Promise.resolve(undefined); + } + + + /** + */ + export async function logout( + ) + : 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 274127d..0000000 --- a/source/base/functions.ts +++ /dev/null @@ -1,63 +0,0 @@ -namespace _dali -{ - - - /** - */ - 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/types.ts b/source/base/types.ts deleted file mode 100644 index da79c50..0000000 --- a/source/base/types.ts +++ /dev/null @@ -1,124 +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 - ); - }; - - - /** - */ - 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 local_resource_event_id = int; - - - /** - */ - 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/base/widget.ts b/source/base/widget.ts deleted file mode 100644 index f971ca8..0000000 --- a/source/base/widget.ts +++ /dev/null @@ -1,18 +0,0 @@ -namespace _dali -{ - - /** - * @todo outsource - */ - export abstract class class_widget - { - - /** - */ - public abstract load( - target_element : Element - ) : Promise; - - } - -} diff --git a/source/data/localization/deu.loc.json b/source/data/localization/deu.loc.json index 1ddbdb1..a19e24e 100644 --- a/source/data/localization/deu.loc.json +++ b/source/data/localization/deu.loc.json @@ -18,6 +18,9 @@ "common.edit": "bearbeiten", "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", @@ -49,12 +52,17 @@ "widget.weekview.controls.week": "Woche", "widget.weekview.controls.count": "Anzahl", "widget.weekview.controls.apply": "Laden", - "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.calendar_edit.actions.add": "Kalender anlegen", + "widget.calendar_edit.actions.change": "ändern", + "widget.calendar_edit.actions.remove": "löschen", + "widget.event_edit.actions.add": "anlegen", + "widget.event_edit.actions.change": "ändern", + "widget.event_edit.actions.remove": "löschen", + "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", @@ -70,14 +78,9 @@ "page.calendar_add.actions.do": "anlegen", "page.calendar_edit.title.regular": "Kalendar bearbeiten", "page.calendar_edit.title.read_only": "Kalendar-Details", - "page.calendar_edit.actions.change": "ändern", - "page.calendar_edit.actions.remove": "löschen", "page.event_add.title": "Termin anlegen", - "page.event_add.actions.do": "anlegen", "page.event_edit.title.regular": "Termin bearbeiten", "page.event_edit.title.read_only": "Termin-Details", - "page.event_edit.actions.change": "ändern", - "page.event_edit.actions.remove": "löschen", "page.overview.title": "Übersicht", "page.overview.login_hint": "anmelden um nicht-öffentliche Termine zu sehen", "page.overview.mode.week": "Wochen-Ansicht", diff --git a/source/data/localization/eng.loc.json b/source/data/localization/eng.loc.json index 25ad7bf..2e14e24 100644 --- a/source/data/localization/eng.loc.json +++ b/source/data/localization/eng.loc.json @@ -18,6 +18,9 @@ "common.edit": "edit", "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", @@ -36,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", @@ -49,12 +52,17 @@ "widget.weekview.controls.week": "Week", "widget.weekview.controls.count": "Count", "widget.weekview.controls.apply": "Load", - "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.calendar_edit.actions.add": "add calendar", + "widget.calendar_edit.actions.change": "change", + "widget.calendar_edit.actions.remove": "delete", + "widget.event_edit.actions.add": "add", + "widget.event_edit.actions.change": "change", + "widget.event_edit.actions.remove": "delete", + "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", @@ -69,15 +77,10 @@ "page.calendar_add.title": "Add calendar", "page.calendar_add.actions.do": "anlegen", "page.event_add.title": "Add event", - "page.event_add.actions.do": "add", "page.calendar_edit.title.regular": "Edit calendar", "page.calendar_edit.title.read_only": "Calendar details", - "page.calendar_edit.actions.change": "change", - "page.calendar_edit.actions.remove": "delete", "page.event_edit.title.regular": "Edit event", "page.event_edit.title.read_only": "Event details", - "page.event_edit.actions.change": "change", - "page.event_edit.actions.remove": "delete", "page.overview.title": "Overview", "page.overview.login_hint": "log in to view non-public events", "page.overview.mode.week": "week view", diff --git a/source/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 e8eb5f4..c2374e0 100644 --- a/source/index.html.tpl +++ b/source/index.html.tpl @@ -24,12 +24,12 @@ document.addEventListener( {{templates}} +
+
+
+
-
diff --git a/source/main.ts b/source/main.ts index 1713e10..73e60f9 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,52 +77,115 @@ 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": "calendar_add", "parameters": {}}, - "label": lib_plankton.translate.get("page.calendar_add.title"), - "groups": ["logged_in"], - }, - { - "location": {"name": "caldav", "parameters": {}}, - "label": lib_plankton.translate.get("page.caldav.title"), - "groups": ["logged_in"], - }, - /* - { - "location": {"name": "event_add", "parameters": {}}, - "label": lib_plankton.translate.get("page.event_add.title"), - "groups": ["logged_in"], - } - */ - { - "location": {"name": "logout", "parameters": {}}, - "label": lib_plankton.translate.get("page.logout.title"), - "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": async () => { + const status = await _dali.backend.status(); + _dali.notify_login(status.name); + _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 (name) => { + widget_menu.set_groups(["logged_in"]); + widget_menu.set_label(name); + } + ); + _dali.listen_logout( + async () => { + widget_menu.set_groups(["logged_out"]); + widget_menu.set_label(null); + } + ); + } 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(status.name); + } + 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/overlay.ts b/source/overlay.ts new file mode 100644 index 0000000..6195c85 --- /dev/null +++ b/source/overlay.ts @@ -0,0 +1,75 @@ +namespace _dali.overlay +{ + + /** + */ + function get_container_element( + ) : HTMLElement + { + return document.querySelector("#overlay"); + } + + + /** + */ + export function get_content_element( + ) : HTMLElement + { + return document.querySelector("#overlay_content"); + } + + + /** + */ + export function clear( + ) : void + { + get_content_element().innerHTML = ""; + } + + + /** + */ + export function toggle( + { + "mode": mode = null, + } + : + { + mode ?: (null | boolean); + } + = + { + } + ) : void + { + get_container_element().classList.toggle("overlay_active", mode ?? undefined); + } + + + /** + */ + export function initialize( + ) : Promise + { + clear(); + const container_element : HTMLElement = get_container_element(); + /* + container_element.addEventListener( + "click", + (event) => { + if (event.target == container_element) + { + toggle({"mode": false}); + } + else + { + // do nothing + } + } + ); + */ + return Promise.resolve(undefined); + } + +} diff --git a/source/pages/calendar_add/logic.ts b/source/pages/calendar_add/logic.ts deleted file mode 100644 index 7438bb4..0000000 --- a/source/pages/calendar_add/logic.ts +++ /dev/null @@ -1,228 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "calendar_add", - async (parameters, target_element) => { - target_element.innerHTML = ""; - target_element.innerHTML = await _dali.helpers.template_coin( - "calendar_add", - "default", - { - "label": lib_plankton.translate.get("page.calendar_add.title") - } - ); - const form : lib_plankton.zoo_form.class_form< - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - }, - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - } - > = new lib_plankton.zoo_form.class_form< - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - }, - { - name : string; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - resource_kind : string; - hue : (null | float); - } - >( - (value) => value, - (raw) => raw, - new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text(), - "label": lib_plankton.translate.get("calendar.name") - }, - { - "name": "hue", - "input": new lib_plankton.zoo_input.class_input_hue( - ), - "label": lib_plankton.translate.get("calendar.hue"), - }, - { - "name": "access", - "input": new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "public", - "input": new lib_plankton.zoo_input.class_input_checkbox(), - "label": lib_plankton.translate.get("calendar.access.public"), - }, - { - "name": "default_level", - "input": _dali.helpers.input_access_level(), - "label": lib_plankton.translate.get("calendar.access.default_level"), - }, - { - "name": "attributed", - "input": await _dali.helpers.input_attributed_access(), - "label": lib_plankton.translate.get("calendar.access.attributed"), - }, - ] - ), - "label": lib_plankton.translate.get("calendar.access.access"), - }, - { - "name": "resource_kind", - "input": new lib_plankton.zoo_input.class_input_selection( - [ - { - "value": "local", - "label": lib_plankton.translate.get("resource.kinds.local.title") - }, - { - "value": "caldav", - "label": lib_plankton.translate.get("resource.kinds.caldav.title") - }, - ] - ), - "label": lib_plankton.translate.get("resource.kind") - }, - ] - ), - [ - { - "label": lib_plankton.translate.get("page.calendar_add.actions.do"), - "procedure": async (get_value, get_representation) => { - const value : any = await get_value(); - const calendar_object : _dali.type.calendar_object = { - "name": value.name, - "access": { - "public": value.access.public, - "default_level": value.access.default_level, - "attributed": value.access.attributed, - }, - "resource": (() => { - switch (value.resource_kind) { - case "local": - { - return { - "kind": "local", - "data": { - "events": [], - } - }; - break; - } - case "caldav": - { - return { - "kind": "caldav", - "data": { - "url": "", // TODO - "read_only": true, // TODO - } - }; - break; - } - default: - { - throw (new Error("invalid resource kind: " + value.resource_kind)); - break; - } - } - }) (), - "hue": value.hue, - }; - try - { - await _dali.backend.calendar_add( - calendar_object - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) - { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - ] - ); - await form.setup(document.querySelector("#calendar_add_form")); - await form.input_write( - { - "name": "", - "access": { - "public": false, - "default_level": _dali.type.enum_access_level.view, - "attributed": lib_plankton.map.hashmap.implementation_map< - _dali.type.user_id, - _dali.type.enum_access_level - >( - lib_plankton.map.hashmap.make< - _dali.type.user_id, - _dali.type.enum_access_level - >( - user_id => user_id.toFixed(0), - ) - ), - }, - "resource_kind": "local", - "hue": lib_plankton.random.generate_unit(), - } - ); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/calendar_add/templates/default.html.tpl b/source/pages/calendar_add/templates/default.html.tpl deleted file mode 100644 index 3b573e7..0000000 --- a/source/pages/calendar_add/templates/default.html.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{label}}

-
-
-
diff --git a/source/pages/calendar_edit/logic.ts b/source/pages/calendar_edit/logic.ts deleted file mode 100644 index 0ed083d..0000000 --- a/source/pages/calendar_edit/logic.ts +++ /dev/null @@ -1,187 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "calendar_edit", - async (parameters, target_element) => { - const read_only : boolean = ((parameters["read_only"] ?? "yes") === "yes"); - const calendar_id : int = parseInt(parameters["calendar_id"]); - target_element.innerHTML = ""; - target_element.innerHTML = await _dali.helpers.template_coin( - "calendar_edit", - "default", - { - "label": lib_plankton.translate.get("page.calendar_edit.title.regular") - } - ); - const form : lib_plankton.zoo_form.class_form< - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - }, - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - } - > = new lib_plankton.zoo_form.class_form< - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - }, - { - name : string; - hue : float; - access : { - public : boolean; - default_level : _dali.type.enum_access_level; - attributed : lib_plankton.map.type_map< - _dali.type.user_id, - _dali.type.enum_access_level - >; - }; - } - >( - (value) => value, - (raw) => raw, - new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text(), - "label": lib_plankton.translate.get("calendar.name") - }, - { - "name": "hue", - "input": new lib_plankton.zoo_input.class_input_hue( - ), - "label": lib_plankton.translate.get("calendar.hue"), - }, - { - "name": "access", - "input": new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "public", - "input": new lib_plankton.zoo_input.class_input_checkbox(), - "label": lib_plankton.translate.get("calendar.access.public"), - }, - { - "name": "default_level", - "input": _dali.helpers.input_access_level(), - "label": lib_plankton.translate.get("calendar.access.default_level"), - }, - { - "name": "attributed", - "input": await _dali.helpers.input_attributed_access(), - "label": lib_plankton.translate.get("calendar.access.attributed"), - }, - ] - ), - "label": lib_plankton.translate.get("calendar.access.access"), - }, - ] - ), - ( - read_only - ? - [ - ] - : - [ - { - "label": lib_plankton.translate.get("page.calendar_edit.actions.change"), - "procedure": async (get_value, get_representation) => { - const data : any = await get_value(); - try { - await _dali.backend.calendar_change( - calendar_id, - data - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - { - "label": lib_plankton.translate.get("page.calendar_edit.actions.remove"), - "procedure": async (get_value, get_representation) => { - try { - await _dali.backend.calendar_remove( - calendar_id - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - ] - ) - ); - await form.setup(document.querySelector("#calendar_edit_form")); - const calendar_object : _dali.type.calendar_object = await _dali.backend.calendar_get( - calendar_id - ); - await form.input_write(calendar_object); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/calendar_edit/templates/default.html.tpl b/source/pages/calendar_edit/templates/default.html.tpl deleted file mode 100644 index 62d2f1a..0000000 --- a/source/pages/calendar_edit/templates/default.html.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{label}}

-
-
-
diff --git a/source/pages/event_add/logic.ts b/source/pages/event_add/logic.ts deleted file mode 100644 index f5b5d90..0000000 --- a/source/pages/event_add/logic.ts +++ /dev/null @@ -1,235 +0,0 @@ -namespace _dali.pages -{ - - /** - */ - lib_plankton.zoo_page.register( - "event_add", - async (parameters, target_element) => { - const calendar_id : (null | int) = ( - ("calendar_id" in parameters) - ? - parseInt(parameters["calendar_id"]) - : - null - ); - const year : (null | int) = ( - ("year" in parameters) - ? - parseInt(parameters["year"]) - : - null - ); - const month : (null | int) = ( - ("month" in parameters) - ? - parseInt(parameters["month"]) - : - null - ); - const day : (null | int) = ( - ("day" in parameters) - ? - parseInt(parameters["day"]) - : - null - ); - const date : lib_plankton.pit.type_date = ( - ( - (year !== null) - && - (month !== null) - && - (day !== null) - ) - ? - { - "year": year, - "month": month, - "day": day, - } - : - lib_plankton.pit.to_datetime(lib_plankton.pit.now()).date - ); - target_element.innerHTML = ""; - target_element.innerHTML = await _dali.helpers.template_coin( - "event_add", - "default", - { - "label": lib_plankton.translate.get("page.event_add.title") - } - ); - const form : lib_plankton.zoo_form.class_form< - { - calendar_id : _dali.type.calendar_id; - event_object : _dali.type.event_object; - }, - { - calendar_id : string; - name : string; - begin : lib_plankton.pit.type_datetime; - end : (null | lib_plankton.pit.type_datetime); - location : (null | string); - link : (null | string); - description : (null | string); - } - > = new lib_plankton.zoo_form.class_form< - { - calendar_id : _dali.type.calendar_id; - event_object : _dali.type.event_object; - }, - { - calendar_id : string; - name : string; - begin : lib_plankton.pit.type_datetime; - end : (null | lib_plankton.pit.type_datetime); - location : (null | string); - link : (null | string); - description : (null | string); - } - >( - (value) => ({ - "calendar_id": value.calendar_id.toFixed(0), - "name": value.event_object.name, - "begin": value.event_object.begin, - "end": value.event_object.end, - "location": value.event_object.location, - "link": value.event_object.link, - "description": value.event_object.description, - }), - (representation) => ({ - "calendar_id": parseInt(representation.calendar_id), - "event_object": { - "name": representation.name, - "begin": representation.begin, - "end": representation.end, - "location": representation.location, - "link": representation.link, - "description": representation.description, - } - }), - new lib_plankton.zoo_input.class_input_group( - [ - { - "name": "calendar_id", - "input": new lib_plankton.zoo_input.class_input_selection( - ( - (await _dali.backend.calendar_list()) - .filter( - (entry) => ( - (entry.access_level === _dali.type.enum_access_level.edit) - || - (entry.access_level === _dali.type.enum_access_level.admin) - ) - ) - .map( - (entry) => ({ - "value": entry.id.toFixed(0), - "label": entry.name, - }) - ) - ) - ), - "label": lib_plankton.translate.get("calendar.calendar") - }, - { - "name": "name", - "input": new lib_plankton.zoo_input.class_input_text( - ), - "label": lib_plankton.translate.get("event.name") - }, - { - "name": "begin", - "input": _dali.helpers.datetime_input(), - "label": lib_plankton.translate.get("event.begin") - }, - { - "name": "end", - "input": new lib_plankton.zoo_input.class_input_soft( - _dali.helpers.datetime_input() - ), - "label": lib_plankton.translate.get("event.end") - }, - { - "name": "location", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_text( - ) - ), - "label": lib_plankton.translate.get("event.location") - }, - { - "name": "link", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_text( - ) - ), - "label": lib_plankton.translate.get("event.link") - }, - { - "name": "description", - "input": new lib_plankton.zoo_input.class_input_soft( - new lib_plankton.zoo_input.class_input_textarea( - ) - ), - "label": lib_plankton.translate.get("event.description") - }, - ] - ), - [ - { - "label": lib_plankton.translate.get("page.event_add.actions.do"), - "target": "submit", - "procedure": async (get_value, get_representation) => { - const value : any = await get_value(); - try { - await _dali.backend.calendar_event_add( - value.calendar_id, - value.event_object - ); - lib_plankton.zoo_page.set( - { - "name": "overview", - "parameters": {} - } - ); - } - catch (error) { - // do nothing - /* - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { - } - } - ); - */ - } - } - }, - ] - ); - await form.setup(document.querySelector("#event_add_form")); - await form.input_write( - { - "calendar_id": (calendar_id ?? 0), - "event_object": { - "name": "", - "begin": { - "timezone_shift": 0, - "date": date, - "time": null - }, - "end": null, - "location": null, - "link": null, - "description": null, - } - } - ); - return Promise.resolve(undefined); - } - ); - -} diff --git a/source/pages/event_add/templates/default.html.tpl b/source/pages/event_add/templates/default.html.tpl deleted file mode 100644 index 3cfad06..0000000 --- a/source/pages/event_add/templates/default.html.tpl +++ /dev/null @@ -1,5 +0,0 @@ -
-

{{label}}

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

{{label}}

-
-
-
diff --git a/source/pages/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..1c3f42a 100644 --- a/source/pages/oidc_finish/logic.ts +++ b/source/pages/oidc_finish/logic.ts @@ -7,7 +7,7 @@ namespace _dali.pages "oidc_finish", async (parameters, target_element) => { target_element.innerHTML = ""; - await _dali.backend.set_session_key(parameters["session_key"]); + await _dali.oidc_finish(parameters["session_key"]); lib_plankton.zoo_page.set( { "name": "overview", diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index 7caeb15..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); }; @@ -27,14 +27,14 @@ namespace _dali.pages.overview // mode switcher { - const widget_mode_switcher : _dali.class_widget = new _dali.widgets.mode_switcher.class_widget_mode_switcher( + const widget_mode_switcher : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.mode_switcher.class_widget_mode_switcher( [ { - "mode": _dali.type.enum_view_mode.week, + "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"), }, ], @@ -57,69 +57,146 @@ namespace _dali.pages.overview await widget_mode_switcher.load(target_element.querySelector("#overview-mode")); } + let widget_sources : _dali.widgets.sources.class_widget_sources; let widget_weekview : _dali.widgets.weekview.class_widget_weekview; let widget_listview : _dali.widgets.listview.class_widget_listview; + + 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 listview + */ + 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(); + }; // 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 { - const data : Array< - { - id : _dali.type.calendar_id; - name : string; - hue : float; - access_level : _dali.type.enum_access_level; - } - > = await _dali.backend.calendar_list( - ); - const widget_sources = new _dali.widgets.sources.class_widget_sources( - data, + widget_sources = new _dali.widgets.sources.class_widget_sources( + _dali.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": (calendar_object) => { + _dali.model.calendar_add( + calendar_object + ) + .then( + () => { + update_sources_and_entries(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + lib_plankton.log.warning( + "dali.overview.calendar_add_error", + {"reason": String(reason)} + ); + } + ); + }, + "initial_value": null, + } + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); + }, "action_open": (entry) => { + let read_only : boolean; switch (entry.access_level) { - 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: { - lib_plankton.zoo_page.set( - { - "name": "calendar_edit", - "parameters": { - "read_only": "yes", - "calendar_id": entry.id, - } - } - ); + read_only = true; break; } - case _dali.type.enum_access_level.admin: + case _dali.enum_access_level.admin: { - lib_plankton.zoo_page.set( - { - "name": "calendar_edit", - "parameters": { - "read_only": "no", - "calendar_id": entry.id, - } - } - ); + read_only = false; break; } } + (async () => { + const calendar_id : _dali.type_calendar_id = entry.id; + const calendar_object : _dali.type_calendar_object = await _dali.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.model.calendar_change( + calendar_id, + data + ) + .then( + () => { + update_sources_and_entries(); + _dali.overlay.toggle({"mode": false}); + } + ); + }, + "action_remove": (data) => { + _dali.model.calendar_remove( + calendar_id + ) + .then( + () => { + update_sources_and_entries(); + _dali.overlay.toggle({"mode": false}); + } + ); + }, + "initial_value": calendar_object, + } + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); }, "action_toggle_visibility": (entry) => { widget_weekview.toggle_visibility(entry.id); @@ -131,51 +208,131 @@ namespace _dali.pages.overview } // events { - const get_entries = (from_pit, to_pit, calendar_ids) => _dali.backend.events( - from_pit, - to_pit, + 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 (! _dali.is_logged_in()) { - "calendar_ids": calendar_ids, + // do nothing } - ); - const action_select_event = (calendar_id, access_level, event_id) => { + else + { + } + */ + 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: { - lib_plankton.zoo_page.set( - { - "name": "event_edit", - "parameters": { - "read_only": "yes", - "calendar_id": calendar_id, - "event_id": event_id, - } - } - ); + read_only = true; break; } - case _dali.type.enum_access_level.edit: - case _dali.type.enum_access_level.admin: + case _dali.enum_access_level.edit: + case _dali.enum_access_level.admin: { - lib_plankton.zoo_page.set( - { - "name": "event_edit", - "parameters": { - "read_only": "no", - "calendar_id": calendar_id, - "event_id": event_id, - } - } - ); + read_only = false; break; } } + (async () => { + const event_object : _dali.type_event_object = await _dali.backend.calendar_event_get( + calendar_id, + event_id + ); + const widget = new _dali.widgets.event_edit.class_widget_event_edit( + (await get_available_calendars()), + { + "calendar_id": calendar_id, + "event_name": event_object.name, + "event_begin": event_object.begin, + "event_end": event_object.end, + "event_location": event_object.location, + "event_link": event_object.link, + "event_description": event_object.description, + }, + { + "read_only": read_only, + "action_cancel": () => { + _dali.overlay.toggle({"mode": false}); + }, + "action_change": (data) => { + _dali.model.event_change( + event_key, + { + "name": data.event_name, + "begin": data.event_begin, + "end": data.event_end, + "location": data.event_location, + "link": data.event_link, + "description": data.event_description, + } + ) + .then( + () => { + update_entries(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + lib_plankton.log.warning( + "dali.overview.event_change.error", + {"reason": String(reason)} + ); + } + ); + }, + "action_remove": () => { + _dali.model.event_remove( + event_key + ) + .then( + () => { + update_entries(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + lib_plankton.log.warning( + "dali.overview.event_remove_error", + {"reason": String(reason)} + ); + } + ); + }, + } + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); }; // listview { @@ -210,23 +367,98 @@ namespace _dali.pages.overview { "action_select_event": action_select_event, "action_select_day": (date) => { - lib_plankton.zoo_page.set( - { - "name": "event_add", - "parameters": { + /* + if (! _dali.is_logged_in()) + { + // do nothing + } + else + { + } + */ + (async () => { + const widget = new _dali.widgets.event_edit.class_widget_event_edit( + (await get_available_calendars()), + { "calendar_id": null, - "year": date.year, - "month": date.month, - "day": date.day, + "event_name": "", + "event_begin": lib_plankton.call.convey( + date, + [ + x => ({ + "timezone_shift": 0, + "date": date, + "time": {"hour": 12, "minute": 0, "second": 0} + }), + lib_plankton.pit.from_datetime, + x => lib_plankton.pit.shift_hour(x, 0), + lib_plankton.pit.to_datetime, + ] + ), + "event_end": lib_plankton.call.convey( + date, + [ + x => ({ + "timezone_shift": 0, + "date": date, + "time": {"hour": 12, "minute": 0, "second": 0} + }), + lib_plankton.pit.from_datetime, + x => lib_plankton.pit.shift_hour(x, +1), + lib_plankton.pit.to_datetime, + ] + ), + "event_location": null, + "event_link": null, + "event_description": null, + }, + { + "read_only": false, + "action_cancel": () => { + _dali.overlay.toggle({"mode": false}); + }, + "action_add": (data) => { + _dali.model.event_add( + data.calendar_id, + { + "name": data.event_name, + "begin": data.event_begin, + "end": data.event_end, + "location": data.event_location, + "link": data.event_link, + "description": data.event_description, + } + ) + .then( + () => { + update_entries(); + _dali.overlay.toggle({"mode": false}); + } + ) + .catch( + (reason) => { + // todo + } + ); + }, } - } - ); + ); + _dali.overlay.clear(); + _dali.overlay.toggle({"mode": true}); + await widget.load(_dali.overlay.get_content_element()); + }) (); }, } ) ); 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 1164246..3fc73d1 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 : ( @@ -14,44 +98,52 @@ namespace _dali.backend /** */ - 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"; - } - } + var _cache : ( + null + | + lib_plankton.cache.type_subject + ); /** */ - 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", } ); + _cache = lib_plankton.cache.make( + /* + lib_plankton.storage.memory.implementation_chest( + { + } + ) + */ + ); + _queue = { + "items": [], + "busy": false, + }; return Promise.resolve(undefined); } @@ -59,7 +151,8 @@ namespace _dali.backend /** */ async function get_session_key( - ) : Promise<(null | string)> + ) + : Promise<(null | string)> { try { @@ -74,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" : @@ -100,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( {}, @@ -137,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); @@ -159,22 +251,98 @@ namespace _dali.backend return Promise.resolve(output); } } - + /** */ - export async function is_logged_in( - ) : Promise + async function process( + ) + : Promise { - // return ((await get_session_key()) !== null); - const result : { + 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, + } + ); + } + ); + process(); + return promise; + } + + + /** + */ + export function status( + ) + : Promise< + { logged_in : boolean; - } = await call( + name : (null | string); + } + > + { + return call( lib_plankton.http.enum_method.get, "/session/status", null - ); - return result.logged_in; + ) } @@ -182,7 +350,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, @@ -196,7 +365,8 @@ namespace _dali.backend */ export function set_session_key( session_key : string - ) : Promise + ) + : Promise { return ( _data_chest.write("session_key", session_key) @@ -209,7 +379,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, @@ -227,7 +398,8 @@ namespace _dali.backend /** */ export async function session_end( - ) : Promise + ) + : Promise { await call( lib_plankton.http.enum_method.delete, @@ -242,7 +414,8 @@ namespace _dali.backend /** */ export function user_list( - ) : Promise< + ) + : Promise< Array< { id : int; @@ -262,7 +435,8 @@ namespace _dali.backend /** */ export function user_dav_conf( - ) : Promise< + ) + : Promise< ( null | @@ -292,7 +466,8 @@ namespace _dali.backend /** */ export function user_dav_token( - ) : Promise + ) + : Promise { return call( lib_plankton.http.enum_method.patch, @@ -305,36 +480,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 ); } @@ -342,158 +503,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}}", { @@ -505,12 +536,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, @@ -529,9 +670,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, @@ -541,18 +688,20 @@ namespace _dali.backend "calendar_id": calendar_id.toFixed(0), } ), - event_object + event_data ); } /** + * @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, @@ -571,9 +720,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, @@ -593,62 +743,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, - options : { - calendar_ids ?: (null | Array<_dali.type.calendar_id>); - } = {} - ) : Promise< + from_timestamp : int, + to_timestamp : int, + { + "calendar_ids": calendar_ids = null, + } + : + { + calendar_ids ?: (null | Array); + } + = + { + } + ) + : Promise< Array< { - 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; } > > { - options = Object.assign( - { - "calendar_ids": null, - }, - options - ); - - return ( - call( - lib_plankton.http.enum_method.get, - "/events", - Object.assign( - { - "from": from_pit, - "to": to_pit, - }, - ( - (options.calendar_ids === null) - ? - {} - : - {"calendar_ids": options.calendar_ids.join(",")} - ) - ) - ) - .then( - (data) => Promise.resolve( - data - .map( - (entry) => ({ - "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 e1d417e..49bbfc4 100644 --- a/source/style/main.css +++ b/source/style/main.css @@ -5,21 +5,54 @@ 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; */ margin-bottom: 16px; } +#overlay +{ + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: hsla(var(--hue), 0%, 0%, 0.75); + z-index: 2; +} + +#overlay_content +{ + position: absolute; + top: 50%; + left: 50%; + + transform: translate(-50%,-50%); + -ms-transform: translate(-50%,-50%); + + padding: 32px; + + background-color: hsl(0, 0%, 12.5%); + color: hsl(0, 0%, 100%); +} + +#overlay:not(.overlay_active) +{ + display: none; +} + nav > ul { list-style-type: none; @@ -56,12 +89,14 @@ nav a:hover a { text-decoration: none; - color: hsl(var(--hue), 50%, 50%); + color: hsl(var(--hue), 0%, 87.5%); } a:hover { - color: hsl(var(--hue), 50%, 75%); + color: hsl(var(--hue), 0%, 100%); + border-bottom: 2px solid hsl(0, 0%, 100%); + transition: 1s ease color; } input,select,textarea @@ -74,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..d8b4b40 --- /dev/null +++ b/source/style/widget-menu.css @@ -0,0 +1,75 @@ +.widget-menu +{ + margin-left: auto; + margin-right: 8px; + width: fit-content; +} + +.widget-menu.widget-menu-collapsed > .widget-menu-platform +{ + display: none; +} + +.widget-menu-button +{ + text-transform: initial; +} + +.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 bfcb618..47dfbb5 100644 --- a/source/style/widget-sources.css +++ b/source/style/widget-sources.css @@ -1,9 +1,23 @@ .sources +{ + font-size: 0.75em; +} + +.sources:not(.sources-priviliged) > .sources-create +{ + display: none; +} + +.sources-create +{ + margin-left: 8px; +} + +.sources-entries { margin: 0; padding: 0; list-style-type: none; - font-size: 0.75em; } .sources-entry 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 new file mode 100644 index 0000000..455670e --- /dev/null +++ b/source/widgets/calendar_edit/logic.ts @@ -0,0 +1,242 @@ +namespace _dali.widgets.calendar_edit +{ + + /** + */ + 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; + + + /** + */ + private action_cancel ?: (null | (() => void)); + + + /** + */ + private action_add ?: (null | ((value : _dali.type_calendar_object) => void)); + + + /** + */ + private action_change ?: (null | ((value : _dali.type_calendar_object) => void)); + + + /** + */ + private action_remove ?: (null | ((value : _dali.type_calendar_object) => void)); + + + /** + */ + 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, + "action_add": action_add = null, + "action_change": action_change = null, + "action_remove": action_remove = null, + "initial_value": initial_value = null, + } + : + { + read_only ?: boolean; + action_cancel ?: (null | (() => void)); + 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; + this.action_change = action_change; + this.action_remove = action_remove; + this.initial_value = initial_value; + } + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) : Promise + { + const form : lib_plankton.zoo_form.class_form< + _dali.type_calendar_object, + _dali.type_calendar_object + > = new lib_plankton.zoo_form.class_form< + _dali.type_calendar_object, + _dali.type_calendar_object + >( + (value) => value, + (raw) => raw, + new lib_plankton.zoo_input.class_input_group( + [ + { + "name": "name", + "input": new lib_plankton.zoo_input.class_input_text(), + "label": lib_plankton.translate.get("calendar.name") + }, + { + "name": "hue", + "input": new lib_plankton.zoo_input.class_input_hue( + ), + "label": lib_plankton.translate.get("calendar.hue"), + }, + { + "name": "access", + "input": new lib_plankton.zoo_input.class_input_group( + [ + { + "name": "public", + "input": new lib_plankton.zoo_input.class_input_checkbox(), + "label": lib_plankton.translate.get("calendar.access.public"), + }, + { + "name": "default_level", + "input": _dali.helpers.input_access_level(), + "label": lib_plankton.translate.get("calendar.access.default_level"), + }, + { + "name": "attributed", + "input": _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"), + }, + ] + ), + ( + [] + // add + .concat( + ((! this.read_only) && (! (this.action_add === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.calendar_edit.actions.add"), + "procedure": async (get_value, get_representation) => { + const value : _dali.type_calendar_object = await get_value(); + this.action_add(value); + } + }, + ] + : + [] + ) + // change + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.calendar_edit.actions.change"), + "procedure": async (get_value, get_representation) => { + const value : _dali.type_calendar_object = await get_value(); + this.action_change(value); + } + }, + ] + : + [] + ) + // remove + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.calendar_edit.actions.remove"), + "procedure": async (get_value, get_representation) => { + const value : _dali.type_calendar_object = await get_value(); + this.action_remove(value); + } + }, + ] + : + [] + ) + // cancel + .concat( + (! (this.action_cancel === null)) + ? + [ + { + "label": lib_plankton.translate.get("common.cancel"), + "procedure": async () => { + this.action_cancel(); + } + }, + ] + : + [] + ) + ) + ); + await form.setup(target_element); + await form.input_write( + (! (this.initial_value === null)) + ? + this.initial_value + : + { + "name": "", + "hue": lib_plankton.random.generate_unit(), + "access": { + "public": false, + "default_level": _dali.enum_access_level.view, + "attributed": lib_plankton.map.hashmap.implementation_map< + _dali.type_user_id, + _dali.enum_access_level + >( + lib_plankton.map.hashmap.make< + _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 new file mode 100644 index 0000000..093d204 --- /dev/null +++ b/source/widgets/event_edit/logic.ts @@ -0,0 +1,296 @@ +namespace _dali.widgets.event_edit +{ + + /** + */ + export type type_value = { + calendar_id : (null | _dali.type_calendar_id); + event_name : string; + event_begin : lib_plankton.pit.type_datetime; + event_end : (null | lib_plankton.pit.type_datetime); + event_location : (null | string); + event_link : (null | string); + event_description : (null | string); + }; + + + /** + */ + export type type_representation = { + calendar_id : string; + event_name : string; + event_begin : lib_plankton.pit.type_datetime; + event_end : (null | lib_plankton.pit.type_datetime); + event_location : (null | string); + event_link : (null | string); + event_description : (null | string); + }; + + + /** + */ + export class class_widget_event_edit + implements lib_plankton.zoo_widget.interface_widget + { + + /** + * [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)); + + + /** + */ + 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, + "action_cancel": action_cancel = null, + "action_add": action_add = null, + "action_change": action_change = null, + "action_remove": action_remove = null, + } + : + { + read_only ?: boolean; + action_cancel ?: (null | (() => void)); + action_add ?: (null | ((value : type_value) => void)); + action_change ?: (null | ((value : type_value) => void)); + action_remove ?: (null | ((value : type_value) => void)); + } + = + { + } + ) + { + // 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; + } + + + /** + * [implementation] + */ + public async load( + target_element : HTMLElement + ) : Promise + { + const form : lib_plankton.zoo_form.class_form< + type_value, + type_representation + > = new lib_plankton.zoo_form.class_form< + type_value, + type_representation + >( + (value) => ({ + "calendar_id": (value.calendar_id ?? this.available_calendars[0].id).toFixed(0), + "event_name": value.event_name, + "event_begin": value.event_begin, + "event_end": value.event_end, + "event_location": value.event_location, + "event_link": value.event_link, + "event_description": value.event_description, + }), + (representation) => ({ + "calendar_id": parseInt(representation.calendar_id), + "event_name": representation.event_name, + "event_begin": representation.event_begin, + "event_end": representation.event_end, + "event_location": representation.event_location, + "event_link": representation.event_link, + "event_description": representation.event_description, + }), + new lib_plankton.zoo_input.class_input_group( + [ + { + "name": "calendar_id", + "input": new lib_plankton.zoo_input.class_input_selection( + ( + this.available_calendars + .map( + (entry) => ({ + "value": entry.id.toFixed(0), + "label": entry.name, + }) + ) + ) + ), + "label": lib_plankton.translate.get("calendar.calendar") + }, + { + "name": "event_name", + "input": new lib_plankton.zoo_input.class_input_text( + ), + "label": lib_plankton.translate.get("event.name") + }, + { + "name": "event_begin", + "input": _dali.helpers.datetime_input(), + "label": lib_plankton.translate.get("event.begin") + }, + { + "name": "event_end", + "input": new lib_plankton.zoo_input.class_input_soft( + _dali.helpers.datetime_input() + ), + "label": lib_plankton.translate.get("event.end") + }, + { + "name": "event_location", + "input": new lib_plankton.zoo_input.class_input_soft( + new lib_plankton.zoo_input.class_input_text( + ) + ), + "label": lib_plankton.translate.get("event.location") + }, + { + "name": "event_link", + "input": new lib_plankton.zoo_input.class_input_soft( + new lib_plankton.zoo_input.class_input_text( + ) + ), + "label": lib_plankton.translate.get("event.link") + }, + { + "name": "event_description", + "input": new lib_plankton.zoo_input.class_input_soft( + new lib_plankton.zoo_input.class_input_textarea( + ) + ), + "label": lib_plankton.translate.get("event.description") + }, + ] + ), + ( + [] + // add + .concat( + ((! this.read_only) && (! (this.action_add === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.event_edit.actions.add"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_add(value); + } + }, + ] + : + [] + ) + // change + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.event_edit.actions.change"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_change(value); + } + }, + ] + : + [] + ) + // remove + .concat( + ((! this.read_only) && (! (this.action_change === null))) + ? + [ + { + "label": lib_plankton.translate.get("widget.event_edit.actions.remove"), + "procedure": async (get_value, get_representation) => { + const value : type_value = await get_value(); + this.action_remove(value); + } + }, + ] + : + [] + ) + // cancel + .concat( + (! (this.action_cancel === null)) + ? + [ + { + "label": lib_plankton.translate.get("common.cancel"), + "procedure": async () => { + this.action_cancel(); + } + }, + ] + : + [] + ) + ) + ); + await form.setup(target_element); + await form.input_write(this.initial_value); + } + + } + +} diff --git a/source/widgets/listview/logic.ts b/source/widgets/listview/logic.ts index 9523e4c..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> @@ -28,7 +28,7 @@ namespace _dali.widgets.listview /** */ - export class class_widget_listview extends _dali.class_widget + export class class_widget_listview implements lib_plankton.zoo_widget.interface_widget { /** @@ -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 @@ -94,7 +94,6 @@ namespace _dali.widgets.listview }, options ); - super(); this.get_entries = get_entries; this.container = null; this.action_select_event = options.action_select_event; @@ -105,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 } @@ -156,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" : @@ -251,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"; } }) (), } @@ -290,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..68c486a --- /dev/null +++ b/source/widgets/menu/logic.ts @@ -0,0 +1,190 @@ +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 label : (null | string); + + + /** + */ + private container : (null | HTMLElement); + + + /** + */ + public constructor( + entry_data_list : Array, + { + "initial_groups": initial_groups = [], + "initial_label": initial_label = "=", + } + : + { + initial_groups ?: Array; + initial_label ?: string; + } + = + { + } + ) + { + this.entries = entry_data_list.map( + entry_data => ( + { + "data": entry_data, + "element": null, + } + ) + ); + this.initial_groups = initial_groups; + this.label = initial_label; + 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)); + } + ); + } + + /** + */ + public set_label( + label ?: string + ) + : void + { + this.label = label; + this.container.querySelector(".widget-menu-button").textContent = ( + (this.label === null) + ? + "[=]" + : + ("[" + this.label + "]") + ); + } + + + /** + */ + 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 = "[" + this.label + "]"; + 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 50a8e53..ca966d2 100644 --- a/source/widgets/mode_switcher/logic.ts +++ b/source/widgets/mode_switcher/logic.ts @@ -4,14 +4,14 @@ namespace _dali.widgets.mode_switcher /** */ type type_option = { - mode : _dali.type.enum_view_mode; + mode : _dali.enum_view_mode; label : string, }; /** */ - export class class_widget_mode_switcher extends _dali.class_widget + export class class_widget_mode_switcher implements lib_plankton.zoo_widget.interface_widget { /** @@ -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,15 +39,14 @@ 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)) } = { } ) { - super(); this.options = options; this.initial_selection = initial_selection; this.action_change = action_change; @@ -100,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 e9b57da..b8cd927 100644 --- a/source/widgets/sources/logic.ts +++ b/source/widgets/sources/logic.ts @@ -4,67 +4,272 @@ 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; }; /** */ - export class class_widget_sources extends _dali.class_widget + export class class_widget_sources implements lib_plankton.zoo_widget.interface_widget { /** + * [dependency] */ - private keys : Array; - - - /** - */ - private data : Record; + private get_entries : (() => Promise>); /** + * [hook] */ private action_open : ((entry : type_entry) => void); /** + * [hook] */ private action_toggle_visibility : ((entry : type_entry) => void); + /** + * [hook] + */ + private action_create : (() => void); + + + /** + */ + private priviliged : boolean; + + + /** + * [state] + */ + private container : (null | Element); + + /** */ public constructor( - entries : Array, - options : { + get_entries : (() => Promise>), + { + "action_open": action_open = ((calendar_id) => {}), + "action_toggle_visibility": action_toggle_visibility = ((calendar_id) => {}), + "action_create": action_create = (() => {}), + "initial_priviliged": initial_priviliged = false, + } + : + { action_open ?: ((entry : type_entry) => void); action_toggle_visibility ?: ((entry : type_entry) => void); - } = {} + action_create ?: (() => void); + initial_priviliged ?: boolean; + } + = + { + } ) { - options = Object.assign( - { - "action_open": (calendar_id) => {}, - "action_toggle_visibility": (calendar_id) => {}, - }, - options + // dependencies + this.get_entries = get_entries; + + // hooks + this.action_open = action_open; + this.action_toggle_visibility = action_toggle_visibility; + this.action_create = action_create; + + // state + this.priviliged = initial_priviliged; + this.container = null; + } + + + /** + */ + private static id_encode( + id : _dali.type_calendar_id + ) + : string + { + return id.toFixed(0); + } + + + /** + */ + private static id_decode( + representation : string + ) + : _dali.type_calendar_id + { + return parseInt(representation); + } + + + /** + */ + public async update( + { + "priviliged": priviliged = null, + } + : + { + priviliged ?: boolean; + } + = + { + } + ) + : Promise + { + 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, + } + ) + ) + ), + } + ) ); - super(); - this.keys = []; - this.data = {}; - entries.forEach( - (entry) => { - const key : string = entry.id.toFixed(0); - this.keys.push(key); - this.data[key] = entry; - } - ); - this.action_open = options.action_open; - this.action_toggle_visibility = options.action_toggle_visibility; + // structure + { + this.container.innerHTML = await _dali.helpers.template_coin( + "widget-sources", + "main", + { + "label_create": lib_plankton.translate.get("widget.sources.create"), + "entries": ( + ( + await _dali.helpers.promise_row( + lib_plankton.map.dump(data) + .map( + (pair) => () => { + return _dali.helpers.template_coin( + "widget-sources", + "entry", + { + "name": pair.value.name, + "label_toggle": lib_plankton.string.coin( + "{{show}}/{{hide}}", + { + "show": lib_plankton.translate.get("common.show"), + "hide": lib_plankton.translate.get("common.hide"), + } + ), + "label_edit": lib_plankton.translate.get("common.edit"), + // "access_level": entry.access_level, // TODO + // TODO centralize + "color_head": lib_plankton.color.output_hex( + lib_plankton.color.make_hsv( + { + "hue": pair.value.hue, + /** + * @todo const and outsource + */ + "saturation": 0.375, + /** + * @todo const and outsource + */ + "value": 0.375, + } + ), + ), + "color_body": lib_plankton.color.output_hex( + lib_plankton.color.make_hsv( + { + "hue": pair.value.hue, + /** + * @todo const and outsource + */ + "saturation": 0.375, + /** + * @todo const and outsource + */ + "value": 0.25, + } + ), + ), + "rel": class_widget_sources.id_encode(pair.key), + } + ); + } + ) + ) + ) + .join("") + ), + } + ); + this.container.querySelector(".sources").classList.toggle("sources-priviliged", this.priviliged); + } + // listeners + { + this.container.querySelector(".sources-create").addEventListener( + "click", + (event) => { + event.preventDefault(); + this.action_create(); + } + ); + this.container.querySelectorAll(".sources-entry-head").forEach( + (element) => { + element.addEventListener( + "click", + (event) => { + element.parentElement.classList.toggle("sources-entry-open"); + } + ); + } + ); + this.container.querySelectorAll(".sources-entry-toggle").forEach( + (element) => { + element.addEventListener( + "click", + () => { + const key_encoded : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); + const calendar_id : _dali.type_calendar_id = class_widget_sources.id_decode(key_encoded); + const entry : type_entry = data.get(calendar_id); + element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-hidden"); + element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-open", false); + this.action_toggle_visibility(entry); + } + ); + } + ); + this.container.querySelectorAll(".sources-entry-edit").forEach( + (element) => { + element.addEventListener( + "click", + (event) => { + const key_encoded : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); + const calendar_id : _dali.type_calendar_id = class_widget_sources.id_decode(key_encoded); + const entry : type_entry = data.get(calendar_id); + this.action_open(entry); + } + ); + } + ); + } } @@ -73,102 +278,13 @@ namespace _dali.widgets.sources */ public async load( target_element : Element - ) : Promise + ) + : Promise { - target_element.innerHTML = await _dali.helpers.template_coin( - "widget-sources", - "main", - { - "entries": ( - ( - await _dali.helpers.promise_row( - this.keys - .map( - (key) => () => { - const entry : type_entry = this.data[key]; - return _dali.helpers.template_coin( - "widget-sources", - "entry", - { - "name": entry.name, - "label_toggle": lib_plankton.string.coin( - "{{show}}/{{hide}}", - { - "show": lib_plankton.translate.get("common.show"), - "hide": lib_plankton.translate.get("common.hide"), - } - ), - "label_edit": lib_plankton.translate.get("common.edit"), - // "access_level": entry.access_level, // TODO - // TODO centralize - "color_head": lib_plankton.color.output_hex( - lib_plankton.color.make_hsv( - { - "hue": entry.hue, - "saturation": 0.375, - "value": 0.375, - } - ), - ), - "color_body": lib_plankton.color.output_hex( - lib_plankton.color.make_hsv( - { - "hue": entry.hue, - "saturation": 0.375, - "value": 0.25, - } - ), - ), - "rel": key, - } - ); - } - ) - ) - ) - .join("") - ), - } - ); - target_element.querySelectorAll(".sources-entry-head").forEach( - (element) => { - element.addEventListener( - "click", - (event) => { - element.parentElement.classList.toggle("sources-entry-open"); - } - ); - } - ); - target_element.querySelectorAll(".sources-entry-toggle").forEach( - (element) => { - element.addEventListener( - "click", - () => { - const key : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); - const entry : type_entry = this.data[key]; - element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-hidden"); - element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-open", false); - this.action_toggle_visibility(entry); - } - ); - } - ); - target_element.querySelectorAll(".sources-entry-edit").forEach( - (element) => { - element.addEventListener( - "click", - (event) => { - const key : string = element.parentElement.parentElement.parentElement.getAttribute("rel"); - const entry : type_entry = this.data[key]; - this.action_open(entry); - } - ); - } - ); - return Promise.resolve(undefined); + this.container = target_element; + await this.update(); } - + } } diff --git a/source/widgets/sources/templates/main.html.tpl b/source/widgets/sources/templates/main.html.tpl index 3f9a9a7..ba432fd 100644 --- a/source/widgets/sources/templates/main.html.tpl +++ b/source/widgets/sources/templates/main.html.tpl @@ -1,3 +1,6 @@ -
    - {{entries}} -
+
+
    + {{entries}} +
+ {{label_create}} +
diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index 85686df..1d20719 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -4,12 +4,13 @@ namespace _dali.widgets.weekview /** */ type type_entry = { - 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; }; @@ -19,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> @@ -28,26 +29,21 @@ namespace _dali.widgets.weekview /** */ - export class class_widget_weekview extends _dali.class_widget + export class class_widget_weekview implements lib_plankton.zoo_widget.interface_widget { /** + * [dependency] */ private get_entries : type_get_entries; /** - */ - private container : (null | Element); - - - /** + * [hook] */ 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 @@ -55,6 +51,7 @@ namespace _dali.widgets.weekview /** + * [hook] */ private action_select_day : ( ( @@ -65,16 +62,58 @@ namespace _dali.widgets.weekview ); + /** + * [state] + */ + private year : int; + + + /** + * [state] + */ + private week : int; + + + /** + * [state] + */ + private count : int; + + + /** + * [state] + */ + private event_map : lib_plankton.map.type_map< + _dali.type_event_key, + { + element : HTMLElement; + hash : string; + } + >; + + + /** + * [state] + */ + private container : (null | Element); + + /** */ public constructor( get_entries : type_get_entries, - options : { + { + "action_select_day": action_select_day = ((date) => {}), + "action_select_event": action_select_event = ((event_key) => {}), + "initial_year": initial_year = null, + "initial_week": initial_week = null, + "initial_count": initial_count = 5, + } + : + { 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,21 +125,70 @@ namespace _dali.widgets.weekview => void ); - } = {} + initial_year ?: (null | int); + initial_week ?: (null | int); + initial_count ?: int; + } + = + {} ) { - options = Object.assign( - { - "action_select_day": (date) => {}, - "action_select_event": (calendar_id, access_level, event_id) => {}, - }, - options - ); - super(); + // dependencies this.get_entries = get_entries; + + // hooks + this.action_select_day = action_select_day; + this.action_select_event = action_select_event; + + // state + const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now()); + this.year = ( + initial_year + ?? + ywd_now.year + ); + this.week = ( + initial_week + ?? + Math.max(0, (ywd_now.week - 1)) + ); + this.count = initial_count; + this.event_map = lib_plankton.map.hashmap.implementation_map( + lib_plankton.map.hashmap.make< + _dali.type_event_key, + { + element : HTMLElement; + hash : string; + } + >( + event_key => event_key + ) + ); this.container = null; - this.action_select_day = options.action_select_day; - this.action_select_event = options.action_select_event; + } + + + /** + * some kind of checksum for comparing entries + * @todo base64 encode? + * @todo sha256 hash? + */ + private static entry_hash( + entry : type_entry + ) : string + { + return lib_plankton.call.convey( + { + "calendar_id": entry.calendar_id, + "calendar_name": entry.calendar_name, + "hue": Math.floor(entry.hue * 0xFFFF), + "access_level": entry.access_level, + "event_object": entry.event_object, + }, + [ + x => lib_plankton.json.encode(x), + ] + ); } @@ -108,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 ( @@ -200,656 +288,501 @@ namespace _dali.widgets.weekview /** - * @todo kein "while" */ - private async calendar_view_table_data( - calendar_ids : ( - null - | - Array<_dali.type.calendar_id> - ), - from : { - year : int; - week : int; - }, - to : { - year : int; - week : int; - }, - timezone_shift : int - ) : Promise< + private async get_entries_wrapped( { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - > + "calendar_ids": calendar_ids = null, + "timezone_shift": timezone_shift = 0, } - > + : + { + calendar_ids ?: (null | Array<_dali.type_calendar_id>); + timezone_shift ?: int; + } + = + { + } + ) + : Promise> { - const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); - const from_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( - { - "year": (from as {year : int; week : int}).year, - "week": (from as {year : int; week : int}).week, - "day": 1, - }, - { - "timezone_shift": (timezone_shift as int), - } - ); - const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( - { - "year": (to as {year : int; week : int}).year, - "week": (to as {year : int; week : int}).week, - "day": 1, - }, - { - "timezone_shift": (timezone_shift as int), - } - ); - - // prepare - const entries : Array = await this.get_entries( - from_pit, - to_pit, + const entries = await this.get_entries( + lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": this.week, + "day": 1, + }, + { + "timezone_shift": timezone_shift, + } + ), + lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": (this.week + this.count), + "day": 1, + }, + { + "timezone_shift": timezone_shift, + } + ), calendar_ids ); - let result : { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - >; - } = { - "sources": lib_plankton.map.hashmap.implementation_map( - lib_plankton.map.hashmap.make( - x => x.toFixed(0), - { - "pairs": ( - entries - .map( - (entry) => ( - { - "key": entry.calendar_id, - "value": { - "name": entry.calendar_name, - "access_level": entry.access_level, - "hue": entry.hue, - } - } - ) - ) - ) - } - ) - ), - "rows": [], - }; - let row : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; + 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); } - > = []; - let day : int = 0; - while (true) { - const pit_current : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day( - from_pit, - day - ); - if ( - lib_plankton.pit.is_before( - pit_current, - to_pit - ) - ) { - day += 1; - row.push( - { - "pit": pit_current, - "entries": [], - "today": false, // TODO - } - ); - if (day % 7 === 0) { - result.rows.push( - { - "week": ( - (from as {year : int; week : int}).week - + - Math.floor(day / 7) - - - 1 // TODO - ), - "data": row - } - ); - row = []; - } - else { - // do nothing - } - } - else { - break; - } - } - - // fill - { - // events - ( - entries - .forEach( - (entry) => { - const distance_seconds : int = ( - lib_plankton.pit.from_datetime( - /** - * so that events without a start time will be put in the correct box - */ - { - "timezone_shift": entry.event_object.begin.timezone_shift, - "date": entry.event_object.begin.date, - "time": ( - entry.event_object.begin.time - ?? - { - "hour": 12, - "minute": 0, - "second": 0 - } - ), - } - ) - - - from_pit - ); - const distance_days : int = (distance_seconds / (60 * 60 * 24)); - - const week : int = Math.floor(Math.floor(distance_days) / 7); - const day : int = (Math.floor(distance_days) % 7); - - if ((week >= 0) && (week < result.rows.length)) { - result.rows[week].data[day].entries.push(entry); - } - else { - // do nothing - } - } - ) - ); - // today - { - const distance_seconds : int = ( - now_pit - - - from_pit - ); - const distance_days : int = (distance_seconds / (60 * 60 * 24)); - - const week : int = Math.floor(Math.floor(distance_days) / 7); - const day : int = (Math.floor(distance_days) % 7); - - if ((week >= 0) && (week < result.rows.length)) { - result.rows[week].data[day].today = true; - } - else { - // do nothing - } - } - } - - return Promise.resolve(result); + ); + return entries; } /** */ - private async table_rows( - options : { - calendar_ids ?: ( - null - | - Array<_dali.type.calendar_id> - ); - from ?: { - year : int; - week : int; - }; - to ?: { - year : int; - week : int; - }; - timezone_shift ?: int; - action_select ?: ( - ( - calendar_id : _dali.type.calendar_id, - event_id : _dali.type.local_resource_event_id - ) - => - void - ) - } = {} - ) : Promise + private async entry_insert( + entry : type_entry + ) : Promise<(null | HTMLElement)> { - const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); - options = Object.assign( + const selector : string = lib_plankton.string.coin( + ".weekview-cell[rel=\"{{rel}}\"] > .weekview-events", { - "calendar_ids": null, - "from": lib_plankton.call.convey( - now_pit, - [ - (x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, -1), - lib_plankton.pit.to_ywd, - x => ({"year": x.year, "week": x.week}), - ] - ), - "to": lib_plankton.call.convey( - now_pit, - [ - (x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, +4), - lib_plankton.pit.to_ywd, - x => ({"year": x.year, "week": x.week}), - ] - ), - "timezone_shift": 0, - }, - options - ); - const stuff : { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - >; - } = await this.calendar_view_table_data( - options.calendar_ids, - options.from, - options.to, - options.timezone_shift - ); - const sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - color : lib_plankton.color.type_color; + "rel": lib_plankton.pit.date_format(entry.event_object.begin.date), } - > = lib_plankton.map.hashmap.implementation_map( - lib_plankton.map.hashmap.make( - (x => x.toFixed(0)), - { - "pairs": ( - lib_plankton.map.dump( - stuff.sources - ) - .map( - (pair) => ({ - "key": pair.key, - "value": { - "name": pair.value.name, - "access_level": pair.value.access_level, - "color": lib_plankton.color.make_hsv( - { - "hue": pair.value.hue, - "saturation": 0.375, - "value": 0.375, - } - ), - } - }) - ) - ) - } - ) ); - return ( - await _dali.helpers.promise_row( - stuff.rows - .map( - (row) => async () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-row", + const dom_cell = this.container.querySelector(selector); + if (dom_cell === null) + { + lib_plankton.log.debug( + "dali.widget.weekview.entry_insert.out_of_scope", + { + "entry": entry, + } + ); + return null; + } + else + { + let dom_dummy : HTMLElement = document.createElement("div"); + dom_dummy.innerHTML = await _dali.helpers.template_coin( + "widget-weekview", + "tableview-cell-entry", + { + "color": lib_plankton.color.output_hex( + lib_plankton.color.make_hsv( + { + "hue": entry.hue, + /** + * @todo as constant + */ + "saturation": 0.375, + /** + * @todo as constant + */ + "value": 0.375, + } + ) + ), + "title": class_widget_weekview.event_generate_tooltip( + entry.calendar_name, + entry.event_object + ), + "name": entry.event_object.name, + "rel": entry.key, + "additional_classes": lib_plankton.string.coin( + " access_level-{{access_level}}", { - "week": row.week.toFixed(0).padStart(2, "0"), - "cells": ( - await _dali.helpers.promise_row( - row.data - .map( - (cell) => async () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell", - { - "extra_classes": ( - [""] - .concat(cell.today ? ["weekview-cell-today"] : []) - .join(" ") - ), - "title": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{year}}-{{month}}-{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ), - ] - ), - "day": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ), - ] - ), - "rel": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{year}}-{{month}}-{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ) - ] - ), - "entries": ( - await _dali.helpers.promise_row( - cell.entries - .map( - (entry) => () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell-entry", - { - "color": lib_plankton.color.output_hex( - sources.get( - entry.calendar_id - ).color - ), - "title": class_widget_weekview.event_generate_tooltip( - sources.get( - entry.calendar_id - ).name, - entry.event_object - ), - "name": entry.event_object.name, - "rel": lib_plankton.string.coin( - "{{calendar_id}}/{{event_id}}/{{access_level}}", - { - "calendar_id": entry.calendar_id.toFixed(0), - "event_id": ( - (entry.event_id === null) - ? - "-" - : - entry.event_id.toFixed(0) - ), - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - "additional_classes": lib_plankton.string.coin( - " access_level-{{access_level}}", - { - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - } - ) - ) - ) - ).join(""), - } - ) - ) - ) - ).join(""), + "access_level": _dali.access_level_encode(entry.access_level), } - ) - ) - ) - ).join(""); + ), + } + ); + const dom_entry : HTMLElement = dom_dummy.querySelector(".weekview-event_entry"); + + // listener + dom_entry.addEventListener( + "click", + (event) => { + const rel : string = dom_entry.getAttribute("rel"); + const event_key : _dali.type_event_key = rel; + this.action_select_event( + event_key + ); + } + ); + + // emplace + dom_cell.appendChild(dom_entry); + + return dom_entry; + } } /** */ - private async update( - year : int, - week : int, - count : int, - options : { - update_controls ?: boolean; - } = {} + private async entry_add( + entry : type_entry ) : Promise { - options = Object.assign( - { - "update_controls": true, - }, - options - ); - const context : Element = this.container; - // controls + const dom_entry : (null | HTMLElement) = await this.entry_insert(entry); + if (dom_entry === null) { - if (! options.update_controls) { - // do nothing - } - else { - (context.querySelector(".weekview-control-year > input") as HTMLInputElement).value = year.toFixed(0); - (context.querySelector(".weekview-control-week > input") as HTMLInputElement).value = week.toFixed(0); - (context.querySelector(".weekview-control-count > input") as HTMLInputElement).value = count.toFixed(0); - } + // do nothing } - // table + else { - context.querySelector(".weekview-table tbody").innerHTML = await this.table_rows( + this.event_map.set( + entry.key, { - "calendar_ids": null, - // TODO - "from": { - "year": year, - "week": week - }, - // TODO - "to": { - "year": year, - "week": (week + count) - }, - "timezone_shift": /*conf.timezone_shift*/0, + "element": dom_entry, + "hash": class_widget_weekview.entry_hash(entry), } ); - // cells + } + } + + + /** + */ + private async entry_update( + key : _dali.type_event_key, + entry : type_entry + ) : Promise + { + if (! this.event_map.has(key)) + { + lib_plankton.log.warning( + "dali.widget.weekview.entry_update.event_missing", + { + "key": key, + } + ); + } + else + { + const value = this.event_map.get(key); + const hash_old : string = value.hash; + const hash_new : string = class_widget_weekview.entry_hash(entry); + if (hash_old === hash_new) { - if (! await _dali.backend.is_logged_in()) { - // do nothing - } - else { - context.querySelectorAll(".weekview-cell-regular").forEach( - (element) => { - element.addEventListener( - "click", - (event) => { - if (! (element === event.target)) { - // do nothing - } - else { - const rel : string = element.getAttribute("rel"); - const parts : Array = rel.split("-"); - const date : lib_plankton.pit.type_date = { - "year": parseInt(parts[0]), - "month": parseInt(parts[1]), - "day": parseInt(parts[2]), - }; - this.action_select_day(date); - } - } - ); - } - ); - } + // do nothing + lib_plankton.log.debug( + "dali.widget.weekview.entry_update.nothing_to_update", + { + "key": key, + "entry": entry, + "element": value.element, + } + ); } - // events + else { - if (! await _dali.backend.is_logged_in()) { + const dom_entry_old : HTMLElement = value.element; + dom_entry_old.remove(); + const dom_entry_new : (null | HTMLElement) = await this.entry_insert(entry); + if (dom_entry_new === null) + { // do nothing } - else { - context.querySelectorAll(".weekview-event_entry").forEach( - (element) => { - element.addEventListener( - "click", - () => { - const rel : string = element.getAttribute("rel"); - const parts : Array = rel.split("/"); - const calendar_id : _dali.type.calendar_id = parseInt(parts[0]); - const event_id : (null | _dali.type.local_resource_event_id) = ( - (parts[1] === "-") - ? - null - : - parseInt(parts[1]) - ); - const access_level : _dali.type.enum_access_level = (() => { - switch (parts[2]) { - case "none": return _dali.type.enum_access_level.none; - case "view": return _dali.type.enum_access_level.view; - case "edit": return _dali.type.enum_access_level.edit; - case "admin": return _dali.type.enum_access_level.admin; - } - }) (); - this.action_select_event( - calendar_id, - access_level, - event_id - ); - } - ); + else + { + this.event_map.set( + entry.key, + { + "element": dom_entry_new, + "hash": hash_new, } ); } } } - return Promise.resolve(undefined); + } + + + /** + */ + private async entry_remove( + key : _dali.type_event_key + ) : Promise + { + if (! this.event_map.has(key)) + { + // do nothing + lib_plankton.log.warning( + "dali.widget.weekview.entry_remove.not_in_map", + { + "key": key, + "pairs": lib_plankton.map.dump(this.event_map), + } + ); + } + else + { + const value = this.event_map.get( + key + ); + this.event_map.delete( + key + ); + value.element.remove(); + } + } + + + /** + */ + public async update_entries( + ) : Promise + { + const entries : Array = await this.get_entries_wrapped( + ); + + const contrast = lib_plankton.list.contrast< + any, + type_entry + >( + lib_plankton.map.dump(this.event_map), + pair => pair.key, + entries, + event => event.key + ); + await Promise.all( + [] + // remove + .concat( + contrast.only_left.map( + ({"key": key, "left": left}) => this.entry_remove(key) + ) + ) + // update + .concat( + contrast.both.map( + ({"key": key, "left": left, "right": right}) => this.entry_update(key, right) + ) + ) + // add + .concat( + contrast.only_right.map( + ({"key": key, "right": right}) => this.entry_add(right) + ) + ) + ); + } + + + /** + */ + private async update_controls( + ) : Promise + { + const context : Element = this.container; + (context.querySelector(".weekview-control-year > input") as HTMLInputElement).value = this.year.toFixed(0); + (context.querySelector(".weekview-control-week > input") as HTMLInputElement).value = this.week.toFixed(0); + (context.querySelector(".weekview-control-count > input") as HTMLInputElement).value = this.count.toFixed(0); + } + + + /** + */ + private async update_table( + ) : Promise + { + /** + * @todo avoid? + */ + lib_plankton.map.clear(this.event_map); + const context : Element = this.container; + // structure + { + /** + * @todo als Variable? + */ + const timezone_shift : int = 0; + const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); + const today_begin_pit : lib_plankton.pit.type_pit = lib_plankton.pit.trunc_day(now_pit); + const today_end_pit : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day(today_begin_pit, 1); + const row_data : Array< + { + week : int; + data : Array< + { + pit : lib_plankton.pit.type_pit; + today : boolean; + } + >; + } + > = ( + lib_plankton.list.sequence(this.count) + .map( + offset => { + const week : int = (this.week + offset); + return { + "week": week, + "data": ( + lib_plankton.list.sequence(7) + .map( + day => { + const day_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": week, + "day": (day + 1), + }, + { + "timezone_shift": timezone_shift, + } + ); + return { + "pit": day_pit, + "today": lib_plankton.pit.is_between( + day_pit, + today_begin_pit, + today_end_pit + ), + }; + } + ) + ), + }; + } + ) + ); + context.querySelector(".weekview-table tbody").innerHTML = ( + await _dali.helpers.promise_row( + row_data + .map( + (row) => async () => _dali.helpers.template_coin( + "widget-weekview", + "tableview-row", + { + "week": row.week.toFixed(0).padStart(2, "0"), + "cells": ( + await _dali.helpers.promise_row( + row.data + .map( + (cell) => async () => _dali.helpers.template_coin( + "widget-weekview", + "tableview-cell", + { + "extra_classes": ( + [""] + .concat(cell.today ? ["weekview-cell-today"] : []) + .join(" ") + ), + "title": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{year}}-{{month}}-{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ), + ] + ), + "day": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ), + ] + ), + "rel": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{year}}-{{month}}-{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ) + ] + ), + "entries": "" + } + ) + ) + ) + ).join(""), + } + ) + ) + ) + ).join(""); + } + // listeners + { + context.querySelectorAll(".weekview-cell-regular").forEach( + (element) => { + element.addEventListener( + "click", + (event) => { + if (! (element === event.target)) + { + // do nothing + } + else + { + const rel : string = element.getAttribute("rel"); + const parts : Array = rel.split("-"); + const date : lib_plankton.pit.type_date = { + "year": parseInt(parts[0]), + "month": parseInt(parts[1]), + "day": parseInt(parts[2]), + }; + this.action_select_day(date); + } + } + ); + } + ); + } } /** */ public toggle_visibility( - calendar_id : _dali.type.calendar_id + calendar_id : _dali.type_calendar_id, + { + "mode": mode = null, + } + : + { + mode ?: (null | boolean); + } + = + { + } ) : void { this.container.querySelectorAll(".weekview-event_entry").forEach( (element) => { const rel : string = element.getAttribute("rel"); const parts : Array = rel.split("/"); - const calendar_id_ : _dali.type.calendar_id = parseInt(parts[0]); - if (! (calendar_id === calendar_id_)) { + const calendar_id_ : _dali.type_calendar_id = parseInt(parts[0]); + if (! (calendar_id === calendar_id_)) + { // do nothing } - else { - element.classList.toggle("weekview-cell-hidden"); + else + { + element.classList.toggle("weekview-cell-hidden", mode ?? undefined); } } ); @@ -883,39 +816,43 @@ namespace _dali.widgets.weekview this.container = target_element.querySelector(".weekview"); // controls { - target_element.querySelector(".weekview-control-apply").addEventListener( - "click", - (event) => { - event.preventDefault(); - const year : int = parseInt((target_element.querySelector(".weekview-control-year > input") as HTMLInputElement).value); - const week : int = parseInt((target_element.querySelector(".weekview-control-week > input") as HTMLInputElement).value); - const count : int = parseInt((target_element.querySelector(".weekview-control-count > input") as HTMLInputElement).value); - this.update( - year, - week, - count, - { - "update_controls": false, + [ + { + "name": "year", + "transform": parseInt, + "write": x => {this.year = x;} + }, + { + "name": "week", + "transform": parseInt, + "write": x => {this.week = x;} + }, + { + "name": "count", + "transform": parseInt, + "write": x => {this.count = x;} + }, + ].forEach( + (entry) => { + const selector : string = (".weekview-control-" + entry.name + " > input"); + const element : HTMLInputElement = (target_element.querySelector(selector) as HTMLInputElement); + element.addEventListener( + "change", + async (event) => { + event.preventDefault(); + const value : int = entry.transform(element.value); + entry.write(value); + await this.update_table(); + await this.update_entries(); } ); } ); } - // table - { - const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now()); - let year : int = ywd_now.year; - let week : int = Math.max(0, (ywd_now.week - 1)); - let count : int = 5; - await this.update( - year, - week, - count, - { - "update_controls": true, - } - ); - } + await this.update_controls(); + await this.update_table(); + await this.update_entries(); + return Promise.resolve(undefined); } diff --git a/source/widgets/weekview/templates/main.html.tpl b/source/widgets/weekview/templates/main.html.tpl index 0ebb5f0..30a6ee5 100644 --- a/source/widgets/weekview/templates/main.html.tpl +++ b/source/widgets/weekview/templates/main.html.tpl @@ -12,7 +12,6 @@ {{label_control_count}} -
diff --git a/tools/makefile b/tools/makefile index 3195a2b..45634f4 100644 --- a/tools/makefile +++ b/tools/makefile @@ -32,17 +32,22 @@ ${dir_build}/index.html: \ .PHONY: templates templates: \ + templates-widgets-login \ templates-widgets-sources \ templates-widgets-listview \ templates-widgets-weekview \ templates-widgets-mode_switcher \ + templates-widgets-calendar_edit \ + templates-widgets-event_edit \ templates-pages-caldav \ - templates-pages-calendar_add \ - templates-pages-calendar_edit \ - templates-pages-event_add \ - templates-pages-event_edit \ - templates-pages-overview \ - templates-pages-login + 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: \ @@ -72,6 +77,20 @@ templates-widgets-mode_switcher: \ @ ${cmd_mkdir} ${dir_build}/templates/widget-mode_switcher @ ${cmd_cp} -r -u -v ${dir_source}/widgets/mode_switcher/templates/* ${dir_build}/templates/widget-mode_switcher/ +.PHONY: templates-widgets-calendar_edit +templates-widgets-calendar_edit: \ + $(wildcard ${dir_source}/widgets/calendar_edit/templates/*) + @ ${cmd_log} "templates:widgets:calendar_edit …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-calendar_edit + # @ ${cmd_cp} -r -u -v ${dir_source}/widgets/calendar_edit/templates/* ${dir_build}/templates/widget-calendar_edit/ + +.PHONY: templates-widgets-event_edit +templates-widgets-event_edit: \ + $(wildcard ${dir_source}/pages/event_edit/templates/*) + @ ${cmd_log} "templates:widgets:event_edit …" + @ ${cmd_mkdir} ${dir_build}/templates/widget-event_edit + # @ ${cmd_cp} -r -u -v ${dir_source}/pages/event_edit/templates/* ${dir_build}/templates/widget-uevent_edit/ + .PHONY: templates-pages-caldav templates-pages-caldav: \ $(wildcard ${dir_source}/pages/caldav/templates/*) @@ -79,34 +98,6 @@ templates-pages-caldav: \ @ ${cmd_mkdir} ${dir_build}/templates/caldav @ ${cmd_cp} -r -u -v ${dir_source}/pages/caldav/templates/* ${dir_build}/templates/caldav/ -.PHONY: templates-pages-calendar_add -templates-pages-calendar_add: \ - $(wildcard ${dir_source}/pages/calendar_add/templates/*) - @ ${cmd_log} "templates:calendar_add …" - @ ${cmd_mkdir} ${dir_build}/templates/calendar_add - @ ${cmd_cp} -r -u -v ${dir_source}/pages/calendar_add/templates/* ${dir_build}/templates/calendar_add/ - -.PHONY: templates-pages-calendar_edit -templates-pages-calendar_edit: \ - $(wildcard ${dir_source}/pages/calendar_edit/templates/*) - @ ${cmd_log} "templates:calendar_edit …" - @ ${cmd_mkdir} ${dir_build}/templates/calendar_edit - @ ${cmd_cp} -r -u -v ${dir_source}/pages/calendar_edit/templates/* ${dir_build}/templates/calendar_edit/ - -.PHONY: templates-pages-event_add -templates-pages-event_add: \ - $(wildcard ${dir_source}/pages/event_add/templates/*) - @ ${cmd_log} "templates:event_add …" - @ ${cmd_mkdir} ${dir_build}/templates/event_add - @ ${cmd_cp} -r -u -v ${dir_source}/pages/event_add/templates/* ${dir_build}/templates/event_add/ - -.PHONY: templates-pages-event_edit -templates-pages-event_edit: \ - $(wildcard ${dir_source}/pages/event_edit/templates/*) - @ ${cmd_log} "templates:event_edit …" - @ ${cmd_mkdir} ${dir_build}/templates/event_edit - @ ${cmd_cp} -r -u -v ${dir_source}/pages/event_edit/templates/* ${dir_build}/templates/event_edit/ - .PHONY: templates-pages-overview templates-pages-overview: \ $(wildcard ${dir_source}/pages/overview/templates/*) @@ -114,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/*) @@ -133,24 +117,23 @@ logic: ${dir_build}/logic.js ${dir_temp}/logic-unlinked.js: \ ${dir_lib}/plankton/plankton.d.ts \ - ${dir_source}/base/helpers.ts \ - ${dir_source}/base/widget.ts \ - ${dir_source}/base/types.ts \ - ${dir_source}/base/functions.ts \ ${dir_source}/resources/conf.ts \ ${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 \ ${dir_source}/widgets/mode_switcher/logic.ts \ - ${dir_source}/pages/login/logic.ts \ - ${dir_source}/pages/logout/logic.ts \ + ${dir_source}/widgets/calendar_edit/logic.ts \ + ${dir_source}/widgets/event_edit/logic.ts \ + ${dir_source}/overlay.ts \ ${dir_source}/pages/caldav/logic.ts \ ${dir_source}/pages/oidc_finish/logic.ts \ - ${dir_source}/pages/calendar_add/logic.ts \ - ${dir_source}/pages/calendar_edit/logic.ts \ - ${dir_source}/pages/event_add/logic.ts \ - ${dir_source}/pages/event_edit/logic.ts \ ${dir_source}/pages/overview/logic.ts \ ${dir_source}/main.ts @ ${cmd_log} "logic | compile …" diff --git a/tools/update-plankton b/tools/update-plankton index 7577b00..665794d 100755 --- a/tools/update-plankton +++ b/tools/update-plankton @@ -14,6 +14,8 @@ modules="${modules} json" modules="${modules} string" modules="${modules} random" modules="${modules} map" +modules="${modules} set" +modules="${modules} cache" modules="${modules} color" # modules="${modules} xml" modules="${modules} map" @@ -23,6 +25,7 @@ modules="${modules} url" modules="${modules} pit" modules="${modules} www_form" modules="${modules} translate" +modules="${modules} zoo-widget" modules="${modules} zoo-page" modules="${modules} zoo-form" modules="${modules} zoo-input"