[add] widget system (to be outsourced to plankton) [add] widget:sources [add] widget:weekview [mod] page:events:make use of new widgets

This commit is contained in:
Fenris Wolf 2024-10-01 21:33:14 +02:00
parent e471e1f252
commit 219a19a173
25 changed files with 1376 additions and 1512 deletions

View file

@ -1,347 +0,0 @@
{
"users": [
{
"id": 1,
"object": {
"name": "christian.frass"
}
},
{
"id": 2,
"object": {
"name": "andre.weichert"
}
},
{
"id": 3,
"object": {
"name": "steffen.doegnitz"
}
},
{
"id": 4,
"object": {
"name": "frank.dietrich"
}
},
{
"id": 5,
"object": {
"name": "michael.berger"
}
},
{
"id": 6,
"object": {
"name": "roland.schroeder"
}
},
{
"id": 7,
"object": {
"name": "rene.hahn"
}
},
{
"id": 8,
"object": {
"name": "max.meierhof"
}
},
{
"id": 9,
"object": {
"name": "klaus.kleba"
}
},
{
"id": 10,
"object": {
"name": "tim.detzner"
}
}
],
"calendars": [
{
"id": 1,
"object": {
"kind": "concrete",
"data": {
"name": "BV",
"private": false,
"hue": 0.0000000000000000,
"users": [
],
"events": [
{
"name": "9. Bundesparteitag | 1. Sitzung | Tag 1",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 10, "day": 18},
"time": {"hour": 10, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 10, "day": 18},
"time": {"hour": 18, "minute": 0, "second": 0}
},
"location": "Halle",
"description": null
},
{
"name": "9. Bundesparteitag | 1. Sitzung | Tag 2",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 10, "day": 19},
"time": {"hour": 10, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 10, "day": 19},
"time": {"hour": 18, "minute": 0, "second": 0}
},
"location": "Halle",
"description": null
},
{
"name": "9. Bundesparteitag | 1. Sitzung | Tag 3",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 10, "day": 20},
"time": {"hour": 10, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 10, "day": 20},
"time": {"hour": 18, "minute": 0, "second": 0}
},
"location": "Halle",
"description": null
}
]
}
}
},
{
"id": 2,
"object": {
"kind": "concrete",
"data": {
"name": "LV Sachsen",
"private": false,
"hue": 0.6180339887498949,
"users": [
{
"id": 9,
"role": "editor"
}
],
"events": [
{
"name": "Sören Pellmann zu den Landtagswahlen im Osten",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 11},
"time": {"hour": 18, "minute": 0, "second": 0}
},
"end": null,
"location": "online: https://v2202002113208108062.supersrv.de/b/har-jbu-lxy-rx1",
"description": null
},
{
"name": "Erinnern versammeln. Praktiken für die Zukünfte einer Gesellschaft der Vielen.",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 13},
"time": {"hour": 17, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 15},
"time": null
},
"location": "Weltecho, Annaberger Straße 24, 09111 Chemnitz",
"description": null
},
{
"name": "Parteikonvent zur Auswertung des Wahljahres",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 14},
"time": {"hour": 10, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 14},
"time": {"hour": 16, "minute": 30, "second": 0}
},
"location": "Veranstaltungs- und Kulturforum STADTPARK | Hammertal 3 | 09669 Frankenberg/Sachsen",
"description": null
},
{
"name": "Ist die extreme Rechte noch zu stoppen?",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 19},
"time": {"hour": 19, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 19},
"time": {"hour": 21, "minute": 0, "second": 0}
},
"location": "online: https://www.dielinke-sachsen.de/termine/?termin_ort=digital-internet-stream",
"description": null
}
]
}
}
},
{
"id": 3,
"object": {
"kind": "concrete",
"data": {
"name": "KV Zwickau",
"private": false,
"hue": 0.4721359549995796,
"events": [
{
"name": "Vorstands-Sitzung",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 5},
"time": {"hour": 18, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 5},
"time": {"hour": 21, "minute": 0, "second": 0}
},
"location": "Zwickau, Innere Schneeberger Straße 17",
"description": null
}
]
}
}
},
{
"id": 4,
"object": {
"kind": "concrete",
"data": {
"name": "OV Glauchau",
"private": false,
"users": [
{
"id": 1,
"role": "editor"
},
{
"id": 5,
"role": "editor"
},
{
"id": 6,
"role": "editor"
}
],
"hue": 0.09016994374947451,
"events": [
{
"name": "Kinderspieletag",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 8},
"time": {"hour": 12, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 8},
"time": {"hour": 16, "minute": 0, "second": 0}
},
"location": null,
"description": null
},
{
"name": "Mitglieder-Sitzung",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 19},
"time": {"hour": 17, "minute": 30, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 19},
"time": {"hour": 19, "minute": 0, "second": 0}
},
"location": null,
"description": null
}
]
}
}
},
{
"id": 5,
"object": {
"kind": "concrete",
"data": {
"name": "OV Zwickau",
"private": false,
"hue": 0.09016994374947451,
"users": [
{
"id": 7,
"role": "editor"
},
{
"id": 8,
"role": "viewer"
}
],
"events": [
{
"name": "4ter Christopher Street Day",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 8, "day": 31},
"time": {"hour": 10, "minute": 0, "second": 0}
},
"end": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 8, "day": 31},
"time": {"hour": 17, "minute": 0, "second": 0}
},
"location": "Zwickau",
"description": null
},
{
"name": "Regionaltreffen Westsachsen: Schule ohne Rassismus Schule mit Courage",
"begin": {
"timezone_shift": 2,
"date": {"year": 2024, "month": 9, "day": 19},
"time": {"hour": 9, "minute": 0, "second": 0}
},
"end": null,
"location": null,
"description": null
}
]
}
}
},
{
"id": 6,
"object": {
"kind": "caldav",
"data": {
"name": "Lixer",
"private": true,
"read_only": true,
"source_url": "https://export.kalender.digital/ics/0/3e10dae66950379d4cc8/gesamterkalender.ics?past_months=3&future_months=36"
}
}
}
]
}

