@iebh/tera-fy 2.0.21 → 2.2.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/CHANGELOG.md +38 -0
- package/api.md +68 -66
- package/dist/lib/projectFile.d.ts +182 -0
- package/dist/lib/projectFile.js +157 -0
- package/dist/lib/projectFile.js.map +1 -0
- package/dist/lib/syncro/entities.d.ts +28 -0
- package/dist/lib/syncro/entities.js +203 -0
- package/dist/lib/syncro/entities.js.map +1 -0
- package/dist/lib/syncro/keyed.d.ts +95 -0
- package/dist/lib/syncro/keyed.js +286 -0
- package/dist/lib/syncro/keyed.js.map +1 -0
- package/dist/lib/syncro/syncro.d.ts +328 -0
- package/dist/lib/syncro/syncro.js +633 -0
- package/dist/lib/syncro/syncro.js.map +1 -0
- package/dist/lib/terafy.bootstrapper.d.ts +42 -0
- package/dist/lib/terafy.bootstrapper.js +130 -0
- package/dist/lib/terafy.bootstrapper.js.map +1 -0
- package/dist/lib/terafy.client.d.ts +532 -0
- package/dist/lib/terafy.client.js +1110 -0
- package/dist/lib/terafy.client.js.map +1 -0
- package/dist/lib/terafy.proxy.d.ts +66 -0
- package/dist/lib/terafy.proxy.js +123 -0
- package/dist/lib/terafy.proxy.js.map +1 -0
- package/dist/lib/terafy.server.d.ts +607 -0
- package/dist/lib/terafy.server.js +1774 -0
- package/dist/lib/terafy.server.js.map +1 -0
- package/dist/plugin.vue2.es2019.js +30 -13
- package/dist/plugins/base.d.ts +20 -0
- package/dist/plugins/base.js +21 -0
- package/dist/plugins/base.js.map +1 -0
- package/dist/plugins/firebase.d.ts +62 -0
- package/dist/plugins/firebase.js +111 -0
- package/dist/plugins/firebase.js.map +1 -0
- package/dist/plugins/vite.d.ts +12 -0
- package/dist/plugins/vite.js +22 -0
- package/dist/plugins/vite.js.map +1 -0
- package/dist/plugins/vue2.d.ts +68 -0
- package/dist/plugins/vue2.js +96 -0
- package/dist/plugins/vue2.js.map +1 -0
- package/dist/plugins/vue3.d.ts +64 -0
- package/dist/plugins/vue3.js +96 -0
- package/dist/plugins/vue3.js.map +1 -0
- package/dist/terafy.bootstrapper.es2019.js +2 -2
- package/dist/terafy.bootstrapper.js +2 -2
- package/dist/terafy.es2019.js +2 -2
- package/dist/terafy.js +1 -1
- package/dist/utils/mixin.d.ts +11 -0
- package/dist/utils/mixin.js +15 -0
- package/dist/utils/mixin.js.map +1 -0
- package/dist/utils/pDefer.d.ts +12 -0
- package/dist/utils/pDefer.js +14 -0
- package/dist/utils/pDefer.js.map +1 -0
- package/dist/utils/pathTools.d.ts +70 -0
- package/dist/utils/pathTools.js +120 -0
- package/dist/utils/pathTools.js.map +1 -0
- package/eslint.config.js +44 -8
- package/lib/{projectFile.js → projectFile.ts} +83 -40
- package/lib/syncro/entities.ts +288 -0
- package/lib/syncro/{keyed.js → keyed.ts} +114 -57
- package/lib/syncro/{syncro.js → syncro.ts} +204 -169
- package/lib/{terafy.bootstrapper.js → terafy.bootstrapper.ts} +49 -31
- package/lib/{terafy.client.js → terafy.client.ts} +94 -86
- package/lib/{terafy.proxy.js → terafy.proxy.ts} +43 -16
- package/lib/{terafy.server.js → terafy.server.ts} +364 -223
- package/package.json +65 -26
- package/plugins/{base.js → base.ts} +3 -1
- package/plugins/{firebase.js → firebase.ts} +34 -16
- package/plugins/{vite.js → vite.ts} +3 -3
- package/plugins/{vue2.js → vue2.ts} +17 -10
- package/plugins/{vue3.js → vue3.ts} +11 -9
- package/tsconfig.json +30 -0
- package/utils/{mixin.js → mixin.ts} +1 -1
- package/utils/{pDefer.js → pDefer.ts} +10 -3
- package/utils/{pathTools.js → pathTools.ts} +11 -9
- package/lib/syncro/entities.js +0 -232
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
// Global 'app' declaration
|
|
2
|
+
declare var app: any;
|
|
3
|
+
|
|
4
|
+
// Global window augmentation
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
panic(text: any): void;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
1
11
|
import {cloneDeep} from 'lodash-es';
|
|
2
12
|
import mixin from '#utils/mixin';
|
|
3
13
|
import {nanoid} from 'nanoid';
|
|
4
14
|
import pathTools from '#utils/pathTools';
|
|
5
15
|
import promiseDefer from '#utils/pDefer';
|
|
16
|
+
// @ts-ignore
|
|
6
17
|
import Reflib from '@iebh/reflib';
|
|
7
18
|
import {reactive} from 'vue';
|
|
8
19
|
|
|
@@ -29,7 +40,7 @@ export default class TeraFyServer {
|
|
|
29
40
|
* @property {String} sitePathLogin Either an absolute URL or the relative path (taken from `siteUrl`) when trying to log in the user
|
|
30
41
|
* @property {Boolean} embedWorkaround Try to use `getUserViaEmbedWorkaround()` to force a login via popup if the user is running in local mode (see function docs for more details). This is toggled to false after the first run
|
|
31
42
|
*/
|
|
32
|
-
settings = {
|
|
43
|
+
settings: any = {
|
|
33
44
|
devMode: false,
|
|
34
45
|
verbosity: 9,
|
|
35
46
|
restrictOrigin: '*',
|
|
@@ -57,20 +68,21 @@ export default class TeraFyServer {
|
|
|
57
68
|
*
|
|
58
69
|
* @returns {Object} A context, which is this instance extended with additional properties
|
|
59
70
|
*/
|
|
60
|
-
createContext(e) {
|
|
71
|
+
createContext(e: MessageEvent): any {
|
|
61
72
|
// Construct wrapper for sendRaw for this client
|
|
62
73
|
return mixin(this, {
|
|
63
74
|
messageEvent: e,
|
|
64
|
-
sendRaw(message) {
|
|
75
|
+
sendRaw(message: any) {
|
|
65
76
|
let payload;
|
|
66
77
|
try {
|
|
67
78
|
payload = {
|
|
68
79
|
TERA: 1,
|
|
69
80
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
70
81
|
};
|
|
71
|
-
e.source
|
|
72
|
-
|
|
73
|
-
|
|
82
|
+
// Use type assertion assuming e.source is a WindowProxy or similar
|
|
83
|
+
(e.source as WindowProxy).postMessage(payload, this.settings.restrictOrigin);
|
|
84
|
+
} catch (err: any) { // Changed variable name e -> err
|
|
85
|
+
this.debug('ERROR', 1, 'Attempted to dispatch payload server(via reply)->client', {payload, e: err});
|
|
74
86
|
throw err;
|
|
75
87
|
}
|
|
76
88
|
},
|
|
@@ -84,14 +96,14 @@ export default class TeraFyServer {
|
|
|
84
96
|
*
|
|
85
97
|
* @returns {Object} A context, which is this instance extended with additional properties
|
|
86
98
|
*/
|
|
87
|
-
getClientContext() {
|
|
99
|
+
getClientContext(): any {
|
|
88
100
|
switch (this.settings.serverMode) {
|
|
89
101
|
case TeraFyServer.SERVERMODE_NONE:
|
|
90
102
|
throw new Error('Client has not yet initiated communication');
|
|
91
103
|
case TeraFyServer.SERVERMODE_EMBEDDED:
|
|
92
104
|
// Server is inside an iFrame so we need to send messages to the window parent
|
|
93
105
|
return mixin(this, {
|
|
94
|
-
sendRaw(message) {
|
|
106
|
+
sendRaw(message: any) {
|
|
95
107
|
let payload;
|
|
96
108
|
try {
|
|
97
109
|
payload = {
|
|
@@ -99,7 +111,7 @@ export default class TeraFyServer {
|
|
|
99
111
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
100
112
|
};
|
|
101
113
|
window.parent.postMessage(payload, this.settings.restrictOrigin);
|
|
102
|
-
} catch (e) {
|
|
114
|
+
} catch (e: any) {
|
|
103
115
|
this.debug('ERROR', 1, 'Attempted to dispatch payload server(iframe)->cient(top level window)', {payload, e});
|
|
104
116
|
throw e;
|
|
105
117
|
}
|
|
@@ -108,28 +120,29 @@ export default class TeraFyServer {
|
|
|
108
120
|
case TeraFyServer.SERVERMODE_TERA:
|
|
109
121
|
case TeraFyServer.SERVERMODE_FRAME: {
|
|
110
122
|
// Server is the top-level window so we need to send messages to an embedded iFrame
|
|
111
|
-
let iFrame = document.querySelector('iframe#external');
|
|
123
|
+
let iFrame = document.querySelector('iframe#external') as HTMLIFrameElement | null;
|
|
112
124
|
if (!iFrame) {
|
|
113
125
|
this.debug('INFO', 2, 'Cannot locate TERA-FY top-level->iFrame#external - maybe there is none');
|
|
114
126
|
return mixin(this, {
|
|
115
|
-
sendRaw(message) {
|
|
127
|
+
sendRaw(message: any) {
|
|
116
128
|
this.debug('INFO', 2, 'Sending broadcast to zero listening clients', {message});
|
|
117
129
|
},
|
|
118
130
|
});
|
|
119
131
|
}
|
|
120
132
|
|
|
121
133
|
return mixin(this, {
|
|
122
|
-
sendRaw(message) {
|
|
134
|
+
sendRaw(message: any) {
|
|
123
135
|
let payload;
|
|
124
136
|
try {
|
|
125
137
|
payload = {
|
|
126
138
|
TERA: 1,
|
|
127
139
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
128
140
|
};
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
// Check if contentWindow exists before posting
|
|
142
|
+
iFrame.contentWindow?.postMessage(payload, this.settings.restrictOrigin);
|
|
143
|
+
} catch (e: any) {
|
|
144
|
+
this.debug('ERROR', 1, 'Attempted to dispatch payload server(top level window)->cient(iframe)', {payload, e});
|
|
145
|
+
throw e;
|
|
133
146
|
}
|
|
134
147
|
},
|
|
135
148
|
});
|
|
@@ -147,7 +160,7 @@ export default class TeraFyServer {
|
|
|
147
160
|
*
|
|
148
161
|
* @type {MessageEvent}
|
|
149
162
|
*/
|
|
150
|
-
messageEvent = null;
|
|
163
|
+
messageEvent: MessageEvent | null = null;
|
|
151
164
|
|
|
152
165
|
|
|
153
166
|
/**
|
|
@@ -159,10 +172,12 @@ export default class TeraFyServer {
|
|
|
159
172
|
*
|
|
160
173
|
* @returns {Promise<*>} The resolved output of the server function
|
|
161
174
|
*/
|
|
162
|
-
senderRpc(method, ...args) {
|
|
175
|
+
senderRpc(method: string, ...args: any[]): Promise<any> {
|
|
163
176
|
if (!this.messageEvent) throw new Error('senderRpc() can only be used if given a context from `createContext()`');
|
|
164
177
|
|
|
165
|
-
|
|
178
|
+
// Create a context specific to this event to use its sendRaw
|
|
179
|
+
const context = this.createContext(this.messageEvent);
|
|
180
|
+
return context.send({ // Use the context's send method if available, otherwise fallback? Assuming send is on the base class.
|
|
166
181
|
action: 'rpc',
|
|
167
182
|
method,
|
|
168
183
|
args,
|
|
@@ -178,7 +193,7 @@ export default class TeraFyServer {
|
|
|
178
193
|
* @returns {Promise<Object>} Basic promise result
|
|
179
194
|
* @property {Date} date Server date
|
|
180
195
|
*/
|
|
181
|
-
handshake() {
|
|
196
|
+
handshake(): Promise<any> {
|
|
182
197
|
return Promise.resolve({
|
|
183
198
|
date: new Date(),
|
|
184
199
|
});
|
|
@@ -187,11 +202,13 @@ export default class TeraFyServer {
|
|
|
187
202
|
|
|
188
203
|
/**
|
|
189
204
|
* Send a message + wait for a response object
|
|
205
|
+
* This method should likely be part of the context returned by createContext
|
|
206
|
+
* Assuming it's intended to work on the base class referencing a stored messageEvent
|
|
190
207
|
*
|
|
191
208
|
* @param {Object} message Message object to send
|
|
192
209
|
* @returns {Promise<*>} A promise which resolves when the operation has completed with the remote reply
|
|
193
210
|
*/
|
|
194
|
-
send(message) {
|
|
211
|
+
send(message: any): Promise<any> {
|
|
195
212
|
if (!this.messageEvent?.source) throw new Error('send() requires a messageEvent with a source');
|
|
196
213
|
|
|
197
214
|
let id = nanoid();
|
|
@@ -201,7 +218,6 @@ export default class TeraFyServer {
|
|
|
201
218
|
Object.assign(this.acceptPostboxes[id], {
|
|
202
219
|
resolve, reject,
|
|
203
220
|
});
|
|
204
|
-
|
|
205
221
|
// Use sendRaw with the specific source from the stored messageEvent
|
|
206
222
|
this.sendRaw({
|
|
207
223
|
id,
|
|
@@ -220,7 +236,7 @@ export default class TeraFyServer {
|
|
|
220
236
|
* @param {Object} message Message object to send
|
|
221
237
|
* @param {Window} sendVia Window context to dispatch the message via if its not the same as the regular window
|
|
222
238
|
*/
|
|
223
|
-
sendRaw(message, sendVia) {
|
|
239
|
+
sendRaw(message: any, sendVia?: any): void {
|
|
224
240
|
let payload;
|
|
225
241
|
try {
|
|
226
242
|
payload = {
|
|
@@ -228,11 +244,14 @@ export default class TeraFyServer {
|
|
|
228
244
|
...cloneDeep(message), // Need to clone to resolve promise nasties
|
|
229
245
|
};
|
|
230
246
|
this.debug('INFO', 3, 'Dispatch response', message, '<=>', payload);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
// Default to parent if sendVia is not provided, but check if it exists
|
|
248
|
+
const target = sendVia || (typeof globalThis !== 'undefined' ? globalThis.parent : undefined);
|
|
249
|
+
if (target) {
|
|
250
|
+
target.postMessage(payload, this.settings.restrictOrigin);
|
|
251
|
+
} else {
|
|
252
|
+
this.debug('WARN', 1, 'Cannot sendRaw, no target window (sendVia or parent) found.');
|
|
253
|
+
}
|
|
254
|
+
} catch (e: any) {
|
|
236
255
|
this.debug('ERROR', 2, 'Attempted to dispatch response server->client', payload);
|
|
237
256
|
this.debug('ERROR', 2, 'Message compose server->client:', e);
|
|
238
257
|
}
|
|
@@ -244,7 +263,7 @@ export default class TeraFyServer {
|
|
|
244
263
|
*
|
|
245
264
|
* @param {String} mode The server mode to set to
|
|
246
265
|
*/
|
|
247
|
-
setServerMode(mode) {
|
|
266
|
+
setServerMode(mode: string): void {
|
|
248
267
|
switch (mode) {
|
|
249
268
|
case 'embedded':
|
|
250
269
|
this.settings.serverMode = TeraFyServer.SERVERMODE_EMBEDDED;
|
|
@@ -266,37 +285,55 @@ export default class TeraFyServer {
|
|
|
266
285
|
*
|
|
267
286
|
* @param {MessageEvent} rawMessage Raw message event to process
|
|
268
287
|
*/
|
|
269
|
-
acceptMessage(rawMessage) {
|
|
270
|
-
|
|
288
|
+
acceptMessage(rawMessage: MessageEvent): void {
|
|
289
|
+
// Ignore messages from the same origin (potential loops)
|
|
290
|
+
if (typeof window !== 'undefined' && rawMessage.origin === window.location.origin) return;
|
|
271
291
|
|
|
272
292
|
let message = rawMessage.data;
|
|
273
|
-
|
|
293
|
+
// Ensure message is an object and has TERA property
|
|
294
|
+
if (typeof message !== 'object' || message === null || !message.TERA) return;
|
|
274
295
|
this.debug('INFO', 3, 'Recieved message', message);
|
|
275
296
|
|
|
276
297
|
Promise.resolve()
|
|
277
298
|
.then(()=> {
|
|
278
|
-
if (message?.action == 'response' && this.acceptPostboxes[message.id]) { // Postbox waiting for reply
|
|
299
|
+
if (message?.action == 'response' && message.id && this.acceptPostboxes[message.id]) { // Postbox waiting for reply
|
|
279
300
|
if (message.isError === true) {
|
|
280
301
|
this.acceptPostboxes[message.id].reject(message.response);
|
|
281
302
|
} else {
|
|
282
303
|
this.acceptPostboxes[message.id].resolve(message.response);
|
|
283
304
|
}
|
|
284
305
|
delete this.acceptPostboxes[message.id]; // Clean up postbox
|
|
285
|
-
} else if (message.action == 'rpc') { // Relay RPC calls
|
|
286
|
-
|
|
287
|
-
|
|
306
|
+
} else if (message.action == 'rpc' && typeof message.method === 'string') { // Relay RPC calls
|
|
307
|
+
const method = message.method as string;
|
|
308
|
+
// Use type assertion for dynamic method call
|
|
309
|
+
if (typeof (this as any)[method] === 'function') {
|
|
310
|
+
// Create context for this specific message event
|
|
311
|
+
const context = this.createContext(rawMessage);
|
|
312
|
+
// Store the event temporarily for potential use in send() called by the RPC method
|
|
313
|
+
context.messageEvent = rawMessage;
|
|
314
|
+
return (this as any)[method].apply(context, message.args || []);
|
|
315
|
+
} else {
|
|
316
|
+
throw new Error(`Unknown RPC method "${method}"`);
|
|
317
|
+
}
|
|
288
318
|
} else {
|
|
289
319
|
this.debug('ERROR', 2, 'Unexpected incoming TERA-FY SERVER message', {message});
|
|
290
|
-
throw
|
|
320
|
+
// Don't throw, just ignore unknown formats silently? Or throw?
|
|
321
|
+
// throw new Error('Unknown message format');
|
|
322
|
+
}
|
|
323
|
+
})
|
|
324
|
+
.then(response => {
|
|
325
|
+
// Only send response if it was an RPC call that returned something
|
|
326
|
+
if (message.action === 'rpc' && rawMessage.source) {
|
|
327
|
+
this.sendRaw({
|
|
328
|
+
id: message.id,
|
|
329
|
+
action: 'response',
|
|
330
|
+
response,
|
|
331
|
+
}, rawMessage.source);
|
|
291
332
|
}
|
|
292
333
|
})
|
|
293
|
-
.then(response => this.sendRaw({
|
|
294
|
-
id: message.id,
|
|
295
|
-
action: 'response',
|
|
296
|
-
response,
|
|
297
|
-
}, rawMessage.source))
|
|
298
334
|
.catch(e => {
|
|
299
335
|
console.warn(`TERA-FY server threw on RPC:${message.method}:`, e);
|
|
336
|
+
// Send error response back if possible
|
|
300
337
|
if (message.action === 'rpc' && message.id && rawMessage.source) {
|
|
301
338
|
this.sendRaw({
|
|
302
339
|
id: message.id,
|
|
@@ -314,7 +351,7 @@ export default class TeraFyServer {
|
|
|
314
351
|
/**
|
|
315
352
|
* Listening postboxes, these correspond to outgoing message IDs that expect a response
|
|
316
353
|
*/
|
|
317
|
-
acceptPostboxes = {};
|
|
354
|
+
acceptPostboxes: Record<string, any> = {};
|
|
318
355
|
|
|
319
356
|
|
|
320
357
|
/**
|
|
@@ -326,7 +363,7 @@ export default class TeraFyServer {
|
|
|
326
363
|
*
|
|
327
364
|
* @returns {Promise<*>} A promise which resolves with the resulting inner callback payload
|
|
328
365
|
*/
|
|
329
|
-
requestFocus(cb) {
|
|
366
|
+
requestFocus(cb: () => Promise<any>): Promise<any> {
|
|
330
367
|
// Ensure messageEvent is set before calling senderRpc
|
|
331
368
|
if (!this.messageEvent && this.settings.serverMode != TeraFyServer.SERVERMODE_TERA) {
|
|
332
369
|
console.warn("requestFocus called without a messageEvent context. Cannot toggle focus.");
|
|
@@ -335,9 +372,11 @@ export default class TeraFyServer {
|
|
|
335
372
|
}
|
|
336
373
|
|
|
337
374
|
return Promise.resolve()
|
|
338
|
-
|
|
375
|
+
// Only toggle focus if not in TERA mode and messageEvent is available
|
|
376
|
+
.then(()=> this.settings.serverMode != TeraFyServer.SERVERMODE_TERA && this.messageEvent && this.senderRpc('toggleFocus', true))
|
|
339
377
|
.then(()=> cb.call(this))
|
|
340
|
-
|
|
378
|
+
// Only toggle focus back if not in TERA mode and messageEvent is available
|
|
379
|
+
.finally(()=> this.settings.serverMode != TeraFyServer.SERVERMODE_TERA && this.messageEvent && this.senderRpc('toggleFocus', false))
|
|
341
380
|
}
|
|
342
381
|
|
|
343
382
|
|
|
@@ -349,8 +388,10 @@ export default class TeraFyServer {
|
|
|
349
388
|
* @param {...*} [args] Optional event payload to send
|
|
350
389
|
* @returns {Promise} A promise which resolves when the transmission has completed
|
|
351
390
|
*/
|
|
352
|
-
emitClients(event, ...args) {
|
|
353
|
-
|
|
391
|
+
emitClients(event: string, ...args: any[]): Promise<void> {
|
|
392
|
+
// Use getClientContext to get the appropriate sendRaw method
|
|
393
|
+
const context = this.getClientContext();
|
|
394
|
+
return context.sendRaw({
|
|
354
395
|
action: 'event',
|
|
355
396
|
id: nanoid(),
|
|
356
397
|
event,
|
|
@@ -364,7 +405,7 @@ export default class TeraFyServer {
|
|
|
364
405
|
*
|
|
365
406
|
* @param {Number} verbosity The desired server verbosity level
|
|
366
407
|
*/
|
|
367
|
-
setServerVerbosity(verbosity) {
|
|
408
|
+
setServerVerbosity(verbosity: number): void {
|
|
368
409
|
this.settings.verbosity = +verbosity;
|
|
369
410
|
this.debug('INFO', 1, 'Server verbosity set to', this.settings.verbosity);
|
|
370
411
|
}
|
|
@@ -390,7 +431,7 @@ export default class TeraFyServer {
|
|
|
390
431
|
*
|
|
391
432
|
* @returns {Promise<User>} The current logged in user or null if none
|
|
392
433
|
*/
|
|
393
|
-
getUser(options) {
|
|
434
|
+
getUser(options?: any): Promise<any | null> {
|
|
394
435
|
let settings = {
|
|
395
436
|
forceRetry: false,
|
|
396
437
|
waitPromises: true,
|
|
@@ -422,7 +463,7 @@ export default class TeraFyServer {
|
|
|
422
463
|
}
|
|
423
464
|
: null
|
|
424
465
|
)
|
|
425
|
-
.catch(e => {
|
|
466
|
+
.catch((e: any) => {
|
|
426
467
|
console.warn('getUser() catch', e);
|
|
427
468
|
return null; // Return null on error
|
|
428
469
|
})
|
|
@@ -436,8 +477,8 @@ export default class TeraFyServer {
|
|
|
436
477
|
*
|
|
437
478
|
* @returns {Promise<User>} A promise which will resolve if the there is a user and they are logged in
|
|
438
479
|
*/
|
|
439
|
-
requireUser() {
|
|
440
|
-
let user; // Last getUser() response
|
|
480
|
+
requireUser(): Promise<any> {
|
|
481
|
+
let user: any; // Last getUser() response
|
|
441
482
|
return Promise.resolve() // NOTE: This promise is upside down, it only continues down the chain if the user is NOT valid, otherwise it throws to exit
|
|
442
483
|
.then(()=> this.getUser())
|
|
443
484
|
.then(res => user = res)
|
|
@@ -495,7 +536,7 @@ export default class TeraFyServer {
|
|
|
495
536
|
*
|
|
496
537
|
* @returns {Object} An object containing 3rd party service credentials
|
|
497
538
|
*/
|
|
498
|
-
getCredentials() {
|
|
539
|
+
getCredentials(): any {
|
|
499
540
|
return app.service('$auth').credentials;
|
|
500
541
|
}
|
|
501
542
|
|
|
@@ -516,10 +557,10 @@ export default class TeraFyServer {
|
|
|
516
557
|
*
|
|
517
558
|
* @returns {Promise} A promise which resolves when the operation has completed
|
|
518
559
|
*/
|
|
519
|
-
async getUserViaEmbedWorkaround() {
|
|
560
|
+
async getUserViaEmbedWorkaround(): Promise<void> {
|
|
520
561
|
this.debug('INFO', 4, 'Attempting to use getUserViaEmbedWorkaround()');
|
|
521
562
|
|
|
522
|
-
let lsState = window.localStorage.getItem('tera.embedUser');
|
|
563
|
+
let lsState: any = window.localStorage.getItem('tera.embedUser');
|
|
523
564
|
if (lsState) {
|
|
524
565
|
this.debug('INFO', 4, 'Using localStorage state');
|
|
525
566
|
try {
|
|
@@ -539,8 +580,8 @@ export default class TeraFyServer {
|
|
|
539
580
|
// Force refresh projects against the new user
|
|
540
581
|
await app.service('$projects').refresh();
|
|
541
582
|
return;
|
|
542
|
-
} catch (
|
|
543
|
-
throw new Error(`Failed to decode local dev state - ${
|
|
583
|
+
} catch (e: any) {
|
|
584
|
+
throw new Error(`Failed to decode local dev state - ${e.toString()}`);
|
|
544
585
|
}
|
|
545
586
|
}
|
|
546
587
|
|
|
@@ -551,7 +592,7 @@ export default class TeraFyServer {
|
|
|
551
592
|
+ '<div class="mt-2"><a class="btn btn-light">Open Popup...</a></div>';
|
|
552
593
|
|
|
553
594
|
// Attach click listner to internal button to re-popup the auth window (in case popups are blocked)
|
|
554
|
-
focusContent.querySelector('a.btn')
|
|
595
|
+
focusContent.querySelector('a.btn')?.addEventListener('click', ()=>
|
|
555
596
|
this.uiWindow(new URL(this.settings.sitePathLogin, this.settings.siteUrl).toString())
|
|
556
597
|
);
|
|
557
598
|
|
|
@@ -559,7 +600,7 @@ export default class TeraFyServer {
|
|
|
559
600
|
let waitOnWindowAuth = promiseDefer();
|
|
560
601
|
|
|
561
602
|
// Create a listener for the message from the downstream window to resolve the promise
|
|
562
|
-
let listenMessages = ({data}) => {
|
|
603
|
+
let listenMessages = ({data}: {data: any}) => {
|
|
563
604
|
this.debug('INFO', 3, 'Recieved message from popup window', {data});
|
|
564
605
|
if (data.TERA && data.action == 'popupUserState' && data.user) { // Signal sent from landing page - we're logged in, yey!
|
|
565
606
|
let $auth = app.service('$auth');
|
|
@@ -585,7 +626,7 @@ export default class TeraFyServer {
|
|
|
585
626
|
window.addEventListener('message', listenMessages);
|
|
586
627
|
|
|
587
628
|
// Go fullscreen, try to open the auth window + prompt the user to retry (if popups are blocked) and wait for resolution
|
|
588
|
-
await this.requestFocus(()=> {
|
|
629
|
+
await this.requestFocus(async ()=> {
|
|
589
630
|
// Try opening the popup automatically - this will likely fail if the user has popup blocking enabled
|
|
590
631
|
this.uiWindow(new URL(this.settings.sitePathLogin, this.settings.siteUrl).toString());
|
|
591
632
|
|
|
@@ -626,7 +667,7 @@ export default class TeraFyServer {
|
|
|
626
667
|
*
|
|
627
668
|
* @returns {Promise<Project|null>} The currently active project, if any
|
|
628
669
|
*/
|
|
629
|
-
getProject() {
|
|
670
|
+
getProject(): Promise<any | null> {
|
|
630
671
|
let $projects = app.service('$projects');
|
|
631
672
|
|
|
632
673
|
return $projects.promise()
|
|
@@ -647,11 +688,11 @@ export default class TeraFyServer {
|
|
|
647
688
|
*
|
|
648
689
|
* @returns {Promise<Array<Project>>} Collection of projects the user has access to
|
|
649
690
|
*/
|
|
650
|
-
getProjects() {
|
|
691
|
+
getProjects(): Promise<any[]> {
|
|
651
692
|
let $projects = app.service('$projects');
|
|
652
693
|
|
|
653
694
|
return $projects.promise()
|
|
654
|
-
.then(()=> $projects.list.map(project => ({
|
|
695
|
+
.then(()=> $projects.list.map((project: any) => ({
|
|
655
696
|
id: project.id,
|
|
656
697
|
name: project.name,
|
|
657
698
|
created: project.created,
|
|
@@ -666,7 +707,7 @@ export default class TeraFyServer {
|
|
|
666
707
|
* @param {Object|String} project The project to set as active - either the full Project object or its ID
|
|
667
708
|
* @returns {Promise} A promise which resolves when the operation has completed
|
|
668
709
|
*/
|
|
669
|
-
setActiveProject(project) {
|
|
710
|
+
setActiveProject(project: any): Promise<void> {
|
|
670
711
|
return app.service('$projects').setActive(project);
|
|
671
712
|
}
|
|
672
713
|
|
|
@@ -684,7 +725,7 @@ export default class TeraFyServer {
|
|
|
684
725
|
*
|
|
685
726
|
* @returns {Promise<Project>} The active project
|
|
686
727
|
*/
|
|
687
|
-
requireProject(options) {
|
|
728
|
+
requireProject(options?: any): Promise<any> {
|
|
688
729
|
let settings = {
|
|
689
730
|
autoRequireUser: true,
|
|
690
731
|
autoSetActiveProject: true,
|
|
@@ -701,13 +742,13 @@ export default class TeraFyServer {
|
|
|
701
742
|
if (active) return active; // Use active project
|
|
702
743
|
|
|
703
744
|
return new Promise((resolve, reject) => {
|
|
704
|
-
let askProject = ()=> Promise.resolve()
|
|
745
|
+
let askProject = (): Promise<any> => Promise.resolve()
|
|
705
746
|
.then(()=> this.selectProject({
|
|
706
747
|
allowCancel: false,
|
|
707
748
|
}))
|
|
708
749
|
.then(project => resolve(project))
|
|
709
750
|
.catch(e => {
|
|
710
|
-
if (e
|
|
751
|
+
if (e == 'cancel' || e === 'CANCEL') { // Handle string 'cancel' or rejected 'CANCEL'
|
|
711
752
|
return this.requestFocus(()=>
|
|
712
753
|
app.service('$prompt').dialog({
|
|
713
754
|
title: settings.noSelectTitle,
|
|
@@ -723,7 +764,7 @@ export default class TeraFyServer {
|
|
|
723
764
|
})
|
|
724
765
|
askProject(); // Kick off intial project loop
|
|
725
766
|
})
|
|
726
|
-
.then(async (project) => {
|
|
767
|
+
.then(async (project: any) => {
|
|
727
768
|
if (settings.autoSetActiveProject) await this.setActiveProject(project);
|
|
728
769
|
return project;
|
|
729
770
|
})
|
|
@@ -741,7 +782,7 @@ export default class TeraFyServer {
|
|
|
741
782
|
*
|
|
742
783
|
* @returns {Promise<Project>} The active project
|
|
743
784
|
*/
|
|
744
|
-
selectProject(options) {
|
|
785
|
+
selectProject(options?: any): Promise<any> {
|
|
745
786
|
let settings = {
|
|
746
787
|
title: 'Select a project to work with',
|
|
747
788
|
allowCancel: true,
|
|
@@ -754,10 +795,10 @@ export default class TeraFyServer {
|
|
|
754
795
|
app.service('$prompt').dialog({
|
|
755
796
|
title: settings.title,
|
|
756
797
|
component: 'projectsSelect',
|
|
757
|
-
buttons: settings.allowCancel ? ['cancel'] :
|
|
798
|
+
buttons: settings.allowCancel ? ['cancel'] : [],
|
|
758
799
|
})
|
|
759
800
|
))
|
|
760
|
-
.then(project => settings.setActive
|
|
801
|
+
.then((project: any) => settings.setActive
|
|
761
802
|
? this.setActiveProject(project)
|
|
762
803
|
.then(()=> project)
|
|
763
804
|
: project
|
|
@@ -776,7 +817,7 @@ export default class TeraFyServer {
|
|
|
776
817
|
*
|
|
777
818
|
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
778
819
|
*/
|
|
779
|
-
getNamespace(name) {
|
|
820
|
+
getNamespace(name: string): Promise<any> {
|
|
780
821
|
if (!/^[\w-]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
|
|
781
822
|
|
|
782
823
|
return app.service('$sync').getSnapshot(`project_namespaces::${app.service('$projects').active.id}::${name}`);
|
|
@@ -794,12 +835,12 @@ export default class TeraFyServer {
|
|
|
794
835
|
*
|
|
795
836
|
* @returns {Promise<Object>} A promise which resolves to the namespace POJO state
|
|
796
837
|
*/
|
|
797
|
-
setNamespace(name, state, options) {
|
|
838
|
+
setNamespace(name: string, state: any, options?: any): Promise<any> {
|
|
798
839
|
if (!/^[\w-]+$/.test(name)) throw new Error('Namespaces must be alphanumeric + hyphens + underscores');
|
|
799
840
|
if (typeof state != 'object') throw new Error('State must be an object');
|
|
800
841
|
|
|
801
|
-
return app.service('$sync').setSnapshot(`project_namespaces::${app.service('$projects').active.id}
|
|
802
|
-
method: options
|
|
842
|
+
return app.service('$sync').setSnapshot(`project_namespaces::${app.service('$projects').active.id}::${name}`, state, {
|
|
843
|
+
method: options?.method ?? 'merge',
|
|
803
844
|
});
|
|
804
845
|
}
|
|
805
846
|
|
|
@@ -810,7 +851,7 @@ export default class TeraFyServer {
|
|
|
810
851
|
* @returns {Promise<Array<Object>>} Collection of available namespaces for the current project
|
|
811
852
|
* @property {String} name The name of the namespace
|
|
812
853
|
*/
|
|
813
|
-
listNamespaces() {
|
|
854
|
+
listNamespaces(): Promise<any[]> {
|
|
814
855
|
return app.service('$projects').listNamespaces();
|
|
815
856
|
}
|
|
816
857
|
// }}}
|
|
@@ -826,7 +867,7 @@ export default class TeraFyServer {
|
|
|
826
867
|
*
|
|
827
868
|
* @returns {Promise<Object>} The current project state snapshot
|
|
828
869
|
*/
|
|
829
|
-
getProjectState(options) {
|
|
870
|
+
getProjectState(options?: any): Promise<any> {
|
|
830
871
|
let settings = {
|
|
831
872
|
autoRequire: true,
|
|
832
873
|
paths: null,
|
|
@@ -859,7 +900,7 @@ export default class TeraFyServer {
|
|
|
859
900
|
*
|
|
860
901
|
* @returns {Promise<*>} A promise which resolves to `value` when the operation has been dispatched to the server and saved
|
|
861
902
|
*/
|
|
862
|
-
setProjectState(path, value, options) {
|
|
903
|
+
setProjectState(path: string | string[], value: any, options?: any): Promise<any> {
|
|
863
904
|
let settings = {
|
|
864
905
|
strategy: 'set',
|
|
865
906
|
...options,
|
|
@@ -899,32 +940,33 @@ export default class TeraFyServer {
|
|
|
899
940
|
*
|
|
900
941
|
* @returns {Promise<*>} A promise which resolves to the eventual input value after defaults have been applied
|
|
901
942
|
*/
|
|
902
|
-
setProjectStateDefaults(path, value, options) {
|
|
903
|
-
let settings = {
|
|
904
|
-
...options,
|
|
905
|
-
};
|
|
943
|
+
setProjectStateDefaults(path: string | string[] | any, value?: any, options?: any): Promise<any> {
|
|
944
|
+
let settings = { ...options }; // Initialize settings from the third argument if present
|
|
906
945
|
if (!app.service('$projects').active) throw new Error('No active project');
|
|
907
946
|
|
|
908
947
|
let target = app.service('$projects').active;
|
|
948
|
+
let actualValue: any;
|
|
909
949
|
|
|
910
950
|
if (typeof path == 'string' || Array.isArray(path)) { // Called as (path, value, options?) Set sub-object
|
|
951
|
+
actualValue = value;
|
|
911
952
|
return this.setProjectState(
|
|
912
953
|
path,
|
|
913
|
-
|
|
954
|
+
actualValue,
|
|
914
955
|
{
|
|
915
956
|
strategy: 'defaults',
|
|
916
|
-
...settings,
|
|
957
|
+
...settings, // Pass options from the third argument
|
|
917
958
|
},
|
|
918
959
|
)
|
|
919
960
|
.then(()=> pathTools.get(target, path));
|
|
920
|
-
} else { // Called as (value) - Populate entire project layout
|
|
921
|
-
|
|
961
|
+
} else { // Called as (value, options?) - Populate entire project layout
|
|
962
|
+
actualValue = path; // The first argument is the value
|
|
963
|
+
settings = { ...value }; // The second argument holds the options
|
|
964
|
+
pathTools.defaults(target, actualValue);
|
|
922
965
|
this.debug('INFO', 1, 'setProjectStateDefaults', {
|
|
923
|
-
defaults:
|
|
966
|
+
defaults: actualValue,
|
|
924
967
|
newState: cloneDeep(target),
|
|
925
968
|
});
|
|
926
|
-
|
|
927
|
-
return Promise.resolve(value);
|
|
969
|
+
return Promise.resolve(target); // Resolve with the modified target state
|
|
928
970
|
}
|
|
929
971
|
}
|
|
930
972
|
|
|
@@ -934,7 +976,7 @@ export default class TeraFyServer {
|
|
|
934
976
|
*
|
|
935
977
|
* @returns {Promise} A promise which resolves when the operation has completed
|
|
936
978
|
*/
|
|
937
|
-
setProjectStateRefresh() {
|
|
979
|
+
setProjectStateRefresh(): Promise<null> {
|
|
938
980
|
this.debug('INFO', 1, 'Force project state refresh!');
|
|
939
981
|
if (!app.service('$projects').active) throw new Error('No active project');
|
|
940
982
|
return app.service('$projects').active.$read({force: true})
|
|
@@ -992,7 +1034,7 @@ export default class TeraFyServer {
|
|
|
992
1034
|
*
|
|
993
1035
|
* @returns {Promise<ProjectFile>} The eventually selected file, if in save mode new files are created as stubs
|
|
994
1036
|
*/
|
|
995
|
-
selectProjectFile(options) {
|
|
1037
|
+
selectProjectFile(options?: any): Promise<any> {
|
|
996
1038
|
let settings = {
|
|
997
1039
|
title: 'Select a file',
|
|
998
1040
|
hint: null,
|
|
@@ -1025,15 +1067,15 @@ export default class TeraFyServer {
|
|
|
1025
1067
|
filters: settings.filters,
|
|
1026
1068
|
},
|
|
1027
1069
|
componentEvents: {
|
|
1028
|
-
fileSave(file) {
|
|
1070
|
+
fileSave(file: any) {
|
|
1029
1071
|
app.service('$prompt').close(true, file);
|
|
1030
1072
|
},
|
|
1031
|
-
fileSelect(file) {
|
|
1073
|
+
fileSelect(file: any) {
|
|
1032
1074
|
app.service('$prompt').close(true, file);
|
|
1033
1075
|
},
|
|
1034
1076
|
},
|
|
1035
1077
|
modalDialogClass: 'modal-dialog-lg',
|
|
1036
|
-
buttons: settings.allowCancel ? ['cancel'] :
|
|
1078
|
+
buttons: settings.allowCancel ? ['cancel'] : [],
|
|
1037
1079
|
})
|
|
1038
1080
|
))
|
|
1039
1081
|
}
|
|
@@ -1049,7 +1091,7 @@ export default class TeraFyServer {
|
|
|
1049
1091
|
*
|
|
1050
1092
|
* @returns {Promise<Array<ProjectFile>>} A collection of project files for the given project
|
|
1051
1093
|
*/
|
|
1052
|
-
getProjectFiles(options) {
|
|
1094
|
+
getProjectFiles(options?: any): Promise<any[]> {
|
|
1053
1095
|
let settings = {
|
|
1054
1096
|
autoRequire: true,
|
|
1055
1097
|
lazy: true,
|
|
@@ -1082,7 +1124,7 @@ export default class TeraFyServer {
|
|
|
1082
1124
|
*
|
|
1083
1125
|
* @returns {Promise<ProjectFile>} The eventual fetched ProjectFile (or requested subkey)
|
|
1084
1126
|
*/
|
|
1085
|
-
getProjectFile(name, options) {
|
|
1127
|
+
getProjectFile(name: string, options?: any): Promise<any> {
|
|
1086
1128
|
let settings = {
|
|
1087
1129
|
subkey: null,
|
|
1088
1130
|
cache: true,
|
|
@@ -1099,12 +1141,12 @@ export default class TeraFyServer {
|
|
|
1099
1141
|
})
|
|
1100
1142
|
: app.service('$projects').activeFiles // Otherwise use file cache
|
|
1101
1143
|
)
|
|
1102
|
-
.then(files =>
|
|
1103
|
-
files.find(file =>
|
|
1144
|
+
.then((files: any[]) =>
|
|
1145
|
+
files.find((file: any) =>
|
|
1104
1146
|
file.name == name
|
|
1105
1147
|
)
|
|
1106
1148
|
)
|
|
1107
|
-
.then(file => file && settings.subkey ? file[settings.subkey] : file)
|
|
1149
|
+
.then((file: any) => file && settings.subkey ? (file as any)[settings.subkey] : file)
|
|
1108
1150
|
}
|
|
1109
1151
|
|
|
1110
1152
|
|
|
@@ -1118,7 +1160,7 @@ export default class TeraFyServer {
|
|
|
1118
1160
|
*
|
|
1119
1161
|
* @returns {*} The file contents in the requested format
|
|
1120
1162
|
*/
|
|
1121
|
-
getProjectFileContents(id, options) {
|
|
1163
|
+
getProjectFileContents(id: string, options?: any): Promise<any> {
|
|
1122
1164
|
let settings = {
|
|
1123
1165
|
format: 'blob',
|
|
1124
1166
|
...options,
|
|
@@ -1139,7 +1181,7 @@ export default class TeraFyServer {
|
|
|
1139
1181
|
* @param {String} name The name + relative directory path component
|
|
1140
1182
|
* @returns {Promise<ProjectFile>} The eventual ProjectFile created
|
|
1141
1183
|
*/
|
|
1142
|
-
createProjectFile(name) {
|
|
1184
|
+
createProjectFile(name: string): Promise<any> {
|
|
1143
1185
|
return Promise.resolve()
|
|
1144
1186
|
.then(()=> app.service('$supabase').fileUpload(app.service('$projects').convertRelativePath(name), {
|
|
1145
1187
|
file: new Blob([''], {type: 'text/plain'}),
|
|
@@ -1152,7 +1194,7 @@ export default class TeraFyServer {
|
|
|
1152
1194
|
.then(()=> this.getProjectFile(name, {
|
|
1153
1195
|
cache: false, // Force cache to update, as this is a new file
|
|
1154
1196
|
}))
|
|
1155
|
-
.then(file => file || Promise.reject(`Could not create new file "${name}"`))
|
|
1197
|
+
.then((file: any) => file || Promise.reject(`Could not create new file "${name}"`))
|
|
1156
1198
|
}
|
|
1157
1199
|
|
|
1158
1200
|
|
|
@@ -1163,7 +1205,7 @@ export default class TeraFyServer {
|
|
|
1163
1205
|
*
|
|
1164
1206
|
* @returns {Promise} A promise which resolves when the operation has completed
|
|
1165
1207
|
*/
|
|
1166
|
-
deleteProjectFile(id) {
|
|
1208
|
+
deleteProjectFile(id: string): Promise<null> {
|
|
1167
1209
|
return app.service('$supabase').fileRemove(app.service('$projects').decodeFilePath(id))
|
|
1168
1210
|
.then(()=> app.service('$projects').refreshFiles({ // Force a local file list update
|
|
1169
1211
|
lazy: false,
|
|
@@ -1187,33 +1229,51 @@ export default class TeraFyServer {
|
|
|
1187
1229
|
*
|
|
1188
1230
|
* @returns {Promise} A promise which will resolve when the write operation has completed
|
|
1189
1231
|
*/
|
|
1190
|
-
setProjectFileContents(id, contents, options) {
|
|
1191
|
-
// Argument
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1232
|
+
setProjectFileContents(id: string | any | null, contents: any, options?: any): Promise<null> {
|
|
1233
|
+
// Argument Mangling Logic (Simplified)
|
|
1234
|
+
let fileId: string | null = null;
|
|
1235
|
+
let fileContents: any;
|
|
1236
|
+
let mergedOptions: any;
|
|
1237
|
+
|
|
1238
|
+
if (typeof id === 'string') {
|
|
1239
|
+
fileId = id;
|
|
1240
|
+
fileContents = contents;
|
|
1241
|
+
mergedOptions = { ...options };
|
|
1242
|
+
} else if (id !== null && typeof id === 'object' && !(id instanceof Blob) && !(id instanceof File) && !(id instanceof FormData) && !Array.isArray(id)) {
|
|
1243
|
+
// Assuming called as (optionsObject)
|
|
1244
|
+
mergedOptions = { ...id };
|
|
1245
|
+
fileId = mergedOptions.id ?? null;
|
|
1246
|
+
fileContents = mergedOptions.contents;
|
|
1197
1247
|
} else {
|
|
1198
|
-
|
|
1248
|
+
// Assuming called as (contents, options)
|
|
1249
|
+
fileId = options?.id ?? null; // Check options for id if provided
|
|
1250
|
+
fileContents = id; // First arg is contents
|
|
1251
|
+
mergedOptions = { ...contents }; // Second arg is options
|
|
1199
1252
|
}
|
|
1200
|
-
|
|
1253
|
+
|
|
1254
|
+
if (fileContents === undefined) throw new Error('setProjectFileContents requires contents to save.');
|
|
1201
1255
|
|
|
1202
1256
|
let settings = {
|
|
1203
|
-
id,
|
|
1257
|
+
id: fileId,
|
|
1204
1258
|
autoRequire: true,
|
|
1205
1259
|
hint: null,
|
|
1206
1260
|
filename: null,
|
|
1207
1261
|
title: 'Save file',
|
|
1208
1262
|
meta: null,
|
|
1209
|
-
...options
|
|
1263
|
+
...mergedOptions, // Apply options derived from mangling
|
|
1210
1264
|
};
|
|
1211
1265
|
|
|
1266
|
+
|
|
1212
1267
|
return Promise.resolve()
|
|
1213
|
-
.then(()=> settings.autoRequire && this.requireProject())
|
|
1214
1268
|
.then(()=> {
|
|
1215
|
-
|
|
1216
|
-
|
|
1269
|
+
settings.autoRequire && this.requireProject()
|
|
1270
|
+
})
|
|
1271
|
+
.then((): Promise<string> => { // Ensure the promise returns a string (fileId)
|
|
1272
|
+
if (settings.id) {
|
|
1273
|
+
// Validate the provided ID exists? Optional, but good practice.
|
|
1274
|
+
// For now, just return it assuming it's valid.
|
|
1275
|
+
return Promise.resolve(settings.id);
|
|
1276
|
+
}
|
|
1217
1277
|
// Prompt for a save filename
|
|
1218
1278
|
return this.selectProjectFile({
|
|
1219
1279
|
title: settings.title,
|
|
@@ -1222,15 +1282,20 @@ export default class TeraFyServer {
|
|
|
1222
1282
|
saveFilename: settings.filename,
|
|
1223
1283
|
autoRequire: false, // Handled above anyway
|
|
1224
1284
|
})
|
|
1225
|
-
.then(file =>
|
|
1285
|
+
.then((file: any) => {
|
|
1286
|
+
if (!file || !file.id) throw new Error('File selection cancelled or failed.');
|
|
1287
|
+
return file.id; // Return the selected file ID
|
|
1288
|
+
});
|
|
1226
1289
|
})
|
|
1227
|
-
.then(()=> {
|
|
1228
|
-
|
|
1290
|
+
.then((resolvedFileId: string) => {
|
|
1291
|
+
settings.id = resolvedFileId; // Update settings.id with the resolved/validated ID
|
|
1292
|
+
if (!settings.id) throw new Error("Could not determine file ID to save to."); // Final check
|
|
1293
|
+
return app.service('$supabase').fileSet(app.service('$projects').decodeFilePath(settings.id), fileContents, {
|
|
1294
|
+
overwrite: true,
|
|
1295
|
+
toast: false,
|
|
1296
|
+
// TODO: Handle settings.meta if $supabase.fileSet supports it
|
|
1297
|
+
});
|
|
1229
1298
|
})
|
|
1230
|
-
.then(()=> app.service('$supabase').fileSet(app.service('$projects').decodeFilePath(settings.id), contents, {
|
|
1231
|
-
overwrite: true,
|
|
1232
|
-
toast: false,
|
|
1233
|
-
}))
|
|
1234
1299
|
.then(()=> null)
|
|
1235
1300
|
}
|
|
1236
1301
|
// }}}
|
|
@@ -1252,7 +1317,7 @@ export default class TeraFyServer {
|
|
|
1252
1317
|
*
|
|
1253
1318
|
* @returns {Promise<Array<Ref>>} A collection of references from the selected file
|
|
1254
1319
|
*/
|
|
1255
|
-
selectProjectLibrary(options) {
|
|
1320
|
+
selectProjectLibrary(options?: any): Promise<any[]> {
|
|
1256
1321
|
let settings = {
|
|
1257
1322
|
title: 'Select a citation library',
|
|
1258
1323
|
hint: null,
|
|
@@ -1268,9 +1333,14 @@ export default class TeraFyServer {
|
|
|
1268
1333
|
...options,
|
|
1269
1334
|
};
|
|
1270
1335
|
|
|
1336
|
+
|
|
1271
1337
|
return app.service('$projects').promise()
|
|
1272
|
-
.then(()=> this.selectProjectFile(settings))
|
|
1273
|
-
.then(selectedFile =>
|
|
1338
|
+
.then(()=> this.selectProjectFile(settings)) // Pass merged settings
|
|
1339
|
+
.then((selectedFile: any) => {
|
|
1340
|
+
if (!selectedFile || !selectedFile.id) throw new Error('Library selection failed or was cancelled.');
|
|
1341
|
+
// Pass relevant options down to getProjectLibrary
|
|
1342
|
+
return this.getProjectLibrary(selectedFile.id, settings);
|
|
1343
|
+
})
|
|
1274
1344
|
}
|
|
1275
1345
|
|
|
1276
1346
|
|
|
@@ -1287,16 +1357,16 @@ export default class TeraFyServer {
|
|
|
1287
1357
|
*
|
|
1288
1358
|
* @returns {Promise<Array<Ref>>|Promise<*>} A collection of references (default bevahiour) or a whatever format was requested
|
|
1289
1359
|
*/
|
|
1290
|
-
getProjectLibrary(id, options) {
|
|
1360
|
+
getProjectLibrary(id: string, options?: any): Promise<any> {
|
|
1291
1361
|
let settings = {
|
|
1292
1362
|
format: 'pojo',
|
|
1293
1363
|
autoRequire: true,
|
|
1294
|
-
filter: file => true, //
|
|
1295
|
-
find: files => files.at(0),
|
|
1364
|
+
filter: (file: any) => true, // Default filter
|
|
1365
|
+
find: (files: any[]) => files.at(0), // Default find
|
|
1296
1366
|
...options,
|
|
1297
1367
|
};
|
|
1298
1368
|
|
|
1299
|
-
let filePath = app.service('$projects').decodeFilePath(id);
|
|
1369
|
+
let filePath: string = app.service('$projects').decodeFilePath(id);
|
|
1300
1370
|
|
|
1301
1371
|
return Promise.resolve()
|
|
1302
1372
|
.then(()=> settings.autoRequire && this.requireProject())
|
|
@@ -1305,7 +1375,6 @@ export default class TeraFyServer {
|
|
|
1305
1375
|
}))
|
|
1306
1376
|
.then(blob => {
|
|
1307
1377
|
if (!blob) throw new Error(`File not found or empty: ${filePath}`);
|
|
1308
|
-
|
|
1309
1378
|
switch (settings.format) {
|
|
1310
1379
|
// NOTE: Any updates to the format list should also extend setProjectLibrary()
|
|
1311
1380
|
case 'pojo':
|
|
@@ -1348,8 +1417,33 @@ export default class TeraFyServer {
|
|
|
1348
1417
|
*
|
|
1349
1418
|
* @returns {Promise} A promise which resolves when the save operation has completed
|
|
1350
1419
|
*/
|
|
1351
|
-
setProjectLibrary(id, refs, options) {
|
|
1420
|
+
setProjectLibrary(id: string | any | null, refs?: any, options?: any): Promise<null> {
|
|
1421
|
+
// Argument Mangling Logic (Simplified)
|
|
1422
|
+
let fileId: string | null = null;
|
|
1423
|
+
let libraryRefs: any;
|
|
1424
|
+
let mergedOptions: any;
|
|
1425
|
+
|
|
1426
|
+
if (typeof id === 'string') {
|
|
1427
|
+
fileId = id;
|
|
1428
|
+
libraryRefs = refs;
|
|
1429
|
+
mergedOptions = { ...options };
|
|
1430
|
+
} else if (id !== null && typeof id === 'object' && !(id instanceof Blob) && !(id instanceof File) && !Array.isArray(id)) {
|
|
1431
|
+
// Assuming called as (optionsObject)
|
|
1432
|
+
mergedOptions = { ...id };
|
|
1433
|
+
fileId = mergedOptions.id ?? null;
|
|
1434
|
+
libraryRefs = mergedOptions.refs;
|
|
1435
|
+
} else {
|
|
1436
|
+
// Assuming called as (refs, options)
|
|
1437
|
+
fileId = options?.id ?? null; // Check options for id if provided
|
|
1438
|
+
libraryRefs = id; // First arg is refs
|
|
1439
|
+
mergedOptions = { ...refs }; // Second arg is options
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
if (libraryRefs === undefined) throw new Error('setProjectLibrary requires refs to save.');
|
|
1443
|
+
|
|
1352
1444
|
let settings = {
|
|
1445
|
+
id: fileId,
|
|
1446
|
+
refs: libraryRefs,
|
|
1353
1447
|
format: 'auto',
|
|
1354
1448
|
autoRequire: true,
|
|
1355
1449
|
hint: null,
|
|
@@ -1357,20 +1451,17 @@ export default class TeraFyServer {
|
|
|
1357
1451
|
title: 'Save citation library',
|
|
1358
1452
|
overwrite: true,
|
|
1359
1453
|
meta: null,
|
|
1360
|
-
...
|
|
1361
|
-
typeof id == 'string' && Array.isArray(refs) ? {id, refs, ...options} // Called as (id, refs, options?)
|
|
1362
|
-
: Array.isArray(id) || refs instanceof Blob || refs instanceof File ? {refs: id, ...refs} // Called as (refs, options?)
|
|
1363
|
-
: id // Called as (options?)
|
|
1364
|
-
)
|
|
1454
|
+
...mergedOptions // Apply options derived from mangling
|
|
1365
1455
|
};
|
|
1366
|
-
if (!settings.refs) throw new Error('No refs to save');
|
|
1367
1456
|
|
|
1368
|
-
let filePath; // Eventual Supabase path to use
|
|
1457
|
+
let filePath: any; // Eventual Supabase path to use
|
|
1369
1458
|
return Promise.resolve()
|
|
1370
1459
|
.then(()=> settings.autoRequire && this.requireProject())
|
|
1371
|
-
.then(()=> {
|
|
1372
|
-
if (settings.id)
|
|
1373
|
-
|
|
1460
|
+
.then((): Promise<string> => { // Ensure promise returns string (fileId)
|
|
1461
|
+
if (settings.id) {
|
|
1462
|
+
// Optional: Validate settings.id exists?
|
|
1463
|
+
return Promise.resolve(settings.id);
|
|
1464
|
+
}
|
|
1374
1465
|
// Prompt for a save filename
|
|
1375
1466
|
return this.selectProjectFile({
|
|
1376
1467
|
title: settings.title,
|
|
@@ -1382,9 +1473,14 @@ export default class TeraFyServer {
|
|
|
1382
1473
|
},
|
|
1383
1474
|
autoRequire: false, // Handled above anyway
|
|
1384
1475
|
})
|
|
1385
|
-
.then(file =>
|
|
1476
|
+
.then((file: any) => {
|
|
1477
|
+
if (!file || !file.id) throw new Error('File selection cancelled or failed.');
|
|
1478
|
+
return file.id; // Return selected file ID
|
|
1479
|
+
});
|
|
1386
1480
|
})
|
|
1387
|
-
.then(()=> { // Compute filePath
|
|
1481
|
+
.then((resolvedFileId: string)=> { // Compute filePath
|
|
1482
|
+
settings.id = resolvedFileId; // Update settings.id
|
|
1483
|
+
if (!settings.id) throw new Error("Could not determine file ID to save library to.");
|
|
1388
1484
|
filePath = app.service('$projects').decodeFilePath(settings.id);
|
|
1389
1485
|
})
|
|
1390
1486
|
.then(()=> {
|
|
@@ -1417,10 +1513,11 @@ export default class TeraFyServer {
|
|
|
1417
1513
|
throw new Error(`Unsupported library format "${settings.format}"`);
|
|
1418
1514
|
}
|
|
1419
1515
|
})
|
|
1420
|
-
.then(fileBlob => app.service('$supabase').fileUpload(filePath, {
|
|
1516
|
+
.then((fileBlob: File) => app.service('$supabase').fileUpload(filePath, { // Expect File type
|
|
1421
1517
|
file: fileBlob,
|
|
1422
1518
|
overwrite: settings.overwrite,
|
|
1423
1519
|
mode: 'encoded',
|
|
1520
|
+
// TODO: Handle settings.meta if $supabase.fileUpload supports it
|
|
1424
1521
|
}))
|
|
1425
1522
|
.then(()=> null)
|
|
1426
1523
|
}
|
|
@@ -1436,7 +1533,7 @@ export default class TeraFyServer {
|
|
|
1436
1533
|
* @param {Object} log The log entry to create
|
|
1437
1534
|
* @returns {Promise} A promise which resolves when the operation has completed
|
|
1438
1535
|
*/
|
|
1439
|
-
projectLog(log) {
|
|
1536
|
+
projectLog(log: any): Promise<void> {
|
|
1440
1537
|
return app.service('$projects').log(log);
|
|
1441
1538
|
}
|
|
1442
1539
|
// }}}
|
|
@@ -1450,7 +1547,7 @@ export default class TeraFyServer {
|
|
|
1450
1547
|
* @param {String} [options.path] The URL path segment to restore on next refresh
|
|
1451
1548
|
* @param {String} [options.title] The page title associated with the path
|
|
1452
1549
|
*/
|
|
1453
|
-
setPage(options) {
|
|
1550
|
+
setPage(options: any): void {
|
|
1454
1551
|
app.service('$projects').setPage(options);
|
|
1455
1552
|
}
|
|
1456
1553
|
// }}}
|
|
@@ -1462,7 +1559,7 @@ export default class TeraFyServer {
|
|
|
1462
1559
|
*
|
|
1463
1560
|
* @param {Object} [options] Additional options to merge into `settings`
|
|
1464
1561
|
*/
|
|
1465
|
-
constructor(options) {
|
|
1562
|
+
constructor(options?: any) {
|
|
1466
1563
|
Object.assign(this.settings, options);
|
|
1467
1564
|
}
|
|
1468
1565
|
|
|
@@ -1470,9 +1567,12 @@ export default class TeraFyServer {
|
|
|
1470
1567
|
/**
|
|
1471
1568
|
* Initialize the browser listener
|
|
1472
1569
|
*/
|
|
1473
|
-
init() {
|
|
1474
|
-
|
|
1475
|
-
|
|
1570
|
+
init(): void {
|
|
1571
|
+
// Ensure this only runs in a browser context
|
|
1572
|
+
if (typeof window !== 'undefined' && typeof globalThis !== 'undefined') {
|
|
1573
|
+
globalThis.addEventListener('message', this.acceptMessage.bind(this));
|
|
1574
|
+
this.debug('INFO', 1, 'Ready');
|
|
1575
|
+
}
|
|
1476
1576
|
}
|
|
1477
1577
|
// }}}
|
|
1478
1578
|
|
|
@@ -1490,7 +1590,7 @@ export default class TeraFyServer {
|
|
|
1490
1590
|
*
|
|
1491
1591
|
* @returns {Promise} A promise which resolves when the alert has been dismissed
|
|
1492
1592
|
*/
|
|
1493
|
-
uiAlert(text, options) {
|
|
1593
|
+
uiAlert(text: string | any, options?: any): Promise<void> {
|
|
1494
1594
|
let settings = {
|
|
1495
1595
|
body: 'Alert!',
|
|
1496
1596
|
isHtml: false,
|
|
@@ -1507,7 +1607,10 @@ export default class TeraFyServer {
|
|
|
1507
1607
|
app.service('$prompt').dialog({
|
|
1508
1608
|
title: settings.title,
|
|
1509
1609
|
body: settings.body,
|
|
1510
|
-
buttons:
|
|
1610
|
+
buttons:
|
|
1611
|
+
settings.buttons == 'ok' ? ['ok']
|
|
1612
|
+
: settings.buttons === false ? []
|
|
1613
|
+
: settings.buttons, // Allow passing custom button arrays
|
|
1511
1614
|
isHtml: settings.isHtml,
|
|
1512
1615
|
dialogClose: 'resolve', // Resolve promise when closed
|
|
1513
1616
|
})
|
|
@@ -1527,7 +1630,7 @@ export default class TeraFyServer {
|
|
|
1527
1630
|
*
|
|
1528
1631
|
* @returns {Promise} A promise which resolves with `Promise.resolve('OK')` or rejects with `Promise.reject('CANCEL')`
|
|
1529
1632
|
*/
|
|
1530
|
-
uiConfirm(text, options) {
|
|
1633
|
+
uiConfirm(text: string | any, options?: any): Promise<'OK'> {
|
|
1531
1634
|
let settings = {
|
|
1532
1635
|
body: 'Confirm?',
|
|
1533
1636
|
isHtml: false,
|
|
@@ -1553,11 +1656,11 @@ export default class TeraFyServer {
|
|
|
1553
1656
|
{
|
|
1554
1657
|
title: 'Cancel',
|
|
1555
1658
|
class: 'btn btn-danger',
|
|
1556
|
-
click: 'reject',
|
|
1659
|
+
click: 'reject', // Reject promise
|
|
1557
1660
|
},
|
|
1558
1661
|
],
|
|
1559
1662
|
})
|
|
1560
|
-
.then(()=> 'OK') // Resolve with 'OK' if OK button clicked
|
|
1663
|
+
.then(()=> 'OK' as 'OK') // Resolve with 'OK' if OK button clicked
|
|
1561
1664
|
.catch(()=> Promise.reject('CANCEL')) // Reject with 'CANCEL' if Cancel button clicked or closed
|
|
1562
1665
|
);
|
|
1563
1666
|
}
|
|
@@ -1569,8 +1672,15 @@ export default class TeraFyServer {
|
|
|
1569
1672
|
* @function uiPanic
|
|
1570
1673
|
* @param {String} [text] Text to display
|
|
1571
1674
|
*/
|
|
1572
|
-
uiPanic(text) {
|
|
1573
|
-
window
|
|
1675
|
+
uiPanic(text: any): void {
|
|
1676
|
+
// Ensure window context exists
|
|
1677
|
+
if (typeof window !== 'undefined' && typeof window.panic === 'function') {
|
|
1678
|
+
window.panic(text);
|
|
1679
|
+
} else {
|
|
1680
|
+
console.error("PANIC (window.panic not available):", text);
|
|
1681
|
+
// Fallback behavior if window.panic doesn't exist
|
|
1682
|
+
alert(`PANIC: ${text}`);
|
|
1683
|
+
}
|
|
1574
1684
|
}
|
|
1575
1685
|
|
|
1576
1686
|
|
|
@@ -1588,10 +1698,19 @@ export default class TeraFyServer {
|
|
|
1588
1698
|
*
|
|
1589
1699
|
* @returns {Promise} A promise which resolves when the dialog has been updated
|
|
1590
1700
|
*/
|
|
1591
|
-
uiProgress(options) {
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
if (
|
|
1701
|
+
uiProgress(options?: any): Promise<void> {
|
|
1702
|
+
let currentOptions = options === false ? {close: true} : options || {};
|
|
1703
|
+
|
|
1704
|
+
if (currentOptions.close) { // Asked to close the dialog
|
|
1705
|
+
const closePromise = this._uiProgress.promise
|
|
1706
|
+
? app.service('$prompt').close(true) // Assume close takes 1 arg
|
|
1707
|
+
: Promise.resolve();
|
|
1708
|
+
return closePromise.then(()=> { // Release state
|
|
1709
|
+
this._uiProgress.options = null;
|
|
1710
|
+
this._uiProgress.promise = null;
|
|
1711
|
+
});
|
|
1712
|
+
} else if (!this._uiProgress.promise) { // Not created the dialog yet
|
|
1713
|
+
// Initialize options if they don't exist
|
|
1595
1714
|
this._uiProgress.options = reactive({
|
|
1596
1715
|
body: '',
|
|
1597
1716
|
bodyHtml: false,
|
|
@@ -1599,39 +1718,32 @@ export default class TeraFyServer {
|
|
|
1599
1718
|
close: false,
|
|
1600
1719
|
progress: 0,
|
|
1601
1720
|
progressMax: 0,
|
|
1602
|
-
backdrop: true,
|
|
1603
|
-
...options
|
|
1721
|
+
backdrop: true, // Default backdrop
|
|
1722
|
+
...currentOptions, // Apply initial options
|
|
1604
1723
|
});
|
|
1605
|
-
} else { // Merge options with existing uiProgress window
|
|
1606
|
-
Object.assign(this._uiProgress.options, options);
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
if (this._uiProgress.options.close) { // Asked to close the dialog
|
|
1610
|
-
return Promise.resolve()
|
|
1611
|
-
.then(()=> this._uiProgress.promise && app.service('$prompt').close(true)) // Close the dialog if its open
|
|
1612
|
-
.then(()=> { // Release state
|
|
1613
|
-
this._uiProgress.options = {};
|
|
1614
|
-
this._uiProgress.promise = null;
|
|
1615
|
-
})
|
|
1616
|
-
} else if (!this._uiProgress.promise) { // Not created the dialog yet
|
|
1617
1724
|
this._uiProgress.promise = this.requestFocus(()=>
|
|
1618
1725
|
app.service('$prompt').dialog({
|
|
1619
|
-
title: this._uiProgress.options
|
|
1620
|
-
backdrop: this._uiProgress.options
|
|
1726
|
+
title: this._uiProgress.options?.title,
|
|
1727
|
+
backdrop: this._uiProgress.options?.backdrop ?? true,
|
|
1621
1728
|
component: 'uiProgress',
|
|
1622
|
-
componentProps: this._uiProgress.options,
|
|
1729
|
+
componentProps: this._uiProgress.options, // Pass reactive object
|
|
1623
1730
|
closeable: false,
|
|
1624
1731
|
keyboard: false,
|
|
1625
1732
|
})
|
|
1626
1733
|
);
|
|
1627
|
-
return Promise.resolve();
|
|
1734
|
+
return Promise.resolve(); // Dialog creation is async via requestFocus
|
|
1735
|
+
} else if (this._uiProgress.options) { // Dialog exists, merge options
|
|
1736
|
+
Object.assign(this._uiProgress.options, currentOptions);
|
|
1737
|
+
return Promise.resolve(); // Updates handled by reactivity
|
|
1628
1738
|
} else {
|
|
1629
|
-
|
|
1739
|
+
// Should not happen if initialized correctly
|
|
1740
|
+
console.warn("uiProgress called in unexpected state");
|
|
1741
|
+
return Promise.resolve();
|
|
1630
1742
|
}
|
|
1631
1743
|
}
|
|
1632
1744
|
|
|
1633
|
-
_uiProgress = {
|
|
1634
|
-
options:
|
|
1745
|
+
_uiProgress: { options: any | null, promise: Promise<any> | null } = {
|
|
1746
|
+
options: null,
|
|
1635
1747
|
promise: null,
|
|
1636
1748
|
};
|
|
1637
1749
|
|
|
@@ -1651,7 +1763,7 @@ export default class TeraFyServer {
|
|
|
1651
1763
|
*
|
|
1652
1764
|
* @returns {Promise<*>} Either the eventual user value or a throw with `Promise.reject('CANCEL')`
|
|
1653
1765
|
*/
|
|
1654
|
-
uiPrompt(text, options) {
|
|
1766
|
+
uiPrompt(text: string | any, options?: any): Promise<any> {
|
|
1655
1767
|
let settings = {
|
|
1656
1768
|
body: '',
|
|
1657
1769
|
isHtml: false,
|
|
@@ -1682,23 +1794,26 @@ export default class TeraFyServer {
|
|
|
1682
1794
|
class: 'btn btn-success',
|
|
1683
1795
|
icon: 'fas fa-check',
|
|
1684
1796
|
title: 'Ok',
|
|
1685
|
-
click() {
|
|
1686
|
-
|
|
1797
|
+
click(): any {
|
|
1798
|
+
// Assuming 'this' is the component instance with 'newValue' property
|
|
1799
|
+
// And $prompt service is available globally via 'app'
|
|
1800
|
+
app.service('$prompt').close(true, (this as any).newValue); // Use app.$prompt.close
|
|
1687
1801
|
},
|
|
1688
1802
|
},
|
|
1689
|
-
'cancel',
|
|
1803
|
+
'cancel', // Standard cancel button that rejects
|
|
1690
1804
|
],
|
|
1691
1805
|
})
|
|
1692
1806
|
)
|
|
1693
|
-
.then(answer => {
|
|
1694
|
-
if
|
|
1807
|
+
.then((answer: any) => {
|
|
1808
|
+
// Check if the answer is non-empty or if required is false
|
|
1809
|
+
if (answer || !settings.required) {
|
|
1695
1810
|
return answer;
|
|
1696
|
-
} else
|
|
1811
|
+
} else {
|
|
1812
|
+
// If required and answer is empty/nullish, treat as cancel
|
|
1697
1813
|
return Promise.reject('CANCEL');
|
|
1698
|
-
} else { // Everything else - relay the raw value
|
|
1699
|
-
return answer;
|
|
1700
1814
|
}
|
|
1701
1815
|
})
|
|
1816
|
+
// Catch rejection from 'cancel' button or closing the dialog
|
|
1702
1817
|
.catch(()=> Promise.reject('CANCEL'))
|
|
1703
1818
|
}
|
|
1704
1819
|
|
|
@@ -1710,7 +1825,7 @@ export default class TeraFyServer {
|
|
|
1710
1825
|
*
|
|
1711
1826
|
* @returns {Void} This function is fatal
|
|
1712
1827
|
*/
|
|
1713
|
-
uiThrow(error) {
|
|
1828
|
+
uiThrow(error: any): Promise<void> {
|
|
1714
1829
|
return this.requestFocus(()=>
|
|
1715
1830
|
app.service('$errors').catch(error)
|
|
1716
1831
|
);
|
|
@@ -1730,7 +1845,10 @@ export default class TeraFyServer {
|
|
|
1730
1845
|
*
|
|
1731
1846
|
* @returns {WindowProxy} The opened window object (if `noopener` is not set in permissions)
|
|
1732
1847
|
*/
|
|
1733
|
-
uiWindow(url, options) {
|
|
1848
|
+
uiWindow(url: string | URL, options?: any): WindowProxy | null {
|
|
1849
|
+
// Ensure this runs only in browser context
|
|
1850
|
+
if (typeof window === 'undefined' || typeof screen === 'undefined') return null;
|
|
1851
|
+
|
|
1734
1852
|
let settings = {
|
|
1735
1853
|
width: 500,
|
|
1736
1854
|
height: 600,
|
|
@@ -1745,7 +1863,9 @@ export default class TeraFyServer {
|
|
|
1745
1863
|
...options,
|
|
1746
1864
|
};
|
|
1747
1865
|
|
|
1748
|
-
|
|
1866
|
+
const urlString = typeof url === 'string' ? url : url.toString();
|
|
1867
|
+
|
|
1868
|
+
const features = Object.entries({
|
|
1749
1869
|
...settings.permissions,
|
|
1750
1870
|
width: settings.width,
|
|
1751
1871
|
height: settings.height,
|
|
@@ -1754,12 +1874,10 @@ export default class TeraFyServer {
|
|
|
1754
1874
|
top: screen.height/2 - settings.height/2,
|
|
1755
1875
|
}),
|
|
1756
1876
|
})
|
|
1757
|
-
.map(([key, val]) => key
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
.join(', ')
|
|
1762
|
-
);
|
|
1877
|
+
.map(([key, val]) => `${key}=${typeof val === 'boolean' ? (val ? 'yes' : 'no') : val}`) // Use yes/no for booleans
|
|
1878
|
+
.join(',');
|
|
1879
|
+
|
|
1880
|
+
return window.open(urlString, '_blank', features);
|
|
1763
1881
|
}
|
|
1764
1882
|
|
|
1765
1883
|
|
|
@@ -1772,30 +1890,42 @@ export default class TeraFyServer {
|
|
|
1772
1890
|
* @param {Object} [options] Additional options to mutate behaviour
|
|
1773
1891
|
* @param {Boolean|String} [options.logo=false] Add a logo to the output, if boolean true the Tera-tools logo is used otherwise specify a path or URL
|
|
1774
1892
|
*/
|
|
1775
|
-
uiSplat(content, options) {
|
|
1893
|
+
uiSplat(content: Element | string | false, options?: any): void {
|
|
1894
|
+
// Ensure this runs only in browser context
|
|
1895
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
1896
|
+
|
|
1776
1897
|
let settings = {
|
|
1777
1898
|
logo: false,
|
|
1778
1899
|
...options,
|
|
1779
1900
|
};
|
|
1780
1901
|
|
|
1781
|
-
|
|
1782
|
-
|
|
1902
|
+
// Remove existing splat first
|
|
1903
|
+
const existingSplat = globalThis.document.body.querySelector('.tera-fy-uiSplat');
|
|
1904
|
+
if (existingSplat) {
|
|
1905
|
+
existingSplat.remove();
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
if (!content) { // If content is false, just remove and return
|
|
1783
1909
|
return;
|
|
1784
1910
|
}
|
|
1785
1911
|
|
|
1786
|
-
let compiledContent
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1912
|
+
let compiledContent: Element;
|
|
1913
|
+
if (typeof content == 'string') {
|
|
1914
|
+
let el = document.createElement('div')
|
|
1915
|
+
el.innerHTML = content;
|
|
1916
|
+
// If the string contained multiple top-level elements, wrap them
|
|
1917
|
+
compiledContent = el.children.length === 1 ? el.firstElementChild! : el;
|
|
1918
|
+
} else {
|
|
1919
|
+
compiledContent = content;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1793
1922
|
|
|
1794
1923
|
compiledContent.classList.add('tera-fy-uiSplat');
|
|
1795
1924
|
|
|
1796
1925
|
if (settings.logo) {
|
|
1797
1926
|
let logoEl = document.createElement('div');
|
|
1798
1927
|
logoEl.innerHTML = `<img src="${typeof settings.logo == 'string' ? settings.logo : '/assets/logo/logo.svg'}" class="img-logo"/>`;
|
|
1928
|
+
// Prepend logo within the content element
|
|
1799
1929
|
compiledContent.prepend(logoEl);
|
|
1800
1930
|
}
|
|
1801
1931
|
|
|
@@ -1814,29 +1944,40 @@ export default class TeraFyServer {
|
|
|
1814
1944
|
* @param {Number} [verboseLevel=1] The verbosity level to trigger at. If `settings.verbosity` is lower than this, the message is ignored
|
|
1815
1945
|
* @param {...*} [msg] Output to show
|
|
1816
1946
|
*/
|
|
1817
|
-
debug(...
|
|
1947
|
+
debug(...inputArgs: any[]): void {
|
|
1948
|
+
// Ensure console exists
|
|
1949
|
+
if (typeof console === 'undefined') return;
|
|
1818
1950
|
if (!this.settings.devMode || this.settings.verbosity < 1) return; // Debugging is disabled
|
|
1819
|
-
|
|
1951
|
+
|
|
1952
|
+
let method: keyof Console = 'log'; // Default method
|
|
1820
1953
|
let verboseLevel = 1;
|
|
1954
|
+
let msgArgs = [...inputArgs]; // Copy args to modify
|
|
1955
|
+
|
|
1821
1956
|
// Argument mangling for prefix method + verbosity level {{{
|
|
1822
|
-
if (typeof
|
|
1823
|
-
|
|
1957
|
+
if (typeof msgArgs[0] == 'string' && ['INFO', 'LOG', 'WARN', 'ERROR'].includes(msgArgs[0].toUpperCase())) {
|
|
1958
|
+
const potentialMethod = msgArgs.shift().toLowerCase() as keyof Console;
|
|
1959
|
+
// Check if it's a valid console method
|
|
1960
|
+
if (potentialMethod in console) {
|
|
1961
|
+
method = potentialMethod;
|
|
1962
|
+
} else {
|
|
1963
|
+
msgArgs.unshift(potentialMethod); // Put it back if not a valid method
|
|
1964
|
+
}
|
|
1824
1965
|
}
|
|
1825
1966
|
|
|
1826
|
-
if (typeof
|
|
1827
|
-
verboseLevel =
|
|
1828
|
-
msg.shift();
|
|
1967
|
+
if (typeof msgArgs[0] == 'number') {
|
|
1968
|
+
verboseLevel = msgArgs.shift();
|
|
1829
1969
|
}
|
|
1830
1970
|
// }}}
|
|
1831
1971
|
|
|
1832
1972
|
if (this.settings.verbosity < verboseLevel) return; // Called but this output is too verbose for our settings - skip
|
|
1833
1973
|
|
|
1834
|
-
console
|
|
1974
|
+
// Use type assertion for dynamic console method call
|
|
1975
|
+
(console as any)[method](
|
|
1835
1976
|
'%c[TERA-FY SERVER]',
|
|
1836
1977
|
'font-weight: bold; color: #4d659c;',
|
|
1837
|
-
...
|
|
1978
|
+
...msgArgs,
|
|
1838
1979
|
);
|
|
1839
1980
|
}
|
|
1840
1981
|
/* eslint-enable */
|
|
1841
1982
|
// }}}
|
|
1842
|
-
}
|
|
1983
|
+
}
|