@saltcorn/mobile-app 1.1.0-beta.2 → 1.1.0-beta.21

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.
Files changed (39) hide show
  1. package/.babelrc +3 -0
  2. package/build_scripts/modify_android_manifest.js +47 -0
  3. package/build_scripts/modify_gradle_cfg.js +34 -0
  4. package/package.json +20 -11
  5. package/src/.eslintrc +21 -0
  6. package/src/helpers/api.js +43 -0
  7. package/src/helpers/auth.js +191 -0
  8. package/src/helpers/common.js +175 -0
  9. package/{www/js/utils/table_utils.js → src/helpers/db_schema.js} +18 -40
  10. package/src/helpers/file_system.js +102 -0
  11. package/{www/js/utils/global_utils.js → src/helpers/navigation.js} +175 -335
  12. package/src/helpers/offline_mode.js +645 -0
  13. package/src/index.js +20 -0
  14. package/src/init.js +424 -0
  15. package/src/routing/index.js +98 -0
  16. package/{www/js → src/routing}/mocks/request.js +5 -5
  17. package/{www/js → src/routing}/mocks/response.js +1 -1
  18. package/{www/js → src/routing}/routes/api.js +10 -15
  19. package/{www/js → src/routing}/routes/auth.js +12 -6
  20. package/{www/js → src/routing}/routes/delete.js +9 -6
  21. package/{www/js → src/routing}/routes/edit.js +9 -6
  22. package/src/routing/routes/error.js +6 -0
  23. package/{www/js → src/routing}/routes/fields.js +7 -2
  24. package/{www/js → src/routing}/routes/page.js +14 -9
  25. package/{www/js → src/routing}/routes/sync.js +9 -5
  26. package/{www/js → src/routing}/routes/view.js +16 -11
  27. package/{www/js/routes/common.js → src/routing/utils.js} +15 -13
  28. package/webpack.config.js +31 -0
  29. package/www/data/encoded_site_logo.js +1 -0
  30. package/www/index.html +23 -493
  31. package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +187 -273
  32. package/config.xml +0 -27
  33. package/res/icon/android/icon.png +0 -0
  34. package/res/screen/android/splash-icon.png +0 -0
  35. package/res/screen/ios/Default@2x~universal~anyany.png +0 -0
  36. package/www/js/routes/error.js +0 -5
  37. package/www/js/routes/init.js +0 -76
  38. package/www/js/utils/file_helpers.js +0 -108
  39. package/www/js/utils/offline_mode_helper.js +0 -625