View file

@ -28,6 +28,10 @@
"calendar.access.access": "Zugriff",
"calendar.access.default_level": "Standard",
"calendar.access.attributed": "Zuweisungen",
"widget.weekview.controls.year": "Jahr",
"widget.weekview.controls.week": "Woche",
"widget.weekview.controls.count": "Anzahl",
"widget.weekview.controls.apply": "Laden",
"page.login.title": "Anmelden",
"page.login.internal.name": "Name",
"page.login.internal.password": "Kennwort",
@ -46,10 +50,6 @@
"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.events.controls.year": "Jahr",
"page.events.controls.week": "Woche",
"page.events.controls.count": "Anzahl",
"page.events.controls.apply": "Laden"
"page.event_edit.actions.remove": "löschen"
}
}

View file

@ -28,6 +28,10 @@
"calendar.access.access": "access",
"calendar.access.default_level": "default",
"calendar.access.attributed": "attributed",
"widget.weekview.controls.year": "Year",
"widget.weekview.controls.week": "Week",
"widget.weekview.controls.count": "Count",
"widget.weekview.controls.apply": "Load",
"page.login.title": "Login",
"page.login.internal.name": "name",
"page.login.internal.password": "password",
@ -46,10 +50,6 @@
"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.events.controls.year": "Year",
"page.events.controls.week": "Week",
"page.events.controls.count": "Count",
"page.events.controls.apply": "Load"
"page.event_edit.actions.remove": "delete"
}
}

View file

@ -257,15 +257,29 @@ namespace _zeitbild.frontend_web.backend
{
id : int;
name : string;
access_level : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>
>
{
return call(
lib_plankton.http.enum_method.get,
"/calendar",
null
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),
})
)
)
)
);
}

View file

