Compare commits

..

2 commits

99 changed files with 4794 additions and 13209 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,6 @@
"path": "" "path": ""
}, },
"misc": { "misc": {
"oidc_redirect_uri_template": "http://localhost:8888/?action=oidc_finish&session_key={{session_key}}", "oidc_redirect_uri_template": "http://localhost:8888/#oidc_finish,session_key={{session_key}}"
"use_central_europe_specific_datetime_inputs": true
} }
} }

View file

@ -2,7 +2,7 @@
## Beschreibung ## Beschreibung
- Web-Frontend für [zeitbild](/zeitbild/meta) - Web-Frontend für [`zeitbild`](https://forgejo.linke.sx/zeitbild/backend)
## Erstellung ## Erstellung

View file

@ -1,148 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali
{
/**
*/
let _actions_login : Array<((name ?: (null | string)) => Promise<void>)> = [];
/**
*/
let _actions_logout : Array<(() => Promise<void>)> = [];
/**
*/
let _is_logged_in : boolean = false;
/**
*/
export function listen_login(
action : ((name ?: (null | string)) => Promise<void>)
)
: void
{
_actions_login.push(action);
}
/**
*/
export function listen_logout(
action : (() => Promise<void>)
)
: void
{
_actions_logout.push(action);
}
/**
*/
export async function notify_login(
name : (null | string)
)
: Promise<void>
{
_is_logged_in = true;
for (const action of _actions_login)
{
await action(name);
}
}
/**
*/
export async function notify_logout(
)
: Promise<void>
{
_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<void>
{
await _dali.backend.set_session_key(session_key);
const status = await _dali.backend.status();
if (! status.logged_in)
{
lib_plankton.log.error(
"dali.oidc_login_failed"
);
await _dali.notify_logout();
return Promise.reject<void>(new Error("oidc login failed"));
}
else
{
await _dali.notify_login(status.name);
return Promise.resolve<void>(undefined);
}
}
/**
*/
export async function logout(
)
: Promise<void>
{
try
{
await _dali.backend.session_end(
);
notify_logout();
}
catch (error)
{
lib_plankton.log.notice(
"dali.logout_failed",
{
"reason": String(error),
}
);
}
}
}

View file

@ -14,27 +14,10 @@
"common.weekday.friday": "Fr", "common.weekday.friday": "Fr",
"common.weekday.saturday": "Sa", "common.weekday.saturday": "Sa",
"common.weekday.sunday": "So", "common.weekday.sunday": "So",
"common.monthname.january": "Jan",
"common.monthname.february": "Feb",
"common.monthname.march": "Mär",
"common.monthname.april": "Apr",
"common.monthname.may": "Mai",
"common.monthname.june": "Jun",
"common.monthname.july": "Jul",
"common.monthname.august": "Aug",
"common.monthname.september": "Sep",
"common.monthname.october": "Okt",
"common.monthname.november": "Nov",
"common.monthname.december": "Dez",
"common.open": "öffnen", "common.open": "öffnen",
"common.close": "schließen",
"common.edit": "bearbeiten", "common.edit": "bearbeiten",
"common.show": "zeigen", "common.show": "zeigen",
"common.hide": "ausblenden", "common.hide": "ausblenden",
"common.cancel": "abbrechen",
"common.login": "anmelden",
"common.logout": "abmelden",
"common.confirm_deletion": "sicher?",
"access_level.none": "nichts", "access_level.none": "nichts",
"access_level.view": "nur lesen", "access_level.view": "nur lesen",
"access_level.edit": "lesen und bearbeiten", "access_level.edit": "lesen und bearbeiten",
@ -55,44 +38,46 @@
"resource.kinds.caldav.read_only": "nur lesen", "resource.kinds.caldav.read_only": "nur lesen",
"calendar.calendar": "Kalender", "calendar.calendar": "Kalender",
"calendar.name": "Name", "calendar.name": "Name",
"calendar.hue": "Farbton",
"calendar.resource": "Resource", "calendar.resource": "Resource",
"calendar.access.access": "Zugriff", "calendar.access.access": "Zugriff",
"calendar.access.public": "öffentlich", "calendar.access.public": "öffentlich",
"calendar.access.default_level": "Standard", "calendar.access.default_level": "Standard",
"calendar.access.attributed_group": "Gruppen-Zuweisungen", "calendar.access.attributed": "Zuweisungen",
"calendar.access.attributed_user": "Nutzer-Zuweisungen",
"widget.listview.add": "Termin anlegen", "widget.listview.add": "Termin anlegen",
"widget.weekview.controls.year": "Jahr", "widget.weekview.controls.year": "Jahr",
"widget.weekview.controls.week": "Woche", "widget.weekview.controls.week": "Woche",
"widget.weekview.controls.count": "Anzahl", "widget.weekview.controls.count": "Anzahl",
"widget.weekview.controls.vertical": "senkrecht",
"widget.weekview.controls.apply": "Laden", "widget.weekview.controls.apply": "Laden",
"widget.calendar_edit.actions.add": "Kalender anlegen", "page.login.title": "Anmelden",
"widget.calendar_edit.actions.change": "ändern", "page.login.internal.name": "Name",
"widget.calendar_edit.actions.remove": "löschen", "page.login.internal.password": "Kennwort",
"widget.event_edit.actions.add": "anlegen", "page.login.internal.do": "Anmelden",
"widget.event_edit.actions.change": "ändern", "page.login.oidc.via": "via {{title}}",
"widget.event_edit.actions.remove": "löschen", "page.logout.title": "Abmelden",
"widget.sources.create": "Kalender anlegen", "page.caldav.title": "CalDAV",
"widget.login.internal.name": "Name", "page.caldav.unavailable": "CalDAV nicht verfügbar",
"widget.login.internal.password": "Kennwort", "page.caldav.conf.title": "Zugangsdaten",
"widget.login.internal.do": "Anmelden", "page.caldav.conf.address": "Adresse (URL)",
"widget.login.oidc.via": "via {{title}}", "page.caldav.conf.username": "Nutzername",
"widget.caldav.title": "CalDAV", "page.caldav.conf.password": "Kennwort",
"widget.caldav.unavailable": "CalDAV nicht verfügbar", "page.caldav.conf.setup_hints": "Einrichtungs-Hinweise",
"widget.caldav.conf.title": "Zugangsdaten", "page.caldav.conf.token_unset": "es muss zunächst ein Token gesetzt werden",
"widget.caldav.conf.address": "Adresse (URL)", "page.caldav.set_token.title": "Token setzen",
"widget.caldav.conf.username": "Nutzername", "page.caldav.set_token.action.set": "setzen",
"widget.caldav.conf.password": "Kennwort", "page.caldav.set_token.action.overwrite": "überschreiben",
"widget.caldav.conf.setup_hints": "Einrichtungs-Hinweise", "page.calendar_add.title": "Kalendar anlegen",
"widget.caldav.conf.token_unset": "es muss zunächst ein Token gesetzt werden", "page.calendar_add.actions.do": "anlegen",
"widget.caldav.set_token.title": "Token setzen", "page.calendar_edit.title.regular": "Kalendar bearbeiten",
"widget.caldav.set_token.action.set": "setzen", "page.calendar_edit.title.read_only": "Kalendar-Details",
"widget.caldav.set_token.action.overwrite": "überschreiben", "page.calendar_edit.actions.change": "ändern",
"widget.overview.title": "Übersicht", "page.calendar_edit.actions.remove": "löschen",
"widget.overview.login_hint": "anmelden um nicht-öffentliche Termine zu sehen", "page.event_add.title": "Termin anlegen",
"widget.overview.mode.week": "Wochen-Ansicht", "page.event_add.actions.do": "anlegen",
"widget.overview.mode.list": "Listen-Ansicht" "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"
} }
} }

View file

@ -14,27 +14,10 @@
"common.weekday.friday": "Fri", "common.weekday.friday": "Fri",
"common.weekday.saturday": "Sat", "common.weekday.saturday": "Sat",
"common.weekday.sunday": "Sun", "common.weekday.sunday": "Sun",
"common.monthname.january": "jan",
"common.monthname.february": "feb",
"common.monthname.march": "mar",
"common.monthname.april": "apr",
"common.monthname.may": "may",
"common.monthname.june": "jun",
"common.monthname.july": "jul",
"common.monthname.august": "aug",
"common.monthname.september": "sep",
"common.monthname.october": "oct",
"common.monthname.november": "nov",
"common.monthname.december": "dec",
"common.open": "open", "common.open": "open",
"common.close": "close",
"common.edit": "edit", "common.edit": "edit",
"common.show": "show", "common.show": "show",
"common.hide": "hide", "common.hide": "hide",
"common.cancel": "cancel",
"common.login": "login",
"common.logout": "logout",
"common.confirm_deletion": "sure?",
"access_level.none": "none", "access_level.none": "none",
"access_level.view": "read only", "access_level.view": "read only",
"access_level.edit": "read and write", "access_level.edit": "read and write",
@ -53,46 +36,48 @@
"resource.kinds.caldav.title": "CalDAV", "resource.kinds.caldav.title": "CalDAV",
"resource.kinds.caldav.url": "URL", "resource.kinds.caldav.url": "URL",
"resource.kinds.caldav.read_only": "read-only", "resource.kinds.caldav.read_only": "read-only",
"calendar.calendar": "calendar", "calendar.calendar": "Kalendar",
"calendar.name": "name", "calendar.name": "name",
"calendar.hue": "hue",
"calendar.resource": "resource", "calendar.resource": "resource",
"calendar.access.access": "access", "calendar.access.access": "access",
"calendar.access.public": "public", "calendar.access.public": "public",
"calendar.access.default_level": "default", "calendar.access.default_level": "default",
"calendar.access.attributed_group": "group attributed", "calendar.access.attributed": "attributed",
"calendar.access.attributed_user": "user attributed",
"widget.listview.add": "add event", "widget.listview.add": "add event",
"widget.weekview.controls.year": "year", "widget.weekview.controls.year": "Year",
"widget.weekview.controls.week": "week", "widget.weekview.controls.week": "Week",
"widget.weekview.controls.count": "count", "widget.weekview.controls.count": "Count",
"widget.weekview.controls.vertical": "vertical",
"widget.weekview.controls.apply": "Load", "widget.weekview.controls.apply": "Load",
"widget.calendar_edit.actions.add": "add calendar", "page.login.title": "Login",
"widget.calendar_edit.actions.change": "change", "page.login.internal.name": "name",
"widget.calendar_edit.actions.remove": "delete", "page.login.internal.password": "password",
"widget.event_edit.actions.add": "add", "page.login.internal.do": "login",
"widget.event_edit.actions.change": "change", "page.login.oidc.via": "via {{title}}",
"widget.event_edit.actions.remove": "delete", "page.logout.title": "Logout",
"widget.sources.create": "create calendar", "page.caldav.title": "CalDAV",
"widget.login.internal.name": "name", "page.caldav.unavailable": "CalDAV not available",
"widget.login.internal.password": "password", "page.caldav.conf.title": "credentials",
"widget.login.internal.do": "login", "page.caldav.conf.address": "address (URL)",
"widget.login.oidc.via": "via {{title}}", "page.caldav.conf.username": "username",
"widget.caldav.title": "CalDAV", "page.caldav.conf.password": "password",
"widget.caldav.unavailable": "CalDAV not available", "page.caldav.conf.setup_hints": "setup hints",
"widget.caldav.conf.title": "credentials", "page.caldav.conf.token_unset": "a token has to be set",
"widget.caldav.conf.address": "address (URL)", "page.caldav.set_token.title": "set token",
"widget.caldav.conf.username": "username", "page.caldav.set_token.action.set": "set",
"widget.caldav.conf.password": "password", "page.caldav.set_token.action.overwrite": "overwrite",
"widget.caldav.conf.setup_hints": "setup hints", "page.calendar_add.title": "Add calendar",
"widget.caldav.conf.token_unset": "a token has to be set", "page.calendar_add.actions.do": "anlegen",
"widget.caldav.set_token.title": "set token", "page.event_add.title": "Add event",
"widget.caldav.set_token.action.set": "set", "page.event_add.actions.do": "add",
"widget.caldav.set_token.action.overwrite": "overwrite", "page.calendar_edit.title.regular": "Edit calendar",
"widget.overview.title": "Overview", "page.calendar_edit.title.read_only": "Calendar details",
"widget.overview.login_hint": "log in to view non-public events", "page.calendar_edit.actions.change": "change",
"widget.overview.mode.week": "week view", "page.calendar_edit.actions.remove": "delete",
"widget.overview.mode.list": "list view" "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"
} }
} }

View file

@ -1,394 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
/**
*/
namespace _dali.helpers
{
/**
* @todo outsource
*/
function is_touch_device(
)
: boolean
{
return (
("ontouchstart" in window)
||
(navigator.maxTouchPoints > 0)
||
(navigator["msMaxTouchPoints"] > 0)
);
}
/**
*/
var _template_cache : Record<string, string> = {};
/**
*/
export function view_mode_determine(
mode_descriptor : string
)
: _dali.enum_view_mode
{
if (mode_descriptor === "auto")
{
return (
(window.innerWidth >= 1000)
?
_dali.enum_view_mode.week
:
_dali.enum_view_mode.list
);
}
else
{
return view_mode_decode(mode_descriptor);
}
}
/**
*/
export function view_kind_determine(
mode_descriptor : string
)
: _dali.enum_view_kind
{
if (mode_descriptor === "auto")
{
return (
is_touch_device()
?
_dali.enum_view_kind.touch
:
_dali.enum_view_kind.regular
);
}
else
{
return view_kind_decode(mode_descriptor);
}
}
/**
*/
export async function template_coin(
group : string,
name : string,
data : Record<string, string>
) : Promise<string>
{
let content : string;
const key : string = lib_plankton.string.coin(
"{{group}}/{{name}}",
{
"group": group,
"name": name,
}
);
if (! (key in _template_cache)) {
content = (
(
await lib_plankton.file.read(
lib_plankton.string.coin(
"templates/{{group}}/{{name}}.html.tpl",
{
"group": group,
"name": name,
}
)
)
)
.toString()
);
_template_cache[key] = content;
}
else {
content = _template_cache[key];
}
return Promise.resolve<string>(
lib_plankton.string.coin(
content,
data
)
);
}
/**
*/
export async function element_from_template(
group : string,
name : string,
data : Record<string, string>
)
: Promise<HTMLElement>
{
const dom_dummy = document.createElement("div");
dom_dummy.innerHTML = await _dali.helpers.template_coin(
group,
name,
data
);
return (dom_dummy.children[0] as HTMLElement);
}
/**
* @todo outsource
*/
export async function promise_row<type_result>(
members : Array<
() => Promise<type_result>
>
)
: Promise<
Array<
type_result
>
>
{
let results : Array<type_result> = [];
for await (const member of members) {
results.push(await member());
}
return Promise.resolve<Array<type_result>>(results);
}
/**
*/
export function input_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.enum_access_level
>(
new lib_plankton.zoo_input.class_input_selection(
[
{
"value": "none",
"label": lib_plankton.translate.get("access_level.none"),
},
{
"value": "view",
"label": lib_plankton.translate.get("access_level.view")
},
{
"value": "edit",
"label": lib_plankton.translate.get("access_level.edit")
},
{
"value": "admin",
"label": lib_plankton.translate.get("access_level.admin")
},
]
),
(raw) => {
switch (raw) {
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.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 input_attributed_access_group(
groups : Array<{id : _dali.type_group_id; object : _dali.type_group_object;}>
)
: lib_plankton.zoo_input.class_input_hashmap<
_dali.type_group_id,
_dali.enum_access_level
>
{
return (
new lib_plankton.zoo_input.class_input_hashmap<_dali.type_group_id, _dali.enum_access_level>(
// hash_key
(group_id) => group_id.toFixed(0),
// key_input_factory
() => new lib_plankton.zoo_input.class_input_wrapped<string, int>(
new lib_plankton.zoo_input.class_input_selection(
groups
.map(
(group) => ({
"value": group.id.toFixed(0),
"label": group.object.label,
})
)
),
x => parseInt(x),
x => x.toFixed(0)
),
// value_input_factory
() => input_access_level()
)
);
}
/**
*/
export function input_attributed_access_user(
users : Array<{id : _dali.type_user_id; name : string;}>
)
: lib_plankton.zoo_input.class_input_hashmap<
_dali.type_user_id,
_dali.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
() => new lib_plankton.zoo_input.class_input_wrapped<string, int>(
new lib_plankton.zoo_input.class_input_selection(
users
.map(
(user) => ({
"value": user.id.toFixed(0),
"label": user.name,
})
)
),
x => parseInt(x),
x => x.toFixed(0)
),
// value_input_factory
() => input_access_level()
)
);
}
/**
*/
export function datetime_input(
)
: lib_plankton.zoo_input.interface_input<lib_plankton.pit.type_datetime>
{
return (
_dali.conf.get().misc.use_central_europe_specific_datetime_inputs
?
new lib_plankton.zoo_input.class_input_datetime_central_europe(
{
"label_date": lib_plankton.translate.get("common.date"),
"label_time": lib_plankton.translate.get("common.time"),
}
)
:
new lib_plankton.zoo_input.class_input_datetime(
{
"label_timezone_shift": lib_plankton.translate.get("common.timezone_shift"),
"label_date": lib_plankton.translate.get("common.date"),
"label_time": lib_plankton.translate.get("common.time"),
}
)
);
}
/**
*/
export function event_color(
hue : float
)
:
string
{
return lib_plankton.color.output_hex(
lib_plankton.color.make_hsv(
{
"hue": hue,
"saturation": 0.375,
"value": 0.375,
}
),
);
}
/**
*/
export function month_name(
month : int
)
: string
{
const keys : Array<string> = [
"common.monthname.january",
"common.monthname.february",
"common.monthname.march",
"common.monthname.april",
"common.monthname.may",
"common.monthname.june",
"common.monthname.july",
"common.monthname.august",
"common.monthname.september",
"common.monthname.october",
"common.monthname.november",
"common.monthname.december",
];
return lib_plankton.translate.get(keys[month-1]);
}
/**
*/
export function loading(
mode : boolean
)
: void
{
if (! mode)
{
_dali.overlay.toggle({"mode": false});
}
else
{
_dali.overlay.get_content_element().innerHTML = ". . .";
_dali.overlay.toggle({"mode": true});
}
}
}

