Skip to content

Commit a7e28d7

Browse files
author
Dennis Labordus
committed
Merge branch 'main' into optimize-memory-usage
2 parents 424a85c + 9d27cc9 commit a7e28d7

File tree

12 files changed

+430
-52
lines changed

12 files changed

+430
-52
lines changed

__snapshots__/compas-session.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# `compas-session`
2+
3+
## `Dialog when almost expired`
4+
5+
#### `looks like the latest snapshot`
6+
7+
```html
8+
<mwc-dialog
9+
heading="[compas.session.headingExpiring]"
10+
id="compasSessionExpiringDialog"
11+
scrimclickaction=""
12+
>
13+
<div>
14+
[compas.session.explainExpiring]
15+
</div>
16+
<mwc-button
17+
dialogaction="close"
18+
slot="primaryAction"
19+
>
20+
[compas.session.continue]
21+
</mwc-button>
22+
</mwc-dialog>
23+
24+
```
25+
26+
## `Dialog when expired without document`
27+
28+
#### `looks like the latest snapshot`
29+
30+
```html
31+
<mwc-dialog
32+
"=""
33+
escapekeyaction=""
34+
heading="[compas.session.headingExpired]"
35+
id="compasSessionExpiredDialog"
36+
scrimclickaction=""
37+
>
38+
<div>
39+
[compas.session.explainExpiredWithoutProject]
40+
</div>
41+
</mwc-dialog>
42+
43+
```
44+
45+
## `Dialog when expired with document`
46+
47+
#### `looks like the latest snapshot`
48+
49+
```html
50+
<mwc-dialog
51+
"=""
52+
escapekeyaction=""
53+
heading="[compas.session.headingExpired]"
54+
id="compasSessionExpiredDialog"
55+
scrimclickaction=""
56+
>
57+
<div>
58+
[compas.session.explainExpiredWithProject]
59+
</div>
60+
<mwc-button slot="primaryAction">
61+
[compas.session.saveProject]
62+
</mwc-button>
63+
</mwc-dialog>
64+
65+
```
66+

__snapshots__/open-scd.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,10 @@
12031203
Save
12041204
</mwc-button>
12051205
</mwc-dialog>
1206+
<compas-session-expiring-dialog>
1207+
</compas-session-expiring-dialog>
1208+
<compas-session-expired-dialog>
1209+
</compas-session-expired-dialog>
12061210

