[add] widget:mode_switcher [mod] page:overview

This commit is contained in:
fenris 2025-10-02 17:00:17 +02:00
parent a49a29da06
commit 4f830b0790
20 changed files with 466 additions and 116 deletions

63
source/base/functions.ts Normal file
View file

@ -0,0 +1,63 @@
namespace _dali
{
/**
*/
export function view_mode_encode(
mode : _dali.type.enum_view_mode
) : string
{
switch (mode)
{
case _dali.type.enum_view_mode.week: {return "week"; break;}
case _dali.type.enum_view_mode.list: {return "list"; break;}
default: {throw (new Error("invalid mode"));}
}
}
/**
*/
export function view_mode_decode(
view_mode_encoded : string
) : _dali.type.enum_view_mode
{
const map : Record<string, _dali.type.enum_view_mode> = {
"week": _dali.type.enum_view_mode.week,
"list": _dali.type.enum_view_mode.list,
};
if (! (view_mode_encoded in map))
{
throw (new Error("invalid mode: " + view_mode_encoded));
}
else
{
return map[view_mode_encoded];
}
}
/**
*/
export function view_mode_determine(
mode_descriptor : string
) : _dali.type.enum_view_mode
{
if (mode_descriptor === "auto")
{
return (
(window.innerWidth >= 800)
?
_dali.type.enum_view_mode.week
:
_dali.type.enum_view_mode.list
);
}
else
{
return view_mode_decode(mode_descriptor);
}
}
}

View file

@ -111,4 +111,13 @@ namespace _dali.type
resource : resource_object;
};
/**
*/
export enum enum_view_mode
{
week,
list,
}
}

View file

