@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@saltcorn/mobile-app",
3
3
  "displayName": "Saltcorn mobile app",
4
- "version": "0.9.0-beta.6",
4
+ "version": "0.9.0-beta.8",
5
5
  "description": "Apache Cordova application with @saltcorn/markup",
6
6
  "main": "index.js",
7
7
  "scripts": {
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 tablesJSON = await readJSON(
303
- "tables.json",
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(tablesJSON);
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);
@@ -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
  },
@@ -40,6 +40,7 @@ function MobileResponse() {
40
40
 
41
41
  function status(value) {
42
42
  resStatus = value;
43
+ return this;
43
44
  }
44
45
 
45
46
  function getStatus() {
@@ -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
+ };
@@ -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
- return routingHistory[routingHistory.length - 1].route;
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 false;
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
- return true;
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
- if (handleOpenModal()) return;
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
- await saltcorn.data.db.update(tblName, { [table.pk_name]: to }, from);
268
- await saltcorn.data.db.query(
269
- `update "${saltcorn.data.db.sqlsanitize(tblName)}_sync_info"
270
- set ref = ${to}
271
- where ref = ${from} and deleted = false`
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 = changes.map((change) =>
300
- deleted
301
- ? change[pkName]
302
- : translated?.[change[pkName]] || change[pkName]
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 handleTranslatedIds(translatedIds);
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(tables, historyFile) {
134
- const history = await readJSON(historyFile, cordova.file.dataDirectory);
135
- return tables.created_at.valueOf() < history.updated_at.valueOf();
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
- async function dbUpdateNeeded(tablesJSON) {
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(tablesJSON, historyFile))
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