mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-25 01:13:25 +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}
|
{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">
|
<div id="egw_fw_firstload">
|
||||||
{firstload_animation}
|
{firstload_animation}
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,10 +2,12 @@ import {css, html, LitElement} 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";
|
||||||
|
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 {repeat} from "lit/directives/repeat.js";
|
import {egw} from "../../api/js/jsapi/egw_global";
|
||||||
import {Function} from "estree";
|
import {SlTab, SlTabGroup} from "@shoelace-style/shoelace";
|
||||||
|
import {EgwFrameworkApp} from "./EgwFrameworkApp";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Accessable, webComponent-based EGroupware framework
|
* @summary Accessable, webComponent-based EGroupware framework
|
||||||
@ -94,9 +96,9 @@ export class EgwFramework extends LitElement
|
|||||||
@property({type: Array, attribute: "application-list"})
|
@property({type: Array, attribute: "application-list"})
|
||||||
applicationList = [];
|
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
|
// Dummy egw so we don't get failures from missing methods
|
||||||
lang: (t) => t,
|
lang: (t) => t,
|
||||||
preference: (n, app, promise? : Function | boolean | undefined) => Promise.resolve(""),
|
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
|
* An application tab is chosen, show the app
|
||||||
*
|
*
|
||||||
* @param e
|
* @param e
|
||||||
* @protected
|
* @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)
|
protected _applicationTabTemplate(app)
|
||||||
{
|
{
|
||||||
return html`
|
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>
|
<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>
|
||||||
@ -167,8 +257,10 @@ export class EgwFramework extends LitElement
|
|||||||
</sl-dropdown>
|
</sl-dropdown>
|
||||||
<sl-tab-group part="open-applications" class="egw_fw__open_applications" activation="manual"
|
<sl-tab-group part="open-applications" class="egw_fw__open_applications" activation="manual"
|
||||||
role="tablist"
|
role="tablist"
|
||||||
@sl-tab-show=${this.handleApplicationTabShow}>
|
@sl-tab-show=${this.handleApplicationTabShow}
|
||||||
${repeat(this.applicationList.filter(app => app.opened), (app) => this._applicationTabTemplate(app))}
|
@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>
|
</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>
|
||||||
|
@ -88,6 +88,9 @@ export class EgwFrameworkApp extends LitElement
|
|||||||
@property()
|
@property()
|
||||||
name = "Application name";
|
name = "Application name";
|
||||||
|
|
||||||
|
@property()
|
||||||
|
url = "";
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
leftCollapsed = false;
|
leftCollapsed = false;
|
||||||
|
|
||||||
@ -122,6 +125,8 @@ export class EgwFrameworkApp extends LitElement
|
|||||||
};
|
};
|
||||||
private resizeTimeout : number;
|
private resizeTimeout : number;
|
||||||
|
|
||||||
|
protected loadingPromise = Promise.resolve();
|
||||||
|
|
||||||
connectedCallback()
|
connectedCallback()
|
||||||
{
|
{
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
@ -133,6 +138,64 @@ 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()
|
||||||
|
{
|
||||||
|
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()
|
public showLeft()
|
||||||
@ -174,6 +237,11 @@ export class EgwFrameworkApp extends LitElement
|
|||||||
return window.egw ?? (<EgwFramework>this.parentElement).egw ?? null;
|
return window.egw ?? (<EgwFramework>this.parentElement).egw ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get framework() : EgwFramework
|
||||||
|
{
|
||||||
|
return this.closest("egw-framework");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User adjusted side slider, update preference
|
* 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()
|
render()
|
||||||
{
|
{
|
||||||
const hasLeftSlots = this.hasSlotController.test('left-header') || this.hasSlotController.test('left') || this.hasSlotController.test('left-footer');
|
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 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 :
|
const leftWidth = this.leftCollapsed || !hasLeftSlots ? this.leftPanelInfo.hiddenWidth :
|
||||||
this.leftPanelInfo.preferenceWidth;
|
this.leftPanelInfo.preferenceWidth;
|
||||||
const rightWidth = this.rightCollapsed || !hasRightSlots ? this.rightPanelInfo.hiddenWidth :
|
const rightWidth = this.rightCollapsed || !hasRightSlots ? this.rightPanelInfo.hiddenWidth :
|
||||||
@ -268,18 +348,7 @@ export class EgwFrameworkApp extends LitElement
|
|||||||
this.leftCollapsed = !this.leftCollapsed;
|
this.leftCollapsed = !this.leftCollapsed;
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}}></sl-icon>
|
}}></sl-icon>
|
||||||
<aside slot="start" part="left" class=${leftClassMap}>
|
${this._asideTemplate("start", "left")}
|
||||||
<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>
|
|
||||||
<sl-split-panel slot="end"
|
<sl-split-panel slot="end"
|
||||||
class=${classMap({"egw_fw_app__innerSplit": true, "no-content": !hasRightSlots})}
|
class=${classMap({"egw_fw_app__innerSplit": true, "no-content": !hasRightSlots})}
|
||||||
primary="start"
|
primary="start"
|
||||||
@ -303,18 +372,7 @@ export class EgwFrameworkApp extends LitElement
|
|||||||
<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>
|
||||||
</footer>
|
</footer>
|
||||||
<aside slot="end" class=${rightClassMap} part="right"
|
${this._asideTemplate("end", "right", this.egw.lang("%1 application details", this.egw.lang(this.name)))}
|
||||||
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>
|
|
||||||
</sl-split-panel>
|
</sl-split-panel>
|
||||||
</sl-split-panel>
|
</sl-split-panel>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,12 +2,21 @@
|
|||||||
* app.ts is auto-built
|
* app.ts is auto-built
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./EgwFramework";
|
import {EgwFramework} from "./EgwFramework";
|
||||||
import "./EgwFrameworkApp";
|
import {EgwFrameworkApp} from "./EgwFrameworkApp";
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () =>
|
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 */
|
/* Set up listener on avatar menu */
|
||||||
const avatarMenu = document.querySelector("#topmenu_info_user_avatar");
|
const avatarMenu = document.querySelector("#topmenu_info_user_avatar");
|
||||||
avatarMenu.addEventListener("sl-select", (e : CustomEvent) =>
|
avatarMenu.addEventListener("sl-select", (e : CustomEvent) =>
|
||||||
|
Loading…
Reference in New Issue
Block a user