/* This file is part of »dali«. Copyright 2025 'kcf' »dali« 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. »dali« 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 »dali«. If not, see . */ namespace _dali.model { /** */ type type_state = { groups : Array< { id : _dali.type_group_id; object : _dali.type_group_object; } >; 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 group_list( ) : Promise< Array< { id : _dali.type_group_id; object : _dali.type_group_object; } > > { return Promise.resolve(_state.groups); } /** */ export async function user_list( ) : Promise< Array< { id : _dali.type_user_id; name : string; } > > { return Promise.resolve(_state.users); } /** */ 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_group": lib_plankton.map.hashmap.implementation_map( lib_plankton.map.hashmap.make( x => x.toFixed(0), { "pairs": ( data.access.attributed_group .map( (entry) => ( { "key": entry.group_id, "value": _dali.access_level_decode(entry.level), } ) ) ), } ) ), "attributed_user": lib_plankton.map.hashmap.implementation_map( lib_plankton.map.hashmap.make( x => x.toFixed(0), { "pairs": ( data.access.attributed_user .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_group": ( lib_plankton.map.dump(calendar_object.access.attributed_group) .map( (pair) => ( { "group_id": pair.key, "level": _dali.access_level_encode(pair.value), } ) ) ), "attributed_user": ( lib_plankton.map.dump(calendar_object.access.attributed_user) .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_group": ( lib_plankton.map.dump(calendar_object.access.attributed_group) .map( (pair) => ({ "group_id": pair.key, "level": _dali.access_level_encode(pair.value), }) ) ), "attributed_user": ( lib_plankton.map.dump(calendar_object.access.attributed_user) .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 */ 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 = { "groups": [], "users": [], "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.groups = ( (await _dali.backend.group_list()) .map( entry => ( { "id": entry.id, "object": { "name": entry.name, "label": entry.label, } } ) ) ); _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.groups = []; _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(); } }