Framework WIP:

- Some work on taking over framework duties
This commit is contained in:
nathan 2024-05-14 08:17:06 -06:00
parent 24e265ef6e
commit 4271b5c72c
3 changed files with 283 additions and 11 deletions

View File

@ -53,6 +53,11 @@ class kdots_framework extends Api\Framework\Ajax
$data['open_app_name'] = $open_app['name'];
$data['open_app_url'] = $open_app['url'];
}
if($data['open_app_name'] && !$this->sidebox_done)
{
$this->do_sidebox();
$data['setSidebox'] = htmlentities(json_encode(static::$extra['setSidebox'], JSON_HEX_QUOT | JSON_HEX_AMP), ENT_QUOTES, 'UTF-8');
}
return $data;
}

View File

@ -103,6 +103,21 @@ export class EgwFramework extends LitElement
private get tabs() : SlTabGroup { return this.shadowRoot.querySelector("sl-tab-group");}
connectedCallback()
{
super.connectedCallback();
if(this.egw.window && this.egw.window.opener == null && !this.egw.window.framework)
{
// This works, but stops a lot else from working
//this.egw.window.framework = this;
}
if(this.egw.window?.framework && this.egw.window?.framework !== this)
{
// Override framework setSidebox, use arrow function to force context
this.egw.framework.setSidebox = (applicationName, sideboxData, hash?) => this.setSidebox(applicationName, sideboxData, hash);
}
}
get egw() : typeof egw
{
return window.egw ?? <typeof egw>{
@ -138,20 +153,44 @@ export class EgwFramework extends LitElement
(menuaction ? '.' + menuaction[1] : '');
};
public loadApp(appname, active = false)
/**
* Load an application into the framework
*
* Loading is done by name, and we look up everything we need in the applicationList
*
* @param {string} appname
* @param {boolean} active
* @param {string} url
* @returns {EgwFrameworkApp}
*/
public loadApp(appname : string, active = false, url = null) : EgwFrameworkApp
{
const existing : EgwFrameworkApp = this.querySelector(`egw-app[name="${appname}"]`);
if(existing)
{
if(active)
{
this.tabs.show(appname);
}
if(url)
{
existing.url = url;
}
return existing;
}
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;
appComponent.url = url ?? app?.url;
this.append(appComponent);
// 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
@ -166,6 +205,110 @@ export class EgwFramework extends LitElement
return appComponent;
}
/**
* Load a link into the framework
*
* @param {string} _link
* @param {string} _app
* @returns {undefined}
*/
public linkHandler(_link : string, _app : string)
{
//Determine the app string from the application parameter
let app = null;
if(_app && typeof _app == 'string')
{
app = this.applicationList.find(a => a.name == _app);
}
if(!app)
{
//The app parameter was false or not a string or the application specified did not exists.
//Determine the target application from the link that had been passed to this function
app = this.parseAppFromUrl(_link);
}
if(app)
{
if(_app == '_tab')
{
// add target flag
_link += '&target=_tab';
const appname = app.appName + ":" + btoa(_link);
this.applicationList[appname] = {...app};
this.applicationList[appname]['name'] = appname;
this.applicationList[appname]['indexUrl'] = _link;
this.applicationList[appname]['tab'] = null;
this.applicationList[appname]['browser'] = null;
this.applicationList[appname]['title'] = 'view';
app = this.applicationList[appname];
}
this.loadApp(app.name, true, _link);
}
else
{
//Display some error messages to have visible feedback
if(typeof _app == 'string')
{
egw_alertHandler('Application "' + _app + '" not found.',
'The application "' + _app + '" the link "' + _link + '" points to is not registered.');
}
else
{
egw_alertHandler("No appropriate target application has been found.",
"Target link: " + _link);
}
}
}
/**
* Tries to obtain the application from a menuaction
* @param {string} _url
*/
protected parseAppFromUrl(_url : string)
{
let _app = null;
// Check the menuaction parts from the url
let matches = _url.match(/menuaction=([a-z0-9_-]+)\./i) ||
// Check the url for a scheme of "/app/something.php"
_url.match(/\/([^\/]+)\/[^\/]+\.php/i);
if(matches)
{
// check if this is a regular app-name
_app = this.applicationList.find(a => a.name == matches[1]);
}
return _app;
}
/**
* Print
*/
public async print()
{
const appElement : EgwFrameworkApp = this.querySelector("egw-app[active]");
try
{
if(appElement)
{
await appElement.print();
}
const appWindow = this.egw.window;
appWindow.setTimeout(appWindow.print, 0);
}
catch
{
// Ignore rejected
}
}
public async setSidebox(appname, sideboxData, hash)
{
const app = this.loadApp(appname);
app.setSidebox(sideboxData, hash);
}
protected getBaseUrl() {return "";}
/**

View File

@ -10,6 +10,7 @@ import {SlSplitPanel} from "@shoelace-style/shoelace";
import {HasSlotController} from "../../api/js/etemplate/Et2Widget/slot";
import type {EgwFramework} from "./EgwFramework";
import {etemplate2} from "../../api/js/etemplate/etemplate2";
import {et2_IPrint} from "../../api/js/etemplate/et2_core_interfaces";
/**
* @summary Application component inside EgwFramework
@ -156,6 +157,14 @@ export class EgwFrameworkApp extends LitElement
this.load(this.url);
}
protected async getUpdateComplete() : Promise<boolean>
{
const result = await super.updateComplete;
await this.loadingPromise;
return result
}
protected load(url)
{
if(!url)
@ -231,6 +240,11 @@ export class EgwFrameworkApp extends LitElement
}
}
public setSidebox(sideboxData, hash?)
{
}
public showLeft()
{
this.showSide("left");
@ -251,6 +265,85 @@ export class EgwFrameworkApp extends LitElement
this.hideSide("right");
}
public async print()
{
let template;
let deferred = [];
let et2_list = [];
const appWindow = this.framework.egw.window;
if((template = appWindow.etemplate2.getById(this.id)) && this == template.DOMContainer)
{
deferred = deferred.concat(template.print());
et2_list.push(template);
}
else
{
// et2 inside, let its widgets prepare
this.querySelectorAll(":scope > *").forEach((domNode : HTMLElement) =>
{
let et2 = appWindow.etemplate2.getById(domNode.id);
if(et2 && (domNode.offsetWidth > 0 || domNode.offsetHeight > 0 || domNode.getClientRects().length > 0))
{
deferred = deferred.concat(et2.print());
et2_list.push(et2);
}
});
}
if(et2_list.length)
{
// Try to clean up after - not guaranteed
let afterPrint = () =>
{
this.egw.loading_prompt(this.name, true, this.egw.lang('please wait...'), this, egwIsMobile() ? 'horizental' : 'spinner');
// Give framework a chance to deal, then reset the etemplates
appWindow.setTimeout(function()
{
for(var i = 0; i < et2_list.length; i++)
{
et2_list[i].widgetContainer.iterateOver(function(_widget)
{
_widget.afterPrint();
}, et2_list[i], et2_IPrint);
}
this.egw.loading_prompt(this.name, false);
}, 100);
appWindow.onafterprint = null;
};
/* Not sure what this did, it triggers while preview is still up
if(appWindow.matchMedia)
{
var mediaQueryList = appWindow.matchMedia('print');
var listener = function(mql)
{
if(!mql.matches)
{
mediaQueryList.removeListener(listener);
afterPrint();
}
};
mediaQueryList.addListener(listener);
}
*/
appWindow.addEventListener("afterprint", afterPrint, {once: true});
// Wait for everything to be ready
return Promise.all(deferred).catch((e) =>
{
afterPrint();
if(typeof e == "undefined")
{
throw "rejected";
}
});
}
}
protected showSide(side)
{
const attribute = `${side}Collapsed`;
@ -396,6 +489,44 @@ export class EgwFrameworkApp extends LitElement
</aside>`;
}
/**
* Top right header, contains application action buttons (reload, print, config)
* @returns {TemplateResult<1>}
* @protected
*/
protected _rightHeaderTemplate()
{
return html`
<sl-button-group>
<et2-button-icon nosubmit name="arrow-clockwise"
label=${this.egw.lang("Reload %1", this.egw.lang(this.name))}
statustext=${this.egw.lang("Reload %1", this.egw.lang(this.name))}
@click=${(e) =>
{
this.egw.refresh("", this.name);
/* Could also be this.load(false); this.load(this.url) */
}}
></et2-button-icon>
<et2-button-icon nosubmit name="printer"
label=${this.egw.lang("Print")}
statustext=${this.egw.lang("Print")}
@click=${(e) => this.framework.print()}
></et2-button-icon>
${this.egw.user('apps')['waffles'] !== "undefined" ? html`
<et2-button-icon nosubmit name="gear-wide"
label=${this.egw.lang("Site configuration for %1", this.egw.lang(this.name))}
statustext=${this.egw.lang("App configuration")}
@click=${(e) =>
{
// @ts-ignore
egw_link_handler(`/egroupware/index.php?menuaction=admin.admin_ui.index&load=admin.uiconfig.index&appname=${this.name}&ajax=true`, 'admin');
}}
></et2-button-icon>` : nothing
}
</sl-button-group>
`;
}
render()
{
const hasLeftSlots = this.hasSideContent("left");
@ -426,14 +557,7 @@ export class EgwFrameworkApp extends LitElement
<header class="egw_fw_app__header" part="header">
<slot name="main-header"><span class="placeholder"> ${this.name} main-header</span></slot>
</header>
<sl-button-group>
<sl-icon-button name="arrow-clockwise"
label=${this.egw.lang("Reload %1", this.egw.lang(this.name))}></sl-icon-button>
<sl-icon-button name="printer"
label=${this.egw.lang("Reload %1", this.egw.lang(this.name))}></sl-icon-button>
<sl-icon-button name="gear-wide"
label=${this.egw.lang("Site configuration for %1", this.egw.lang(this.name))}></sl-icon-button>
</sl-button-group>
${this._rightHeaderTemplate()}
</div>
<div class="egw_fw_app__main" part="main">
<sl-split-panel class=${classMap({"egw_fw_app__outerSplit": true, "no-content": !hasLeftSlots})}