Framework WIP:

- Avatar menu working
- App list dropdown in header
- Currently open apps in tabs in header
This commit is contained in:
nathan 2024-04-25 16:05:14 -06:00
parent 0bcb402b2e
commit 45d905378d
5 changed files with 311 additions and 18 deletions

View File

@ -0,0 +1,203 @@
html, body {
width: 100vw;
height: 100vh;
overflow: clip;
padding: 0px;
margin: 0px;
}
egw-framework#egw_fw_basecontainer {
--icon-size: 32px;
--sl-tooltip-arrow-size: 0;
/* Internals */
&::part(status-split) {
--max: 150px;
}
&::part(header) {
background-color: var(--primary-background-color);
color: var(--sl-color-neutral-0);
gap: var(--sl-spacing-medium);
font-size: var(--icon-size);
}
/* Content slotted inside */
[slot="logo"] img {
max-width: 220px;
max-height: var(--icon-size);
display: block;
text-align: center;
}
sl-icon-button[slot="header"], et2-image[slot="header"] {
font-size: var(--icon-size);
color: inherit
}
div#egw_fw_toggler {
position: initial;
display: none;
}
#egw_fw_topmenu_info_items {
position: relative;
order: 99;
margin-left: auto;
display: flex;
.topmenu_info_item {
min-width: 1em;
min-height: 1em;
}
}
div#egw_fw_sidebar_r {
position: initial;
top: initial;
}
.egw_fw_ui_sidemenu_entry_header {
display: flex;
gap: var(--sl-spacing-medium);
padding-left: 1em;
}
}
egw-app {
&::part(name) {
align-items: center;
}
}
/*** HEADER ***/
#egw_fw_topmenu_info_items {
display: flex !important;
flex-direction: row-reverse;
height: var(--icon-size);
background-color: #fbfbfb;
& > * {
border: none;
background: transparent;
}
et2-avatar {
--size: var(--icon-size);
}
.topmenu_info_item {
height: var(--icon-size);
width: var(--icon-size);
border-left: 1px solid #bfc0bf;
display: inline-block;
padding-left: 0px;
background-size: 20px;
background-position: center center;
background-repeat: no-repeat;
cursor: pointer;
&:hover {
background-color: var(--sl-input-background-color-hover);
}
}
#topmenu_info_timer {
order: 1;
position: relative;
#topmenu_timer {
position: relative;
top: 10px !important;
display: block;
height: 45px;
width: 45px;
}
&:hover {
cursor: pointer;
}
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url(../../../timesheet/templates/default/images/navbar.svg);
background-repeat: no-repeat;
background-size: 32px;
background-position: center center;
filter: opacity(0.3);
}
}
ul a#topmenu_cats {
background-image: url(../../../api/templates/default/images/topmenu_items/category.svg);
}
ul a#topmenu_password {
background-image: url(../../../api/templates/default/images/topmenu_items/password.svg);
}
ul a#topmenu_search {
background-image: url(../../../api/templates/default/images/topmenu_items/search.svg);
}
ul a#topmenu_prefs {
background-image: url(../../../api/templates/default/images/topmenu_items/setup.svg);
}
ul a#topmenu_home {
background-image: url(../../../api/templates/default/images/topmenu_items/home.svg);
}
ul a#topmenu_acl {
background-image: url(../../../api/templates/default/images/topmenu_items/access.svg);
}
ul a#topmenu_useraccount {
background-image: url(../../../api/templates/default/images/accounts.svg);
background-repeat: no-repeat;
background-size: 18px;
background-position-x: -2px;
}
ul a#topmenu_calls {
background-image: url(../../../api/templates/default/images/phone.svg);
background-repeat: no-repeat;
background-position-x: -2px;
}
}
#topmenu_info_logout {
background-image: url(../../../api/templates/default/images/logout.svg);
a {
width: 45px;
height: 45px;
display: inline-block;
}
}
#topmenu_info_print_title {
background-image: url(../../../api/templates/default/images/print.svg);
span {
width: 45px;
height: 45px;
display: inline-block;
}
}
/*** END HEADER ***/
div#egw_fw_basecontainer {
display: none;
}

