This commit is contained in:
Fenris Wolf 2024-09-11 20:19:13 +02:00
parent 12697e2a7a
commit 762f3ba3d6
9 changed files with 267 additions and 231 deletions

View file

@ -1,5 +1,4 @@
{
"view_mode": "table",
"calendar_id": 6,
"timezone_shift": 0
}

View file

@ -63,7 +63,7 @@
],
"calendars": [
{
"id": 5,
"id": 1,
"object": {
"kind": "concrete",
"data": {
@ -123,7 +123,7 @@
}
},
{
"id": 3,
"id": 2,
"object": {
"kind": "concrete",
"data": {
@ -198,7 +198,7 @@
}
},
{
"id": 4,
"id": 3,
"object": {
"kind": "concrete",
"data": {
@ -226,7 +226,7 @@
}
},
{
"id": 1,
"id": 4,
"object": {
"kind": "concrete",
"data": {
@ -283,7 +283,7 @@
}
},
{
"id": 2,
"id": 5,
"object": {
"kind": "concrete",
"data": {
@ -333,27 +333,11 @@
},
{
"id": 6,
"object": {
"kind": "collection",
"data": {
"name": "Überblick",
"sources": [
1,
2,
3,
4,
5
]
}
}
},
{
"id": 7,
"object": {
"kind": "caldav",
"data": {
"name": "Lixer",
"private": false,
"private": true,
"read_only": true,
"source_url": "https://export.kalender.digital/ics/0/3e10dae66950379d4cc8/gesamterkalender.ics?past_months=3&future_months=36"
}

View file

@ -1,5 +1,6 @@
- Kalender sollen unabhängig von Nutzern bestehen können
- einem Kalender können beliebig viele Nutzer zugeordnet werden, die jeweils bestimmte Berechtigungen haben (z.B. als Rollen "admin", "editor", "viewer", …)
- Events bilden keine Domäne
- Berechtigungen:
- Kalender anlegen
- Kalender-Stammdaten ändern
@ -11,11 +12,10 @@
- es gibt verschiedene Arten von Kalendern:
- konkret
- enthält Veranstaltungen
- Sammlung
- enthält selbst keine Veranstaltungen
- verweist auf beliebig viele andere Kalender (Kreise vermeiden)
- extern
- über CalDAV
- sollte read-only- und read/write-Modus haben
- nach dem Anmelden sieht man eine Liste mit öffentlichen Kalendern, gruppiert nach jewiliger Rolle
- nach dem Anmelden sieht man eine Kalender-Ansicht mit folgenden Kalendern kombiniert angezeigt:
- öffentliche Kalender
- nicht öffentliche Kalendar, bei welchen man Lese-Berechtigung hat
- Entwurfsname: "zeitbild"

View file

@ -130,14 +130,6 @@ namespace _zeitbild.frontend.resources.backend
};
break;
}
case "collection": {
return {
"key": calendar_entry.id,
"preview": {
"name": calendar_entry.object.data.name,
}
};
}
case "caldav": {
return {
"key": calendar_entry.id,
@ -179,7 +171,7 @@ namespace _zeitbild.frontend.resources.backend
* @todo prevent loops
*/
export async function calendar_gather_events(
calendar_id : type_calendar_id,
calendar_ids : Array<type_calendar_id>,
from_pit : _zeitbild.frontend.helpers.type_pit,
to_pit : _zeitbild.frontend.helpers.type_pit
) : Promise<
@ -192,148 +184,150 @@ namespace _zeitbild.frontend.resources.backend
>
>
{
await init();
const calendar_object : type_calendar_object = await calendar_read(
calendar_id
);
lib_plankton.log.info(
"calendar_gather_events",
{
"calendar_id": calendar_id,
"calendar_ids": calendar_ids,
}
);
switch (calendar_object.kind) {
case "concrete": {
return Promise.resolve(
calendar_object.data.events
.filter(
(event) => _zeitbild.frontend.helpers.pit_is_between(
_zeitbild.frontend.helpers.pit_from_datetime(event.begin),
from_pit,
to_pit
)
)
.map(
(event) => ({
"calendar_id": calendar_id,
"calendar_name": calendar_object.data.name,
"event": event
})
)
);
break;
await init();
let result : Array<
{
calendar_id : type_calendar_id;
calendar_name : string;
event : type_event;
}
case "collection": {
return (
Promise.all(
calendar_object.data.sources
.map(
(source_calendar_id) => calendar_gather_events(
source_calendar_id,
from_pit,
to_pit
)
)
)
.then(
(entries) => Promise.resolve(
entries
.reduce(
(x, y) => x.concat(y),
[]
)
)
)
);
break;
}
case "caldav": {
const url : lib_plankton.url.type_url = lib_plankton.url.decode(
calendar_object.data.source_url
);
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/2",
"scheme": ((url.scheme === "https") ? "https" : "http"),
"host": url.host,
"path": (url.path ?? "/"),
"query": url.query,
"method": lib_plankton.http.enum_method.get,
"headers": {},
"body": null,
};
// TODO: cache?
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
http_request,
> = [];
for await (const calendar_id of calendar_ids) {
const calendar_object : type_calendar_object = await calendar_read(
calendar_id
);
if (calendar_object.data.private) {
lib_plankton.log.info(
"calendar_gather_events_private_calendar_blocked",
{
"calendar_id": calendar_id,
}
);
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(
http_response.body.toString(),
{
}
else {
switch (calendar_object.kind) {
case "concrete": {
result = (
result
.concat(
calendar_object.data.events
.filter(
(event) => _zeitbild.frontend.helpers.pit_is_between(
_zeitbild.frontend.helpers.pit_from_datetime(event.begin),
from_pit,
to_pit
)
)
.map(
(event) => ({
"calendar_id": calendar_id,
"calendar_name": calendar_object.data.name,
"event": event
})
)
)
);
break;
}
);
return Promise.resolve(
vcalendar.vevents
.map(
(vevent : lib_plankton.ical.type_vevent) => (
(vevent.dtstart !== undefined)
?
case "caldav": {
const url : lib_plankton.url.type_url = lib_plankton.url.decode(
calendar_object.data.source_url
);
const http_request : lib_plankton.http.type_request = {
"version": "HTTP/2",
"scheme": ((url.scheme === "https") ? "https" : "http"),
"host": url.host,
"path": (url.path ?? "/"),
"query": url.query,
"method": lib_plankton.http.enum_method.get,
"headers": {},
"body": null,
};
// TODO: cache?
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
http_request,
{
"name": (
(vevent.summary !== undefined)
?
vevent.summary
:
"???"
),
"begin": _zeitbild.frontend.helpers.ical_dt_to_own_datetime(vevent.dtstart),
"end": (
(vevent.dtend !== undefined)
?
_zeitbild.frontend.helpers.ical_dt_to_own_datetime(vevent.dtend)
:
null
),
"location": (
(vevent.location !== undefined)
?
vevent.location
:
null
),
"description": (
(vevent.description !== undefined)
?
vevent.description
:
null
),
}
:
null
)
)
.filter(
(event) => (event !== null)
)
.filter(
(event) => _zeitbild.frontend.helpers.pit_is_between(
_zeitbild.frontend.helpers.pit_from_datetime(event.begin),
from_pit,
to_pit
)
)
.map(
(event) => ({
"calendar_id": calendar_id,
"calendar_name": calendar_object.data.name,
"event": event,
})
)
);
break;
);
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(
http_response.body.toString(),
{
}
);
result = (
result
.concat(
vcalendar.vevents
.map(
(vevent : lib_plankton.ical.type_vevent) => (
(vevent.dtstart !== undefined)
?
{
"name": (
(vevent.summary !== undefined)
?
vevent.summary
:
"???"
),
"begin": _zeitbild.frontend.helpers.ical_dt_to_own_datetime(vevent.dtstart),
"end": (
(vevent.dtend !== undefined)
?
_zeitbild.frontend.helpers.ical_dt_to_own_datetime(vevent.dtend)
:
null
),
"location": (
(vevent.location !== undefined)
?
vevent.location
:
null
),
"description": (
(vevent.description !== undefined)
?
vevent.description
:
null
),
}
:
null
)
)
.filter(
(event) => (event !== null)
)
.filter(
(event) => _zeitbild.frontend.helpers.pit_is_between(
_zeitbild.frontend.helpers.pit_from_datetime(event.begin),
from_pit,
to_pit
)
)
.map(
(event) => ({
"calendar_id": calendar_id,
"calendar_name": calendar_object.data.name,
"event": event,
})
)
)
);
break;
}
}
}
}
return Promise.resolve(result);
}
}

View file

@ -7,11 +7,87 @@ namespace _zeitbild.frontend
*/
type type_conf = {
view_mode : string;
calendar_id : int;
calendar_ids : Array<int>;
timezone_shift : int;
};
/**
*/
async function render(
conf : type_conf,
calendar_ids : Array<type_calendar_id>
) : Promise<void>
{
calendar_ids.sort();
const target : HTMLElement = (document.querySelector("body") as HTMLBodyElement);
switch (conf.view_mode) {
default: {
throw (new Error("invalid view mode"));
break;
}
case "table": {
const content : string = await _zeitbild.frontend.view.calendar_view_table_html(
calendar_ids,
{
"from": {
"year": 2024,
"week": 35
},
"to": {
"year": 2024,
"week": 43
},
"timezone_shift": conf.timezone_shift,
}
);
target.innerHTML = content;
/*
document.querySelectorAll(".tableview-sources-entry").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
const element_ : HTMLElement = (event.target as HTMLElement);
const calendar_id : type_calendar_id = parseInt(element_.getAttribute("rel") as string);
const active : boolean = element_.classList.toggle("tableview-sources-entry-active");
render(
conf,
lib_plankton.call.convey(
calendar_ids,
[
(x : Array<int>) => (
active
?
calendar_ids.concat([calendar_id])
:
calendar_ids.filter(y => (y !== calendar_id))
),
]
)
);
}
);
}
);
*/
break;
}
case "list": {
const content : string = await _zeitbild.frontend.view.calendar_view_list_html(
calendar_ids,
{
"timezone_shift": conf.timezone_shift,
}
);
target.innerHTML = content;
break;
}
}
return Promise.resolve<void>(undefined);
}
/**
*/
export async function main(
@ -27,42 +103,29 @@ namespace _zeitbild.frontend
// conf
const conf : type_conf = lib_plankton.json.decode(await lib_plankton.file.read("conf.json"));
// args
const url : URL = new URL(window.location.toString());
const calendar_ids : Array<type_calendar_id> = (
(url.searchParams.get("ids") !== null)
?
lib_plankton.call.convey(
url.searchParams.get("ids"),
[
(x : string) => lib_plankton.string.split(x, ","),
(x : Array<string>) => x.map(y => parseInt(y)),
]
)
:
(await _zeitbild.frontend.resources.backend.calendar_list()).map(x => x.key)
);
// exec
let content : string;
switch (conf.view_mode) {
default: {
content = "";
throw (new Error("invalid view mode"));
break;
}
case "table": {
content = await _zeitbild.frontend.view.calendar_view_table_html(
conf.calendar_id,
{
"from": {
"year": 2024,
"week": 35
},
"to": {
"year": 2024,
"week": 43
},
"timezone_shift": conf.timezone_shift,
}
);
break;
}
case "list": {
content = await _zeitbild.frontend.view.calendar_view_list_html(
conf.calendar_id,
{
"timezone_shift": conf.timezone_shift,
}
);
break;
}
}
(document.querySelector("body") as HTMLBodyElement).innerHTML = content;
await render(
conf,
calendar_ids
);
return Promise.resolve<void>(undefined);
}

View file

@ -72,17 +72,6 @@ namespace _zeitbild.frontend
};
}
|
{
kind : "collection";
data : {
name : string;
private : boolean;
sources : Array<
type_calendar_id
>;
}
}
|
{
kind : "caldav";
data : {

View file

@ -65,7 +65,7 @@ namespace _zeitbild.frontend.view
{
"year": event.end.date.year.toFixed(0).padStart(4, "0"),
"month": event.end.date.month.toFixed(0).padStart(2, "0"),
"day": event.end.date.month.toFixed(0).padStart(2, "0"),
"day": event.end.date.day.toFixed(0).padStart(2, "0"),
}
)
:
@ -134,7 +134,7 @@ namespace _zeitbild.frontend.view
* @todo kein "while"
*/
async function calendar_view_table_data(
calendar_id : type_calendar_id,
calendar_ids : Array<type_calendar_id>,
options : {
from ?: {
year : int;
@ -232,7 +232,7 @@ namespace _zeitbild.frontend.view
event : type_event;
}
> = await _zeitbild.frontend.resources.backend.calendar_gather_events(
calendar_id,
calendar_ids,
from_pit,
to_pit
);
@ -389,7 +389,7 @@ namespace _zeitbild.frontend.view
/**
*/
export async function calendar_view_table_html(
calendar_id : type_calendar_id,
calendar_ids : Array<type_calendar_id>,
options : {
from ?: {
year : int;
@ -428,7 +428,7 @@ namespace _zeitbild.frontend.view
}
>;
} = await calendar_view_table_data(
calendar_id,
calendar_ids,
options
);
const sources : lib_plankton.structures.type_hashmap<
@ -448,7 +448,7 @@ namespace _zeitbild.frontend.view
"value": {
"name": pair.value.name,
"color": lib_plankton.color.give_generic(
(pair.key + 0.2),
(pair.key - 1),
{
"saturation": 0.375,
"value": 0.375,
@ -470,6 +470,7 @@ namespace _zeitbild.frontend.view
{
"name": data.name,
"color": lib_plankton.color.output_hex(data.color),
"rel": calendar_id.toFixed(0),
}
)
)
@ -567,7 +568,7 @@ namespace _zeitbild.frontend.view
/**
*/
async function calendar_view_list_data(
calendar_id : type_calendar_id,
calendar_ids : Array<type_calendar_id>,
options : {
from ?: _zeitbild.frontend.helpers.type_pit;
to ?: _zeitbild.frontend.helpers.type_pit;
@ -608,7 +609,7 @@ namespace _zeitbild.frontend.view
event : type_event;
}
> = await _zeitbild.frontend.resources.backend.calendar_gather_events(
calendar_id,
calendar_ids,
(options.from as _zeitbild.frontend.helpers.type_pit),
(options.to as _zeitbild.frontend.helpers.type_pit)
);
@ -628,7 +629,7 @@ namespace _zeitbild.frontend.view
/**
*/
export async function calendar_view_list_html(
calendar_id : type_calendar_id,
calendar_ids : Array<type_calendar_id>,
options : {
from ?: _zeitbild.frontend.helpers.type_pit;
to ?: _zeitbild.frontend.helpers.type_pit;
@ -642,7 +643,7 @@ namespace _zeitbild.frontend.view
event : type_event;
}
> = await calendar_view_list_data(
calendar_id,
calendar_ids,
options
);
return Promise.resolve<string>(

View file

@ -22,11 +22,17 @@ html {
margin: 0;
padding: 0;
list-style-type: none;
font-size: 0.75em;
}
.tableview-sources-entry {
margin: 8px;
padding: 4px;
cursor: pointer;
}
.tableview-sources-entry:not(.tableview-sources-entry-active) {
filter: saturate(0);
}
.calendar table {

View file

@ -1 +1 @@
<li class="tableview-sources-entry" style="background-color: {{color}}">{{name}}</li>
<li class="tableview-sources-entry tableview-sources-entry-active" style="background-color: {{color}}" rel="{{rel}}">{{name}}</li>