[mod]
This commit is contained in:
parent
12697e2a7a
commit
762f3ba3d6
9 changed files with 267 additions and 231 deletions
|
@ -1,5 +1,4 @@
|
|||
{
|
||||
"view_mode": "table",
|
||||
"calendar_id": 6,
|
||||
"timezone_shift": 0
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -72,17 +72,6 @@ namespace _zeitbild.frontend
|
|||
};
|
||||
}
|
||||
|
|
||||
{
|
||||
kind : "collection";
|
||||
data : {
|
||||
name : string;
|
||||
private : boolean;
|
||||
sources : Array<
|
||||
type_calendar_id
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
|
||||
{
|
||||
kind : "caldav";
|
||||
data : {
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue