@saltcorn/mobile-app 0.9.0-beta.6 → 0.9.0-beta.8
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 +1 -1
- package/www/index.html +14 -3
- package/www/js/mocks/request.js +2 -0
- package/www/js/mocks/response.js +1 -0
- package/www/js/routes/fields.js +31 -0
- package/www/js/routes/init.js +5 -1
- package/www/js/utils/global_utils.js +16 -5
- package/www/js/utils/iframe_view_utils.js +7 -2
- package/www/js/utils/offline_mode_helper.js +41 -13
- package/www/js/utils/table_utils.js +17 -6
package/package.json
CHANGED
package/www/index.html
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
<script src="js/routes/error.js"></script>
|
|
23
23
|
<script src="js/routes/page.js"></script>
|
|
24
24
|
<script src="js/routes/view.js"></script>
|
|
25
|
+
<script src="js/routes/fields.js"></script>
|
|
25
26
|
<script src="js/routes/sync.js"></script>
|
|
26
27
|
<script src="js/routes/init.js"></script>
|
|
27
28
|
|
|
@@ -299,15 +300,20 @@
|
|
|
299
300
|
"config",
|
|
300
301
|
`${cordova.file.applicationDirectory}www`
|
|
301
302
|
);
|
|
302
|
-
const
|
|
303
|
-
"
|
|
303
|
+
const { created_at } = await readJSON(
|
|
304
|
+
"tables_created_at.json",
|
|
304
305
|
`${cordova.file.applicationDirectory}${"www"}`
|
|
305
306
|
);
|
|
307
|
+
let tablesJSON = null;
|
|
306
308
|
await addScripts(config.version_tag);
|
|
307
309
|
saltcorn.data.db.connectObj.version_tag = config.version_tag;
|
|
308
310
|
await saltcorn.data.db.init();
|
|
309
|
-
const updateNeeded = await dbUpdateNeeded(
|
|
311
|
+
const updateNeeded = await dbUpdateNeeded(created_at);
|
|
310
312
|
if (updateNeeded) {
|
|
313
|
+
tablesJSON = await readJSON(
|
|
314
|
+
"tables.json",
|
|
315
|
+
`${cordova.file.applicationDirectory}${"www"}`
|
|
316
|
+
);
|
|
311
317
|
// update '_sc_plugins' first because of loadPlugins()
|
|
312
318
|
await updateScPlugins(tablesJSON);
|
|
313
319
|
}
|
|
@@ -318,6 +324,11 @@
|
|
|
318
324
|
state.registerPlugin("sbadmin2", saltcorn.sbadmin2);
|
|
319
325
|
collectPluginHeaders(await loadPlugins(state));
|
|
320
326
|
if (updateNeeded) {
|
|
327
|
+
if (!tablesJSON)
|
|
328
|
+
tablesJSON = await readJSON(
|
|
329
|
+
"tables.json",
|
|
330
|
+
`${cordova.file.applicationDirectory}${"www"}`
|
|
331
|
+
);
|
|
321
332
|
await updateDb(tablesJSON);
|
|
322
333
|
}
|
|
323
334
|
await createSyncInfoTables(config.synchedTables);
|
package/www/js/mocks/request.js
CHANGED
|
@@ -4,6 +4,7 @@ function MobileRequest({
|
|
|
4
4
|
xhr = false,
|
|
5
5
|
files = undefined,
|
|
6
6
|
query = undefined,
|
|
7
|
+
body = undefined,
|
|
7
8
|
refererRoute = undefined,
|
|
8
9
|
} = {}) {
|
|
9
10
|
const cfg = saltcorn.data.state.getState().mobileConfig;
|
|
@@ -54,6 +55,7 @@ function MobileRequest({
|
|
|
54
55
|
xhr,
|
|
55
56
|
files,
|
|
56
57
|
query,
|
|
58
|
+
body,
|
|
57
59
|
headers: {
|
|
58
60
|
referer: referer,
|
|
59
61
|
},
|
package/www/js/mocks/response.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*global saltcorn, apiCall, MobileRequest, MobileResponse, offlineHelper, parseQuery */
|
|
2
|
+
|
|
3
|
+
const postShowCalculated = async (context) => {
|
|
4
|
+
const { tableName, fieldName, fieldview } = context.params;
|
|
5
|
+
const mobileConfig = saltcorn.data.state.getState().mobileConfig;
|
|
6
|
+
const table = saltcorn.data.models.Table.findOne({ name: tableName });
|
|
7
|
+
if (!table) throw new Error(`The table '${tableName}' does not exist.`);
|
|
8
|
+
if (
|
|
9
|
+
mobileConfig.isOfflineMode ||
|
|
10
|
+
mobileConfig.localTableIds.indexOf(table.id) >= 0
|
|
11
|
+
) {
|
|
12
|
+
const req = new MobileRequest({
|
|
13
|
+
query: context.query ? parseQuery(context.query) : {},
|
|
14
|
+
body: context.data || {},
|
|
15
|
+
});
|
|
16
|
+
const res = new MobileResponse();
|
|
17
|
+
await saltcorn.data.web_mobile_commons.show_calculated_fieldview(req, res, {
|
|
18
|
+
tableName,
|
|
19
|
+
fieldName,
|
|
20
|
+
fieldview,
|
|
21
|
+
});
|
|
22
|
+
return res.getSendData();
|
|
23
|
+
} else {
|
|
24
|
+
const response = await apiCall({
|
|
25
|
+
method: "POST",
|
|
26
|
+
path: `/field/show-calculated/${tableName}/${fieldName}/${fieldview}`,
|
|
27
|
+
body: context.data || {},
|
|
28
|
+
});
|
|
29
|
+
return response.data;
|
|
30
|
+
}
|
|
31
|
+
};
|
package/www/js/routes/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*global postView, postViewRoute, getView, postToggleField, deleteRows, postPageAction, getPage, getLoginView, logoutAction, getSignupView, getErrorView, window, getSyncSettingsView, getAskDeleteOfflineData, getAskUploadNotEnded, updateTableRow, insertTableRow */
|
|
1
|
+
/*global postView, postViewRoute, getView, postToggleField, deleteRows, postPageAction, getPage, getLoginView, logoutAction, getSignupView, getErrorView, window, getSyncSettingsView, getAskDeleteOfflineData, getAskUploadNotEnded, updateTableRow, insertTableRow, postShowCalculated */
|
|
2
2
|
// TODO module namespacese
|
|
3
3
|
|
|
4
4
|
const initRoutes = async () => {
|
|
@@ -67,6 +67,10 @@ const initRoutes = async () => {
|
|
|
67
67
|
path: "get/sync/ask_delete_offline_data",
|
|
68
68
|
action: getAskDeleteOfflineData,
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
path: "post/field/show-calculated/:tableName/:fieldName/:fieldview",
|
|
72
|
+
action: postShowCalculated,
|
|
73
|
+
},
|
|
70
74
|
];
|
|
71
75
|
window.router = new window.UniversalRouter(routes);
|
|
72
76
|
};
|
|
@@ -4,7 +4,11 @@ let routingHistory = [];
|
|
|
4
4
|
|
|
5
5
|
function currentLocation() {
|
|
6
6
|
if (routingHistory.length == 0) return undefined;
|
|
7
|
-
|
|
7
|
+
let index = routingHistory.length - 1;
|
|
8
|
+
while (index > 0 && routingHistory[index].route.startsWith("post/")) {
|
|
9
|
+
index--;
|
|
10
|
+
}
|
|
11
|
+
return routingHistory[index].route;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
function currentQuery() {
|
|
@@ -210,12 +214,15 @@ async function gotoEntryView() {
|
|
|
210
214
|
}
|
|
211
215
|
|
|
212
216
|
function handleOpenModal() {
|
|
217
|
+
const result = { moddalWasOpen: false, noSubmitReload: false };
|
|
213
218
|
const iframe = document.getElementById("content-iframe");
|
|
214
|
-
if (!iframe) return
|
|
219
|
+
if (!iframe) return result;
|
|
215
220
|
const openModal = iframe.contentWindow.$("#scmodal.modal.show");
|
|
216
|
-
if (openModal.length === 0) return;
|
|
221
|
+
if (openModal.length === 0) return result;
|
|
222
|
+
result.moddalWasOpen = true;
|
|
217
223
|
iframe.contentWindow.bootstrap.Modal.getInstance(openModal[0]).hide();
|
|
218
|
-
|
|
224
|
+
result.noSubmitReload = openModal[0].classList.contains("no-submit-reload");
|
|
225
|
+
return result;
|
|
219
226
|
}
|
|
220
227
|
|
|
221
228
|
async function handleRoute(route, query, files, data) {
|
|
@@ -242,7 +249,11 @@ async function handleRoute(route, query, files, data) {
|
|
|
242
249
|
alerts: [],
|
|
243
250
|
});
|
|
244
251
|
if (page.redirect) {
|
|
245
|
-
|
|
252
|
+
const { moddalWasOpen, noSubmitReload } = handleOpenModal();
|
|
253
|
+
if (moddalWasOpen) {
|
|
254
|
+
if (noSubmitReload) return;
|
|
255
|
+
else return await reload();
|
|
256
|
+
}
|
|
246
257
|
if (
|
|
247
258
|
page.redirect.startsWith("http://localhost") ||
|
|
248
259
|
page.redirect === "undefined"
|
|
@@ -479,9 +479,10 @@ async function mobile_modal(url, opts = {}) {
|
|
|
479
479
|
var modal = bootstrap.Modal.getInstance(myModalEl);
|
|
480
480
|
modal.dispose();
|
|
481
481
|
}
|
|
482
|
+
if (opts.submitReload === false) $("#scmodal").addClass("no-submit-reload");
|
|
483
|
+
else $("#scmodal").removeClass("no-submit-reload");
|
|
482
484
|
try {
|
|
483
485
|
const { path, query } = parent.splitPathQuery(url);
|
|
484
|
-
// submitReload ?
|
|
485
486
|
const mobileConfig = parent.saltcorn.data.state.getState().mobileConfig;
|
|
486
487
|
if (
|
|
487
488
|
mobileConfig.networkState === "none" &&
|
|
@@ -534,17 +535,21 @@ async function local_post(url, args) {
|
|
|
534
535
|
}
|
|
535
536
|
}
|
|
536
537
|
|
|
537
|
-
async function local_post_json(url) {
|
|
538
|
+
async function local_post_json(url, data, cb) {
|
|
538
539
|
try {
|
|
539
540
|
showLoadSpinner();
|
|
540
541
|
const result = await parent.router.resolve({
|
|
541
542
|
pathname: `post${url}`,
|
|
543
|
+
data: data,
|
|
544
|
+
query: parent.currentQuery(),
|
|
542
545
|
});
|
|
543
546
|
if (result.server_eval) await evalServerCode(url);
|
|
544
547
|
if (result.redirect) await parent.handleRoute(result.redirect);
|
|
545
548
|
else common_done(result, "", false);
|
|
549
|
+
if (cb?.success) cb.success(result);
|
|
546
550
|
} catch (error) {
|
|
547
551
|
parent.errorAlert(error);
|
|
552
|
+
if (cb?.error) cb.error(error);
|
|
548
553
|
} finally {
|
|
549
554
|
removeLoadSpinner();
|
|
550
555
|
}
|
|
@@ -254,22 +254,26 @@ var offlineHelper = (() => {
|
|
|
254
254
|
return result;
|
|
255
255
|
};
|
|
256
256
|
|
|
257
|
-
const handleTranslatedIds = async (allTranslations) => {
|
|
257
|
+
const handleTranslatedIds = async (allUniqueConflicts, allTranslations) => {
|
|
258
258
|
const idToTable = {};
|
|
259
259
|
for (const [tblName, translations] of Object.entries(allTranslations)) {
|
|
260
260
|
const fks = await saltcorn.data.models.Field.find({
|
|
261
261
|
reftable_name: tblName,
|
|
262
262
|
});
|
|
263
|
+
const uniqueConflicts = (allUniqueConflicts[tblName] =
|
|
264
|
+
allUniqueConflicts[tblName] || []);
|
|
263
265
|
const table = saltcorn.data.models.Table.findOne({ name: tblName });
|
|
264
266
|
const transArr = Array.from(Object.entries(translations));
|
|
265
267
|
transArr.sort((a, b) => parseInt(b[1]) - parseInt(a[1]));
|
|
266
268
|
for (const [from, to] of transArr) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
if (!uniqueConflicts.find((conf) => conf[table.pk_name] === to)) {
|
|
270
|
+
await saltcorn.data.db.update(tblName, { [table.pk_name]: to }, from);
|
|
271
|
+
await saltcorn.data.db.query(
|
|
272
|
+
`update "${saltcorn.data.db.sqlsanitize(tblName)}_sync_info"
|
|
273
|
+
set ref = ${to}
|
|
274
|
+
where ref = ${from} and deleted = false`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
273
277
|
for (const fk of fks) {
|
|
274
278
|
if (!idToTable[fk.table_id])
|
|
275
279
|
idToTable[fk.table_id] = saltcorn.data.models.Table.findOne(
|
|
@@ -287,6 +291,25 @@ var offlineHelper = (() => {
|
|
|
287
291
|
}
|
|
288
292
|
};
|
|
289
293
|
|
|
294
|
+
const handleUniqueConflicts = async (uniqueConflicts, translatedIds) => {
|
|
295
|
+
for (const [tblName, conflicts] of Object.entries(uniqueConflicts)) {
|
|
296
|
+
const table = saltcorn.data.models.Table.findOne({ name: tblName });
|
|
297
|
+
const pkName = table.pk_name || "id";
|
|
298
|
+
const translated = translatedIds[tblName] || {};
|
|
299
|
+
for (const conflict of conflicts) {
|
|
300
|
+
for (const [from, to] of Object.entries(translated)) {
|
|
301
|
+
if (to === conflict[pkName]) {
|
|
302
|
+
await table.deleteRows({ [pkName]: from });
|
|
303
|
+
await saltcorn.data.db.deleteWhere(`${table.name}_sync_info`, {
|
|
304
|
+
ref: from,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
await saltcorn.data.db.insert(tblName, conflict, { replace: true });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
290
313
|
const updateSyncInfos = async (
|
|
291
314
|
offlineChanges,
|
|
292
315
|
allTranslations,
|
|
@@ -296,10 +319,14 @@ var offlineHelper = (() => {
|
|
|
296
319
|
const table = saltcorn.data.models.Table.findOne({ name: tblName });
|
|
297
320
|
const pkName = table.pk_name;
|
|
298
321
|
const translated = allTranslations[tblName];
|
|
299
|
-
const refIds =
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
322
|
+
const refIds = Array.from(
|
|
323
|
+
new Set(
|
|
324
|
+
changes.map((change) =>
|
|
325
|
+
deleted
|
|
326
|
+
? change[pkName]
|
|
327
|
+
: translated?.[change[pkName]] || change[pkName]
|
|
328
|
+
)
|
|
329
|
+
)
|
|
303
330
|
);
|
|
304
331
|
const values = refIds.map(
|
|
305
332
|
(ref) => `(${ref}, ${syncTimestamp}, ${deleted}, false)`
|
|
@@ -342,11 +369,12 @@ var offlineHelper = (() => {
|
|
|
342
369
|
path: `/sync/upload_finished?dir_name=${encodeURIComponent(syncDir)}`,
|
|
343
370
|
});
|
|
344
371
|
pollCount++;
|
|
345
|
-
const { finished, translatedIds, error } = pollResp.data;
|
|
372
|
+
const { finished, translatedIds, uniqueConflicts, error } = pollResp.data;
|
|
346
373
|
if (finished) {
|
|
347
374
|
if (error) throw new Error(error.message);
|
|
348
375
|
else {
|
|
349
|
-
await
|
|
376
|
+
await handleUniqueConflicts(uniqueConflicts, translatedIds);
|
|
377
|
+
await handleTranslatedIds(uniqueConflicts, translatedIds);
|
|
350
378
|
await updateSyncInfos(offlineChanges, translatedIds, syncTimestamp);
|
|
351
379
|
return syncDir;
|
|
352
380
|
}
|
|
@@ -130,15 +130,26 @@ async function createSyncInfoTables(synchTbls) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
async function tablesUptodate(
|
|
134
|
-
const
|
|
135
|
-
|
|
133
|
+
async function tablesUptodate(createdAt, historyFile) {
|
|
134
|
+
const { updated_at } = await readJSON(
|
|
135
|
+
historyFile,
|
|
136
|
+
cordova.file.dataDirectory
|
|
137
|
+
);
|
|
138
|
+
if (!updated_at) {
|
|
139
|
+
console.log("No updated_at in history file");
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
return createdAt < updated_at;
|
|
136
143
|
}
|
|
137
144
|
|
|
138
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Do a table update when the history file doesn't exist or is older than createdAt
|
|
147
|
+
* @param {number} createdAt UTC Date number when the tables.json file was created on the server
|
|
148
|
+
*/
|
|
149
|
+
async function dbUpdateNeeded(createdAt) {
|
|
139
150
|
return (
|
|
140
151
|
!(await fileExists(`${cordova.file.dataDirectory}${historyFile}`)) ||
|
|
141
|
-
!(await tablesUptodate(
|
|
152
|
+
!(await tablesUptodate(createdAt, historyFile))
|
|
142
153
|
);
|
|
143
154
|
}
|
|
144
155
|
|
|
@@ -147,7 +158,7 @@ async function updateDb(tablesJSON) {
|
|
|
147
158
|
await saltcorn.data.state.getState().refresh_tables();
|
|
148
159
|
await updateUserDefinedTables();
|
|
149
160
|
await writeJSON(historyFile, cordova.file.dataDirectory, {
|
|
150
|
-
updated_at: new Date(),
|
|
161
|
+
updated_at: new Date().valueOf(),
|
|
151
162
|
});
|
|
152
163
|
}
|
|
153
164
|
|