View file

@ -11,7 +11,7 @@
document.addEventListener( document.addEventListener(
"DOMContentLoaded", "DOMContentLoaded",
() => { () => {
_dali.main() _zeitbild.frontend_web.main()
.then( .then(
() => {} () => {}
) )
@ -20,14 +20,16 @@ document.addEventListener(
) )
} }
); );
</script> </script>
{{templates}}
</head> </head>
<body> <body>
<div id="overlay">
<div id="overlay_content">
</div>
</div>
<header> <header>
<nav>
<ul>
</ul>
</nav>
</header> </header>
<main> <main>
</main> </main>

630
source/logic/backend.ts Normal file
View file

@ -0,0 +1,630 @@
/**
*/
namespace _zeitbild.frontend_web.backend
{
/**
*/
var _data_chest : (
null
|
lib_plankton.storage.type_chest<string, string, void, string, string>
) = null;
/**
*/
function access_level_encode(
access_level : _zeitbild.frontend_web.type.enum_access_level
) : ("none" | "view" | "edit" | "admin")
{
switch (access_level) {
case _zeitbild.frontend_web.type.enum_access_level.none: return "none";
case _zeitbild.frontend_web.type.enum_access_level.view: return "view";
case _zeitbild.frontend_web.type.enum_access_level.edit: return "edit";
case _zeitbild.frontend_web.type.enum_access_level.admin: return "admin";
}
}
/**
*/
function access_level_decode(
access_level_encoded : ("none" | "view" | "edit" | "admin")
) : _zeitbild.frontend_web.type.enum_access_level
{
switch (access_level_encoded) {
case "none": return _zeitbild.frontend_web.type.enum_access_level.none;
case "view": return _zeitbild.frontend_web.type.enum_access_level.view;
case "edit": return _zeitbild.frontend_web.type.enum_access_level.edit;
case "admin": return _zeitbild.frontend_web.type.enum_access_level.admin;
}
}
/**
*/
export async function init(
) : Promise<void>
{
_data_chest = lib_plankton.storage.localstorage.implementation_chest(
{
"corner": "zeitbild",
}
);
return Promise.resolve<void>(undefined);
}
/**
*/
async function get_session_key(
) : Promise<(null | string)>
{
try {
return (await _data_chest.read("session_key"));
}
catch (error) {
return null;
}
}
/**
*/
export async function is_logged_in(
) : Promise<boolean>
{
return ((await get_session_key()) !== null);
}
/**
*/
async function call(
method : lib_plankton.http.enum_method,
action : string,
input : (null | any)
) : Promise<any>
{
const with_body : boolean = (
[
lib_plankton.http.enum_method.post,
lib_plankton.http.enum_method.put,
lib_plankton.http.enum_method.patch,
].includes(method)
);
const session_key : (null | string) = await get_session_key();
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/2",
"scheme": (
(_zeitbild.frontend_web.conf.get()["backend"]["scheme"] === "http")
?
"http"
:
"https"
),
"host": lib_plankton.string.coin(
"{{host}}:{{port}}",
{
"host": _zeitbild.frontend_web.conf.get()["backend"]["host"],
"port": _zeitbild.frontend_web.conf.get()["backend"]["port"].toFixed(0),
}
),
"path": lib_plankton.string.coin(
"{{base}}{{action}}",
{
"base": _zeitbild.frontend_web.conf.get()["backend"]["path"],
"action": action,
}
),
"method": method,
"query": (
(with_body || (input === null))
?
null
:
("?" + lib_plankton.www_form.encode(input))
),
"headers": Object.assign(
{},
(
(! with_body)
?
{}
:
{"Content-Type": "application/json"}
),
(
(session_key === null)
?
{}
:
{"X-Session-Key": session_key}
)
),
"body": (
((! with_body) || (input === null))
?
null
:
/*Buffer.from*/(lib_plankton.json.encode(input))
),
};
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(http_request);
if (
! (
(http_response.status_code >= 200)
&&
(http_response.status_code < 300)
)
) {
return Promise.reject<any>(http_response.body.toString());
}
else {
const output : any = lib_plankton.json.decode(http_response.body.toString());
return Promise.resolve<any>(output);
}
}
/**
*/
export async function session_prepare(
input : any
) : Promise<{kind : string; data : any;}>
{
return call(
lib_plankton.http.enum_method.post,
"/session/prepare",
input
);
}
/**
*/
export function set_session_key(
session_key : string
) : Promise<void>
{
return (
_data_chest.write("session_key", session_key)
.then<void>(() => Promise.resolve<void>(undefined))
);
}
/**
*/
export async function session_begin(
name : string,
password : string
) : Promise<void>
{
const session_key : string = await call(
lib_plankton.http.enum_method.post,
"/session/begin",
{
"name": name,
"password": password,
}
);
await _data_chest.write("session_key", session_key);
return Promise.resolve<void>(undefined);
}
/**
*/
export async function session_end(
) : Promise<void>
{
await call(
lib_plankton.http.enum_method.delete,
"/session/end",
null
);
await _data_chest.delete("session_key");
return Promise.resolve<void>(undefined);
}
/**
*/
export function user_list(
) : Promise<
Array<
{
id : int;
name : string;
}
>
>
{
return call(
lib_plankton.http.enum_method.get,
"/users",
null
);
}
/**
*/
export function user_dav_conf(
) : Promise<
(
null
|
{
address : string;
username : string;
password : string;
setup_hints : Array<
{
label : string;
link : string;
remark : (null | string);
}
>;
}
)
>
{
return call(
lib_plankton.http.enum_method.get,
"/user_dav_conf",
null
);
}
/**
*/
export function user_dav_token(
) : Promise<void>
{
return call(
lib_plankton.http.enum_method.patch,
"/user_dav_token",
null
);
}
/**
*/
export async function calendar_list(
) : Promise<
Array<
{
id : int;
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>
>
{
return (
call(
lib_plankton.http.enum_method.get,
"/calendar",
null
)
.then(
(entries) => Promise.resolve(
entries
.map(
(entry) => ({
"id": entry.id,
"name": entry.name,
"access_level": access_level_decode(entry.access_level),
})
)
)
)
);
}
/**
*/
export async function calendar_get(
calendar_id : _zeitbild.frontend_web.type.calendar_id
) : Promise<
_zeitbild.frontend_web.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,
"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 : _zeitbild.frontend_web.type.calendar_object
) : Promise<
_zeitbild.frontend_web.type.calendar_id
>
{
return call(
lib_plankton.http.enum_method.post,
lib_plankton.string.coin(
"/calendar",
{
}
),
{
"name": calendar_object.name,
"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 : _zeitbild.frontend_web.type.calendar_id,
calendar_object : _zeitbild.frontend_web.type.calendar_object
) : Promise<
void
>
{
return call(
lib_plankton.http.enum_method.put,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}",
{
"calendar_id": calendar_id.toFixed(0),
}
),
{
"name": calendar_object.name,
"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_remove(
calendar_id : _zeitbild.frontend_web.type.calendar_id
) : Promise<
void
>
{
return call(
lib_plankton.http.enum_method.delete,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}",
{
"calendar_id": calendar_id.toFixed(0),
}
),
null
);
}
/**
*/
export async function calendar_event_get(
calendar_id : _zeitbild.frontend_web.type.calendar_id,
event_id : _zeitbild.frontend_web.type.local_resource_event_id
) : Promise<_zeitbild.frontend_web.type.event_object>
{
return call(
lib_plankton.http.enum_method.get,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event/{{event_id}}",
{
"calendar_id": calendar_id.toFixed(0),
"event_id": event_id.toFixed(0),
}
),
null
);
}
/**
*/
export async function calendar_event_add(
calendar_id : _zeitbild.frontend_web.type.calendar_id,
event_object : _zeitbild.frontend_web.type.event_object
) : Promise<void>
{
return call(
lib_plankton.http.enum_method.post,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event",
{
"calendar_id": calendar_id.toFixed(0),
}
),
event_object
);
}
/**
*/
export async function calendar_event_change(
calendar_id : _zeitbild.frontend_web.type.calendar_id,
event_id : _zeitbild.frontend_web.type.local_resource_event_id,
event_object : _zeitbild.frontend_web.type.event_object
) : Promise<void>
{
return call(
lib_plankton.http.enum_method.put,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event/{{event_id}}",
{
"calendar_id": calendar_id.toFixed(0),
"event_id": event_id.toFixed(0),
}
),
event_object
);
}
/**
*/
export async function calendar_event_remove(
calendar_id : _zeitbild.frontend_web.type.calendar_id,
event_id : _zeitbild.frontend_web.type.local_resource_event_id
) : Promise<void>
{
return call(
lib_plankton.http.enum_method.delete,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event/{{event_id}}",
{
"calendar_id": calendar_id.toFixed(0),
"event_id": event_id.toFixed(0),
}
),
null
);
}
/**
* @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<_zeitbild.frontend_web.type.calendar_id>);
} = {}
) : Promise<
Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
calendar_name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.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,
"access_level": access_level_decode(entry.access_level),
"event_id": entry.event_id,
"event_object": entry.event_object,
})
)
)
)
);
}
}

View file

@ -1,25 +1,5 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org> namespace _zeitbild.frontend_web.conf
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.conf
{ {
/** /**
@ -73,16 +53,10 @@ namespace _dali.conf
"default": "http://localhost:8888/#oidc_finish,session_key={{session_key}}" "default": "http://localhost:8888/#oidc_finish,session_key={{session_key}}"
}, },
"use_central_europe_specific_datetime_inputs": { "use_central_europe_specific_datetime_inputs": {
"nullable": false, "nullable": true,
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"weekview_cell_day_format": {
"nullable": false,
"type": "string",
"default": "d.b",
"description": "available placeholders: Y,m,b,d,W,w (as in UNIX command 'date')"
},
}, },
"required": [ "required": [
], ],
@ -99,25 +73,7 @@ namespace _dali.conf
/** /**
*/ */
type type_data = { var _data : (null | any) = null;
version : string;
backend : {
scheme : string;
host : string;
port : int;
path : string;
};
misc : {
oidc_redirect_uri_template : string;
use_central_europe_specific_datetime_inputs : string;
weekview_cell_day_format : string;
};
};
/**
*/
var _data : (null | type_data) = null;
/** /**
@ -132,10 +88,9 @@ namespace _dali.conf
/** /**
*/ */
export function get( export function get(
) : type_data ) : any
{
if (_data === null)
{ {
if (_data === null) {
throw (new Error("conf not loaded yet")); throw (new Error("conf not loaded yet"));
} }
else { else {
@ -150,7 +105,10 @@ namespace _dali.conf
path : string path : string
) : Promise<void> ) : Promise<void>
{ {
_data = ((await lib_plankton.conf.load(_schema, path)) as type_data); _data = await lib_plankton.conf.load(
_schema,
path
);
return Promise.resolve<void>(undefined); return Promise.resolve<void>(undefined);
} }

186
source/logic/helpers.ts Normal file
View file

@ -0,0 +1,186 @@
/**
*/
namespace _zeitbild.frontend_web.helpers
{
/**
*/
var _template_cache : Record<string, string> = {};
/**
*/
export async function template_coin(
group : string,
name : string,
data : Record<string, string>
) : Promise<string>
{
let content : string;
const key : string = lib_plankton.string.coin(
"{{group}}/{{name}}",
{
"group": group,
"name": name,
}
);
if (! (key in _template_cache)) {
content = (
(
await lib_plankton.file.read(
lib_plankton.string.coin(
"templates/{{group}}/{{name}}.html.tpl",
{
"group": group,
"name": name,
}
)
)
)
.toString()
);
_template_cache[key] = content;
}
else {
content = _template_cache[key];
}
return Promise.resolve<string>(
lib_plankton.string.coin(
content,
data
)
);
}
/**
* @todo outsource
*/
export async function promise_row<type_result>(
members : Array<
() => Promise<type_result>
>
) : Promise<
Array<
type_result
>
>
{
let results : Array<type_result> = [];
for await (const member of members) {
results.push(await member());
}
return Promise.resolve<Array<type_result>>(results);
}
/**
*/
export function input_access_level(
) : lib_plankton.zoo_input.interface_input<_zeitbild.frontend_web.type.enum_access_level>
{
return (
new lib_plankton.zoo_input.class_input_wrapped<
/*("none" | "view" | "edit" | "admin")*/string,
_zeitbild.frontend_web.type.enum_access_level
>(
new lib_plankton.zoo_input.class_input_selection(
[
{
"value": "none",
"label": lib_plankton.translate.get("access_level.none"),
},
{
"value": "view",
"label": lib_plankton.translate.get("access_level.view")
},
{
"value": "edit",
"label": lib_plankton.translate.get("access_level.edit")
},
{
"value": "admin",
"label": lib_plankton.translate.get("access_level.admin")
},
]
),
(raw) => {
switch (raw) {
case "none": return _zeitbild.frontend_web.type.enum_access_level.none;
case "view": return _zeitbild.frontend_web.type.enum_access_level.view;
case "edit": return _zeitbild.frontend_web.type.enum_access_level.edit;
case "admin": return _zeitbild.frontend_web.type.enum_access_level.admin;
}
},
(access_level) => {
switch (access_level) {
case _zeitbild.frontend_web.type.enum_access_level.none: return "none";
case _zeitbild.frontend_web.type.enum_access_level.view: return "view";
case _zeitbild.frontend_web.type.enum_access_level.edit: return "edit";
case _zeitbild.frontend_web.type.enum_access_level.admin: return "admin";
}
},
)
);
}
/**
*/
export async function input_attributed_access(
) : Promise<lib_plankton.zoo_input.class_input_hashmap<_zeitbild.frontend_web.type.user_id, _zeitbild.frontend_web.type.enum_access_level>>
{
const users : Array<{id : _zeitbild.frontend_web.type.user_id; name : string;}> = await _zeitbild.frontend_web.backend.user_list(
);
return Promise.resolve(
new lib_plankton.zoo_input.class_input_hashmap<_zeitbild.frontend_web.type.user_id, _zeitbild.frontend_web.type.enum_access_level>(
// hash_key
(user_id) => user_id.toFixed(0),
// key_input_factory
() => new lib_plankton.zoo_input.class_input_wrapped<string, int>(
new lib_plankton.zoo_input.class_input_selection(
users
.map(
(user) => ({
"value": user.id.toFixed(0),
"label": user.name,
})
)
),
x => parseInt(x),
x => x.toFixed(0)
),
// value_input_factory
() => input_access_level()
)
);
}
/**
*/
export function datetime_input(
) : lib_plankton.zoo_input.interface_input<lib_plankton.pit.type_datetime>
{
return (
_zeitbild.frontend_web.conf.get().misc.use_central_europe_specific_datetime_inputs
?
new lib_plankton.zoo_input.class_input_datetime_central_europe(
{
"label_date": lib_plankton.translate.get("common.date"),
"label_time": lib_plankton.translate.get("common.time"),
}
)
:
new lib_plankton.zoo_input.class_input_datetime(
{
"label_timezone_shift": lib_plankton.translate.get("common.timezone_shift"),
"label_date": lib_plankton.translate.get("common.date"),
"label_time": lib_plankton.translate.get("common.time"),
}
)
);
}
}

79
source/logic/main.ts Normal file
View file

@ -0,0 +1,79 @@
/**
*/
namespace _zeitbild.frontend_web
{
/**
*/
export async function main(
) : Promise<void>
{
// conf
await _zeitbild.frontend_web.conf.init(
"conf.json"
);
// init
lib_plankton.log.set_main_logger(
[
{"kind": "console", "data": {"threshold": "info"}},
]
);
await _zeitbild.frontend_web.backend.init(
);
await lib_plankton.translate.initialize(
{
"verbosity": 1,
"packages": [
JSON.parse(await lib_plankton.file.read("data/localization/deu.loc.json")),
JSON.parse(await lib_plankton.file.read("data/localization/eng.loc.json")),
],
"order": ["deu", "eng"],
"autopromote": false,
}
);
lib_plankton.zoo_page.init(
document.querySelector("main"),
{
"fallback": {
"name": "overview",
"parameters": {}
}
}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "login", "parameters": {}},
{"label": lib_plankton.translate.get("page.login.title")}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "overview", "parameters": {}},
{"label": lib_plankton.translate.get("page.overview.title")}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "calendar_add", "parameters": {}},
{"label": lib_plankton.translate.get("page.calendar_add.title")}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "caldav", "parameters": {}},
{"label": lib_plankton.translate.get("page.caldav.title")}
);
/*
lib_plankton.zoo_page.add_nav_entry(
{"name": "event_add", "parameters": {}},
{"label": lib_plankton.translate.get("page.event_add.title")}
);
*/
lib_plankton.zoo_page.add_nav_entry(
{"name": "logout", "parameters": {}},
{"label": lib_plankton.translate.get("page.logout.title")}
);
// exec
lib_plankton.zoo_page.start();
return Promise.resolve<void>(undefined);
}
}

