@saltcorn/mobile-app 0.7.2-beta.5
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/config.xml +14 -0
- package/index.js +1 -0
- package/package.json +30 -0
- package/www/index.html +138 -0
- package/www/js/mocks/request.js +22 -0
- package/www/js/mocks/response.js +27 -0
- package/www/js/routes/delete.js +22 -0
- package/www/js/routes/edit.js +21 -0
- package/www/js/routes/init.js +34 -0
- package/www/js/routes/page.js +17 -0
- package/www/js/routes/view.js +102 -0
- package/www/js/utils/file_helpers.js +73 -0
- package/www/js/utils/global_utils.js +108 -0
- package/www/js/utils/iframe_view_utils.js +281 -0
- package/www/js/utils/login_form.js +40 -0
- package/www/js/utils/table_utils.js +61 -0
package/config.xml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
2
|
+
<widget id="saltcorn.mobile.app" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<name>SaltcornMobileApp</name>
|
|
4
|
+
<description>Apache Cordova application with @saltcorn/markup</description>
|
|
5
|
+
<platform name="android">
|
|
6
|
+
<edit-config file="app/src/main/AndroidManifest.xml" target="/manifest/application" mode="merge">
|
|
7
|
+
<application android:usesCleartextTraffic="true"/>
|
|
8
|
+
</edit-config>
|
|
9
|
+
<preference name="Scheme" value="http"/>
|
|
10
|
+
<preference name="MixedContentMode" value="2"/>
|
|
11
|
+
</platform>
|
|
12
|
+
<content src="index.html"/>
|
|
13
|
+
<access origin="*"/>
|
|
14
|
+
</widget>
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = {};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@saltcorn/mobile-app",
|
|
3
|
+
"displayName": "Saltcorn mobile app",
|
|
4
|
+
"version": "0.7.2-beta.5",
|
|
5
|
+
"description": "Apache Cordova application with @saltcorn/markup",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"add-platform": "cordova platform add",
|
|
9
|
+
"build-app": "cordova build"
|
|
10
|
+
},
|
|
11
|
+
"author": "Christian Hugo",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"cordova": {
|
|
14
|
+
"platforms": [
|
|
15
|
+
"android"
|
|
16
|
+
],
|
|
17
|
+
"plugins": {
|
|
18
|
+
"cordova-sqlite-ext": {},
|
|
19
|
+
"cordova-plugin-file": {}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"cordova": "^11.0.0",
|
|
24
|
+
"cordova-sqlite-ext": "^6.0.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"cordova-android": "^10.1.2",
|
|
28
|
+
"cordova-plugin-file": "^6.0.2"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/www/index.html
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<script src="cordova.js"></script>
|
|
5
|
+
<script src="js/utils/global_utils.js"></script>
|
|
6
|
+
<script src="js/utils/iframe_view_utils.js"></script>
|
|
7
|
+
<script src="js/mocks/response.js"></script>
|
|
8
|
+
<script src="js/mocks/request.js"></script>
|
|
9
|
+
<script src="npm_packages/jwt-decode.js"></script>
|
|
10
|
+
<script src="https://unpkg.com/universal-router/universal-router.min.js"></script>
|
|
11
|
+
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
12
|
+
<script type="module">
|
|
13
|
+
import { initRoutes } from "./js/routes/init.js";
|
|
14
|
+
import { renderLoginForm } from "./js/utils/login_form.js";
|
|
15
|
+
import {
|
|
16
|
+
updateDbIfNecessary,
|
|
17
|
+
getTableIds,
|
|
18
|
+
} from "./js/utils/table_utils.js";
|
|
19
|
+
import { readJSON } from "./js/utils/file_helpers.js";
|
|
20
|
+
|
|
21
|
+
const staticPlugins = ["base", "sbadmin2"];
|
|
22
|
+
|
|
23
|
+
async function addScript(path) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let script = document.createElement("script");
|
|
26
|
+
document.head.appendChild(script);
|
|
27
|
+
script.onload = () => {
|
|
28
|
+
resolve();
|
|
29
|
+
};
|
|
30
|
+
// TODO reject after some seconds
|
|
31
|
+
script.src = path;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function loadPlugin(plugin) {
|
|
36
|
+
return addScript(`js/bundles/${plugin.name}.bundle.js`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function loadPlugins(state) {
|
|
40
|
+
let plugins = (await saltcorn.data.models.Plugin.find()).filter(
|
|
41
|
+
(plugin) => !staticPlugins.includes(plugin.name)
|
|
42
|
+
);
|
|
43
|
+
for (const plugin of plugins) {
|
|
44
|
+
await loadPlugin(plugin);
|
|
45
|
+
state.registerPlugin(plugin.name, saltcorn[plugin.name]);
|
|
46
|
+
}
|
|
47
|
+
return plugins;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function addScripts(version_tag) {
|
|
51
|
+
const scripts = [
|
|
52
|
+
`static_assets/${version_tag}/jquery-3.6.0.min.js`,
|
|
53
|
+
"js/bundles/common_chunks.bundle.js",
|
|
54
|
+
"js/bundles/markup.bundle.js",
|
|
55
|
+
"js/bundles/data.bundle.js",
|
|
56
|
+
"js/bundles/base_plugin.bundle.js",
|
|
57
|
+
"js/bundles/sbadmin2.bundle.js",
|
|
58
|
+
];
|
|
59
|
+
for (const script of scripts) {
|
|
60
|
+
await addScript(script);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const addPluginScriptTagsToCfg = (pluginRows) => {
|
|
65
|
+
window.config.pluginHeaders = [];
|
|
66
|
+
for (const row of pluginRows) {
|
|
67
|
+
const pluginHeaders = saltcorn[row.name].headers;
|
|
68
|
+
if (pluginHeaders) {
|
|
69
|
+
for (const header of pluginHeaders) {
|
|
70
|
+
window.config.pluginHeaders.push(header);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
document.addEventListener("deviceready", async () => {
|
|
77
|
+
window.config = await readJSON(
|
|
78
|
+
"config",
|
|
79
|
+
`${cordova.file.applicationDirectory}www`
|
|
80
|
+
);
|
|
81
|
+
await addScripts(config.version_tag);
|
|
82
|
+
await saltcorn.data.db.init();
|
|
83
|
+
const state = saltcorn.data.state.getState();
|
|
84
|
+
state.registerPlugin("base", saltcorn.base_plugin);
|
|
85
|
+
state.registerPlugin("sbadmin2", saltcorn.sbadmin2);
|
|
86
|
+
addPluginScriptTagsToCfg(await loadPlugins(state));
|
|
87
|
+
await updateDbIfNecessary();
|
|
88
|
+
await state.refresh_tables();
|
|
89
|
+
await state.refresh_views();
|
|
90
|
+
await state.refresh_pages();
|
|
91
|
+
state.localTableIds = await getTableIds(config.localUserTables);
|
|
92
|
+
await initRoutes();
|
|
93
|
+
const entryView = config.entry_view;
|
|
94
|
+
const jwt = localStorage.getItem("auth_jwt");
|
|
95
|
+
if (jwt && jwt !== "undefined") {
|
|
96
|
+
const decodedJwt = jwt_decode(jwt);
|
|
97
|
+
saltcorn.data.state.getState().role_id = decodedJwt?.role_id
|
|
98
|
+
? decodedJwt.role_id
|
|
99
|
+
: 10;
|
|
100
|
+
addRoute({ route: entryView, query: undefined });
|
|
101
|
+
const page = await router.resolve({
|
|
102
|
+
pathname: entryView,
|
|
103
|
+
fullWrap: true,
|
|
104
|
+
});
|
|
105
|
+
replaceIframe(page.content);
|
|
106
|
+
} else {
|
|
107
|
+
replaceIframe(renderLoginForm(entryView, config.version_tag));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
document.addEventListener("backbutton", async () => {
|
|
112
|
+
await goBack(1, true);
|
|
113
|
+
});
|
|
114
|
+
</script>
|
|
115
|
+
</head>
|
|
116
|
+
|
|
117
|
+
<body>
|
|
118
|
+
<iframe
|
|
119
|
+
id="content-iframe"
|
|
120
|
+
style="
|
|
121
|
+
position: fixed;
|
|
122
|
+
top: 0;
|
|
123
|
+
left: 0;
|
|
124
|
+
bottom: 0;
|
|
125
|
+
right: 0;
|
|
126
|
+
width: 100%;
|
|
127
|
+
height: 100%;
|
|
128
|
+
border: none;
|
|
129
|
+
margin: 0;
|
|
130
|
+
padding: 0;
|
|
131
|
+
overflow: hidden;
|
|
132
|
+
z-index: 999999;
|
|
133
|
+
"
|
|
134
|
+
>
|
|
135
|
+
<p>Your browser does not support iframes.</p>
|
|
136
|
+
</iframe>
|
|
137
|
+
</body>
|
|
138
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
function MobileRequest(xhr = false) {
|
|
2
|
+
const roleId = saltcorn.data.state.getState().role_id
|
|
3
|
+
? saltcorn.data.state.getState().role_id
|
|
4
|
+
: 10;
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
__: (s) => s,
|
|
8
|
+
getLocale: () => "en",
|
|
9
|
+
user: {
|
|
10
|
+
role_id: roleId,
|
|
11
|
+
},
|
|
12
|
+
flash: (str) => {
|
|
13
|
+
console.log("flash ->->");
|
|
14
|
+
console.log(str);
|
|
15
|
+
},
|
|
16
|
+
get: (key) => {
|
|
17
|
+
return "";
|
|
18
|
+
},
|
|
19
|
+
csrfToken: () => "",
|
|
20
|
+
xhr,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function MobileResponse() {
|
|
2
|
+
let jsonData = null;
|
|
3
|
+
let sendData = null;
|
|
4
|
+
|
|
5
|
+
function json(data) {
|
|
6
|
+
jsonData = data;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function send(data) {
|
|
10
|
+
sendData = data;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getJson() {
|
|
14
|
+
return jsonData;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getSendData() {
|
|
18
|
+
return getSendData;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
json,
|
|
23
|
+
send,
|
|
24
|
+
getJson,
|
|
25
|
+
getSendData,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// post/delete/:name/:id
|
|
2
|
+
export const deleteRows = async (context) => {
|
|
3
|
+
const { name, id } = context.params;
|
|
4
|
+
try {
|
|
5
|
+
const table = await saltcorn.data.models.Table.findOne({ name });
|
|
6
|
+
const state = saltcorn.data.state.getState();
|
|
7
|
+
if (state.localTableIds.indexOf(table.id) >= 0) {
|
|
8
|
+
await table.deleteRows({ id });
|
|
9
|
+
} else {
|
|
10
|
+
await apiCall({ method: "POST", path: `/delete/${name}/${id}` });
|
|
11
|
+
}
|
|
12
|
+
const redirect = context.data?.after_delete_url
|
|
13
|
+
? context.data.after_delete_url === "/"
|
|
14
|
+
? "/"
|
|
15
|
+
: `get${new URL(context.data?.after_delete_url).pathname}`
|
|
16
|
+
: new URLSearchParams(context.query).get("redirect");
|
|
17
|
+
return { redirect };
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// TODO ch message?
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// /toggle/:name/:id/:field_name
|
|
2
|
+
export const postToggleField = async (context) => {
|
|
3
|
+
const { name, id, field_name } = context.params;
|
|
4
|
+
try {
|
|
5
|
+
const table = await saltcorn.data.models.Table.findOne({ name });
|
|
6
|
+
const state = saltcorn.data.state.getState();
|
|
7
|
+
if (state.localTableIds.indexOf(table.id) >= 0) {
|
|
8
|
+
await table.toggleBool(+id, field_name);
|
|
9
|
+
} else {
|
|
10
|
+
await apiCall({
|
|
11
|
+
method: "POST",
|
|
12
|
+
path: `/edit/toggle/${name}/${id}/${field_name}`,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const redirect = new URLSearchParams(context.query).get("redirect");
|
|
16
|
+
return { redirect };
|
|
17
|
+
} catch (error) {
|
|
18
|
+
// TODO ch message?
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { deleteRows } from "./delete.js";
|
|
2
|
+
import { getView, postView, postViewRoute } from "./view.js";
|
|
3
|
+
import { postToggleField } from "./edit.js";
|
|
4
|
+
import { postPageAction } from "./page.js";
|
|
5
|
+
|
|
6
|
+
export const initRoutes = async () => {
|
|
7
|
+
const routes = [
|
|
8
|
+
{
|
|
9
|
+
path: "post/view/:viewname",
|
|
10
|
+
action: postView,
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
path: "post/view/:viewname/:route",
|
|
14
|
+
action: postViewRoute,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
path: "get/view/:viewname",
|
|
18
|
+
action: getView,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
path: "post/edit/toggle/:name/:id/:field_name",
|
|
22
|
+
action: postToggleField,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
path: "post/delete/:name/:id",
|
|
26
|
+
action: deleteRows,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
path: "post/page/:pagename/action/:rndid",
|
|
30
|
+
action: postPageAction,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
window.router = new window.UniversalRouter(routes);
|
|
34
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// post/page/:pagename/action/:rndid
|
|
2
|
+
export const postPageAction = async (context) => {
|
|
3
|
+
const { pagename, rndid } = context.params;
|
|
4
|
+
const db_page = await saltcorn.data.models.Page.findOne({ name: pagename });
|
|
5
|
+
let col;
|
|
6
|
+
saltcorn.data.models.layout.traverseSync(db_page.layout, {
|
|
7
|
+
action(segment) {
|
|
8
|
+
if (segment.rndid === rndid) col = segment;
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
const result = await saltcorn.data.plugin_helper.run_action_column({
|
|
12
|
+
col,
|
|
13
|
+
referrer: "",
|
|
14
|
+
req: new MobileRequest(context.xhr),
|
|
15
|
+
});
|
|
16
|
+
return result || {};
|
|
17
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const getHeaders = (versionTag) => {
|
|
2
|
+
const stdHeaders = [
|
|
3
|
+
{ css: `static_assets/${versionTag}/saltcorn.css` },
|
|
4
|
+
{ script: `static_assets/${versionTag}/saltcorn-common.js` },
|
|
5
|
+
{ script: "js/utils/iframe_view_utils.js" },
|
|
6
|
+
];
|
|
7
|
+
return [...stdHeaders, ...window.config.pluginHeaders];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
*
|
|
12
|
+
* @param {*} context
|
|
13
|
+
* @returns
|
|
14
|
+
*/
|
|
15
|
+
export const postView = async (context) => {
|
|
16
|
+
let body = {};
|
|
17
|
+
let redirect = undefined;
|
|
18
|
+
for (const [k, v] of new URLSearchParams(context.query).entries()) {
|
|
19
|
+
body[k] = v;
|
|
20
|
+
if (k === "redirect") redirect = v;
|
|
21
|
+
}
|
|
22
|
+
const view = await saltcorn.data.models.View.findOne({
|
|
23
|
+
name: context.params.viewname,
|
|
24
|
+
});
|
|
25
|
+
const response = new MobileResponse();
|
|
26
|
+
await view.runPost(
|
|
27
|
+
{},
|
|
28
|
+
body,
|
|
29
|
+
{
|
|
30
|
+
req: new MobileRequest(context.xhr),
|
|
31
|
+
res: response,
|
|
32
|
+
redirect,
|
|
33
|
+
},
|
|
34
|
+
view.isRemoteTable()
|
|
35
|
+
);
|
|
36
|
+
return response.getJson();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param {*} context
|
|
42
|
+
*/
|
|
43
|
+
export const postViewRoute = async (context) => {
|
|
44
|
+
const view = await saltcorn.data.models.View.findOne({
|
|
45
|
+
name: context.params.viewname,
|
|
46
|
+
});
|
|
47
|
+
const response = new MobileResponse();
|
|
48
|
+
const request = new MobileRequest(context.xhr);
|
|
49
|
+
await view.runRoute(
|
|
50
|
+
context.params.route,
|
|
51
|
+
context.data,
|
|
52
|
+
response,
|
|
53
|
+
{ req: request, res: response },
|
|
54
|
+
view.isRemoteTable()
|
|
55
|
+
);
|
|
56
|
+
return response.getJson();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param {*} context
|
|
62
|
+
* @returns
|
|
63
|
+
*/
|
|
64
|
+
export const getView = async (context) => {
|
|
65
|
+
let query = {};
|
|
66
|
+
const parsedQuery =
|
|
67
|
+
typeof context.query === "string"
|
|
68
|
+
? new URLSearchParams(context.query)
|
|
69
|
+
: undefined;
|
|
70
|
+
if (parsedQuery) {
|
|
71
|
+
for (let [key, value] of parsedQuery) {
|
|
72
|
+
query[key] = value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const viewname = context.params.viewname;
|
|
76
|
+
const view = saltcorn.data.models.View.findOne({ name: viewname });
|
|
77
|
+
const viewContent = await view.run_possibly_on_page(
|
|
78
|
+
query,
|
|
79
|
+
new MobileRequest(context.xhr),
|
|
80
|
+
new MobileResponse(),
|
|
81
|
+
view.isRemoteTable()
|
|
82
|
+
);
|
|
83
|
+
const state = saltcorn.data.state.getState();
|
|
84
|
+
const layout = state.getLayout({ role_id: state.role_id });
|
|
85
|
+
const wrappedContent = context.fullWrap
|
|
86
|
+
? layout.wrap({
|
|
87
|
+
title: viewname,
|
|
88
|
+
body: { above: [viewContent] },
|
|
89
|
+
alerts: [],
|
|
90
|
+
role: state.role_id,
|
|
91
|
+
headers: getHeaders(window.config.version_tag),
|
|
92
|
+
bodyClass: "",
|
|
93
|
+
brand: {},
|
|
94
|
+
})
|
|
95
|
+
: layout.renderBody({
|
|
96
|
+
title: viewname,
|
|
97
|
+
body: { above: [viewContent] },
|
|
98
|
+
alerts: [],
|
|
99
|
+
role: state.role_id,
|
|
100
|
+
});
|
|
101
|
+
return { content: wrappedContent, title: viewname };
|
|
102
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export async function fileExists(path) {
|
|
2
|
+
try {
|
|
3
|
+
await getDirEntry(path);
|
|
4
|
+
return true;
|
|
5
|
+
} catch (error) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getDirEntry(directory) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
window.resolveLocalFileSystemURL(
|
|
13
|
+
directory,
|
|
14
|
+
function (fs) {
|
|
15
|
+
resolve(fs);
|
|
16
|
+
},
|
|
17
|
+
function (error) {
|
|
18
|
+
reject(error);
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function readJSON(fileName, dirName) {
|
|
25
|
+
const dirEntry = await getDirEntry(dirName);
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
dirEntry.getFile(
|
|
28
|
+
fileName,
|
|
29
|
+
{ create: false, exclusive: false },
|
|
30
|
+
function (fileEntry) {
|
|
31
|
+
fileEntry.file(function (file) {
|
|
32
|
+
let reader = new FileReader();
|
|
33
|
+
reader.onloadend = function (e) {
|
|
34
|
+
resolve(JSON.parse(this.result));
|
|
35
|
+
};
|
|
36
|
+
reader.readAsText(file);
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
function (err) {
|
|
40
|
+
console.log(`unable to read ${fileName}`);
|
|
41
|
+
console.log(err);
|
|
42
|
+
reject(err);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function writeJSON(fileName, dirName, content) {
|
|
49
|
+
const dirEntry = await getDirEntry(dirName);
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
dirEntry.getFile(
|
|
52
|
+
fileName,
|
|
53
|
+
{ create: true, exclusive: false },
|
|
54
|
+
function (fileEntry) {
|
|
55
|
+
fileEntry.createWriter(function (fileWriter) {
|
|
56
|
+
fileWriter.onwriteend = function () {
|
|
57
|
+
resolve();
|
|
58
|
+
};
|
|
59
|
+
fileWriter.onerror = function (e) {
|
|
60
|
+
console.log("Failed file write: " + e.toString());
|
|
61
|
+
reject(e);
|
|
62
|
+
};
|
|
63
|
+
fileWriter.write(JSON.stringify(content));
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
function (err) {
|
|
67
|
+
console.log(`unable to get ${fileName}`);
|
|
68
|
+
console.log(err);
|
|
69
|
+
reject(err);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
let routingHistory = [];
|
|
2
|
+
|
|
3
|
+
function currentLocation() {
|
|
4
|
+
if (routingHistory.length == 0) return undefined;
|
|
5
|
+
return routingHistory[routingHistory.length - 1].route;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function currentQuery() {
|
|
9
|
+
if (routingHistory.length == 0) return undefined;
|
|
10
|
+
return routingHistory[routingHistory.length - 1].query;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function addRoute(routeEntry) {
|
|
14
|
+
routingHistory.push(routeEntry);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function apiCall({ method, path, params, body }) {
|
|
18
|
+
const serverPath = config.server_path;
|
|
19
|
+
const token = localStorage.getItem("auth_jwt");
|
|
20
|
+
const url = `${serverPath}${path}`;
|
|
21
|
+
try {
|
|
22
|
+
return await axios({
|
|
23
|
+
url: url,
|
|
24
|
+
method: method,
|
|
25
|
+
params: params,
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `jwt ${token}`,
|
|
28
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
29
|
+
"X-Saltcorn-Client": "mobile-app",
|
|
30
|
+
},
|
|
31
|
+
data: body,
|
|
32
|
+
});
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(`error while calling: ${method} ${url}`);
|
|
35
|
+
console.log(JSON.stringify(error));
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function splitPathQuery(url) {
|
|
41
|
+
let path = url;
|
|
42
|
+
let query = undefined;
|
|
43
|
+
const queryStart = url.indexOf("?");
|
|
44
|
+
if (queryStart > 0) {
|
|
45
|
+
path = url.substring(0, queryStart);
|
|
46
|
+
query = url.substring(queryStart);
|
|
47
|
+
}
|
|
48
|
+
return { path, query };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function replaceIframe(content) {
|
|
52
|
+
let iframe = document.getElementById("content-iframe");
|
|
53
|
+
iframe.contentWindow.document.open();
|
|
54
|
+
iframe.contentWindow.document.write(content);
|
|
55
|
+
iframe.contentWindow.document.close();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function replaceIframeInnerContent(content) {
|
|
59
|
+
const iframe = document.getElementById("content-iframe");
|
|
60
|
+
const iframeDocument = iframe.contentWindow.document;
|
|
61
|
+
let innerContentDiv = iframeDocument.getElementById("page-inner-content");
|
|
62
|
+
innerContentDiv.innerHTML = content;
|
|
63
|
+
let scripts = innerContentDiv.getElementsByTagName("script");
|
|
64
|
+
for (let script of scripts) {
|
|
65
|
+
iframe.contentWindow.eval(script.innerHTML);
|
|
66
|
+
}
|
|
67
|
+
const scmodal = iframe.contentWindow.$("#scmodal");
|
|
68
|
+
if (scmodal) {
|
|
69
|
+
scmodal.modal("hide");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function gotoEntryView() {
|
|
74
|
+
const entryPath = config.entry_view;
|
|
75
|
+
const page = await router.resolve({
|
|
76
|
+
pathname: entryPath,
|
|
77
|
+
});
|
|
78
|
+
addRoute({ entryPath, query: undefined });
|
|
79
|
+
replaceIframeInnerContent(page.content);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function handleRoute(route, query) {
|
|
83
|
+
if (route === "/") return await gotoEntryView();
|
|
84
|
+
addRoute({ route, query });
|
|
85
|
+
const page = await router.resolve({
|
|
86
|
+
pathname: route,
|
|
87
|
+
query: query,
|
|
88
|
+
});
|
|
89
|
+
if (page.redirect) {
|
|
90
|
+
const { path, query } = splitPathQuery(page.redirect);
|
|
91
|
+
await handleRoute(path, query);
|
|
92
|
+
} else if (page.content) {
|
|
93
|
+
replaceIframeInnerContent(page.content);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function goBack(steps = 1, exitOnFirstPage = false) {
|
|
98
|
+
if (exitOnFirstPage && routingHistory.length === 1) {
|
|
99
|
+
navigator.app.exitApp();
|
|
100
|
+
} else if (routingHistory.length <= steps) {
|
|
101
|
+
routingHistory = [];
|
|
102
|
+
await gotoEntryView();
|
|
103
|
+
} else {
|
|
104
|
+
routingHistory = routingHistory.slice(0, routingHistory.length - steps);
|
|
105
|
+
const newCurrent = routingHistory.pop();
|
|
106
|
+
await handleRoute(newCurrent.route, newCurrent.query);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
function combineFormAndQuery(form, query) {
|
|
2
|
+
let paramsList = [];
|
|
3
|
+
const formData = new FormData(form[0]);
|
|
4
|
+
for (const [k, v] of formData.entries()) {
|
|
5
|
+
paramsList.push(`${k}=${v}`);
|
|
6
|
+
}
|
|
7
|
+
let sp = new URLSearchParams(query);
|
|
8
|
+
for (let [k, v] of sp.entries()) {
|
|
9
|
+
if (k === "redirect") v = `get${v}`;
|
|
10
|
+
paramsList.push(`${k}=${v}`);
|
|
11
|
+
}
|
|
12
|
+
return paramsList.length > 0 ? `?${paramsList.join("&")}` : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param {*} url
|
|
18
|
+
*/
|
|
19
|
+
async function execLink(url) {
|
|
20
|
+
const { path, query } = parent.splitPathQuery(url);
|
|
21
|
+
await parent.handleRoute(`get${path}`, query);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {*} e
|
|
27
|
+
* @param {*} urlSuffix
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
async function formSubmit(e, urlSuffix, viewname) {
|
|
31
|
+
e.submit();
|
|
32
|
+
const queryStr = new URLSearchParams(new FormData(e)).toString();
|
|
33
|
+
await parent.handleRoute(`post${urlSuffix}${viewname}`, queryStr);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function saveAndContinue(e, action, k) {
|
|
37
|
+
const form = $(e).closest("form");
|
|
38
|
+
submitWithEmptyAction(form[0]);
|
|
39
|
+
const queryStr = new URLSearchParams(new FormData(form[0])).toString();
|
|
40
|
+
const res = await parent.router.resolve({
|
|
41
|
+
pathname: `post${action}`,
|
|
42
|
+
query: queryStr,
|
|
43
|
+
xhr: true,
|
|
44
|
+
});
|
|
45
|
+
if (res.id && form.find("input[name=id")) {
|
|
46
|
+
form.append(
|
|
47
|
+
`<input type="hidden" class="form-control " name="id" value="${res.id}">`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (k) await k();
|
|
51
|
+
// TODO ch error (request.responseText?)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function login(email, password) {
|
|
55
|
+
try {
|
|
56
|
+
const response = await parent.apiCall({
|
|
57
|
+
method: "GET",
|
|
58
|
+
path: "/auth/login-with/jwt",
|
|
59
|
+
params: {
|
|
60
|
+
email,
|
|
61
|
+
password,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return response.data;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// TODO ch message
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function loginFormSubmit(e, entryView) {
|
|
72
|
+
let formData = new FormData(e);
|
|
73
|
+
const token = await login(formData.get("email"), formData.get("password"));
|
|
74
|
+
if (token) {
|
|
75
|
+
parent.localStorage.setItem("auth_jwt", token);
|
|
76
|
+
const decodedJwt = parent.jwt_decode(token);
|
|
77
|
+
parent.saltcorn.data.state.getState().role_id = decodedJwt?.role_id
|
|
78
|
+
? decodedJwt.role_id
|
|
79
|
+
: 10;
|
|
80
|
+
parent.addRoute({ route: entryView, query: undefined });
|
|
81
|
+
const page = await parent.router.resolve({
|
|
82
|
+
pathname: entryView,
|
|
83
|
+
fullWrap: true,
|
|
84
|
+
});
|
|
85
|
+
parent.replaceIframe(page.content);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function local_post_btn(e) {
|
|
90
|
+
const form = $(e).closest("form");
|
|
91
|
+
const url = form.attr("action");
|
|
92
|
+
const method = form.attr("method");
|
|
93
|
+
const { path, query } = parent.splitPathQuery(url);
|
|
94
|
+
await parent.handleRoute(
|
|
95
|
+
`${method}${path}`,
|
|
96
|
+
combineFormAndQuery(form, query)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
*
|
|
102
|
+
* @param {*} e
|
|
103
|
+
* @param {*} path
|
|
104
|
+
*/
|
|
105
|
+
async function stateFormSubmit(e, path) {
|
|
106
|
+
const formQuery = new URLSearchParams(new FormData(e)).toString();
|
|
107
|
+
await parent.handleRoute(path, formQuery);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function removeQueryStringParameter(queryStr, key) {
|
|
111
|
+
let params = [];
|
|
112
|
+
for (const [k, v] of new URLSearchParams(queryStr).entries()) {
|
|
113
|
+
if (k !== key) {
|
|
114
|
+
params.push(`${k}=${v}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return params.join("&");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function updateQueryStringParameter(queryStr, key, value) {
|
|
121
|
+
if (!queryStr) {
|
|
122
|
+
return `${key}=${value}`;
|
|
123
|
+
}
|
|
124
|
+
let params = [];
|
|
125
|
+
let updated = false;
|
|
126
|
+
for (const [k, v] of new URLSearchParams(queryStr).entries()) {
|
|
127
|
+
if (k === key) {
|
|
128
|
+
params.push(`${key}=${value}`);
|
|
129
|
+
updated = true;
|
|
130
|
+
} else {
|
|
131
|
+
params.push(`${k}=${v}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (!updated) {
|
|
135
|
+
params.push(`${key}=${value}`);
|
|
136
|
+
}
|
|
137
|
+
return params.join("&");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function set_state_fields(kvs, href) {
|
|
141
|
+
let queryParams = [];
|
|
142
|
+
let currentQuery = parent.currentQuery();
|
|
143
|
+
Object.entries(kvs).forEach((kv) => {
|
|
144
|
+
if (kv[1].unset && kv[1].unset === true) {
|
|
145
|
+
currentQuery = removeQueryStringParameter(currentQuery, kv[0]);
|
|
146
|
+
} else {
|
|
147
|
+
currentQuery = updateQueryStringParameter(currentQuery, kv[0], kv[1]);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
for (const [k, v] of new URLSearchParams(currentQuery).entries()) {
|
|
151
|
+
queryParams.push(`${k}=${v}`);
|
|
152
|
+
}
|
|
153
|
+
await parent.handleRoute(href, queryParams.join("&"));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function set_state_field(key, value) {
|
|
157
|
+
const query = updateQueryStringParameter(parent.currentQuery(), key, value);
|
|
158
|
+
await parent.handleRoute(parent.currentLocation(), query);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function unset_state_field(key) {
|
|
162
|
+
const href = parent.currentLocation();
|
|
163
|
+
const query = removeQueryStringParameter(parent.currentLocation(), key);
|
|
164
|
+
await parent.handleRoute(href, query);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function sortby(k, desc, viewname) {
|
|
168
|
+
await set_state_fields(
|
|
169
|
+
{ _sortby: k, _sortdesc: desc ? "on" : { unset: true } },
|
|
170
|
+
`get/view/${viewname}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function gopage(n, pagesize, extra) {
|
|
175
|
+
await set_state_fields(
|
|
176
|
+
{ ...extra, _page: n, _pagesize: pagesize },
|
|
177
|
+
parent.currentLocation()
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function mobile_modal(url, opts = {}) {
|
|
182
|
+
if ($("#scmodal").length === 0) {
|
|
183
|
+
$("body").append(`<div id="scmodal", class="modal">
|
|
184
|
+
<div class="modal-dialog">
|
|
185
|
+
<div class="modal-content">
|
|
186
|
+
<div class="modal-header">
|
|
187
|
+
<h5 class="modal-title">Modal title</h5>
|
|
188
|
+
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
|
|
189
|
+
</button>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="modal-body">
|
|
192
|
+
<p>Modal body text goes here.</p>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>`);
|
|
197
|
+
} else if ($("#scmodal").hasClass("show")) {
|
|
198
|
+
var myModalEl = document.getElementById("scmodal");
|
|
199
|
+
var modal = bootstrap.Modal.getInstance(myModalEl);
|
|
200
|
+
modal.dispose();
|
|
201
|
+
}
|
|
202
|
+
const { path, query } = parent.splitPathQuery(url);
|
|
203
|
+
// submitReload ?
|
|
204
|
+
parent.router
|
|
205
|
+
.resolve({ pathname: `get${path}`, query: query })
|
|
206
|
+
.then((page) => {
|
|
207
|
+
const modalContent = page.content;
|
|
208
|
+
const title = page.title;
|
|
209
|
+
if (title) $("#scmodal .modal-title").html(title);
|
|
210
|
+
$("#scmodal .modal-body").html(modalContent);
|
|
211
|
+
new bootstrap.Modal($("#scmodal")).show();
|
|
212
|
+
// onOpen onClose initialize_page?
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function view_post(viewname, route, data, onDone) {
|
|
217
|
+
const result = await parent.router.resolve({
|
|
218
|
+
pathname: `post/view/${viewname}/${route}`,
|
|
219
|
+
data,
|
|
220
|
+
});
|
|
221
|
+
common_done(result);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function local_post(url, args) {
|
|
225
|
+
const result = await parent.router.resolve({
|
|
226
|
+
pathname: `post${url}`,
|
|
227
|
+
data: args,
|
|
228
|
+
});
|
|
229
|
+
if (result.redirect) await parent.handleRoute(result.redirect);
|
|
230
|
+
else common_done(result);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function local_post_json(url) {
|
|
234
|
+
const result = await parent.router.resolve({
|
|
235
|
+
pathname: `post${url}`,
|
|
236
|
+
});
|
|
237
|
+
if (result.redirect) await parent.handleRoute(result.redirect);
|
|
238
|
+
else common_done(result);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function make_unique_field(
|
|
242
|
+
id,
|
|
243
|
+
table_id,
|
|
244
|
+
field_name,
|
|
245
|
+
elem,
|
|
246
|
+
space,
|
|
247
|
+
start,
|
|
248
|
+
always_append,
|
|
249
|
+
char_type
|
|
250
|
+
) {
|
|
251
|
+
const value = $(elem).val();
|
|
252
|
+
if (!value) return;
|
|
253
|
+
const path = `/api/${table_id}?approximate=true&${encodeURIComponent(
|
|
254
|
+
field_name
|
|
255
|
+
)}=${encodeURIComponent(value)}&fields=${encodeURIComponent(field_name)}`;
|
|
256
|
+
try {
|
|
257
|
+
// TODO ch support local tables
|
|
258
|
+
const response = await parent.apiCall({
|
|
259
|
+
method: "GET",
|
|
260
|
+
path,
|
|
261
|
+
});
|
|
262
|
+
if (response.data.success) {
|
|
263
|
+
unique_field_from_rows(
|
|
264
|
+
response.data.success,
|
|
265
|
+
id,
|
|
266
|
+
field_name,
|
|
267
|
+
space,
|
|
268
|
+
start,
|
|
269
|
+
always_append,
|
|
270
|
+
char_type,
|
|
271
|
+
value
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.log("unable to 'make_unique_field'");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function reload_on_init() {
|
|
280
|
+
console.log("not yet supported");
|
|
281
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const buildForm = (entryView) => {
|
|
2
|
+
return new saltcorn.data.models.Form({
|
|
3
|
+
class: "login",
|
|
4
|
+
fields: [
|
|
5
|
+
new saltcorn.data.models.Field({
|
|
6
|
+
label: "E-mail",
|
|
7
|
+
name: "email",
|
|
8
|
+
type: "String",
|
|
9
|
+
attributes: {
|
|
10
|
+
input_type: "email",
|
|
11
|
+
},
|
|
12
|
+
validator: (s) => s.length < 128,
|
|
13
|
+
}),
|
|
14
|
+
new saltcorn.data.models.Field({
|
|
15
|
+
label: "Password",
|
|
16
|
+
name: "password",
|
|
17
|
+
input_type: "password",
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
action: "javascript:void(0);",
|
|
21
|
+
onSubmit: `javascript:loginFormSubmit(this, '${entryView}')`,
|
|
22
|
+
submitLabel: "Login",
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const renderLoginForm = (entryView, version_tag) => {
|
|
27
|
+
const loginForm = buildForm(entryView);
|
|
28
|
+
const layout = saltcorn.data.state.getState().layouts["sbadmin2"];
|
|
29
|
+
return layout.authWrap({
|
|
30
|
+
title: "login",
|
|
31
|
+
form: loginForm,
|
|
32
|
+
authLinks: { signup: "/auth/signup" }, // TODO ch '/auth/signup' link
|
|
33
|
+
alerts: [],
|
|
34
|
+
headers: [
|
|
35
|
+
{ css: `static_assets/${version_tag}/saltcorn.css` },
|
|
36
|
+
{ script: "js/utils/iframe_view_utils.js" },
|
|
37
|
+
],
|
|
38
|
+
csrfToken: false,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { fileExists, readJSON, writeJSON } from "./file_helpers.js";
|
|
2
|
+
|
|
3
|
+
async function updateScTables(tablesJSON) {
|
|
4
|
+
saltcorn.data.db.query("PRAGMA foreign_keys = OFF;");
|
|
5
|
+
for (const { table, rows } of tablesJSON.sc_tables) {
|
|
6
|
+
await saltcorn.data.db.deleteWhere(table);
|
|
7
|
+
for (const row of rows) {
|
|
8
|
+
await saltcorn.data.db.insert(table, row);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
saltcorn.data.db.query("PRAGMA foreign_keys = ON;");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function handleUserDefinedTables() {
|
|
15
|
+
const tables = await saltcorn.data.models.Table.find();
|
|
16
|
+
const existingTables = await saltcorn.data.db.listUserDefinedTables();
|
|
17
|
+
const skipIds = [1]; // skip user table
|
|
18
|
+
for (const table of tables) {
|
|
19
|
+
if (existingTables.find((row) => row.name === table.name)) {
|
|
20
|
+
skipIds.push(table.id);
|
|
21
|
+
} else if (table.name !== "users") {
|
|
22
|
+
// CREATE TABLE without inserting into _sc_tables
|
|
23
|
+
await saltcorn.data.models.Table.create(table.name, {}, table.id);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const fields = await saltcorn.data.models.Field.find();
|
|
27
|
+
for (const field of fields) {
|
|
28
|
+
if (skipIds.indexOf(field.table_id) < 0 && field.name !== "id")
|
|
29
|
+
await saltcorn.data.models.Field.create(field, false, field.id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function tablesUptodate(tables, historyFile) {
|
|
34
|
+
const history = await readJSON(historyFile, cordova.file.dataDirectory);
|
|
35
|
+
return tables.created_at.valueOf() < history.updated_at.valueOf();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function updateDbIfNecessary() {
|
|
39
|
+
const historyFile = "update_history";
|
|
40
|
+
const tablesJSON = await readJSON(
|
|
41
|
+
"tables.json",
|
|
42
|
+
`${cordova.file.applicationDirectory}${"www"}`
|
|
43
|
+
);
|
|
44
|
+
if (
|
|
45
|
+
!(await fileExists(`${cordova.file.dataDirectory}${historyFile}`)) ||
|
|
46
|
+
!(await tablesUptodate(tablesJSON, historyFile))
|
|
47
|
+
) {
|
|
48
|
+
await updateScTables(tablesJSON);
|
|
49
|
+
await saltcorn.data.state.getState().refresh_tables();
|
|
50
|
+
await handleUserDefinedTables();
|
|
51
|
+
await writeJSON(historyFile, cordova.file.dataDirectory, {
|
|
52
|
+
updated_at: new Date(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function getTableIds(tableNames) {
|
|
58
|
+
return (await saltcorn.data.models.Table.find())
|
|
59
|
+
.filter((table) => tableNames.indexOf(table.name) > -1)
|
|
60
|
+
.map((table) => table.id);
|
|
61
|
+
}
|