namespace _dali.widgets.weekview { /** */ type type_entry = { key : _dali.type_event_key; calendar_id : _dali.type_calendar_id; calendar_name : string; hue : float; access_level : _dali.enum_access_level; event_id : (null | _dali.type_local_resource_event_id); event_object : _dali.type_event_object; }; /** */ type type_get_entries = ( ( from_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit, calendar_ids : Array<_dali.type_calendar_id> ) => Promise> ); /** */ export class class_widget_weekview implements lib_plankton.zoo_widget.interface_widget { /** * [dependency] */ private get_entries : type_get_entries; /** * [hook] */ private action_select_event : ( ( event_key : _dali.type_event_key ) => void ); /** * [hook] */ private action_select_day : ( ( date : lib_plankton.pit.type_date ) => void ); /** * [state] */ private vertical : boolean; /** * [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, { "action_select_day": action_select_day = ((date) => {}), "action_select_event": action_select_event = ((event_key) => {}), "vertical": vertical = false, "initial_year": initial_year = null, "initial_week": initial_week = null, "initial_count": initial_count = 5, } : { action_select_event ?: ( ( event_key : _dali.type_event_key ) => void ); action_select_day ?: ( ( date : lib_plankton.pit.type_date ) => void ); initial_year ?: (null | int); initial_week ?: (null | int); initial_count ?: int; } = {} ) { // 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; } /** * 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), ] ); } /** */ private static event_generate_tooltip( calendar_name : string, event_object : _dali.type_event_object ) : string { return ( lib_plankton.string.coin( "[{{calendar_name}}] {{event_name}}\n", { "calendar_name": calendar_name, "event_name": event_object.name, } ) + "--\n" + ( (event_object.begin.time !== null) ? lib_plankton.string.coin( "{{label}}: {{value}}\n", { "label": lib_plankton.translate.get("event.when"), "value": lib_plankton.pit.timespan_format( event_object.begin, event_object.end, { "timezone_indicator": lib_plankton.translate.get("common.timezone_indicator"), "adjust_to_ce": true, "show_timezone": true, "omit_date": true, } ), } ) : "" ) + ( (event_object.location !== null) ? ( lib_plankton.string.coin( "{{label}}: {{value}}\n", { "label": lib_plankton.translate.get("event.location"), "value": event_object.location, } ) ) : "" ) + ( (event_object.link !== null) ? ( lib_plankton.string.coin( "{{label}}: {{value}}\n", { "label": lib_plankton.translate.get("event.link"), "value": event_object.link, } ) ) : "" ) /* + ( (event_object.description !== null) ? ( "--\n" + lib_plankton.string.coin( "{{description}}\n", { "description": event_object.description, } ) ) : "" ) */ ); } /** */ private async get_entries_wrapped( { "calendar_ids": calendar_ids = null, "timezone_shift": timezone_shift = 0, } : { calendar_ids ?: (null | Array<_dali.type_calendar_id>); timezone_shift ?: int; } = { } ) : Promise> { 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 ); entries.sort( (entry1, entry2) => { const b1 : string = lib_plankton.pit.datetime_format(entry1.event_object.begin); const b2 : string = lib_plankton.pit.datetime_format(entry2.event_object.begin); return ((b1 <= b2) ? -1 : +1); } ); return entries; } /** */ private async entry_insert( entry : type_entry ) : Promise<(null | HTMLElement)> { const selector : string = lib_plankton.string.coin( ".weekview-cell[rel=\"{{rel}}\"] > .weekview-events", { "rel": lib_plankton.pit.date_format(entry.event_object.begin.date), } ); 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}}", { "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 event_key : _dali.type_event_key = rel; this.action_select_event( event_key ); } ); // emplace dom_cell.appendChild(dom_entry); return dom_entry; } } /** */ private async entry_add( entry : type_entry ) : Promise { const dom_entry : (null | HTMLElement) = await this.entry_insert(entry); if (dom_entry === null) { // do nothing } else { this.event_map.set( entry.key, { "element": dom_entry, "hash": class_widget_weekview.entry_hash(entry), } ); } } /** */ 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) { // do nothing lib_plankton.log.debug( "dali.widget.weekview.entry_update.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 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 { type type_row_data = Array< { week : int; day_pits : Array; } >; /** * @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_original : type_row_data = ( lib_plankton.list.sequence(this.count) .map( offset => { const week : int = (this.week + offset); return { "week": week, "day_pits": ( lib_plankton.list.sequence(7) .map( day => lib_plankton.pit.from_ywd( { "year": this.year, "week": week, "day": (1 + day), }, { "timezone_shift": timezone_shift, } ) ) ), }; } ) ); const row_data_alternative : type_row_data = ( lib_plankton.list.sequence(7) .map( day_of_week => { return { /*"day_of_week"*/"week": day_of_week, /*"week_pits"*/"day_pits": ( lib_plankton.list.sequence(this.count) .map( offset => { const week : int = (this.week + offset); return lib_plankton.pit.from_ywd( { "year": this.year, "week": week, "day": (1 + day_of_week), }, { "timezone_shift": timezone_shift, } ); } ) ) }; } ) ); const row_data : type_row_data = ( false ? row_data_original : row_data_alternative ); 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.day_pits .map( (day_pit) => async () => { const is_today : boolean = lib_plankton.pit.is_between( day_pit, today_begin_pit, today_end_pit ); return _dali.helpers.template_coin( "widget-weekview", "tableview-cell", { "extra_classes": ( [""] .concat(is_today ? ["weekview-cell-today"] : []) .join(" ") ), "title": lib_plankton.call.convey( day_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( day_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( day_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, { "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_)) { // do nothing } else { element.classList.toggle("weekview-cell-hidden", mode ?? undefined); } } ); } /** * [implementation] */ public async load( target_element : Element ) : Promise { target_element.innerHTML = await _dali.helpers.template_coin( "widget-weekview", "main", { "label_control_year": lib_plankton.translate.get("widget.weekview.controls.year"), "label_control_week": lib_plankton.translate.get("widget.weekview.controls.week"), "label_control_count": lib_plankton.translate.get("widget.weekview.controls.count"), "label_control_apply": lib_plankton.translate.get("widget.weekview.controls.apply"), "label_weekday_monday": lib_plankton.translate.get("common.weekday.monday"), "label_weekday_tuesday": lib_plankton.translate.get("common.weekday.tuesday"), "label_weekday_wednesday": lib_plankton.translate.get("common.weekday.wednesday"), "label_weekday_thursday": lib_plankton.translate.get("common.weekday.thursday"), "label_weekday_friday": lib_plankton.translate.get("common.weekday.friday"), "label_weekday_saturday": lib_plankton.translate.get("common.weekday.saturday"), "label_weekday_sunday": lib_plankton.translate.get("common.weekday.sunday"), } ); this.container = target_element.querySelector(".weekview"); // controls { [ { "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); } } }