@saltcorn/mobile-app 1.1.0-beta.9 → 1.1.1-beta.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/.babelrc +3 -0
- package/build_scripts/modify_android_manifest.js +47 -0
- package/build_scripts/modify_gradle_cfg.js +21 -0
- package/package.json +21 -12
- package/src/.eslintrc +21 -0
- package/src/helpers/api.js +43 -0
- package/src/helpers/auth.js +191 -0
- package/src/helpers/common.js +189 -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} +189 -332
- 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 +15 -10
- package/{www/js → src/routing}/routes/sync.js +17 -8
- package/{www/js → src/routing}/routes/view.js +20 -15
- package/{www/js/routes/common.js → src/routing/utils.js} +18 -13
- package/unsecure-default-key.jks +0 -0
- 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} +193 -274
- 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
package/.babelrc
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { parseStringPromise, Builder } = require("xml2js");
|
|
2
|
+
const { join } = require("path");
|
|
3
|
+
const { readFileSync, writeFileSync } = require("fs");
|
|
4
|
+
|
|
5
|
+
(async () => {
|
|
6
|
+
try {
|
|
7
|
+
const androidManifest = join(
|
|
8
|
+
"android",
|
|
9
|
+
"app",
|
|
10
|
+
"src",
|
|
11
|
+
"main",
|
|
12
|
+
"AndroidManifest.xml"
|
|
13
|
+
);
|
|
14
|
+
const content = readFileSync(androidManifest);
|
|
15
|
+
const parsed = await parseStringPromise(content);
|
|
16
|
+
|
|
17
|
+
parsed.manifest["uses-permission"] = [
|
|
18
|
+
{ $: { "android:name": "android.permission.READ_EXTERNAL_STORAGE" } },
|
|
19
|
+
{ $: { "android:name": "android.permission.WRITE_EXTERNAL_STORAGE" } },
|
|
20
|
+
{ $: { "android:name": "android.permission.INTERNET" } },
|
|
21
|
+
{ $: { "android:name": "android.permission.CAMERA" } },
|
|
22
|
+
{ $: { "android:name": "android.permission.ACCESS_COARSE_LOCATION" } },
|
|
23
|
+
{ $: { "android:name": "android.permission.ACCESS_FINE_LOCATION" } },
|
|
24
|
+
];
|
|
25
|
+
parsed.manifest["uses-feature"] = [
|
|
26
|
+
{ $: { "android:name": "android.hardware.location.gps" } },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
parsed.manifest.application[0].$ = {
|
|
30
|
+
...parsed.manifest.application[0].$,
|
|
31
|
+
"android:allowBackup": "false",
|
|
32
|
+
"android:fullBackupContent": "false",
|
|
33
|
+
"android:dataExtractionRules": "@xml/data_extraction_rules",
|
|
34
|
+
"android:networkSecurityConfig": "@xml/network_security_config",
|
|
35
|
+
"android:usesCleartextTraffic": "true",
|
|
36
|
+
};
|
|
37
|
+
const xmlBuilder = new Builder();
|
|
38
|
+
const newCfg = xmlBuilder.buildObject(parsed);
|
|
39
|
+
writeFileSync(androidManifest, newCfg);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.log(
|
|
42
|
+
`Unable to modify the AndroidManifest.xml: ${
|
|
43
|
+
error.message ? error.message : "Unknown error"
|
|
44
|
+
}`
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
})();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { join } = require("path");
|
|
2
|
+
const { readFileSync, writeFileSync } = require("fs");
|
|
3
|
+
|
|
4
|
+
console.log("Writing gradle config");
|
|
5
|
+
console.log("args", process.argv);
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const appVersion = args[0].split("=")[1];
|
|
8
|
+
|
|
9
|
+
const gradleFile = join(__dirname, "..", "android", "app", "build.gradle");
|
|
10
|
+
const gradleContent = readFileSync(gradleFile, "utf8");
|
|
11
|
+
|
|
12
|
+
// generate versionCode from appVersion
|
|
13
|
+
const parts = appVersion.split(".");
|
|
14
|
+
const versionCode =
|
|
15
|
+
parseInt(parts[0]) * 1000000 + parseInt(parts[1]) * 1000 + parseInt(parts[2]);
|
|
16
|
+
let newGradleContent = gradleContent
|
|
17
|
+
.replace(/versionName "1.0"/, `versionName "${appVersion}"`)
|
|
18
|
+
.replace(/versionCode 1/, `versionCode ${versionCode}`);
|
|
19
|
+
|
|
20
|
+
console.log("newGradleContent", newGradleContent);
|
|
21
|
+
writeFileSync(gradleFile, newGradleContent, "utf8");
|
package/package.json
CHANGED
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/mobile-app",
|
|
3
3
|
"displayName": "Saltcorn mobile app",
|
|
4
|
-
"version": "1.1.
|
|
5
|
-
"description": "
|
|
4
|
+
"version": "1.1.1-beta.0",
|
|
5
|
+
"description": "Saltcorn mobile app for Android and iOS",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"
|
|
9
|
-
"add-
|
|
10
|
-
"
|
|
8
|
+
"build": "webpack",
|
|
9
|
+
"add-platform": "npx cap add",
|
|
10
|
+
"modify-gradle-cfg": "node build_scripts/modify_gradle_cfg.js",
|
|
11
|
+
"modify-android-manifest": "node build_scripts/modify_android_manifest.js",
|
|
11
12
|
"test": "echo NO TESTS"
|
|
12
13
|
},
|
|
13
14
|
"author": "Christian Hugo",
|
|
14
15
|
"license": "MIT",
|
|
15
|
-
"cordova": {
|
|
16
|
-
"platforms": [
|
|
17
|
-
"android"
|
|
18
|
-
]
|
|
19
|
-
},
|
|
20
16
|
"overrides": {
|
|
21
17
|
"ansi-regex": "4.1.1"
|
|
22
18
|
},
|
|
23
|
-
"dependencies": {
|
|
24
|
-
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"universal-router": "9.2.0",
|
|
21
|
+
"axios": "^1.7.7",
|
|
22
|
+
"jwt-decode": "4.0.0",
|
|
23
|
+
"i18next": "23.16.4",
|
|
24
|
+
"i18next-sprintf-postprocessor": "0.2.2"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"xml2js": "0.6.0",
|
|
28
|
+
"@babel/core": "^7.26.0",
|
|
29
|
+
"@babel/preset-env": "^7.26.0",
|
|
30
|
+
"babel-loader": "^9.2.1",
|
|
31
|
+
"webpack": "^5.96.1",
|
|
32
|
+
"webpack-cli": "^5.1.4"
|
|
33
|
+
}
|
|
25
34
|
}
|
package/src/.eslintrc
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "eslint:recommended",
|
|
3
|
+
|
|
4
|
+
"parserOptions": {
|
|
5
|
+
"ecmaVersion": 2021,
|
|
6
|
+
"sourceType": "module",
|
|
7
|
+
|
|
8
|
+
// typescript-eslint specific options
|
|
9
|
+
"warnOnUnsupportedTypeScriptVersion": true
|
|
10
|
+
},
|
|
11
|
+
"env": {
|
|
12
|
+
"browser": true
|
|
13
|
+
},
|
|
14
|
+
"ignorePatterns": ["dist/**"],
|
|
15
|
+
"rules": {
|
|
16
|
+
"no-unused-vars": "warn",
|
|
17
|
+
"no-case-declarations": "off",
|
|
18
|
+
"no-empty": "warn",
|
|
19
|
+
"no-fallthrough": "warn"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*global saltcorn, splashConfig*/
|
|
2
|
+
|
|
3
|
+
import axios from "axios";
|
|
4
|
+
|
|
5
|
+
export async function apiCall({
|
|
6
|
+
method,
|
|
7
|
+
path,
|
|
8
|
+
params,
|
|
9
|
+
body,
|
|
10
|
+
responseType,
|
|
11
|
+
timeout,
|
|
12
|
+
additionalHeaders,
|
|
13
|
+
}) {
|
|
14
|
+
const config =
|
|
15
|
+
typeof saltcorn !== "undefined"
|
|
16
|
+
? saltcorn.data.state.getState().mobileConfig
|
|
17
|
+
: splashConfig;
|
|
18
|
+
const serverPath = config.server_path;
|
|
19
|
+
const url = `${serverPath}${path}`;
|
|
20
|
+
const headers = {
|
|
21
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
22
|
+
"X-Saltcorn-Client": "mobile-app",
|
|
23
|
+
...(additionalHeaders || {}),
|
|
24
|
+
};
|
|
25
|
+
if (config.tenantAppName) headers["X-Saltcorn-App"] = config.tenantAppName;
|
|
26
|
+
const token = config.jwt;
|
|
27
|
+
if (token) headers.Authorization = `jwt ${token}`;
|
|
28
|
+
try {
|
|
29
|
+
const result = await axios({
|
|
30
|
+
url: url,
|
|
31
|
+
method,
|
|
32
|
+
params,
|
|
33
|
+
headers,
|
|
34
|
+
responseType: responseType ? responseType : "json",
|
|
35
|
+
data: body,
|
|
36
|
+
timeout: timeout ? timeout : 0,
|
|
37
|
+
});
|
|
38
|
+
return result;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
error.message = `Unable to call ${method} ${url}:\n${error.message}`;
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/*global saltcorn*/
|
|
2
|
+
|
|
3
|
+
import { jwtDecode } from "jwt-decode";
|
|
4
|
+
import i18next from "i18next";
|
|
5
|
+
import { apiCall } from "./api";
|
|
6
|
+
import { router } from "../routing/index";
|
|
7
|
+
import { getLastOfflineSession, deleteOfflineData, sync } from "./offline_mode";
|
|
8
|
+
import { addRoute, replaceIframe } from "../helpers/navigation";
|
|
9
|
+
import { showAlerts } from "./common";
|
|
10
|
+
|
|
11
|
+
async function loginRequest({ email, password, isSignup, isPublic }) {
|
|
12
|
+
const opts = isPublic
|
|
13
|
+
? {
|
|
14
|
+
method: "GET",
|
|
15
|
+
path: "/auth/login-with/jwt",
|
|
16
|
+
}
|
|
17
|
+
: isSignup
|
|
18
|
+
? {
|
|
19
|
+
method: "POST",
|
|
20
|
+
path: "/auth/signup",
|
|
21
|
+
body: {
|
|
22
|
+
email,
|
|
23
|
+
password,
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
: {
|
|
27
|
+
method: "GET",
|
|
28
|
+
path: "/auth/login-with/jwt",
|
|
29
|
+
params: {
|
|
30
|
+
email,
|
|
31
|
+
password,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
const response = await apiCall(opts);
|
|
35
|
+
return response.data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function login({ email, password, entryPoint, isSignup }) {
|
|
39
|
+
const loginResult = await loginRequest({
|
|
40
|
+
email,
|
|
41
|
+
password,
|
|
42
|
+
isSignup,
|
|
43
|
+
});
|
|
44
|
+
if (typeof loginResult === "string") {
|
|
45
|
+
// use it as a token
|
|
46
|
+
const decodedJwt = jwtDecode(loginResult);
|
|
47
|
+
const config = saltcorn.data.state.getState().mobileConfig;
|
|
48
|
+
config.user = decodedJwt.user;
|
|
49
|
+
config.isPublicUser = false;
|
|
50
|
+
config.isOfflineMode = false;
|
|
51
|
+
await insertUser(config.user);
|
|
52
|
+
await setJwt(loginResult);
|
|
53
|
+
config.jwt = loginResult;
|
|
54
|
+
i18next.changeLanguage(config.user.language);
|
|
55
|
+
const alerts = [];
|
|
56
|
+
if (config.allowOfflineMode) {
|
|
57
|
+
const { offlineUser, hasOfflineData } =
|
|
58
|
+
(await getLastOfflineSession()) || {};
|
|
59
|
+
if (!offlineUser || offlineUser === config.user.email) {
|
|
60
|
+
await sync();
|
|
61
|
+
} else {
|
|
62
|
+
if (hasOfflineData)
|
|
63
|
+
alerts.push({
|
|
64
|
+
type: "warning",
|
|
65
|
+
msg: `'${offlineUser}' has not yet uploaded offline data.`,
|
|
66
|
+
});
|
|
67
|
+
else {
|
|
68
|
+
await deleteOfflineData(true);
|
|
69
|
+
await sync();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
alerts.push({
|
|
74
|
+
type: "success",
|
|
75
|
+
msg: i18next.t("Welcome, %s!", {
|
|
76
|
+
postProcess: "sprintf",
|
|
77
|
+
sprintf: [config.user.email],
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
addRoute({ route: entryPoint, query: undefined });
|
|
81
|
+
const page = await router.resolve({
|
|
82
|
+
pathname: entryPoint,
|
|
83
|
+
fullWrap: true,
|
|
84
|
+
alerts,
|
|
85
|
+
});
|
|
86
|
+
if (page.content) await replaceIframe(page.content, page.isFile);
|
|
87
|
+
} else if (loginResult?.alerts) {
|
|
88
|
+
showAlerts(loginResult?.alerts);
|
|
89
|
+
} else {
|
|
90
|
+
throw new Error("The login failed.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function publicLogin(entryPoint) {
|
|
95
|
+
try {
|
|
96
|
+
const loginResult = await loginRequest({ isPublic: true });
|
|
97
|
+
if (typeof loginResult === "string") {
|
|
98
|
+
const config = saltcorn.data.state.getState().mobileConfig;
|
|
99
|
+
config.user = {
|
|
100
|
+
role_id: 100,
|
|
101
|
+
email: "public",
|
|
102
|
+
language: "en",
|
|
103
|
+
};
|
|
104
|
+
config.isPublicUser = true;
|
|
105
|
+
await setJwt(loginResult);
|
|
106
|
+
config.jwt = loginResult;
|
|
107
|
+
i18next.changeLanguage(config.user.language);
|
|
108
|
+
addRoute({
|
|
109
|
+
route: entryPoint,
|
|
110
|
+
query: undefined,
|
|
111
|
+
});
|
|
112
|
+
const page = await router.resolve({
|
|
113
|
+
pathname: entryPoint,
|
|
114
|
+
fullWrap: true,
|
|
115
|
+
alerts: [
|
|
116
|
+
{
|
|
117
|
+
type: "success",
|
|
118
|
+
msg: i18next.t("Welcome to %s!", {
|
|
119
|
+
postProcess: "sprintf",
|
|
120
|
+
sprintf: [
|
|
121
|
+
saltcorn.data.state.getState().getConfig("site_name") ||
|
|
122
|
+
"Saltcorn",
|
|
123
|
+
],
|
|
124
|
+
}),
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
if (page.content) await replaceIframe(page.content, page.isFile);
|
|
129
|
+
} else if (loginResult?.alerts) {
|
|
130
|
+
showAlerts(loginResult?.alerts);
|
|
131
|
+
} else {
|
|
132
|
+
throw new Error("The login failed.");
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(error);
|
|
136
|
+
showAlerts([
|
|
137
|
+
{
|
|
138
|
+
type: "error",
|
|
139
|
+
msg: error.message ? error.message : "An error occured.",
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function logout() {
|
|
147
|
+
try {
|
|
148
|
+
const config = saltcorn.data.state.getState().mobileConfig;
|
|
149
|
+
const page = await router.resolve({
|
|
150
|
+
pathname: "get/auth/logout",
|
|
151
|
+
entryView: config.entry_point,
|
|
152
|
+
versionTag: config.version_tag,
|
|
153
|
+
});
|
|
154
|
+
await replaceIframe(page.content);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
showAlerts([
|
|
157
|
+
{
|
|
158
|
+
type: "error",
|
|
159
|
+
msg: error.message ? error.message : "An error occured.",
|
|
160
|
+
},
|
|
161
|
+
]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function insertUser({ id, email, role_id, language }) {
|
|
166
|
+
await saltcorn.data.db.insert(
|
|
167
|
+
"users",
|
|
168
|
+
{ id, email, role_id, language },
|
|
169
|
+
{ ignoreExisting: true }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function removeJwt() {
|
|
174
|
+
await saltcorn.data.db.deleteWhere("jwt_table");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function setJwt(jwt) {
|
|
178
|
+
await removeJwt();
|
|
179
|
+
await saltcorn.data.db.insert("jwt_table", { jwt: jwt });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export async function checkJWT(jwt) {
|
|
183
|
+
if (jwt && jwt !== "undefined") {
|
|
184
|
+
const response = await apiCall({
|
|
185
|
+
method: "GET",
|
|
186
|
+
path: "/auth/authenticated",
|
|
187
|
+
timeout: 10000,
|
|
188
|
+
});
|
|
189
|
+
return response.data.authenticated;
|
|
190
|
+
} else return false;
|
|
191
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/*global saltcorn, $*/
|
|
2
|
+
|
|
3
|
+
import { apiCall } from "./api";
|
|
4
|
+
import { Camera, CameraResultType } from "@capacitor/camera";
|
|
5
|
+
import { Geolocation } from "@capacitor/geolocation";
|
|
6
|
+
import { ScreenOrientation } from "@capacitor/screen-orientation";
|
|
7
|
+
|
|
8
|
+
const orientationChangeListeners = new Set();
|
|
9
|
+
|
|
10
|
+
export function clearAlerts() {
|
|
11
|
+
const iframe = document.getElementById("content-iframe");
|
|
12
|
+
const alertsArea =
|
|
13
|
+
iframe.contentWindow.document.getElementById("toasts-area");
|
|
14
|
+
alertsArea.innerHTML = "";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function showAlerts(alerts, toast = true) {
|
|
18
|
+
if (typeof saltcorn === "undefined") {
|
|
19
|
+
console.log("Not yet initalized.");
|
|
20
|
+
console.log(alerts);
|
|
21
|
+
} else {
|
|
22
|
+
const iframe = document.getElementById("content-iframe");
|
|
23
|
+
let area = iframe.contentWindow.document.getElementById(
|
|
24
|
+
toast ? "toasts-area" : "top-alert"
|
|
25
|
+
);
|
|
26
|
+
if (!area) {
|
|
27
|
+
const areaHtml = `<div class="container">
|
|
28
|
+
<div
|
|
29
|
+
id="toasts-area"
|
|
30
|
+
class="toast-container position-fixed bottom-0 start-50 p-0"
|
|
31
|
+
style="z-index: 9999;"
|
|
32
|
+
aria-live="polite"
|
|
33
|
+
aria-atomic="true">
|
|
34
|
+
</div>
|
|
35
|
+
</div>`;
|
|
36
|
+
iframe.contentWindow.document
|
|
37
|
+
.getElementById("page-inner-content")
|
|
38
|
+
.insertAdjacentHTML("beforeend", areaHtml);
|
|
39
|
+
area = iframe.contentWindow.document.getElementById(
|
|
40
|
+
toast ? "toasts-area" : "top-alert"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const successIds = [];
|
|
44
|
+
area.innerHTML = "";
|
|
45
|
+
for (const { type, msg } of alerts) {
|
|
46
|
+
if (toast) {
|
|
47
|
+
const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
|
|
48
|
+
area.innerHTML += saltcorn.markup.toast(type, msg, rndid);
|
|
49
|
+
if (type === "success") successIds.push(rndid);
|
|
50
|
+
} else area.innerHTML += saltcorn.markup.alert(type, msg);
|
|
51
|
+
}
|
|
52
|
+
if (successIds.length > 0) {
|
|
53
|
+
setTimeout(() => {
|
|
54
|
+
for (const id of successIds) {
|
|
55
|
+
const toastEl = iframe.contentWindow.document.getElementById(id);
|
|
56
|
+
if (toastEl) $(toastEl).removeClass("show");
|
|
57
|
+
}
|
|
58
|
+
}, 5000);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function showLoadSpinner() {
|
|
65
|
+
const iframe = document.getElementById("content-iframe");
|
|
66
|
+
if (iframe) iframe.contentWindow.showLoadSpinner();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function removeLoadSpinner() {
|
|
70
|
+
const iframe = document.getElementById("content-iframe");
|
|
71
|
+
if (iframe) iframe.contentWindow.removeLoadSpinner();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function clearTopAlerts() {
|
|
75
|
+
const iframe = document.getElementById("content-iframe");
|
|
76
|
+
const area = iframe.contentWindow.document.getElementById("alerts-area");
|
|
77
|
+
if (area) area.innerHTML = "";
|
|
78
|
+
const topAlert = iframe.contentWindow.document.getElementById("top-alert");
|
|
79
|
+
if (topAlert) topAlert.innerHTML = "";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function errorAlert(error) {
|
|
83
|
+
showAlerts([
|
|
84
|
+
{
|
|
85
|
+
type: "error",
|
|
86
|
+
msg: error.message ? error.message : "An error occured.",
|
|
87
|
+
},
|
|
88
|
+
]);
|
|
89
|
+
console.error(error);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// TODO combine with loadEncodedFile
|
|
93
|
+
export async function loadFileAsText(fileId) {
|
|
94
|
+
try {
|
|
95
|
+
const response = await apiCall({
|
|
96
|
+
method: "GET",
|
|
97
|
+
path: `/files/download/${fileId}`,
|
|
98
|
+
responseType: "blob",
|
|
99
|
+
});
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const reader = new FileReader();
|
|
102
|
+
reader.onloadend = () => {
|
|
103
|
+
return resolve(reader.result);
|
|
104
|
+
};
|
|
105
|
+
reader.onerror = (error) => {
|
|
106
|
+
return reject(error);
|
|
107
|
+
};
|
|
108
|
+
reader.readAsText(response.data);
|
|
109
|
+
});
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (
|
|
112
|
+
!showAlerts([
|
|
113
|
+
{
|
|
114
|
+
type: "error",
|
|
115
|
+
msg: error.message ? error.message : "An error occured.",
|
|
116
|
+
},
|
|
117
|
+
])
|
|
118
|
+
);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function loadEncodedFile(fileId) {
|
|
124
|
+
try {
|
|
125
|
+
const response = await apiCall({
|
|
126
|
+
method: "GET",
|
|
127
|
+
path: `/files/download/${fileId}`,
|
|
128
|
+
responseType: "blob",
|
|
129
|
+
});
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const reader = new FileReader();
|
|
132
|
+
reader.onloadend = () => {
|
|
133
|
+
return resolve(reader.result);
|
|
134
|
+
};
|
|
135
|
+
reader.onerror = (error) => {
|
|
136
|
+
return reject(error);
|
|
137
|
+
};
|
|
138
|
+
reader.readAsDataURL(response.data);
|
|
139
|
+
});
|
|
140
|
+
} catch (error) {
|
|
141
|
+
showAlerts([
|
|
142
|
+
{
|
|
143
|
+
type: "error",
|
|
144
|
+
msg: error.message ? error.message : "An error occured.",
|
|
145
|
+
},
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function takePhoto() {
|
|
151
|
+
try {
|
|
152
|
+
const image = await Camera.getPhoto({
|
|
153
|
+
quality: 90,
|
|
154
|
+
allowEditing: false,
|
|
155
|
+
resultType: CameraResultType.Uri,
|
|
156
|
+
});
|
|
157
|
+
return image.path;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
showAlerts([
|
|
160
|
+
{
|
|
161
|
+
type: "error",
|
|
162
|
+
msg: error.message ? error.message : "An error occured.",
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function getGeolocation(successCb, errorCb) {
|
|
170
|
+
try {
|
|
171
|
+
const coordinates = await Geolocation.getCurrentPosition();
|
|
172
|
+
if (successCb) successCb(coordinates);
|
|
173
|
+
return coordinates;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (errorCb) errorCb(error);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function registerScreenOrientationListener(name, listener) {
|
|
181
|
+
if (!orientationChangeListeners.has(name)) {
|
|
182
|
+
orientationChangeListeners.add(name, listener);
|
|
183
|
+
ScreenOrientation.addListener("screenOrientationChange", listener);
|
|
184
|
+
} else console.warn(`Listener with name ${name} already registered.`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function getScreenOrientation() {
|
|
188
|
+
return await ScreenOrientation.orientation();
|
|
189
|
+
}
|