mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-21 15:33:23 +01:00
Kdots: Implement messages in framework
This commit is contained in:
parent
8390b82b71
commit
4212cbc5b6
@ -5,6 +5,21 @@ body {
|
||||
overflow: clip;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
/** Messages **/
|
||||
/** end messages **/
|
||||
}
|
||||
html .sl-toast-stack,
|
||||
body .sl-toast-stack {
|
||||
top: auto;
|
||||
bottom: 0px;
|
||||
}
|
||||
html .sl-toast-stack sl-alert et2-checkbox,
|
||||
body .sl-toast-stack sl-alert et2-checkbox {
|
||||
padding-top: var(--sl-spacing-large);
|
||||
}
|
||||
html #egw_message,
|
||||
body #egw_message {
|
||||
display: none;
|
||||
}
|
||||
.egw_menu::part(popup) {
|
||||
z-index: var(--sl-z-index-dropdown);
|
||||
|
@ -4,6 +4,23 @@ html, body {
|
||||
overflow: clip;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
/** Messages **/
|
||||
|
||||
.sl-toast-stack {
|
||||
top: auto;
|
||||
bottom: 0px;
|
||||
|
||||
sl-alert et2-checkbox {
|
||||
padding-top: var(--sl-spacing-large);
|
||||
}
|
||||
}
|
||||
|
||||
#egw_message {
|
||||
display: none
|
||||
}
|
||||
|
||||
/** end messages **/
|
||||
}
|
||||
|
||||
.egw_menu::part(popup)
|
||||
|
@ -3,12 +3,13 @@ import {customElement} from "lit/decorators/custom-element.js";
|
||||
import {property} from "lit/decorators/property.js";
|
||||
import {classMap} from "lit/directives/class-map.js";
|
||||
import {repeat} from "lit/directives/repeat.js";
|
||||
import {until} from "lit/directives/until.js";
|
||||
import "@shoelace-style/shoelace/dist/components/split-panel/split-panel.js";
|
||||
import styles from "./EgwFramework.styles";
|
||||
import {egw} from "../../api/js/jsapi/egw_global";
|
||||
import {SlDropdown, SlTab, SlTabGroup} from "@shoelace-style/shoelace";
|
||||
import {SlAlert, SlDropdown, SlTab, SlTabGroup} from "@shoelace-style/shoelace";
|
||||
import {EgwFrameworkApp} from "./EgwFrameworkApp";
|
||||
import {until} from "lit/directives/until.js";
|
||||
import {EgwFrameworkMessage} from "./EgwFrameworkMessage";
|
||||
|
||||
/**
|
||||
* @summary Accessable, webComponent-based EGroupware framework
|
||||
@ -105,6 +106,9 @@ export class EgwFramework extends LitElement
|
||||
// Keep track of open popups
|
||||
private _popups : Window[] = [];
|
||||
|
||||
// Keep track of open messages
|
||||
private _messages : SlAlert[] = [];
|
||||
|
||||
private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");}
|
||||
|
||||
connectedCallback()
|
||||
@ -143,6 +147,18 @@ export class EgwFramework extends LitElement
|
||||
// These need egw fully loaded
|
||||
this.getEgwComplete().then(() =>
|
||||
{
|
||||
// Regisert the "message" plugin
|
||||
this.egw.registerJSONPlugin((type, res, req) =>
|
||||
{
|
||||
//Check whether all needed parameters have been passed and call the alertHandler function
|
||||
if((typeof res.data.message != 'undefined'))
|
||||
{
|
||||
this.message(res.data.message, res.data.type)
|
||||
return true;
|
||||
}
|
||||
throw 'Invalid parameters';
|
||||
}, null, 'message');
|
||||
|
||||
// Quick add
|
||||
this.egw.link_quick_add('topmenu_info_quick_add');
|
||||
|
||||
@ -493,6 +509,67 @@ export class EgwFramework extends LitElement
|
||||
app.setSidebox(sideboxData, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a message, with an optional type
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {"" | "help" | "info" | "error" | "warning" | "success"} type
|
||||
* @param {number} duration The length of time, seconds, the alert will show before closing itself. Success
|
||||
* messages are shown for 5s, other messages require manual closing by the user.
|
||||
* @param {boolean} closable=true Message can be closed by the user
|
||||
* @param {string} _discardID unique string id (appname:id) in order to register
|
||||
* the message as discardable. Discardable messages offer a checkbox to never be shown again.
|
||||
* If no appname given, the id will be prefixed with current app. The discardID will be stored in local storage.
|
||||
* @returns {Promise<SlAlert>} SlAlert element
|
||||
*/
|
||||
public async message(message : string, type : "" | "help" | "info" | "error" | "warning" | "success" = "", duration : null | number = null, closable = true, _discardID : null | string = null)
|
||||
{
|
||||
if(message && !type)
|
||||
{
|
||||
const error_reg_exp = new RegExp('(error|' + egw.lang('error') + ')', 'i');
|
||||
type = message.match(error_reg_exp) ? 'error' : 'success';
|
||||
}
|
||||
|
||||
// Do not add a same message twice if it's still not dismissed
|
||||
const hash = await this.egw.hashString(message);
|
||||
if(typeof this._messages[hash] !== "undefined")
|
||||
{
|
||||
return this._messages[hash];
|
||||
}
|
||||
|
||||
// Already discarded, just stop
|
||||
if(_discardID && EgwFrameworkMessage.isDiscarded(_discardID))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = {
|
||||
type: type,
|
||||
closable: closable,
|
||||
duration: duration * 1000,
|
||||
discard: _discardID,
|
||||
message: message,
|
||||
"data-hash": hash
|
||||
}
|
||||
if(!duration)
|
||||
{
|
||||
delete attributes.duration;
|
||||
}
|
||||
|
||||
const alert = Object.assign(document.createElement("egw-message"), attributes);
|
||||
alert.addEventListener("sl-hide", (e) =>
|
||||
{
|
||||
delete this._messages[e.target["data-hash"] ?? ""];
|
||||
});
|
||||
this._messages[hash] = alert;
|
||||
document.body.append(alert);
|
||||
await alert.updateComplete;
|
||||
|
||||
alert.toast();
|
||||
|
||||
return alert;
|
||||
}
|
||||
|
||||
protected getBaseUrl() {return "";}
|
||||
|
||||
/**
|
||||
|
@ -158,6 +158,7 @@ export default css`
|
||||
|
||||
.egw_fw_app__loading {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
|
||||
sl-spinner {
|
||||
--track-width: 1rem;
|
||||
|
202
kdots/js/EgwFrameworkMessage.ts
Normal file
202
kdots/js/EgwFrameworkMessage.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import {html, LitElement, nothing, TemplateResult} from "lit";
|
||||
import {customElement} from "lit/decorators/custom-element.js";
|
||||
import {property} from "lit/decorators/property.js";
|
||||
import {SlAlert} from "@shoelace-style/shoelace";
|
||||
import {egw} from "../../api/js/jsapi/egw_global";
|
||||
import {Et2Checkbox} from "../../api/js/etemplate/Et2Checkbox/Et2Checkbox";
|
||||
|
||||
/**
|
||||
* @summary System message
|
||||
*
|
||||
* @dependency sl-alert
|
||||
* @dependency sl-icon
|
||||
*
|
||||
* @slot - Content
|
||||
* @slot icon - An icon to show in the message
|
||||
*
|
||||
* @csspart base - Wraps it all.
|
||||
* @csspart icon -
|
||||
*/
|
||||
@customElement('egw-message')
|
||||
export class EgwFrameworkMessage extends LitElement
|
||||
{
|
||||
/**
|
||||
* The type of message
|
||||
* @type { "help" | "info" | "error" | "warning" | "success"}
|
||||
*/
|
||||
@property()
|
||||
type : "help" | "info" | "error" | "warning" | "success" = "success";
|
||||
|
||||
/**
|
||||
* Message
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
@property()
|
||||
message : string = "";
|
||||
/**
|
||||
* Enables a close button that allows the user to dismiss the message
|
||||
* @type {boolean}
|
||||
*/
|
||||
@property()
|
||||
closable = true;
|
||||
|
||||
/**
|
||||
* Length of time, in seconds, before the message closes automatically.
|
||||
* Success messages close in 5s, for other types the default is never close.
|
||||
* @type {number}
|
||||
*/
|
||||
@property()
|
||||
duration : number = null;
|
||||
|
||||
/**
|
||||
* Unique string id (appname:id) in order to register the message as discardable.
|
||||
* Discardable messages offer a checkbox to never be shown again.
|
||||
* If no appname given, the id will be prefixed with current app. The discardID will be stored in local storage.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
@property()
|
||||
discard : string = "";
|
||||
|
||||
// Map types to icons
|
||||
private static ICON_MAP = {
|
||||
help: "question-circle",
|
||||
error: "exclamation-octagon",
|
||||
warning: "exclamation-triangle",
|
||||
success: "check2-circle",
|
||||
info: "info-circle",
|
||||
};
|
||||
|
||||
// Map our message types to shoelace variants
|
||||
private static TYPE_MAP = {info: "primary", error: "danger"};
|
||||
|
||||
private __alert : SlAlert;
|
||||
|
||||
/**
|
||||
* Check if a message has been discarded
|
||||
* @param {string} discardId
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public static isDiscarded(discardId : string) : boolean
|
||||
{
|
||||
let discardAppName = "";
|
||||
if(discardId)
|
||||
{
|
||||
let discardID = discardId.split(':');
|
||||
if(discardID.length < 2)
|
||||
{
|
||||
discardId = window.egw.app_name() + ":" + discardID.pop();
|
||||
}
|
||||
discardAppName = discardID.length > 1 ? discardID[0] : window.egw.app_name();
|
||||
}
|
||||
|
||||
const discarded = JSON.parse(window.egw.getLocalStorageItem(discardAppName, 'discardedMsgs'));
|
||||
if(Array.isArray(discarded))
|
||||
{
|
||||
return discarded.includes(discardId);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Display the alert as a toast notification
|
||||
* The returned promise will resolve after the message is hidden.
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public toast() : Promise<void>
|
||||
{
|
||||
this.__alert = this.alert;
|
||||
this.updateComplete.then(() =>
|
||||
{
|
||||
this.remove();
|
||||
})
|
||||
return this.alert.toast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the message
|
||||
* @returns {() => Promise<void>}
|
||||
*/
|
||||
public show() : () => Promise<void>
|
||||
{
|
||||
return this.alert.show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the message
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public hide() : Promise<void>
|
||||
{
|
||||
return this.alert.hide();
|
||||
}
|
||||
|
||||
get alert() : SlAlert { return this.shadowRoot?.querySelector("sl-alert") as SlAlert ?? this.__alert; }
|
||||
|
||||
get egw() : typeof egw
|
||||
{
|
||||
return window.egw
|
||||
}
|
||||
|
||||
protected handleHide(e)
|
||||
{
|
||||
// Store user's discard choice, if it was offered
|
||||
const check = <Et2Checkbox>this.alert.querySelector("#discard");
|
||||
if(this.discard && check && check.value && !EgwFrameworkMessage.isDiscarded(this.discard))
|
||||
{
|
||||
const discardAppName = this.discard.split(":").shift();
|
||||
let discarded : string | string[] = this.egw.getLocalStorageItem(discardAppName, 'discardedMsgs');
|
||||
if(!discarded)
|
||||
{
|
||||
discarded = [this.discard];
|
||||
}
|
||||
else
|
||||
{
|
||||
discarded = <string[]>JSON.parse(discarded)
|
||||
discarded.push(this.discard);
|
||||
}
|
||||
this.egw.setLocalStorageItem(discardAppName, 'discardedMsgs', JSON.stringify(discarded));
|
||||
}
|
||||
this.updateComplete.then(() =>
|
||||
{
|
||||
this.dispatchEvent(new CustomEvent("sl-hide"));
|
||||
});
|
||||
}
|
||||
|
||||
render()
|
||||
{
|
||||
const icon = EgwFrameworkMessage.ICON_MAP[this.type] ?? "info-circle";
|
||||
const variant = EgwFrameworkMessage.TYPE_MAP[this.type] ?? "success";
|
||||
const duration = this.type == "success" && !this.duration ? 5000 : this.duration;
|
||||
|
||||
let discard : symbol | TemplateResult = nothing;
|
||||
if(this.discard && EgwFrameworkMessage.isDiscarded(this.discard))
|
||||
{
|
||||
// Don't show discarded messages
|
||||
return nothing;
|
||||
}
|
||||
else if(this.discard)
|
||||
{
|
||||
discard = html`
|
||||
<et2-checkbox
|
||||
id="discard"
|
||||
label="${this.egw.lang("Don't show this again")}"
|
||||
></et2-checkbox>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<sl-alert
|
||||
variant=${variant}
|
||||
?closable=${this.closable}
|
||||
duration=${duration || nothing}
|
||||
@sl-hide=${this.handleHide}
|
||||
>
|
||||
<sl-icon name=${icon} slot="icon"></sl-icon>
|
||||
<et2-description activateLinks value=${this.message}></et2-description>
|
||||
${discard}
|
||||
</sl-alert>
|
||||
`;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user