@ -1,791 +0,0 @@
/**
*/
namespace _zeitbild.frontend_web.view
{
/**
*/
function event_generate_tooltip(
calendar_name : string,
event : _zeitbild.frontend_web.type.event_object
) : string
{
return (
lib_plankton.string.coin(
"[{{calendar_name}}] {{event_name}}\n",
{
"calendar_name": calendar_name,
"event_name": event.name,
}
)
+
"--\n"
+
(
(event.begin.time !== null)
?
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": "Anfang", // TODO: translate
"value": lib_plankton.string.coin(
"{{hour}}:{{minute}}",
{
"hour": event.begin.time.hour.toFixed(0).padStart(2, "0"),
"minute": event.begin.time.minute.toFixed(0).padStart(2, "0"),
}
), // TODO: outsource
}
)
:
""
)
+
(
(event.end !== null)
?
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": "Ende", // TODO: translate
"value": (
[
(
(
(event.end.date.year !== event.begin.date.year)
||
(event.end.date.month !== event.begin.date.month)
||
(event.end.date.day !== event.begin.date.day)
)
?
lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": event.end.date.year.toFixed(0).padStart(4, "0"),
"month": event.end.date.month.toFixed(0).padStart(2, "0"),
"day": event.end.date.day.toFixed(0).padStart(2, "0"),
}
)
:
null
),
(
(event.end.time !== null)
?
lib_plankton.string.coin(
"{{hour}}:{{minute}}",
{
"hour": event.end.time.hour.toFixed(0).padStart(2, "0"),
"minute": event.end.time.minute.toFixed(0).padStart(2, "0"),
}
)
:
null
),
]
.filter(x => (x !== null))
.join(",")
),
}
)
:
""
)
+
(
(event.location !== null)
?
(
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": "Ort", // TODO
"value": event.location,
}
)
)
:
""
)
+
(
(event.description !== null)
?
(
"--\n"
+
lib_plankton.string.coin(
"{{description}}\n",
{
"description": event.description,
}
)
)
:
""
)
);
}
/**
* @todo kein "while"
*/
async function calendar_view_table_data(
calendar_ids : (
null
|
Array<_zeitbild.frontend_web.type.calendar_id>
),
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
},
timezone_shift : int,
) : Promise<
{
sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>;
rows : Array<
{
week : int;
data : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
>;
}
>
}
>
{
const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now();
const from_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd(
{
"year": (from as {year : int; week : int}).year,
"week": (from as {year : int; week : int}).week,
"day": 1,
},
{
"timezone_shift": (timezone_shift as int),
}
);
const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd(
{
"year": (to as {year : int; week : int}).year,
"week": (to as {year : int; week : int}).week,
"day": 1,
},
{
"timezone_shift": (timezone_shift as int),
}
);
// prepare
const entries : 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;
}
> = await _zeitbild.frontend_web.backend.events(
from_pit,
to_pit,
{
"calendar_ids": calendar_ids,
}
);
let result : {
sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>;
rows : Array<
{
week : int;
data : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
>;
}
>;
} = {
"sources": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
entries
.map(
(entry) => (
{
"key": entry.calendar_id,
"value": {
"name": entry.calendar_name,
"access_level": entry.access_level,
}
}
)
)
)
}
)
),
"rows": [],
};
let row : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
> = [];
let day : int = 0;
while (true) {
const pit_current : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day(
from_pit,
day
);
if (
lib_plankton.pit.is_before(
pit_current,
to_pit
)
) {
day += 1;
row.push(
{
"pit": pit_current,
"entries": [],
"today": false, // TODO
}
);
if (day % 7 === 0) {
result.rows.push(
{
"week": (
(from as {year : int; week : int}).week
+
Math.floor(day / 7)
-
1 // TODO
),
"data": row
}
);
row = [];
}
else {
// do nothing
}
}
else {
break;
}
}
// fill
{
// events
(
entries
.forEach(
(entry) => {
const distance_seconds : int = (
lib_plankton.pit.from_datetime(entry.event_object.begin)
-
from_pit
);
const distance_days : int = (distance_seconds / (60 * 60 * 24));
const week : int = Math.floor(Math.floor(distance_days) / 7);
const day : int = (Math.floor(distance_days) % 7);
if ((week >= 0) && (week < result.rows.length)) {
result.rows[week].data[day].entries.push(entry);
}
else {
// do nothing
}
}
)
);
// today
{
const distance_seconds : int = (
now_pit
-
from_pit
);
const distance_days : int = (distance_seconds / (60 * 60 * 24));
const week : int = Math.floor(Math.floor(distance_days) / 7);
const day : int = (Math.floor(distance_days) % 7);
if ((week >= 0) && (week < result.rows.length)) {
result.rows[week].data[day].today = true;
}
else {
// do nothing
}
}
}
return Promise.resolve(result);
}
/**
*/
export async function calendar_view_table_html(
options : {
calendar_ids ?: (
null
|
Array<_zeitbild.frontend_web.type.calendar_id>
);
from ?: {
year : int;
week : int;
};
to ?: {
year : int;
week : int;
};
timezone_shift ?: int;
action_select ?: (
(
calendar_id : _zeitbild.frontend_web.type.calendar_id,
event_id : _zeitbild.frontend_web.type.local_resource_event_id
)
=>
void
)
} = {}
) : Promise<string>
{
const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now();
options = Object.assign(
{
"calendar_ids": null,
"from": lib_plankton.call.convey(
now_pit,
[
(x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, -1),
lib_plankton.pit.to_ywd,
x => ({"year": x.year, "week": x.week}),
]
),
"to": lib_plankton.call.convey(
now_pit,
[
(x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, +4),
lib_plankton.pit.to_ywd,
x => ({"year": x.year, "week": x.week}),
]
),
"timezone_shift": 0,
},
options
);
const stuff : {
sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>;
rows : Array<
{
week : int;
data : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
>;
}
>;
} = await calendar_view_table_data(
options.calendar_ids,
options.from,
options.to,
options.timezone_shift
);
const sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
color : lib_plankton.color.type_color;
}
> = lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
(x => x.toFixed(0)),
{
"pairs": (
lib_plankton.map.dump(
stuff.sources
)
.map(
(pair) => ({
"key": pair.key,
"value": {
"name": pair.value.name,
"access_level": pair.value.access_level,
"color": lib_plankton.color.give_generic(
(pair.key - 1),
{
"saturation": 0.375,
"value": 0.375,
}
),
}
})
)
)
}
)
);
return _zeitbild.frontend_web.helpers.template_coin(
"events",
"tableview",
{
"sources": (
await _zeitbild.frontend_web.helpers.promise_row<string>(
lib_plankton.map.dump(sources)
.map(
({"key": calendar_id, "value": data}) => async () => _zeitbild.frontend_web.helpers.template_coin(
"events",
"tableview-sources-entry",
{
"name": data.name,
// "access_level": data.access_level,
"color": lib_plankton.color.output_hex(data.color),
"rel": calendar_id.toFixed(0),
}
)
)
)
).join(""),
"rows": (
await _zeitbild.frontend_web.helpers.promise_row<string>(
stuff.rows
.map(
(row) => async () => _zeitbild.frontend_web.helpers.template_coin(
"events",
"tableview-row",
{
"week": row.week.toFixed(0).padStart(2, "0"),
"cells": (
await _zeitbild.frontend_web.helpers.promise_row<string>(
row.data
.map(
(cell) => async () => _zeitbild.frontend_web.helpers.template_coin(
"events",
"tableview-cell",
{
"extra_classes": (
[""]
.concat(cell.today ? ["calendar-cell-today"] : [])
.join(" ")
),
"title": lib_plankton.call.convey(
cell.pit,
[
lib_plankton.pit.to_datetime,
(x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": x.date.year.toFixed(0).padStart(4, "0"),
"month": x.date.month.toFixed(0).padStart(2, "0"),
"day": x.date.day.toFixed(0).padStart(2, "0"),
}
),
]
),
"day": lib_plankton.call.convey(
cell.pit,
[
lib_plankton.pit.to_datetime,
(x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin(
"{{day}}",
{
"year": x.date.year.toFixed(0).padStart(4, "0"),
"month": x.date.month.toFixed(0).padStart(2, "0"),
"day": x.date.day.toFixed(0).padStart(2, "0"),
}
),
]
),
"rel": lib_plankton.call.convey(
cell.pit,
[
lib_plankton.pit.to_datetime,
(x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": x.date.year.toFixed(0).padStart(4, "0"),
"month": x.date.month.toFixed(0).padStart(2, "0"),
"day": x.date.day.toFixed(0).padStart(2, "0"),
}
)
]
),
"entries": (
await _zeitbild.frontend_web.helpers.promise_row<string>(
cell.entries
.map(
(entry) => () => _zeitbild.frontend_web.helpers.template_coin(
"events",
"tableview-cell-entry",
{
"color": lib_plankton.color.output_hex(
sources.get(
entry.calendar_id
).color
),
"title": event_generate_tooltip(
sources.get(
entry.calendar_id
).name,
entry.event_object
),
"name": entry.event_object.name,
"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": (() => {
const access_level : _zeitbild.frontend_web.type.enum_access_level = sources.get(entry.calendar_id).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";
}
}) (),
}
),
"additional_classes": lib_plankton.string.coin(
" access_level-{{access_level}}",
{
"access_level": (() => {
const access_level : _zeitbild.frontend_web.type.enum_access_level = sources.get(entry.calendar_id).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";
}
}) (),
}
),
}
)
)
)
).join(""),
}
)
)
)
).join(""),
}
)
)
)
).join(""),
}
);
}
/**
*/
async function calendar_view_list_data(
calendar_ids : Array<_zeitbild.frontend_web.type.calendar_id>,
options : {
from ?: lib_plankton.pit.type_pit;
to ?: lib_plankton.pit.type_pit;
timezone_shift ?: int;
} = {}
) : Promise<
Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>
>
{
const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now();
options = Object.assign(
{
"from": lib_plankton.call.convey(
now_pit,
[
(x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_day(x, -1),
]
),
"to": lib_plankton.call.convey(
now_pit,
[
(x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, +4),
]
),
"timezone_shift": 0,
},
options
);
const entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
> = await _zeitbild.frontend_web.backend.events(
(options.from as lib_plankton.pit.type_pit),
(options.to as lib_plankton.pit.type_pit),
{
"calendar_ids": calendar_ids,
}
);
// TODO: optimize
entries.sort(
(entry_1, entry_2) => (
lib_plankton.pit.from_datetime(entry_1.event_object.begin)
-
lib_plankton.pit.from_datetime(entry_2.event_object.begin)
)
);
return Promise.resolve(entries);
}
/**
*/
export async function calendar_view_list_html(
calendar_ids : Array<_zeitbild.frontend_web.type.calendar_id>,
options : {
from ?: lib_plankton.pit.type_pit;
to ?: lib_plankton.pit.type_pit;
timezone_shift ?: int;
} = {}
) : Promise<string>
{
const stuff : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
> = await calendar_view_list_data(
calendar_ids,
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()
);
}
}

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