View File

@ -44,13 +44,7 @@
</script> </script>
</div> </div>
<!-- status app is looking for this -->
<!-- Fake apps -->
<sl-icon-button name="backpack" slot="header" label="Backpack application"></sl-icon-button>
<sl-icon-button name="airplane" slot="header" label="Airplaine application"></sl-icon-button>
<sl-icon-button name="mortarboard" slot="header" label="Mortarboard application"></sl-icon-button>
<et2-image src="mail/navbar" slot="header"></et2-image>
<div slot="aside" id="egw_fw_sidebar_r"></div> <div slot="aside" id="egw_fw_sidebar_r"></div>
<!-- Fake app --> <!-- Fake app -->

View File

@ -28,7 +28,7 @@ class kdots_framework extends Api\Framework\Ajax
protected function _get_header(array $extra = array()) protected function _get_header(array $extra = array())
{ {
$data = parent::_get_header($extra); $data = parent::_get_header($extra);
$data['applicationlist'] = htmlentities(json_encode($extra['navbar-apps'], JSON_HEX_QUOT | JSON_HEX_AMP), ENT_QUOTES, 'UTF-8'); $data['application-list'] = htmlentities(json_encode($extra['navbar-apps'], JSON_HEX_QUOT | JSON_HEX_AMP), ENT_QUOTES, 'UTF-8');
return $data; return $data;
} }

View File

@ -6,6 +6,8 @@ export default css`
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
position: relative; position: relative;
--icon-size: 32px;
} }
.egw_fw__layout-default { .egw_fw__layout-default {
@ -109,4 +111,35 @@ export default css`
.egw_fw__header sl-icon-button { .egw_fw__header sl-icon-button {
color: inherit; color: inherit;
} }
.egw_fw__app_list::part(panel) {
display: grid;
grid-template-columns: repeat(5, 1fr);
background-color: var(--sl-color-neutral-0);
font-size: var(--icon-size);
}
.egw_fw__open_applications et2-image {
height: var(--icon-size);
width: var(--icon-size);
}
.egw_fw__open_applications sl-tab::part(base) {
padding: 0px;
font-size: var(--icon-size);
}
.egw_fw__open_applications sl-tab::part(close-button) {
visibility: hidden;
margin-inline-start: var(--sl-spacing-2x-small);
color: var(--sl-color-neutral-100);
}
.egw_fw__open_applications sl-tab et2-image {
padding: var(--sl-spacing-small) var(--sl-spacing-3x-small);
}
.egw_fw__open_applications sl-tab:hover::part(close-button) {
visibility: visible;
}
` `

View File

