From 4612128134084da9ef55f1cf69b8a1eafd8f9d9d Mon Sep 17 00:00:00 2001 From: Fenris Wolf Date: Tue, 14 Oct 2025 22:10:56 +0200 Subject: [PATCH] [task-408] [mod] Wochenansich-Steuerelement umgestellt --- source/base/functions.ts | 35 +- source/base/types.ts | 7 +- source/pages/overview/logic.ts | 31 +- source/resources/backend.ts | 67 +- source/widgets/weekview/logic.ts | 1468 +++++++---------- .../widgets/weekview/templates/main.html.tpl | 1 - 6 files changed, 676 insertions(+), 933 deletions(-) diff --git a/source/base/functions.ts b/source/base/functions.ts index 274127d..a4721c9 100644 --- a/source/base/functions.ts +++ b/source/base/functions.ts @@ -1,7 +1,38 @@ namespace _dali { - + /** + */ + export function access_level_encode( + access_level : _dali.type.enum_access_level + ) : ("none" | "view" | "edit" | "admin") + { + switch (access_level) { + case _dali.type.enum_access_level.none: return "none"; + case _dali.type.enum_access_level.view: return "view"; + case _dali.type.enum_access_level.edit: return "edit"; + case _dali.type.enum_access_level.admin: return "admin"; + } + } + + + /** + */ + export function access_level_decode( + representation : /*("none" | "view" | "edit" | "admin")*/string + ) : _dali.type.enum_access_level + { + switch (representation) + { + case "none": return _dali.type.enum_access_level.none; + case "view": return _dali.type.enum_access_level.view; + case "edit": return _dali.type.enum_access_level.edit; + case "admin": return _dali.type.enum_access_level.admin; + default: throw (new Error("invalid access level representation: " + representation)); + } + } + + /** */ export function view_mode_encode( @@ -58,6 +89,6 @@ namespace _dali { return view_mode_decode(mode_descriptor); } - } + } } diff --git a/source/base/types.ts b/source/base/types.ts index e97b1d9..ac35b84 100644 --- a/source/base/types.ts +++ b/source/base/types.ts @@ -42,9 +42,9 @@ namespace _dali.type * extern eingebundene Events kodiert * * @example "local:1234" - * @example "ics:2345" + * @example "ics~2345" */ - export type event_id = string; + export type event_key = string; /** @@ -78,7 +78,8 @@ namespace _dali.type /** */ export type event_entry = { - id : event_id; + id : (null | local_resource_event_id); + key : event_key; object : event_object; }; diff --git a/source/pages/overview/logic.ts b/source/pages/overview/logic.ts index e798974..94bbb20 100644 --- a/source/pages/overview/logic.ts +++ b/source/pages/overview/logic.ts @@ -17,13 +17,6 @@ namespace _dali.pages.overview target_element.querySelector("#overview").classList.toggle("overview-compact", compact); }; - /** - * @todo geschickter bauen (MVC, MVVM, …) - */ - const update = () => { - lib_plankton.zoo_page.reload(); - }; - // exec target_element.innerHTML = await _dali.helpers.template_coin( "overview", @@ -66,6 +59,12 @@ namespace _dali.pages.overview let widget_weekview : _dali.widgets.weekview.class_widget_weekview; let widget_listview : _dali.widgets.listview.class_widget_listview; + /** + * @todo update sources + */ + const update = () => { + widget_weekview.update_entries(); + }; // hint { if (! await _dali.backend.is_logged_in()) @@ -176,6 +175,15 @@ namespace _dali.pages.overview } ); const action_select_event = (calendar_id, access_level, event_id) => { + /* + if (! await _dali.backend.is_logged_in()) + { + // do nothing + } + else + { + } + */ let read_only : boolean; switch (access_level) { @@ -304,6 +312,15 @@ namespace _dali.pages.overview { "action_select_event": action_select_event, "action_select_day": (date) => { + /* + if (! await _dali.backend.is_logged_in()) + { + // do nothing + } + else + { + } + */ (async () => { const widget = new _dali.widgets.event_edit.class_widget_event_edit( { diff --git a/source/resources/backend.ts b/source/resources/backend.ts index 9991f92..c0f6377 100644 --- a/source/resources/backend.ts +++ b/source/resources/backend.ts @@ -14,6 +14,16 @@ namespace _dali.backend /** */ + var _cache : ( + null + | + lib_plankton.cache.type_subject + ); + + + /** + * meant for translation of the API values + */ function access_level_encode( access_level : _dali.type.enum_access_level ) : ("none" | "view" | "edit" | "admin") @@ -28,6 +38,7 @@ namespace _dali.backend /** + * meant for translation of the API values */ function access_level_decode( access_level_encoded : ("none" | "view" | "edit" | "admin") @@ -52,6 +63,14 @@ namespace _dali.backend "corner": "zeitbild", } ); + _cache = lib_plankton.cache.make( + /* + lib_plankton.storage.memory.implementation_chest( + { + } + ) + */ + ); return Promise.resolve(undefined); } @@ -162,19 +181,27 @@ namespace _dali.backend /** + * @todo mneh … */ export async function is_logged_in( ) : Promise { - // return ((await get_session_key()) !== null); - const result : { - logged_in : boolean; - } = await call( - lib_plankton.http.enum_method.get, - "/session/status", - null + return lib_plankton.cache.get( + _cache, + "status", + 5, + async () => { + // return ((await get_session_key()) !== null); + const result : { + logged_in : boolean; + } = await call( + lib_plankton.http.enum_method.get, + "/session/status", + null + ); + return result.logged_in; + } ); - return result.logged_in; } @@ -596,12 +623,20 @@ namespace _dali.backend export async function events( from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, - options : { + { + "calendar_ids": calendar_ids = null, + } + : + { calendar_ids ?: (null | Array<_dali.type.calendar_id>); - } = {} + } + = + { + } ) : Promise< Array< { + key : _dali.type.event_key; calendar_id : _dali.type.calendar_id; calendar_name : string; hue : float; @@ -612,13 +647,6 @@ namespace _dali.backend > > { - options = Object.assign( - { - "calendar_ids": null, - }, - options - ); - return ( call( lib_plankton.http.enum_method.get, @@ -629,11 +657,11 @@ namespace _dali.backend "to": to_pit, }, ( - (options.calendar_ids === null) + (calendar_ids === null) ? {} : - {"calendar_ids": options.calendar_ids.join(",")} + {"calendar_ids": calendar_ids.join(",")} ) ) ) @@ -642,6 +670,7 @@ namespace _dali.backend data .map( (entry) => ({ + "key": entry.hash, "calendar_id": entry.calendar_id, "calendar_name": entry.calendar_name, "hue": entry.hue, diff --git a/source/widgets/weekview/logic.ts b/source/widgets/weekview/logic.ts index a6265a5..457d9e9 100644 --- a/source/widgets/weekview/logic.ts +++ b/source/widgets/weekview/logic.ts @@ -4,6 +4,7 @@ namespace _dali.widgets.weekview /** */ type type_entry = { + key : _dali.type.event_key; calendar_id : _dali.type.calendar_id; calendar_name : string; hue : float; @@ -32,24 +33,13 @@ namespace _dali.widgets.weekview { /** + * [dependency] */ private get_entries : type_get_entries; /** - */ - private container : (null | Element); - - - /** - */ - private event_map : lib_plankton.map.type_map< - _dali.type.event_id, - HTMLElement - >; - - - /** + * [hook] */ private action_select_event : ( ( @@ -63,6 +53,7 @@ namespace _dali.widgets.weekview /** + * [hook] */ private action_select_day : ( ( @@ -73,11 +64,55 @@ namespace _dali.widgets.weekview ); + /** + * [state] + */ + private year : int; + + + /** + * [state] + */ + private week : int; + + + /** + * [state] + */ + private count : int; + + + /** + * [state] + */ + private event_map : lib_plankton.map.type_map< + _dali.type.event_key, + { + element : HTMLElement; + hash : string; + } + >; + + + /** + * [state] + */ + private container : (null | Element); + + /** */ public constructor( get_entries : type_get_entries, - options : { + { + "action_select_day": action_select_day = ((date) => {}), + "action_select_event": action_select_event = ((calendar_id, access_level, event_id) => {}), + "initial_year": initial_year = null, + "initial_week": initial_week = null, + "initial_count": initial_count = 5, + } + : + { action_select_event ?: ( ( calendar_id : _dali.type.calendar_id, @@ -94,28 +129,70 @@ namespace _dali.widgets.weekview => void ); - } = {} + initial_year ?: (null | int); + initial_week ?: (null | int); + initial_count ?: int; + } + = + {} ) { - options = Object.assign( - { - "action_select_day": (date) => {}, - "action_select_event": (calendar_id, access_level, event_id) => {}, - }, - options - ); + // dependencies this.get_entries = get_entries; - this.container = null; + + // hooks + this.action_select_day = action_select_day; + this.action_select_event = action_select_event; + + // state + const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now()); + this.year = ( + initial_year + ?? + ywd_now.year + ); + this.week = ( + initial_week + ?? + Math.max(0, (ywd_now.week - 1)) + ); + this.count = initial_count; this.event_map = lib_plankton.map.hashmap.implementation_map( lib_plankton.map.hashmap.make< - _dali.type.event_id, - HTMLElement + _dali.type.event_key, + { + element : HTMLElement; + hash : string; + } >( - event_id => event_id + event_key => event_key ) ); - this.action_select_day = options.action_select_day; - this.action_select_event = options.action_select_event; + this.container = null; + } + + + /** + * some kind of checksum for comparing entries + * @todo base64 encode? + * @todo sha256 hash? + */ + private static entry_hash( + entry : type_entry + ) : string + { + return lib_plankton.call.convey( + { + "calendar_id": entry.calendar_id, + "calendar_name": entry.calendar_name, + "hue": Math.floor(entry.hue * 0xFFFF), + "access_level": entry.access_level, + "event_object": entry.event_object, + }, + [ + x => lib_plankton.json.encode(x), + ] + ); } @@ -216,885 +293,479 @@ namespace _dali.widgets.weekview /** */ - private create_dom_entry( - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - /** - * @todo replace with hue? - */ - color : lib_plankton.color.type_color; - } - >, - // event_entry : _dali.type.event_entry - entry : type_entry - ) : HTMLElement - { - return _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell-entry", - { - "color": lib_plankton.color.output_hex( - sources.get( - entry.calendar_id - ).color - ), - "title": class_widget_weekview.event_generate_tooltip( - sources.get( - entry.calendar_id - ).name, - entry.event_object - ), - "name": entry.event_object.name, - "rel": lib_plankton.string.coin( - "{{calendar_id}}/{{event_id}}/{{access_level}}", - { - "calendar_id": entry.calendar_id.toFixed(0), - "event_id": ( - (entry.event_id === null) - ? - "-" - : - entry.event_id.toFixed(0) - ), - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) - { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - "additional_classes": lib_plankton.string.coin( - " access_level-{{access_level}}", - { - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) - { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - } - ); - } - - - /** - */ - private add_entry( - event_entry : _dali.type.event_entry - ) : void - { - const dom_entry : HTMLElement = this.create_dom_entry(event_entry); - /** - * @todo insert dom_entry into DOM - */ - this.event_map.set( - event_entry.id, - dom_entry - ); - } - - - /** - */ - private update_entry( - event_id : _dali.type.event_id, - event_entry : _dali.type.event_entry - ) : void - { - const dom_entry_old : HTMLElement = lib_plankton.map.get( - this.event_map, - event_id - ); - /** - * @todo remove dom_entry_old from DOM - */ - const dom_entry_new : HTMLElement = this.create_dom_entry(event_entry); - /** - * @todo insert dom_entry_new into DOM - */ - this.event_map.set( - event_id, - dom_entry_new - ); - } - - - /** - */ - private remove_entry( - event_id : _dali.type.event_id - ) : void - { - const dom_entry : HTMLElement = lib_plankton.map.get( - this.event_map, - event_id - ); - /** - * @todo remove dom_entry from DOM - */ - this.event_map.delete( - event_id - ); - } - - - /** - */ - private sync_events( - events : Array<_dali.type.event_entry> - ) : void - { - const track : Record< - string, - { - external : (null | _dali.type.event_entry); - internal : (null | _dali.type.event_id); - } - > = {}; - events.forEach( - event => { - const key : string = event.id; - if (! (key in track)) track[key] = {"external": null, "internal": null}; - track[key].external = event; - } - ); - lib_plankton.map.dump(this.event_map).forEach( - pair => { - const key : string = pair.key; - if (! (key in track)) track[key] = {"external": null, "internal": null}; - track[key].internal = pair.key; - } - ); - - Object.entries(track).forEach( - ([key, value]) => { - if ( - ! (value.external !== null) - && - ! (value.internal !== null) - ) - { - throw (new Error("impossible!")); - } - else if ( - (value.external !== null) - && - ! (value.internal !== null) - ) - { - this.add_entry(value.external); - } - else if ( - (value.external !== null) - && - (value.internal !== null) - ) - { - this.update_entry(value.internal, value.external); - } - else if ( - ! (value.external !== null) - && - (value.internal !== null) - ) - { - this.remove_entry(value.external.id); - } - else - { - throw (new Error("impossible!")); - } - } - ); - } - - - /** - * @todo kein "while" - */ - private async calendar_view_table_data( - calendar_ids : ( - null - | - Array<_dali.type.calendar_id> - ), - from : { - year : int; - week : int; - }, - to : { - year : int; - week : int; - }, - timezone_shift : int - ) : Promise< + private get_entries_wrapped( { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - > + "calendar_ids": calendar_ids = null, + "timezone_shift": timezone_shift = 0, } - > + : + { + calendar_ids ?: (null | Array<_dali.type.calendar_id>); + timezone_shift ?: int; + } + = + { + } + ) + : Promise> { - const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); - const from_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( - { - "year": (from as {year : int; week : int}).year, - "week": (from as {year : int; week : int}).week, - "day": 1, - }, - { - "timezone_shift": (timezone_shift as int), - } - ); - const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( - { - "year": (to as {year : int; week : int}).year, - "week": (to as {year : int; week : int}).week, - "day": 1, - }, - { - "timezone_shift": (timezone_shift as int), - } - ); - - // prepare - const entries : Array = await this.get_entries( - from_pit, - to_pit, + return this.get_entries( + lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": this.week, + "day": 1, + }, + { + "timezone_shift": timezone_shift, + } + ), + lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": (this.week + this.count), + "day": 1, + }, + { + "timezone_shift": timezone_shift, + } + ), calendar_ids ); - let result : { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - >; - } = { - "sources": lib_plankton.map.hashmap.implementation_map( - lib_plankton.map.hashmap.make( - x => x.toFixed(0), - { - "pairs": ( - entries - .map( - (entry) => ( - { - "key": entry.calendar_id, - "value": { - "name": entry.calendar_name, - "access_level": entry.access_level, - "hue": entry.hue, - } - } - ) - ) - ) - } - ) - ), - "rows": [], - }; - let row : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - > = []; - let day : int = 0; - while (true) - { - const pit_current : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day( - from_pit, - day - ); - if ( - lib_plankton.pit.is_before( - pit_current, - to_pit - ) - ) - { - day += 1; - row.push( - { - "pit": pit_current, - "entries": [], - "today": false, // TODO - } - ); - if (day % 7 === 0) - { - result.rows.push( - { - "week": ( - (from as {year : int; week : int}).week - + - Math.floor(day / 7) - - - 1 // TODO - ), - "data": row - } - ); - row = []; - } - else - { - // do nothing - } - } - else - { - break; - } - } - - // fill - { - // events - ( - entries - .forEach( - (entry) => { - const distance_seconds : int = ( - lib_plankton.pit.from_datetime( - /** - * so that events without a start time will be put in the correct box - */ - { - "timezone_shift": entry.event_object.begin.timezone_shift, - "date": entry.event_object.begin.date, - "time": ( - entry.event_object.begin.time - ?? - { - "hour": 12, - "minute": 0, - "second": 0 - } - ), - } - ) - - - from_pit - ); - const distance_days : int = (distance_seconds / (60 * 60 * 24)); - - const week : int = Math.floor(Math.floor(distance_days) / 7); - const day : int = (Math.floor(distance_days) % 7); - - if ((week >= 0) && (week < result.rows.length)) { - result.rows[week].data[day].entries.push(entry); - } - else { - // do nothing - } - } - ) - ); - // today - { - const distance_seconds : int = ( - now_pit - - - from_pit - ); - const distance_days : int = (distance_seconds / (60 * 60 * 24)); - - const week : int = Math.floor(Math.floor(distance_days) / 7); - const day : int = (Math.floor(distance_days) % 7); - - if ((week >= 0) && (week < result.rows.length)) { - result.rows[week].data[day].today = true; - } - else { - // do nothing - } - } - } - - return Promise.resolve(result); } /** */ - private async table_rows( - options : { - calendar_ids ?: ( - null - | - Array<_dali.type.calendar_id> - ); - from ?: { - year : int; - week : int; - }; - to ?: { - year : int; - week : int; - }; - timezone_shift ?: int; - action_select ?: ( - ( - calendar_id : _dali.type.calendar_id, - event_id : _dali.type.local_resource_event_id - ) - => - void - ) - } = {} - ) : Promise + private async entry_insert( + entry : type_entry + ) : Promise<(null | HTMLElement)> { - const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); - options = Object.assign( + const selector : string = lib_plankton.string.coin( + ".weekview-cell[rel=\"{{rel}}\"] > .weekview-events", { - "calendar_ids": null, - "from": lib_plankton.call.convey( - now_pit, - [ - (x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, -1), - lib_plankton.pit.to_ywd, - x => ({"year": x.year, "week": x.week}), - ] - ), - "to": lib_plankton.call.convey( - now_pit, - [ - (x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, +4), - lib_plankton.pit.to_ywd, - x => ({"year": x.year, "week": x.week}), - ] - ), - "timezone_shift": 0, - }, - options - ); - const stuff : { - sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - hue : float; - } - >; - rows : Array< - { - week : int; - data : Array< - { - pit : lib_plankton.pit.type_pit; - entries : Array< - { - calendar_id : _dali.type.calendar_id; - event_id : (null | _dali.type.local_resource_event_id); - event_object : _dali.type.event_object; - } - >; - today : boolean; - } - >; - } - >; - } = await this.calendar_view_table_data( - options.calendar_ids, - options.from, - options.to, - options.timezone_shift - ); - const sources : lib_plankton.map.type_map< - _dali.type.calendar_id, - { - name : string; - access_level : _dali.type.enum_access_level; - color : lib_plankton.color.type_color; + "rel": lib_plankton.pit.date_format(entry.event_object.begin.date), } - > = lib_plankton.map.hashmap.implementation_map( - lib_plankton.map.hashmap.make( - (x => x.toFixed(0)), - { - "pairs": ( - lib_plankton.map.dump( - stuff.sources - ) - .map( - (pair) => ({ - "key": pair.key, - "value": { - "name": pair.value.name, - "access_level": pair.value.access_level, - "color": lib_plankton.color.make_hsv( - { - "hue": pair.value.hue, - "saturation": 0.375, - "value": 0.375, - } - ), - } - }) - ) - ) - } - ) ); - return ( - await _dali.helpers.promise_row( - stuff.rows - .map( - (row) => async () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-row", + const dom_cell = this.container.querySelector(selector); + if (dom_cell === null) + { + console.warn("entry out of scope"); + return null; + } + else + { + let dom_dummy : HTMLElement = document.createElement("div"); + dom_dummy.innerHTML = await _dali.helpers.template_coin( + "widget-weekview", + "tableview-cell-entry", + { + "color": lib_plankton.color.output_hex( + lib_plankton.color.make_hsv( + { + "hue": entry.hue, + /** + * @todo as constant + */ + "saturation": 0.375, + /** + * @todo as constant + */ + "value": 0.375, + } + ) + ), + "title": class_widget_weekview.event_generate_tooltip( + entry.calendar_name, + entry.event_object + ), + "name": entry.event_object.name, + "rel": lib_plankton.string.coin( + "{{calendar_id}}/{{event_id}}/{{access_level}}", { - "week": row.week.toFixed(0).padStart(2, "0"), - "cells": ( - await _dali.helpers.promise_row( - row.data - .map( - (cell) => async () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell", - { - "extra_classes": ( - [""] - .concat(cell.today ? ["weekview-cell-today"] : []) - .join(" ") - ), - "title": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{year}}-{{month}}-{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ), - ] - ), - "day": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ), - ] - ), - "rel": lib_plankton.call.convey( - cell.pit, - [ - lib_plankton.pit.to_datetime_ce, - (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( - "{{year}}-{{month}}-{{day}}", - { - "year": x.date.year.toFixed(0).padStart(4, "0"), - "month": x.date.month.toFixed(0).padStart(2, "0"), - "day": x.date.day.toFixed(0).padStart(2, "0"), - } - ) - ] - ), - "entries": ( - await _dali.helpers.promise_row( - cell.entries - .map( - (entry) => () => _dali.helpers.template_coin( - "widget-weekview", - "tableview-cell-entry", - { - "color": lib_plankton.color.output_hex( - sources.get( - entry.calendar_id - ).color - ), - "title": class_widget_weekview.event_generate_tooltip( - sources.get( - entry.calendar_id - ).name, - entry.event_object - ), - "name": entry.event_object.name, - "rel": lib_plankton.string.coin( - "{{calendar_id}}/{{event_id}}/{{access_level}}", - { - "calendar_id": entry.calendar_id.toFixed(0), - "event_id": ( - (entry.event_id === null) - ? - "-" - : - entry.event_id.toFixed(0) - ), - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - "additional_classes": lib_plankton.string.coin( - " access_level-{{access_level}}", - { - "access_level": (() => { - const access_level : _dali.type.enum_access_level = sources.get(entry.calendar_id).access_level; - switch (access_level) { - case _dali.type.enum_access_level.none: return "none"; - case _dali.type.enum_access_level.view: return "view"; - case _dali.type.enum_access_level.edit: return "edit"; - case _dali.type.enum_access_level.admin: return "admin"; - } - }) (), - } - ), - } - ) - ) - ) - ).join(""), - } - ) - ) - ) - ).join(""), + "calendar_id": entry.calendar_id.toFixed(0), + "event_id": ( + (entry.event_id === null) + ? + "-" + : + entry.event_id.toFixed(0) + ), + "access_level": _dali.access_level_encode(entry.access_level), } - ) - ) - ) - ).join(""); + ), + "additional_classes": lib_plankton.string.coin( + " access_level-{{access_level}}", + { + "access_level": _dali.access_level_encode(entry.access_level), + } + ), + } + ); + const dom_entry : HTMLElement = dom_dummy.querySelector(".weekview-event_entry"); + + // listener + dom_entry.addEventListener( + "click", + (event) => { + const rel : string = dom_entry.getAttribute("rel"); + const parts : Array = rel.split("/"); + const calendar_id : _dali.type.calendar_id = parseInt(parts[0]); + const event_id : (null | _dali.type.local_resource_event_id) = ( + (parts[1] === "-") + ? + null + : + parseInt(parts[1]) + ); + const access_level : _dali.type.enum_access_level = _dali.access_level_decode(parts[2]); + this.action_select_event( + calendar_id, + access_level, + event_id + ); + } + ); + + // emplace + dom_cell.appendChild(dom_entry); + + return dom_entry; + } } /** */ - private async update( - year : int, - week : int, - count : int, - options : { - update_controls ?: boolean; - } = {} + private async entry_add( + entry : type_entry ) : Promise { - options = Object.assign( - { - "update_controls": true, - }, - options - ); - const context : Element = this.container; - // controls + const dom_entry : (null | HTMLElement) = await this.entry_insert(entry); + if (dom_entry === null) { - if (! options.update_controls) - { - // do nothing - } - else - { - (context.querySelector(".weekview-control-year > input") as HTMLInputElement).value = year.toFixed(0); - (context.querySelector(".weekview-control-week > input") as HTMLInputElement).value = week.toFixed(0); - (context.querySelector(".weekview-control-count > input") as HTMLInputElement).value = count.toFixed(0); - } + // do nothing } - // table + else { - context.querySelector(".weekview-table tbody").innerHTML = await this.table_rows( + this.event_map.set( + entry.key, { - "calendar_ids": null, - // TODO - "from": { - "year": year, - "week": week - }, - // TODO - "to": { - "year": year, - "week": (week + count) - }, - "timezone_shift": /*conf.timezone_shift*/0, + "element": dom_entry, + "hash": class_widget_weekview.entry_hash(entry), } ); - // cells - { - if (! await _dali.backend.is_logged_in()) - { - // do nothing - } - else - { - context.querySelectorAll(".weekview-cell-regular").forEach( - (element) => { - element.addEventListener( - "click", - (event) => { - if (! (element === event.target)) - { - // do nothing - } - else - { - const rel : string = element.getAttribute("rel"); - const parts : Array = rel.split("-"); - const date : lib_plankton.pit.type_date = { - "year": parseInt(parts[0]), - "month": parseInt(parts[1]), - "day": parseInt(parts[2]), - }; - this.action_select_day(date); - } - } - ); - } - ); - } - } - // events - { - if (! await _dali.backend.is_logged_in()) - { - // do nothing - } - else - { - context.querySelectorAll(".weekview-event_entry").forEach( - (element) => { - element.addEventListener( - "click", - () => { - const rel : string = element.getAttribute("rel"); - const parts : Array = rel.split("/"); - const calendar_id : _dali.type.calendar_id = parseInt(parts[0]); - const event_id : (null | _dali.type.local_resource_event_id) = ( - (parts[1] === "-") - ? - null - : - parseInt(parts[1]) - ); - const access_level : _dali.type.enum_access_level = (() => { - switch (parts[2]) { - case "none": return _dali.type.enum_access_level.none; - case "view": return _dali.type.enum_access_level.view; - case "edit": return _dali.type.enum_access_level.edit; - case "admin": return _dali.type.enum_access_level.admin; - } - }) (); - this.action_select_event( - calendar_id, - access_level, - event_id - ); - } - ); - } - ); - } - } } - return Promise.resolve(undefined); } /** */ - private react_on_control_change( - dom_context : Element - ) : void + private async entry_update( + key : _dali.type.event_key, + entry : type_entry + ) : Promise { - const year : int = parseInt((dom_context.querySelector(".weekview-control-year > input") as HTMLInputElement).value); - const week : int = parseInt((dom_context.querySelector(".weekview-control-week > input") as HTMLInputElement).value); - const count : int = parseInt((dom_context.querySelector(".weekview-control-count > input") as HTMLInputElement).value); - this.update( - year, - week, - count, + if (! this.event_map.has(key)) + { + console.warn("event missing: " + key); + } + else + { + const value = this.event_map.get(key); + const hash_old : string = value.hash; + const hash_new : string = class_widget_weekview.entry_hash(entry); + if (hash_old === hash_new) { - "update_controls": false, + // do nothing + // console.info("nothing to update", {"key": key, "entry": entry, "element": value.element}); } + else + { + const dom_entry_old : HTMLElement = value.element; + dom_entry_old.remove(); + const dom_entry_new : (null | HTMLElement) = await this.entry_insert(entry); + if (dom_entry_new === null) + { + // do nothing + } + else + { + this.event_map.set( + entry.key, + { + "element": dom_entry_new, + "hash": hash_new, + } + ); + } + } + } + } + + + /** + */ + private async entry_remove( + key : _dali.type.event_key + ) : Promise + { + if (! this.event_map.has(key)) + { + // do nothing + // console.warn("not in map", {"key": key, "map": lib_plankton.map.dump(this.event_map)}); + } + else + { + const value = this.event_map.get( + key + ); + this.event_map.delete( + key + ); + value.element.remove(); + } + } + + + /** + */ + public async update_entries( + ) : Promise + { + const entries : Array = await this.get_entries_wrapped( ); + + const contrast = lib_plankton.list.contrast< + any, + type_entry + >( + lib_plankton.map.dump(this.event_map), + pair => pair.key, + entries, + event => event.key + ); + await Promise.all( + [] + // remove + .concat( + contrast.only_left.map( + ({"key": key, "left": left}) => this.entry_remove(key) + ) + ) + // update + .concat( + contrast.both.map( + ({"key": key, "left": left, "right": right}) => this.entry_update(key, right) + ) + ) + // add + .concat( + contrast.only_right.map( + ({"key": key, "right": right}) => this.entry_add(right) + ) + ) + ); + } + + + /** + */ + private async update_controls( + ) : Promise + { + const context : Element = this.container; + (context.querySelector(".weekview-control-year > input") as HTMLInputElement).value = this.year.toFixed(0); + (context.querySelector(".weekview-control-week > input") as HTMLInputElement).value = this.week.toFixed(0); + (context.querySelector(".weekview-control-count > input") as HTMLInputElement).value = this.count.toFixed(0); + } + + + /** + */ + private async update_table( + ) : Promise + { + /** + * @todo avoid? + */ + lib_plankton.map.clear(this.event_map); + const context : Element = this.container; + // structure + { + /** + * @todo als Variable? + */ + const timezone_shift : int = 0; + const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); + const today_begin_pit : lib_plankton.pit.type_pit = lib_plankton.pit.trunc_day(now_pit); + const today_end_pit : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day(today_begin_pit, 1); + const row_data : Array< + { + week : int; + data : Array< + { + pit : lib_plankton.pit.type_pit; + today : boolean; + } + >; + } + > = ( + lib_plankton.list.sequence(this.count) + .map( + offset => { + const week : int = (this.week + offset); + return { + "week": week, + "data": ( + lib_plankton.list.sequence(7) + .map( + day => { + const day_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd( + { + "year": this.year, + "week": week, + "day": (day + 1), + }, + { + "timezone_shift": timezone_shift, + } + ); + return { + "pit": day_pit, + "today": lib_plankton.pit.is_between( + day_pit, + today_begin_pit, + today_end_pit + ), + }; + } + ) + ), + }; + } + ) + ); + context.querySelector(".weekview-table tbody").innerHTML = ( + await _dali.helpers.promise_row( + row_data + .map( + (row) => async () => _dali.helpers.template_coin( + "widget-weekview", + "tableview-row", + { + "week": row.week.toFixed(0).padStart(2, "0"), + "cells": ( + await _dali.helpers.promise_row( + row.data + .map( + (cell) => async () => _dali.helpers.template_coin( + "widget-weekview", + "tableview-cell", + { + "extra_classes": ( + [""] + .concat(cell.today ? ["weekview-cell-today"] : []) + .join(" ") + ), + "title": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{year}}-{{month}}-{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ), + ] + ), + "day": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ), + ] + ), + "rel": lib_plankton.call.convey( + cell.pit, + [ + lib_plankton.pit.to_datetime_ce, + (x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin( + "{{year}}-{{month}}-{{day}}", + { + "year": x.date.year.toFixed(0).padStart(4, "0"), + "month": x.date.month.toFixed(0).padStart(2, "0"), + "day": x.date.day.toFixed(0).padStart(2, "0"), + } + ) + ] + ), + "entries": "" + } + ) + ) + ) + ).join(""), + } + ) + ) + ) + ).join(""); + } + // listeners + { + context.querySelectorAll(".weekview-cell-regular").forEach( + (element) => { + element.addEventListener( + "click", + (event) => { + if (! (element === event.target)) + { + // do nothing + } + else + { + const rel : string = element.getAttribute("rel"); + const parts : Array = rel.split("-"); + const date : lib_plankton.pit.type_date = { + "year": parseInt(parts[0]), + "month": parseInt(parts[1]), + "day": parseInt(parts[2]), + }; + this.action_select_day(date); + } + } + ); + } + ); + } } /** */ public toggle_visibility( - calendar_id : _dali.type.calendar_id + calendar_id : _dali.type.calendar_id, + { + "mode": mode = null, + } + : + { + mode ?: (null | boolean); + } + = + { + } ) : void { this.container.querySelectorAll(".weekview-event_entry").forEach( @@ -1108,7 +779,7 @@ namespace _dali.widgets.weekview } else { - element.classList.toggle("weekview-cell-hidden"); + element.classList.toggle("weekview-cell-hidden", mode ?? undefined); } } ); @@ -1142,48 +813,43 @@ namespace _dali.widgets.weekview this.container = target_element.querySelector(".weekview"); // controls { - target_element.querySelector(".weekview-control-apply").addEventListener( - "click", - (event) => { - event.preventDefault(); - this.react_on_control_change(target_element); - } - ); - if (false) - { - [ - "year", - "week", - "count", - ].forEach( - (name) => { - const selector : string = (".weekview-control-" + name + " > input"); - (target_element.querySelector(selector) as HTMLInputElement).addEventListener( - "change", - (event) => { - event.preventDefault(); - this.react_on_control_change(target_element); - } - ); - } - ); - } - } - // table - { - const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now()); - let year : int = ywd_now.year; - let week : int = Math.max(0, (ywd_now.week - 1)); - let count : int = 5; - await this.update( - year, - week, - count, + [ { - "update_controls": true, + "name": "year", + "transform": parseInt, + "write": x => {this.year = x;} + }, + { + "name": "week", + "transform": parseInt, + "write": x => {this.week = x;} + }, + { + "name": "count", + "transform": parseInt, + "write": x => {this.count = x;} + }, + ].forEach( + (entry) => { + const selector : string = (".weekview-control-" + entry.name + " > input"); + const element : HTMLInputElement = (target_element.querySelector(selector) as HTMLInputElement); + element.addEventListener( + "change", + async (event) => { + event.preventDefault(); + const value : int = entry.transform(element.value); + entry.write(value); + await this.update_table(); + await this.update_entries(); + } + ); } ); } + await this.update_controls(); + await this.update_table(); + await this.update_entries(); + return Promise.resolve(undefined); } diff --git a/source/widgets/weekview/templates/main.html.tpl b/source/widgets/weekview/templates/main.html.tpl index 0ebb5f0..30a6ee5 100644 --- a/source/widgets/weekview/templates/main.html.tpl +++ b/source/widgets/weekview/templates/main.html.tpl @@ -12,7 +12,6 @@ {{label_control_count}} -