mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 14:41:29 +01:00
Framework WIP:
- Starting to get app loading
This commit is contained in:
parent
e3d66c2cc6
commit
a9d57499a3
@ -61,30 +61,6 @@
|
||||
|
||||
{hook_after_navbar}
|
||||
|
||||
<!-- END framework -->
|
||||
<!--
|
||||
|
||||
<div id="egw_fw_basecontainer" lang="{lang_code}">
|
||||
<div id="egw_fw_header">
|
||||
<div id="egw_fw_topmenu">
|
||||
<div id="egw_fw_topmenu_items">
|
||||
{topmenu_items}
|
||||
<div class="timezone">
|
||||
{user_info}
|
||||
</div>
|
||||
{powered_by}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="egw_fw_sidebar">
|
||||
<div id="egw_fw_sidemenu"></div>
|
||||
<div id="egw_fw_splitter"></div>
|
||||
</div>
|
||||
<div id="egw_fw_main">
|
||||
<div id="egw_fw_tabs">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="egw_fw_firstload">
|
||||
{firstload_animation}
|
||||
</div>
|
||||
|
@ -2,10 +2,12 @@ import {css, html, LitElement} from "lit";
|
||||
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 "@shoelace-style/shoelace/dist/components/split-panel/split-panel.js";
|
||||
import styles from "./EgwFramework.styles";
|
||||
import {repeat} from "lit/directives/repeat.js";
|
||||
import {Function} from "estree";
|
||||
import {egw} from "../../api/js/jsapi/egw_global";
|
||||
import {SlTab, SlTabGroup} from "@shoelace-style/shoelace";
|
||||
import {EgwFrameworkApp} from "./EgwFrameworkApp";
|
||||
|
||||
/**
|
||||
* @summary Accessable, webComponent-based EGroupware framework
|
||||
@ -94,9 +96,9 @@ export class EgwFramework extends LitElement
|
||||
@property({type: Array, attribute: "application-list"})
|
||||
applicationList = [];
|
||||
|
||||
get egw()
|
||||
get egw() : typeof egw
|
||||
{
|
||||
return window.egw ?? {
|
||||
return window.egw ?? <typeof egw>{
|
||||
// Dummy egw so we don't get failures from missing methods
|
||||
lang: (t) => t,
|
||||
preference: (n, app, promise? : Function | boolean | undefined) => Promise.resolve(""),
|
||||
@ -104,15 +106,103 @@ export class EgwFramework extends LitElement
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param _function Framework function to be called on the server.
|
||||
* @param _ajax_exec_url Actual function we want to call.
|
||||
* @returns {string}
|
||||
*/
|
||||
public getMenuaction(_fun, _ajax_exec_url, appName = 'home')
|
||||
{
|
||||
let baseUrl = this.getBaseUrl();
|
||||
|
||||
// Check whether the baseurl is actually set. If not, then this application
|
||||
// resides inside the same egw instance as the jdots framework. We'll simply
|
||||
// return a menu action and not a full featured url here.
|
||||
if(baseUrl != '')
|
||||
{
|
||||
baseUrl = baseUrl + 'json.php?menuaction=';
|
||||
}
|
||||
|
||||
const menuaction = _ajax_exec_url ? _ajax_exec_url.match(/menuaction=([^&]+)/) : null;
|
||||
|
||||
// use template handler to call current framework, eg. pixelegg
|
||||
return baseUrl + appName + '.kdots_framework.' + _fun + '.template' +
|
||||
(menuaction ? '.' + menuaction[1] : '');
|
||||
};
|
||||
|
||||
public loadApp(appname)
|
||||
{
|
||||
const app = this.applicationList.find(a => a.name == appname);
|
||||
let appComponent = <EgwFrameworkApp>document.createElement("egw-app");
|
||||
appComponent.id = appname;
|
||||
appComponent.name = appname;
|
||||
appComponent.url = app?.url;
|
||||
this.append(appComponent);
|
||||
app.opened = this.shadowRoot.querySelectorAll("sl-tab").length;
|
||||
this.requestUpdate("applicationList");
|
||||
|
||||
return appComponent;
|
||||
}
|
||||
|
||||
protected getBaseUrl() {return "";}
|
||||
|
||||
/**
|
||||
* An application tab is chosen, show the app
|
||||
*
|
||||
* @param e
|
||||
* @protected
|
||||
*/
|
||||
protected handleApplicationTabShow(e)
|
||||
protected handleApplicationTabShow(event)
|
||||
{
|
||||
this.querySelectorAll("egw-app").forEach(app => app.removeAttribute("active"));
|
||||
|
||||
// Create & show app
|
||||
const appname = event.target.activeTab.panel;
|
||||
let appComponent = this.querySelector(`egw-app#${appname}`);
|
||||
if(!appComponent)
|
||||
{
|
||||
appComponent = this.loadApp(appname);
|
||||
}
|
||||
appComponent.setAttribute("active", "");
|
||||
|
||||
// Update the list on the server
|
||||
this.updateTabs(event.target.activeTab);
|
||||
}
|
||||
|
||||
/**
|
||||
* An application tab is closed
|
||||
*/
|
||||
protected handleApplicationTabClose(event)
|
||||
{
|
||||
const tabGroup : SlTabGroup = this.shadowRoot.querySelector("sl-tab-group.egw_fw__open_applications");
|
||||
const tab = event.target;
|
||||
const panel = tabGroup.querySelector(`sl-tab-panel[name="${tab.panel}"]`);
|
||||
|
||||
// Show the previous tab if the tab is currently active
|
||||
if(tab.active)
|
||||
{
|
||||
tabGroup.show(tab.previousElementSibling.panel);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Show will update, but closing in the background we call directly
|
||||
this.updateTabs(tabGroup.querySelector("sl-tab[active]"));
|
||||
}
|
||||
|
||||
// Remove the tab + panel
|
||||
tab.remove();
|
||||
panel.remove();
|
||||
}
|
||||
|
||||
private updateTabs(activeTab)
|
||||
{
|
||||
let appList = [];
|
||||
Array.from(this.shadowRoot.querySelectorAll("sl-tab-group.egw_fw__open_applications sl-tab")).forEach((tab : SlTab) =>
|
||||
{
|
||||
appList.push({appName: tab.panel, active: activeTab.panel == tab.panel})
|
||||
});
|
||||
this.egw.jsonq('EGroupware\\Api\\Framework\\Ajax::ajax_tab_changed_state', [appList]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +224,7 @@ export class EgwFramework extends LitElement
|
||||
protected _applicationTabTemplate(app)
|
||||
{
|
||||
return html`
|
||||
<sl-tab slot="nav" panel="${app.app}" closable aria-label="${app.title}">
|
||||
<sl-tab slot="nav" panel="${app.name}" closable aria-label="${app.title}">
|
||||
<sl-tooltip placement="bottom" content="${app.title}" hoist>
|
||||
<et2-image src="${app.icon}"></et2-image>
|
||||
</sl-tooltip>
|
||||
@ -167,8 +257,10 @@ export class EgwFramework extends LitElement
|
||||
</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-show=${this.handleApplicationTabShow}
|
||||
@sl-close=${this.handleApplicationTabClose}
|
||||
>
|
||||
${repeat(this.applicationList.filter(app => app.opened).sort((a, b) => a.opened - b.opened), (app) => this._applicationTabTemplate(app))}
|
||||
</sl-tab-group>
|
||||
<slot name="header"><span class="placeholder">header</span></slot>
|
||||
<slot name="header-right"><span class="placeholder">header-right</span></slot>
|
||||
|
@ -88,6 +88,9 @@ export class EgwFrameworkApp extends LitElement
|
||||
@property()
|
||||
name = "Application name";
|
||||
|
||||
@property()
|
||||
url = "";
|
||||
|
||||
@state()
|
||||
leftCollapsed = false;
|
||||
|
||||
@ -122,6 +125,8 @@ export class EgwFrameworkApp extends LitElement
|
||||
};
|
||||
private resizeTimeout : number;
|
||||
|
||||
protected loadingPromise = Promise.resolve();
|
||||
|
||||
connectedCallback()
|
||||
{
|
||||
super.connectedCallback();
|
||||
@ -133,6 +138,64 @@ export class EgwFrameworkApp extends LitElement
|
||||
{
|
||||
this.rightPanelInfo.preferenceWidth = parseInt(width) ?? this.rightPanelInfo.defaultWidth;
|
||||
});
|
||||
|
||||
// Register the "data" plugin
|
||||
this.egw.registerJSONPlugin(this.jsonDataHandler, this, 'data');
|
||||
}
|
||||
|
||||
disconnectedCallback()
|
||||
{
|
||||
super.disconnectedCallback();
|
||||
this.egw.unregisterJSONPlugin(this.jsonDataHandler, this, "data", false)
|
||||
}
|
||||
|
||||
firstUpdated()
|
||||
{
|
||||
this.load(this.url);
|
||||
}
|
||||
|
||||
protected load(url)
|
||||
{
|
||||
if(!url)
|
||||
{
|
||||
while(this.firstChild)
|
||||
{
|
||||
this.removeChild(this.lastChild);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let targetUrl = "";
|
||||
let useIframe = false;
|
||||
let matches = url.match(/\/index.php\?menuaction=([A-Za-z0-9_\.]*.*&ajax=true.*)$/);
|
||||
if(matches)
|
||||
{
|
||||
// Matches[1] contains the menuaction which should be executed - replace
|
||||
// the given url with the following line. This will be evaluated by the
|
||||
// jdots_framework ajax_exec function which will be called by the code
|
||||
// below as we set useIframe to false.
|
||||
targetUrl = "index.php?menuaction=" + matches[1];
|
||||
useIframe = false;
|
||||
}
|
||||
|
||||
// Destroy application js
|
||||
if(window.app[this.name] && window.app[this.name].destroy)
|
||||
{
|
||||
window.app[this.name].destroy();
|
||||
delete window.app[this.name]; // really delete it, so new object get constructed and registered for push
|
||||
}
|
||||
return this.loadingPromise = this.egw.request(
|
||||
this.framework.getMenuaction('ajax_exec', targetUrl, this.name),
|
||||
[targetUrl]
|
||||
);
|
||||
}
|
||||
|
||||
protected jsonDataHandler(type, res, req)
|
||||
{
|
||||
if(req.context !== this)
|
||||
{
|
||||
return;
|
||||
}
|
||||
debugger;
|
||||
}
|
||||
|
||||
public showLeft()
|
||||
@ -174,6 +237,11 @@ export class EgwFrameworkApp extends LitElement
|
||||
return window.egw ?? (<EgwFramework>this.parentElement).egw ?? null;
|
||||
}
|
||||
|
||||
get framework() : EgwFramework
|
||||
{
|
||||
return this.closest("egw-framework");
|
||||
}
|
||||
|
||||
/**
|
||||
* User adjusted side slider, update preference
|
||||
*
|
||||
@ -207,21 +275,33 @@ export class EgwFrameworkApp extends LitElement
|
||||
}
|
||||
}
|
||||
|
||||
protected _asideTemplate(parentSlot, side, label?)
|
||||
{
|
||||
const asideClassMap = classMap({
|
||||
"egw_fw_app__aside": true,
|
||||
"egw_fw_app__left": true,
|
||||
"egw_fw_app__aside-collapsed": this.leftCollapsed,
|
||||
});
|
||||
return html`
|
||||
<aside slot="${parentSlot}" part="${side}" class=${asideClassMap} aria-label="${label}">
|
||||
<div class="egw_fw_app__aside_header header">
|
||||
<slot name="${side}-header"><span class="placeholder">${side}-header</span></slot>
|
||||
</div>
|
||||
<div class="egw_fw_app__aside_content content">
|
||||
<slot name="${side}"><span class="placeholder">${side}</span></slot>
|
||||
</div>
|
||||
|
||||
<div class="egw_fw_app__aside_footer footer">
|
||||
<slot name="${side}-footer"><span class="placeholder">${side}-footer</span></slot>
|
||||
</div>
|
||||
</aside>`;
|
||||
}
|
||||
|
||||
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 :
|
||||
@ -268,18 +348,7 @@ export class EgwFrameworkApp extends LitElement
|
||||
this.leftCollapsed = !this.leftCollapsed;
|
||||
this.requestUpdate();
|
||||
}}></sl-icon>
|
||||
<aside slot="start" part="left" class=${leftClassMap}>
|
||||
<div class="egw_fw_app__aside_header header">
|
||||
<slot name="left-header"><span class="placeholder">left-header</span></slot>
|
||||
</div>
|
||||
<div class="egw_fw_app__aside_content content">
|
||||
<slot name="left"><span class="placeholder">left</span></slot>
|
||||
</div>
|
||||
|
||||
<div class="egw_fw_app__aside_footer footer">
|
||||
<slot name="left-footer"><span class="placeholder">left-footer</span></slot>
|
||||
</div>
|
||||
</aside>
|
||||
${this._asideTemplate("start", "left")}
|
||||
<sl-split-panel slot="end"
|
||||
class=${classMap({"egw_fw_app__innerSplit": true, "no-content": !hasRightSlots})}
|
||||
primary="start"
|
||||
@ -303,18 +372,7 @@ export class EgwFrameworkApp extends LitElement
|
||||
<footer slot="start" class="egw_fw_app__footer footer" part="footer">
|
||||
<slot name="footer"><span class="placeholder">main-footer</span></slot>
|
||||
</footer>
|
||||
<aside slot="end" class=${rightClassMap} part="right"
|
||||
aria-label="${this.egw.lang("%1 application details", this.egw.lang(this.name))}">
|
||||
<header class="egw_fw_app__aside_header header">
|
||||
<slot name="right-header"><span class="placeholder">right-header</span></slot>
|
||||
</header>
|
||||
<div class="egw_fw_app__aside_content content" tabindex="0">
|
||||
<slot name="right"><span class="placeholder">right</span></slot>
|
||||
</div>
|
||||
<footer class="egw_fw_app__aside_footer footer">
|
||||
<slot name="right-footer"><span class="placeholder">right-footer</span></slot>
|
||||
</footer>
|
||||
</aside>
|
||||
${this._asideTemplate("end", "right", this.egw.lang("%1 application details", this.egw.lang(this.name)))}
|
||||
</sl-split-panel>
|
||||
</sl-split-panel>
|
||||
</div>
|
||||
|
@ -2,12 +2,21 @@
|
||||
* app.ts is auto-built
|
||||
*/
|
||||
|
||||
import "./EgwFramework";
|
||||
import "./EgwFrameworkApp";
|
||||
import {EgwFramework} from "./EgwFramework";
|
||||
import {EgwFrameworkApp} from "./EgwFrameworkApp";
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () =>
|
||||
{
|
||||
// Not sure what's up here
|
||||
if(!window.customElements.get("egw-framework"))
|
||||
{
|
||||
window.customElements.define("egw-framework", EgwFramework);
|
||||
}
|
||||
if(!window.customElements.get("egw-app"))
|
||||
{
|
||||
window.customElements.define("egw-app", EgwFrameworkApp);
|
||||
}
|
||||
/* Set up listener on avatar menu */
|
||||
const avatarMenu = document.querySelector("#topmenu_info_user_avatar");
|
||||
avatarMenu.addEventListener("sl-select", (e : CustomEvent) =>
|
||||
|
Loading…
Reference in New Issue
Block a user