@saltcorn/mobile-app 1.5.0-beta.17 → 1.5.0-beta.19

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { Capacitor } from "@capacitor/core";
4
4
  import { apiCall } from "./api";
5
- import { showAlerts } from "./common";
5
+ import { showToasts } from "./common";
6
6
  import { PushNotifications } from "@capacitor/push-notifications";
7
7
  import { Device } from "@capacitor/device";
8
8
 
@@ -151,7 +151,7 @@ export function addPusNotifyHandler() {
151
151
  const state = saltcorn.data.state.getState();
152
152
  state.mobile_push_handler["push_notification"] = (notification) => {
153
153
  console.log("Push notification received:", notification);
154
- showAlerts([
154
+ showToasts([
155
155
  {
156
156
  type: "info",
157
157
  msg: notification.body,
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.5.0-beta.17",
4
+ "version": "1.5.0-beta.19",
5
5
  "description": "Saltcorn mobile app for Android and iOS",
6
6
  "main": "index.js",
7
7
  "scripts": {
@@ -7,7 +7,7 @@ import { router } from "../routing/index";
7
7
  import { getLastOfflineSession, deleteOfflineData, sync } from "./offline_mode";
8
8
  import { addRoute, replaceIframe, clearHistory } from "../helpers/navigation";
9
9
  import {
10
- showAlerts,
10
+ showToasts,
11
11
  tryInitBackgroundSync,
12
12
  tryInitPush,
13
13
  tryStopBackgroundSync,
@@ -69,7 +69,7 @@ const initialSync = async (config) => {
69
69
  const alerts = [];
70
70
  const { offlineUser, hasOfflineData } = (await getLastOfflineSession()) || {};
71
71
  if (!offlineUser || offlineUser === config.user.email) {
72
- await sync(false, alerts);
72
+ await sync(true, true, alerts);
73
73
  } else {
74
74
  if (hasOfflineData)
75
75
  alerts.push({
@@ -78,7 +78,7 @@ const initialSync = async (config) => {
78
78
  });
79
79
  else {
80
80
  await deleteOfflineData(true);
81
- await sync(false, alerts);
81
+ await sync(true, true, alerts);
82
82
  }
83
83
  }
84
84
  return alerts;
@@ -138,7 +138,7 @@ export async function login({ email, password, isSignup, token }) {
138
138
  });
139
139
  if (page.content) await replaceIframe(page.content, page.isFile);
140
140
  } else if (loginResult?.alerts) {
141
- showAlerts(loginResult?.alerts);
141
+ showToasts(loginResult?.alerts);
142
142
  } else {
143
143
  throw new Error("The login failed.");
144
144
  }
@@ -180,13 +180,13 @@ export async function publicLogin(entryPoint) {
180
180
  });
181
181
  if (page.content) await replaceIframe(page.content, page.isFile);
182
182
  } else if (loginResult?.alerts) {
183
- showAlerts(loginResult?.alerts);
183
+ showToasts(loginResult?.alerts);
184
184
  } else {
185
185
  throw new Error("The login failed.");
186
186
  }
187
187
  } catch (error) {
188
188
  console.error(error);
189
- showAlerts([
189
+ showToasts([
190
190
  {
191
191
  type: "error",
192
192
  msg: error.message ? error.message : "An error occured.",
@@ -215,7 +215,7 @@ export async function logout() {
215
215
  } else throw new Error("Unable to logout.");
216
216
  } catch (error) {
217
217
  console.error("unable to logout:", error);
218
- showAlerts([
218
+ showToasts([
219
219
  {
220
220
  type: "error",
221
221
  msg: error.message ? error.message : "An error occured.",
@@ -8,22 +8,28 @@ import { addPushSyncHandler } from "./offline_mode";
8
8
 
9
9
  const orientationChangeListeners = new Set();
10
10
 
11
- export function clearAlerts() {
11
+ /**
12
+ * Make the toast area shown at the bottom empty
13
+ */
14
+ export function clearToasts() {
12
15
  const iframe = document.getElementById("content-iframe");
13
16
  const alertsArea =
14
17
  iframe.contentWindow.document.getElementById("toasts-area");
15
18
  alertsArea.innerHTML = "";
16
19
  }
17
20
 
18
- export function showAlerts(alerts, toast = true) {
21
+ /**
22
+ * Show toasts in the toast area at the bottom
23
+ * @param toasts
24
+ * @returns
25
+ */
26
+ export function showToasts(toasts) {
19
27
  if (typeof saltcorn === "undefined") {
20
28
  console.log("Not yet initalized.");
21
- console.log(alerts);
29
+ console.log(toasts);
22
30
  } else {
23
31
  const iframe = document.getElementById("content-iframe");
24
- let area = iframe.contentWindow.document.getElementById(
25
- toast ? "toasts-area" : "top-alert"
26
- );
32
+ let area = iframe.contentWindow.document.getElementById("toasts-area");
27
33
  if (!area) {
28
34
  const areaHtml = `<div class="container">
29
35
  <div
@@ -37,18 +43,14 @@ export function showAlerts(alerts, toast = true) {
37
43
  iframe.contentWindow.document
38
44
  .getElementById("page-inner-content")
39
45
  .insertAdjacentHTML("beforeend", areaHtml);
40
- area = iframe.contentWindow.document.getElementById(
41
- toast ? "toasts-area" : "top-alert"
42
- );
46
+ area = iframe.contentWindow.document.getElementById("toasts-area");
43
47
  }
44
48
  const successIds = [];
45
49
  area.innerHTML = "";
46
- for (const { type, msg, title } of alerts) {
47
- if (toast) {
48
- const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
49
- area.innerHTML += saltcorn.markup.toast(type, msg, rndid, title);
50
- if (type === "success") successIds.push(rndid);
51
- } else area.innerHTML += saltcorn.markup.alert(type, msg);
50
+ for (const { type, msg, title } of toasts) {
51
+ const rndid = `tab${Math.floor(Math.random() * 16777215).toString(16)}`;
52
+ area.innerHTML += saltcorn.markup.toast(type, msg, rndid, title);
53
+ if (type === "success") successIds.push(rndid);
52
54
  }
53
55
  if (successIds.length > 0) {
54
56
  setTimeout(() => {
@@ -62,6 +64,34 @@ export function showAlerts(alerts, toast = true) {
62
64
  return true;
63
65
  }
64
66
 
67
+ /**
68
+ * Make the alert area at the top empty
69
+ */
70
+ export function clearAlerts() {
71
+ const iframe = document.getElementById("content-iframe");
72
+ const area = iframe.contentWindow.document.getElementById("alerts-area");
73
+ if (area) area.innerHTML = "";
74
+ const topAlert = iframe.contentWindow.document.getElementById("top-alert");
75
+ if (topAlert) topAlert.innerHTML = "";
76
+ }
77
+
78
+ /**
79
+ * Show alerts in the alert area at the top
80
+ * @param alerts
81
+ */
82
+ export function showAlerts(alerts) {
83
+ if (typeof saltcorn === "undefined") {
84
+ console.log("Not yet initalized.");
85
+ console.log(alerts);
86
+ } else {
87
+ for (const { type, msg, title } of alerts) {
88
+ const iframe = document.getElementById("content-iframe");
89
+ const area = iframe.contentWindow.document.getElementById("top-alert");
90
+ area.innerHTML += saltcorn.markup.alert(type, msg);
91
+ }
92
+ }
93
+ }
94
+
65
95
  export function showLoadSpinner() {
66
96
  const iframe = document.getElementById("content-iframe");
67
97
  if (iframe) iframe.contentWindow.showLoadSpinner();
@@ -72,17 +102,9 @@ export function removeLoadSpinner() {
72
102
  if (iframe) iframe.contentWindow.removeLoadSpinner();
73
103
  }
74
104
 
75
- export function clearTopAlerts() {
76
- const iframe = document.getElementById("content-iframe");
77
- const area = iframe.contentWindow.document.getElementById("alerts-area");
78
- if (area) area.innerHTML = "";
79
- const topAlert = iframe.contentWindow.document.getElementById("top-alert");
80
- if (topAlert) topAlert.innerHTML = "";
81
- }
82
-
83
105
  export function errorAlert(error) {
84
106
  console.error(error);
85
- showAlerts([
107
+ showToasts([
86
108
  {
87
109
  type: "error",
88
110
  msg: error.message ? error.message : "An error occured.",
@@ -110,7 +132,7 @@ export async function loadFileAsText(fileId) {
110
132
  });
111
133
  } catch (error) {
112
134
  if (
113
- !showAlerts([
135
+ !showToasts([
114
136
  {
115
137
  type: "error",
116
138
  msg: error.message ? error.message : "An error occured.",
@@ -139,7 +161,7 @@ export async function loadEncodedFile(fileId) {
139
161
  reader.readAsDataURL(response.data);
140
162
  });
141
163
  } catch (error) {
142
- showAlerts([
164
+ showToasts([
143
165
  {
144
166
  type: "error",
145
167
  msg: error.message ? error.message : "An error occured.",
@@ -157,7 +179,7 @@ export async function takePhoto() {
157
179
  });
158
180
  return image.path;
159
181
  } catch (error) {
160
- showAlerts([
182
+ showToasts([
161
183
  {
162
184
  type: "error",
163
185
  msg: error.message ? error.message : "An error occured.",
@@ -3,7 +3,7 @@ import i18next from "i18next";
3
3
 
4
4
  import { router } from "../routing/index";
5
5
  import { startOfflineMode } from "./offline_mode";
6
- import { showAlerts } from "./common";
6
+ import { showToasts } from "./common";
7
7
 
8
8
  export let routingHistory = [];
9
9
 
@@ -186,7 +186,7 @@ export async function handleRoute(route, query, files, data) {
186
186
  await replaceIframeInnerContent(page.content);
187
187
  else await replaceIframe(page.content, page.isFile);
188
188
  } else {
189
- showAlerts([
189
+ showToasts([
190
190
  {
191
191
  type: "warning",
192
192
  msg: i18next.t("%s finished without a result", {
@@ -199,7 +199,7 @@ export async function handleRoute(route, query, files, data) {
199
199
  }
200
200
  } catch (error) {
201
201
  if (routeAdded) popRoute();
202
- showAlerts([
202
+ showToasts([
203
203
  {
204
204
  type: "error",
205
205
  msg: `${i18next.t("In %s", {
@@ -253,7 +253,7 @@ export async function gotoEntryView() {
253
253
  addRoute({ route: mobileConfig.entry_point, query: undefined });
254
254
  await replaceIframeInnerContent(page.content);
255
255
  } catch (error) {
256
- showAlerts([
256
+ showToasts([
257
257
  {
258
258
  type: "error",
259
259
  msg: error.message ? error.message : "An error occured.",
@@ -2,8 +2,10 @@
2
2
 
3
3
  import { apiCall } from "./api";
4
4
  import {
5
- showAlerts,
5
+ showToasts,
6
+ clearToasts,
6
7
  clearAlerts,
8
+ showAlerts,
7
9
  errorAlert,
8
10
  showLoadSpinner,
9
11
  removeLoadSpinner,
@@ -318,12 +320,28 @@ const handleUniqueConflicts = async (uniqueConflicts, translatedIds) => {
318
320
  }
319
321
  };
320
322
 
321
- const handleUpdateConflicts = async (dataConflicts, alerts) => {
323
+ /**
324
+ * If there was a field level data conflict, the server version is applied
325
+ * When it also was translated, change the untranslated row and translate it later the normal way
326
+ * @param {*} dataConflicts
327
+ * @param {*} translatedIds
328
+ * @param {*} alerts
329
+ */
330
+ const handleUpdateConflicts = async (dataConflicts, translatedIds, alerts) => {
322
331
  let hasConflicts = false;
323
332
  for (const [tblName, updates] of Object.entries(dataConflicts)) {
324
333
  const table = saltcorn.data.models.Table.findOne({ name: tblName });
325
334
  const pkName = table.pk_name || "id";
335
+ const translations = translatedIds[tblName] || {};
326
336
  for (const update of updates) {
337
+ // search a translation where the current row is the target
338
+ // and if found, make sure the untranslated row is updated
339
+ for (const [from, to] of Object.entries(translations)) {
340
+ if (to === update[pkName]) {
341
+ update[pkName] = from;
342
+ }
343
+ }
344
+
327
345
  const { [pkName]: _sc_pkValue, ...rest } = update;
328
346
  await table.updateRow(rest, _sc_pkValue);
329
347
  hasConflicts = true;
@@ -404,7 +422,7 @@ const syncOfflineData = async (synchedTables, syncTimestamp, alerts) => {
404
422
  if (finished) {
405
423
  if (error) throw new Error(error.message);
406
424
  else {
407
- await handleUpdateConflicts(dataConflicts, alerts);
425
+ await handleUpdateConflicts(dataConflicts, translatedIds, alerts);
408
426
  await handleUniqueConflicts(uniqueConflicts, translatedIds);
409
427
  await handleTranslatedIds(uniqueConflicts, translatedIds);
410
428
  await updateSyncInfos(offlineChanges, translatedIds, syncTimestamp);
@@ -481,16 +499,26 @@ const setSpinnerText = () => {
481
499
 
482
500
  let syncInProgress = false;
483
501
 
484
- export async function isSyncInProgress() {
502
+ export function isSyncInProgress() {
485
503
  return syncInProgress;
486
504
  }
487
505
 
488
- export async function sync(background = false, alerts = []) {
506
+ /**
507
+ *
508
+ * @param {boolean} withWakelock try request a wakelock and show a spinner during sync (propably not in background mode)
509
+ * @param {boolean} switchOnline end the offline mode and go online again after sync
510
+ * @param {string[]} alerts output alerts collected during sync
511
+ */
512
+ export async function sync(
513
+ withWakelock = true,
514
+ switchOnline = true,
515
+ alerts = []
516
+ ) {
489
517
  if (syncInProgress)
490
518
  throw new Error("A synchronization is already in progress.");
491
-
519
+ syncInProgress = true;
492
520
  try {
493
- if (!background) setSpinnerText();
521
+ if (withWakelock) setSpinnerText();
494
522
  const state = saltcorn.data.state.getState();
495
523
  const { user } = state.mobileConfig;
496
524
  const { offlineUser, hasOfflineData, uploadStarted, uploadStartTime } =
@@ -509,7 +537,7 @@ export async function sync(background = false, alerts = []) {
509
537
  const syncTimestamp = await getServerTime();
510
538
  await setUploadStarted(true, syncTimestamp);
511
539
  let lock = null;
512
- if (!background) {
540
+ if (withWakelock) {
513
541
  try {
514
542
  if (window.navigator?.wakeLock?.request)
515
543
  lock = await window.navigator.wakeLock.request();
@@ -529,7 +557,7 @@ export async function sync(background = false, alerts = []) {
529
557
  syncDir = await syncOfflineData(synchedTables, syncTimestamp, alerts);
530
558
  await syncRemoteData(syncInfos, syncTimestamp);
531
559
  await setLocalSyncTimestamp(syncTimestamp);
532
- if (!background) await endOfflineMode(true);
560
+ if (switchOnline) await endOfflineMode(true);
533
561
  await setUploadStarted(false);
534
562
  await saltcorn.data.db.query("COMMIT");
535
563
  transactionOpen = false;
@@ -626,21 +654,50 @@ export async function clearLocalData(inTransaction) {
626
654
  }
627
655
  }
628
656
 
629
- export function networkChangeCallback(status) {
657
+ export async function networkChangeCallback(status) {
630
658
  console.log("Network status changed", status);
631
659
  const mobileConfig = saltcorn.data.state.getState().mobileConfig;
632
660
  if (status.connectionType !== "none" && mobileConfig.isOfflineMode) {
633
661
  const iframeWindow = $("#content-iframe")[0].contentWindow;
634
- if (iframeWindow) {
635
- clearAlerts();
636
- iframeWindow.notifyAlert(
637
- `An internet connection is available, to end the offline mode click ${saltcorn.markup.a(
638
- {
639
- href: "javascript:execLink('/sync/sync_settings')",
640
- },
641
- "here"
642
- )}`
643
- );
662
+ if (mobileConfig.syncOnReconnect) {
663
+ if (isSyncInProgress()) {
664
+ console.log("Sync already in progress, skipping automatic sync");
665
+ return;
666
+ }
667
+ console.log("Network restored, starting automatic sync");
668
+ const toasts = [];
669
+ try {
670
+ await sync(false, true, toasts);
671
+ if (iframeWindow) {
672
+ clearAlerts();
673
+ showAlerts([
674
+ {
675
+ type: "info",
676
+ msg: "You are online again.",
677
+ },
678
+ ]);
679
+ }
680
+ toasts.push({
681
+ type: "info",
682
+ msg: "Synchronized your offline data.",
683
+ });
684
+ } catch (error) {
685
+ console.log("Error during push sync:", error);
686
+ }
687
+ if (toasts.length > 0) showToasts(toasts);
688
+ } else {
689
+ const iframeWindow = $("#content-iframe")[0].contentWindow;
690
+ if (iframeWindow) {
691
+ clearToasts();
692
+ iframeWindow.notifyAlert(
693
+ `An internet connection is available, to end the offline mode click ${saltcorn.markup.a(
694
+ {
695
+ href: "javascript:execLink('/sync/sync_settings')",
696
+ },
697
+ "here"
698
+ )}`
699
+ );
700
+ }
644
701
  }
645
702
  }
646
703
  mobileConfig.networkState = status.connectionType;
@@ -680,7 +737,7 @@ export async function deleteOfflineData(noFeedback) {
680
737
  await clearLocalData(false);
681
738
  await setHasOfflineData(false);
682
739
  if (!noFeedback)
683
- showAlerts([
740
+ showToasts([
684
741
  {
685
742
  type: "info",
686
743
  msg: "Deleted your offline data.",
@@ -700,8 +757,8 @@ export function addPushSyncHandler() {
700
757
  console.log("Push sync received:", notification);
701
758
  const alerts = [];
702
759
  try {
703
- await sync(true, alerts);
704
- if (alerts.length > 0) showAlerts(alerts);
760
+ await sync(false, false, alerts);
761
+ if (alerts.length > 0) showToasts(alerts);
705
762
  } catch (error) {
706
763
  console.log("Error during push sync:", error);
707
764
  }
package/src/init.js CHANGED
@@ -472,7 +472,7 @@ export async function init(mobileConfig) {
472
472
  }
473
473
  } else if (offlineUser) {
474
474
  if (offlineUser === mobileConfig.user.email) {
475
- await sync(false, alerts);
475
+ await sync(true, true, alerts);
476
476
  alerts.push({
477
477
  type: "info",
478
478
  msg: "Synchronized your offline data.",
@@ -483,7 +483,7 @@ export async function init(mobileConfig) {
483
483
  msg: `'${offlineUser}' has not yet uploaded offline data.`,
484
484
  });
485
485
  } else {
486
- await sync(false, alerts);
486
+ await sync(true, true, alerts);
487
487
  alerts.push({
488
488
  type: "info",
489
489
  msg: "Synchronized your offline data.",
@@ -192,7 +192,7 @@ async function inline_local_submit(e, opts1) {
192
192
  });
193
193
  inline_submit_success(e, form, opts);
194
194
  } catch (error) {
195
- parent.saltcorn.mobileApp.common.showAlerts([
195
+ parent.saltcorn.mobileApp.common.showToasts([
196
196
  {
197
197
  type: "error",
198
198
  msg: error.message ? error.message : "An error occured.",
@@ -565,7 +565,7 @@ async function mobile_modal(url, opts = {}) {
565
565
  // onOpen onClose initialize_page?
566
566
  }
567
567
  } catch (error) {
568
- parent.saltcorn.mobileApp.common.showAlerts([
568
+ parent.saltcorn.mobileApp.common.showToasts([
569
569
  {
570
570
  type: "error",
571
571
  msg: error.message ? error.message : "An error occured.",
@@ -665,7 +665,7 @@ async function make_unique_field(
665
665
  );
666
666
  }
667
667
  } catch (error) {
668
- parent.saltcorn.mobileApp.common.showAlerts([
668
+ parent.saltcorn.mobileApp.common.showToasts([
669
669
  {
670
670
  type: "error",
671
671
  msg: "unable to 'make_unique_field'",
@@ -835,16 +835,13 @@ async function switchNetworkMode() {
835
835
  parent.saltcorn.mobileApp.navigation.addRoute({
836
836
  route: "get/sync/sync_settings",
837
837
  });
838
- parent.saltcorn.mobileApp.common.showAlerts(
839
- [
840
- {
841
- type: "info",
842
- msg: parent.saltcorn.mobileApp.offlineMode.getOfflineMsg(),
843
- },
844
- ],
845
- false
846
- );
847
- parent.saltcorn.mobileApp.common.clearAlerts();
838
+ parent.saltcorn.mobileApp.common.showAlerts([
839
+ {
840
+ type: "info",
841
+ msg: parent.saltcorn.mobileApp.offlineMode.getOfflineMsg(),
842
+ },
843
+ ]);
844
+ parent.saltcorn.mobileApp.common.clearToasts();
848
845
  } else {
849
846
  if (networkState === "none")
850
847
  throw new Error("No internet connection is available.");
@@ -854,16 +851,16 @@ async function switchNetworkMode() {
854
851
  parent.saltcorn.mobileApp.navigation.addRoute({
855
852
  route: "get/sync/sync_settings",
856
853
  });
857
- parent.saltcorn.mobileApp.common.showAlerts([
854
+ parent.saltcorn.mobileApp.common.showToasts([
858
855
  {
859
856
  type: "info",
860
857
  msg: "You are online again.",
861
858
  },
862
859
  ]);
863
- parent.saltcorn.mobileApp.common.clearTopAlerts();
860
+ parent.saltcorn.mobileApp.common.clearAlerts();
864
861
  }
865
862
  } catch (error) {
866
- parent.saltcorn.mobileApp.common.showAlerts([
863
+ parent.saltcorn.mobileApp.common.showToasts([
867
864
  {
868
865
  type: "error",
869
866
  msg: `Unable to change the network mode: ${
@@ -883,7 +880,7 @@ async function callSync() {
883
880
  try {
884
881
  const mobileConfig = parent.saltcorn.data.state.getState().mobileConfig;
885
882
  if (mobileConfig.networkState === "none") {
886
- parent.saltcorn.mobileApp.common.showAlerts([
883
+ parent.saltcorn.mobileApp.common.showToasts([
887
884
  {
888
885
  type: "error",
889
886
  msg: "You don't have an internet connection.",
@@ -893,14 +890,14 @@ async function callSync() {
893
890
  const wasOffline = mobileConfig.isOfflineMode;
894
891
  showLoadSpinner();
895
892
  const alerts = [];
896
- await parent.saltcorn.mobileApp.offlineMode.sync(false, alerts);
897
- parent.saltcorn.mobileApp.common.clearAlerts();
893
+ await parent.saltcorn.mobileApp.offlineMode.sync(true, true, alerts);
894
+ parent.saltcorn.mobileApp.common.clearToasts();
898
895
  if (!wasOffline) {
899
896
  alerts.push({
900
897
  type: "info",
901
898
  msg: "Synchronized your offline data.",
902
899
  });
903
- parent.saltcorn.mobileApp.common.showAlerts(alerts);
900
+ parent.saltcorn.mobileApp.common.showToasts(alerts);
904
901
  } else {
905
902
  setNetworSwitcherOn();
906
903
  parent.saltcorn.mobileApp.navigation.clearHistory();
@@ -912,8 +909,8 @@ async function callSync() {
912
909
  type: "info",
913
910
  msg: "Synchronized your offline data, you are online again.",
914
911
  });
915
- parent.saltcorn.mobileApp.common.showAlerts(alerts);
916
- parent.saltcorn.mobileApp.common.clearTopAlerts();
912
+ parent.saltcorn.mobileApp.common.showToasts(alerts);
913
+ parent.saltcorn.mobileApp.common.clearAlerts();
917
914
  }
918
915
  }
919
916
  } catch (error) {
@@ -929,14 +926,14 @@ async function deleteOfflineDataClicked() {
929
926
  await parent.saltcorn.mobileApp.offlineMode.getLastOfflineSession();
930
927
  const { user } = parent.saltcorn.data.state.getState().mobileConfig;
931
928
  if (!lastOfflineSession?.offlineUser) {
932
- parent.saltcorn.mobileApp.common.showAlerts([
929
+ parent.saltcorn.mobileApp.common.showToasts([
933
930
  {
934
931
  type: "error",
935
932
  msg: "You don't have any offline data.",
936
933
  },
937
934
  ]);
938
935
  } else if (lastOfflineSession.offlineUser !== user.email) {
939
- parent.saltcorn.mobileApp.common.showAlerts([
936
+ parent.saltcorn.mobileApp.common.showToasts([
940
937
  {
941
938
  type: "error",
942
939
  msg: `The offline data is owned by '${lastOfflineSession.offlineUser}'.`,