@ -112,7 +112,11 @@ namespace _zeitbild.frontend_web.pages
(
(await _zeitbild.frontend_web.backend.calendar_list())
.filter(
(entry) => (["edit","admin"].includes(entry.access_level))
(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) => ({

View file

@ -1,4 +1,4 @@
namespace _zeitbild.frontend_web.pages
namespace _zeitbild.frontend_web.pages.events
{
/**
@ -6,183 +6,117 @@ namespace _zeitbild.frontend_web.pages
lib_plankton.zoo_page.register(
"events",
async (parameters, target_element) => {
const load = async function (year, week, count) {
// controls
{
(target_element.querySelector("#events_control_year > input") as HTMLInputElement).value = year.toFixed(0);
(target_element.querySelector("#events_control_week > input") as HTMLInputElement).value = week.toFixed(0);
(target_element.querySelector("#events_control_count > input") as HTMLInputElement).value = count.toFixed(0);
}
// table
{
target_element.querySelector("#events_table").innerHTML = await _zeitbild.frontend_web.view.calendar_view_table_html(
{
"calendar_ids": null,
// TODO
"from": {
"year": year,
"week": week
},
// TODO
"to": {
"year": year,
"week": (week + count)
},
"timezone_shift": /*conf.timezone_shift*/0,
}
);
// sources
{
target_element.querySelectorAll(".tableview-sources-entry").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
const calendar_id : _zeitbild.frontend_web.type.calendar_id = parseInt(element.getAttribute("rel"));
lib_plankton.zoo_page.set(
{
"name": "calendar_edit",
"parameters": {
"read_only": false, // TODO
"calendar_id": calendar_id,
}
}
);
}
);
}
);
}
// cells
{
target_element.querySelectorAll(".calendar-cell-regular").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
if (! (element === event.target)) {
// do nothing
}
else {
const rel : string = element.getAttribute("rel");
const parts : Array<string> = rel.split("-");
const year : int = parseInt(parts[0]);
const month : int = parseInt(parts[1]);
const day : int = parseInt(parts[2]);
lib_plankton.zoo_page.set(
{
"name": "event_add",
"parameters": {
"calendar_id": null,
"year": year,
"month": month,
"day": day,
}
}
);
}
}
);
}
);
}
// events
{
target_element.querySelectorAll(".calendar-event_entry").forEach(
(element) => {
element.addEventListener(
"click",
() => {
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;
}
}) ();
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;
}
}
}
);
}
);
}
}
};
target_element.innerHTML = "";
target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"events",
"events",
"default",
{
"label_control_year": lib_plankton.translate.get("page.events.controls.year"),
"label_control_week": lib_plankton.translate.get("page.events.controls.week"),
"label_control_count": lib_plankton.translate.get("page.events.controls.count"),
"label_control_apply": lib_plankton.translate.get("page.events.controls.apply"),
}
);
// controls
// sources
{
target_element.querySelector("#events_control_apply").addEventListener(
"click",
(event) => {
event.preventDefault();
const year : int = parseInt((target_element.querySelector("#events_control_year > input") as HTMLInputElement).value);
const week : int = parseInt((target_element.querySelector("#events_control_week > input") as HTMLInputElement).value);
const count : int = parseInt((target_element.querySelector("#events_control_count > input") as HTMLInputElement).value);
load(year, week, count);
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_select": (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;
}
}
},
}
);
await widget_sources.load(target_element.querySelector("#events-pane-left"));
}
// table
// weekview
{
const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now());
let year : int = ywd_now.year;
let week : int = Math.max(0, (ywd_now.week - 1));
let count : int = 5;
load(year, week, count);
const widget_weekview = new _zeitbild.frontend_web.widgets.weekview.class_widget_weekview(
{
"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,
}
}
);
},
"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;
}
}
}
}
);
await widget_weekview.load(target_element.querySelector("#events-pane-right"));
}
return Promise.resolve<void>(undefined);
},

View file

@ -0,0 +1,6 @@
<div id="events">
<div id="events-pane-left">
</div>
<div id="events-pane-right">
</div>
</div>

View file

@ -1,19 +0,0 @@
<div id="events">
<div id="events_controls">
<label id="events_control_year">
<span>{{label_control_year}}</span>
<input type="number"/>
</label>
<label id="events_control_week">
<span>{{label_control_week}}</span>
<input type="number"/>
</label>
<label id="events_control_count">
<span>{{label_control_count}}</span>
<input type="number"/>
</label>
<input type="submit" id="events_control_apply" value="{{label_control_apply}}"/>
</div>
<div id="events_table">
</div>
</div>

View file

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

73
source/style/common.css Normal file
View file