114
source/logic/types.ts Normal file
View file

@ -0,0 +1,114 @@
/**
*/
namespace _zeitbild.frontend_web.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;
access : {
public : boolean;
default_level : enum_access_level;
attributed : lib_plankton.map.type_map<
user_id,
enum_access_level
>;
};
resource : resource_object;
};
}

18
source/logic/widget.ts Normal file
View file

@ -0,0 +1,18 @@
namespace _zeitbild
{
/**
* @todo outsource
*/
export abstract class class_widget
{
/**
*/
public abstract load(
target_element : Element
) : Promise<void>;
}
}

View file

@ -1,221 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
/**
*/
namespace _dali
{
/**
*/
export async function main(
)
: Promise<void>
{
// conf
await _dali.conf.init(
"conf.json"
);
// init:logger
lib_plankton.log.set_main_logger(
[
{
"kind": "minlevel",
"data": {
"core": {
"kind": "console",
"data": {
}
},
"threshold": "info"
}
},
]
);
// init:overlay
{
await _dali.overlay.initialize();
_dali.helpers.loading(true);
}
// init:localization
{
const order : Array<string> = ["deu", "eng"];
await lib_plankton.translate.initialize(
{
"verbosity": 1,
"packages": await Promise.all(
order.map(
code => (
Promise.resolve(code)
.then<string>(code => Promise.resolve(lib_plankton.string.coin("data/localization/{{code}}.loc.json", {"code": code})))
.then<string>(lib_plankton.file.read)
.then<any>(content => Promise.resolve(JSON.parse(content)))
)
)
),
"order": order,
"autopromote": false,
}
);
}
// init:backend
await _dali.backend.initialize(
_dali.conf.get()["backend"]
);
// init:model
await _dali.model.initialize(
);
// init:page
lib_plankton.zoo_page.init(
document.querySelector("main"),
{
"fallback": {
"name": "overview",
"parameters": {}
},
}
);
// init:menu
{
const widget_menu : _dali.widgets.class_widget_menu = new _dali.widgets.class_widget_menu(
[
{
"label": lib_plankton.translate.get("common.login"),
"groups": ["logged_out"],
"action": async () => {
_dali.helpers.loading(true);
const widget : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.class_widget_login(
{
"action_cancel": () => {
_dali.overlay.clear();
_dali.overlay.toggle({"mode": false});
},
"action_success": async () => {
_dali.helpers.loading(true);
const status = await _dali.backend.status();
_dali.notify_login(status.name);
// _dali.overlay.clear();
_dali.helpers.loading(false);
},
}
);
await widget.load(_dali.overlay.get_content_element());
// _dali.helpers.loading(false);
_dali.overlay.toggle({"mode": true});
},
},
{
"label": lib_plankton.translate.get("widget.caldav.title"),
"groups": ["logged_in"],
"action": async () => {
_dali.helpers.loading(true);
const widget : lib_plankton.zoo_widget.interface_widget = new _dali.widgets.class_widget_caldav();
await widget.load(_dali.overlay.get_content_element());
// _dali.helpers.loading(false);
_dali.overlay.toggle({"mode": true});
},
},
{
"label": lib_plankton.translate.get("common.logout"),
"groups": ["logged_in"],
"action": async () => {
_dali.helpers.loading(true);
await _dali.logout();
_dali.helpers.loading(false);
},
},
]
);
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);
}
);
}
// process actions
{
let url_search_params : URLSearchParams = new URLSearchParams(window.location.search);
const action : (null | string) = url_search_params.get("action");
switch (action)
{
case "oidc_finish":
{
try
{
await _dali.oidc_finish(url_search_params.get("session_key"));
}
catch (error_)
{
// do nothing
}
document.location = "/";
break;
}
default:
case null:
{
// do nothing
break;
}
}
}
// process status
{
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();
}
}
// load overview
{
const widget : lib_plankton.zoo_widget.interface_widget = (
new _dali.widgets.class_widget_overview(
)
);
await widget.load(document.querySelector("main"));
}
_dali.helpers.loading(false);
return Promise.resolve<void>(undefined);
}
}

View file

