saasufy-components 1.0.6 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/collection-adder.js +5 -4
- package/collection-browser.js +25 -19
- package/collection-router.js +156 -0
- package/model-input.js +14 -5
- package/model-text.js +16 -6
- package/package.json +1 -1
- package/socket.js +95 -7
- package/static-router.js +94 -0
- package/utils.js +23 -0
package/collection-adder.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SocketConsumer } from './socket.js';
|
|
2
2
|
import AGCollection from '/node_modules/ag-collection/ag-collection.js';
|
|
3
3
|
|
|
4
|
-
class CollectionAdder extends
|
|
4
|
+
class CollectionAdder extends SocketConsumer {
|
|
5
5
|
constructor(options) {
|
|
6
6
|
super();
|
|
7
|
-
options = options || {};
|
|
8
|
-
|
|
7
|
+
this.options = options || {};
|
|
8
|
+
|
|
9
9
|
this.typeCastFunctions = {
|
|
10
10
|
text: String,
|
|
11
11
|
checkbox: Boolean,
|
|
@@ -16,6 +16,7 @@ class CollectionAdder extends HTMLElement {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
connectedCallback() {
|
|
19
|
+
this.socket = this.getSocket();
|
|
19
20
|
this.isReady = true;
|
|
20
21
|
this.render();
|
|
21
22
|
}
|
package/collection-browser.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SocketConsumer } from './socket.js';
|
|
2
2
|
import AGCollection from '/node_modules/ag-collection/ag-collection.js';
|
|
3
|
-
import {
|
|
3
|
+
import { renderTemplate } from './utils.js';
|
|
4
4
|
|
|
5
5
|
const DEFAULT_RELOAD_DELAY = 0;
|
|
6
6
|
|
|
7
|
-
class CollectionBrowser extends
|
|
7
|
+
class CollectionBrowser extends SocketConsumer {
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
10
|
this.attachShadow({ mode: 'open' });
|
|
11
|
-
this.socket = getSocket();
|
|
12
11
|
|
|
13
12
|
this.handleShowModalEvent = (event) => {
|
|
14
13
|
let modal = this.shadowRoot.querySelector('slot[name="modal"]').assignedNodes()[0];
|
|
@@ -40,12 +39,13 @@ class CollectionBrowser extends HTMLElement {
|
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
this.handleSlotChangeEvent = () => {
|
|
43
|
-
this.
|
|
42
|
+
this.renderList();
|
|
44
43
|
};
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
connectedCallback() {
|
|
48
47
|
this.isReady = true;
|
|
48
|
+
this.socket = this.getSocket();
|
|
49
49
|
this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
|
|
50
50
|
this.addEventListener('showModal', this.handleShowModalEvent);
|
|
51
51
|
this.addEventListener('crudCreate', this.handleCRUDCreateEvent);
|
|
@@ -146,28 +146,23 @@ class CollectionBrowser extends HTMLElement {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
renderList() {
|
|
150
150
|
if (!this.collection || !this.collection.isLoaded) return;
|
|
151
151
|
let itemTemplate = this.shadowRoot.querySelector('slot[name="item"]').assignedNodes()[0];
|
|
152
152
|
if (!itemTemplate) return;
|
|
153
|
-
let
|
|
154
|
-
if (!
|
|
153
|
+
let viewportNode = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
|
|
154
|
+
if (!viewportNode) return;
|
|
155
155
|
let items = [];
|
|
156
156
|
|
|
157
157
|
for (let modelItem of this.collection.value) {
|
|
158
|
-
let itemString = itemTemplate.innerHTML;
|
|
159
|
-
|
|
160
|
-
for (let [ field, value ] of Object.entries(modelItem)) {
|
|
161
|
-
let regExp = new RegExp(`{{${field}}}`, 'g');
|
|
162
|
-
itemString = itemString.replace(regExp, getSafeHTML(value));
|
|
163
|
-
}
|
|
158
|
+
let itemString = renderTemplate(itemTemplate.innerHTML, this.collection.type, modelItem);
|
|
164
159
|
items.push(itemString);
|
|
165
160
|
}
|
|
166
161
|
let noItemTemplate = this.shadowRoot.querySelector('slot[name="no-item"]').assignedNodes()[0];
|
|
167
162
|
if (noItemTemplate && !items.length) {
|
|
168
|
-
|
|
163
|
+
viewportNode.innerHTML = noItemTemplate.innerHTML;
|
|
169
164
|
} else {
|
|
170
|
-
|
|
165
|
+
viewportNode.innerHTML = items.join('');
|
|
171
166
|
}
|
|
172
167
|
}
|
|
173
168
|
|
|
@@ -178,6 +173,7 @@ class CollectionBrowser extends HTMLElement {
|
|
|
178
173
|
let collectionPageSize = this.getAttribute('collection-page-size');
|
|
179
174
|
let collectionViewParams = this.getAttribute('collection-view-params');
|
|
180
175
|
let collectionPageOffset = this.getAttribute('collection-page-offset');
|
|
176
|
+
let hideErrorLogs = this.hasAttribute('hide-error-logs');
|
|
181
177
|
let collectionReloadDelay = Number(
|
|
182
178
|
this.getAttribute('collection-reload-delay') || DEFAULT_RELOAD_DELAY
|
|
183
179
|
);
|
|
@@ -198,7 +194,7 @@ class CollectionBrowser extends HTMLElement {
|
|
|
198
194
|
this.shadowRoot.innerHTML = `
|
|
199
195
|
<slot name="item"></slot>
|
|
200
196
|
<slot name="no-item"></slot>
|
|
201
|
-
<slot name="
|
|
197
|
+
<slot name="viewport"></slot>
|
|
202
198
|
<slot name="previous-page"></slot>
|
|
203
199
|
<slot name="page-number"></slot>
|
|
204
200
|
<slot name="next-page"></slot>
|
|
@@ -221,7 +217,7 @@ class CollectionBrowser extends HTMLElement {
|
|
|
221
217
|
(async () => {
|
|
222
218
|
await this.collection.listener('load').once();
|
|
223
219
|
if (!this.collection.value.length) {
|
|
224
|
-
this.
|
|
220
|
+
this.renderList();
|
|
225
221
|
}
|
|
226
222
|
this.setAttribute('is-loaded', '');
|
|
227
223
|
this.dispatchEvent(new CustomEvent('load', { bubbles: true }));
|
|
@@ -229,10 +225,20 @@ class CollectionBrowser extends HTMLElement {
|
|
|
229
225
|
|
|
230
226
|
(async () => {
|
|
231
227
|
for await (let event of this.collection.listener('change')) {
|
|
232
|
-
this.
|
|
228
|
+
this.renderList();
|
|
233
229
|
this.updatePageButtons();
|
|
234
230
|
}
|
|
235
231
|
})();
|
|
232
|
+
|
|
233
|
+
if (!hideErrorLogs) {
|
|
234
|
+
(async () => {
|
|
235
|
+
for await (let { error } of this.collection.listener('error')) {
|
|
236
|
+
console.error(
|
|
237
|
+
`Collection browser encountered an error: ${error.message}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
})();
|
|
241
|
+
}
|
|
236
242
|
}
|
|
237
243
|
}
|
|
238
244
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { SocketConsumer } from './socket.js';
|
|
2
|
+
import AGCollection from '/node_modules/ag-collection/ag-collection.js';
|
|
3
|
+
import { renderTemplate } from './utils.js';
|
|
4
|
+
import './static-router.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_RELOAD_DELAY = 0;
|
|
7
|
+
|
|
8
|
+
export class CollectionRouter extends SocketConsumer {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.attachShadow({ mode: 'open' });
|
|
12
|
+
this.pages = {};
|
|
13
|
+
|
|
14
|
+
this.handleSlotChangeEvent = () => {
|
|
15
|
+
let pageTemplates = this.shadowRoot.querySelector('slot[name="page"]').assignedNodes();
|
|
16
|
+
|
|
17
|
+
for (let template of pageTemplates) {
|
|
18
|
+
let routePath = template.getAttribute('route-path');
|
|
19
|
+
this.pages[routePath] = template;
|
|
20
|
+
}
|
|
21
|
+
if (this.collection && this.collection.isLoaded) {
|
|
22
|
+
this.renderRoutes();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
connectedCallback() {
|
|
28
|
+
this.isReady = true;
|
|
29
|
+
this.socket = this.getSocket();
|
|
30
|
+
this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
|
|
31
|
+
this.render();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
disconnectedCallback() {
|
|
35
|
+
this.shadowRoot.removeEventListener('slotchange', this.handleSlotChangeEvent);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static get observedAttributes() {
|
|
39
|
+
return [
|
|
40
|
+
'collection-type',
|
|
41
|
+
'collection-fields',
|
|
42
|
+
'collection-view',
|
|
43
|
+
'collection-view-params',
|
|
44
|
+
'collection-page-size',
|
|
45
|
+
'collection-page-offset',
|
|
46
|
+
'hide-route-logs',
|
|
47
|
+
'hide-error-logs'
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
52
|
+
if (!this.isReady) return;
|
|
53
|
+
if (this.collection && name === 'collection-page-offset') {
|
|
54
|
+
let newOffset = Number(newValue);
|
|
55
|
+
if (newOffset !== this.collection.meta.pageOffset) {
|
|
56
|
+
this.collection.fetchPage(newOffset);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
this.render();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
renderRoutes() {
|
|
64
|
+
let hideRouteLogs = this.hasAttribute('hide-route-logs');
|
|
65
|
+
let viewport = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
|
|
66
|
+
if (!viewport) return;
|
|
67
|
+
|
|
68
|
+
let pageEntries = Object.entries(this.pages);
|
|
69
|
+
if (!pageEntries.length) return;
|
|
70
|
+
|
|
71
|
+
let routesList = [];
|
|
72
|
+
|
|
73
|
+
viewport.innerHTML = `
|
|
74
|
+
<static-router>
|
|
75
|
+
${
|
|
76
|
+
pageEntries.map(
|
|
77
|
+
([ route, template ]) => {
|
|
78
|
+
let routePath = template.getAttribute('route-path');
|
|
79
|
+
let pageTemplates = this.collection.value.map(
|
|
80
|
+
(modelItem) => {
|
|
81
|
+
let pagePath = renderTemplate(routePath, this.collection.type, modelItem);
|
|
82
|
+
let templateHTML = renderTemplate(template.innerHTML, this.collection.type, modelItem);
|
|
83
|
+
routesList.push(pagePath);
|
|
84
|
+
return `<template slot="page" route-path="${pagePath}">${templateHTML}</template>`
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
return pageTemplates.join('');
|
|
88
|
+
}
|
|
89
|
+
).join('')
|
|
90
|
+
}
|
|
91
|
+
<div slot="viewport"></div>
|
|
92
|
+
</static-router>
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
if (!hideRouteLogs) console.log(`The following routes have been registered: ${routesList.join(', ')}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
render() {
|
|
99
|
+
let collectionType = this.getAttribute('collection-type');
|
|
100
|
+
let collectionFields = this.getAttribute('collection-fields');
|
|
101
|
+
let collectionView = this.getAttribute('collection-view');
|
|
102
|
+
let collectionPageSize = this.getAttribute('collection-page-size');
|
|
103
|
+
let collectionViewParams = this.getAttribute('collection-view-params');
|
|
104
|
+
let collectionPageOffset = this.getAttribute('collection-page-offset');
|
|
105
|
+
let hideErrorLogs = this.hasAttribute('hide-error-logs');
|
|
106
|
+
let collectionReloadDelay = Number(
|
|
107
|
+
this.getAttribute('collection-reload-delay') || DEFAULT_RELOAD_DELAY
|
|
108
|
+
);
|
|
109
|
+
let viewParams = Object.fromEntries(collectionViewParams.split(',').map((pair) => pair.split('=')));
|
|
110
|
+
if (this.collection) this.collection.destroy();
|
|
111
|
+
|
|
112
|
+
this.collection = new AGCollection({
|
|
113
|
+
socket: this.socket,
|
|
114
|
+
type: collectionType,
|
|
115
|
+
fields: collectionFields.split(',').map(field => field.trim()),
|
|
116
|
+
view: collectionView,
|
|
117
|
+
viewParams,
|
|
118
|
+
pageSize: Number(collectionPageSize || 10),
|
|
119
|
+
pageOffset: Number(collectionPageOffset || 0),
|
|
120
|
+
changeReloadDelay: collectionReloadDelay
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.shadowRoot.innerHTML = `
|
|
124
|
+
<slot name="page"></slot>
|
|
125
|
+
<slot name="viewport"></slot>
|
|
126
|
+
`;
|
|
127
|
+
|
|
128
|
+
(async () => {
|
|
129
|
+
await this.collection.listener('load').once();
|
|
130
|
+
if (!this.collection.value.length) {
|
|
131
|
+
this.renderRoutes();
|
|
132
|
+
}
|
|
133
|
+
this.setAttribute('is-loaded', '');
|
|
134
|
+
this.dispatchEvent(new CustomEvent('load', { bubbles: true }));
|
|
135
|
+
})();
|
|
136
|
+
|
|
137
|
+
(async () => {
|
|
138
|
+
for await (let event of this.collection.listener('change')) {
|
|
139
|
+
if (!this.collection.isLoaded) continue;
|
|
140
|
+
this.renderRoutes();
|
|
141
|
+
}
|
|
142
|
+
})();
|
|
143
|
+
|
|
144
|
+
if (!hideErrorLogs) {
|
|
145
|
+
(async () => {
|
|
146
|
+
for await (let { error } of this.collection.listener('error')) {
|
|
147
|
+
console.error(
|
|
148
|
+
`Collection router encountered an error: ${error.message}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
})();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
window.customElements.define('collection-router', CollectionRouter);
|
package/model-input.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SocketConsumer } from './socket.js';
|
|
2
2
|
import { getSafeHTML, debouncer } from './utils.js';
|
|
3
3
|
import AGModel from '/node_modules/ag-model/ag-model.js';
|
|
4
4
|
|
|
5
|
-
class ModelInput extends
|
|
5
|
+
class ModelInput extends SocketConsumer {
|
|
6
6
|
constructor() {
|
|
7
7
|
super();
|
|
8
8
|
this.isReady = false;
|
|
9
9
|
this.inputElement = null;
|
|
10
|
-
this.socket = null;
|
|
11
10
|
}
|
|
12
11
|
connectedCallback() {
|
|
13
12
|
this.isReady = true;
|
|
@@ -73,6 +72,7 @@ class ModelInput extends HTMLElement {
|
|
|
73
72
|
let modelField = this.getAttribute('model-field');
|
|
74
73
|
let options = this.getAttribute('options');
|
|
75
74
|
let height = this.getAttribute('height');
|
|
75
|
+
let hideErrorLogs = this.hasAttribute('hide-error-logs');
|
|
76
76
|
let currentNode = this.parentNode;
|
|
77
77
|
let model;
|
|
78
78
|
let isModelLocal = false;
|
|
@@ -80,17 +80,26 @@ class ModelInput extends HTMLElement {
|
|
|
80
80
|
while (currentNode) {
|
|
81
81
|
model = currentNode[modelInstanceProperty];
|
|
82
82
|
if (model) break;
|
|
83
|
-
currentNode = currentNode.parentNode;
|
|
83
|
+
currentNode = currentNode.getRootNode().host || currentNode.parentNode;
|
|
84
84
|
}
|
|
85
85
|
if (!model) return;
|
|
86
86
|
} else {
|
|
87
|
-
this.socket = getSocket();
|
|
87
|
+
this.socket = this.getSocket();
|
|
88
88
|
model = new AGModel({
|
|
89
89
|
socket: this.socket,
|
|
90
90
|
type: modelType,
|
|
91
91
|
id: modelId,
|
|
92
92
|
fields: [ modelField ]
|
|
93
93
|
});
|
|
94
|
+
if (!hideErrorLogs) {
|
|
95
|
+
(async () => {
|
|
96
|
+
for await (let { error } of model.listener('error')) {
|
|
97
|
+
console.error(
|
|
98
|
+
`Model input encountered an error: ${error.message}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
})();
|
|
102
|
+
}
|
|
94
103
|
isModelLocal = true;
|
|
95
104
|
}
|
|
96
105
|
if (model.isLoaded) {
|
package/model-text.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SocketConsumer } from './socket.js';
|
|
2
2
|
import { getSafeHTML } from './utils.js';
|
|
3
|
+
import AGModel from '/node_modules/ag-model/ag-model.js';
|
|
3
4
|
|
|
4
|
-
class ModelText extends
|
|
5
|
+
class ModelText extends SocketConsumer {
|
|
5
6
|
connectedCallback() {
|
|
6
7
|
this.isReady = true;
|
|
7
8
|
this.destroy = () => {};
|
|
8
|
-
this.socket = null;
|
|
9
9
|
this.render();
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -34,6 +34,7 @@ class ModelText extends HTMLElement {
|
|
|
34
34
|
let modelType = this.getAttribute('model-type');
|
|
35
35
|
let modelId = this.getAttribute('model-id');
|
|
36
36
|
let modelField = this.getAttribute('model-field');
|
|
37
|
+
let hideErrorLogs = this.hasAttribute('hide-error-logs');
|
|
37
38
|
let currentNode = this.parentNode;
|
|
38
39
|
let model;
|
|
39
40
|
let isModelLocal = false;
|
|
@@ -41,17 +42,26 @@ class ModelText extends HTMLElement {
|
|
|
41
42
|
while (currentNode) {
|
|
42
43
|
model = currentNode[modelInstanceProperty];
|
|
43
44
|
if (model) break;
|
|
44
|
-
currentNode = currentNode.parentNode;
|
|
45
|
+
currentNode = currentNode.getRootNode().host || currentNode.parentNode;
|
|
45
46
|
}
|
|
46
47
|
if (!model) return;
|
|
47
48
|
} else {
|
|
48
|
-
this.socket = getSocket();
|
|
49
|
+
this.socket = this.getSocket();
|
|
49
50
|
model = new AGModel({
|
|
50
51
|
socket: this.socket,
|
|
51
52
|
type: modelType,
|
|
52
53
|
id: modelId,
|
|
53
54
|
fields: [ modelField ]
|
|
54
55
|
});
|
|
56
|
+
if (!hideErrorLogs) {
|
|
57
|
+
(async () => {
|
|
58
|
+
for await (let { error } of model.listener('error')) {
|
|
59
|
+
console.error(
|
|
60
|
+
`Model input encountered an error: ${error.message}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
}
|
|
55
65
|
isModelLocal = true;
|
|
56
66
|
}
|
|
57
67
|
if (model.isLoaded) {
|
|
@@ -69,7 +79,7 @@ class ModelText extends HTMLElement {
|
|
|
69
79
|
(async () => {
|
|
70
80
|
for await (let event of changeConsumer) {
|
|
71
81
|
if (event.resourceField !== modelField || !event.isRemote) continue;
|
|
72
|
-
fieldValue = getSafeHTML(model.value[modelField]);
|
|
82
|
+
let fieldValue = getSafeHTML(model.value[modelField]);
|
|
73
83
|
this.innerHTML = fieldValue;
|
|
74
84
|
}
|
|
75
85
|
})();
|
package/package.json
CHANGED
package/socket.js
CHANGED
|
@@ -2,19 +2,107 @@ import { create } from '/node_modules/socketcluster-client/socketcluster-client.
|
|
|
2
2
|
|
|
3
3
|
const DEFAULT_AUTH_TOKEN_NAME = 'saasufy.authToken';
|
|
4
4
|
|
|
5
|
-
let
|
|
5
|
+
let globalSocket;
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
let urlPartsRegExp = /(^[^:]+):\/\/([^:\/]*)(:[0-9]*)?(\/.*)/;
|
|
8
|
+
|
|
9
|
+
export function createGlobalSocket(options) {
|
|
10
|
+
globalSocket = create({
|
|
9
11
|
authTokenName: DEFAULT_AUTH_TOKEN_NAME,
|
|
10
12
|
...options
|
|
11
13
|
});
|
|
12
|
-
return
|
|
14
|
+
return globalSocket;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getGlobalSocket(options) {
|
|
18
|
+
if (globalSocket) {
|
|
19
|
+
return globalSocket;
|
|
20
|
+
}
|
|
21
|
+
return createGlobalSocket(options);
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
export
|
|
16
|
-
|
|
24
|
+
export class SocketProvider extends HTMLElement {
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this.saasufySocket = null;
|
|
28
|
+
this.isReady = false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
connectedCallback() {
|
|
32
|
+
this.isReady = true;
|
|
33
|
+
this.getSocket();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
disconnectedCallback() {
|
|
37
|
+
this.destroySocket();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static get observedAttributes() {
|
|
41
|
+
return [
|
|
42
|
+
'url',
|
|
43
|
+
'auth-token-name'
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
48
|
+
if (!this.isReady) return;
|
|
49
|
+
this.destroySocket();
|
|
50
|
+
this.getSocket();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
destroySocket() {
|
|
54
|
+
if (this.saasufySocket) {
|
|
55
|
+
this.saasufySocket.disconnect();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getSocket() {
|
|
60
|
+
if (this.saasufySocket) {
|
|
61
|
+
return this.saasufySocket;
|
|
62
|
+
}
|
|
63
|
+
return this.createSocket();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
createSocket() {
|
|
67
|
+
let authTokenName = this.getAttribute('auth-token-name') || DEFAULT_AUTH_TOKEN_NAME;
|
|
68
|
+
let url = this.getAttribute('url') || '';
|
|
69
|
+
let urlOptions;
|
|
70
|
+
let matchedList = url.match(urlPartsRegExp);
|
|
71
|
+
if (matchedList) {
|
|
72
|
+
let [ fullMatch, protocolScheme, hostname, port, path ] = matchedList;
|
|
73
|
+
if (port) {
|
|
74
|
+
port = port.replace(/^:/, '');
|
|
75
|
+
}
|
|
76
|
+
urlOptions = {
|
|
77
|
+
protocolScheme,
|
|
78
|
+
hostname,
|
|
79
|
+
port,
|
|
80
|
+
path
|
|
81
|
+
};
|
|
82
|
+
} else {
|
|
83
|
+
urlOptions = {};
|
|
84
|
+
}
|
|
85
|
+
this.saasufySocket = create({
|
|
86
|
+
authTokenName,
|
|
87
|
+
...urlOptions
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return this.saasufySocket;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class SocketConsumer extends HTMLElement {
|
|
95
|
+
getSocket() {
|
|
96
|
+
let socket = null;
|
|
97
|
+
let currentNode = this.parentNode;
|
|
98
|
+
while (currentNode) {
|
|
99
|
+
socket = currentNode.saasufySocket;
|
|
100
|
+
if (socket) break;
|
|
101
|
+
currentNode = currentNode.getRootNode().host || currentNode.parentNode;
|
|
102
|
+
}
|
|
17
103
|
return socket;
|
|
18
104
|
}
|
|
19
|
-
return createSocket(options);
|
|
20
105
|
}
|
|
106
|
+
|
|
107
|
+
window.customElements.define('socket-provider', SocketProvider);
|
|
108
|
+
window.customElements.define('socket-consumer', SocketConsumer);
|
package/static-router.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getSafeHTML } from './utils.js';
|
|
2
|
+
|
|
3
|
+
export class StaticRouter extends HTMLElement {
|
|
4
|
+
constructor() {
|
|
5
|
+
super();
|
|
6
|
+
this.attachShadow({ mode: 'open' });
|
|
7
|
+
|
|
8
|
+
this.pages = {};
|
|
9
|
+
this.pageRouteInfos = [];
|
|
10
|
+
|
|
11
|
+
this.hashStartRegex = /^#/;
|
|
12
|
+
|
|
13
|
+
this.handleHashChangeEvent = () => {
|
|
14
|
+
this.renderCurrentPage();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
this.handleSlotChangeEvent = () => {
|
|
18
|
+
let pageTemplates = this.shadowRoot.querySelector('slot[name="page"]').assignedNodes();
|
|
19
|
+
|
|
20
|
+
for (let template of pageTemplates) {
|
|
21
|
+
let routePath = template.getAttribute('route-path');
|
|
22
|
+
this.pages[routePath] = template;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.pageRouteInfos = Object.keys(this.pages).map((route) => {
|
|
26
|
+
return {
|
|
27
|
+
regExp: new RegExp(`^${route.replace(/\/:[^\/]+/g, '/([^/]*)').replace(/\//g, '\\/')}$`, 'g'),
|
|
28
|
+
route,
|
|
29
|
+
params: [ ...route.matchAll(/:[^\/]*/g) ].map(paramMatch => paramMatch[0].replace(':', ''))
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
this.renderCurrentPage();
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
computeRouteArgs(page, regExp, params) {
|
|
38
|
+
let match = page.matchAll(regExp).next().value || [];
|
|
39
|
+
let argEntries = match
|
|
40
|
+
.slice(1)
|
|
41
|
+
.map((groupMatch, i) => [ params[i], groupMatch ]);
|
|
42
|
+
return Object.fromEntries(argEntries);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
connectedCallback() {
|
|
46
|
+
window.addEventListener('hashchange', this.handleHashChangeEvent);
|
|
47
|
+
this.shadowRoot.addEventListener('slotchange', this.handleSlotChangeEvent);
|
|
48
|
+
this.render();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
disconnectedCallback() {
|
|
52
|
+
window.removeEventListener('hashchange', this.handleHashChangeEvent);
|
|
53
|
+
this.shadowRoot.removeEventListener('slotchange', this.handleSlotChangeEvent);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
renderCurrentPage() {
|
|
57
|
+
let routerViewport = this.shadowRoot.querySelector('slot[name="viewport"]').assignedNodes()[0];
|
|
58
|
+
if (!routerViewport) return;
|
|
59
|
+
|
|
60
|
+
let { hash } = location;
|
|
61
|
+
let pagePath = hash.replace(this.hashStartRegex, '');
|
|
62
|
+
|
|
63
|
+
let { route, regExp, params } = this.pageRouteInfos.find(
|
|
64
|
+
({ regExp }) => pagePath.match(regExp)
|
|
65
|
+
) || {};
|
|
66
|
+
|
|
67
|
+
let pageTemplate = this.pages[route];
|
|
68
|
+
if (!pageTemplate) return;
|
|
69
|
+
|
|
70
|
+
let routeArgs;
|
|
71
|
+
if (route) {
|
|
72
|
+
routeArgs = this.computeRouteArgs(pagePath, regExp, params);
|
|
73
|
+
} else {
|
|
74
|
+
routeArgs = {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let templateHTML = pageTemplate.innerHTML;
|
|
78
|
+
for (let [ key, value ] of Object.entries(routeArgs)) {
|
|
79
|
+
let regExp = new RegExp(`{{${key}}}`, 'g');
|
|
80
|
+
templateHTML = templateHTML.replace(regExp, getSafeHTML(value));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
routerViewport.innerHTML = templateHTML;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
render() {
|
|
87
|
+
this.shadowRoot.innerHTML = `
|
|
88
|
+
<slot name="page"></slot>
|
|
89
|
+
<slot name="viewport"></slot>
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
window.customElements.define('static-router', StaticRouter);
|
package/utils.js
CHANGED
|
@@ -155,6 +155,29 @@ export function createModel(modelOptions) {
|
|
|
155
155
|
return model;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
let templateFormatters = {
|
|
159
|
+
url: (value) => String(value).toLowerCase().replace(/ /g, '-'),
|
|
160
|
+
lowerCase: (value) => String(value).toLowerCase(),
|
|
161
|
+
upperCase: (value) => String(value).toUpperCase(),
|
|
162
|
+
capitalize: (value) => {
|
|
163
|
+
let valueString = String(value);
|
|
164
|
+
return `${valueString.slice(0, 1).toUpperCase()}${valueString.slice(1)}`;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export function renderTemplate(templateString, dataType, data) {
|
|
169
|
+
for (let [ field, value ] of Object.entries(data)) {
|
|
170
|
+
let safeValue = getSafeHTML(value);
|
|
171
|
+
for (let [ formatName, formatFunction ] of Object.entries(templateFormatters)) {
|
|
172
|
+
let formatRegExp = new RegExp(`{{${formatName}:${dataType}.${field}}}`, 'g');
|
|
173
|
+
templateString = templateString.replace(formatRegExp, formatFunction(safeValue));
|
|
174
|
+
}
|
|
175
|
+
let regExp = new RegExp(`{{${dataType}.${field}}}`, 'g');
|
|
176
|
+
templateString = templateString.replace(regExp, safeValue);
|
|
177
|
+
}
|
|
178
|
+
return templateString;
|
|
179
|
+
}
|
|
180
|
+
|
|
158
181
|
export function wait(duration) {
|
|
159
182
|
return new Promise((resolve) => setTimeout(resolve, duration));
|
|
160
183
|
}
|