Framework WIP:

- Apps loading
This commit is contained in:
nathan 2024-05-08 13:46:09 -06:00
parent c1db022fb8
commit c6c8de0a3b
6 changed files with 92 additions and 31 deletions

View File

@ -48,14 +48,8 @@
<!-- status app is looking for this --> <!-- status app is looking for this -->
<div slot="aside" id="egw_fw_sidebar_r"></div> <div slot="aside" id="egw_fw_sidebar_r"></div>
<!-- Fake app --> <!-- Currently open app -->
<egw-app name="fake app" class="placeholder"> <egw-app name="{open_app_name}" url="{open_app_url}" active></egw-app>
<div style="border: 1px dotted">Something inside the app - main</div>
<div slot="left">Left content</div>
<div slot="right">right content</div>
<div slot="right-header">
</div>
</egw-app>
</egw-framework> </egw-framework>

View File

@ -29,7 +29,12 @@ class kdots_framework extends Api\Framework\Ajax
{ {
$data = parent::_get_header($extra); $data = parent::_get_header($extra);
$data['application-list'] = 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');
$open_app = current(array_filter($extra['navbar-apps'], function ($app)
{
return $app['active'] ?? false;
})) ?? [];
$data['open_app_name'] = $open_app['name'];
$data['open_app_url'] = $open_app['url'];
return $data; return $data;
} }

View File