@ -1,692 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
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<void>)> = [];
/**
*/
function make_date_set(
{
"elements": elements = [],
}
:
{
elements ?: Array<lib_plankton.pit.type_date>;
}
=
{
}
)
: lib_plankton.set.type_set<lib_plankton.pit.type_date>
{
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<lib_plankton.pit.type_date>
{
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<void>
{
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<void>
{
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<void>
{
/*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<void>
{
/*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<void>
{
const queried_dates : lib_plankton.set.type_set<lib_plankton.pit.type_date> = timeframe_to_date_set(
timeframe
);
const new_dates : lib_plankton.set.type_set<lib_plankton.pit.type_date> = 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<Array<_dali.type_event_object_extended>>
{
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<void>
{
/**
* @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<void>
{
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<void>
{
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>)
)
: void
{
_listeners_reset.push(action);
}
/**
*/
async function notify_reset(
priviliged ?: boolean
)
: Promise<void>
{
for (const action of _listeners_reset)
{
await action(priviliged);
}
}
/**
*/
export async function initialize(
)
: Promise<void>
{
_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();
}
}

View file

@ -1,100 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
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<void>
{
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<void>(undefined);
}
}

View file

@ -0,0 +1,119 @@
namespace _zeitbild.frontend_web.pages
{
/**
*/
lib_plankton.zoo_page.register(
"caldav",
async (parameters, target_element) => {
target_element.innerHTML = "";
const conf = await _zeitbild.frontend_web.backend.user_dav_conf();
target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"caldav",
"main",
{
"label": lib_plankton.translate.get("page.caldav.title"),
"content": (
(conf === null)
?
await _zeitbild.frontend_web.helpers.template_coin(
"caldav",
"unavailable",
{
"text": lib_plankton.translate.get("page.caldav.unavailable"),
}
)
:
await _zeitbild.frontend_web.helpers.template_coin(
"caldav",
"available",
{
"conf_title": lib_plankton.translate.get("page.caldav.conf.title"),
"conf_content": (
(conf.password === null)
?
await _zeitbild.frontend_web.helpers.template_coin(
"caldav",
"conf-token_unset",
{
"text": lib_plankton.translate.get("page.caldav.conf.token_unset")
}
)
:
await _zeitbild.frontend_web.helpers.template_coin(
"caldav",
"conf-token_set",
{
"address_label": lib_plankton.translate.get("page.caldav.conf.address"),
"address_value": conf.address,
"username_label": lib_plankton.translate.get("page.caldav.conf.username"),
"username_value": conf.username,
"password_label": lib_plankton.translate.get("page.caldav.conf.password"),
"password_value": conf.password,
"setup_hints_label": lib_plankton.translate.get("page.caldav.conf.setup_hints"),
"setup_hint_entries": (
await lib_plankton.call.promise_condense<string, unknown>(
conf.setup_hints
.map(
entry => () => _zeitbild.frontend_web.helpers.template_coin(
"caldav",
"conf-setup_hint_entry",
{
"text": entry.label,
"href": entry.link,
"remark": (
(entry.remark === null)
?
""
:
lib_plankton.string.coin(
" — {{content}}",
{
"content": entry.remark,
}
)
)
}
)
)
)
).join("")
}
)
),
"set_token_title": lib_plankton.translate.get("page.caldav.set_token.title"),
"set_token_action": (
(conf.password === null)
?
lib_plankton.translate.get("page.caldav.set_token.action.set")
:
lib_plankton.translate.get("page.caldav.set_token.action.overwrite")
),
}
)
)
}
);
/**
* logic: set token
*/
{
if (conf !== null)
{
document.querySelector("#caldav-set_token > button").addEventListener(
"click",
async () => {
await _zeitbild.frontend_web.backend.user_dav_token();
lib_plankton.zoo_page.reload();
}
);
}
}
return Promise.resolve<void>(undefined);
}
);
}

View file

@ -1,8 +1,8 @@
<div class="widget-caldav-conf"> <div id="caldav-conf">
<h3>{{conf_title}}</h3> <h3>{{conf_title}}</h3>
{{conf_content}} {{conf_content}}
</div> </div>
<div class="widget-caldav-set_token"> <div id="caldav-set_token">
<h3>{{set_token_title}}</h3> <h3>{{set_token_title}}</h3>
<button>{{set_token_action}}</button> <button>{{set_token_action}}</button>
</div> </div>

View file

@ -1,3 +1,3 @@
<li class="widget-caldav-conf-setup_hints-entry"> <li class="caldav-conf-setup_hints-entry">
<a target="_blank" href="{{href}}">{{text}}</a>{{remark}} <a target="_blank" href="{{href}}">{{text}}</a>{{remark}}
</li> </li>

View file

@ -0,0 +1,18 @@
<div class="caldav-conf-section" id="caldav-conf-address">
<span class="caldav-conf-section-label">{{address_label}}</span>
<span class="caldav-conf-section-value caldav-conf-section-value-regular">{{address_value}}</span>
</div>
<div class="caldav-conf-section" id="caldav-conf-username">
<span class="caldav-conf-section-label">{{username_label}}</span>
<span class="caldav-conf-section-value caldav-conf-section-value-regular">{{username_value}}</span>
</div>
<div class="caldav-conf-section" id="caldav-conf-password">
<span class="caldav-conf-section-label">{{password_label}}</span>
<span class="caldav-conf-section-value caldav-conf-section-value-regular">{{password_value}}</span>
</div>
<div class="caldav-conf-section" id="caldav-conf-setup_hints">
<span class="caldav-conf-section-label">{{setup_hints_label}}</span>
<ul>
{{setup_hint_entries}}
</ul>
</div>

View file

@ -0,0 +1,4 @@
<div id="caldav-conf-info">
({{text}})
</div>

View file

@ -0,0 +1,6 @@
<div id="caldav">
<h2>{{label}}</h2>
<div id="caldav-content">
{{content}}
</div>
</div>

View file

@ -0,0 +1,3 @@
<div id="caldav-info">
{{text}}
</div>

View file

@ -0,0 +1,168 @@
namespace _zeitbild.frontend_web.pages
{
/**
*/
lib_plankton.zoo_page.register(
"calendar_add",
async (parameters, target_element) => {
target_element.innerHTML = "";
target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"calendar_add",
"default",
{
"label": lib_plankton.translate.get("page.calendar_add.title")
}
);
const form : lib_plankton.zoo_form.class_form<
_zeitbild.frontend_web.type.calendar_object,
{
name : string;
access : {
public : boolean;
default_level : _zeitbild.frontend_web.type.enum_access_level;
attributed : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.user_id,
_zeitbild.frontend_web.type.enum_access_level
>;
};
resource_kind : string;
}
> = new lib_plankton.zoo_form.class_form<
_zeitbild.frontend_web.type.calendar_object,
{
name : string;
access : {
public : boolean;
default_level : _zeitbild.frontend_web.type.enum_access_level;
attributed : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.user_id,
_zeitbild.frontend_web.type.enum_access_level
>;
};
resource_kind : string;
}
>(
(calendar_object) => ({
"name": calendar_object.name,
"access": calendar_object.access,
"resource_kind": calendar_object.resource.kind,
}),
(raw) => ({
"name": raw.name,
"access": raw.access,
"resource": (() => {
switch (raw.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: " + raw.resource_kind));
break;
}
}
}) (),
}),
new lib_plankton.zoo_input.class_input_group<any>(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("calendar.name")
},
{
"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": _zeitbild.frontend_web.helpers.input_access_level(),
"label": lib_plankton.translate.get("calendar.access.default_level"),
},
{
"name": "attributed",
"input": await _zeitbild.frontend_web.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"),
"target": "submit",
"procedure": async (get_value, get_representation) => {
const value : any = await get_value();
try {
await _zeitbild.frontend_web.backend.calendar_add(
value
);
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"));
return Promise.resolve<void>(undefined);
}
);
}

View file

@ -0,0 +1,5 @@
<div id="calendar_add">
<h2>{{label}}</h2>
<div id="calendar_add_form">
</div>
</div>

View file

@ -0,0 +1,171 @@
namespace _zeitbild.frontend_web.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 _zeitbild.frontend_web.helpers.template_coin(
"calendar_edit",
"default",
{
"label": lib_plankton.translate.get("page.calendar_edit.title.regular")
}
);
const form : lib_plankton.zoo_form.class_form<
_zeitbild.frontend_web.type.calendar_object,
{
name : string;
access : {
public : boolean;
default_level : _zeitbild.frontend_web.type.enum_access_level;
attributed : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.user_id,
_zeitbild.frontend_web.type.enum_access_level
>;
};
}
> = new lib_plankton.zoo_form.class_form<
_zeitbild.frontend_web.type.calendar_object,
{
name : string;
access : {
public : boolean;
default_level : _zeitbild.frontend_web.type.enum_access_level;
attributed : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.user_id,
_zeitbild.frontend_web.type.enum_access_level
>;
};
}
>(
(calendar_object) => ({
"name": calendar_object.name,
"access": calendar_object.access,
}),
(raw) => ({
"name": raw.name,
"access": raw.access,
"resource": {
"kind": "local",
"data": {
"events": [],
}
},
}),
new lib_plankton.zoo_input.class_input_group<any>(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text(),
"label": lib_plankton.translate.get("calendar.name")
},
{
"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": _zeitbild.frontend_web.helpers.input_access_level(),
"label": lib_plankton.translate.get("calendar.access.default_level"),
},
{
"name": "attributed",
"input": await _zeitbild.frontend_web.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"),
"target": "submit",
"procedure": async (get_value, get_representation) => {
const value : any = await get_value();
try {
await _zeitbild.frontend_web.backend.calendar_change(
calendar_id,
value
);
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"),
"target": "submit",
"procedure": async (get_value, get_representation) => {
try {
await _zeitbild.frontend_web.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 : _zeitbild.frontend_web.type.calendar_object = await _zeitbild.frontend_web.backend.calendar_get(
calendar_id
);
await form.input_write(calendar_object);
return Promise.resolve<void>(undefined);
}
);
}

View file

@ -0,0 +1,5 @@
<div id="calendar_edit">
<h2>{{label}}</h2>
<div id="calendar_edit_form">
</div>
</div>

View file

@ -0,0 +1,235 @@
namespace _zeitbild.frontend_web.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 _zeitbild.frontend_web.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 : _zeitbild.frontend_web.type.calendar_id;
event_object : _zeitbild.frontend_web.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 : _zeitbild.frontend_web.type.calendar_id;
event_object : _zeitbild.frontend_web.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<any>(
[
{
"name": "calendar_id",
"input": new lib_plankton.zoo_input.class_input_selection(
(
(await _zeitbild.frontend_web.backend.calendar_list())
.filter(
(entry) => (
(entry.access_level === _zeitbild.frontend_web.type.enum_access_level.edit)
||
(entry.access_level === _zeitbild.frontend_web.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": _zeitbild.frontend_web.helpers.datetime_input(),
"label": lib_plankton.translate.get("event.begin")
},
{
"name": "end",
"input": new lib_plankton.zoo_input.class_input_soft<lib_plankton.pit.type_datetime>(
_zeitbild.frontend_web.helpers.datetime_input()
),
"label": lib_plankton.translate.get("event.end")
},
{
"name": "location",
"input": new lib_plankton.zoo_input.class_input_soft<string>(
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<string>(
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<string>(
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 _zeitbild.frontend_web.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<void>(undefined);
}
);
}

View file

@ -0,0 +1,5 @@
<div id="event_add">
<h2>{{label}}</h2>
<div id="event_add_form">
</div>
</div>

View file

@ -0,0 +1,195 @@
namespace _zeitbild.frontend_web.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 _zeitbild.frontend_web.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<
_zeitbild.frontend_web.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<
_zeitbild.frontend_web.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<any>(
[
{
"name": "name",
"input": new lib_plankton.zoo_input.class_input_text(
),
"label": lib_plankton.translate.get("event.name")
},
{
"name": "begin",
"input": _zeitbild.frontend_web.helpers.datetime_input(),
"label": lib_plankton.translate.get("event.begin")
},
{
"name": "end",
"input": new lib_plankton.zoo_input.class_input_soft<lib_plankton.pit.type_datetime>(
_zeitbild.frontend_web.helpers.datetime_input()
),
"label": lib_plankton.translate.get("event.end")
},
{
"name": "location",
"input": new lib_plankton.zoo_input.class_input_soft<string>(
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<string>(
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<string>(
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 _zeitbild.frontend_web.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 _zeitbild.frontend_web.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 : _zeitbild.frontend_web.type.event_object = await _zeitbild.frontend_web.backend.calendar_event_get(
calendar_id,
event_id
);
await form.input_write(
event_object
);
return Promise.resolve<void>(undefined);
}
);
}

View file

@ -0,0 +1,5 @@
<div id="event_edit">
<h2>{{label}}</h2>
<div id="event_edit_form">
</div>
</div>

111
source/pages/login/logic.ts Normal file
View file

@ -0,0 +1,111 @@
namespace _zeitbild.frontend_web.pages
{
/**
*/
lib_plankton.zoo_page.register(
"login",
async (parameters, target_element) => {
target_element.innerHTML = "";
const preparation : {kind : string; data : any;} = await _zeitbild.frontend_web.backend.session_prepare(
{
"oidc_redirect_uri_template": _zeitbild.frontend_web.conf.get()["misc"]["oidc_redirect_uri_template"],
}
);
switch (preparation.kind) {
case "internal": {
target_element.innerHTML = await _zeitbild.frontend_web.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 _zeitbild.frontend_web.backend.session_begin(
value.name,
value.password
);
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<void>(undefined);
}
);
}

View file

@ -0,0 +1,2 @@
<div id="login">
</div>

View file

@ -0,0 +1,23 @@
namespace _zeitbild.frontend_web.pages
{
/**
*/
lib_plankton.zoo_page.register(
"logout",
async (parameters, target_element) => {
target_element.innerHTML = "";
await _zeitbild.frontend_web.backend.session_end(
);
lib_plankton.zoo_page.set(
{
"name": "overview",
"parameters": {
}
}
);
return Promise.resolve<void>(undefined);
}
);
}

View file

@ -0,0 +1,21 @@
namespace _zeitbild.frontend_web.pages
{
/**
*/
lib_plankton.zoo_page.register(
"oidc_finish",
async (parameters, target_element) => {
target_element.innerHTML = "";
await _zeitbild.frontend_web.backend.set_session_key(parameters["session_key"]);
lib_plankton.zoo_page.set(
{
"name": "overview",
"parameters": {}
}
);
return Promise.resolve<void>(undefined);
}
);
}

View file

@ -0,0 +1,197 @@
namespace _zeitbild.frontend_web.pages.overview
{
/**
*/
lib_plankton.zoo_page.register(
"overview",
async (parameters, target_element) => {
// params
const compact : boolean = (
(
parameters["compact"]
??
(
(window.innerWidth >= 800)
?
"no"
:
"yes"
)
)
===
"yes"
);
// exec
target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"overview",
"default",
{
}
);
target_element.querySelector("#overview").classList.toggle("overview-compact", compact);
let widget_weekview : _zeitbild.frontend_web.widgets.weekview.class_widget_weekview;
let widget_listview : _zeitbild.frontend_web.widgets.listview.class_widget_listview;
// hint
{
if (! await _zeitbild.frontend_web.backend.is_logged_in()) {
target_element.querySelector("#overview-head").textContent = lib_plankton.translate.get("page.overview.login_hint");
}
else {
// do nothing
}
}
// sources
{
const data : Array<
{
id : _zeitbild.frontend_web.type.calendar_id;
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
> = await _zeitbild.frontend_web.backend.calendar_list(
);
const widget_sources = new _zeitbild.frontend_web.widgets.sources.class_widget_sources(
data,
{
"action_open": (entry) => {
switch (entry.access_level) {
case _zeitbild.frontend_web.type.enum_access_level.none: {
throw (new Error("this event should not be visible"));
break;
}
case _zeitbild.frontend_web.type.enum_access_level.edit:
case _zeitbild.frontend_web.type.enum_access_level.view: {
lib_plankton.zoo_page.set(
{
"name": "calendar_edit",
"parameters": {
"read_only": "yes",
"calendar_id": entry.id,
}
}
);
break;
}
case _zeitbild.frontend_web.type.enum_access_level.admin: {
lib_plankton.zoo_page.set(
{
"name": "calendar_edit",
"parameters": {
"read_only": "no",
"calendar_id": entry.id,
}
}
);
break;
}
}
},
"action_toggle_visibility": (entry) => {
widget_weekview.toggle_visibility(entry.id);
},
}
);
await widget_sources.load(target_element.querySelector("#overview-pane-left"));
}
// events
{
const get_entries = (from_pit, to_pit, calendar_ids) => _zeitbild.frontend_web.backend.events(
from_pit,
to_pit,
{
"calendar_ids": calendar_ids,
}
);
const action_select_event = (calendar_id, access_level, event_id) => {
switch (access_level) {
case _zeitbild.frontend_web.type.enum_access_level.none: {
throw (new Error("this event should not be visible"));
break;
}
case _zeitbild.frontend_web.type.enum_access_level.view: {
lib_plankton.zoo_page.set(
{
"name": "event_edit",
"parameters": {
"read_only": "yes",
"calendar_id": calendar_id,
"event_id": event_id,
}
}
);
break;
}
case _zeitbild.frontend_web.type.enum_access_level.edit:
case _zeitbild.frontend_web.type.enum_access_level.admin: {
lib_plankton.zoo_page.set(
{
"name": "event_edit",
"parameters": {
"read_only": "no",
"calendar_id": calendar_id,
"event_id": event_id,
}
}
);
break;
}
}
};
// listview
{
widget_listview = (
new _zeitbild.frontend_web.widgets.listview.class_widget_listview(
get_entries,
{
"action_select_event": action_select_event,
"action_add": () => {
lib_plankton.zoo_page.set(
{
"name": "event_add",
"parameters": {
"calendar_id": null,
"year": null,
"month": null,
"day": null,
}
}
);
},
}
)
);
await widget_listview.load(target_element.querySelector("#overview-pane-right-listview"));
}
// weekview
{
widget_weekview = (
new _zeitbild.frontend_web.widgets.weekview.class_widget_weekview(
get_entries,
{
"action_select_event": action_select_event,
"action_select_day": (date) => {
lib_plankton.zoo_page.set(
{
"name": "event_add",
"parameters": {
"calendar_id": null,
"year": date.year,
"month": date.month,
"day": date.day,
}
}
);
},
}
)
);
await widget_weekview.load(target_element.querySelector("#overview-pane-right-weekview"));
}
}
return Promise.resolve<void>(undefined);
},
);
}

View file

@ -0,0 +1,14 @@
<div id="overview">
<div id="overview-head">
</div>
<div id="overview-body">
<div id="overview-pane-left">
</div>
<div id="overview-pane-right">
<div id="overview-pane-right-weekview">
</div>
<div id="overview-pane-right-listview">
</div>
</div>
</div>
</div>

View file

@ -1,799 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
/**
*/
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 : (
null
|
lib_plankton.storage.type_chest<string, string, void, string, string>
) = null;
/**
*/
var _cache : (
null
|
lib_plankton.cache.type_subject<boolean>
);
/**
*/
var _queue : lib_plankton.call.type_queue<type_request, any>;
/**
*/
export async function initialize(
conf : type_conf
)
: Promise<void>
{
_conf = conf;
_data_chest = lib_plankton.storage.localstorage.implementation_chest(
{
"corner": "zeitbild",
}
);
_cache = lib_plankton.cache.make<boolean>(
/*
lib_plankton.storage.memory.implementation_chest(
{
}
)
*/
);
_queue = lib_plankton.call.queue_make<type_request, any>(
call_real
);
return Promise.resolve<void>(undefined);
}
/**
*/
async function get_session_key(
)
: Promise<(null | string)>
{
try
{
return (await _data_chest.read("session_key"));
}
catch (error)
{
return null;
}
}
/**
*/
async function call_real(
request : type_request
)
: Promise<any>
{
const with_body : boolean = (
[
lib_plankton.http.enum_method.post,
lib_plankton.http.enum_method.put,
lib_plankton.http.enum_method.patch,
].includes(request.method)
);
const session_key : (null | string) = await get_session_key();
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/2",
"scheme": (
(_conf.scheme === "http")
?
"http"
:
"https"
),
"host": lib_plankton.string.coin(
"{{host}}:{{port}}",
{
"host": _conf.host,
"port": _conf.port.toFixed(0),
}
),
"path": lib_plankton.string.coin(
"{{base}}{{action}}",
{
"base": _conf.path,
"action": request.action,
}
),
"method": request.method,
"query": (
(with_body || (request.input === null))
?
null
:
("?" + lib_plankton.www_form.encode(request.input))
),
"headers": Object.assign(
{},
(
(! with_body)
?
{}
:
{"Content-Type": "application/json"}
),
(
(session_key === null)
?
{}
:
{"X-Session-Key": session_key}
)
),
"body": (
((! with_body) || (request.input === null))
?
null
:
/*Buffer.from*/(lib_plankton.json.encode(request.input))
),
};
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(http_request);
if (
! (
(http_response.status_code >= 200)
&&
(http_response.status_code < 300)
)
)
{
return Promise.reject<any>(http_response.body.toString());
}
else
{
const output : any = lib_plankton.json.decode(http_response.body.toString());
return Promise.resolve<any>(output);
}
}
/**
*/
async function call(
method : lib_plankton.http.enum_method,
action : string,
input : (null | any)
)
: Promise<any>
{
const request : type_request = {
"method": method,
"action": action,
"input": input,
};
return (
new Promise<any>(
(resolve, reject) => {
lib_plankton.call.queue_add<type_request, any>(
_queue,
request,
resolve,
reject
);
}
)
);
}
/**
*/
export function status(
)
: Promise<
{
logged_in : boolean;
name : (null | string);
}
>
{
return call(
lib_plankton.http.enum_method.get,
"/session/status",
null
)
}
/**
*/
export async function session_prepare(
input : any
)
: Promise<{kind : string; data : any;}>
{
return call(
lib_plankton.http.enum_method.post,
"/session/prepare",
input
);
}
/**
*/
export function set_session_key(
session_key : string
)
: Promise<void>
{
return (
_data_chest.write("session_key", session_key)
.then<void>(() => Promise.resolve<void>(undefined))
);
}
/**
*/
export async function session_begin(
name : string,
password : string
)
: Promise<void>
{
const session_key : string = await call(
lib_plankton.http.enum_method.post,
"/session/begin",
{
"name": name,
"password": password,
}
);
await _data_chest.write("session_key", session_key);
return Promise.resolve<void>(undefined);
}
/**
*/
export async function session_end(
)
: Promise<void>
{
await call(
lib_plankton.http.enum_method.delete,
"/session/end",
null
);
await _data_chest.delete("session_key");
return Promise.resolve<void>(undefined);
}
/**
*/
export function group_list(
)
: Promise<
Array<
{
id : int;
name : string;
label : string;
}
>
>
{
return call(
lib_plankton.http.enum_method.get,
"/groups",
null
);
}
/**
*/
export function user_list(
)
: Promise<
Array<
{
id : int;
name : string;
}
>
>
{
return call(
lib_plankton.http.enum_method.get,
"/users",
null
);
}
/**
*/
export function user_dav_conf(
)
: Promise<
(
null
|
{
address : string;
username : string;
password : string;
setup_hints : Array<
{
label : string;
link : string;
remark : (null | string);
}
>;
}
)
>
{
return call(
lib_plankton.http.enum_method.get,
"/user_dav_conf",
null
);
}
/**
*/
export function user_dav_token(
)
: Promise<void>
{
return call(
lib_plankton.http.enum_method.patch,
"/user_dav_token",
null
);
}
/**
*/
export async function calendar_list(
)
: Promise<
Array<
{
id : int;
name : string;
hue : float;
access_level : string;
}
>
>
{
return call(
lib_plankton.http.enum_method.get,
"/calendar",
null
);
}
/**
*/
export async function calendar_get(
calendar_id : int
)
: Promise<
{
name : string;
hue : float;
access : {
public : boolean;
default_level : string;
attributed_group : Array<
{
group_id : int;
level : string;
}
>;
attributed_user : Array<
{
user_id : int;
level : string;
}
>;
};
resource_id : int;
}
>
{
return call(
lib_plankton.http.enum_method.get,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}",
{
"calendar_id": calendar_id.toFixed(0),
}
),
null
);
}
/**
*/
export async function calendar_add(
data : {
name : string;
access : {
public : boolean;
default_level : string;
attributed_group : Array<
{
group_id : int;
level : string;
}
>;
attributed_user : 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_group : Array<
{
group_id : int;
level : string;
}
>;
attributed_user : 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 : int,
event_id : int
)
: Promise<
type_event_object
>
{
return call(
lib_plankton.http.enum_method.get,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event/{{event_id}}",
{
"calendar_id": calendar_id.toFixed(0),
"event_id": event_id.toFixed(0),
}
),
null
);
}
/**
*/
export async function calendar_event_add(
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,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event",
{
"calendar_id": calendar_id.toFixed(0),
}
),
event_data
);
}
/**
* @todo Möglichkeit den Kalender zu ändern
*/
export async function calendar_event_change(
calendar_id : int,
event_id : int,
event_object : type_event_object
)
: Promise<void>
{
return call(
lib_plankton.http.enum_method.put,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event/{{event_id}}",
{
"calendar_id": calendar_id.toFixed(0),
"event_id": event_id.toFixed(0),
}
),
event_object
);
}
/**
*/
export async function calendar_event_remove(
calendar_id : int,
event_id : int
)
: Promise<void>
{
return call(
lib_plankton.http.enum_method.delete,
lib_plankton.string.coin(
"/calendar/{{calendar_id}}/event/{{event_id}}",
{
"calendar_id": calendar_id.toFixed(0),
"event_id": event_id.toFixed(0),
}
),
null
);
}
/**
* @todo prevent loops
*/
export async function events(
from_timestamp : int,
to_timestamp : int,
{
"calendar_ids": calendar_ids = null,
}
:
{
calendar_ids ?: (null | Array<int>);
}
=
{
}
)
: Promise<
Array<
{
hash : string;
calendar_id : int;
calendar_name : string;
hue : float;
access_level : string;
event_id : (null | int);
event_object : type_event_object;
}
>
>
{
return call(
lib_plankton.http.enum_method.get,
"/events",
Object.assign(
{
"from": from_timestamp,
"to": to_timestamp,
},
(
(calendar_ids === null)
?
{}
:
{"calendar_ids": calendar_ids.join(",")}
)
)
);
}
}

4
source/style/hacks.css Normal file
View file

@ -0,0 +1,4 @@
.plankton_input_group_field[rel="resource_kind"]
{
display: none;
}

View file

@ -5,56 +5,77 @@
html html
{ {
background-color: hsl(var(--hue), 0%, 12.5%); background-color: hsl(0, 0%, 12.5%);
color: hsl(var(--hue), 0%, 100%); color: hsl(0, 0%, 100%);
font-family: sans-serif; font-family: sans-serif;
} }
header header
{ {
/*
background-color: hsl(0, 0%, 25%); background-color: hsl(0, 0%, 25%);
/*
border-bottom: 2px solid #888; border-bottom: 2px solid #888;
padding-bottom: 16px; padding-bottom: 16px;
*/ */
margin-bottom: 16px; margin-bottom: 16px;
} }
a nav > ul
{ {
list-style-type: none;
margin: 0;
padding: 0;
}
nav > ul > li
{
display: inline-block;
margin: 8px;
padding: 8px;
}
nav a
{
padding: 8px;
text-decoration: none; text-decoration: none;
color: hsl(var(--hue), 0%, 87.5%); color: hsl(var(--hue), 0%, 87.5%);
} }
a:hover nav a:hover
{ {
color: hsl(var(--hue), 0%, 100%); color: hsl(var(--hue), 0%, 100%);
border-bottom: 2px solid hsl(0, 0%, 100%); border-bottom: 2px solid hsl(0, 0%, 100%);
transition: 1s ease color; transition: 1s ease color;
} }
button a
{ {
padding: 8px 12px; text-decoration: none;
text-transform: uppercase; color: hsl(var(--hue), 50%, 50%);
cursor: pointer; }
background-color: hsl(var(--hue), 0%, 6.125%); a:hover
border: 1px solid hsl(var(--hue), 0%, 6.125%); {
color: hsl(0, 0%, 87.5%); color: hsl(var(--hue), 50%, 75%);
margin: 4px;
border-radius: 4px;
} }
input,select,textarea input,select,textarea
{ {
background-color: hsl(0, 0%, 25%);
border: 1px solid hsl(0, 0%, 25%);
color: hsl(0, 0%, 100%);
padding: 4px; padding: 4px;
margin: 4px; }
border-radius: 4px;
/* button
font-family: monospace; {
*/ padding: 8px;
text-transform: uppercase;
cursor: pointer;
}
input,select,textarea,button
{
background-color: hsl(0, 0%, 0%);
color: hsl(0, 0%, 100%);
border: 1px solid hsl(0, 0%, 75%);
margin: 4px;
border-radius: 2px;
} }

View file

@ -1,33 +0,0 @@
#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;
overflow: auto;
}
#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;
}

View file

@ -1,9 +1,9 @@
.widget-caldav-conf-section .caldav-conf-section
{ {
margin-bottom: 16px; margin-bottom: 16px;
} }
.widget-caldav-conf-section-label .caldav-conf-section-label
{ {
margin-left: 16px; margin-left: 16px;
display: block; display: block;
@ -11,12 +11,12 @@
text-transform: capitalize; text-transform: capitalize;
} }
.widget-caldav-conf-section-value .caldav-conf-section-value
{ {
margin-left: 32px; margin-left: 32px;
} }
.widget-caldav-conf-section-value-regular .caldav-conf-section-value-regular
{ {
font-family: monospace; font-family: monospace;
} }

View file

@ -0,0 +1,13 @@
#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;
}

View file

@ -0,0 +1,8 @@
#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;
}

View file

@ -0,0 +1,8 @@
#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;
}

View file

@ -0,0 +1,63 @@
#overview-head
{
text-align: center;
font-weight: bold;
margin-bottom: 12px;
}
#overview-body
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
#overview-body #overview-pane-left
{
flex-grow: 0;
flex-shrink: 1;
}
#overview-body #overview-pane-right
{
flex-grow: 1;
flex-shrink: 1;
}
@media all and (max-width: 950px)
{
#overview #overview-pane-left
{
flex-basis: 0%;
}
#overview #overview-pane-left > *
{
display: none;
}
#overview #overview-pane-right
{
flex-basis: 100%;
}
#overview #overview-pane-right-listview {}
#overview #overview-pane-right-weekview {display: none;}
}
@media not all and (max-width: 950px)
{
#overview #overview-pane-left
{
flex-basis: 12.5%;
}
#overview #overview-pane-right
{
flex-basis: 87.5%;
}
#overview #overview-pane-right-listview {display: none;}
#overview #overview-pane-right-weekview {}
}

View file

@ -1,33 +1,26 @@
.plankton_input_group_field .plankton_input_group_field {
{
margin-bottom: 8px; margin-bottom: 8px;
} }
.plankton_input_group_field_label .plankton_input_group_field_label {
{
display: block; display: block;
font-weight: bold; font-weight: bold;
font-size: 0.8em; font-size: 0.8em;
text-transform: uppercase;
} }
.plankton_input_soft_container > * .plankton_input_soft_container > * {
{
display: inline-block; display: inline-block;
} }
.plankton_input_soft_setter .plankton_input_soft_setter {
{
margin-right: 8px; margin-right: 8px;
} }
.plankton_input_soft_inactive .plankton_input_soft_inactive {
{
display: none !important; display: none !important;
} }
.plankton_input_group .plankton_input_group {
{
margin-left: 24px; margin-left: 24px;
} }
@ -51,8 +44,3 @@
{ {
vertical-align: top; vertical-align: top;
} }
.plankton_input_password_exhibit
{
display: none;
}

View file

@ -1,6 +1,8 @@
.listview-add .listview-add
{ {
margin-left: 12px; /*
text-transform: capitalize;
*/
} }
.listview-add-hidden .listview-add-hidden
@ -24,11 +26,6 @@
cursor: pointer; cursor: pointer;
} }
.listview-entry-hidden
{
display: none;
}
.listview-entry-name .listview-entry-name
{ {
font-weight: bold; font-weight: bold;

View file

@ -0,0 +1,52 @@
.sources
{
margin: 0;
padding: 0;
list-style-type: none;
font-size: 0.75em;
}
.sources-entry
{
margin: 8px;
padding: 4px;
}
.sources-entry-head
{
cursor: pointer;
}
.sources-entry-body
{
display: block;
transition: max-height ease 0.5s;
}
.sources-entry-body > ul
{
list-style-type: none;
padding: 0;
margin-left: 8px;
margin-bottom: 8px;
}
.sources-entry-body > ul > li
{
/*
display: block;
*/
margin-top: 8px;
cursor: pointer;
}
.sources-entry:not(.sources-entry-open) > .sources-entry-head {}
.sources-entry:not(.sources-entry-open) > .sources-entry-body {max-height: 0; overflow: hidden;}
.sources-entry.sources-entry-open > .sources-entry-head {}
.sources-entry.sources-entry-open > .sources-entry-body {max-height: 240px; overflow: auto;}
.sources-entry-hidden
{
filter: saturate(0);
}

View file

@ -1,32 +1,12 @@
.weekview-controls .weekview-controls
{ {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: right;
margin-bottom: 12px; margin-bottom: 12px;
text-align: center; text-align: center;
} }
.weekview-controls > * .weekview-control
{ {
flex-basis: 0; margin: 0 12px;
flex-grow: 0;
flex-shrink: 1;
margin: 0 0 0 16px;
}
.weekview-control-label
{
display: block;
font-size: 0.75em;
/*
text-transform: uppercase;
*/
} }
.weekview-table table .weekview-table table
@ -44,21 +24,18 @@
.weekview-cell-day .weekview-cell-day
{ {
/* todo */
width: 13.5%; width: 13.5%;
} }
.weekview-cell-week .weekview-cell-week
{ {
/* todo */
width: 5.5%; width: 5.5%;
} }
.weekview-cell-regular .weekview-cell-regular
{ {
/* todo */
width: 13.5%; width: 13.5%;
height: 100px; height: 120px;
cursor: copy; cursor: copy;
} }
@ -76,12 +53,12 @@
.weekview-day .weekview-day
{ {
font-size: 0.75em; font-size: 0.75em;
cursor: help;
} }
.weekview-events .weekview-events
{ {
margin: 0; margin: 0; padding: 0;
padding: 0;
list-style-type: none; list-style-type: none;
} }

View file

@ -1,334 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
/**
*/
namespace _dali
{
/**
*/
export enum enum_access_level
{
none,
view,
edit,
admin
}
/**
*/
export type type_group_id = int;
/**
*/
export type type_group_object = {
name : string;
label : string;
};
/**
*/
export type type_user_id = int;
/**
*/
export type type_user_object = {
name : string;
label : 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_group : lib_plankton.map.type_map<
type_group_id,
enum_access_level
>;
attributed_user : 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 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 enum enum_view_mode
{
week,
list,
}
/**
*/
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<string, _dali.enum_view_mode> = {
"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];
}
}
/**
*/
export enum enum_view_kind
{
regular,
touch,
}
/**
*/
export function view_kind_encode(
kind : _dali.enum_view_kind
)
: string
{
switch (kind)
{
case _dali.enum_view_kind.regular: {return "regular"; break;}
case _dali.enum_view_kind.touch: {return "touch"; break;}
default: {throw (new Error("invalid kind"));}
}
}
/**
*/
export function view_kind_decode(
view_kind_encoded : string
)
: _dali.enum_view_kind
{
const map : Record<string, _dali.enum_view_kind> = {
"regular": _dali.enum_view_kind.regular,
"touch": _dali.enum_view_kind.touch,
};
if (! (view_kind_encoded in map))
{
throw (new Error("invalid kind: " + view_kind_encoded));
}
else
{
return map[view_kind_encoded];
}
}
}

View file

@ -1,173 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
export class class_widget_caldav
{
/**
*/
public constructor(
)
{
}
/**
*/
public async load(
target_element : HTMLElement
)
: Promise<void>
{
target_element.innerHTML = "";
const conf = await _dali.backend.user_dav_conf();
target_element.innerHTML = await _dali.helpers.template_coin(
"widget-caldav",
"main",
{
"title": lib_plankton.translate.get("widget.caldav.title"),
"content": (
(conf === null)
?
await _dali.helpers.template_coin(
"widget-caldav",
"unavailable",
{
"text": lib_plankton.translate.get("widget.caldav.unavailable"),
}
)
:
await _dali.helpers.template_coin(
"widget-caldav",
"available",
{
"conf_title": lib_plankton.translate.get("widget.caldav.conf.title"),
"conf_content": (
(conf.password === null)
?
await _dali.helpers.template_coin(
"widget-caldav",
"conf-token_unset",
{
"text": lib_plankton.translate.get("widget.caldav.conf.token_unset")
}
)
:
await _dali.helpers.template_coin(
"widget-caldav",
"conf-token_set",
{
"address_label": lib_plankton.translate.get("widget.caldav.conf.address"),
"address_value": conf.address,
"username_label": lib_plankton.translate.get("widget.caldav.conf.username"),
"username_value": conf.username,
"password_label": lib_plankton.translate.get("widget.caldav.conf.password"),
"password_value": conf.password,
"setup_hints_label": lib_plankton.translate.get("widget.caldav.conf.setup_hints"),
"setup_hint_entries": (
await lib_plankton.call.promise_condense<string, unknown>(
conf.setup_hints
.map(
entry => () => _dali.helpers.template_coin(
"widget-caldav",
"conf-setup_hint_entry",
{
"text": entry.label,
"href": entry.link,
"remark": (
(entry.remark === null)
?
""
:
lib_plankton.string.coin(
" — {{content}}",
{
"content": entry.remark,
}
)
)
}
)
)
)
).join("")
}
)
),
"set_token_title": lib_plankton.translate.get("widget.caldav.set_token.title"),
"set_token_action": (
(conf.password === null)
?
lib_plankton.translate.get("widget.caldav.set_token.action.set")
:
lib_plankton.translate.get("widget.caldav.set_token.action.overwrite")
),
}
)
),
"close": lib_plankton.translate.get("common.close"),
}
);
// logic
{
// set token
{
if (conf === null)
{
// do nothing
}
else
{
target_element.querySelector(".widget-caldav-set_token > button").addEventListener(
"click",
async () => {
await _dali.backend.user_dav_token();
await this.load(target_element);
}
);
}
}
// close
{
target_element.querySelector(".widget-caldav-close").addEventListener(
"click",
() => {
_dali.overlay.toggle({"mode": false});
}
);
}
}
// init
{
(target_element.querySelector(".widget-caldav-close") as HTMLElement).focus();
}
return Promise.resolve<void>(undefined);
}
}
}

View file

@ -1,18 +0,0 @@
<div class="widget-caldav-conf-section" class="widget-caldav-conf-address">
<span class="widget-caldav-conf-section-label">{{address_label}}</span>
<span class="widget-caldav-conf-section-value widget-caldav-conf-section-value-regular">{{address_value}}</span>
</div>
<div class="widget-caldav-conf-section" class="widget-caldav-conf-username">
<span class="widget-caldav-conf-section-label">{{username_label}}</span>
<span class="widget-caldav-conf-section-value widget-caldav-conf-section-value-regular">{{username_value}}</span>
</div>
<div class="widget-caldav-conf-section" class="widget-caldav-conf-password">
<span class="widget-caldav-conf-section-label">{{password_label}}</span>
<span class="widget-caldav-conf-section-value widget-caldav-conf-section-value-regular">{{password_value}}</span>
</div>
<div class="widget-caldav-conf-section" class="widget-caldav-conf-setup_hints">
<span class="widget-caldav-conf-section-label">{{setup_hints_label}}</span>
<ul>
{{setup_hint_entries}}
</ul>
</div>

View file

@ -1,4 +0,0 @@
<div class="widget-caldav-conf-info">
({{text}})
</div>

View file

@ -1,8 +0,0 @@
<div class="widget-caldav">
<h2>{{title}}</h2>
<div class="widget-caldav-content">
{{content}}
</div>
<hr/>
<button class="widget-caldav-close">{{close}}</button>
</div>

View file

@ -1,3 +0,0 @@
<div class="widget-caldav-info">
{{text}}
</div>

View file

@ -1,263 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
export class class_widget_calendar_edit
implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private groups : Array<{id : _dali.type_group_id; object : _dali.type_group_object;}>;
/**
*/
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 : _dali.type_calendar_object;
/**
*/
public constructor(
groups : Array<{id : _dali.type_group_id; object : _dali.type_group_object;}>,
users : Array<{id : _dali.type_user_id; name : string;}>,
initial_value : _dali.type_calendar_object,
{
"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 : _dali.type_calendar_object) => void))
action_change ?: (null | ((value : _dali.type_calendar_object) => void));
action_remove ?: (null | ((value : _dali.type_calendar_object) => void));
}
=
{
}
)
{
this.groups = groups;
this.users = users;
this.initial_value = initial_value;
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;
}
/**
* [implementation]
*/
public async load(
target_element : HTMLElement
) : Promise<void>
{
const dom_root = await _dali.helpers.element_from_template(
"widget-calendar_edit",
"main",
{
}
);
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<any>(
[
{
"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_group",
"input": _dali.helpers.input_attributed_access_group(this.groups),
"label": lib_plankton.translate.get("calendar.access.attributed_group"),
},
{
"name": "attributed_user",
"input": _dali.helpers.input_attributed_access_user(this.users),
"label": lib_plankton.translate.get("calendar.access.attributed_user"),
},
]
),
"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_remove === null)))
?
[
{
"label": lib_plankton.translate.get("widget.calendar_edit.actions.remove"),
"procedure": async (get_value, get_representation) => {
if (! window.confirm(lib_plankton.translate.get("common.confirm_deletion")))
{
// do nothing
}
else
{
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(dom_root);
await form.input_lock(this.read_only);
await form.input_write(this.initial_value);
target_element.appendChild(dom_root);
form.input_focus();
}
}
}

View file

@ -1,4 +0,0 @@
.widget-calendar_edit .plankton_input_group_field[rel="resource"]
{
display: none;
}

View file

@ -1,2 +0,0 @@
<div class="widget-calendar_edit">
</div>

View file

@ -1,336 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
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<void>
{
const dom_root = await _dali.helpers.element_from_template(
"widget-event_edit",
"main",
{
}
);
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<any>(
[
{
"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<lib_plankton.pit.type_datetime>(
_dali.helpers.datetime_input(
)
),
"label": lib_plankton.translate.get("event.end")
},
{
"name": "event_location",
"input": new lib_plankton.zoo_input.class_input_soft<string>(
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<string>(
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<string>(
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_remove === null)))
?
[
{
"label": lib_plankton.translate.get("widget.event_edit.actions.remove"),
"procedure": async (get_value, get_representation) => {
if (! window.confirm(lib_plankton.translate.get("common.confirm_deletion")))
{
// do nothing
}
else
{
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(dom_root);
await form.input_lock(this.read_only);
await form.input_write(this.initial_value);
target_element.appendChild(dom_root);
form.input_focus();
}
}
}

View file

@ -1,2 +0,0 @@
<div class="widget-event_edit">
</div>

View file

@ -1,56 +1,47 @@
/* namespace _zeitbild.frontend_web.widgets.listview
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{ {
/**
*/
type type_entry = {
calendar_id : _zeitbild.frontend_web.type.calendar_id;
calendar_name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
};
/** /**
*/ */
type type_get_entries = ( type type_get_entries = (
( (
from_pit : lib_plankton.pit.type_pit, from_pit : lib_plankton.pit.type_pit,
to_pit : lib_plankton.pit.type_pit, to_pit : lib_plankton.pit.type_pit,
calendar_ids : Array<_dali.type_calendar_id> calendar_ids : Array<_zeitbild.frontend_web.type.calendar_id>
) )
=> =>
Promise<Array<_dali.type_event_object_extended>> Promise<Array<type_entry>>
); );
/** /**
*/ */
export class class_widget_listview implements lib_plankton.zoo_widget.interface_widget export class class_widget_listview extends _zeitbild.class_widget
{ {
/** /**
* [dependency]
*/ */
private get_entries : type_get_entries; private get_entries : type_get_entries;
/** /**
* [hook]
*/ */
private action_select : ( private action_select_event : (
( (
event_key : _dali.type_event_key calendar_id : _zeitbild.frontend_web.type.calendar_id,
access_level : _zeitbild.frontend_web.type.enum_access_level,
event_id : _zeitbild.frontend_web.type.local_resource_event_id
) )
=> =>
void void
@ -58,7 +49,6 @@ namespace _dali.widgets
/** /**
* [hook]
*/ */
private action_add : ( private action_add : (
( (
@ -68,32 +58,16 @@ namespace _dali.widgets
); );
/**
*/
private include_passed : boolean;
/**
* [state]
*/
private container : (null | Element);
/** /**
*/ */
public constructor( public constructor(
get_entries : type_get_entries, get_entries : type_get_entries,
{ options : {
"include_passed": include_passed = false, action_select_event ?: (
"action_select": action_select = ((event_key) => {}),
"action_add": action_add = (() => {}),
}
:
{
include_passed ?: boolean;
action_select ?: (
( (
event_key : _dali.type_event_key calendar_id : _zeitbild.frontend_web.type.calendar_id,
access_level : _zeitbild.frontend_web.type.enum_access_level,
event_id : _zeitbild.frontend_web.type.local_resource_event_id
) )
=> =>
void void
@ -104,75 +78,34 @@ namespace _dali.widgets
=> =>
void void
); );
} } = {}
=
{
}
) )
{ {
// dependencies options = Object.assign(
{
"action_select_event": (calendar_id, access_level, event_id) => {},
"action_select_add": () => {},
},
options
);
super();
this.get_entries = get_entries; this.get_entries = get_entries;
this.action_select_event = options.action_select_event;
// hooks this.action_add = options.action_add;
this.action_select = action_select;
this.action_add = action_add;
// state
this.include_passed = include_passed;
this.container = null;
} }
/** /**
* [implementation]
*/ */
public toggle_visibility( public async load(
calendar_id : _dali.type_calendar_id, target_element : Element
{ ) : Promise<void>
"mode": mode = null,
}
:
{
mode ?: (null | boolean);
}
=
{
}
)
: void
{
this.container.querySelectorAll(".listview-entry").forEach(
(element) => {
const rel : string = element.getAttribute("rel");
const parts : Array<string> = rel.split("/");
const calendar_id_ : _dali.type_calendar_id = parseInt(parts[0]);
if (! (calendar_id === calendar_id_))
{
// do nothing
}
else
{
element.classList.toggle(
"listview-entry-hidden",
((mode !== null) ? (! mode) : undefined)
);
}
}
);
}
/**
*/
public async update_entries(
)
: Promise<void>
{
// structure
{ {
const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now(); const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now();
const from_pit : lib_plankton.pit.type_pit = now_pit; const from_pit : lib_plankton.pit.type_pit = now_pit;
const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.shift_week(now_pit, +4); const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.shift_week(now_pit, +4);
const entries : Array<_dali.type_event_object_extended> = await this.get_entries( const entries : Array<type_entry> = await this.get_entries(
from_pit, from_pit,
to_pit, to_pit,
null null
@ -184,25 +117,28 @@ namespace _dali.widgets
lib_plankton.pit.from_datetime(y.event_object.begin) lib_plankton.pit.from_datetime(y.event_object.begin)
) )
); );
const dom_list : HTMLElement = this.container.querySelector(".listview-entries");
dom_list.innerHTML = ( // view
( {
await _dali.helpers.promise_row<string>( target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
entries "widget-listview",
.filter( "main",
(entry) => ( {
this.include_passed "add_href": "",
"add_label": lib_plankton.translate.get("widget.listview.add"),
"add_extra_classes": (
(! await _zeitbild.frontend_web.backend.is_logged_in())
? ?
true " listview-add-hidden"
: :
lib_plankton.pit.is_after( ""
lib_plankton.pit.from_datetime(entry.event_object.begin), ),
now_pit "entries": (
) (
) await _zeitbild.frontend_web.helpers.promise_row<string>(
) entries
.map( .map(
(entry) => () => _dali.helpers.template_coin( (entry) => () => _zeitbild.frontend_web.helpers.template_coin(
"widget-listview", "widget-listview",
"entry", "entry",
{ {
@ -264,66 +200,43 @@ namespace _dali.widgets
entry.event_object.description entry.event_object.description
), ),
"raw": JSON.stringify(entry), "raw": JSON.stringify(entry),
"color": _dali.helpers.event_color(entry.hue), "color": lib_plankton.color.output_hex(
"rel": entry.key, lib_plankton.color.give_generic(
(entry.calendar_id - 1),
{
"saturation": 0.375,
"value": 0.375,
}
),
),
"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": (() => {
switch (entry.access_level) {
case _zeitbild.frontend_web.type.enum_access_level.none: return "none";
case _zeitbild.frontend_web.type.enum_access_level.view: return "view";
case _zeitbild.frontend_web.type.enum_access_level.edit: return "edit";
case _zeitbild.frontend_web.type.enum_access_level.admin: return "admin";
}
}) (),
}
),
}, },
) )
) )
) )
) )
.join("") .join("")
);
}
// listeners
{
this.container.querySelectorAll(".listview-entry").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
if ((event.target as Element).nodeName === "A")
{
// do nothing
}
else
{
const event_key : string = element.getAttribute("rel");
this.action_select(event_key);
}
}
);
}
);
}
}
/**
* [implementation]
*/
public async load(
target_element : Element
)
: Promise<void>
{
this.container = target_element;
// view
{
target_element.innerHTML = await _dali.helpers.template_coin(
"widget-listview",
"main",
{
"add_href": "",
"add_label": lib_plankton.translate.get("widget.listview.add"),
"add_extra_classes": (
(! await _dali.is_logged_in())
?
" listview-add-hidden"
:
""
), ),
"entries": "",
} }
); );
} }
@ -337,9 +250,44 @@ namespace _dali.widgets
this.action_add(); this.action_add();
} }
); );
target_element.querySelectorAll(".listview-entry").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
if ((event.target as Element).nodeName === "A") {
// do nothing
}
else {
const rel : string = element.getAttribute("rel");
const parts : Array<string> = rel.split("/");
const calendar_id : _zeitbild.frontend_web.type.calendar_id = parseInt(parts[0]);
const event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id) = (
parts[1] === "-"
?
null
:
parseInt(parts[1])
);
const access_level : _zeitbild.frontend_web.type.enum_access_level = (() => {
switch (parts[2]) {
case "none": return _zeitbild.frontend_web.type.enum_access_level.none;
case "view": return _zeitbild.frontend_web.type.enum_access_level.view;
case "edit": return _zeitbild.frontend_web.type.enum_access_level.edit;
case "admin": return _zeitbild.frontend_web.type.enum_access_level.admin;
}
}) ();
this.action_select_event(
calendar_id,
access_level,
event_id,
);
}
}
);
}
);
} }
await this.update_entries();
return Promise.resolve<void>(undefined); return Promise.resolve<void>(undefined);
} }