@ -1,4 +1,4 @@
namespace _zeitbild
namespace _dali
{
/**

View file

@ -4,6 +4,37 @@
namespace _dali
{
/**
*/
function nav_groups(
logged_in : boolean
) : Array<string>
{
return (
logged_in
?
["logged_in"]
:
["logged_out"]
);
}
/**
* @todo reload page when switching to "logged_out"
*/
async function update(
) : Promise<void>
{
lib_plankton.log.debug(
"dali.update"
);
const logged_in : boolean = await _dali.backend.is_logged_in();
lib_plankton.zoo_page.nav_set_groups(nav_groups(logged_in));
// lib_plankton.zoo_page.reload();
}
/**
*/
export async function main(
@ -39,34 +70,50 @@ namespace _dali
"fallback": {
"name": "overview",
"parameters": {}
}
},
"nav_entries": [
{
"location": {"name": "login", "parameters": {}},
"label": lib_plankton.translate.get("page.login.title"),
"groups": ["logged_out"],
},
{
"location": {"name": "overview", "parameters": {}},
"label": lib_plankton.translate.get("page.overview.title"),
"groups": ["logged_out", "logged_in"],
},
{
"location": {"name": "calendar_add", "parameters": {}},
"label": lib_plankton.translate.get("page.calendar_add.title"),
"groups": ["logged_in"],
},
{
"location": {"name": "caldav", "parameters": {}},
"label": lib_plankton.translate.get("page.caldav.title"),
"groups": ["logged_in"],
},
/*
{
"location": {"name": "event_add", "parameters": {}},
"label": lib_plankton.translate.get("page.event_add.title"),
"groups": ["logged_in"],
}
*/
{
"location": {"name": "logout", "parameters": {}},
"label": lib_plankton.translate.get("page.logout.title"),
"groups": ["logged_in"],
},
],
"nav_initial_groups": [],
}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "login", "parameters": {}},
{"label": lib_plankton.translate.get("page.login.title")}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "overview", "parameters": {}},
{"label": lib_plankton.translate.get("page.overview.title")}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "calendar_add", "parameters": {}},
{"label": lib_plankton.translate.get("page.calendar_add.title")}
);
lib_plankton.zoo_page.add_nav_entry(
{"name": "caldav", "parameters": {}},
{"label": lib_plankton.translate.get("page.caldav.title")}
);
/*
lib_plankton.zoo_page.add_nav_entry(
{"name": "event_add", "parameters": {}},
{"label": lib_plankton.translate.get("page.event_add.title")}
);
*/
lib_plankton.zoo_page.add_nav_entry(
{"name": "logout", "parameters": {}},
{"label": lib_plankton.translate.get("page.logout.title")}
await update();
lib_plankton.call.loop(
() => {
update();
},
_dali.conf.get().misc.update_interval
);
// exec

View file

@ -12,8 +12,10 @@ namespace _dali.pages
"oidc_redirect_uri_template": _dali.conf.get()["misc"]["oidc_redirect_uri_template"],
}
);
switch (preparation.kind) {
case "internal": {
switch (preparation.kind)
{
case "internal":
{
target_element.innerHTML = await _dali.helpers.template_coin(
"login",
"default",
@ -51,11 +53,13 @@ namespace _dali.pages
"target": "submit",
"procedure": async (get_value, get_representation) => {
const value : any = await get_value();
try {
try
{
await _dali.backend.session_begin(
value.name,
value.password
);
lib_plankton.zoo_page.nav_set_groups(["logged_in"]);
lib_plankton.zoo_page.set(
{
"name": "overview",
@ -63,7 +67,8 @@ namespace _dali.pages
}
);
}
catch (error) {
catch (error)
{
lib_plankton.zoo_page.set(
{
"name": "login",
@ -86,7 +91,8 @@ namespace _dali.pages
);
break;
}
case "oidc": {
case "oidc":
{
let element_a : HTMLElement = document.createElement("a");;
element_a.textContent = lib_plankton.string.coin(
lib_plankton.translate.get("page.login.oidc.via"),
@ -99,7 +105,8 @@ namespace _dali.pages
target_element.appendChild(element_a);
break;
}
default: {
default:
{
break;
}
}

View file

@ -7,8 +7,21 @@ namespace _dali.pages
"logout",
async (parameters, target_element) => {
target_element.innerHTML = "";
await _dali.backend.session_end(
);
try
{
await _dali.backend.session_end(
);
}
catch (error)
{
lib_plankton.log.notice(
"dali.logout_failed",
{
"details": String(error),
}
);
}
lib_plankton.zoo_page.nav_set_groups(["logged_out"]);
lib_plankton.zoo_page.set(
{
"name": "overview",

View file

@ -7,21 +7,15 @@ namespace _dali.pages.overview
"overview",
async (parameters, target_element) => {
// params
const compact : boolean = (
(
parameters["compact"]
??
(
(window.innerWidth >= 800)
?
"no"
:
"yes"
)
)
===
"yes"
);
const view_mode : _dali.type.enum_view_mode = view_mode_determine(parameters["mode"] ?? "auto");
/**
* @todo ordentlich machen (nicht nur week und list)
*/
const set_view_mode = (view_mode) => {
const compact : boolean = (view_mode !== _dali.type.enum_view_mode.week);
target_element.querySelector("#overview").classList.toggle("overview-compact", compact);
};
// exec
target_element.innerHTML = await _dali.helpers.template_coin(
@ -30,15 +24,49 @@ namespace _dali.pages.overview
{
}
);
target_element.querySelector("#overview").classList.toggle("overview-compact", compact);
// mode switcher
{
const widget_mode_switcher : _dali.class_widget = new _dali.widgets.mode_switcher.class_widget_mode_switcher(
[
{
"mode": _dali.type.enum_view_mode.week,
"label": lib_plankton.translate.get("page.overview.mode.week"),
},
{
"mode": _dali.type.enum_view_mode.list,
"label": lib_plankton.translate.get("page.overview.mode.list"),
},
],
{
"initial_selection": view_mode,
"action_change": (view_mode) => {
lib_plankton.zoo_page.set(
{
"name": "overview",
"parameters": {
"mode": _dali.view_mode_encode(view_mode),
}
}
);
set_view_mode(view_mode);
}
}
);
set_view_mode(view_mode);
await widget_mode_switcher.load(target_element.querySelector("#overview-mode"));
}
let widget_weekview : _dali.widgets.weekview.class_widget_weekview;
let widget_listview : _dali.widgets.listview.class_widget_listview;
// hint
{
if (! await _dali.backend.is_logged_in()) {
target_element.querySelector("#overview-head").textContent = lib_plankton.translate.get("page.overview.login_hint");
if (! await _dali.backend.is_logged_in())
{
target_element.querySelector("#overview-hint").textContent = lib_plankton.translate.get("page.overview.login_hint");
}
else {
else
{
// do nothing
}
}
@ -56,13 +84,16 @@ namespace _dali.pages.overview
data,
{
"action_open": (entry) => {
switch (entry.access_level) {
case _dali.type.enum_access_level.none: {
switch (entry.access_level)
{
case _dali.type.enum_access_level.none:
{
throw (new Error("this event should not be visible"));
break;
}
case _dali.type.enum_access_level.edit:
case _dali.type.enum_access_level.view: {
case _dali.type.enum_access_level.view:
{
lib_plankton.zoo_page.set(
{
"name": "calendar_edit",
@ -74,7 +105,8 @@ namespace _dali.pages.overview
);
break;
}
case _dali.type.enum_access_level.admin: {
case _dali.type.enum_access_level.admin:
{
lib_plankton.zoo_page.set(
{
"name": "calendar_edit",
@ -90,6 +122,7 @@ namespace _dali.pages.overview
},
"action_toggle_visibility": (entry) => {
widget_weekview.toggle_visibility(entry.id);
widget_listview.toggle_visibility(entry.id);
},
}
);
@ -105,12 +138,15 @@ namespace _dali.pages.overview
}
);
const action_select_event = (calendar_id, access_level, event_id) => {
switch (access_level) {
case _dali.type.enum_access_level.none: {
switch (access_level)
{
case _dali.type.enum_access_level.none:
{
throw (new Error("this event should not be visible"));
break;
}
case _dali.type.enum_access_level.view: {
case _dali.type.enum_access_level.view:
{
lib_plankton.zoo_page.set(
{
"name": "event_edit",
@ -124,7 +160,8 @@ namespace _dali.pages.overview
break;
}
case _dali.type.enum_access_level.edit:
case _dali.type.enum_access_level.admin: {
case _dali.type.enum_access_level.admin:
{
lib_plankton.zoo_page.set(
{
"name": "event_edit",

View file

@ -1,5 +1,9 @@
<div id="overview">
<div id="overview-head">
<div id="overview-hint">
</div>
<div id="overview-mode">
</div>
</div>
<div id="overview-body">
<div id="overview-pane-left">

View file

@ -61,24 +61,17 @@ namespace _dali.backend
async function get_session_key(
) : Promise<(null | string)>
{
try {
try
{
return (await _data_chest.read("session_key"));
}
catch (error) {
catch (error)
{
return null;
}
}
/**
*/
export async function is_logged_in(
) : Promise<boolean>
{
return ((await get_session_key()) !== null);
}
/**
*/
async function call(
@ -166,6 +159,23 @@ namespace _dali.backend
return Promise.resolve<any>(output);
}
}
/**
*/
export async function is_logged_in(
) : Promise<boolean>
{
// return ((await get_session_key()) !== null);
const result : {
logged_in : boolean;
} = await call(
lib_plankton.http.enum_method.get,
"/session/status",
null
);
return result.logged_in;
}
/**

View file

@ -57,6 +57,11 @@ namespace _dali.conf
"type": "boolean",
"default": false
},
"update_interval": {
"nullable": false,
"type": "integer",
"default": 60
},
},
"required": [
],
@ -90,7 +95,8 @@ namespace _dali.conf
export function get(
) : any
{
if (_data === null) {
if (_data === null)
{
throw (new Error("conf not loaded yet"));
}
else {

View file

@ -34,6 +34,11 @@ nav > ul > li
padding: 8px;
}
nav > ul > li:not(.active)
{
display: none;
}
nav a
{
padding: 8px;

View file

@ -1,15 +1,21 @@
#overview-head
{
padding-bottom: 12px;
margin-bottom: 12px;
border-bottom: 2px solid;
}
#overview-hint
{
text-align: center;
font-weight: bold;
margin-bottom: 12px;
}
#overview-body
{
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex-wrap: nowrap;
}
#overview-body #overview-pane-left
@ -24,40 +30,12 @@
flex-shrink: 1;
}
@media all and (max-width: 950px)
{
#overview #overview-pane-left
{
flex-basis: 0%;
}
#overview.overview-compact #overview-pane-left {flex-basis: 25%;}
#overview.overview-compact #overview-pane-right {flex-basis: 75%;}
#overview.overview-compact #overview-pane-right-listview {}
#overview.overview-compact #overview-pane-right-weekview {display: none;}
#overview #overview-pane-left > *
{
display: none;
}
#overview #overview-pane-right
{
flex-basis: 100%;
}
#overview #overview-pane-right-listview {}
#overview #overview-pane-right-weekview {display: none;}
}
@media not all and (max-width: 950px)
{
#overview #overview-pane-left
{
flex-basis: 12.5%;
}
#overview #overview-pane-right
{
flex-basis: 87.5%;
}
#overview #overview-pane-right-listview {display: none;}
#overview #overview-pane-right-weekview {}
}
#overview:not(.overview-compact) #overview-pane-left {flex-basis: 12.5%;}
#overview:not(.overview-compact) #overview-pane-right {flex-basis: 87.5%;}
#overview:not(.overview-compact) #overview-pane-right-listview {display: none;}
#overview:not(.overview-compact) #overview-pane-right-weekview {}

View file

@ -26,6 +26,11 @@
cursor: pointer;
}
.listview-entry-hidden
{
display: none;
}
.listview-entry-name
{
font-weight: bold;

View file

@ -27,7 +27,7 @@ namespace _dali.widgets.listview
/**
*/
export class class_widget_listview extends _zeitbild.class_widget
export class class_widget_listview extends _dali.class_widget
{
/**
@ -35,6 +35,11 @@ namespace _dali.widgets.listview
private get_entries : type_get_entries;
/**
*/
private container : (null | Element);
/**
*/
private action_select_event : (
@ -90,11 +95,34 @@ namespace _dali.widgets.listview
);
super();
this.get_entries = get_entries;
this.container = null;
this.action_select_event = options.action_select_event;
this.action_add = options.action_add;
}
/**
*/
public toggle_visibility(
calendar_id : _dali.type.calendar_id
) : void
{
this.container.querySelectorAll(".listview-entry").forEach(
(element) => {
const rel : string = element.getAttribute("rel");
const parts : Array<string> = rel.split("/");
const calendar_id_ : _dali.type.calendar_id = parseInt(parts[0]);
if (! (calendar_id === calendar_id_)) {
// do nothing
}
else {
element.classList.toggle("listview-entry-hidden");
}
}
);
}
/**
* [implementation]
*/
@ -289,6 +317,8 @@ namespace _dali.widgets.listview
);
}
this.container = target_element.querySelector(".listview");
return Promise.resolve<void>(undefined);
}

View file

@ -0,0 +1,119 @@
namespace _dali.widgets.mode_switcher
{
/**
*/
type type_option = {
mode : _dali.type.enum_view_mode;
label : string,
};
/**
*/
export class class_widget_mode_switcher extends _dali.class_widget
{
/**
*/
private options : Array<type_option>;
/**
*/
private initial_selection : (null | _dali.type.enum_view_mode);
/**
*/
private action_change : (null | ((mode : _dali.type.enum_view_mode) => void));
/**
*/
public constructor(
options : Array<type_option>,
{
"initial_selection": initial_selection = null,
"action_change": action_change = null,
}
:
{
initial_selection ?: (null | _dali.type.enum_view_mode),
action_change ?: (null | ((mode : _dali.type.enum_view_mode) => void))
}
=
{
}
)
{
super();
this.options = options;
this.initial_selection = initial_selection;
this.action_change = action_change;
}
/**
* [implementation]
*/
public async load(
target_element : Element
) : Promise<void>
{
target_element.innerHTML = await _dali.helpers.template_coin(
"widget-mode_switcher",
"main",
{
"options": (await lib_plankton.call.promise_condense(
this.options.map(
option => () => _dali.helpers.template_coin(
"widget-mode_switcher",
"option",
{
"rel": _dali.view_mode_encode(option.mode),
"value": _dali.view_mode_encode(option.mode),
"label": option.label,
}
)
)
)).join(""),
}
);
// initial selection
{
if (this.initial_selection !== null)
{
const selector : string = lib_plankton.string.coin(
".widget-mode_switcher-option[rel=\"{{rel}}\"] > input",
{
"rel": _dali.view_mode_encode(this.initial_selection)
}
);
(target_element.querySelector(selector) as HTMLInputElement).checked = true;
}
}
// set listeners
{
if (this.action_change !== null)
{
target_element.querySelectorAll(".widget-mode_switcher-option").forEach(
element => {
const view_mode_encoded : string = element.getAttribute("rel");
const mode : _dali.type.enum_view_mode = _dali.view_mode_decode(view_mode_encoded);
element.querySelector("input").addEventListener(
"change",
(event) => {
this.action_change(mode);
}
);
}
);
}
}
return Promise.resolve<void>(undefined);
}
}
}

View file

@ -0,0 +1,3 @@
<div class="widget-mode_switcher">
{{options}}
</div>

View file

@ -0,0 +1,4 @@
<label class="widget-mode_switcher-option" rel="{{rel}}">
<input type="radio" name="mode" value="{{value}}"/>
<span>{{label}}</span>
</label>

View file

@ -12,7 +12,7 @@ namespace _dali.widgets.sources
/**
*/
export class class_widget_sources extends _zeitbild.class_widget
export class class_widget_sources extends _dali.class_widget
{
/**

View file

@ -27,7 +27,7 @@ namespace _dali.widgets.weekview
/**
*/
export class class_widget_weekview extends _zeitbild.class_widget
export class class_widget_weekview extends _dali.class_widget
{
/**
@ -832,7 +832,7 @@ namespace _dali.widgets.weekview
/**
*/
public toggle_visibility(
calendar_id: _dali.type.calendar_id
calendar_id : _dali.type.calendar_id
) : void
{
this.container.querySelectorAll(".weekview-event_entry").forEach(

View file

@ -35,6 +35,7 @@ templates: \
templates-widgets-sources \
templates-widgets-listview \
templates-widgets-weekview \
templates-widgets-mode_switcher \
templates-pages-caldav \
templates-pages-calendar_add \
templates-pages-calendar_edit \
@ -64,6 +65,13 @@ 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-widgets-mode_switcher
templates-widgets-mode_switcher: \
$(wildcard ${dir_source}/widgets/mode_switcher/templates/*)
@ ${cmd_log} "templates:widgets:mode_switcher …"
@ ${cmd_mkdir} ${dir_build}/templates/widget-mode_switcher
@ ${cmd_cp} -r -u -v ${dir_source}/widgets/mode_switcher/templates/* ${dir_build}/templates/widget-mode_switcher/
.PHONY: templates-pages-caldav
templates-pages-caldav: \
$(wildcard ${dir_source}/pages/caldav/templates/*)
@ -128,11 +136,13 @@ ${dir_temp}/logic-unlinked.js: \
${dir_source}/base/helpers.ts \
${dir_source}/base/widget.ts \
${dir_source}/base/types.ts \
${dir_source}/base/functions.ts \
${dir_source}/resources/conf.ts \
${dir_source}/resources/backend.ts \
${dir_source}/widgets/sources/logic.ts \
${dir_source}/widgets/listview/logic.ts \
${dir_source}/widgets/weekview/logic.ts \
${dir_source}/widgets/mode_switcher/logic.ts \
${dir_source}/pages/login/logic.ts \
${dir_source}/pages/logout/logic.ts \
${dir_source}/pages/caldav/logic.ts \