@saltcorn/mobile-app 1.1.0-beta.11 → 1.1.0-beta.13
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 +41 -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} +169 -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 -491
- package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +137 -269
- 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,7 @@ function currentLocation() {
|
|
|
11
16
|
return routingHistory[index].route;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
function currentQuery(skipPosts = false) {
|
|
19
|
+
export function currentQuery(skipPosts = false) {
|
|
15
20
|
if (routingHistory.length == 0) return undefined;
|
|
16
21
|
let index = routingHistory.length - 1;
|
|
17
22
|
if (skipPosts)
|
|
@@ -21,7 +26,7 @@ function currentQuery(skipPosts = false) {
|
|
|
21
26
|
return routingHistory[index].query;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
|
-
function addQueryParam(key, value) {
|
|
29
|
+
export function addQueryParam(key, value) {
|
|
25
30
|
let query = currentQuery();
|
|
26
31
|
if (!query) {
|
|
27
32
|
routingHistory[routingHistory.length - 1].query = `${key}=${value}`;
|
|
@@ -32,251 +37,54 @@ function addQueryParam(key, value) {
|
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
function addRoute(routeEntry) {
|
|
40
|
+
export function addRoute(routeEntry) {
|
|
36
41
|
routingHistory.push(routeEntry);
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
function clearHistory() {
|
|
44
|
+
export function clearHistory() {
|
|
40
45
|
routingHistory = [];
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
function popRoute() {
|
|
48
|
+
export function popRoute() {
|
|
44
49
|
routingHistory.pop();
|
|
45
50
|
}
|
|
46
51
|
|
|
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) {
|
|
52
|
+
export async function goBack(steps = 1, exitOnFirstPage = false) {
|
|
53
|
+
const { inLoadState } = saltcorn.data.state.getState().mobileConfig;
|
|
54
|
+
if (inLoadState) return;
|
|
211
55
|
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
|
|
56
|
+
if (
|
|
57
|
+
routingHistory.length === 0 ||
|
|
58
|
+
(exitOnFirstPage && routingHistory.length === 1)
|
|
59
|
+
) {
|
|
60
|
+
navigator.app.exitApp();
|
|
61
|
+
} else if (routingHistory.length <= steps) {
|
|
62
|
+
try {
|
|
63
|
+
if (iframe?.contentWindow?.showLoadSpinner)
|
|
64
|
+
iframe.contentWindow.showLoadSpinner();
|
|
65
|
+
routingHistory = [];
|
|
66
|
+
await handleRoute("/");
|
|
67
|
+
} finally {
|
|
68
|
+
if (iframe?.contentWindow?.removeLoadSpinner)
|
|
69
|
+
iframe.contentWindow.removeLoadSpinner();
|
|
247
70
|
}
|
|
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);
|
|
71
|
+
} else {
|
|
72
|
+
try {
|
|
73
|
+
if (iframe?.contentWindow?.showLoadSpinner)
|
|
74
|
+
iframe.contentWindow.showLoadSpinner();
|
|
75
|
+
routingHistory = routingHistory.slice(0, routingHistory.length - steps);
|
|
76
|
+
// don't repeat a post
|
|
77
|
+
if (routingHistory[routingHistory.length - 1].route.startsWith("post/")) {
|
|
78
|
+
routingHistory.pop();
|
|
79
|
+
}
|
|
80
|
+
const newCurrent = routingHistory.pop();
|
|
81
|
+
await handleRoute(newCurrent.route, newCurrent.query);
|
|
82
|
+
} finally {
|
|
83
|
+
if (iframe?.contentWindow?.removeLoadSpinner)
|
|
84
|
+
iframe.contentWindow.removeLoadSpinner();
|
|
270
85
|
}
|
|
271
86
|
}
|
|
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
87
|
}
|
|
279
|
-
|
|
280
88
|
function clearContentDiv() {
|
|
281
89
|
const iframe = document.getElementById("content-iframe");
|
|
282
90
|
if (iframe) {
|
|
@@ -286,53 +94,12 @@ function clearContentDiv() {
|
|
|
286
94
|
}
|
|
287
95
|
}
|
|
288
96
|
|
|
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
97
|
function isModalOpen() {
|
|
330
98
|
const iframe = document.getElementById("content-iframe");
|
|
331
99
|
if (!iframe) return false;
|
|
332
100
|
const openModal = iframe.contentWindow.$("#scmodal.modal.show");
|
|
333
101
|
return openModal.length > 0;
|
|
334
102
|
}
|
|
335
|
-
|
|
336
103
|
function replaceModalContent(content) {
|
|
337
104
|
const iframe = document.getElementById("content-iframe");
|
|
338
105
|
if (!iframe) throw new Error("No iframe found");
|
|
@@ -343,12 +110,7 @@ function replaceModalContent(content) {
|
|
|
343
110
|
modalBody.html(content);
|
|
344
111
|
}
|
|
345
112
|
|
|
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) {
|
|
113
|
+
export async function handleRoute(route, query, files, data) {
|
|
352
114
|
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
353
115
|
let routeAdded = false;
|
|
354
116
|
const isModal = isModalOpen();
|
|
@@ -359,7 +121,7 @@ async function handleRoute(route, query, files, data) {
|
|
|
359
121
|
mobileConfig.allowOfflineMode &&
|
|
360
122
|
!mobileConfig.isOfflineMode
|
|
361
123
|
) {
|
|
362
|
-
await
|
|
124
|
+
await startOfflineMode();
|
|
363
125
|
clearHistory();
|
|
364
126
|
await gotoEntryView();
|
|
365
127
|
} else {
|
|
@@ -395,7 +157,8 @@ async function handleRoute(route, query, files, data) {
|
|
|
395
157
|
);
|
|
396
158
|
}
|
|
397
159
|
} else if (page.content) {
|
|
398
|
-
if (isModal
|
|
160
|
+
if (isModal && route?.startsWith("post"))
|
|
161
|
+
replaceModalContent(page.content);
|
|
399
162
|
else if (!page.replaceIframe)
|
|
400
163
|
await replaceIframeInnerContent(page.content);
|
|
401
164
|
else await replaceIframe(page.content, page.isFile);
|
|
@@ -425,66 +188,137 @@ async function handleRoute(route, query, files, data) {
|
|
|
425
188
|
}
|
|
426
189
|
}
|
|
427
190
|
|
|
191
|
+
function handleOpenModal() {
|
|
192
|
+
const result = { moddalWasOpen: false, noSubmitReload: false };
|
|
193
|
+
const iframe = document.getElementById("content-iframe");
|
|
194
|
+
if (!iframe) return result;
|
|
195
|
+
const openModal = iframe.contentWindow.$("#scmodal.modal.show");
|
|
196
|
+
if (openModal.length === 0) return result;
|
|
197
|
+
result.moddalWasOpen = true;
|
|
198
|
+
iframe.contentWindow.bootstrap.Modal.getInstance(openModal[0]).hide();
|
|
199
|
+
result.noSubmitReload = openModal[0].classList.contains("no-submit-reload");
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function isHtmlFile() {
|
|
204
|
+
const iframe = document.getElementById("content-iframe");
|
|
205
|
+
return iframe.getAttribute("is-html-file") === "true";
|
|
206
|
+
}
|
|
207
|
+
|
|
428
208
|
async function reload() {
|
|
429
209
|
const currentRoute = currentLocation();
|
|
430
210
|
if (!currentRoute) await gotoEntryView();
|
|
431
211
|
await handleRoute(currentRoute, currentQuery(true));
|
|
432
212
|
}
|
|
433
213
|
|
|
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();
|
|
214
|
+
export async function gotoEntryView() {
|
|
215
|
+
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
216
|
+
try {
|
|
217
|
+
if (mobileConfig.inErrorState) window.location.reload(true);
|
|
218
|
+
else if (
|
|
219
|
+
mobileConfig.networkState === "none" &&
|
|
220
|
+
mobileConfig.allowOfflineMode &&
|
|
221
|
+
!mobileConfig.isOfflineMode
|
|
222
|
+
) {
|
|
223
|
+
await startOfflineMode();
|
|
224
|
+
clearHistory();
|
|
467
225
|
}
|
|
226
|
+
const page = await router.resolve({
|
|
227
|
+
pathname: mobileConfig.entry_point,
|
|
228
|
+
alerts: [],
|
|
229
|
+
});
|
|
230
|
+
addRoute({ route: mobileConfig.entry_point, query: undefined });
|
|
231
|
+
await replaceIframeInnerContent(page.content);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
showAlerts([
|
|
234
|
+
{
|
|
235
|
+
type: "error",
|
|
236
|
+
msg: error.message ? error.message : "An error occured.",
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
468
239
|
}
|
|
469
240
|
}
|
|
470
241
|
|
|
471
|
-
function
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
242
|
+
export async function replaceIframe(content, isFile = false) {
|
|
243
|
+
const iframe = document.getElementById("content-iframe");
|
|
244
|
+
iframe.srcdoc = content;
|
|
245
|
+
if (isFile) {
|
|
246
|
+
iframe.setAttribute("is-html-file", true);
|
|
247
|
+
await new Promise((resolve, reject) => {
|
|
248
|
+
iframe.onload = () => {
|
|
249
|
+
try {
|
|
250
|
+
const _iframe = document.getElementById("content-iframe");
|
|
251
|
+
const iframeDoc = _iframe.contentWindow.document;
|
|
252
|
+
const baseEl = iframeDoc.createElement("base");
|
|
253
|
+
iframeDoc.head.appendChild(baseEl);
|
|
254
|
+
baseEl.href = "http://localhost";
|
|
255
|
+
const scriptEl = iframeDoc.createElement("script");
|
|
256
|
+
iframeDoc.body.appendChild(scriptEl);
|
|
257
|
+
scriptEl.onload = () => {
|
|
258
|
+
resolve();
|
|
259
|
+
};
|
|
260
|
+
scriptEl.src = "js/iframe_view_utils.js";
|
|
261
|
+
} catch (e) {
|
|
262
|
+
reject(e);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
iframe.onerror = () => {
|
|
266
|
+
reject();
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
} else iframe.setAttribute("is-html-file", false);
|
|
479
270
|
}
|
|
480
271
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
272
|
+
export function addScriptToIframeHead(iframeDoc, script) {
|
|
273
|
+
return new Promise((resolve /*reject*/) => {
|
|
274
|
+
const srcAttr = script.attributes.getNamedItem("src").value;
|
|
275
|
+
const existingScripts = iframeDoc.head.getElementsByTagName("script");
|
|
276
|
+
for (const existing of existingScripts) {
|
|
277
|
+
const existingSrc = existing.attributes.getNamedItem("src");
|
|
278
|
+
if (existingSrc && existingSrc.value === srcAttr) return resolve(); // already there
|
|
279
|
+
}
|
|
280
|
+
const scriptEl = iframeDoc.createElement("script");
|
|
281
|
+
iframeDoc.head.appendChild(scriptEl);
|
|
282
|
+
scriptEl.onload = () => {
|
|
283
|
+
resolve();
|
|
284
|
+
};
|
|
285
|
+
scriptEl.src = srcAttr;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function replaceIframeInnerContent(content) {
|
|
290
|
+
const iframe = document.getElementById("content-iframe");
|
|
291
|
+
const iframeDocument = iframe.contentWindow.document;
|
|
292
|
+
if (iframe.contentWindow.$) {
|
|
293
|
+
const scmodal = iframe.contentWindow.$("#scmodal");
|
|
294
|
+
if (scmodal.length > 0) {
|
|
295
|
+
scmodal.modal("hide");
|
|
296
|
+
scmodal.remove();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const modal = iframeDocument.getElementById("scmodal");
|
|
300
|
+
if (modal) modal.remove();
|
|
301
|
+
const innerContentDiv = iframeDocument.getElementById("page-inner-content");
|
|
302
|
+
innerContentDiv.innerHTML = content;
|
|
303
|
+
const scripts = innerContentDiv.getElementsByTagName("script");
|
|
304
|
+
for (const script of scripts) {
|
|
305
|
+
if (script.attributes.getNamedItem("src")) {
|
|
306
|
+
await addScriptToIframeHead(iframe.contentWindow.document, script);
|
|
307
|
+
} else {
|
|
308
|
+
iframe.contentWindow.eval(script.innerHTML);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
iframe.contentWindow.scrollTo(0, 0);
|
|
312
|
+
iframe.contentWindow.initialize_page();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function splitPathQuery(url) {
|
|
316
|
+
let path = url;
|
|
317
|
+
let query = undefined;
|
|
318
|
+
const queryStart = url.indexOf("?");
|
|
319
|
+
if (queryStart > 0) {
|
|
320
|
+
path = url.substring(0, queryStart);
|
|
321
|
+
query = url.substring(queryStart);
|
|
322
|
+
}
|
|
323
|
+
return { path, query };
|
|
490
324
|
}
|