View file

@ -1,126 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
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 : HTMLElement
)
: Promise<void>
{
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":
{
const sub_widget : lib_plankton.zoo_widget.interface_widget = (
new class_widget_login_internal(
preparation.data,
{
"initial_name": this.initial_name,
"action_cancel": this.action_cancel,
"action_success": this.action_success,
}
)
);
await sub_widget.load(target_element);
break;
}
case "oidc":
{
const sub_widget : lib_plankton.zoo_widget.interface_widget = (
new class_widget_login_oidc(
preparation.data,
{
"action_cancel": this.action_cancel,
"action_success": this.action_success,
}
)
);
await sub_widget.load(target_element);
break;
}
default:
{
throw (new Error("unhandled login kind: " + preparation.kind));
break;
}
}
}
}
}

View file

@ -1,165 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
export class class_widget_login_internal
implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private preparation_data : any;
/**
*/
private initial_name : (null | string);
/**
* [hook]
*/
private action_cancel : (null | (() => void));
/**
* [hook]
*/
private action_success : (null | (() => void));
/**
*/
public constructor(
preparation_data : any,
{
"initial_name": initial_name = null,
"action_cancel": action_cancel = null,
"action_success": action_success = null,
}
:
{
initial_name ?: (null | string);
action_cancel ?: (null | (() => void));
action_success ?: (null | (() => void));
}
=
{
}
)
{
this.preparation_data = preparation_data;
this.initial_name = initial_name;
this.action_cancel = action_cancel;
this.action_success = action_success;
}
/**
* [implementation]
*/
public async load(
target_element : HTMLElement
)
: Promise<void>
{
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();
}
}
]
)
)
);
target_element.innerHTML = "";
await form.setup(target_element);
await form.input_write(
{
"name": this.initial_name,
"password": "",
}
);
form.input_focus();
}
}
}

