@saltcorn/mobile-app 1.3.0-beta.1 → 1.3.0-beta.11
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/package.json +5 -2
- package/src/helpers/db_schema.js +2 -1
- package/src/helpers/navigation.js +1 -0
- package/src/init.js +35 -18
- package/src/routing/index.js +27 -2
- package/src/routing/routes/api.js +67 -3
- package/src/routing/routes/delete.js +3 -3
- package/webpack.config.js +8 -0
- package/www/index.html +11 -5
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/mobile-app",
|
|
3
3
|
"displayName": "Saltcorn mobile app",
|
|
4
|
-
"version": "1.3.0-beta.
|
|
4
|
+
"version": "1.3.0-beta.11",
|
|
5
5
|
"description": "Saltcorn mobile app for Android and iOS",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
@@ -21,7 +21,10 @@
|
|
|
21
21
|
"axios": "^1.7.7",
|
|
22
22
|
"jwt-decode": "4.0.0",
|
|
23
23
|
"i18next": "24.2.2",
|
|
24
|
-
"i18next-sprintf-postprocessor": "0.2.2"
|
|
24
|
+
"i18next-sprintf-postprocessor": "0.2.2",
|
|
25
|
+
"crypto-browserify": "3.12.1",
|
|
26
|
+
"vm-browserify": "1.1.2",
|
|
27
|
+
"stream-browserify": "3.0.0"
|
|
25
28
|
},
|
|
26
29
|
"devDependencies": {
|
|
27
30
|
"xml2js": "0.6.2",
|
package/src/helpers/db_schema.js
CHANGED
|
@@ -54,7 +54,8 @@ export async function updateScTables(tablesJSON, skipScPlugins = true) {
|
|
|
54
54
|
if (
|
|
55
55
|
(skipScPlugins && table === "_sc_plugins") ||
|
|
56
56
|
table === "_sc_workflow_runs" ||
|
|
57
|
-
table === "_sc_workflow_trace"
|
|
57
|
+
table === "_sc_workflow_trace" ||
|
|
58
|
+
table === "_sc_metadata"
|
|
58
59
|
)
|
|
59
60
|
continue;
|
|
60
61
|
if (table === "_sc_tables") await dropDeletedTables(rows);
|
|
@@ -333,6 +333,7 @@ export async function replaceIframeInnerContent(content) {
|
|
|
333
333
|
}
|
|
334
334
|
iframe.contentWindow.scrollTo(0, 0);
|
|
335
335
|
iframe.contentWindow.initialize_page();
|
|
336
|
+
iframeDocument.dispatchEvent(new Event("mobile-loaded"));
|
|
336
337
|
}
|
|
337
338
|
|
|
338
339
|
export function splitPathQuery(url) {
|
package/src/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*global saltcorn, Capacitor, cordova */
|
|
1
|
+
/*global saltcorn, Capacitor, cordova, _test_schema_ */
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
startOfflineMode,
|
|
@@ -31,6 +31,8 @@ import { jwtDecode } from "jwt-decode";
|
|
|
31
31
|
|
|
32
32
|
import { Network } from "@capacitor/network";
|
|
33
33
|
|
|
34
|
+
import { defineCustomElements } from "jeep-sqlite/loader";
|
|
35
|
+
|
|
34
36
|
async function addScript(scriptObj) {
|
|
35
37
|
let waited = 0;
|
|
36
38
|
const maxWait = 3000;
|
|
@@ -162,13 +164,15 @@ const initI18Next = async () => {
|
|
|
162
164
|
for (const key of Object.keys(
|
|
163
165
|
saltcorn.data.models.config.available_languages
|
|
164
166
|
)) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
167
|
+
if (Capacitor.platform !== "web") {
|
|
168
|
+
const localeFile = await readJSONCordova(
|
|
169
|
+
`${key}.json`,
|
|
170
|
+
`${cordova.file.applicationDirectory}public/data/locales`
|
|
171
|
+
);
|
|
172
|
+
resources[key] = {
|
|
173
|
+
translation: localeFile,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
172
176
|
}
|
|
173
177
|
await i18next.use(i18nextSprintfPostProcessor).init({
|
|
174
178
|
lng: "en",
|
|
@@ -305,22 +309,28 @@ const postShare = async (shareData) => {
|
|
|
305
309
|
};
|
|
306
310
|
|
|
307
311
|
const readSchemaIfNeeded = async () => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
);
|
|
313
|
-
const updateNeeded = await dbUpdateNeeded(created_at);
|
|
314
|
-
if (updateNeeded) {
|
|
315
|
-
tablesJSON = await readJSONCordova(
|
|
316
|
-
"tables.json",
|
|
312
|
+
if (Capacitor.platform !== "web") {
|
|
313
|
+
let tablesJSON = null;
|
|
314
|
+
const { created_at } = await readJSONCordova(
|
|
315
|
+
"tables_created_at.json",
|
|
317
316
|
`${cordova.file.applicationDirectory}${"public"}/data`
|
|
318
317
|
);
|
|
318
|
+
const updateNeeded = await dbUpdateNeeded(created_at);
|
|
319
|
+
if (updateNeeded) {
|
|
320
|
+
tablesJSON = await readJSONCordova(
|
|
321
|
+
"tables.json",
|
|
322
|
+
`${cordova.file.applicationDirectory}${"public"}/data`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
return { updateNeeded, tablesJSON };
|
|
326
|
+
} else {
|
|
327
|
+
// test environment
|
|
328
|
+
return { updateNeeded: true, tablesJSON: _test_schema_ };
|
|
319
329
|
}
|
|
320
|
-
return { updateNeeded, tablesJSON };
|
|
321
330
|
};
|
|
322
331
|
|
|
323
332
|
const readSiteLogo = async (state) => {
|
|
333
|
+
if (Capacitor.platform === "web") return "";
|
|
324
334
|
try {
|
|
325
335
|
const base64 = await readTextCordova(
|
|
326
336
|
"encoded_site_logo.txt",
|
|
@@ -340,6 +350,13 @@ const readSiteLogo = async (state) => {
|
|
|
340
350
|
// device is ready
|
|
341
351
|
export async function init(mobileConfig) {
|
|
342
352
|
try {
|
|
353
|
+
if (Capacitor.platform === "web") {
|
|
354
|
+
defineCustomElements(window);
|
|
355
|
+
await customElements.whenDefined("jeep-sqlite");
|
|
356
|
+
const jeepSqlite = document.createElement("jeep-sqlite");
|
|
357
|
+
document.body.appendChild(jeepSqlite);
|
|
358
|
+
await jeepSqlite.componentOnReady();
|
|
359
|
+
}
|
|
343
360
|
const lastLocation = takeLastLocation();
|
|
344
361
|
document.addEventListener("resume", onResume, false);
|
|
345
362
|
await addScripts(mobileConfig.version_tag);
|
package/src/routing/index.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import UniversalRouter from "universal-router";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
runAction,
|
|
5
|
+
loadTableRows,
|
|
6
|
+
updateTableRow,
|
|
7
|
+
insertTableRow,
|
|
8
|
+
} from "./routes/api";
|
|
4
9
|
import { getLoginView, logoutAction, getSignupView } from "./routes/auth";
|
|
5
10
|
import { deleteRows } from "./routes/delete";
|
|
6
11
|
import { postToggleField } from "./routes/edit";
|
|
@@ -22,14 +27,28 @@ import {
|
|
|
22
27
|
|
|
23
28
|
const routes = [
|
|
24
29
|
// api
|
|
30
|
+
{
|
|
31
|
+
path: "post/api/action/:action",
|
|
32
|
+
action: runAction,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
path: "get/api/:tableName",
|
|
36
|
+
action: loadTableRows,
|
|
37
|
+
},
|
|
25
38
|
{
|
|
26
39
|
path: "post/api/:tableName/:id",
|
|
27
40
|
action: updateTableRow,
|
|
28
41
|
},
|
|
42
|
+
|
|
29
43
|
{
|
|
30
44
|
path: "post/api/:tableName/",
|
|
31
45
|
action: insertTableRow,
|
|
32
46
|
},
|
|
47
|
+
{
|
|
48
|
+
path: "post/api/:tableName",
|
|
49
|
+
action: insertTableRow,
|
|
50
|
+
},
|
|
51
|
+
|
|
33
52
|
// auth
|
|
34
53
|
{
|
|
35
54
|
path: "get/auth/login",
|
|
@@ -44,10 +63,16 @@ const routes = [
|
|
|
44
63
|
action: getSignupView,
|
|
45
64
|
},
|
|
46
65
|
// delete
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
path: "post/delete/:tableName/:id", // legacy
|
|
69
|
+
action: deleteRows,
|
|
70
|
+
},
|
|
47
71
|
{
|
|
48
|
-
path: "
|
|
72
|
+
path: "delete/api/:tableName/:id",
|
|
49
73
|
action: deleteRows,
|
|
50
74
|
},
|
|
75
|
+
|
|
51
76
|
// edit
|
|
52
77
|
{
|
|
53
78
|
path: "post/edit/toggle/:name/:id/:field_name",
|
|
@@ -1,7 +1,36 @@
|
|
|
1
|
-
/*global saltcorn */
|
|
1
|
+
/* global saltcorn */
|
|
2
2
|
|
|
3
3
|
import { apiCall } from "../../helpers/api";
|
|
4
4
|
import { setHasOfflineData } from "../../helpers/offline_mode";
|
|
5
|
+
import { parseQuery } from "../utils";
|
|
6
|
+
import { MobileRequest } from "../mocks/request";
|
|
7
|
+
|
|
8
|
+
// get/api/:tableName
|
|
9
|
+
export const loadTableRows = async (context) => {
|
|
10
|
+
const { tableName } = context.params;
|
|
11
|
+
const queryString = context.query;
|
|
12
|
+
const query = queryString ? parseQuery(queryString) : {};
|
|
13
|
+
|
|
14
|
+
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
15
|
+
const table = saltcorn.data.models.Table.findOne({ name: tableName });
|
|
16
|
+
if (!table) throw new Error(`The table '${tableName}' does not exist.`);
|
|
17
|
+
if (
|
|
18
|
+
mobileConfig.isOfflineMode ||
|
|
19
|
+
mobileConfig.localTableIds.indexOf(table.id) >= 0
|
|
20
|
+
) {
|
|
21
|
+
const rows = await table.getRows(query, {
|
|
22
|
+
orderBy: table.pk_name,
|
|
23
|
+
forUser: mobileConfig.user,
|
|
24
|
+
});
|
|
25
|
+
return { success: rows };
|
|
26
|
+
} else {
|
|
27
|
+
const response = await apiCall({
|
|
28
|
+
method: "GET",
|
|
29
|
+
path: `/api/${tableName}${queryString ? `?${queryString}` : ""}`,
|
|
30
|
+
});
|
|
31
|
+
return response.data;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
5
34
|
|
|
6
35
|
// post/api/:tableName/:id
|
|
7
36
|
export const updateTableRow = async (context) => {
|
|
@@ -32,7 +61,7 @@ export const updateTableRow = async (context) => {
|
|
|
32
61
|
const response = await apiCall({
|
|
33
62
|
method: "POST",
|
|
34
63
|
path: `/api/${tableName}/${id}`,
|
|
35
|
-
body: context.query,
|
|
64
|
+
body: context.body || context.query,
|
|
36
65
|
});
|
|
37
66
|
return response.data;
|
|
38
67
|
}
|
|
@@ -67,8 +96,43 @@ export const insertTableRow = async (context) => {
|
|
|
67
96
|
const response = await apiCall({
|
|
68
97
|
method: "POST",
|
|
69
98
|
path: `/api/${tableName}`,
|
|
70
|
-
body: context.query,
|
|
99
|
+
body: context.body || context.query,
|
|
100
|
+
});
|
|
101
|
+
return response.data;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// post/api/:action
|
|
106
|
+
export const runAction = async (context) => {
|
|
107
|
+
const { action } = context.params;
|
|
108
|
+
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
109
|
+
if (mobileConfig.isOfflineMode) {
|
|
110
|
+
const req = new MobileRequest();
|
|
111
|
+
const trigger = saltcorn.data.models.Trigger.findOne({
|
|
112
|
+
name: action,
|
|
113
|
+
});
|
|
114
|
+
if (!trigger) throw new Error(`Action '${action}' does not exist.`);
|
|
115
|
+
const actionObj = saltcorn.data.state.getState().actions[trigger.action];
|
|
116
|
+
const resp = await actionObj.run({
|
|
117
|
+
configuration: trigger.configuration,
|
|
118
|
+
body: context.body || {},
|
|
119
|
+
row: context.body || {},
|
|
120
|
+
req,
|
|
121
|
+
user: req.user,
|
|
71
122
|
});
|
|
123
|
+
if (resp?.error) {
|
|
124
|
+
const { error, ...rest } = resp;
|
|
125
|
+
return { success: false, error, data: rest };
|
|
126
|
+
} else return { success: true, data: resp };
|
|
127
|
+
} else {
|
|
128
|
+
const response = await apiCall({
|
|
129
|
+
method: "POST",
|
|
130
|
+
path: `/api/action/${action}`,
|
|
131
|
+
body: context.body,
|
|
132
|
+
});
|
|
133
|
+
if (response.success === false) {
|
|
134
|
+
throw new Error(`Action '${action}' failed: ${response.error}`);
|
|
135
|
+
}
|
|
72
136
|
return response.data;
|
|
73
137
|
}
|
|
74
138
|
};
|
|
@@ -5,8 +5,8 @@ import i18next from "i18next";
|
|
|
5
5
|
|
|
6
6
|
// post/delete/:name/:id
|
|
7
7
|
export const deleteRows = async (context) => {
|
|
8
|
-
const {
|
|
9
|
-
const table = await saltcorn.data.models.Table.findOne({ name });
|
|
8
|
+
const { tableName, id } = context.params;
|
|
9
|
+
const table = await saltcorn.data.models.Table.findOne({ name: tableName });
|
|
10
10
|
const { isOfflineMode, localTableIds, user } =
|
|
11
11
|
saltcorn.data.state.getState().mobileConfig;
|
|
12
12
|
if (isOfflineMode || localTableIds.indexOf(table.id) >= 0) {
|
|
@@ -21,7 +21,7 @@ export const deleteRows = async (context) => {
|
|
|
21
21
|
// await offlineHelper.setOfflineSession(null);
|
|
22
22
|
// }
|
|
23
23
|
} else {
|
|
24
|
-
await apiCall({ method: "POST", path: `/delete/${
|
|
24
|
+
await apiCall({ method: "POST", path: `/delete/${tableName}/${id}` });
|
|
25
25
|
}
|
|
26
26
|
const redirect = context.data?.after_delete_url
|
|
27
27
|
? context.data.after_delete_url === "/"
|
package/webpack.config.js
CHANGED
|
@@ -37,6 +37,14 @@ module.exports = {
|
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
39
|
},
|
|
40
|
+
resolve: {
|
|
41
|
+
fallback: {
|
|
42
|
+
"crypto": require.resolve("crypto-browserify"),
|
|
43
|
+
vm: require.resolve("vm-browserify"),
|
|
44
|
+
stream: require.resolve("stream-browserify"),
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
},
|
|
40
48
|
target: "web", // Use web target for browser compatibility
|
|
41
49
|
mode: "development",
|
|
42
50
|
};
|
package/www/index.html
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
|
7
7
|
/>
|
|
8
8
|
<script src="data/config.js"></script>
|
|
9
|
+
<!-- only set in test mode -->
|
|
10
|
+
<script src="data/test_schema.js"></script>
|
|
9
11
|
<script type="module" src="./dist/bundle.js"></script>
|
|
10
12
|
<script src="js/iframe_view_utils.js"></script>
|
|
11
13
|
|
|
@@ -14,12 +16,16 @@
|
|
|
14
16
|
if (window.saltcorn) window.saltcorn.mobileApp = mobileApp;
|
|
15
17
|
else window.saltcorn = { mobileApp };
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
if (Capacitor.platform !== "web") {
|
|
20
|
+
document.addEventListener("deviceready", () => {
|
|
21
|
+
saltcorn.mobileApp.init(_sc_mobile_config);
|
|
22
|
+
});
|
|
23
|
+
document.addEventListener("backbutton", async () => {
|
|
24
|
+
await saltcorn.mobileApp.navigation.goBack(1, true);
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
18
27
|
saltcorn.mobileApp.init(_sc_mobile_config);
|
|
19
|
-
}
|
|
20
|
-
document.addEventListener("backbutton", async () => {
|
|
21
|
-
await saltcorn.mobileApp.navigation.goBack(1, true);
|
|
22
|
-
});
|
|
28
|
+
}
|
|
23
29
|
</script>
|
|
24
30
|
</head>
|
|
25
31
|
|