mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-21 23:43:17 +01:00
Kdots: Implement messages in framework
This commit is contained in:
parent
8390b82b71
commit
4212cbc5b6
@ -5,6 +5,21 @@ body {
|
|||||||
overflow: clip;
|
overflow: clip;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 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) {
|
.egw_menu::part(popup) {
|
||||||
z-index: var(--sl-z-index-dropdown);
|
z-index: var(--sl-z-index-dropdown);
|
||||||
|
@ -4,6 +4,23 @@ html, body {
|
|||||||
overflow: clip;
|
overflow: clip;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 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)
|
.egw_menu::part(popup)
|
||||||
|
@ -3,12 +3,13 @@ import {customElement} from "lit/decorators/custom-element.js";
|
|||||||
import {property} from "lit/decorators/property.js";
|
import {property} from "lit/decorators/property.js";
|
||||||
import {classMap} from "lit/directives/class-map.js";
|
import {classMap} from "lit/directives/class-map.js";
|
||||||
import {repeat} from "lit/directives/repeat.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 "@shoelace-style/shoelace/dist/components/split-panel/split-panel.js";
|
||||||
import styles from "./EgwFramework.styles";
|
import styles from "./EgwFramework.styles";
|
||||||
import {egw} from "../../api/js/jsapi/egw_global";
|
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 {EgwFrameworkApp} from "./EgwFrameworkApp";
|
||||||
import {until} from "lit/directives/until.js";
|
import {EgwFrameworkMessage} from "./EgwFrameworkMessage";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Accessable, webComponent-based EGroupware framework
|
* @summary Accessable, webComponent-based EGroupware framework
|
||||||
@ -105,6 +106,9 @@ export class EgwFramework extends LitElement
|
|||||||
// Keep track of open popups
|
// Keep track of open popups
|
||||||
private _popups : Window[] = [];
|
private _popups : Window[] = [];
|
||||||
|
|
||||||
|
// Keep track of open messages
|
||||||
|
private _messages : SlAlert[] = [];
|
||||||
|
|
||||||
private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");}
|
private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");}
|
||||||
|
|
||||||
connectedCallback()
|
connectedCallback()
|
||||||
@ -143,6 +147,18 @@ export class EgwFramework extends LitElement
|
|||||||
// These need egw fully loaded
|
// These need egw fully loaded
|
||||||
this.getEgwComplete().then(() =>
|
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
|
// Quick add
|
||||||
this.egw.link_quick_add('topmenu_info_quick_add');
|
this.egw.link_quick_add('topmenu_info_quick_add');
|
||||||
|
|
||||||
@ -493,6 +509,67 @@ export class EgwFramework extends LitElement
|
|||||||
app.setSidebox(sideboxData, hash);
|
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 "";}
|
protected getBaseUrl() {return "";}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,6 +158,7 @@ export default css`
|
|||||||
|
|
||||||
.egw_fw_app__loading {
|
.egw_fw_app__loading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
sl-spinner {
|
sl-spinner {
|
||||||
--track-width: 1rem;
|
--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