View file

@ -1,110 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
export class class_widget_login_oidc
implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private preparation_data : any;
/**
* [hook]
*/
private action_cancel : (null | (() => void));
/**
* [hook]
*/
private action_success : (null | (() => void));
/**
*/
public constructor(
preparation_data : any,
{
"action_cancel": action_cancel = null,
"action_success": action_success = null,
}
:
{
action_cancel ?: (null | (() => void));
action_success ?: (null | (() => void));
}
=
{
}
)
{
this.preparation_data = preparation_data;
this.action_cancel = action_cancel;
this.action_success = action_success;
}
/**
* [implementation]
*/
public async load(
target_element : HTMLElement
)
: Promise<void>
{
// structure
{
target_element.innerHTML = await _dali.helpers.template_coin(
"widget-login_oidc",
"main",
{
"label": lib_plankton.string.coin(
lib_plankton.translate.get("widget.login.oidc.via"),
{
"title": this.preparation_data.label,
}
),
"href": this.preparation_data.url,
"cancel": lib_plankton.translate.get("common.cancel"),
}
);
}
// controls
{
target_element.querySelector(".widget-login_oidc-cancel").addEventListener(
"click",
() => {
this.action_cancel();
}
);
}
}
}
}

View file

@ -1,5 +0,0 @@
.widget-login_oidc-cancel
{
display: block;
margin-top: 32px;
}

View file

@ -1,4 +0,0 @@
<div class="widget-login_oidc">
<a class="widget-login_oidc-link" href="{{href}}">{{label}}</a>
<button class="widget-login_oidc-cancel">{{cancel}}</button>
</div>

View file

@ -1,214 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
type type_entry_data = {
label : string;
groups : Array<string>;
action : (() => void);
}
/**
*/
export class class_widget_menu implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private entries : Array<type_entry_data>;
/**
*/
private initial_groups : Array<string>;
/**
*/
private label : (null | string);
/**
*/
private container : (null | HTMLElement);
/**
*/
public constructor(
entries : Array<type_entry_data>,
{
"initial_groups": initial_groups = [],
"initial_label": initial_label = "",
}
:
{
initial_groups ?: Array<string>;
initial_label ?: string;
}
=
{
}
)
{
this.entries = entries;
this.initial_groups = initial_groups;
this.label = initial_label;
this.container = null;
}
/**
*/
public set_groups(
groups : Array<string>
)
: void
{
this.entries.forEach(
(entry, index) => {
const active : boolean = groups.some(group => entry.groups.includes(group));
const rel : string = index.toFixed(0);
const dom_entry = this.container.querySelector(".widget-menu-entry[rel=\"" + rel + "\"]");
dom_entry.classList.toggle("widget-menu-entry-hidden", (! active));
}
);
}
/**
*/
public set_label(
label : (null | string)
)
: void
{
this.label = label;
this.container.querySelector(".widget-menu-button").innerHTML = (
(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<void>
{
// structure
this.container = await _dali.helpers.element_from_template(
"widget-menu",
"main",
{
"entries": (
(
await lib_plankton.call.promise_condense(
this.entries.map(
(entry, index) => () => _dali.helpers.template_coin(
"widget-menu",
"entry",
{
"label": entry.label,
"rel": index.toFixed(0),
}
)
)
)
)
.join("")
),
}
);
// logic
{
// collapser
{
this.container.querySelector(".widget-menu-button").addEventListener(
"click",
() => {
this.toggle_collapsed();
}
);
}
// entries
{
this.container.querySelectorAll(".widget-menu-entry").forEach(
dom_entry => {
dom_entry.addEventListener(
"click",
() => {
const index : int = parseInt(dom_entry.getAttribute("rel"));
const entry : type_entry_data = this.entries[index];
this.toggle_collapsed({"mode": true});
entry.action();
}
);
}
);
}
}
// init
{
this.toggle_collapsed({"mode": true});
this.set_groups(this.initial_groups);
this.set_label(null);
}
// finish
target_element.appendChild(this.container);
}
}
}

View file

@ -1,75 +0,0 @@
.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: 50px;
right: 20px;
z-index: 2;
}
.widget-menu-entries
{
padding: 0;
margin: 0;
list-style-type: none;
}
.widget-menu-entry
{
margin: 12px 8px;
text-transform: capitalize;
background-color: hsl(var(--hue), 0%, 25%);
color: hsl(var(--hue), 0%, 100%);
}
.widget-menu-entry:not(:hover) > span
{
border-bottom: 2px solid hsl(0, 0%, 25%);
}
.widget-menu-entry:hover > span
{
border-bottom: 2px solid hsl(0, 0%, 100%);
transition: 1s ease color;
}
.widget-menu-entry.widget-menu-entry-hidden
{
display: none;
}

View file

@ -1,3 +0,0 @@
<li class="widget-menu-entry" rel="{{rel}}">
<span>{{label}}</span>
</li>

View file

@ -1,8 +0,0 @@
<div class="widget-menu">
<button class="widget-menu-button"></button>
<div class="widget-menu-platform">
<ul class="widget-menu-entries">
{{entries}}
</ul>
</div>
</div>

View file

@ -1,139 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
type type_option = {
mode : _dali.enum_view_mode;
label : string,
};
/**
*/
export class class_widget_mode_switcher implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private options : Array<type_option>;
/**
*/
private initial_selection : (null | _dali.enum_view_mode);
/**
*/
private action_change : (null | ((mode : _dali.enum_view_mode) => void));
/**
*/
public constructor(
options : Array<type_option>,
{
"initial_selection": initial_selection = null,
"action_change": action_change = null,
}
:
{
initial_selection ?: (null | _dali.enum_view_mode),
action_change ?: (null | ((mode : _dali.enum_view_mode) => void))
}
=
{
}
)
{
this.options = options;
this.initial_selection = initial_selection;
this.action_change = action_change;
}
/**
* [implementation]
*/
public async load(
target_element : Element
)
: Promise<void>
{
target_element.innerHTML = await _dali.helpers.template_coin(
"widget-mode_switcher",
"main",
{
"options": (await lib_plankton.call.promise_condense(
this.options.map(
option => () => _dali.helpers.template_coin(
"widget-mode_switcher",
"option",
{
"rel": _dali.view_mode_encode(option.mode),
"value": _dali.view_mode_encode(option.mode),
"label": option.label,
}
)
)
)).join(""),
}
);
// initial selection
{
if (this.initial_selection !== null)
{
const selector : string = lib_plankton.string.coin(
".widget-mode_switcher-option[rel=\"{{rel}}\"] > input",
{
"rel": _dali.view_mode_encode(this.initial_selection)
}
);
(target_element.querySelector(selector) as HTMLInputElement).checked = true;
}
}
// set listeners
{
if (this.action_change !== null)
{
target_element.querySelectorAll(".widget-mode_switcher-option").forEach(
element => {
const view_mode_encoded : string = element.getAttribute("rel");
const mode : _dali.enum_view_mode = _dali.view_mode_decode(view_mode_encoded);
element.querySelector("input").addEventListener(
"change",
(event) => {
this.action_change(mode);
}
);
}
);
}
}
return Promise.resolve<void>(undefined);
}
}
}

View file

@ -1,4 +0,0 @@
.widget-mode_switcher-option
{
margin-left: 16px;
}

View file

@ -1,3 +0,0 @@
<div class="widget-mode_switcher">
{{options}}
</div>

View file

@ -1,4 +0,0 @@
<label class="widget-mode_switcher-option" rel="{{rel}}">
<input type="radio" name="mode" value="{{value}}"/>
<span>{{label}}</span>
</label>

View file

@ -1,278 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
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<Array<_dali.type_event_object_extended>>
);
/**
*/
type type_create_event = (
(
stuff ?: {
date ?: lib_plankton.pit.type_date;
}
)
=>
Promise<void>
);
/**
*/
type type_edit_event = (
(
event_key : _dali.type_event_key
)
=>
Promise<void>
);
/**
*/
export class class_widget_multiview
implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private initial_view_mode : _dali.enum_view_mode;
/**
*/
private weekview_initial_vertical : boolean;
/**
*/
private get_entries : type_get_entries;
/**
*/
private action_create_event : type_create_event;
/**
*/
private action_edit_event : type_edit_event;
/**
*/
private dom_context : (null | HTMLElement);
/**
*/
private widget_mode_switcher : _dali.widgets.class_widget_mode_switcher;
/**
*/
private widget_weekview : _dali.widgets.class_widget_weekview;
/**
*/
private widget_listview : _dali.widgets.class_widget_listview;
/**
*/
public constructor(
get_entries : type_get_entries,
{
"initial_view_mode": initial_view_mode = _dali.enum_view_mode.week,
"weekview_initial_vertical": weekview_initial_vertical = false,
"action_create_event": action_create_event = ((stuff) => Promise.resolve<void>(undefined)),
"action_edit_event": action_edit_event = ((event_key) => Promise.resolve<void>(undefined)),
}
:
{
initial_view_mode ?: _dali.enum_view_mode;
weekview_initial_vertical ?: boolean;
action_create_event ?: type_create_event;
action_edit_event ?: type_edit_event;
}
=
{
}
)
{
this.get_entries = get_entries;
this.initial_view_mode = initial_view_mode;
this.weekview_initial_vertical = weekview_initial_vertical;
this.action_create_event = action_create_event;
this.action_edit_event = action_edit_event;
this.dom_context = null;
}
/**
*/
public toggle_calendar_visibilty(
calendar_id : _dali.type_calendar_id,
{
"mode": mode = null,
}
:
{
mode ?: (null | boolean);
}
=
{
}
)
: void
{
this.widget_weekview.toggle_visibility(calendar_id, {"mode": mode});
this.widget_listview.toggle_visibility(calendar_id, {"mode": mode});
}
/**
*/
private set_view_mode
(
view_mode : _dali.enum_view_mode
)
: void
{
this.dom_context.setAttribute("rel", _dali.view_mode_encode(view_mode));
}
/**
*/
public async update_entries(
)
:
Promise<void>
{
await Promise.all(
[
this.widget_weekview.update_entries(),
this.widget_listview.update_entries(),
]
);
}
/**
* [implementation]
*/
public async load(
target_element : Element
)
: Promise<void>
{
this.dom_context = (target_element as HTMLElement);
this.dom_context.classList.add("widget-multiview");
// mode switcher
{
this.widget_mode_switcher = (
new _dali.widgets.class_widget_mode_switcher(
[
{
"mode": _dali.enum_view_mode.week,
/**
* @todo as dependency
*/
"label": lib_plankton.translate.get("widget.overview.mode.week"),
},
{
"mode": _dali.enum_view_mode.list,
/**
* @todo as dependency
*/
"label": lib_plankton.translate.get("widget.overview.mode.list"),
},
],
{
"initial_selection": this.initial_view_mode,
"action_change": (view_mode) => {
this.set_view_mode(view_mode);
}
}
)
);
let dom_wrapper = document.createElement("div");
dom_wrapper.classList.add("widget-multiview-mode_switcher");
await this.widget_mode_switcher.load(dom_wrapper);
this.dom_context.appendChild(dom_wrapper);
}
// weekview
{
this.widget_weekview = (
new _dali.widgets.class_widget_weekview(
this.get_entries,
{
"action_select_event": (event_key) => this.action_edit_event(event_key),
"action_select_day": (date) => this.action_create_event({"date": date}),
"vertical": this.weekview_initial_vertical,
}
)
);
let dom_wrapper = document.createElement("div");
dom_wrapper.classList.add("widget-multiview-weekview");
await this.widget_weekview.load(dom_wrapper);
this.dom_context.appendChild(dom_wrapper);
}
// listview
{
this.widget_listview = (
new _dali.widgets.class_widget_listview(
this.get_entries,
{
"action_select": (event_key) => this.action_edit_event(event_key),
"action_add": () => this.action_create_event(),
}
)
);
let dom_wrapper = document.createElement("div");
dom_wrapper.classList.add("widget-multiview-listview");
await this.widget_listview.load(dom_wrapper);
this.dom_context.appendChild(dom_wrapper);
}
this.set_view_mode(this.initial_view_mode);
return Promise.resolve<void>(undefined);
}
}
}