@ -0,0 +1,73 @@
.tableview-sources-entry:not(.tableview-sources-entry-active)
{
filter: saturate(0);
}
.calendar-cell
{
border: 1px solid hsl(0,0%,37.5%);
padding: 8px;
vertical-align: top;
}
.calendar-cell-day
{
width: 13.5%;
}
.calendar-cell-week
{
width: 5.5%;
}
.calendar-cell-regular
{
width: 13.5%;
height: 120px;
cursor: copy;
}
.calendar-cell-today
{
outline: 2px solid hsl(0, 0%, 100%);
}
.calendar-day
{
font-size: 0.75em;
cursor: help;
}
.calendar-events
{
margin: 0; padding: 0;
list-style-type: none;
}
.calendar-event_entry
{
margin: 4px;
padding: 4px;
border-radius: 2px;
font-size: 0.75em;
color: #FFF;
font-weight: bold;
}
.calendar-event_entry.access_level-none
,
.calendar-event_entry.access_level-view
{
/*
cursor: default;
*/
cursor: pointer;
}
.calendar-event_entry.access_level-edit
,
.calendar-event_entry.access_level-admin
{
cursor: pointer;
}

View file

@ -1,10 +1,12 @@
html {
html
{
background-color: hsl(0, 0%, 12.5%);
color: hsl(0, 0%, 100%);
font-family: sans-serif;
}
header {
header
{
background-color: hsl(0, 0%, 25%);
/*
border-bottom: 2px solid #888;
@ -13,25 +15,29 @@ header {
margin-bottom: 16px;
}
nav > ul {
nav > ul
{
list-style-type: none;
margin: 0;
padding: 0;
}
nav > ul > li {
nav > ul > li
{
display: inline-block;
margin: 8px;
padding: 8px;
}
a {
a
{
padding: 8px;
text-decoration: none;
color: hsl(0, 0%, 87.5%);
}
a:hover {
a:hover
{
color: hsl(0, 0%, 100%);
border-bottom: 2px solid hsl(0, 0%, 100%);
transition: 1s ease color;
@ -57,159 +63,3 @@ input,select,button
margin: 4px;
border-radius: 2px;
}
.calendar {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.calendar-pane-left {
flex-basis: 12.5%;
}
.calendar-pane-right {
flex-basis: 87.5%;
}
.tableview-sources {
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 {
width: 100%;
border-collapse: collapse;
}
.calendar-cell {
border: 1px solid hsl(0,0%,37.5%);
padding: 8px;
vertical-align: top;
}
.calendar-cell-day {
width: 13.5%;
}
.calendar-cell-week {
width: 5.5%;
}
.calendar-cell-regular {
width: 13.5%;
height: 120px;
cursor: copy;
}
.calendar-cell-today {
outline: 2px solid hsl(0, 0%, 100%);
}
.calendar-day {
font-size: 0.75em;
cursor: help;
}
.calendar-events {
margin: 0; padding: 0;
list-style-type: none;
}
.calendar-event_entry {
margin: 4px;
padding: 4px;
border-radius: 2px;
font-size: 0.75em;
color: #FFF;
font-weight: bold;
}
.calendar-event_entry.access_level-none
,
.calendar-event_entry.access_level-view
{
/*
cursor: default;
*/
cursor: pointer;
}
.calendar-event_entry.access_level-edit
,
.calendar-event_entry.access_level-admin
{
cursor: pointer;
}
.plankton_input_group_field {
margin-bottom: 8px;
}
.plankton_input_group_field_label {
display: block;
font-weight: bold;
font-size: 0.8em;
}
.plankton_input_soft_container > * {
display: inline-block;
}
.plankton_input_soft_setter {
margin-right: 8px;
}
.plankton_input_soft_inactive {
display: none !important;
}
.plankton_input_group {
margin-left: 24px;
}
#events_controls {
margin-bottom: 12px;
text-align: center;
}
#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;
}
#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
,
#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;
}

43
source/style/pages.css Normal file
View file

@ -0,0 +1,43 @@
#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;
}
#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
,
#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;
}
#events
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
#events-pane-left
{
flex-basis: 12.5%;
}
#events-pane-right
{
flex-basis: 87.5%;
}

25
source/style/plankton.css Normal file
View file

@ -0,0 +1,25 @@
.plankton_input_group_field {
margin-bottom: 8px;
}
.plankton_input_group_field_label {
display: block;
font-weight: bold;
font-size: 0.8em;
}
.plankton_input_soft_container > * {
display: inline-block;
}
.plankton_input_soft_setter {
margin-right: 8px;
}
.plankton_input_soft_inactive {
display: none !important;
}
.plankton_input_group {
margin-left: 24px;
}

26
source/style/widgets.css Normal file
View file

@ -0,0 +1,26 @@
.sources
{
margin: 0;
padding: 0;
list-style-type: none;
font-size: 0.75em;
}
.sources-entry
{
margin: 8px;
padding: 4px;
cursor: pointer;
}
.weekview-controls
{
margin-bottom: 12px;
text-align: center;
}
.weekview-table table
{
width: 100%;
border-collapse: collapse;
}

View file

@ -0,0 +1,123 @@
namespace _zeitbild.frontend_web.widgets.sources
{
/**
*/
type type_entry = {
id : _zeitbild.frontend_web.type.calendar_id;
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
};
/**
*/
export class class_widget_sources extends _zeitbild.class_widget
{
/**
*/
private keys : Array<string>;
/**
*/
private data : Record<string, type_entry>;
/**
*/
private action_select : ((entry : type_entry) => void);
/**
*/
public constructor(
entries : Array<type_entry>,
options : {
action_select ?: ((entry : type_entry) => void);
} = {}
)
{
options = Object.assign(
{
"action_select": (calendar_id) => {},
},
options
);
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_select = options.action_select;
}
/**
* [implementation]
*/
public async load(
target_element : Element
) : Promise<void>
{
target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"widget-sources",
"main",
{
"entries": (
(
await _zeitbild.frontend_web.helpers.promise_row<string>(
this.keys
.map(
(key) => () => {
const entry : type_entry = this.data[key];
return _zeitbild.frontend_web.helpers.template_coin(
"widget-sources",
"entry",
{
"name": entry.name,
// "access_level": entry.access_level, // TODO
"color": lib_plankton.color.output_hex(
lib_plankton.color.give_generic(
(entry.id - 1),
{
"saturation": 0.375,
"value": 0.375,
}
),
),
"rel": key,
}
);
}
)
)
)
.join("")
),
}
);
target_element.querySelectorAll(".sources-entry").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
const key : string = element.getAttribute("rel");
const entry : type_entry = this.data[key];
this.action_select(entry);
}
);
}
);
return Promise.resolve<void>(undefined);
}
}
}