@ -131,4 +131,12 @@ export default css`
.egw_fw__open_applications sl-tab:hover::part(close-button), .egw_fw__open_applications sl-tab[active]::part(close-button) { .egw_fw__open_applications sl-tab:hover::part(close-button), .egw_fw__open_applications sl-tab[active]::part(close-button) {
visibility: visible; visibility: visible;
} }
::slotted(egw-app) {
display: none;
}
::slotted(egw-app[active]) {
display: flex;
}
` `

View File

@ -1,4 +1,4 @@
import {css, html, LitElement} from "lit"; import {css, html, LitElement, nothing} from "lit";
import {customElement} from "lit/decorators/custom-element.js"; 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";
@ -6,7 +6,7 @@ import {repeat} from "lit/directives/repeat.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 {SlTab, SlTabGroup} from "@shoelace-style/shoelace"; import {SlDropdown, SlTab, SlTabGroup} from "@shoelace-style/shoelace";
import {EgwFrameworkApp} from "./EgwFrameworkApp"; import {EgwFrameworkApp} from "./EgwFrameworkApp";
/** /**
@ -93,9 +93,16 @@ export class EgwFramework extends LitElement
@property() @property()
layout = "default"; layout = "default";
/**
* This is the list of all applications we know about
*
* @type {any[]}
*/
@property({type: Array, attribute: "application-list"}) @property({type: Array, attribute: "application-list"})
applicationList = []; applicationList = [];
private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");}
get egw() : typeof egw get egw() : typeof egw
{ {
return window.egw ?? <typeof egw>{ return window.egw ?? <typeof egw>{
@ -131,16 +138,30 @@ export class EgwFramework extends LitElement
(menuaction ? '.' + menuaction[1] : ''); (menuaction ? '.' + menuaction[1] : '');
}; };
public loadApp(appname) public loadApp(appname, active = false)
{ {
const app = this.applicationList.find(a => a.name == appname); const app = this.applicationList.find(a => a.name == appname);
let appComponent = <EgwFrameworkApp>document.createElement("egw-app"); let appComponent = <EgwFrameworkApp>document.createElement("egw-app");
appComponent.id = appname; appComponent.id = appname;
appComponent.name = appname; appComponent.name = appname;
appComponent.url = app?.url; appComponent.url = app?.url;
this.append(appComponent); this.append(appComponent);
app.opened = this.shadowRoot.querySelectorAll("sl-tab").length; // App was not in the tab list
if(typeof app.opened == "undefined")
{
app.opened = this.shadowRoot.querySelectorAll("sl-tab").length;
this.requestUpdate("applicationList"); this.requestUpdate("applicationList");
}
// Wait until new tab is there to activate it
if(active)
{
this.updateComplete.then(() =>
{
this.tabs.show(appname);
})
}
return appComponent; return appComponent;
} }
@ -214,17 +235,28 @@ export class EgwFramework extends LitElement
*/ */
protected _applicationListAppTemplate(app) protected _applicationListAppTemplate(app)
{ {
if(app.status !== "1")
{
return nothing;
}
return html` return html`
<sl-tooltip placement="bottom" role="menuitem" content="${app.title}"> <sl-tooltip placement="bottom" role="menuitem" content="${app.title}">
<et2-button-icon src="${app.icon}" aria-label="${app.title}" role="menuitem" noSubmit <et2-button-icon src="${app.icon}" aria-label="${app.title}" role="menuitem" noSubmit
helptext="${app.title}"></et2-button-icon> helptext="${app.title}"
@click=${() =>
{
this.loadApp(app.name, true);
(<SlDropdown>this.shadowRoot.querySelector(".egw_fw__app_list")).hide();
}}
></et2-button-icon>
</sl-tooltip>`; </sl-tooltip>`;
} }
protected _applicationTabTemplate(app) protected _applicationTabTemplate(app)
{ {
return html` return html`
<sl-tab slot="nav" panel="${app.name}" closable aria-label="${app.title}"> <sl-tab slot="nav" panel="${app.name}" closable aria-label="${app.title}" ?active=${app.active}>
<sl-tooltip placement="bottom" content="${app.title}" hoist> <sl-tooltip placement="bottom" content="${app.title}" hoist>
<et2-image src="${app.icon}"></et2-image> <et2-image src="${app.icon}"></et2-image>
</sl-tooltip> </sl-tooltip>
@ -260,7 +292,9 @@ export class EgwFramework extends LitElement
@sl-tab-show=${this.handleApplicationTabShow} @sl-tab-show=${this.handleApplicationTabShow}
@sl-close=${this.handleApplicationTabClose} @sl-close=${this.handleApplicationTabClose}
> >
${repeat(this.applicationList.filter(app => app.opened).sort((a, b) => a.opened - b.opened), (app) => this._applicationTabTemplate(app))} ${repeat(this.applicationList
.filter(app => typeof app.opened !== "undefined")
.sort((a, b) => a.opened - b.opened), (app) => this._applicationTabTemplate(app))}
</sl-tab-group> </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>

View File

@ -122,6 +122,20 @@ export default css`
overflow: hidden; overflow: hidden;
} }
.egw_fw_app {
--application-color: var(--primary-background-color);
}
.egw_fw_app__loading {
text-align: center;
sl-spinner {
--track-width: 1rem;
font-size: 10rem;
--indicator-color: var(--application-color, var(--primary-background-color, var(--sl-color-primary-600)));
}
}
@media (min-width: 600px) { @media (min-width: 600px) {
.egw_fw_app__main { .egw_fw_app__main {
grid-template-columns: [start left] min-content [ main] 1fr [right] min-content [end]; grid-template-columns: [start left] min-content [ main] 1fr [right] min-content [end];

View File

@ -1,8 +1,9 @@
import {css, html, LitElement, nothing} from "lit"; import {css, html, LitElement, nothing, render} from "lit";
import {customElement} from "lit/decorators/custom-element.js"; import {customElement} from "lit/decorators/custom-element.js";
import {property} from "lit/decorators/property.js"; import {property} from "lit/decorators/property.js";
import {state} from "lit/decorators/state.js"; import {state} from "lit/decorators/state.js";
import {classMap} from "lit/directives/class-map.js"; import {classMap} from "lit/directives/class-map.js";
import {unsafeHTML} from "lit/directives/unsafe-html.js";
import styles from "./EgwFrameworkApp.styles"; import styles from "./EgwFrameworkApp.styles";
import {SlSplitPanel} from "@shoelace-style/shoelace"; import {SlSplitPanel} from "@shoelace-style/shoelace";
@ -138,15 +139,11 @@ export class EgwFrameworkApp extends LitElement
{ {
this.rightPanelInfo.preferenceWidth = parseInt(width) ?? this.rightPanelInfo.defaultWidth; this.rightPanelInfo.preferenceWidth = parseInt(width) ?? this.rightPanelInfo.defaultWidth;
}); });
// Register the "data" plugin
this.egw.registerJSONPlugin(this.jsonDataHandler, this, 'data');
} }
disconnectedCallback() disconnectedCallback()
{ {
super.disconnectedCallback(); super.disconnectedCallback();
this.egw.unregisterJSONPlugin(this.jsonDataHandler, this, "data", false)
} }
firstUpdated() firstUpdated()
@ -186,16 +183,11 @@ export class EgwFrameworkApp extends LitElement
return this.loadingPromise = this.egw.request( return this.loadingPromise = this.egw.request(
this.framework.getMenuaction('ajax_exec', targetUrl, this.name), this.framework.getMenuaction('ajax_exec', targetUrl, this.name),
[targetUrl] [targetUrl]
); ).then((data : string[]) =>
}
protected jsonDataHandler(type, res, req)
{
if(req.context !== this)
{ {
return; // Load request returns HTML. Shove it in.
} render(html`${unsafeHTML(data.join(""))}`, this);
debugger; });
} }
public showLeft() public showLeft()
@ -275,6 +267,20 @@ export class EgwFrameworkApp extends LitElement
} }
} }
/**
* Displayed for the time between when the application is added and when the server responds with content
*
* @returns {TemplateResult<1>}
* @protected
*/
protected _loadingTemplate()
{
return html`
<div class="egw_fw_app__loading">
<sl-spinner></sl-spinner>
</div>`;
}
protected _asideTemplate(parentSlot, side, label?) protected _asideTemplate(parentSlot, side, label?)
{ {
const asideClassMap = classMap({ const asideClassMap = classMap({
@ -367,7 +373,7 @@ export class EgwFrameworkApp extends LitElement
</header> </header>
<div slot="start" class="egw_fw_app__main_content content" part="content" <div slot="start" class="egw_fw_app__main_content content" part="content"
aria-label="${this.name}" tabindex="0"> aria-label="${this.name}" tabindex="0">
<slot><span class="placeholder">main</span></slot> <slot>${this._loadingTemplate()}<span class="placeholder">main</span></slot>
</div> </div>
<footer slot="start" class="egw_fw_app__footer footer" part="footer"> <footer slot="start" class="egw_fw_app__footer footer" part="footer">
<slot name="footer"><span class="placeholder">main-footer</span></slot> <slot name="footer"><span class="placeholder">main-footer</span></slot>