arrmatura 5.2.0 → 6.2.1
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/index.ts +20 -0
- package/package.json +14 -44
- package/src/core/composition.ts +71 -0
- package/src/core/conditionals.ts +80 -0
- package/src/core/elementary.ts +40 -0
- package/src/core/fragment.ts +21 -0
- package/src/core/index.ts +19 -0
- package/src/core/iterations.ts +121 -0
- package/src/core/root.ts +46 -0
- package/src/core/routing.ts +37 -0
- package/src/core/selection.ts +57 -0
- package/src/core/slot.ts +36 -0
- package/src/ctx/Ctx.ts +424 -0
- package/src/ctx/CtxNode.ts +159 -0
- package/src/utils/compileExpression.ts +126 -0
- package/src/utils/stringify.ts +39 -0
- package/LICENSE +0 -21
- package/README.md +0 -54
- package/commons/auth/AuthService.ts +0 -111
- package/commons/auth/UserService.ts +0 -57
- package/commons/auth/index.ts +0 -5
- package/commons/auth/user.xml +0 -64
- package/commons/components/buttons.xml +0 -25
- package/commons/components/index.ts +0 -13
- package/commons/components/inputs.xml +0 -33
- package/commons/components/layouts.xml +0 -77
- package/commons/components/media.xml +0 -21
- package/commons/components/modal.xml +0 -37
- package/commons/components/navigation.xml +0 -73
- package/commons/components/popups.xml +0 -12
- package/commons/components/searchbar.xml +0 -54
- package/commons/components/table.xml +0 -140
- package/commons/components/toasts.xml +0 -18
- package/commons/components/viewport.xml +0 -48
- package/commons/forms/FieldItem.xml +0 -20
- package/commons/forms/FormController.ts +0 -87
- package/commons/forms/Item.ts +0 -132
- package/commons/forms/ItemCollectionController.ts +0 -113
- package/commons/forms/ItemController.ts +0 -91
- package/commons/forms/MultiEnumField.xml +0 -44
- package/commons/forms/MultivalueController.ts +0 -49
- package/commons/forms/NewItemController.ts +0 -46
- package/commons/forms/SmartareaField.xml +0 -51
- package/commons/forms/SuggestionController.ts +0 -80
- package/commons/forms/SuggestionField.xml +0 -28
- package/commons/forms/fields.xml +0 -151
- package/commons/forms/form.xml +0 -37
- package/commons/forms/index.ts +0 -29
- package/commons/index.ts +0 -6
- package/commons/services/ApiClientService.ts +0 -32
- package/commons/services/ErrorHandlingService.ts +0 -11
- package/commons/services/FirebaseService.ts +0 -185
- package/commons/services/GSheetsService.ts +0 -120
- package/commons/services/HeartbeatService.ts +0 -35
- package/commons/services/Invoke.ts +0 -29
- package/commons/services/JsonpService.ts +0 -18
- package/commons/services/Loader.ts +0 -17
- package/commons/services/MetadataService.ts +0 -50
- package/commons/services/NavigationService.ts +0 -74
- package/commons/services/Service.ts +0 -62
- package/commons/services/ServiceWorker.ts +0 -111
- package/commons/services/Storage.ts +0 -55
- package/commons/services/ToastService.ts +0 -61
- package/commons/services/index.ts +0 -13
- package/core/Arrmatura.ts +0 -79
- package/core/CNode.ts +0 -111
- package/core/Component.ts +0 -424
- package/core/cnodes/composition.ts +0 -100
- package/core/cnodes/conditionals.ts +0 -52
- package/core/cnodes/elementary.ts +0 -44
- package/core/cnodes/iterations.ts +0 -92
- package/core/cnodes/routing.ts +0 -25
- package/core/compileNode.ts +0 -39
- package/core/expression.ts +0 -73
- package/core/index.ts +0 -1
- package/core/register.ts +0 -43
- package/core/utils.ts +0 -23
- package/core-web/WebPlatform.ts +0 -25
- package/core-web/attributes.ts +0 -218
- package/core-web/elements.ts +0 -81
- package/core-web/index.ts +0 -28
- package/core-web/reflow.ts +0 -30
- package/core-web/support.ts +0 -27
- package/core-web/type.ts +0 -18
- package/docs/index.js +0 -485
- package/lib/array/arrayGroupBy.ts +0 -19
- package/lib/array/arraySortBy.ts +0 -49
- package/lib/array/arrayToObject.ts +0 -18
- package/lib/array/arrayish.ts +0 -7
- package/lib/array/index.ts +0 -4
- package/lib/crypto/index.ts +0 -25
- package/lib/date/index.ts +0 -127
- package/lib/func/curry.ts +0 -11
- package/lib/func/fx.ts +0 -56
- package/lib/func/identity.ts +0 -6
- package/lib/func/nextId.ts +0 -9
- package/lib/func/noop.ts +0 -6
- package/lib/geo/index.js +0 -22
- package/lib/index.ts +0 -17
- package/lib/object/index.ts +0 -69
- package/lib/parsing/codus.ts +0 -25
- package/lib/parsing/compiler.ts +0 -26
- package/lib/parsing/scalarParse.ts +0 -25
- package/lib/parsing/urls.ts +0 -91
- package/lib/parsing/xml.ts +0 -117
- package/lib/resources/l10n.ts +0 -40
- package/lib/resources/resources.ru.ts +0 -20
- package/lib/resources/resources.ts +0 -20
- package/lib/string/index.ts +0 -58
- package/lib/support/createListeners.ts +0 -12
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { Service } from './Service';
|
|
2
|
-
|
|
3
|
-
const unpackDocs = (s) =>
|
|
4
|
-
s.docs.reduce((r, e) => {
|
|
5
|
-
const d = e.data();
|
|
6
|
-
d.id = e.id;
|
|
7
|
-
r.push(d);
|
|
8
|
-
return r;
|
|
9
|
-
}, []);
|
|
10
|
-
|
|
11
|
-
const firebase = window.firebase;
|
|
12
|
-
|
|
13
|
-
export class FirebaseService {
|
|
14
|
-
firestore: any;
|
|
15
|
-
auth: any;
|
|
16
|
-
providers: { Google: any };
|
|
17
|
-
|
|
18
|
-
constructor({ ...config }) {
|
|
19
|
-
firebase.initializeApp(config);
|
|
20
|
-
this.firestore = firebase.firestore();
|
|
21
|
-
this.firestore.enablePersistence({ synchronizeTabs: true }).catch(function (err) {
|
|
22
|
-
if (err.code === 'failed-precondition') {
|
|
23
|
-
// Multiple tabs open, persistence can only be enabled
|
|
24
|
-
// in one tab at a time.
|
|
25
|
-
// ...
|
|
26
|
-
} else if (err.code === 'unimplemented') {
|
|
27
|
-
// The current browser does not support all of the
|
|
28
|
-
// features required to enable persistence
|
|
29
|
-
// ...
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
this.auth = firebase.auth();
|
|
33
|
-
this.auth.languageCode = 'by';
|
|
34
|
-
this.providers = {
|
|
35
|
-
Google: firebase.auth.GoogleAuthProvider,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
done() {
|
|
40
|
-
firebase.app().delete();
|
|
41
|
-
}
|
|
42
|
-
// auth
|
|
43
|
-
signInAnonymously() {
|
|
44
|
-
this.auth.signInAnonymously().catch(function (error) {
|
|
45
|
-
var errorMessage = error.message;
|
|
46
|
-
console.log('signInAnonymously err=' + errorMessage);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
listenUser(cb) {
|
|
50
|
-
this.auth.onAuthStateChanged(cb);
|
|
51
|
-
}
|
|
52
|
-
getCurrentUser() {
|
|
53
|
-
return this.auth.currentUser;
|
|
54
|
-
}
|
|
55
|
-
linkProvider() {
|
|
56
|
-
const provider = new this.providers.Google();
|
|
57
|
-
provider.addScope('https://www.googleapis.com/auth/contacts.readonly');
|
|
58
|
-
return this.auth
|
|
59
|
-
.signInWithPopup(provider)
|
|
60
|
-
.then((result) => {
|
|
61
|
-
// This gives you a Google Access Token. You can use it to access the Google API.
|
|
62
|
-
// var token = result.credential.accessToken
|
|
63
|
-
// The signed-in user info.
|
|
64
|
-
var googleUser = result.user;
|
|
65
|
-
var credential = this.auth.GoogleAuthProvider.credential(googleUser.getAuthResponse().id_token);
|
|
66
|
-
this.getCurrentUser()
|
|
67
|
-
.linkAndRetrieveDataWithCredential(credential)
|
|
68
|
-
.then(
|
|
69
|
-
function (usercred) {
|
|
70
|
-
var user = usercred.user;
|
|
71
|
-
console.log('Anonymous account successfully upgraded', user);
|
|
72
|
-
},
|
|
73
|
-
function (error) {
|
|
74
|
-
console.log('Error upgrading anonymous account', error);
|
|
75
|
-
}
|
|
76
|
-
);
|
|
77
|
-
// ...
|
|
78
|
-
})
|
|
79
|
-
.catch(function (error) {
|
|
80
|
-
// Handle Errors here.
|
|
81
|
-
// var errorCode = error.code
|
|
82
|
-
var errorMessage = error.message;
|
|
83
|
-
// The email of the user's account used.
|
|
84
|
-
// var email = error.email
|
|
85
|
-
// The firebase.auth.AuthCredential type that was used.
|
|
86
|
-
// var credential = error.credential
|
|
87
|
-
console.log('signInAnonymously err=' + errorMessage);
|
|
88
|
-
// ...
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
logout() {
|
|
92
|
-
return this.signInAnonymously();
|
|
93
|
-
}
|
|
94
|
-
getCollection(coll, since) {
|
|
95
|
-
return ((c) => (since ? c.where('modified_at', '>', since) : c))(this.firestore.collection(coll))
|
|
96
|
-
.get()
|
|
97
|
-
.then(unpackDocs);
|
|
98
|
-
}
|
|
99
|
-
// db
|
|
100
|
-
listenCollection(coll, ts = 0, cb) {
|
|
101
|
-
return ((c) => (ts ? c.where('modified_at', '>', ts) : c))(this.firestore.collection(coll)).onSnapshot(function (
|
|
102
|
-
querySnapshot
|
|
103
|
-
) {
|
|
104
|
-
var r = [];
|
|
105
|
-
querySnapshot.forEach(function (e) {
|
|
106
|
-
const d = e.data();
|
|
107
|
-
d.id = e.id;
|
|
108
|
-
r.push(d);
|
|
109
|
-
});
|
|
110
|
-
cb(null, { [coll]: r });
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
nextId(coll) {
|
|
114
|
-
return this.firestore.collection(coll).doc().id;
|
|
115
|
-
}
|
|
116
|
-
update(delta) {
|
|
117
|
-
const now = Date.now().valueOf();
|
|
118
|
-
// Get a new write batch
|
|
119
|
-
var batch = this.firestore.batch();
|
|
120
|
-
Object.keys(delta).forEach((coll) => {
|
|
121
|
-
const c = this.firestore.collection(coll);
|
|
122
|
-
delta[coll].forEach((d) => {
|
|
123
|
-
d.modified_at = now;
|
|
124
|
-
if (!d.created_at) {
|
|
125
|
-
d.created_at = now;
|
|
126
|
-
}
|
|
127
|
-
var ref = c.doc('' + d.id);
|
|
128
|
-
batch.set(ref, d, { merge: true });
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
return batch.commit();
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export class FirebaseUserService extends Service {
|
|
136
|
-
get fb() {
|
|
137
|
-
return this.$.app.firebase.impl;
|
|
138
|
-
}
|
|
139
|
-
init() {
|
|
140
|
-
this.fb.listenUser((user) => {
|
|
141
|
-
if (user) {
|
|
142
|
-
} else {
|
|
143
|
-
this.fb.signInAnonymously();
|
|
144
|
-
// User is signed out.
|
|
145
|
-
// ...
|
|
146
|
-
}
|
|
147
|
-
this.emit('signed');
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
getInfo() {
|
|
151
|
-
const user = this.fb.getCurrentUser();
|
|
152
|
-
if (user !== null) {
|
|
153
|
-
user.providerData.forEach(function (profile) {
|
|
154
|
-
// console.log('Sign-in provider: ' + profile.providerId)
|
|
155
|
-
// console.log(' Provider-specific UID: ' + profile.uid)
|
|
156
|
-
// console.log(' Name: ' + profile.displayName)
|
|
157
|
-
// console.log(' Email: ' + profile.email)
|
|
158
|
-
// console.log(' Photo URL: ' + profile.photoURL)
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
return !user
|
|
162
|
-
? { isLoading: true }
|
|
163
|
-
: {
|
|
164
|
-
...user,
|
|
165
|
-
isLoading: false,
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
onLogin() {
|
|
169
|
-
return this.fb.linkProvider();
|
|
170
|
-
}
|
|
171
|
-
onLogout() {
|
|
172
|
-
return this.fb.logout();
|
|
173
|
-
}
|
|
174
|
-
onSigned() {
|
|
175
|
-
return {
|
|
176
|
-
_: NaN,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const hot = typeof module === 'undefined' ? null : module.hot;
|
|
182
|
-
if (hot) {
|
|
183
|
-
// hot.addStatusHandler(function (d) {})
|
|
184
|
-
hot.accept();
|
|
185
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { arrayToObject } from '../../lib';
|
|
2
|
-
import { JsonpService } from './JsonpService';
|
|
3
|
-
import { Storage } from './Storage';
|
|
4
|
-
import { Service } from './Service';
|
|
5
|
-
|
|
6
|
-
export class GSheetsService extends Service {
|
|
7
|
-
url: string = '';
|
|
8
|
-
token: string = '';
|
|
9
|
-
kind: string = 'items';
|
|
10
|
-
remote = new JsonpService();
|
|
11
|
-
persistence: 'local' | 'session' | 'transient' = 'transient';
|
|
12
|
-
local?: Storage;
|
|
13
|
-
|
|
14
|
-
get items() {
|
|
15
|
-
return this.local?.get(this.kind) || null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
set items(items) {
|
|
19
|
-
this.local?.set(this.kind, !items ? null : items.filter((e) => !e.deleted && e.status !== 'deleted'));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
get since() {
|
|
23
|
-
return +this.local?.get(this.kind + '_since') || 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
set since(s) {
|
|
27
|
-
this.local?.set(this.kind + '_since', s || 0);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
init() {
|
|
31
|
-
this.local = new Storage(this.persistence);
|
|
32
|
-
return this.sync();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
submit(_data: any): Promise<any> {
|
|
36
|
-
throw new Error('Method not implemented.');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
sync() {
|
|
40
|
-
const promise = this.submit({ path: `?action=items&kind=${this.kind}`, body: { since: String(this.since || 0) } })
|
|
41
|
-
.then(({ items, reset, timestamp }) => {
|
|
42
|
-
this.log('sync ' + this.kind, timestamp, items?.length);
|
|
43
|
-
if (items?.length || !this.since) {
|
|
44
|
-
if (!reset) {
|
|
45
|
-
const items0 = this.items || [];
|
|
46
|
-
items = Object.values({ ...arrayToObject(items0), ...arrayToObject(items) });
|
|
47
|
-
}
|
|
48
|
-
return { items, isLoading: false, since: timestamp };
|
|
49
|
-
}
|
|
50
|
-
return { isLoading: false };
|
|
51
|
-
})
|
|
52
|
-
.catch((error) => {
|
|
53
|
-
const message = error?.message || error || 'unknown';
|
|
54
|
-
this.toast({ id: message, mode: 'error', message: 'Ошибка: ' + message });
|
|
55
|
-
return { isLoading: false, error: message };
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
isLoading: true,
|
|
60
|
-
error: null,
|
|
61
|
-
'*items': promise,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
submitData(data: Delta) {
|
|
66
|
-
return {
|
|
67
|
-
busy: true,
|
|
68
|
-
error: null,
|
|
69
|
-
'*upsert': this.submit({ path: `?action=upsert&kind=${this.kind}`, body: data })
|
|
70
|
-
.then(({ item }) => {
|
|
71
|
-
this.log('update sync' + this.kind, item.id);
|
|
72
|
-
return {
|
|
73
|
-
busy: false,
|
|
74
|
-
lastUpdatedItemId: item.id,
|
|
75
|
-
items: [item, ...this.items?.filter((e: Hash) => e.id != item.id)],
|
|
76
|
-
};
|
|
77
|
-
})
|
|
78
|
-
.catch((error) => {
|
|
79
|
-
return { busy: false, error };
|
|
80
|
-
}),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
syncItem(data: Delta) {
|
|
85
|
-
return {
|
|
86
|
-
isItemLoading: true,
|
|
87
|
-
error: null,
|
|
88
|
-
'*item': this.submit({ path: `?action=item&kind=${this.kind}`, body: data })
|
|
89
|
-
.then(({ item }) => {
|
|
90
|
-
this.log('sync item' + this.kind, item.id, item);
|
|
91
|
-
return {
|
|
92
|
-
isItemLoading: false,
|
|
93
|
-
lastUpdatedItemId: item.id,
|
|
94
|
-
items: [item, ...this.items?.filter((e: Hash) => e.id != item.id)],
|
|
95
|
-
};
|
|
96
|
-
})
|
|
97
|
-
.catch((error) => {
|
|
98
|
-
return { isItemLoading: false, error: error };
|
|
99
|
-
}),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// event handlers:
|
|
104
|
-
|
|
105
|
-
onReload() {
|
|
106
|
-
return this.sync();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
onUpsert(data: Delta) {
|
|
110
|
-
return this.submitData(data);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
onItem(data: Delta) {
|
|
114
|
-
return this.syncItem(data);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
onDelete({ id }: Delta) {
|
|
118
|
-
return this.submitData({ [String(id)]: { status: 'deleted' } });
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export class HeartbeatService {
|
|
2
|
-
interval: any;
|
|
3
|
-
watcher?: Procedure;
|
|
4
|
-
|
|
5
|
-
constructor(watcher?: Procedure) {
|
|
6
|
-
this.setWatcher(watcher);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
setWatcher(watcher?: Procedure) {
|
|
10
|
-
this.watcher = watcher;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
startWatch() {
|
|
14
|
-
if (!this.interval) {
|
|
15
|
-
this.interval = setInterval(() => {
|
|
16
|
-
this.watcher?.();
|
|
17
|
-
}, 1000);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
stopWatch() {
|
|
22
|
-
if (this.interval) {
|
|
23
|
-
clearInterval(this.interval);
|
|
24
|
-
this.interval = 0;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
setActive(active: boolean) {
|
|
29
|
-
if (!active) {
|
|
30
|
-
this.stopWatch();
|
|
31
|
-
} else {
|
|
32
|
-
this.startWatch();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export class Invoke {
|
|
2
|
-
action?: (d?: Delta) => void;
|
|
3
|
-
data?: Delta;
|
|
4
|
-
delay = 0;
|
|
5
|
-
hot = false;
|
|
6
|
-
trigger?: any;
|
|
7
|
-
|
|
8
|
-
setTrigger(val: any) {
|
|
9
|
-
this.trigger = val;
|
|
10
|
-
if (val) {
|
|
11
|
-
this.reload();
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
init() {
|
|
16
|
-
if (this.hot) {
|
|
17
|
-
this.reload();
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
reload() {
|
|
22
|
-
setTimeout(() => {
|
|
23
|
-
if (!this.isDone) {
|
|
24
|
-
console.log(this.data);
|
|
25
|
-
this.action?.(this.data);
|
|
26
|
-
}
|
|
27
|
-
}, (this.delay || 0) * 1000);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { urlAppendParams } from '../../lib';
|
|
2
|
-
|
|
3
|
-
let count = 0;
|
|
4
|
-
|
|
5
|
-
export class JsonpService {
|
|
6
|
-
fetch(url: string) {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
const jsonp: string = 'jsonp_' + ++count;
|
|
9
|
-
const a = window.document.createElement('script');
|
|
10
|
-
a.async = true;
|
|
11
|
-
a.onerror = reject;
|
|
12
|
-
a.src = urlAppendParams(url, { jsonp });
|
|
13
|
-
window[jsonp] = resolve;
|
|
14
|
-
const m = window.document.getElementsByTagName('body')[0];
|
|
15
|
-
m?.parentNode?.insertBefore(a, m);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export class Loader {
|
|
2
|
-
init() {
|
|
3
|
-
this.reload();
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
setTrigger(val) {
|
|
7
|
-
this.trigger = val;
|
|
8
|
-
this.reload();
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
reload() {
|
|
12
|
-
const { from, data, into } = this;
|
|
13
|
-
if (from && into) {
|
|
14
|
-
from({ data, callback: (error, result) => into({ error, ...result }) });
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Storage } from './Storage';
|
|
2
|
-
import { Service } from './Service';
|
|
3
|
-
|
|
4
|
-
const mergeMeta = (meta: Hash<any>, target: Hash<any>) =>
|
|
5
|
-
Object.entries(meta).reduce((res, [key, val]) => {
|
|
6
|
-
res[key] = typeof res[key] === 'object' && !Array.isArray(res[key]) ? { ...res[key], ...val } : val;
|
|
7
|
-
return res;
|
|
8
|
-
}, target);
|
|
9
|
-
|
|
10
|
-
const load = (url: string, version: string) =>
|
|
11
|
-
fetch(url, {
|
|
12
|
-
method: 'POST',
|
|
13
|
-
mode: 'cors',
|
|
14
|
-
redirect: 'follow',
|
|
15
|
-
body: JSON.stringify({ data: { version } }),
|
|
16
|
-
}).then((res) => res.json());
|
|
17
|
-
|
|
18
|
-
export class MetadataService extends Service {
|
|
19
|
-
url: string = '';
|
|
20
|
-
version: string = '';
|
|
21
|
-
ttl = 24 * 3600000;
|
|
22
|
-
|
|
23
|
-
init() {
|
|
24
|
-
const local = new Storage('local', this.url.replace(/\//g, '_'));
|
|
25
|
-
const meta0 = local.get('$meta') || {};
|
|
26
|
-
if (+(meta0?.timestamp || 0) + this.ttl > Date.now()) {
|
|
27
|
-
mergeMeta(meta0, this.res(''));
|
|
28
|
-
return { ready: true, meta: meta0 };
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
ready: false,
|
|
32
|
-
meta: meta0,
|
|
33
|
-
error: null,
|
|
34
|
-
'*': load(this.url, meta0?.version).then(
|
|
35
|
-
(meta2) => {
|
|
36
|
-
const isUpdated = meta0?.version !== meta2?.version;
|
|
37
|
-
const meta = !isUpdated ? { ...meta0 } : meta2;
|
|
38
|
-
meta.timestamp = Date.now();
|
|
39
|
-
mergeMeta(meta, this.res(''));
|
|
40
|
-
local.set('$meta', meta);
|
|
41
|
-
return { ready: true, isUpdated, meta };
|
|
42
|
-
},
|
|
43
|
-
(error) => {
|
|
44
|
-
const message = error?.message || error || 'unknown';
|
|
45
|
-
return { ready: true, error: message };
|
|
46
|
-
}
|
|
47
|
-
),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { urlParse, urlStringify } from '../../lib';
|
|
2
|
-
import { Service } from './Service';
|
|
3
|
-
|
|
4
|
-
export class NavigationService extends Service {
|
|
5
|
-
prevHash: string = '';
|
|
6
|
-
host: any;
|
|
7
|
-
path: any;
|
|
8
|
-
params: any;
|
|
9
|
-
|
|
10
|
-
init() {
|
|
11
|
-
this.prevHash;
|
|
12
|
-
window.addEventListener('hashchange', () => this.hashchange());
|
|
13
|
-
setTimeout(() => this.hashchange(), 0);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
hashchange() {
|
|
17
|
-
const hash = window.location.hash.slice(1);
|
|
18
|
-
if (hash[0] === '/' && hash !== this.prevHash) {
|
|
19
|
-
this.emit('this.hash', { value: hash.slice(1) });
|
|
20
|
-
this.prevHash = hash;
|
|
21
|
-
} else if (!this.prevHash) {
|
|
22
|
-
this.emit('this.hash', { value: 'main' });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
done() {
|
|
27
|
-
window.removeEventListener('hashchange', this.hashchange);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
setTitle(d: string) {
|
|
31
|
-
window.document.title = d;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
setHash(d) {
|
|
35
|
-
const { host, path = ['*'], params } = urlParse('/' + d);
|
|
36
|
-
const state = {
|
|
37
|
-
host: (!host || host === '*' ? this.host : host) || 'main',
|
|
38
|
-
path: path[0] === '*' ? this.path : path,
|
|
39
|
-
params: params.reset ? { ...params, reset: null } : { ...this.params, ...params },
|
|
40
|
-
};
|
|
41
|
-
window.location.hash = this.prevHash = urlStringify(state).slice(1);
|
|
42
|
-
this.up(state);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getRoute() {
|
|
46
|
-
return {
|
|
47
|
-
host: this.host,
|
|
48
|
-
path: this.path,
|
|
49
|
-
params: this.params,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
onReload() {
|
|
54
|
-
window.location.reload();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
onCopyToClipboard({ value }) {
|
|
58
|
-
navigator.clipboard.writeText(value).then(
|
|
59
|
-
() => {
|
|
60
|
-
this.toast('Copied to clipboard');
|
|
61
|
-
},
|
|
62
|
-
(err) => {
|
|
63
|
-
this.toast('Could not copy to clipboard: ' + err.message);
|
|
64
|
-
}
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
onHash({ value }) {
|
|
69
|
-
this.setHash('/' + value);
|
|
70
|
-
}
|
|
71
|
-
onParams(params) {
|
|
72
|
-
this.setHash({ params: { ...params, data: null } });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { Component } from '../../core/Component';
|
|
2
|
-
import { Toast } from './ToastService';
|
|
3
|
-
|
|
4
|
-
export class Service {
|
|
5
|
-
#$: Component;
|
|
6
|
-
state: any;
|
|
7
|
-
|
|
8
|
-
constructor(state: unknown, $: Component) {
|
|
9
|
-
this.state = state;
|
|
10
|
-
this.#$ = $;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Framework
|
|
15
|
-
*/
|
|
16
|
-
lookupService(ref: string) {
|
|
17
|
-
return this.#$.getByRef(ref);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
up(d: Delta) {
|
|
21
|
-
return this.#$.up(d);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
defer(fn: ComponentProcedure) {
|
|
25
|
-
return this.#$.defer(fn);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
emit(key: string, data: Delta) {
|
|
29
|
-
return this.#$?.emit(key, data);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
createEmitter(key: string) {
|
|
33
|
-
return (data: Delta) => this.emit(key, data);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
res(key: string) {
|
|
37
|
-
return this.#$.res(key);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
log(val: any, ...args: unknown[]) {
|
|
41
|
-
return this.#$.log(val, ...args);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Error handling
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
handleError({ message = '', code = '' }) {
|
|
49
|
-
// may overriden from props
|
|
50
|
-
const handler = this.lookupService('errorHandler');
|
|
51
|
-
if (handler) {
|
|
52
|
-
handler.handleError({ message, code, source: this.#$ });
|
|
53
|
-
} else {
|
|
54
|
-
console.error(this.#$.ref + ': ERROR: ', message, code);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
toast(message: string | Partial<Toast> = 'ok') {
|
|
59
|
-
// may overriden from props
|
|
60
|
-
this.emit('toasters.send', typeof message === 'string' ? { message } : message);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { Service } from './Service';
|
|
2
|
-
|
|
3
|
-
export class ServiceWorker extends Service {
|
|
4
|
-
get api() {
|
|
5
|
-
return navigator.serviceWorker;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
init() {
|
|
9
|
-
try {
|
|
10
|
-
Function.assert(this.api, 'Service Workers are not supported');
|
|
11
|
-
|
|
12
|
-
const { source = '/service-worker.js', scope = '/', push } = this;
|
|
13
|
-
|
|
14
|
-
this.api
|
|
15
|
-
.register(source, { scope })
|
|
16
|
-
.then((registration) => this.registered(registration))
|
|
17
|
-
.then(() => this.ready(() => this.log('Service Worker Ready')));
|
|
18
|
-
|
|
19
|
-
this.api.addEventListener('message', (ev) => this.onMessage(ev));
|
|
20
|
-
|
|
21
|
-
if (push) {
|
|
22
|
-
this.subscribe();
|
|
23
|
-
}
|
|
24
|
-
} catch (error) {
|
|
25
|
-
this.fallback(error);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ensures that `fn` executed when api is ready
|
|
30
|
-
ready(fn) {
|
|
31
|
-
return this.api.ready.then(fn);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
fallback(error) {
|
|
35
|
-
this.log(error);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// hook on registered
|
|
39
|
-
registered(registration) {
|
|
40
|
-
this.log('Service Worker Registered');
|
|
41
|
-
return registration;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Push
|
|
45
|
-
*/
|
|
46
|
-
|
|
47
|
-
withPushManager(fn) {
|
|
48
|
-
return this.ready(({ pushManager }) => pushManager).then(fn);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
unsubscribe() {
|
|
52
|
-
this.withPushManager((pushManager) => pushManager.getSubscription())
|
|
53
|
-
.then((ss) => ss && ss.unsubscribe())
|
|
54
|
-
.then(() => this.saveSubscription())
|
|
55
|
-
.catch(function (e) {
|
|
56
|
-
console.log('Error thrown while unsubscribing from push messaging.', e);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
subscribe() {
|
|
61
|
-
function urlBase64ToUint8Array(base64String) {
|
|
62
|
-
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
|
|
63
|
-
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
64
|
-
|
|
65
|
-
const rawData = window.atob(base64);
|
|
66
|
-
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
|
|
67
|
-
}
|
|
68
|
-
if (Notification.permission === 'granted') {
|
|
69
|
-
/* do our magic */
|
|
70
|
-
} else if (Notification.permission === 'blocked') {
|
|
71
|
-
/* the user has previously denied push. Can't reprompt. */
|
|
72
|
-
} else {
|
|
73
|
-
/* show a prompt to the user */
|
|
74
|
-
}
|
|
75
|
-
const applicationServerKey = urlBase64ToUint8Array(this.vapidPublicKey);
|
|
76
|
-
this.withPushManager((pushManager) =>
|
|
77
|
-
pushManager
|
|
78
|
-
.getSubscription()
|
|
79
|
-
// .then((ss) => ss && ss.unsubscribe())
|
|
80
|
-
.then(
|
|
81
|
-
(ss) =>
|
|
82
|
-
ss ||
|
|
83
|
-
pushManager.subscribe({
|
|
84
|
-
userVisibleOnly: true,
|
|
85
|
-
applicationServerKey,
|
|
86
|
-
})
|
|
87
|
-
)
|
|
88
|
-
.then((subscription) => this.saveSubscription(subscription.toJSON()))
|
|
89
|
-
.catch((err) => {
|
|
90
|
-
if (Notification.permission === 'denied') {
|
|
91
|
-
this.log('The user has blocked notifications.');
|
|
92
|
-
}
|
|
93
|
-
this.handleError(err);
|
|
94
|
-
})
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// to be overriden from props
|
|
99
|
-
saveSubscription(ss) {
|
|
100
|
-
this.subscription = ss;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Intercommunication between service worker.
|
|
105
|
-
*/
|
|
106
|
-
|
|
107
|
-
// handles a message posted from Service worker.
|
|
108
|
-
onMessage(payload) {
|
|
109
|
-
this.log('onMessage', payload);
|
|
110
|
-
}
|
|
111
|
-
}
|