12071211
```
12081212

public/js/plugins.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ export const officialPlugins = [
1212
icon: 'settings_ethernet',
1313
default: true,
1414
kind: 'editor',
15-
1615
},
1716
{
1817
name: 'Templates',

src/compas-services/CompasUserInfoService.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ export function CompasUserInfoService() {
1414
.catch(handleError)
1515
.then(handleResponse)
1616
.then(parseXml);
17+
},
18+
19+
ping(): Promise<string> {
20+
const pingUrl = getCompasSettings().sclDataServiceUrl + '/q/health/ready';
21+
return fetch(pingUrl)
22+
.catch(handleError)
23+
.then(handleResponse);
1724
}
1825
}
1926
}

src/compas/CompasSession.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import {css, customElement, html, LitElement, property, TemplateResult} from "lit-element";
2+
import {translate, translateUnsafeHTML} from "lit-translate";
3+
import {Dialog} from "@material/mwc-dialog";
4+
5+
import {saveDocumentToFile} from "../file.js";
6+
import {getOpenScdElement} from "./foundation.js";
7+
8+
import {CompasUserInfoService} from "../compas-services/CompasUserInfoService.js";
9+
10+
@customElement('compas-session-expiring-dialog')
11+
export class CompasSessionExpiringDialogElement extends LitElement {
12+
@property({ type: Number })
13+
expiringSessionWarning: number = 10 * 60 * 1000;
14+
@property({ type: Number })
15+
expiredSessionMessage: number = 15 * 60 * 1000;
16+
17+
private expiringSessionWarningTimer: NodeJS.Timeout | null = null;
18+
19+
static getElement(): CompasSessionExpiringDialogElement {
20+
return (<CompasSessionExpiringDialogElement>getOpenScdElement()
21+
.shadowRoot!.querySelector('compas-session-expiring-dialog'));
22+
}
23+
24+
resetTimer(): void {
25+
if (this.expiringSessionWarningTimer) {
26+
clearTimeout(this.expiringSessionWarningTimer);
27+
}
28+
this.expiringSessionWarningTimer = setTimeout(showExpiringSessionWarning, this.expiringSessionWarning);
29+
}
30+
31+
private getDialog(): Dialog {
32+
return <Dialog>this.shadowRoot!.querySelector('mwc-dialog[id="compasSessionExpiringDialog"]');
33+
}
34+
35+
show(): void {
36+
const expiringDialog = this.getDialog();
37+
if (expiringDialog && !expiringDialog.open) {
38+
expiringDialog.show();
39+
}
40+
}
41+
42+
close(): void {
43+
const expiringDialog = this.getDialog();
44+
if (expiringDialog && expiringDialog.open) {
45+
expiringDialog.close();
46+
}
47+
}
48+
49+
render(): TemplateResult {
50+
return html`
51+
<mwc-dialog id="compasSessionExpiringDialog"
52+
heading="${translate('compas.session.headingExpiring')}"
53+
scrimClickAction="">
54+
<div>${translateUnsafeHTML('compas.session.explainExpiring',
55+
{timeTillExpire: ((this.expiredSessionMessage - this.expiringSessionWarning) / 60 / 1000),
56+
expiringSessionWarning: (this.expiringSessionWarning / 60 / 1000)})}</div>
57+
<mwc-button slot="primaryAction" dialogAction="close">
58+
${translate('compas.session.continue')}
59+
</mwc-button>
60+
</mwc-dialog>
61+
`;
62+
}
63+
64+
static styles = css`
65+
#compasSessionExpiringDialog {
66+
--mdc-dialog-max-width: 800px;
67+
}
68+
`
69+
}
70+
71+
@customElement('compas-session-expired-dialog')
72+
export class CompasSessionExpiredDialogElement extends LitElement {
73+
@property({ type: Document })
74+
doc: XMLDocument | null = null;
75+
@property({ type: String })
76+
docName = '';
77+
@property({ type: Number })
78+
expiredSessionMessage: number = 15 * 60 * 1000;
79+
80+
private expiredSessionMessageTimer: NodeJS.Timeout | null = null;
81+
82+
static getElement(): CompasSessionExpiredDialogElement {
83+
return (<CompasSessionExpiredDialogElement>getOpenScdElement()
84+
.shadowRoot!.querySelector('compas-session-expired-dialog'));
85+
}
86+
87+
resetTimer(): void {
88+
if (this.expiredSessionMessageTimer) {
89+
clearTimeout(this.expiredSessionMessageTimer);
90+
}
91+
this.expiredSessionMessageTimer = setTimeout(showExpiredSessionMessage, this.expiredSessionMessage);
92+
}
93+
94+
private getDialog(): Dialog {
95+
return <Dialog>this.shadowRoot!.querySelector('mwc-dialog[id="compasSessionExpiredDialog"]');
96+
}
97+
98+
show(): void {
99+
const expiringDialog = this.getDialog();
100+
if (expiringDialog && !expiringDialog.open) {
101+
expiringDialog.show();
102+
}
103+
}
104+
105+
close(): void {
106+
const expiringDialog = this.getDialog();
107+
if (expiringDialog && expiringDialog.open) {
108+
expiringDialog.close();
109+
}
110+
}
111+
112+
save(): void {
113+
saveDocumentToFile(this.doc, this.docName);
114+
}
115+
116+
render(): TemplateResult {
117+
const expiredSessionMessage = (this.expiredSessionMessage / 60 / 1000);
118+
return html`
119+
<mwc-dialog id="compasSessionExpiredDialog"
120+
heading="${translate('compas.session.headingExpired')}"
121+
scrimClickAction=""
122+
escapeKeyAction=""">
123+
<div>${(this.doc == null) ?
124+
translateUnsafeHTML('compas.session.explainExpiredWithoutProject',
125+
{expiredSessionMessage: expiredSessionMessage}) :
126+
translateUnsafeHTML('compas.session.explainExpiredWithProject',
127+
{expiredSessionMessage: expiredSessionMessage}) }
128+
</div>
129+
${(this.doc != null) ?
130+
html `<mwc-button slot="primaryAction"
131+
@click=${() => this.save()}
132+
?disabled=${this.doc == null}>
133+
${translate('compas.session.saveProject')}
134+
</mwc-button>` :
135+
html `` }
136+
</mwc-dialog>
137+
`;
138+
}
139+
140+
static styles = css`
141+
#compasSessionExpiredDialog {
142+
--mdc-dialog-max-width: 1024px;
143+
}
144+
`
145+
}
146+
147+
export function renderCompasSessionDialogs(doc: Document | null, docName: string): TemplateResult {
148+
return html `
149+
<compas-session-expiring-dialog></compas-session-expiring-dialog>
150+
<compas-session-expired-dialog .doc="${doc}" .docName="${docName}"></compas-session-expired-dialog>
151+
`;
152+
}
153+
154+
let pingTimer: NodeJS.Timeout | null = null;
155+
156+
async function executeKeepAlivePing() {
157+
await CompasUserInfoService().ping()
158+
.finally(() => pingTimer = null)
159+
}
160+
161+
function schedulePing() {
162+
if (!pingTimer) {
163+
// Every minute we will send a Ping to the CoMPAS Services while the user is still active.
164+
// This to keep the connection alive so long the user is working.
165+
pingTimer = setTimeout(executeKeepAlivePing, (60 * 1000));
166+
}
167+
}
168+
169+
function showExpiringSessionWarning() {
170+
CompasSessionExpiringDialogElement.getElement().show();
171+
}
172+
173+
function showExpiredSessionMessage() {
174+
CompasSessionExpiringDialogElement.getElement().close();
175+
CompasSessionExpiredDialogElement.getElement().show();
176+
unregisterEvents();
177+
}
178+
179+
export function resetTimer() {
180+
CompasSessionExpiringDialogElement.getElement().resetTimer();
181+
CompasSessionExpiredDialogElement.getElement().resetTimer();
182+
schedulePing();
183+
}
184+
185+
function unregisterEvents() {
186+
window.removeEventListener('click', resetTimer);
187+
window.removeEventListener('keydown', resetTimer);
188+
}
189+
190+
function registerEvents() {
191+
window.addEventListener('click', resetTimer);
192+
window.addEventListener('keydown', resetTimer);
193+
}
194+
195+
export function setSessionTimeouts(sessionWarning: number, sessionExpires: number): void {
196+
const expiringSessionWarning = sessionWarning * 60 * 1000;
197+
const expiredSessionMessage = sessionExpires * 60 * 1000;
198+
199+
CompasSessionExpiringDialogElement.getElement().expiringSessionWarning = expiringSessionWarning;
200+
CompasSessionExpiringDialogElement.getElement().expiredSessionMessage = expiredSessionMessage;
201+
CompasSessionExpiredDialogElement.getElement().expiredSessionMessage = expiredSessionMessage;
202+
resetTimer();
203+
registerEvents();
204+
}

src/compas/foundation.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {OpenSCD} from "../open-scd.js";
66
import {CompasSclDataService} from "../compas-services/CompasSclDataService.js";
77
import {CompasUserInfoService} from "../compas-services/CompasUserInfoService.js";
88
import {createLogEvent} from "../compas-services/foundation.js";
9+
import {setSessionTimeouts} from "./CompasSession.js";
910

1011
const FILE_EXTENSION_LENGTH = 3;
1112

@@ -45,13 +46,21 @@ export function updateDocumentInOpenSCD(doc: Document): void {
4546
getOpenScdElement().dispatchEvent(newOpenDocEvent(doc, docName, {detail: {docId: id}}));
4647
}
4748

48-
export async function showOptionalUserInfo(): Promise<void> {
49+
export async function retrieveUserInfo(): Promise<void> {
4950
await CompasUserInfoService().getCompasUserInfo()
5051
.then(response => {
5152
const name = response.querySelectorAll("Name").item(0)?.textContent;
52-
if (name != null)
53+
if (name != null) {
5354
getOpenScdElement().dispatchEvent(newUserInfoEvent(name));
55+
}
56+
57+
const sessionWarning = response.querySelectorAll("SessionWarning").item(0)?.textContent??"15";
58+
const sessionExpires = response.querySelectorAll("SessionExpires").item(0)?.textContent??"10";
59+
setSessionTimeouts(parseInt(sessionWarning), parseInt(sessionExpires));
5460
})
55-
.catch(createLogEvent);
61+
.catch(reason => {
62+
createLogEvent(reason);
63+
setSessionTimeouts(10, 15);
64+
});
5665
}
57-
showOptionalUserInfo();
66+
retrieveUserInfo();

src/file.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
function formatXml(xml: string, tab?: string): string {
2+
let formatted = '',
3+
indent = '';
4+
5+
if (!tab) tab = '\t';
6+
xml.split(/>\s*</).forEach(function (node) {
7+
if (node.match(/^\/\w/)) indent = indent.substring(tab!.length);
8+
formatted += indent + '<' + node + '>\r\n';
9+
if (node.match(/^<?\w[^>]*[^/]$/)) indent += tab;
10+
});
11+
return formatted.substring(1, formatted.length - 3);
12+
}
13+
14+
export function saveDocumentToFile(doc: Document | null, docName: string) {
15+
if (doc) {
16+
const blob = new Blob(
17+
[formatXml(new XMLSerializer().serializeToString(doc))],
18+
{
19+
type: 'application/xml',
20+
}
21+
);
22+
23+
const a = document.createElement('a');
24+
a.download = docName;
25+
a.href = URL.createObjectURL(blob);
26+
a.dataset.downloadurl = ['application/xml', a.download, a.href].join(':');
27+
a.style.display = 'none';
28+
document.body.appendChild(a);
29+
a.click();
30+
document.body.removeChild(a);
31+
setTimeout(function () {
32+
URL.revokeObjectURL(a.href);
33+
}, 5000);
34+
}
35+
}

0 commit comments

Comments
 (0)