@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.
- package/.babelrc +3 -0
- package/build_scripts/modify_android_manifest.js +47 -0
- package/build_scripts/modify_gradle_cfg.js +34 -0
- package/package.json +20 -11
- package/src/.eslintrc +21 -0
- package/src/helpers/api.js +43 -0
- package/src/helpers/auth.js +191 -0
- package/src/helpers/common.js +175 -0
- package/{www/js/utils/table_utils.js → src/helpers/db_schema.js} +18 -40
- package/src/helpers/file_system.js +102 -0
- package/{www/js/utils/global_utils.js → src/helpers/navigation.js} +175 -335
- package/src/helpers/offline_mode.js +645 -0
- package/src/index.js +20 -0
- package/src/init.js +424 -0
- package/src/routing/index.js +98 -0
- package/{www/js → src/routing}/mocks/request.js +5 -5
- package/{www/js → src/routing}/mocks/response.js +1 -1
- package/{www/js → src/routing}/routes/api.js +10 -15
- package/{www/js → src/routing}/routes/auth.js +12 -6
- package/{www/js → src/routing}/routes/delete.js +9 -6
- package/{www/js → src/routing}/routes/edit.js +9 -6
- package/src/routing/routes/error.js +6 -0
- package/{www/js → src/routing}/routes/fields.js +7 -2
- package/{www/js → src/routing}/routes/page.js +14 -9
- package/{www/js → src/routing}/routes/sync.js +9 -5
- package/{www/js → src/routing}/routes/view.js +16 -11
- package/{www/js/routes/common.js → src/routing/utils.js} +15 -13
- package/webpack.config.js +31 -0
- package/www/data/encoded_site_logo.js +1 -0
- package/www/index.html +23 -493
- package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +187 -273
- package/config.xml +0 -27
- package/res/icon/android/icon.png +0 -0
- package/res/screen/android/splash-icon.png +0 -0
- package/res/screen/ios/Default@2x~universal~anyany.png +0 -0
- package/www/js/routes/error.js +0 -5
- package/www/js/routes/init.js +0 -76
- package/www/js/utils/file_helpers.js +0 -108
- 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
|
|
1
|
+
/*global saltcorn*/
|
|
2
|
+
import i18next from "i18next";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
import { router } from "../routing/index";
|
|
5
|
+
import { startOfflineMode } from "./offline_mode";
|
|
6
|
+
import { showAlerts } from "./common";
|
|
4
7
|
|
|
5
|
-
|
|
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
|
|
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
|
|
48
|
-
const
|
|
49
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
}
|