@saltcorn/mobile-app 0.8.6-beta.9 → 0.8.7-beta.0

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 CHANGED
@@ -3,6 +3,7 @@
3
3
  <name>SaltcornMobileApp</name>
4
4
  <description>Apache Cordova application with @saltcorn/markup</description>
5
5
  <platform name="android">
6
+ <icon density="ldpi" foreground="res/icon/android/icon.png" background="res/icon/android/icon.png" />
6
7
  <edit-config file="app/src/main/AndroidManifest.xml" target="/manifest/application" mode="merge">
7
8
  <application android:usesCleartextTraffic="true"/>
8
9
  </edit-config>
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.8.6-beta.9",
4
+ "version": "0.8.7-beta.0",
5
5
  "description": "Apache Cordova application with @saltcorn/markup",
6
6
  "main": "index.js",
7
7
  "scripts": {
Binary file
package/www/index.html CHANGED
@@ -179,6 +179,7 @@
179
179
 
180
180
  // the app comes back from background
181
181
  const onResume = async () => {
182
+ if (typeof saltcorn === "undefined") return;
182
183
  const state = saltcorn.data.state.getState();
183
184
  const mobileConfig = state.mobileConfig;
184
185
  if (mobileConfig?.allowOfflineMode) {
@@ -367,6 +368,7 @@
367
368
  overflow: hidden;
368
369
  z-index: 999999;
369
370
  "
371
+ src="splash_page.html"
370
372
  >
371
373
  <p>Your browser does not support iframes.</p>
372
374
  </iframe>
@@ -131,7 +131,8 @@ const wrapContents = (contents, title, context, req) => {
131
131
  role: state.mobileConfig.role_id,
132
132
  menu: getMenu(req),
133
133
  headers: getHeaders(),
134
- brand: { name: "Saltcorn" },
134
+ // TODO logo as BASE 64 image
135
+ brand: { name: state.getConfig("site_name") || "Saltcorn" },
135
136
  bodyClass: "",
136
137
  currentUrl: "",
137
138
  })
@@ -47,6 +47,30 @@ async function readJSON(fileName, dirName) {
47
47
  });
48
48
  }
49
49
 
