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, "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": { "object": {
"kind": "collection", "kind": "collection",
"data": { "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 = ( 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 ( return (
data.calendars data.calendars
.map( .map(
(calendar_entry) => ({ (calendar_entry) => {
switch (calendar_entry.object.kind) {
case "concrete": {
return {
"key": calendar_entry.id, "key": calendar_entry.id,
"preview": { "preview": {
"name": calendar_entry.object.data.name, "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, data : type_datamodel,
calendar_id : type_calendar_id, calendar_id : type_calendar_id,
from_pit : type_pit, from_pit : type_pit,
to_pit : type_pit to_pit : type_pit
) : Array< ) : Promise<
Array<
{ {
calendar_id : type_calendar_id; calendar_id : type_calendar_id;
event : type_event; event : type_event;
} }
>
> >
{ {
const calendar_object : type_calendar_object = calendar_read( const calendar_object : type_calendar_object = calendar_read(
@ -159,7 +192,7 @@ function calendar_gather_events(
); );
switch (calendar_object.kind) { switch (calendar_object.kind) {
case "concrete": { case "concrete": {
return ( return Promise.resolve(
calendar_object.data.events calendar_object.data.events
.filter( .filter(
(event) => pit_is_between( (event) => pit_is_between(
@ -176,6 +209,7 @@ function calendar_gather_events(
} }
case "collection": { case "collection": {
return ( return (
Promise.all(
calendar_object.data.sources calendar_object.data.sources
.map( .map(
(source_calendar_id) => calendar_gather_events( (source_calendar_id) => calendar_gather_events(
@ -185,10 +219,81 @@ function calendar_gather_events(
to_pit to_pit
) )
) )
)
.then(
(sources) => Promise.resolve(
sources
.reduce( .reduce(
(x, y) => x.concat(y), (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; break;
} }
@ -197,22 +302,24 @@ function calendar_gather_events(
/** /**
* @todo kein "while"
*/ */
function calendar_view_table( async function calendar_view_table_data(
data : type_datamodel, data : type_datamodel,
calendar_id : type_calendar_id, calendar_id : type_calendar_id,
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
},
options : { options : {
from ?: {
year : int;
week : int;
},
to ?: {
year : int;
week : int;
},
timezone_shift ?: int; timezone_shift ?: int;
} = {} } = {}
) : Array< ) : Promise<
Array<
{ {
week : int; week : int;
data : Array< data : Array<
@ -228,10 +335,34 @@ function calendar_view_table(
} }
>; >;
} }
>
> >
{ {
const now_pit : type_pit = pit_now();
options = Object.assign( 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, "timezone_shift": 0,
}, },
options options
@ -243,20 +374,19 @@ function calendar_view_table(
); );
*/ */
const from_pit : type_pit = pit_from_year_and_week( const from_pit : type_pit = pit_from_year_and_week(
from.year, (options.from as {year : int; week : int}).year,
from.week, (options.from as {year : int; week : int}).week,
{ {
"timezone_shift": (options.timezone_shift as int), "timezone_shift": (options.timezone_shift as int),
} }
); );
const to_pit : type_pit = pit_from_year_and_week( const to_pit : type_pit = pit_from_year_and_week(
to.year, (options.to as {year : int; week : int}).year,
to.week, (options.to as {year : int; week : int}).week,
{ {
"timezone_shift": (options.timezone_shift as int), "timezone_shift": (options.timezone_shift as int),
} }
); );
const now_pit : type_pit = pit_now();
// prepare // prepare
const entries : Array< const entries : Array<
@ -264,7 +394,7 @@ function calendar_view_table(
calendar_id : type_calendar_id; calendar_id : type_calendar_id;
event : type_event; event : type_event;
} }
> = calendar_gather_events( > = await calendar_gather_events(
data, data,
calendar_id, calendar_id,
from_pit, from_pit,
@ -312,7 +442,18 @@ function calendar_view_table(
} }
); );
if (day % 7 === 0) { 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 = []; row = [];
} }
else { else {
@ -337,8 +478,13 @@ function calendar_view_table(
const week : int = Math.floor(Math.floor(distance_days) / 7); const week : int = Math.floor(Math.floor(distance_days) / 7);
const day : int = (Math.floor(distance_days) % 7); const day : int = (Math.floor(distance_days) % 7);
if ((week >= 0) && (week < result.length)) {
result[week].data[day].entries.push(entry); result[week].data[day].entries.push(entry);
} }
else {
// do nothing
}
}
) )
); );
// today // today
@ -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, data : type_datamodel,
calendar_id : type_calendar_id, calendar_id : type_calendar_id,
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
},
options : { options : {
from ?: {
year : int;
week : int;
};
to ?: {
year : int;
week : int;
};
timezone_shift ?: int; timezone_shift ?: int;
} = {} } = {}
) : string ) : Promise<string>
{ {
options = Object.assign( const stuff : Array<
{
"timezone_shift": 0,
},
options
);
const rows : Array<
{ {
week : int; week : int;
data : Array< data : Array<
@ -402,16 +542,12 @@ function calendar_view_table_html(
} }
>; >;
} }
> = calendar_view_table( > = await calendar_view_table_data(
data, data,
calendar_id, calendar_id,
from, options
to,
{
"timezone_shift": options.timezone_shift,
}
); );
return ( return Promise.resolve<string>(
new lib_plankton.xml.class_node_complex( new lib_plankton.xml.class_node_complex(
"div", "div",
{ {
@ -433,7 +569,7 @@ function calendar_view_table_html(
+ +
".calendar-cell-week {width: 5.5%;}\n" ".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" ".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-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( .map(
(row) => ( (row) => (
new lib_plankton.xml.class_node_complex( 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), new lib_plankton.xml.class_node_text(entry.event.name),
@ -620,3 +862,133 @@ function calendar_view_table_html(
).compile() ).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({ const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler({
"data_path": lib_plankton.args.class_argument.volatile({ "data_path": lib_plankton.args.class_argument.volatile({
"indicators_long": ["data_path"], "indicators_long": ["data-path"],
"indicators_short": ["d"], "indicators_short": ["d"],
"type": lib_plankton.args.enum_type.string, "type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace, "mode": lib_plankton.args.enum_mode.replace,
@ -17,14 +17,32 @@ async function main(
"name": "data-path", "name": "data-path",
}), }),
"timezone_shift": lib_plankton.args.class_argument.volatile({ "timezone_shift": lib_plankton.args.class_argument.volatile({
"indicators_long": ["timezone_shift"], "indicators_long": ["timezone-shift"],
"indicators_short": ["t"], "indicators_short": ["t"],
"type": lib_plankton.args.enum_type.string, "type": lib_plankton.args.enum_type.integer,
"mode": lib_plankton.args.enum_mode.replace, "mode": lib_plankton.args.enum_mode.replace,
"default": 0, "default": 0,
// "info": null, // "info": null,
"name": "timezone-shift", "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({ "help": lib_plankton.args.class_argument.volatile({
"indicators_long": ["help"], "indicators_long": ["help"],
"indicators_short": ["h"], "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(" ")); 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 // exec
if (args["help"]) { if (args["help"]) {
process.stdout.write( process.stdout.write(
@ -67,6 +93,10 @@ async function main(
"object": ( "object": (
((calendar_object_raw) => { ((calendar_object_raw) => {
switch (calendar_object_raw["kind"]) { switch (calendar_object_raw["kind"]) {
default: {
return calendar_object_raw;
break;
}
case "concrete": { case "concrete": {
return { return {
"kind": "concrete", "kind": "concrete",
@ -94,10 +124,6 @@ async function main(
}; };
break; break;
} }
case "collection": {
return calendar_object_raw;
break;
}
} }
}) (calendar_entry_raw["object"]) }) (calendar_entry_raw["object"])
), ),
@ -108,21 +134,34 @@ async function main(
), ),
] ]
); );
const output : string = calendar_view_table_html( 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, data,
4, args.calendar_id,
{ {
"year": 2024, "timezone_shift": args["timezone_shift"],
"week": 35
},
{
"year": 2024,
"week": 41
},
{
"timezone_shift": parseInt(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); process.stdout.write(output);
} }
return Promise.resolve<void>(undefined); return Promise.resolve<void>(undefined);
@ -164,7 +203,9 @@ process.stderr.write(
.then( .then(
() => {} () => {}
) )
/*
.catch( .catch(
(error) => {process.stderr.write(String(error) + "\n");} (error) => {process.stderr.write(String(error) + "\n");}
) )
*/
); );

View file

@ -8,13 +8,6 @@ import argparse as _argparse
def main(): def main():
## args ## args
argument_parser = _argparse.ArgumentParser() 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( argument_parser.add_argument(
"-o", "-o",
"--output-directory", "--output-directory",
@ -28,8 +21,6 @@ def main():
## exec ## exec
targets = [] targets = []
targets.append("default") targets.append("default")
if args.tests:
targets.append("test")
_os.system( _os.system(
"make dir_build=%s --file=tools/makefile %s" "make dir_build=%s --file=tools/makefile %s"
% ( % (