View file

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

View file

@ -0,0 +1,3 @@
<ul class="sources">
{{entries}}
</ul>

View file

@ -0,0 +1,875 @@
namespace _zeitbild.frontend_web.widgets.weekview
{
/**
*/
export class class_widget_weekview extends _zeitbild.class_widget
{
/**
*/
private container : (null | Element);
/**
*/
private action_select_day : (
(
date : lib_plankton.pit.type_date
)
=>
void
);
/**
*/
private action_select_event : (
(
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
);
/**
*/
public constructor(
options : {
action_select_day ?: (
(
date : lib_plankton.pit.type_date
)
=>
void
);
action_select_event ?: (
(
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
);
} = {}
)
{
options = Object.assign(
{
"action_select_day": (date) => {},
"action_select_event": (calendar_id, access_level, event_id) => {},
},
options
);
super();
this.container = null;
this.action_select_day = options.action_select_day;
this.action_select_event = options.action_select_event;
}
/**
*/
private static event_generate_tooltip(
calendar_name : string,
event_object : _zeitbild.frontend_web.type.event_object
) : string
{
return (
lib_plankton.string.coin(
"[{{calendar_name}}] {{event_name}}\n",
{
"calendar_name": calendar_name,
"event_name": event_object.name,
}
)
+
"--\n"
+
(
(event_object.begin.time !== null)
?
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": lib_plankton.translate.get("event.begin"),
"value": lib_plankton.string.coin(
"{{hour}}:{{minute}}",
{
"hour": event_object.begin.time.hour.toFixed(0).padStart(2, "0"),
"minute": event_object.begin.time.minute.toFixed(0).padStart(2, "0"),
}
), // TODO: outsource
}
)
:
""
)
+
(
(event_object.end !== null)
?
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": lib_plankton.translate.get("event.end"),
"value": (
[
(
(
(event_object.end.date.year !== event_object.begin.date.year)
||
(event_object.end.date.month !== event_object.begin.date.month)
||
(event_object.end.date.day !== event_object.begin.date.day)
)
?
lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": event_object.end.date.year.toFixed(0).padStart(4, "0"),
"month": event_object.end.date.month.toFixed(0).padStart(2, "0"),
"day": event_object.end.date.day.toFixed(0).padStart(2, "0"),
}
)
:
null
),
(
(event_object.end.time !== null)
?
lib_plankton.string.coin(
"{{hour}}:{{minute}}",
{
"hour": event_object.end.time.hour.toFixed(0).padStart(2, "0"),
"minute": event_object.end.time.minute.toFixed(0).padStart(2, "0"),
}
)
:
null
),
]
.filter(x => (x !== null))
.join(",")
),
}
)
:
""
)
+
(
(event_object.location !== null)
?
(
lib_plankton.string.coin(
"{{label}}: {{value}}\n",
{
"label": lib_plankton.translate.get("event.location"),
"value": event_object.location,
}
)
)
:
""
)
/*
+
(
(event_object.description !== null)
?
(
"--\n"
+
lib_plankton.string.coin(
"{{description}}\n",
{
"description": event_object.description,
}
)
)
:
""
)
*/
);
}
/**
* @todo kein "while"
*/
private static async calendar_view_table_data(
calendar_ids : (
null
|
Array<_zeitbild.frontend_web.type.calendar_id>
),
from : {
year : int;
week : int;
},
to : {
year : int;
week : int;
},
timezone_shift : int
) : Promise<
{
sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>;
rows : Array<
{
week : int;
data : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
>;
}
>
}
>
{
const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now();
const from_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd(
{
"year": (from as {year : int; week : int}).year,
"week": (from as {year : int; week : int}).week,
"day": 1,
},
{
"timezone_shift": (timezone_shift as int),
}
);
const to_pit : lib_plankton.pit.type_pit = lib_plankton.pit.from_ywd(
{
"year": (to as {year : int; week : int}).year,
"week": (to as {year : int; week : int}).week,
"day": 1,
},
{
"timezone_shift": (timezone_shift as int),
}
);
// prepare
const entries : 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;
}
> = await _zeitbild.frontend_web.backend.events(
from_pit,
to_pit,
{
"calendar_ids": calendar_ids,
}
);
let result : {
sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>;
rows : Array<
{
week : int;
data : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
>;
}
>;
} = {
"sources": lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
x => x.toFixed(0),
{
"pairs": (
entries
.map(
(entry) => (
{
"key": entry.calendar_id,
"value": {
"name": entry.calendar_name,
"access_level": entry.access_level,
}
}
)
)
)
}
)
),
"rows": [],
};
let row : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
> = [];
let day : int = 0;
while (true) {
const pit_current : lib_plankton.pit.type_pit = lib_plankton.pit.shift_day(
from_pit,
day
);
if (
lib_plankton.pit.is_before(
pit_current,
to_pit
)
) {
day += 1;
row.push(
{
"pit": pit_current,
"entries": [],
"today": false, // TODO
}
);
if (day % 7 === 0) {
result.rows.push(
{
"week": (
(from as {year : int; week : int}).week
+
Math.floor(day / 7)
-
1 // TODO
),
"data": row
}
);
row = [];
}
else {
// do nothing
}
}
else {
break;
}
}
// fill
{
// events
(
entries
.forEach(
(entry) => {
const distance_seconds : int = (
lib_plankton.pit.from_datetime(entry.event_object.begin)
-
from_pit
);
const distance_days : int = (distance_seconds / (60 * 60 * 24));
const week : int = Math.floor(Math.floor(distance_days) / 7);
const day : int = (Math.floor(distance_days) % 7);
if ((week >= 0) && (week < result.rows.length)) {
result.rows[week].data[day].entries.push(entry);
}
else {
// do nothing
}
}
)
);
// today
{
const distance_seconds : int = (
now_pit
-
from_pit
);
const distance_days : int = (distance_seconds / (60 * 60 * 24));
const week : int = Math.floor(Math.floor(distance_days) / 7);
const day : int = (Math.floor(distance_days) % 7);
if ((week >= 0) && (week < result.rows.length)) {
result.rows[week].data[day].today = true;
}
else {
// do nothing
}
}
}
return Promise.resolve(result);
}
/**
*/
private async table_rows(
options : {
calendar_ids ?: (
null
|
Array<_zeitbild.frontend_web.type.calendar_id>
);
from ?: {
year : int;
week : int;
};
to ?: {
year : int;
week : int;
};
timezone_shift ?: int;
action_select ?: (
(
calendar_id : _zeitbild.frontend_web.type.calendar_id,
event_id : _zeitbild.frontend_web.type.local_resource_event_id
)
=>
void
)
} = {}
) : Promise<string>
{
const now_pit : lib_plankton.pit.type_pit = lib_plankton.pit.now();
options = Object.assign(
{
"calendar_ids": null,
"from": lib_plankton.call.convey(
now_pit,
[
(x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, -1),
lib_plankton.pit.to_ywd,
x => ({"year": x.year, "week": x.week}),
]
),
"to": lib_plankton.call.convey(
now_pit,
[
(x : lib_plankton.pit.type_pit) => lib_plankton.pit.shift_week(x, +4),
lib_plankton.pit.to_ywd,
x => ({"year": x.year, "week": x.week}),
]
),
"timezone_shift": 0,
},
options
);
const stuff : {
sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
}
>;
rows : Array<
{
week : int;
data : Array<
{
pit : lib_plankton.pit.type_pit;
entries : Array<
{
calendar_id : _zeitbild.frontend_web.type.calendar_id;
event_id : (null | _zeitbild.frontend_web.type.local_resource_event_id);
event_object : _zeitbild.frontend_web.type.event_object;
}
>;
today : boolean;
}
>;
}
>;
} = await class_widget_weekview.calendar_view_table_data(
options.calendar_ids,
options.from,
options.to,
options.timezone_shift
);
const sources : lib_plankton.map.type_map<
_zeitbild.frontend_web.type.calendar_id,
{
name : string;
access_level : _zeitbild.frontend_web.type.enum_access_level;
color : lib_plankton.color.type_color;
}
> = lib_plankton.map.hashmap.implementation_map(
lib_plankton.map.hashmap.make(
(x => x.toFixed(0)),
{
"pairs": (
lib_plankton.map.dump(
stuff.sources
)
.map(
(pair) => ({
"key": pair.key,
"value": {
"name": pair.value.name,
"access_level": pair.value.access_level,
"color": lib_plankton.color.give_generic(
(pair.key - 1),
{
"saturation": 0.375,
"value": 0.375,
}
),
}
})
)
)
}
)
);
return (
await _zeitbild.frontend_web.helpers.promise_row<string>(
stuff.rows
.map(
(row) => async () => _zeitbild.frontend_web.helpers.template_coin(
"widget-weekview",
"tableview-row",
{
"week": row.week.toFixed(0).padStart(2, "0"),
"cells": (
await _zeitbild.frontend_web.helpers.promise_row<string>(
row.data
.map(
(cell) => async () => _zeitbild.frontend_web.helpers.template_coin(
"widget-weekview",
"tableview-cell",
{
"extra_classes": (
[""]
.concat(cell.today ? ["calendar-cell-today"] : [])
.join(" ")
),
"title": lib_plankton.call.convey(
cell.pit,
[
lib_plankton.pit.to_datetime,
(x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": x.date.year.toFixed(0).padStart(4, "0"),
"month": x.date.month.toFixed(0).padStart(2, "0"),
"day": x.date.day.toFixed(0).padStart(2, "0"),
}
),
]
),
"day": lib_plankton.call.convey(
cell.pit,
[
lib_plankton.pit.to_datetime,
(x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin(
"{{day}}",
{
"year": x.date.year.toFixed(0).padStart(4, "0"),
"month": x.date.month.toFixed(0).padStart(2, "0"),
"day": x.date.day.toFixed(0).padStart(2, "0"),
}
),
]
),
"rel": lib_plankton.call.convey(
cell.pit,
[
lib_plankton.pit.to_datetime,
(x : lib_plankton.pit.type_datetime) => lib_plankton.string.coin(
"{{year}}-{{month}}-{{day}}",
{
"year": x.date.year.toFixed(0).padStart(4, "0"),
"month": x.date.month.toFixed(0).padStart(2, "0"),
"day": x.date.day.toFixed(0).padStart(2, "0"),
}
)
]
),
"entries": (
await _zeitbild.frontend_web.helpers.promise_row<string>(
cell.entries
.map(
(entry) => () => _zeitbild.frontend_web.helpers.template_coin(
"widget-weekview",
"tableview-cell-entry",
{
"color": lib_plankton.color.output_hex(
sources.get(
entry.calendar_id
).color
),
"title": class_widget_weekview.event_generate_tooltip(
sources.get(
entry.calendar_id
).name,
entry.event_object
),
"name": entry.event_object.name,
"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": (() => {
const access_level : _zeitbild.frontend_web.type.enum_access_level = sources.get(entry.calendar_id).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";
}
}) (),
}
),
"additional_classes": lib_plankton.string.coin(
" access_level-{{access_level}}",
{
"access_level": (() => {
const access_level : _zeitbild.frontend_web.type.enum_access_level = sources.get(entry.calendar_id).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";
}
}) (),
}
),
}
)
)
)
).join(""),
}
)
)
)
).join(""),
}
)
)
)
).join("");
}
/**
*/
private async update(
year : int,
week : int,
count : int,
options : {
update_controls ?: boolean;
} = {}
) : Promise<void>
{
options = Object.assign(
{
"update_controls": true,
},
options
);
const context : Element = this.container;
// controls
{
if (! options.update_controls) {
// do nothing
}
else {
(context.querySelector(".weekview-control-year > input") as HTMLInputElement).value = year.toFixed(0);
(context.querySelector(".weekview-control-week > input") as HTMLInputElement).value = week.toFixed(0);
(context.querySelector(".weekview-control-count > input") as HTMLInputElement).value = count.toFixed(0);
}
}
// table
{
context.querySelector(".weekview-table tbody").innerHTML = await this.table_rows(
{
"calendar_ids": null,
// TODO
"from": {
"year": year,
"week": week
},
// TODO
"to": {
"year": year,
"week": (week + count)
},
"timezone_shift": /*conf.timezone_shift*/0,
}
);
// cells
{
context.querySelectorAll(".calendar-cell-regular").forEach(
(element) => {
element.addEventListener(
"click",
(event) => {
if (! (element === event.target)) {
// do nothing
}
else {
const rel : string = element.getAttribute("rel");
const parts : Array<string> = rel.split("-");
const date : lib_plankton.pit.type_date = {
"year": parseInt(parts[0]),
"month": parseInt(parts[1]),
"day": parseInt(parts[2]),
};
this.action_select_day(date);
}
}
);
}
);
}
// events
{
context.querySelectorAll(".calendar-event_entry").forEach(
(element) => {
element.addEventListener(
"click",
() => {
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
);
}
);
}
);
}
}
return Promise.resolve<void>(undefined);
}
/**
* [implementation]
*/
public async load(
target_element : Element
) : Promise<void>
{
target_element.innerHTML = await _zeitbild.frontend_web.helpers.template_coin(
"widget-weekview",
"main",
{
"label_control_year": lib_plankton.translate.get("widget.weekview.controls.year"),
"label_control_week": lib_plankton.translate.get("widget.weekview.controls.week"),
"label_control_count": lib_plankton.translate.get("widget.weekview.controls.count"),
"label_control_apply": lib_plankton.translate.get("widget.weekview.controls.apply"),
}
);
this.container = target_element.querySelector(".weekview");
// controls
{
target_element.querySelector(".weekview-control-apply").addEventListener(
"select",
(event) => {
event.preventDefault();
const year : int = parseInt((target_element.querySelector(".weekview-control-year > input") as HTMLInputElement).value);
const week : int = parseInt((target_element.querySelector(".weekview-control-week > input") as HTMLInputElement).value);
const count : int = parseInt((target_element.querySelector(".weekview-control-count > input") as HTMLInputElement).value);
this.update(
year,
week,
count,
{
"update_controls": false,
}
);
}
);
}
// table
{
const ywd_now : lib_plankton.pit.type_ywd = lib_plankton.pit.to_ywd(lib_plankton.pit.now());
let year : int = ywd_now.year;
let week : int = Math.max(0, (ywd_now.week - 1));
let count : int = 5;
await this.update(
year,
week,
count,
{
"update_controls": true,
}
);
}
return Promise.resolve<void>(undefined);
}
}
}