@@ -0,0 +1,102 @@
1
+ import { Filesystem, Encoding, Directory } from "@capacitor/filesystem";
2
+
3
+ export async function writeFile(name, directory, content) {
4
+ try {
5
+ await Filesystem.writeFile({
6
+ path: name,
7
+ data: content,
8
+ directory: directory,
9
+ encoding: Encoding.UTF8,
10
+ });
11
+ } catch (error) {
12
+ console.log("Unable to write file", error);
13
+ throw error;
14
+ }
15
+ }
16
+
17
+ export async function readFile(name, directory) {
18
+ try {
19
+ const contents = await Filesystem.readFile({
20
+ path: name,
21
+ directory: directory,
22
+ encoding: Encoding.UTF8,
23
+ });
24
+ return contents.data;
25
+ } catch (error) {
26
+ console.log("Unable to read file", error);
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ export async function fileExists(name, directory) {
32
+ try {
33
+ await Filesystem.stat({ path: name, directory: directory });
34
+ return true;
35
+ } catch (error) {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ export async function writeJSON(name, directory, json) {
41
+ const contents = JSON.stringify(json);
42
+ await writeFile(name, directory, contents);
43
+ }
44
+
45
+ function getDirEntryCordova(directory) {
46
+ return new Promise((resolve, reject) => {
47
+ window.resolveLocalFileSystemURL(
48
+ directory,
49
+ function (fs) {
50
+ resolve(fs);
51
+ },
52
+ function (error) {
53
+ reject(error);
54
+ }
55
+ );
56
+ });
57
+ }
58
+
59
+ export async function readBinaryCordova(fileName, dirName) {
60
+ const dirEntry = await getDirEntryCordova(dirName);
61
+ return new Promise((resolve, reject) => {
62
+ dirEntry.getFile(
63
+ fileName,
64
+ { create: false, exclusive: false },
65
+ function (fileEntry) {
66
+ fileEntry.file(function (file) {
67
+ let reader = new FileReader();
68
+ reader.onloadend = function (e) {
69
+ resolve({ buffer: this.result, file: file });
70
+ };
71
+ reader.readAsArrayBuffer(file);
72
+ });
73
+ },
74
+ function (err) {
75
+ console.log(`unable to read ${fileName}`);
76
+ console.log(err);
77
+ reject(err);
78
+ }
79
+ );
80
+ });
81
+ }
82
+
83
+ // TODO get this working
84
+ // export function normalizeFilePath(filePath) {
85
+ // if (filePath.startsWith("file:///")) {
86
+ // return filePath.replace("file:///storage/emulated/0/", "");
87
+ // }
88
+ // return filePath;
89
+ // }
90
+
91
+ // export async function readBinary(name, directory) {
92
+ // try {
93
+ // const file = await Filesystem.readFile({
94
+ // path: normalizeFilePath(`${directory}/${name}`),
95
+ // directory: Directory.ExternalStorage,
96
+ // });
97
+ // return Uint8Array.from(file.data, (char) => char.charCodeAt(0));
98
+ // } catch (error) {
99
+ // console.log("Unable to read file", error);
100
+ // throw error;
101
+ // }
102
+ // }
@@ -1,8 +1,13 @@
1
- /*global window, $, offlineHelper, axios, write, cordova, router, getDirEntry, saltcorn, document, FileReader, navigator, splashConfig, i18next*/
1
+ /*global saltcorn*/
2
+ import i18next from "i18next";
2
3
 
3
- let routingHistory = [];
4
+ import { router } from "../routing/index";
5
+ import { startOfflineMode } from "./offline_mode";
6
+ import { showAlerts } from "./common";
4
7
 
5
- function currentLocation() {
8
+ export let routingHistory = [];
9
+
10
+ export function currentLocation() {
6
11
  if (routingHistory.length == 0) return undefined;
7
12
  let index = routingHistory.length - 1;
8
13
  while (index > 0 && routingHistory[index].route.startsWith("post/")) {
@@ -11,7 +16,13 @@ function currentLocation() {
11
16
  return routingHistory[index].route;
12
17
  }
13
18
 
14
- function currentQuery(skipPosts = false) {
19
+ export function currentUrl() {
20
+ const query = currentQuery();
21
+ const safeQuery = query ? (query.startsWith("?") ? query : `?${query}`) : "";
22
+ return `${currentLocation()}${safeQuery}`;
23
+ }
24
+
25
+ export function currentQuery(skipPosts = false) {
15
26
  if (routingHistory.length == 0) return undefined;
16
27
  let index = routingHistory.length - 1;
17
28
  if (skipPosts)
@@ -21,7 +32,7 @@ function currentQuery(skipPosts = false) {
21
32
  return routingHistory[index].query;
22
33
  }
23
34
 
24
- function addQueryParam(key, value) {
35
+ export function addQueryParam(key, value) {
25
36
  let query = currentQuery();
26
37
  if (!query) {
27
38
  routingHistory[routingHistory.length - 1].query = `${key}=${value}`;
@@ -32,251 +43,54 @@ function addQueryParam(key, value) {
32
43
  }
33
44
  }
34
45
 
35
- function addRoute(routeEntry) {
46
+ export function addRoute(routeEntry) {
36
47
  routingHistory.push(routeEntry);
37
48
  }
38
49
 
39
- function clearHistory() {
50
+ export function clearHistory() {
40
51
  routingHistory = [];
41
52
  }
42
53
 
43
- function popRoute() {
54
+ export function popRoute() {
44
55
  routingHistory.pop();
45
56
  }
46
57
 
47
- async function apiCall({ method, path, params, body, responseType, timeout }) {
48
- const config =
49
- typeof saltcorn !== "undefined"
50
- ? saltcorn.data.state.getState().mobileConfig
51
- : splashConfig;
52
- const serverPath = config.server_path;
53
- const url = `${serverPath}${path}`;
54
- const headers = {
55
- "X-Requested-With": "XMLHttpRequest",
56
- "X-Saltcorn-Client": "mobile-app",
57
- };
58
- if (config.tenantAppName) headers["X-Saltcorn-App"] = config.tenantAppName;
59
- const token = config.jwt;
60
- if (token) headers.Authorization = `jwt ${token}`;
61
- try {
62
- const result = await axios({
63
- url: url,
64
- method,
65
- params,
66
- headers,
67
- responseType: responseType ? responseType : "json",
68
- data: body,
69
- timeout: timeout ? timeout : 0,
70
- });
71
- return result;
72
- } catch (error) {
73
- error.message = `Unable to call ${method} ${url}:\n${error.message}`;
74
- throw error;
75
- }
76
- }
77
-
78
- function clearAlerts() {
79
- const iframe = document.getElementById("content-iframe");
80
- const alertsArea =
81
- iframe.contentWindow.document.getElementById("toasts-area");
82
- alertsArea.innerHTML = "";
83
- }
84
-
85
- function showAlerts(alerts, toast = true) {
86
- if (typeof saltcorn === "undefined") {
87
- console.log("Not yet initalized.");
88
- console.log(alerts);
89
- } else {
90
- const iframe = document.getElementById("content-iframe");
91
- let area = iframe.contentWindow.document.getElementById(
92
- toast ? "toasts-area" : "top-alert"
93
- );
94
- if (!area) {
95
- const areaHtml = `<div class="container">
96
- <div
97
- id="toasts-area"
98
- class="toast-container position-fixed bottom-0 start-50 p-0"
99
- style="z-index: 9999;"
100
- aria-live="polite"
101
- aria-atomic="true">
102
- </div>
103
- </div>`;
104
- iframe.contentWindow.document
105
- .getElementById("page-inner-content")
106
- .insertAdjacentHTML("beforeend", areaHtml);
107
- area = iframe.contentWindow.document.getElementById(
108
- toast ? "toasts-area" : "top-alert"
109
- );
110
- }
111
-
112
- const successIds = [];
113
- area.innerHTML = "";
114
- for (const { type, msg } of alerts) {
115
- if (toast) {
116
- const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
117
- area.innerHTML += saltcorn.markup.toast(type, msg, rndid);
118
- if (type === "success") successIds.push(rndid);
119
- } else area.innerHTML += saltcorn.markup.alert(type, msg);
120
- }
121
- if (successIds.length > 0) {
122
- setTimeout(() => {
123
- for (const id of successIds) {
124
- const toastEl = iframe.contentWindow.document.getElementById(id);
125
- if (toastEl) $(toastEl).removeClass("show");
126
- }
127
- }, 5000);
128
- }
129
- }
130
- return true;
131
- }
132
-
133
- function clearTopAlerts() {
134
- const iframe = document.getElementById("content-iframe");
135
- const area = iframe.contentWindow.document.getElementById("alerts-area");
136
- if (area) area.innerHTML = "";
137
- const topAlert = iframe.contentWindow.document.getElementById("top-alert");
138
- if (topAlert) topAlert.innerHTML = "";
139
- }
140
-
141
- // TODO combine with loadEncodedFile
142
- async function loadFileAsText(fileId) {
143
- try {
144
- const response = await apiCall({
145
- method: "GET",
146
- path: `/files/download/${fileId}`,
147
- responseType: "blob",
148
- });
149
- return new Promise((resolve, reject) => {
150
- const reader = new FileReader();
151
- reader.onloadend = () => {
152
- return resolve(reader.result);
153
- };
154
- reader.onerror = (error) => {
155
- return reject(error);
156
- };
157
- reader.readAsText(response.data);
158
- });
159
- } catch (error) {
160
- if (
161
- !showAlerts([
162
- {
163
- type: "error",
164
- msg: error.message ? error.message : "An error occured.",
165
- },
166
- ])
167
- );
168
- throw error;
169
- }
170
- }
171
-
172
- async function loadEncodedFile(fileId) {
173
- try {
174
- const response = await apiCall({
175
- method: "GET",
176
- path: `/files/download/${fileId}`,
177
- responseType: "blob",
178
- });
179
- return new Promise((resolve, reject) => {
180
- const reader = new FileReader();
181
- reader.onloadend = () => {
182
- return resolve(reader.result);
183
- };
184
- reader.onerror = (error) => {
185
- return reject(error);
186
- };
187
- reader.readAsDataURL(response.data);
188
- });
189
- } catch (error) {
190
- showAlerts([
191
- {
192
- type: "error",
193
- msg: error.message ? error.message : "An error occured.",
194
- },
195
- ]);
196
- }
197
- }
198
-
199
- function splitPathQuery(url) {
200
- let path = url;
201
- let query = undefined;
202
- const queryStart = url.indexOf("?");
203
- if (queryStart > 0) {
204
- path = url.substring(0, queryStart);
205
- query = url.substring(queryStart);
206
- }
207
- return { path, query };
208
- }
209
-
210
- async function replaceIframe(content, isFile = false) {
58
+ export async function goBack(steps = 1, exitOnFirstPage = false) {
59
+ const { inLoadState } = saltcorn.data.state.getState().mobileConfig;
60
+ if (inLoadState) return;
211
61
  const iframe = document.getElementById("content-iframe");
212
- iframe.srcdoc = content;
213
- if (isFile) {
214
- iframe.setAttribute("is-html-file", true);
215
- await new Promise((resolve, reject) => {
216
- iframe.onload = () => {
217
- try {
218
- const _iframe = document.getElementById("content-iframe");
219
- const iframeDoc = _iframe.contentWindow.document;
220
- const baseEl = iframeDoc.createElement("base");
221
- iframeDoc.head.appendChild(baseEl);
222
- baseEl.href = "http://localhost";
223
- const scriptEl = iframeDoc.createElement("script");
224
- iframeDoc.body.appendChild(scriptEl);
225
- scriptEl.onload = () => {
226
- resolve();
227
- };
228
- scriptEl.src = "js/utils/iframe_view_utils.js";
229
- } catch (e) {
230
- reject(e);
231
- }
232
- };
233
- iframe.onerror = () => {
234
- reject();
235
- };
236
- });
237
- } else iframe.setAttribute("is-html-file", false);
238
- }
239
-
240
- function addScriptToIframeHead(iframeDoc, script) {
241
- return new Promise((resolve, reject) => {
242
- const srcAttr = script.attributes.getNamedItem("src").value;
243
- const existingScripts = iframeDoc.head.getElementsByTagName("script");
244
- for (const existing of existingScripts) {
245
- const existingSrc = existing.attributes.getNamedItem("src");
246
- if (existingSrc && existingSrc.value === srcAttr) return resolve(); // already there
62
+ if (
63
+ routingHistory.length === 0 ||
64
+ (exitOnFirstPage && routingHistory.length === 1)
65
+ ) {
66
+ navigator.app.exitApp();
67
+ } else if (routingHistory.length <= steps) {
68
+ try {
69
+ if (iframe?.contentWindow?.showLoadSpinner)
70
+ iframe.contentWindow.showLoadSpinner();
71
+ routingHistory = [];
72
+ await handleRoute("/");
73
+ } finally {
74
+ if (iframe?.contentWindow?.removeLoadSpinner)
75
+ iframe.contentWindow.removeLoadSpinner();
247
76
  }
248
- const scriptEl = iframeDoc.createElement("script");
249
- iframeDoc.head.appendChild(scriptEl);
250
- scriptEl.onload = () => {
251
- resolve();
252
- };
253
- scriptEl.src = srcAttr;
254
- });
255
- }
256
-
257
- async function replaceIframeInnerContent(content) {
258
- const iframe = document.getElementById("content-iframe");
259
- const iframeDocument = iframe.contentWindow.document;
260
- const modal = iframeDocument.getElementById("scmodal");
261
- if (modal) modal.remove();
262
- const innerContentDiv = iframeDocument.getElementById("page-inner-content");
263
- innerContentDiv.innerHTML = content;
264
- const scripts = innerContentDiv.getElementsByTagName("script");
265
- for (const script of scripts) {
266
- if (script.attributes.getNamedItem("src")) {
267
- await addScriptToIframeHead(iframe.contentWindow.document, script);
268
- } else {
269
- iframe.contentWindow.eval(script.innerHTML);
77
+ } else {
78
+ try {
79
+ if (iframe?.contentWindow?.showLoadSpinner)
80
+ iframe.contentWindow.showLoadSpinner();
81
+ routingHistory = routingHistory.slice(0, routingHistory.length - steps);
82
+ // don't repeat a post
83
+ if (routingHistory[routingHistory.length - 1].route.startsWith("post/")) {
84
+ routingHistory.pop();
85
+ }
86
+ const newCurrent = routingHistory.pop();
87
+ await handleRoute(newCurrent.route, newCurrent.query);
88
+ } finally {
89
+ if (iframe?.contentWindow?.removeLoadSpinner)
90
+ iframe.contentWindow.removeLoadSpinner();
270
91
  }
271
92
  }
272
- const scmodal = iframe.contentWindow.$("#scmodal");
273
- if (scmodal) {
274
- scmodal.modal("hide");
275
- }
276
- iframe.contentWindow.scrollTo(0, 0);
277
- iframe.contentWindow.initialize_page();
278
93
  }
279
-
280
94
  function clearContentDiv() {
281
95
  const iframe = document.getElementById("content-iframe");
282
96
  if (iframe) {
@@ -286,53 +100,12 @@ function clearContentDiv() {
286
100
  }
287
101
  }
288
102
 
289
- async function gotoEntryView() {
290
- const mobileConfig = saltcorn.data.state.getState().mobileConfig;
291
- try {
292
- if (mobileConfig.inErrorState) window.location.reload(true);
293
- else if (
294
- mobileConfig.networkState === "none" &&
295
- mobileConfig.allowOfflineMode &&
296
- !mobileConfig.isOfflineMode
297
- ) {
298
- await offlineHelper.startOfflineMode();
299
- clearHistory();
300
- }
301
- const page = await router.resolve({
302
- pathname: mobileConfig.entry_point,
303
- alerts: [],
304
- });
305
- addRoute({ route: mobileConfig.entry_point, query: undefined });
306
- await replaceIframeInnerContent(page.content);
307
- } catch (error) {
308
- showAlerts([
309
- {
310
- type: "error",
311
- msg: error.message ? error.message : "An error occured.",
312
- },
313
- ]);
314
- }
315
- }
316
-
317
- function handleOpenModal() {
318
- const result = { moddalWasOpen: false, noSubmitReload: false };
319
- const iframe = document.getElementById("content-iframe");
320
- if (!iframe) return result;
321
- const openModal = iframe.contentWindow.$("#scmodal.modal.show");
322
- if (openModal.length === 0) return result;
323
- result.moddalWasOpen = true;
324
- iframe.contentWindow.bootstrap.Modal.getInstance(openModal[0]).hide();
325
- result.noSubmitReload = openModal[0].classList.contains("no-submit-reload");
326
- return result;
327
- }
328
-
329
103
  function isModalOpen() {
330
104
  const iframe = document.getElementById("content-iframe");
331
105
  if (!iframe) return false;
332
106
  const openModal = iframe.contentWindow.$("#scmodal.modal.show");
333
107
  return openModal.length > 0;
334
108
  }
335
-
336
109
  function replaceModalContent(content) {
337
110
  const iframe = document.getElementById("content-iframe");
338
111
  if (!iframe) throw new Error("No iframe found");
@@ -343,12 +116,7 @@ function replaceModalContent(content) {
343
116
  modalBody.html(content);
344
117
  }
345
118
 
346
- function isHtmlFile() {
347
- const iframe = document.getElementById("content-iframe");
348
- return iframe.getAttribute("is-html-file") === "true";
349
- }
350
-
351
- async function handleRoute(route, query, files, data) {
119
+ export async function handleRoute(route, query, files, data) {
352
120
  const mobileConfig = saltcorn.data.state.getState().mobileConfig;
353
121
  let routeAdded = false;
354
122
  const isModal = isModalOpen();
@@ -359,7 +127,7 @@ async function handleRoute(route, query, files, data) {
359
127
  mobileConfig.allowOfflineMode &&
360
128
  !mobileConfig.isOfflineMode
361
129
  ) {
362
- await offlineHelper.startOfflineMode();
130
+ await startOfflineMode();
363
131
  clearHistory();
364
132
  await gotoEntryView();
365
133
  } else {
@@ -395,7 +163,8 @@ async function handleRoute(route, query, files, data) {
395
163
  );
396
164
  }
397
165
  } else if (page.content) {
398
- if (isModal) replaceModalContent(page.content);
166
+ if (isModal && route?.startsWith("post"))
167
+ replaceModalContent(page.content);
399
168
  else if (!page.replaceIframe)
400
169
  await replaceIframeInnerContent(page.content);
401
170
  else await replaceIframe(page.content, page.isFile);
@@ -425,66 +194,137 @@ async function handleRoute(route, query, files, data) {
425
194
  }
426
195
  }
427
196
 
197
+ function handleOpenModal() {
198
+ const result = { moddalWasOpen: false, noSubmitReload: false };
199
+ const iframe = document.getElementById("content-iframe");
200
+ if (!iframe) return result;
201
+ const openModal = iframe.contentWindow.$("#scmodal.modal.show");
202
+ if (openModal.length === 0) return result;
203
+ result.moddalWasOpen = true;
204
+ iframe.contentWindow.bootstrap.Modal.getInstance(openModal[0]).hide();
205
+ result.noSubmitReload = openModal[0].classList.contains("no-submit-reload");
206
+ return result;
207
+ }
208
+
209
+ export function isHtmlFile() {
210
+ const iframe = document.getElementById("content-iframe");
211
+ return iframe.getAttribute("is-html-file") === "true";
212
+ }
213
+
428
214
  async function reload() {
429
215
  const currentRoute = currentLocation();
430
216
  if (!currentRoute) await gotoEntryView();
431
217
  await handleRoute(currentRoute, currentQuery(true));
432
218
  }
433
219
 
434
- async function goBack(steps = 1, exitOnFirstPage = false) {
435
- const { inLoadState } = saltcorn.data.state.getState().mobileConfig;
436
- if (inLoadState) return;
437
- const iframe = document.getElementById("content-iframe");
438
- if (
439
- routingHistory.length === 0 ||
440
- (exitOnFirstPage && routingHistory.length === 1)
441
- ) {
442
- navigator.app.exitApp();
443
- } else if (routingHistory.length <= steps) {
444
- try {
445
- if (iframe?.contentWindow?.showLoadSpinner)
446
- iframe.contentWindow.showLoadSpinner();
447
- routingHistory = [];
448
- await handleRoute("/");
449
- } finally {
450
- if (iframe?.contentWindow?.removeLoadSpinner)
451
- iframe.contentWindow.removeLoadSpinner();
452
- }
453
- } else {
454
- try {
455
- if (iframe?.contentWindow?.showLoadSpinner)
456
- iframe.contentWindow.showLoadSpinner();
457
- routingHistory = routingHistory.slice(0, routingHistory.length - steps);
458
- // don't repeat a post
459
- if (routingHistory[routingHistory.length - 1].route.startsWith("post/")) {
460
- routingHistory.pop();
461
- }
462
- const newCurrent = routingHistory.pop();
463
- await handleRoute(newCurrent.route, newCurrent.query);
464
- } finally {
465
- if (iframe?.contentWindow?.removeLoadSpinner)
466
- iframe.contentWindow.removeLoadSpinner();
220
+ export async function gotoEntryView() {
221
+ const mobileConfig = saltcorn.data.state.getState().mobileConfig;
222
+ try {
223
+ if (mobileConfig.inErrorState) window.location.reload(true);
224
+ else if (
225
+ mobileConfig.networkState === "none" &&
226
+ mobileConfig.allowOfflineMode &&
227
+ !mobileConfig.isOfflineMode
228
+ ) {
229
+ await startOfflineMode();
230
+ clearHistory();
467
231
  }
232
+ const page = await router.resolve({
233
+ pathname: mobileConfig.entry_point,
234
+ alerts: [],
235
+ });
236
+ addRoute({ route: mobileConfig.entry_point, query: undefined });
237
+ await replaceIframeInnerContent(page.content);
238
+ } catch (error) {
239
+ showAlerts([
240
+ {
241
+ type: "error",
242
+ msg: error.message ? error.message : "An error occured.",
243
+ },
244
+ ]);
468
245
  }
469
246
  }
470
247
 
471
- function errorAlert(error) {
472
- showAlerts([
473
- {
474
- type: "error",
475
- msg: error.message ? error.message : "An error occured.",
476
- },
477
- ]);
478
- console.error(error);
248
+ export async function replaceIframe(content, isFile = false) {
249
+ const iframe = document.getElementById("content-iframe");
250
+ iframe.srcdoc = content;
251
+ if (isFile) {
252
+ iframe.setAttribute("is-html-file", true);
253
+ await new Promise((resolve, reject) => {
254
+ iframe.onload = () => {
255
+ try {
256
+ const _iframe = document.getElementById("content-iframe");
257
+ const iframeDoc = _iframe.contentWindow.document;
258
+ const baseEl = iframeDoc.createElement("base");
259
+ iframeDoc.head.appendChild(baseEl);
260
+ baseEl.href = "http://localhost";
261
+ const scriptEl = iframeDoc.createElement("script");
262
+ iframeDoc.body.appendChild(scriptEl);
263
+ scriptEl.onload = () => {
264
+ resolve();
265
+ };
266
+ scriptEl.src = "js/iframe_view_utils.js";
267
+ } catch (e) {
268
+ reject(e);
269
+ }
270
+ };
271
+ iframe.onerror = () => {
272
+ reject();
273
+ };
274
+ });
275
+ } else iframe.setAttribute("is-html-file", false);
479
276
  }
480
277
 
481
- async function checkJWT(jwt) {
482
- if (jwt && jwt !== "undefined") {
483
- const response = await apiCall({
484
- method: "GET",
485
- path: "/auth/authenticated",
486
- timeout: 10000,
487
- });
488
- return response.data.authenticated;
489
- } else return false;
278
+ export function addScriptToIframeHead(iframeDoc, script) {
279
+ return new Promise((resolve /*reject*/) => {
280
+ const srcAttr = script.attributes.getNamedItem("src").value;
281
+ const existingScripts = iframeDoc.head.getElementsByTagName("script");
282
+ for (const existing of existingScripts) {
283
+ const existingSrc = existing.attributes.getNamedItem("src");
284
+ if (existingSrc && existingSrc.value === srcAttr) return resolve(); // already there
285
+ }
286
+ const scriptEl = iframeDoc.createElement("script");
287
+ iframeDoc.head.appendChild(scriptEl);
288
+ scriptEl.onload = () => {
289
+ resolve();
290
+ };
291
+ scriptEl.src = srcAttr;
292
+ });
293
+ }
294
+
295
+ export async function replaceIframeInnerContent(content) {
296
+ const iframe = document.getElementById("content-iframe");
297
+ const iframeDocument = iframe.contentWindow.document;
298
+ if (iframe.contentWindow.$) {
299
+ const scmodal = iframe.contentWindow.$("#scmodal");
300
+ if (scmodal.length > 0) {
301
+ scmodal.modal("hide");
302
+ scmodal.remove();
303
+ }
304
+ }
305
+ const modal = iframeDocument.getElementById("scmodal");
306
+ if (modal) modal.remove();
307
+ const innerContentDiv = iframeDocument.getElementById("page-inner-content");
308
+ innerContentDiv.innerHTML = content;
309
+ const scripts = innerContentDiv.getElementsByTagName("script");
310
+ for (const script of scripts) {
311
+ if (script.attributes.getNamedItem("src")) {
312
+ await addScriptToIframeHead(iframe.contentWindow.document, script);
313
+ } else {
314
+ iframe.contentWindow.eval(script.innerHTML);
315
+ }
316
+ }
317
+ iframe.contentWindow.scrollTo(0, 0);
318
+ iframe.contentWindow.initialize_page();
319
+ }
320
+
321
+ export function splitPathQuery(url) {
322
+ let path = url;
323
+ let query = undefined;
324
+ const queryStart = url.indexOf("?");
325
+ if (queryStart > 0) {
326
+ path = url.substring(0, queryStart);
327
+ query = url.substring(queryStart);
328
+ }
329
+ return { path, query };
490
330
  }