50
+ async function readBinary(fileName, dirName) {
51
+ const dirEntry = await getDirEntry(dirName);
52
+ return new Promise((resolve, reject) => {
53
+ dirEntry.getFile(
54
+ fileName,
55
+ { create: false, exclusive: false },
56
+ function (fileEntry) {
57
+ fileEntry.file(function (file) {
58
+ let reader = new FileReader();
59
+ reader.onloadend = function (e) {
60
+ resolve(this.result);
61
+ };
62
+ reader.readAsArrayBuffer(file);
63
+ });
64
+ },
65
+ function (err) {
66
+ console.log(`unable to read ${fileName}`);
67
+ console.log(err);
68
+ reject(err);
69
+ }
70
+ );
71
+ });
72
+ }
73
+
50
74
  async function write(fileName, dirName, content) {
51
75
  const dirEntry = await getDirEntry(dirName);
52
76
  return new Promise((resolve, reject) => {
@@ -1,4 +1,4 @@
1
- /*global window, offlineHelper, axios, write, cordova, router, getDirEntry, saltcorn, document, FileReader, navigator*/
1
+ /*global window, offlineHelper, axios, write, cordova, router, getDirEntry, saltcorn, document, FileReader, navigator, splashConfig*/
2
2
 
3
3
  let routingHistory = [];
4
4
 
@@ -25,7 +25,10 @@ function popRoute() {
25
25
  }
26
26
 
27
27
  async function apiCall({ method, path, params, body, responseType, timeout }) {
28
- const config = saltcorn.data.state.getState().mobileConfig;
28
+ const config =
29
+ typeof saltcorn !== "undefined"
30
+ ? saltcorn.data.state.getState().mobileConfig
31
+ : splashConfig;
29
32
  const serverPath = config.server_path;
30
33
  const url = `${serverPath}${path}`;
31
34
  const headers = {
@@ -60,12 +63,17 @@ function clearAlerts() {
60
63
  }
61
64
 
62
65
  function showAlerts(alerts) {
63
- const iframe = document.getElementById("content-iframe");
64
- const alertsArea =
65
- iframe.contentWindow.document.getElementById("alerts-area");
66
- alertsArea.innerHTML = "";
67
- for (const { type, msg } of alerts) {
68
- alertsArea.innerHTML += saltcorn.markup.alert(type, msg);
66
+ if (typeof saltcorn === "undefined") {
67
+ console.log("Not yet initalized.");
68
+ console.log(alerts);
69
+ } else {
70
+ const iframe = document.getElementById("content-iframe");
71
+ const alertsArea =
72
+ iframe.contentWindow.document.getElementById("alerts-area");
73
+ alertsArea.innerHTML = "";
74
+ for (const { type, msg } of alerts) {
75
+ alertsArea.innerHTML += saltcorn.markup.alert(type, msg);
76
+ }
69
77
  }
70
78
  }
71
79
 
@@ -194,9 +202,10 @@ async function handleRoute(route, query, files) {
194
202
  await gotoEntryView();
195
203
  } else {
196
204
  if (route === "/") return await gotoEntryView();
197
- addRoute({ route, query });
205
+ const safeRoute = route ? route : currentLocation();
206
+ addRoute({ route: safeRoute, query });
198
207
  const page = await router.resolve({
199
- pathname: route,
208
+ pathname: safeRoute,
200
209
  query: query,
201
210
  files: files,
202
211
  alerts: mobileConfig.isOfflineMode
@@ -35,13 +35,26 @@ async function execNavbarLink(url) {
35
35
  * @param {*} urlSuffix
36
36
  * @returns
37
37
  */
38
- async function formSubmit(e, urlSuffix, viewname) {
39
- e.submit();
38
+ async function formSubmit(e, urlSuffix, viewname, noSubmitCb) {
39
+ if (!noSubmitCb) e.submit();
40
40
  const files = {};
41
41
  const urlParams = new URLSearchParams();
42
42
  for (const entry of new FormData(e).entries()) {
43
43
  if (entry[1] instanceof File) files[entry[0]] = entry[1];
44
- else urlParams.append(entry[0], entry[1]);
44
+ else {
45
+ // is there a hidden input with a filename?
46
+ const domEl = $(e).find(
47
+ `[name='${entry[0]}'][mobile-camera-input='true']`
48
+ );
49
+ if (domEl.length > 0) {
50
+ const tokens = entry[1].split("/");
51
+ const fileName = tokens[tokens.length - 1];
52
+ const directory = tokens.splice(0, tokens.length - 1).join("/");
53
+ // read and add file to submit
54
+ const binary = await parent.readBinary(fileName, directory);
55
+ files[entry[0]] = new File([binary], fileName);
56
+ } else urlParams.append(entry[0], entry[1]);
57
+ }
45
58
  }
46
59
  const queryStr = urlParams.toString();
47
60
  await parent.handleRoute(`post${urlSuffix}${viewname}`, queryStr, files);
@@ -771,6 +784,48 @@ function removeLoadSpinner() {
771
784
  $("#scspinner").remove();
772
785
  }
773
786
 
787
+ /**
788
+ * is called when an input with capture=camera is used
789
+ * It takes a picture with the camera plugin, saves the file, and adds the filename as a hidden input.
790
+ * @param {*} fieldName
791
+ */
792
+ async function getPicture(fieldName) {
793
+ const cameraOptions = {
794
+ quality: 50,
795
+ encodingType: parent.Camera.EncodingType.JPEG,
796
+ destinationType: parent.Camera.DestinationType.FILE_URI,
797
+ };
798
+ const getPictureWithPromise = () => {
799
+ return new Promise((resolve, reject) => {
800
+ parent.navigator.camera.getPicture(
801
+ (imageDate) => {
802
+ return resolve(imageDate);
803
+ },
804
+ (message) => {
805
+ return reject(message);
806
+ },
807
+ cameraOptions
808
+ );
809
+ });
810
+ };
811
+ try {
812
+ const form = $(`#cptbtn${fieldName}`).closest("form");
813
+ const onsubmit = form.attr("onsubmit");
814
+ form.attr("onsubmit", "javascript:void(0)");
815
+ const fileURI = await getPictureWithPromise();
816
+ form.attr("onsubmit", onsubmit);
817
+ const inputId = `input${fieldName}`;
818
+ form.find(`#${inputId}`).remove();
819
+ form.append(
820
+ `<input class="d-none" id="${inputId}" name="${fieldName}" value="${fileURI}" mobile-camera-input="true" />`
821
+ );
822
+ const tokens = fileURI.split("/");
823
+ $(`#cpt-file-name-${fieldName}`).text(tokens[tokens.length - 1]);
824
+ } catch (error) {
825
+ parent.errorAlert(error);
826
+ }
827
+ }
828
+
774
829
  function reload_on_init() {
775
830
  console.log("not yet supported");
776
831
  }
@@ -34,7 +34,10 @@ async function updateScTables(tablesJSON, skipScPlugins = true) {
34
34
  // pick fields that really exist
35
35
  const insertRow = {};
36
36
  for (const safeField of existingFields) {
37
- if (row[safeField]) insertRow[safeField] = row[safeField];
37
+ const fromRow = row[safeField];
38
+ if (fromRow !== null && fromRow !== undefined) {
39
+ insertRow[safeField] = fromRow;
40
+ }
38
41
  }
39
42
  await saltcorn.data.db.insert(table, insertRow);
40
43
  }
@@ -67,7 +70,7 @@ async function updateUserDefinedTables() {
67
70
  const existingFields = (
68
71
  await saltcorn.data.db.query(`PRAGMA table_info('${sanitized}')`)
69
72
  ).rows.map((row) => row.name);
70
- for (const field of await table.getFields()) {
73
+ for (const field of table.getFields()) {
71
74
  if (
72
75
  existingFields.indexOf(saltcorn.data.db.sqlsanitize(field.name)) < 0
73
76
  ) {
@@ -0,0 +1,3 @@
1
+ <html>
2
+ <body></body>
3
+ </html>