View file

@ -1,10 +1,20 @@
<div class="calendar">
<div class="calendar-pane calendar-pane-left">
<ul class="tableview-sources">
{{sources}}
</ul>
<div class="weekview">
<div class="weekview-controls">
<label class="weekview-control-year">
<span>{{label_control_year}}</span>
<input type="number"/>
</label>
<label class="weekview-control-week">
<span>{{label_control_week}}</span>
<input type="number"/>
</label>
<label class="weekview-control-count">
<span>{{label_control_count}}</span>
<input type="number"/>
</label>
<input type="submit" class="weekview-control-apply" value="{{label_control_apply}}"/>
</div>
<div class="calendar-pane calendar-pane-right">
<div class="weekview-table">
<table>
<thead>
<tr>
@ -19,7 +29,6 @@
</tr>
</thead>
<tbody>
{{rows}}
</tbody>
</table>
</div>

View file

@ -32,6 +32,8 @@ ${dir_build}/index.html: \
.PHONY: templates
templates: \
templates-widgets-sources \
templates-widgets-weekview \
templates-pages-calendar_add \
templates-pages-calendar_edit \
templates-pages-event_add \
@ -39,6 +41,20 @@ templates: \
templates-pages-events \
templates-pages-login
.PHONY: templates-widgets-sources
templates-widgets-sources: \
$(wildcard ${dir_source}/widgets/sources/templates/*)
@ ${cmd_log} "templates:widgets:sources …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-sources
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/sources/templates/* ${dir_build}/templates/widget-sources/
.PHONY: templates-widgets-weekview
templates-widgets-weekview: \
$(wildcard ${dir_source}/widgets/weekview/templates/*)
@ ${cmd_log} "templates:widgets:weekview …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-weekview
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/weekview/templates/* ${dir_build}/templates/widget-weekview/
.PHONY: templates-pages-calendar_add
templates-pages-calendar_add: \
$(wildcard ${dir_source}/pages/calendar_add/templates/*)
@ -94,10 +110,12 @@ logic: ${dir_build}/logic.js
${dir_temp}/logic-unlinked.js: \
${dir_lib}/plankton/plankton.d.ts \
${dir_source}/logic/helpers.ts \
${dir_source}/logic/widget.ts \
${dir_source}/logic/conf.ts \
${dir_source}/logic/types.ts \
${dir_source}/logic/backend.ts \
${dir_source}/logic/view.ts \
${dir_source}/widgets/sources/logic.ts \
${dir_source}/widgets/weekview/logic.ts \
${dir_source}/pages/login/logic.ts \
${dir_source}/pages/logout/logic.ts \
${dir_source}/pages/oidc_finish/logic.ts \