This commit is contained in:
Fenris Wolf 2024-09-10 01:11:58 +02:00
parent 04192d6ea2
commit 94485919a7
5 changed files with 588 additions and 113 deletions

View file

@ -129,6 +129,24 @@
},
{
"id": 4,
"object": {
"kind": "caldav",
"data": {
"source_url": "https://export.kalender.digital/ics/0/3e10dae66950379d4cc8/gesamterkalender.ics?past_months=3&future_months=36"
}
}
},
{
"id": 5,
"object": {
"kind": "caldav",
"data": {
"source_url": "https://roydfalk:uywvui66svLHFMUp6LndxNO9ZASNrY9vmUDcAPPNIr7PWmjAMpEQce0JiA9AAeUH@vikunja.linke.sx/dav/projects/4/"
}
}
},
{
"id": 6,
"object": {
"kind": "collection",
"data": {

View file

@ -459,3 +459,56 @@ function pit_from_year_and_week(
);
}
/**
* @todo timezone
*/
function ical_datetime_to_own_datetime(
ical_datetime : lib_plankton.ical.type_datetime
) : type_datetime
{
return {
"timezone_shift": 0,
"date": {
"year": ical_datetime.date.year,
"month": ical_datetime.date.month,
"day": ical_datetime.date.day,
},
"time": (
(ical_datetime.time === null)
?
null
:
{
"hour": ical_datetime.time.hour,
"minute": ical_datetime.time.minute,
"second": ical_datetime.time.second,
}
)
};
}
/**
* @todo timezone
*/
function ical_dt_to_own_datetime(
ical_dt: lib_plankton.ical.type_dt
) : type_datetime
{
return {
"timezone_shift": 0,
"date": ical_dt.value.date,
"time": (
(ical_dt.value.time === null)
?
null
:
{
"hour": ical_dt.value.time.hour,
"minute": ical_dt.value.time.minute,
"second": ical_dt.value.time.second,
}
)
};
}

View file

@ -44,6 +44,7 @@ type type_calendar_id = int;
/**
* @todo bei "collection" Kreise vermeiden
*/
type type_calendar_object = (
{
@ -69,6 +70,13 @@ type type_calendar_object = (
>;
}
}
|
{
kind : "caldav";
data : {
source_url : string;
}
}
);
@ -106,12 +114,35 @@ function calendar_list(
return (
data.calendars
.map(
(calendar_entry) => ({
"key": calendar_entry.id,
"preview": {
"name": calendar_entry.object.data.name,
(calendar_entry) => {
switch (calendar_entry.object.kind) {
case "concrete": {
return {
"key": calendar_entry.id,
"preview": {
"name": calendar_entry.object.data.name,
}
};
break;
}
case "collection": {
return {
"key": calendar_entry.id,
"preview": {
"name": calendar_entry.object.data.name,
}
};
}
case "caldav": {
return {
"key": calendar_entry.id,
"preview": {
"name": "(imported)",
}
};
}
}
})
}
)
);
}
@ -141,16 +172,18 @@ function calendar_read(
/**
*/
function calendar_gather_events(
async function calendar_gather_events(
data : type_datamodel,
calendar_id : type_calendar_id,
from_pit : type_pit,
to_pit : type_pit
) : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
) : Promise<
Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>
>
{
const calendar_object : type_calendar_object = calendar_read(
@ -159,7 +192,7 @@ function calendar_gather_events(
);
switch (calendar_object.kind) {
case "concrete": {
return (
return Promise.resolve(
calendar_object.data.events
.filter(
(event) => pit_is_between(
@ -176,18 +209,90 @@ function calendar_gather_events(
}
case "collection": {
return (
calendar_object.data.sources
.map(
(source_calendar_id) => calendar_gather_events(
data,
source_calendar_id,
from_pit,
to_pit
Promise.all(
calendar_object.data.sources
.map(
(source_calendar_id) => calendar_gather_events(
data,
source_calendar_id,
from_pit,
to_pit
)
)
)
.reduce(
(x, y) => x.concat(y),
[]
.then(
(sources) => Promise.resolve(
sources
.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/1.1",
"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,
};
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(
http_request,
{
}
);
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(
http_response.body.toString(),
{
}
);
return Promise.resolve(
vcalendar.vevents
.map(
(vevent : lib_plankton.ical.type_vevent) => (
(vevent.dtstart !== undefined)
?
{
"name": (
(vevent.summary !== undefined)
?
vevent.summary
:
"???"
),
"begin": ical_dt_to_own_datetime(vevent.dtstart),
"end": (
(vevent.dtend !== undefined)
?
ical_dt_to_own_datetime(vevent.dtend)
:
null
),
"description": (
(vevent.description !== undefined)
?
vevent.description
:
null
),
}
:
null
)
)
.filter(
(event) => (event !== null)
)
.map(
(event) => ({"calendar_id": calendar_id, "event": event})
)
);
break;
@ -197,41 +302,67 @@ function calendar_gather_events(
/**
* @todo kein "while"
*/
function calendar_view_table(
async function calendar_view_table_data(
data : type_datamodel,
calendar_id : type_calendar_id,
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
},
options : {
from ?: {
year : int;
week : int;
},
to ?: {
year : int;
week : int;
},
timezone_shift ?: int;
} = {}
) : Array<
{
week : int;
data : Array<
{
pit : type_pit;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>;
today : boolean;
}
>;
}
) : Promise<
Array<
{
week : int;
data : Array<
{
pit : type_pit;
entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>;
today : boolean;
}
>;
}
>
>
{
const now_pit : type_pit = pit_now();
options = Object.assign(
{
"from": lib_plankton.call.convey(
now_pit,
[
(x : type_pit) => pit_shift_week(x, -1),
pit_to_date_object,
(x : Date) => ({
"year": x.getFullYear(),
"week": date_object_get_week_of_year(x),
})
]
),
"to": lib_plankton.call.convey(
now_pit,
[
(x : type_pit) => pit_shift_week(x, +4),
pit_to_date_object,
(x : Date) => ({
"year": x.getFullYear(),
"week": date_object_get_week_of_year(x),
})
]
),
"timezone_shift": 0,
},
options
@ -243,20 +374,19 @@ function calendar_view_table(
);
*/
const from_pit : type_pit = pit_from_year_and_week(
from.year,
from.week,
(options.from as {year : int; week : int}).year,
(options.from as {year : int; week : int}).week,
{
"timezone_shift": (options.timezone_shift as int),
}
);
const to_pit : type_pit = pit_from_year_and_week(
to.year,
to.week,
(options.to as {year : int; week : int}).year,
(options.to as {year : int; week : int}).week,
{
"timezone_shift": (options.timezone_shift as int),
}
);
const now_pit : type_pit = pit_now();
// prepare
const entries : Array<
@ -264,7 +394,7 @@ function calendar_view_table(
calendar_id : type_calendar_id;
event : type_event;
}
> = calendar_gather_events(
> = await calendar_gather_events(
data,
calendar_id,
from_pit,
@ -312,7 +442,18 @@ function calendar_view_table(
}
);
if (day % 7 === 0) {
result.push({"week": (from.week + Math.floor(day / 7)), "data": row});
result.push(
{
"week": (
(options.from as {year : int; week : int}).week
+
Math.floor(day / 7)
-
1 // TODO
),
"data": row
}
);
row = [];
}
else {
@ -337,7 +478,12 @@ function calendar_view_table(
const week : int = Math.floor(Math.floor(distance_days) / 7);
const day : int = (Math.floor(distance_days) % 7);
result[week].data[day].entries.push(entry);
if ((week >= 0) && (week < result.length)) {
result[week].data[day].entries.push(entry);
}
else {
// do nothing
}
}
)
);
@ -358,35 +504,29 @@ function calendar_view_table(
}
}
return result;
return Promise.resolve(result);
}
/**
*/
function calendar_view_table_html(
async function calendar_view_table_html(
data : type_datamodel,
calendar_id : type_calendar_id,
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
},
options : {
from ?: {
year : int;
week : int;
};
to ?: {
year : int;
week : int;
};
timezone_shift ?: int;
} = {}
) : string
) : Promise<string>
{
options = Object.assign(
{
"timezone_shift": 0,
},
options
);
const rows : Array<
const stuff : Array<
{
week : int;
data : Array<
@ -402,16 +542,12 @@ function calendar_view_table_html(
}
>;
}
> = calendar_view_table(
> = await calendar_view_table_data(
data,
calendar_id,
from,
to,
{
"timezone_shift": options.timezone_shift,
}
options
);
return (
return Promise.resolve<string>(
new lib_plankton.xml.class_node_complex(
"div",
{
@ -433,7 +569,7 @@ function calendar_view_table_html(
+
".calendar-cell-week {width: 5.5%;}\n"
+
".calendar-cell-regular {width: 13.5%;}\n"
".calendar-cell-regular {width: 13.5%; height: 150px;}\n"
+
".calendar-cell-today {background-color: #333;}\n"
+
@ -441,7 +577,7 @@ function calendar_view_table_html(
+
".calendar-events {margin: 0; padding: 0; list-style-type: none;}\n"
+
".calendar-event_entry {margin: 4px; padding: 4px; border-radius: 2px; font-size: 0.75em;}\n"
".calendar-event_entry {margin: 4px; padding: 4px; border-radius: 2px; font-size: 0.75em; color: #FFF; font-weight: bold; cursor: pointer;}\n"
)
]
),
@ -493,7 +629,7 @@ function calendar_view_table_html(
{
},
(
rows
stuff
.map(
(row) => (
new lib_plankton.xml.class_node_complex(
@ -594,6 +730,112 @@ function calendar_view_table_html(
),
}
),
"title": (
lib_plankton.string.coin(
"[{{calendar_name}}] {{event_name}}\n",
{
"calendar_name": entry.calendar_id.toFixed(0),
"event_name": entry.event.name,
}
)
+
"--\n"
+
(
(entry.event.begin.time !== null)
?
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": "Anfang", // TODO: translate
"value": lib_plankton.string.coin(
"{{hour}}:{{minute}}",
{
"hour": entry.event.begin.time.hour.toFixed(0).padStart(2, "0"),
"minute": entry.event.begin.time.minute.toFixed(0).padStart(2, "0"),
}
), // TODO: outsource
}
)
:
""
)
+
(
(entry.event.end !== null)
?
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": "Ende", // TODO: translate
"value": (
[
(
(
(entry.event.end.date.year !== entry.event.begin.date.year)
||
(entry.event.end.date.month !== entry.event.begin.date.month)
||
(entry.event.end.date.day !== entry.event.begin.date.day)
)
?
lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": entry.event.end.date.year.toFixed(0).padStart(4, "0"),
"month": entry.event.end.date.month.toFixed(0).padStart(2, "0"),
"day": entry.event.end.date.month.toFixed(0).padStart(2, "0"),
}
)
:
null
),
(
(entry.event.end.time !== null)
?
lib_plankton.string.coin(
"{{hour}}:{{minute}}",
{
"hour": entry.event.end.time.hour.toFixed(0).padStart(2, "0"),
"minute": entry.event.end.time.minute.toFixed(0).padStart(2, "0"),
}
)
:
null
),
]
.filter(x => (x !== null))
.join(",")
),
}
)
:
""
)
+
(
(entry.event.description !== null)
?
(
"--\n"
+
lib_plankton.string.coin(
"{{description}}\n",
{
"description": (
(entry.event.description !== null)
?
entry.event.description
:
"?"
),
}
)
)
:
""
)
),
},
[
new lib_plankton.xml.class_node_text(entry.event.name),
@ -620,3 +862,133 @@ function calendar_view_table_html(
).compile()
);
}
/**
*/
async function calendar_view_list_data(
data : type_datamodel,
calendar_id : type_calendar_id,
options : {
from ?: type_pit;
to ?: type_pit;
timezone_shift ?: int;
} = {}
) : Promise<
Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
>
>
{
const now_pit : type_pit = pit_now();
options = Object.assign(
{
"from": lib_plankton.call.convey(
now_pit,
[
(x : type_pit) => pit_shift_day(x, -1),
]
),
"to": lib_plankton.call.convey(
now_pit,
[
(x : type_pit) => pit_shift_week(x, +4),
]
),
"timezone_shift": 0,
},
options
);
const entries : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
> = await calendar_gather_events(
data,
calendar_id,
(options.from as type_pit),
(options.to as type_pit)
);
// TODO: optimize
entries.sort(
(entry_1, entry_2) => (pit_from_datetime(entry_1.event.begin) - pit_from_datetime(entry_2.event.begin))
);
return Promise.resolve(entries);
}
/**
*/
async function calendar_view_list_html(
data : type_datamodel,
calendar_id : type_calendar_id,
options : {
from ?: type_pit;
to ?: type_pit;
timezone_shift ?: int;
} = {}
) : Promise<string>
{
const stuff : Array<
{
calendar_id : type_calendar_id;
event : type_event;
}
> = await calendar_view_list_data(
data,
calendar_id,
options
);
return Promise.resolve<string>(
new lib_plankton.xml.class_node_complex(
"div",
{
"class": "list",
},
[
new lib_plankton.xml.class_node_complex(
"style",
{},
[
new lib_plankton.xml.class_node_text(
"html {background-color: #111; color: #FFF; font-family: sans-serif;}\n"
+
"table {width: 100%; border-collapse: collapse;}\n"
)
]
),
new lib_plankton.xml.class_node_complex(
"ul",
{
"class": "list-events",
},
(
stuff
.map(
(entry) => (
new lib_plankton.xml.class_node_complex(
"li",
{
"class": "list-event_entry",
},
[
new lib_plankton.xml.class_node_text(
JSON.stringify(entry)
),
]
)
)
)
)
),
]
).compile()
);
}

View file

@ -8,7 +8,7 @@ async function main(
{
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler({
"data_path": lib_plankton.args.class_argument.volatile({
"indicators_long": ["data_path"],
"indicators_long": ["data-path"],
"indicators_short": ["d"],
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
@ -17,14 +17,32 @@ async function main(
"name": "data-path",
}),
"timezone_shift": lib_plankton.args.class_argument.volatile({
"indicators_long": ["timezone_shift"],
"indicators_long": ["timezone-shift"],
"indicators_short": ["t"],
"type": lib_plankton.args.enum_type.string,
"type": lib_plankton.args.enum_type.integer,
"mode": lib_plankton.args.enum_mode.replace,
"default": 0,
// "info": null,
"name": "timezone-shift",
}),
"calendar_id": lib_plankton.args.class_argument.volatile({
"indicators_long": ["calendar"],
"indicators_short": ["c"],
"type": lib_plankton.args.enum_type.integer,
"mode": lib_plankton.args.enum_mode.replace,
"default": 1,
// "info": null,
"name": "calendar_id",
}),
"view_mode": lib_plankton.args.class_argument.volatile({
"indicators_long": ["view-moode"],
"indicators_short": ["m"],
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
"default": "table",
// "info": null,
"name": "view_mode",
}),
"help": lib_plankton.args.class_argument.volatile({
"indicators_long": ["help"],
"indicators_short": ["h"],
@ -37,6 +55,14 @@ async function main(
});
const args : Record<string, any> = arg_handler.read(lib_plankton.args.enum_environment.cli, args_raw.join(" "));
// init
lib_plankton.log.conf_push(
[
lib_plankton.log.channel_make({"kind": "file", "data": {"threshold": "debug", "path": "/tmp/kalender.log"}}),
// lib_plankton.log.channel_make({"kind": "stdout", "data": {"threshold": "info"}}),
]
);
// exec
if (args["help"]) {
process.stdout.write(
@ -67,6 +93,10 @@ async function main(
"object": (
((calendar_object_raw) => {
switch (calendar_object_raw["kind"]) {
default: {
return calendar_object_raw;
break;
}
case "concrete": {
return {
"kind": "concrete",
@ -94,10 +124,6 @@ async function main(
};
break;
}
case "collection": {
return calendar_object_raw;
break;
}
}
}) (calendar_entry_raw["object"])
),
@ -108,21 +134,34 @@ async function main(
),
]
);
const output : string = calendar_view_table_html(
data,
4,
{
"year": 2024,
"week": 35
},
{
"year": 2024,
"week": 41
},
{
"timezone_shift": parseInt(args["timezone_shift"]),
let output : string;
switch (args["view_mode"]) {
default: {
output = "";
throw (new Error("invalid view mode"));
break;
}
);
case "table": {
output = await calendar_view_table_html(
data,
args.calendar_id,
{
"timezone_shift": args["timezone_shift"],
}
);
break;
}
case "list": {
output = await calendar_view_list_html(
data,
args.calendar_id,
{
"timezone_shift": args["timezone_shift"],
}
);
break;
}
}
process.stdout.write(output);
}
return Promise.resolve<void>(undefined);
@ -164,7 +203,9 @@ process.stderr.write(
.then(
() => {}
)
/*
.catch(
(error) => {process.stderr.write(String(error) + "\n");}
)
*/
);

View file

@ -8,13 +8,6 @@ import argparse as _argparse
def main():
## args
argument_parser = _argparse.ArgumentParser()
argument_parser.add_argument(
"-t",
"--tests",
action = "store_true",
default = False,
help = "whether to also build the test routines",
)
argument_parser.add_argument(
"-o",
"--output-directory",
@ -28,8 +21,6 @@ def main():
## exec
targets = []
targets.append("default")
if args.tests:
targets.append("test")
_os.system(
"make dir_build=%s --file=tools/makefile %s"
% (