@ -3,10 +3,33 @@ 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 "@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 {repeat} from "lit/directives/repeat.js"; import {repeat} from "lit/directives/repeat.js";
/**
* @summary Accessable, webComponent-based EGroupware framework
*
* @dependency sl-dropdown
* @dependency sl-icon-button
*
* @slot - Current application
* @slot banner - Very top, used for things like persistant, system wide messages. Normally hidden.
* @slot header - Top of page, contains logo, app icons.
* @slot header-right - Top right, contains user info / actions.
* @slot status - Home of the status app, it is limited in size and can be resized and hidden.
* @slot footer - Very bottom. Normally hidden.
* *
* @csspart base - Wraps it all.
* @csspart banner -
* @csspart header -
* @csspart open-applications - Tab group that has the currently open applications
* @csspart status-split - Status splitter
* @csspart main
* @csspart status
* @csspart footer
*
* @cssproperty [--icon-size=32] - Height of icons used in the framework
*/
@customElement('egw-framework') @customElement('egw-framework')
//@ts-ignore //@ts-ignore
export class EgwFramework extends LitElement export class EgwFramework extends LitElement
@ -67,7 +90,7 @@ export class EgwFramework extends LitElement
@property() @property()
layout = "default"; layout = "default";
@property({type: Array}) @property({type: Array, attribute: "application-list"})
applicationList = []; applicationList = [];
get egw() get egw()
@ -79,9 +102,47 @@ export class EgwFramework extends LitElement
}; };
} }
/**
* An application tab is chosen, show the app
*
* @param e
* @protected
*/
protected handleApplicationTabShow(e)
{
}
/**
* Renders one application into the 9-dots application menu
*
* @param app
* @returns {TemplateResult<1>}
* @protected
*/
protected _applicationListAppTemplate(app)
{
return html`
<sl-tooltip placement="bottom" role="menuitem" content="${app.title}">
<et2-button-icon src="${app.icon}" aria-label="${app.title}" role="menuitem" noSubmit
helptext="${app.title}"></et2-button-icon>
</sl-tooltip>`;
}
protected _applicationTabTemplate(app)
{
return html`
<sl-tab slot="nav" panel="${app.app}" closable aria-label="${app.title}">
<sl-tooltip placement="bottom" content="${app.title}" hoist>
<et2-image src="${app.icon}"></et2-image>
</sl-tooltip>
</sl-tab>`;
}
render() render()
{ {
const statusPosition = this.egw?.preference("statusPosition", this.egw?.app_name()) ?? "36"; const iconSize = getComputedStyle(this).getPropertyValue("--icon-size");
const statusPosition = this.egw?.preference("statusPosition", "common") ?? parseInt(iconSize) ?? "36";
const classes = { const classes = {
"egw_fw__base": true "egw_fw__base": true
@ -95,24 +156,26 @@ export class EgwFramework extends LitElement
</div> </div>
<header class="egw_fw__header" part="header"> <header class="egw_fw__header" part="header">
<slot name="logo"></slot> <slot name="logo"></slot>
<sl-dropdown class="egw_fw__app_list"> <sl-dropdown class="egw_fw__app_list" role="menu">
<sl-icon-button slot="trigger" name="grid-3x3-gap" <sl-icon-button slot="trigger" name="grid-3x3-gap"
label="${this.egw.lang("Application list")}" label="${this.egw.lang("Application list")}"
aria-description="${this.egw.lang("Activate for a list of applications")}" aria-description="${this.egw.lang("Activate for a list of applications")}"
></sl-icon-button> ></sl-icon-button>
${repeat(this.applicationList, (app) => html` ${repeat(this.applicationList, (app) => this._applicationListAppTemplate(app))}
<et2-image src="${app.icon}" aria-label="${app.title}"></et2-image>`)}
</sl-dropdown> </sl-dropdown>
<sl-tab-group part="open-applications" class="egw_fw__open_applications" activation="manual"
role="tablist"
@sl-tab-show=${this.handleApplicationTabShow}>
${repeat(this.applicationList.filter(app => app.opened), (app) => this._applicationTabTemplate(app))}
</sl-tab-group>
<slot name="header"><span class="placeholder">header</span></slot> <slot name="header"><span class="placeholder">header</span></slot>
<slot name="header-right"><span class="placeholder">header-right</span></slot> <slot name="header-right"><span class="placeholder">header-right</span></slot>
</header> </header>
<div class="egw_fw__divider"> <div class="egw_fw__divider">
<sl-split-panel part="status-split" position-in-pixels="${statusPosition}" primary="end" <sl-split-panel part="status-split" position-in-pixels="${statusPosition}" primary="end"
snap="150px 45px 0px" snap="150px ${iconSize} 0px"
snap-threshold="40" snap-threshold="${Math.min(40, parseInt(iconSize) - 5)}"
aria-label="Side menu resize"> aria-label="Side menu resize">
<main slot="start" part="main"> <main slot="start" part="main">
<slot></slot> <slot></slot>
</main> </main>