import {css, html, LitElement, nothing} from "lit"; import {customElement} from "lit/decorators/custom-element.js"; import {property} from "lit/decorators/property.js"; import {state} from "lit/decorators/state.js"; import {classMap} from "lit/directives/class-map.js"; import styles from "./EgwFrameworkApp.styles"; import {SlSplitPanel} from "@shoelace-style/shoelace"; import {HasSlotController} from "../../api/js/etemplate/Et2Widget/slot"; import type {EgwFramework} from "./EgwFramework"; /** * @summary Application component inside EgwFramework * * Contain an EGroupware application inside the main framework. It consists of left, main and right areas. Each area * has a header, content and footer. Side content areas are not shown when there is no content. * * @dependency sl-split-panel * * @slot - Main application content. Other slots are normally hidden if they have no content * @slot header - Top of app, contains logo, app icons. * @slot footer - Very bottom of the main content. * @slot left - Optional content to the left. Use for application navigation. * @slot left-header - Top of left side * @slot left-footer - bottom of left side * @slot right - Optional content to the right. Use for application context details. * @slot right-header - Top of right side * @slot right-footer - bottom of right side * * @csspart name - Top left, holds the application name. * @csspart header - Top main application header, optional application toolbar goes here. * @csspart content-header - Top of center, optional. * @csspart main - Main application content. * @csspart left - Left optional content. * @csspart right - Right optional content. * @csspart footer - Very bottom of the main content. * * @cssproperty [--left-min=0] - Minimum width of the left content * @cssproperty [--left-max=20%] - Maximum width of the left content * @cssproperty [--right-min=0] - Minimum width of the right content * @cssproperty [--right-max=50%] - Maximum width of the right content */ @customElement('egw-app') //@ts-ignore export class EgwFrameworkApp extends LitElement { static get styles() { return [ styles, // TEMP STUFF css` :host .placeholder { display: none; } :host(.placeholder) .placeholder { display: block; --placeholder-background-color: #e97234; } .placeholder { width: 100%; font-size: 200%; text-align: center; background-color: var(--placeholder-background-color); } .placeholder:after, .placeholder:before { content: " ⌖ "; } :host(.placeholder) [class*="left"] .placeholder { background-color: color-mix(in lch, var(--placeholder-background-color), rgba(.5, .5, 1, .1)); } :host(.placeholder) [class*="right"] .placeholder { background-color: color-mix(in lch, var(--placeholder-background-color), rgba(.5, 1, .5, .1)); } :host(.placeholder) [class*="footer"] .placeholder { background-color: color-mix(in lch, var(--placeholder-background-color), rgba(1, 1, 1, .05)); } ` ]; } @property() name = "Application name"; @state() leftCollapsed = false; @state() rightCollapsed = false; get leftSplitter() { return this.shadowRoot.querySelector(".egw_fw_app__outerSplit");} get rightSplitter() { return this.shadowRoot.querySelector(".egw_fw_app__innerSplit");} protected readonly hasSlotController = new HasSlotController(this, 'left', 'left-header', 'left-footer', 'right', 'right-header', 'right-footer', ); // Left is in pixels private leftPanelInfo : PanelInfo = { side: "left", preference: "jdotssideboxwidth", defaultWidth: 200, hiddenWidth: 0, preferenceWidth: 200 }; // Right is in percentage private rightPanelInfo : PanelInfo = { side: "right", preference: "app_right_width", defaultWidth: 50, hiddenWidth: 100, preferenceWidth: 50 }; private resizeTimeout : number; connectedCallback() { super.connectedCallback(); (>this.egw.preference(this.leftPanelInfo.preference, this.name, true)).then((width) => { this.leftPanelInfo.preferenceWidth = parseInt(width) ?? this.leftPanelInfo.defaultWidth; }); (>this.egw.preference(this.rightPanelInfo.preference, this.name, true)).then((width) => { this.rightPanelInfo.preferenceWidth = parseInt(width) ?? this.rightPanelInfo.defaultWidth; }); } public showLeft() { this.showSide("left"); } public hideLeft() { this.hideSide("left"); } public showRight() { this.showSide("right"); } public hideRight() { this.hideSide("right"); } protected showSide(side) { const attribute = `${side}Collapsed`; this[attribute] = false; this[`${side}Splitter`].position = this[`${side}PanelInfo`].preferenceWidth || this[`${side}PanelInfo`].defaultWidth; } protected hideSide(side : "left" | "right") { const attribute = `${side}Collapsed`; this[attribute] = true; this[`${side}Splitter`].position = this[`${side}PanelInfo`].hiddenWidth; } get egw() { return window.egw ?? (this.parentElement).egw ?? null; } /** * User adjusted side slider, update preference * * @param event * @protected */ protected async handleSlide(event) { if(typeof event.target?.panelInfo != "object") { return; } // Left side is in pixels, round to 2 decimals let newPosition = Math.round(event.target.panelInfo.side == "left" ? event.target.positionInPixels * 100 : event.target.position * 100) / 100; // Update collapsed this[`${event.target.panelInfo.name}Collapsed`] = newPosition == event.target.panelInfo.hiddenWidth; let preferenceName = event.target.panelInfo.preference; if(newPosition != event.target.panelInfo.preferenceWidth) { event.target.panelInfo.preferenceWidth = newPosition; if(this.resizeTimeout) { window.clearTimeout(this.resizeTimeout); } window.setTimeout(() => { this.egw.set_preference(this.name, preferenceName, newPosition); }, 500); } } render() { const hasLeftSlots = this.hasSlotController.test('left-header') || this.hasSlotController.test('left') || this.hasSlotController.test('left-footer'); const hasRightSlots = this.hasSlotController.test('right-header') || this.hasSlotController.test('right') || this.hasSlotController.test('right-footer'); const leftClassMap = classMap({ "egw_fw_app__aside": true, "egw_fw_app__left": true, "egw_fw_app__aside-collapsed": this.leftCollapsed, }); const rightClassMap = classMap({ "egw_fw_app__aside": true, "egw_fw_app__right": true, "egw_fw_app__aside-collapsed": this.rightCollapsed, }); const leftWidth = this.leftCollapsed || !hasLeftSlots ? this.leftPanelInfo.hiddenWidth : this.leftPanelInfo.preferenceWidth; const rightWidth = this.rightCollapsed || !hasRightSlots ? this.rightPanelInfo.hiddenWidth : this.rightPanelInfo.preferenceWidth; return html`
${hasLeftSlots ? html` { this.leftCollapsed = !this.leftCollapsed; // Just in case they collapsed it manually, reset this.leftPanelInfo.preferenceWidth = this.leftPanelInfo.preferenceWidth || this.leftPanelInfo.defaultWidth; this.requestUpdate("leftCollapsed") }} >` : nothing }

${this.egw?.lang(this.name) ?? this.name}

${this.name} main-header
this.handleSlide(e)} > { this.leftCollapsed = !this.leftCollapsed; this.requestUpdate(); }}> this.handleSlide(e)} > { this.rightCollapsed = !this.rightCollapsed; this.requestUpdate(); }}>
header
main
main-footer
`; } } type PanelInfo = { side : "left" | "right", preference : "jdotssideboxwidth" | "app_right_width", hiddenWidth : number, defaultWidth : number, preferenceWidth : number | string }