View file

@ -1,10 +0,0 @@
.widget-multiview-mode_switcher
{
margin-bottom: 12px;
}
.widget-multiview[rel="week"] > .widget-multiview-weekview {}
.widget-multiview[rel="week"] > .widget-multiview-listview {display: none;}
.widget-multiview[rel="list"] > .widget-multiview-weekview {display: none;}
.widget-multiview[rel="list"] > .widget-multiview-listview {}

View file

@ -1,648 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
type type_action_create_calendar = (
(
)
=>
Promise<void>
);
/**
*/
type type_action_edit_calendar = (
(
type_calendar_object_reduced_with_id
)
=>
Promise<void>
);
/**
*/
type type_action_create_event = (
(
stuff ?: {
date ?: lib_plankton.pit.type_date;
}
)
=>
Promise<void>
);
/**
*/
type type_action_edit_event = (
(
event_key : _dali.type_event_key
)
=>
Promise<void>
);
/**
*/
export class class_widget_overview
implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private widget_sources : _dali.widgets.class_widget_sources;
/**
*/
private widget_multiview : _dali.widgets.class_widget_multiview;
/**
*/
private action_create_calendar : type_action_create_calendar;
/**
*/
private action_edit_calendar : type_action_edit_calendar;
/**
*/
private action_create_event : type_action_create_event;
/**
*/
private action_edit_event : type_action_edit_event;
/**
*/
public constructor(
)
{
this.action_create_calendar = async (
) => {
const widget = new _dali.widgets.class_widget_calendar_edit(
await _dali.model.group_list(),
await _dali.model.user_list(),
{
"name": "",
"hue": lib_plankton.random.generate_unit(),
"access": {
"public": false,
"default_level": _dali.enum_access_level.none,
"attributed_group": lib_plankton.map.hashmap.implementation_map<
_dali.type_group_id,
_dali.enum_access_level
>(
lib_plankton.map.hashmap.make<
_dali.type_group_id,
_dali.enum_access_level
>(
group_id => group_id.toFixed(0),
)
),
"attributed_user": 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,
},
{
"read_only": false,
"action_cancel": () => {
_dali.overlay.toggle({"mode": false});
},
"action_add": (calendar_object) => {
_dali.model.calendar_add(
calendar_object
)
.then(
() => {
this.update_sources_and_entries();
_dali.overlay.toggle({"mode": false});
}
)
.catch(
(reason) => {
lib_plankton.log.warning(
"dali.overview.calendar_add_error",
{"reason": String(reason)}
);
}
);
},
}
);
_dali.overlay.clear();
_dali.overlay.toggle({"mode": true});
await widget.load(_dali.overlay.get_content_element());
};
this.action_edit_calendar = async (
calendar_object_reduced_with_id
) => {
const read_only : boolean = (() => {
switch (calendar_object_reduced_with_id.access_level)
{
case _dali.enum_access_level.none:
{
throw (new Error("this event should not be visible"));
break;
}
case _dali.enum_access_level.edit:
case _dali.enum_access_level.view:
{
return true;
break;
}
case _dali.enum_access_level.admin:
{
return false;
break;
}
}
}) ();
if (read_only)
{
lib_plankton.log.notice(
"dali.overview.may_not_edit_calendar",
{
"calendar_id": calendar_object_reduced_with_id.id,
}
);
}
else
{
const calendar_id : _dali.type_calendar_id = calendar_object_reduced_with_id.id;
const calendar_object : _dali.type_calendar_object = await _dali.model.calendar_get(
calendar_id
);
const widget = new _dali.widgets.class_widget_calendar_edit(
await _dali.model.group_list(),
await _dali.model.user_list(),
calendar_object,
{
"read_only": read_only,
"action_cancel": () => {
_dali.overlay.toggle({"mode": false});
},
"action_change": (data) => {
_dali.model.calendar_change(
calendar_id,
data
)
.then(
() => {
this.update_sources_and_entries();
_dali.overlay.toggle({"mode": false});
}
);
},
"action_remove": (data) => {
_dali.model.calendar_remove(
calendar_id
)
.then(
() => {
this.update_sources_and_entries();
_dali.overlay.toggle({"mode": false});
}
);
},
}
);
_dali.overlay.clear();
_dali.overlay.toggle({"mode": true});
await widget.load(_dali.overlay.get_content_element());
}
}
/**
* @todo unterschiedliches Verhalten bei Anmeldung?
*/
this.action_create_event = async (
{
"date": date = lib_plankton.pit.to_datetime(lib_plankton.pit.now()).date,
}
:
{
date ?: lib_plankton.pit.type_date;
}
=
{
}
) => {
if (! _dali.is_logged_in())
{
// do nothing
}
else
{
const widget = new _dali.widgets.class_widget_event_edit(
(await this.get_available_calendars()),
{
"calendar_id": null,
"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(
() => {
this.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());
}
}
/**
*/
this.action_edit_event = async (
event_key
) => {
const event_object_extended : _dali.type_event_object_extended = await _dali.model.event_get(event_key);
const read_only : boolean = (() => {
switch (event_object_extended.access_level)
{
case _dali.enum_access_level.none:
{
throw (new Error("this event should not be visible"));
break;
}
case _dali.enum_access_level.view:
{
return true;
break;
}
case _dali.enum_access_level.edit:
case _dali.enum_access_level.admin:
{
return false;
break;
}
}
}) ();
const widget = new _dali.widgets.class_widget_event_edit(
(await this.get_available_calendars()),
{
"calendar_id": event_object_extended.calendar_id,
"event_name": event_object_extended.event_object.name,
"event_begin": event_object_extended.event_object.begin,
"event_end": event_object_extended.event_object.end,
"event_location": event_object_extended.event_object.location,
"event_link": event_object_extended.event_object.link,
"event_description": event_object_extended.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(
() => {
this.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(
() => {
this.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());
}
}
/**
*/
private async get_available_calendars(
)
: Promise<
Array<
_dali.type_calendar_object_reduced_with_id
>
>
{
return (
(await _dali.model.calendar_list())
/*
.filter(
(entry) => (
(entry.access_level === _dali.enum_access_level.edit)
||
(entry.access_level === _dali.enum_access_level.admin)
)
)
*/
);
}
/**
*/
private async get_entries(
from_pit : lib_plankton.pit.type_pit,
to_pit : lib_plankton.pit.type_pit,
calendar_ids : Array<_dali.type_calendar_id>
)
: Promise<Array<_dali.type_event_object_extended>>
{
/**
* @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();
}
/**
*/
private async update_sources_and_entries(
{
"priviliged": priviliged = null,
}
:
{
priviliged ?: (null | boolean);
}
=
{
}
)
: Promise<void>
{
await this.widget_sources.update({"priviliged": priviliged});
await this.widget_multiview.update_entries();
}
/**
* @todo use priviliged?
*/
private async update_entries(
{
"priviliged": priviliged = null,
}
:
{
priviliged ?: (null | boolean);
}
=
{
}
)
: Promise<void>
{
await this.widget_multiview.update_entries();
}
/**
* [implementation]
*/
public async load(
target_element : HTMLElement
)
: Promise<void>
{
const view_mode : _dali.enum_view_mode = _dali.helpers.view_mode_determine("auto");
const view_kind : _dali.enum_view_kind = _dali.helpers.view_kind_determine("auto");
this.widget_sources = (
new _dali.widgets.class_widget_sources(
_dali.model.calendar_list,
{
"initial_priviliged": _dali.is_logged_in(),
"action_add": this.action_create_calendar,
"action_select": this.action_edit_calendar,
"action_toggle": (entry, mode) => {
this.widget_multiview.toggle_calendar_visibilty(entry.id, {"mode": mode});
},
}
)
);
this.widget_multiview = (
new _dali.widgets.class_widget_multiview(
this.get_entries,
{
"initial_view_mode": view_mode,
"weekview_initial_vertical": (view_kind === _dali.enum_view_kind.touch),
"action_create_event": this.action_create_event,
"action_edit_event": this.action_edit_event,
}
)
);
target_element.innerHTML = await _dali.helpers.template_coin(
"widget-overview",
"main",
{
}
);
// head
{
// hint
{
const dom_hint = target_element.querySelector(".widget-overview-hint");
dom_hint.textContent = lib_plankton.translate.get("widget.overview.login_hint");
dom_hint.classList.toggle("widget-overview-hint-hidden", _dali.is_logged_in());
}
}
// body
{
let widget_slider : lib_plankton.zoo_widget.interface_widget;
switch (view_kind)
{
case _dali.enum_view_kind.regular:
{
widget_slider = new lib_plankton.zoo_widget.class_slider(
[
new lib_plankton.zoo_widget.class_bunch(
[
this.widget_sources,
this.widget_multiview,
]
),
],
{
"threshold": 100,
"initial_index": 1,
}
);
break;
}
case _dali.enum_view_kind.touch:
{
widget_slider = new lib_plankton.zoo_widget.class_slider(
[
this.widget_sources,
this.widget_multiview,
],
{
"threshold": 100,
"initial_index": 2,
}
);
break;
}
}
await widget_slider.load(target_element.querySelector(".widget-overview-body"));
}
_dali.model.listen_reset(
async (priviliged) => {
this.update_sources_and_entries({"priviliged": priviliged});
target_element.querySelector(".widget-overview-hint").classList.toggle("widget-overview-hint-hidden", priviliged);
}
);
return Promise.resolve<void>(undefined);
}
}
}

View file

@ -1,59 +0,0 @@
.widget-overview-head
{
padding-bottom: 12px;
margin-bottom: 12px;
border-bottom: 1px solid;
}
.widget-overview-hint
{
text-align: center;
font-weight: bold;
}
.widget-overview-hint.widget-overview-hint-hidden
{
display: none;
}
.widget-overview-body
{
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.widget-overview-pane-left
{
flex-basis: 12.5%;
flex-grow: 0;
flex-shrink: 1;
}
.widget-overview-pane-right
{
flex-basis: 87.5%;
flex-grow: 1;
flex-shrink: 1;
}
.widget-overview-body .widget-slider-slide .widget-bunch
{
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.widget-overview-body .widget-slider-slide .widget-bunch-element:nth-child(1)
{
flex-grow: 0;
flex-shrink: 1;
flex-basis: 12.5%;
}
.widget-overview-body .widget-slider-slide .widget-bunch-element:nth-child(2)
{
flex-grow: 1;
flex-shrink: 1;
flex-basis: 87.5%;
}

View file

@ -1,8 +0,0 @@
<div class="widget-overview">
<div class="widget-overview-head">
<div class="widget-overview-hint">
</div>
</div>
<div class="widget-overview-body">
</div>
</div>

View file

@ -1,198 +1,124 @@
/* namespace _zeitbild.frontend_web.widgets.sources
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{ {
/** /**
*/ */
type type_entry = { type type_entry = {
id : _dali.type_calendar_id; id : _zeitbild.frontend_web.type.calendar_id;
name : string; name : string;
hue : float; access_level : _zeitbild.frontend_web.type.enum_access_level;
access_level : _dali.enum_access_level;
}; };
/** /**
*/ */
export class class_widget_sources implements lib_plankton.zoo_widget.interface_widget export class class_widget_sources extends _zeitbild.class_widget
{ {
/** /**
* [dependency]
*/ */
private get_entries : (() => Promise<Array<type_entry>>); private keys : Array<string>;
/** /**
* [hook]
*/ */
private action_toggle : ((entry : type_entry, mode : boolean) => void); private data : Record<string, type_entry>;
/** /**
* [hook]
*/ */
private action_select : ((entry : type_entry) => void); private action_open : ((entry : type_entry) => void);
/** /**
* [hook]
*/ */
private action_add : (() => void); private action_toggle_visibility : ((entry : type_entry) => void);
/**
* [state]
*/
private priviliged : boolean;
/**
* [state]
*/
private container : (null | Element);
/** /**
*/ */
public constructor( public constructor(
get_entries : (() => Promise<Array<type_entry>>), entries : Array<type_entry>,
{ options : {
"action_select": action_select = ((calendar_id) => {}), action_open ?: ((entry : type_entry) => void);
"action_toggle": action_toggle = ((calendar_id, mode) => {}), action_toggle_visibility ?: ((entry : type_entry) => void);
"action_add": action_add = (() => {}), } = {}
"initial_priviliged": initial_priviliged = false,
}
:
{
action_select ?: ((entry : type_entry) => void);
action_toggle ?: ((entry : type_entry, mode : boolean) => void);
action_add ?: (() => void);
initial_priviliged ?: boolean;
}
=
{
}
) )
{ {
// dependencies options = Object.assign(
this.get_entries = get_entries;
// hooks
this.action_select = action_select;
this.action_toggle = action_toggle;
this.action_add = action_add;
// state
this.priviliged = initial_priviliged;
this.container = null;
}
/**
*/
private static id_encode(
id : _dali.type_calendar_id
)
: string
{ {
return id.toFixed(0); "action_open": (calendar_id) => {},
} "action_toggle_visibility": (calendar_id) => {},
},
options
/**
*/
private static id_decode(
representation : string
)
: _dali.type_calendar_id
{
return parseInt(representation);
}
/**
*/
public async update(
{
"priviliged": priviliged = null,
}
:
{
priviliged ?: boolean;
}
=
{
}
)
: Promise<void>
{
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,
}
)
)
),
}
)
); );
// structure 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;
}
/**
* [implementation]
*/
public async load(
target_element : Element
) : Promise<void>
{ {
this.container.innerHTML = await _dali.helpers.template_coin( target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"widget-sources", "widget-sources",
"main", "main",
{ {
"label_create": lib_plankton.translate.get("widget.sources.create"),
"entries": ( "entries": (
( (
await _dali.helpers.promise_row<string>( await _zeitbild.frontend_web.helpers.promise_row<string>(
lib_plankton.map.dump(data) this.keys
.map( .map(
(pair) => () => { (key) => () => {
return _dali.helpers.template_coin( const entry : type_entry = this.data[key];
return _zeitbild.frontend_web.helpers.template_coin(
"widget-sources", "widget-sources",
"entry", "entry",
{ {
"name": pair.value.name, "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 // "access_level": entry.access_level, // TODO
"color": _dali.helpers.event_color(pair.value.hue), // TODO centralize
"rel": class_widget_sources.id_encode(pair.key), "color_head": lib_plankton.color.output_hex(
lib_plankton.color.give_generic(
(entry.id - 1),
{
"saturation": 0.375,
"value": 0.375,
}
),
),
"color_body": lib_plankton.color.output_hex(
lib_plankton.color.give_generic(
(entry.id - 1),
{
"saturation": 0.375,
"value": 0.25,
}
),
),
"rel": key,
} }
); );
} }
@ -203,66 +129,43 @@ namespace _dali.widgets
), ),
} }
); );
this.container.querySelector(".sources").classList.toggle("sources-priviliged", this.priviliged); target_element.querySelectorAll(".sources-entry-head").forEach(
}
// listeners
{
this.container.querySelector(".sources-create").addEventListener(
"click",
(event) => {
event.preventDefault();
this.action_add();
}
);
this.container.querySelectorAll(".sources-entry-visibility").forEach(
(element) => { (element) => {
element.addEventListener( element.addEventListener(
"change", "click",
(event) => {
element.parentElement.classList.toggle("sources-entry-open");
}
);
}
);
target_element.querySelectorAll(".sources-entry-toggle").forEach(
(element) => {
element.addEventListener(
"click",
() => { () => {
const mode : boolean = (element as HTMLInputElement).checked; const key : string = element.parentElement.parentElement.parentElement.getAttribute("rel");
const key_encoded : string = element.parentElement.getAttribute("rel"); const entry : type_entry = this.data[key];
const calendar_id : _dali.type_calendar_id = class_widget_sources.id_decode(key_encoded); element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-hidden");
const entry : type_entry = data.get(calendar_id); element.parentElement.parentElement.parentElement.classList.toggle("sources-entry-open", false);
element.parentElement.classList.toggle("sources-entry-hidden", (! mode)); this.action_toggle_visibility(entry);
this.action_toggle(entry, mode);
} }
); );
} }
); );
this.container.querySelectorAll(".sources-entry").forEach( target_element.querySelectorAll(".sources-entry-edit").forEach(
(element) => { (element) => {
element.addEventListener( element.addEventListener(
"click", "click",
(event) => { (event) => {
if ((event.target as Element).classList.contains("sources-entry-visibility")) const key : string = element.parentElement.parentElement.parentElement.getAttribute("rel");
{ const entry : type_entry = this.data[key];
// do nothing this.action_open(entry);
}
else
{
const key_encoded : string = element.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_select(entry);
}
} }
); );
} }
); );
} return Promise.resolve<void>(undefined);
}
/**
* [implementation]
*/
public async load(
target_element : Element
)
: Promise<void>
{
this.container = target_element;
await this.update();
} }
} }

View file

@ -1,36 +0,0 @@
.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;
}
.sources-entry
{
margin: 8px;
padding: 4px;
cursor: pointer;
}
/*
.sources-entry-hidden
{
filter: saturate(0);
}
*/

View file

@ -1,4 +1,11 @@
<li class="sources-entry" style="background-color: {{color}}" rel="{{rel}}"> <li class="sources-entry" style="background-color: {{color_head}}" rel="{{rel}}">
<input class="sources-entry-visibility" type="checkbox" checked="checked"/> <div class="sources-entry-head">
<span class="sources-entry-name">{{name}}</span> <span>{{name}}</span>
</div>
<div class="sources-entry-body" style="background-color: {{color_body}}">
<ul>
<li class="sources-entry-action sources-entry-toggle">{{label_toggle}}</li>
<li class="sources-entry-action sources-entry-edit">{{label_edit}}</li>
</ul>
</div>
</li> </li>

View file

@ -1,6 +1,3 @@
<div class="sources"> <ul class="sources">
<ul class="sources-entries">
{{entries}} {{entries}}
</ul> </ul>
<a class="sources-create" href="">{{label_create}}</a>
</div>

View file

@ -1,167 +0,0 @@
/*
This file is part of »dali«.
Copyright 2025 'kcf' <fenris@folksprak.org>
»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 <http://www.gnu.org/licenses/>.
*/
namespace _dali.widgets
{
/**
*/
export class class_widget_special_number_input
implements lib_plankton.zoo_widget.interface_widget
{
/**
*/
private action_change : ((int) => Promise<void>);
/**
*/
private label : (null | string);
/**
*/
private minimum : (null | int);
/**
*/
private maximum : (null | int);
/**
*/
private value : int;
/**
*/
public constructor(
{
"label": label = null,
"minimum": minimum = null,
"maximum": maximum = null,
"initial_value": initial_value = 0,
"action_change": action_change = ((value) => Promise.resolve<void>(undefined)),
}
:
{
label ?: (null | string);
minimum ?: (null | int);
maximum ?: (null | int);
initial_value ?: int;
action_change ?: ((int) => Promise<void>);
}
=
{
}
)
{
this.label = label;
this.minimum = minimum;
this.maximum = maximum;
this.value = initial_value;
this.action_change = action_change;
}
/**
* [implementation]
*/
public async load(
target_element : HTMLElement
)
: Promise<void>
{
const dom_root = await _dali.helpers.element_from_template(
"widget-special_number_input",
"main",
{
"label": this.label,
}
);
const dom_input : HTMLInputElement = (dom_root.querySelector(".widget-special_number_input-input") as HTMLInputElement);
// listeners
{
dom_input.addEventListener(
"change",
() => {
const value : int = parseInt(dom_input.value);
if (
((this.minimum === null) || (value >= this.minimum))
&&
((this.maximum === null) || (value <= this.maximum))
)
{
this.value = value;
this.action_change(this.value);
}
else
{
// do nothing
}
}
);
dom_root.querySelector(".widget-special_number_input-prev").addEventListener(
"click",
() => {
if ((this.minimum === null) || (this.value > this.minimum))
{
this.value -= 1;
dom_input.value = this.value.toFixed(0);
this.action_change(this.value);
}
else
{
// do nothing
}
}
);
dom_root.querySelector(".widget-special_number_input-next").addEventListener(
"click",
() => {
if ((this.maximum === null) || (this.value < this.maximum))
{
this.value += 1;
dom_input.value = this.value.toFixed(0);
this.action_change(this.value);
}
else
{
// do nothing
}
}
);
}
// content
{
dom_input.value = this.value.toFixed(0);
}
target_element.appendChild(dom_root);
}
}
}

View file

@ -1,34 +0,0 @@
.widget-special_number_input-controls
{
white-space: nowrap;
}
.widget-special_number_input-controls > *
{
display: inline-block;
}
.widget-special_number_input-button
{
padding: 4px 4px;
margin: 0;
cursor: pointer;
}
.widget-special_number_input-label
{
display: block;
font-size: 0.75em;
/*
text-transform: uppercase;
*/
}
.widget-special_number_input-input
{
max-width: 40px;
margin: 0;
text-align: center;
}

View file

@ -1,8 +0,0 @@
<div class="widget-special_number_input">
<label class="widget-special_number_input-label">{{label}}</label>
<div class="widget-special_number_input-controls">
<div class="widget-special_number_input-button widget-special_number_input-prev">&#x25C2;</div>
<input type="text" class="widget-special_number_input-input" pattern="-?[0-9]+"/>
<div class="widget-special_number_input-button widget-special_number_input-next">&#x25B8;</div>
</div>
</div>

File diff suppressed because it is too large Load diff

View file

@ -1,6 +0,0 @@
<div class="weekview-control weekview-control-vertical">
<label class="weekview-control-label">{{label}}</label>
<div class="weekview-control-input">
<input type="checkbox"/>
</div>
</div>

View file

@ -1,9 +1,32 @@
<div class="weekview"> <div class="weekview">
<div class="weekview-controls"> <div class="weekview-controls">
<label class="weekview-control weekview-control-year">
<span>{{label_control_year}}</span>
<input type="number"/>
</label>
<label class="weekview-control weekview-control-week">
<span>{{label_control_week}}</span>
<input type="number"/>
</label>
<label class="weekview-control weekview-control-count">
<span>{{label_control_count}}</span>
<input type="number"/>
</label>
<input type="submit" class="weekview-control weekview-control-apply" value="{{label_control_apply}}"/>
</div> </div>
<div class="weekview-table"> <div class="weekview-table">
<table> <table>
<thead> <thead>
<tr>
<th class="calendar-cell"></th>
<th class="calendar-cell calendar-cell-day">{{label_weekday_monday}}</th>
<th class="calendar-cell calendar-cell-day">{{label_weekday_tuesday}}</th>
<th class="calendar-cell calendar-cell-day">{{label_weekday_wednesday}}</th>
<th class="calendar-cell calendar-cell-day">{{label_weekday_thursday}}</th>
<th class="calendar-cell calendar-cell-day">{{label_weekday_friday}}</th>
<th class="calendar-cell calendar-cell-day">{{label_weekday_saturday}}</th>
<th class="calendar-cell calendar-cell-day">{{label_weekday_sunday}}</th>
</tr>
</thead> </thead>
<tbody> <tbody>
</tbody> </tbody>

View file

@ -1,3 +1,3 @@
<li class="weekview-event_entry{{additional_classes}}" style="background-color: {{color}};" rel="{{rel}}"> <li class="weekview-event_entry{{additional_classes}}" style="background-color: {{color}};" title="{{title}}" rel="{{rel}}">
{{name}} {{name}}
</li> </li>

View file

@ -1,5 +1,5 @@
<td class="weekview-cell weekview-cell-regular{{extra_classes}}" rel="{{rel}}"> <td class="weekview-cell weekview-cell-regular{{extra_classes}}" rel="{{rel}}">
<span class="weekview-day"> <span class="weekview-day" title="{{title}}">
{{day}} {{day}}
</span> </span>
<ul class="weekview-events"> <ul class="weekview-events">

View file

@ -24,24 +24,16 @@ def main():
metavar = "<conf-path>", metavar = "<conf-path>",
help = "conf path", help = "conf path",
) )
argument_parser.add_argument(
"-f",
"--force",
action = 'store_true',
default = False,
help = "force",
)
args = argument_parser.parse_args() args = argument_parser.parse_args()
## exec ## exec
targets = [] targets = []
targets.append("default") targets.append("default")
_os.system( _os.system(
"make dir_build=%s --file=tools/makefile %s%s" "make dir_build=%s --file=tools/makefile %s"
% ( % (
args.output_directory, args.output_directory,
" ".join(targets), " ".join(targets),
(" --always-make" if args.force else ""),
) )
) )
if True: if True:

View file

@ -19,9 +19,9 @@ def main():
"--target-directory", "--target-directory",
type = str, type = str,
dest = "target_directory", dest = "target_directory",
default = "/opt/dali", default = "/opt/zbwf",
metavar = "<target-directory>", metavar = "<target-directory>",
help = "directory on the target system, where the files shall be put; default: /opt/dali", help = "directory on the target system, where the files shall be put; default: /opt/zbwf",
) )
argument_parser.add_argument( argument_parser.add_argument(
"-b", "-b",

View file

@ -31,6 +31,14 @@ def main(
dest = "index_template_path", dest = "index_template_path",
type = str, type = str,
) )
argument_parser.add_argument(
"-t",
"--template",
dest = "template_paths",
type = str,
action = "append",
default = []
)
args = argument_parser.parse_args() args = argument_parser.parse_args()
## exec ## exec
@ -38,6 +46,12 @@ def main(
string_coin( string_coin(
file_read(args.index_template_path), file_read(args.index_template_path),
{ {
"templates": "".join(
map(
file_read,
args.template_paths
)
),
} }
) )
) )

View file

@ -26,123 +26,123 @@ ${dir_build}/index.html: \
${dir_source}/index.html.tpl ${dir_source}/index.html.tpl
@ ${cmd_log} "index …" @ ${cmd_log} "index …"
@ ${cmd_mkdir} $(dir $@) @ ${cmd_mkdir} $(dir $@)
@ tools/make-index ${dir_source}/index.html.tpl > $@ @ tools/make-index \
${dir_source}/index.html.tpl \
> $@
.PHONY: templates .PHONY: templates
templates: \ templates: \
templates-widgets-special_number_input \
templates-widgets-login_oidc \
templates-widgets-menu \
templates-widgets-sources \ templates-widgets-sources \
templates-widgets-calendar_edit \
templates-widgets-event_edit \
templates-widgets-caldav \
templates-widgets-listview \ templates-widgets-listview \
templates-widgets-weekview \ templates-widgets-weekview \
templates-widgets-overview \ templates-pages-caldav \
templates-widgets-mode_switcher templates-pages-calendar_add \
templates-pages-calendar_edit \
.PHONY: templates-widgets-special_number_input templates-pages-event_add \
templates-widgets-special_number_input: \ templates-pages-event_edit \
$(wildcard ${dir_source}/widgets/special_number_input/templates/*) templates-pages-overview \
@ ${cmd_log} "templates:widget:special_number_input …" templates-pages-login
@ ${cmd_mkdir} ${dir_build}/templates/widget-special_number_input
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/special_number_input/templates/* ${dir_build}/templates/widget-special_number_input/
.PHONY: templates-widgets-login_oidc
templates-widgets-login_oidc: \
$(wildcard ${dir_source}/widgets/login_oidc/templates/*)
@ ${cmd_log} "templates:widget:login_oidc …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-login_oidc
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/login_oidc/templates/* ${dir_build}/templates/widget-login_oidc/
.PHONY: templates-widgets-menu
templates-widgets-menu: \
$(wildcard ${dir_source}/widgets/menu/templates/*)
@ ${cmd_log} "templates:widget:menu …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-menu
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/menu/templates/* ${dir_build}/templates/widget-menu/
.PHONY: templates-widgets-sources .PHONY: templates-widgets-sources
templates-widgets-sources: \ templates-widgets-sources: \
$(wildcard ${dir_source}/widgets/sources/templates/*) $(wildcard ${dir_source}/widgets/sources/templates/*)
@ ${cmd_log} "templates:widget:sources …" @ ${cmd_log} "templates:widgets:sources …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-sources @ ${cmd_mkdir} ${dir_build}/templates/widget-sources
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/sources/templates/* ${dir_build}/templates/widget-sources/ @ ${cmd_cp} -r -u -v ${dir_source}/widgets/sources/templates/* ${dir_build}/templates/widget-sources/
.PHONY: templates-widgets-calendar_edit
templates-widgets-calendar_edit: \
$(wildcard ${dir_source}/widgets/calendar_edit/templates/*)
@ ${cmd_log} "templates:widget: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}/widgets/event_edit/templates/*)
@ ${cmd_log} "templates:widget:event_edit …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-event_edit
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/event_edit/templates/* ${dir_build}/templates/widget-event_edit/
.PHONY: templates-widgets-caldav
templates-widgets-caldav: \
$(wildcard ${dir_source}/widgets/caldav/templates/*)
@ ${cmd_log} "templates:widget:caldav …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-caldav
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/caldav/templates/* ${dir_build}/templates/widget-caldav/
.PHONY: templates-widgets-listview .PHONY: templates-widgets-listview
templates-widgets-listview: \ templates-widgets-listview: \
$(wildcard ${dir_source}/widgets/listview/templates/*) $(wildcard ${dir_source}/widgets/listview/templates/*)
@ ${cmd_log} "templates:widget:listview …" @ ${cmd_log} "templates:widgets:listview …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-listview @ ${cmd_mkdir} ${dir_build}/templates/widget-listview
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/listview/templates/* ${dir_build}/templates/widget-listview/ @ ${cmd_cp} -r -u -v ${dir_source}/widgets/listview/templates/* ${dir_build}/templates/widget-listview/
.PHONY: templates-widgets-weekview .PHONY: templates-widgets-weekview
templates-widgets-weekview: \ templates-widgets-weekview: \
$(wildcard ${dir_source}/widgets/weekview/templates/*) $(wildcard ${dir_source}/widgets/weekview/templates/*)
@ ${cmd_log} "templates:widget:weekview …" @ ${cmd_log} "templates:widgets:weekview …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-weekview @ ${cmd_mkdir} ${dir_build}/templates/widget-weekview
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/weekview/templates/* ${dir_build}/templates/widget-weekview/ @ ${cmd_cp} -r -u -v ${dir_source}/widgets/weekview/templates/* ${dir_build}/templates/widget-weekview/
.PHONY: templates-widgets-overview .PHONY: templates-pages-caldav
templates-widgets-overview: \ templates-pages-caldav: \
$(wildcard ${dir_source}/widgets/overview/templates/*) $(wildcard ${dir_source}/pages/caldav/templates/*)
@ ${cmd_log} "templates:widget:overview …" @ ${cmd_log} "templates:caldav …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-overview @ ${cmd_mkdir} ${dir_build}/templates/caldav
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/overview/templates/* ${dir_build}/templates/widget-overview/ @ ${cmd_cp} -r -u -v ${dir_source}/pages/caldav/templates/* ${dir_build}/templates/caldav/
.PHONY: templates-widgets-mode_switcher .PHONY: templates-pages-calendar_add
templates-widgets-mode_switcher: \ templates-pages-calendar_add: \
$(wildcard ${dir_source}/widgets/mode_switcher/templates/*) $(wildcard ${dir_source}/pages/calendar_add/templates/*)
@ ${cmd_log} "templates:widget:mode_switcher …" @ ${cmd_log} "templates:calendar_add …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-mode_switcher @ ${cmd_mkdir} ${dir_build}/templates/calendar_add
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/mode_switcher/templates/* ${dir_build}/templates/widget-mode_switcher/ @ ${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/*)
@ ${cmd_log} "templates: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 .PHONY: style
style: ${dir_build}/style.css style: \
$(wildcard ${dir_source}/style/*)
${dir_build}/style.css: \
$(wildcard ${dir_source}/style/*.css) \
$(wildcard ${dir_source}/widgets/*/style.css)
@ ${cmd_log} "style …" @ ${cmd_log} "style …"
@ ${cmd_mkdir} ${dir_build} @ ${cmd_mkdir} ${dir_build}
@ ${cmd_cat} $^ > $@ @ ${cmd_cat} ${dir_source}/style/* > ${dir_build}/style.css
.PHONY: logic .PHONY: logic
logic: ${dir_build}/logic.js logic: ${dir_build}/logic.js
${dir_temp}/logic-unlinked.js: \ ${dir_temp}/logic-unlinked.js: \
${dir_lib}/plankton/plankton.d.ts \ ${dir_lib}/plankton/plankton.d.ts \
${dir_source}/resources/conf.ts \ ${dir_source}/logic/helpers.ts \
${dir_source}/resources/backend.ts \ ${dir_source}/logic/widget.ts \
${dir_source}/base.ts \ ${dir_source}/logic/conf.ts \
${dir_source}/types.ts \ ${dir_source}/logic/types.ts \
${dir_source}/model.ts \ ${dir_source}/logic/backend.ts \
${dir_source}/overlay.ts \ ${dir_source}/widgets/sources/logic.ts \
${dir_source}/helpers.ts \ ${dir_source}/widgets/listview/logic.ts \
$(wildcard ${dir_source}/widgets/*/logic.ts) \ ${dir_source}/widgets/weekview/logic.ts \
${dir_source}/main.ts ${dir_source}/pages/login/logic.ts \
${dir_source}/pages/logout/logic.ts \
${dir_source}/pages/caldav/logic.ts \
${dir_source}/pages/oidc_finish/logic.ts \
${dir_source}/pages/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}/logic/main.ts
@ ${cmd_log} "logic | compile …" @ ${cmd_log} "logic | compile …"
@ ${cmd_mkdir} $(dir $@) @ ${cmd_mkdir} $(dir $@)
@ ${cmd_tsc} --lib dom,es2020 $^ --outFile $@ # --strict @ ${cmd_tsc} --lib dom,es2020 $^ --outFile $@ # --strict

View file

@ -12,10 +12,7 @@ modules="${modules} storage"
modules="${modules} file" modules="${modules} file"
modules="${modules} json" modules="${modules} json"
modules="${modules} string" modules="${modules} string"
modules="${modules} random"
modules="${modules} map" modules="${modules} map"
modules="${modules} set"
modules="${modules} cache"
modules="${modules} color" modules="${modules} color"
# modules="${modules} xml" # modules="${modules} xml"
modules="${modules} map" modules="${modules} map"
@ -25,7 +22,6 @@ modules="${modules} url"
modules="${modules} pit" modules="${modules} pit"
modules="${modules} www_form" modules="${modules} www_form"
modules="${modules} translate" modules="${modules} translate"
modules="${modules} zoo-widget"
modules="${modules} zoo-page" modules="${modules} zoo-page"
modules="${modules} zoo-form" modules="${modules} zoo-form"
modules="${modules} zoo-input" modules="${modules} zoo-input"