@saltcorn/mobile-app 1.1.0-beta.8 → 1.1.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.
Files changed (40) hide show
  1. package/.babelrc +3 -0
  2. package/build_scripts/modify_android_manifest.js +47 -0
  3. package/build_scripts/modify_gradle_cfg.js +21 -0
  4. package/package.json +21 -12
  5. package/src/.eslintrc +21 -0
  6. package/src/helpers/api.js +43 -0
  7. package/src/helpers/auth.js +191 -0
  8. package/src/helpers/common.js +189 -0
  9. package/{www/js/utils/table_utils.js → src/helpers/db_schema.js} +18 -40
  10. package/src/helpers/file_system.js +102 -0
  11. package/{www/js/utils/global_utils.js → src/helpers/navigation.js} +189 -332
  12. package/src/helpers/offline_mode.js +645 -0
  13. package/src/index.js +20 -0
  14. package/src/init.js +424 -0
  15. package/src/routing/index.js +98 -0
  16. package/{www/js → src/routing}/mocks/request.js +5 -5
  17. package/{www/js → src/routing}/mocks/response.js +1 -1
  18. package/{www/js → src/routing}/routes/api.js +10 -15
  19. package/{www/js → src/routing}/routes/auth.js +12 -6
  20. package/{www/js → src/routing}/routes/delete.js +9 -6
  21. package/{www/js → src/routing}/routes/edit.js +9 -6
  22. package/src/routing/routes/error.js +6 -0
  23. package/{www/js → src/routing}/routes/fields.js +7 -2
  24. package/{www/js → src/routing}/routes/page.js +15 -10
  25. package/{www/js → src/routing}/routes/sync.js +17 -8
  26. package/{www/js → src/routing}/routes/view.js +20 -15
  27. package/{www/js/routes/common.js → src/routing/utils.js} +18 -13
  28. package/unsecure-default-key.jks +0 -0
  29. package/webpack.config.js +31 -0
  30. package/www/data/encoded_site_logo.js +1 -0
  31. package/www/index.html +23 -493
  32. package/www/js/{utils/iframe_view_utils.js → iframe_view_utils.js} +193 -274
  33. package/config.xml +0 -27
  34. package/res/icon/android/icon.png +0 -0
  35. package/res/screen/android/splash-icon.png +0 -0
  36. package/res/screen/ios/Default@2x~universal~anyany.png +0 -0
  37. package/www/js/routes/error.js +0 -5
  38. package/www/js/routes/init.js +0 -76
  39. package/www/js/utils/file_helpers.js +0 -108
  40. package/www/js/utils/offline_mode_helper.js +0 -625
@@ -1,16 +1,19 @@
1
- /*global i18next, apiCall, saltcorn, offlineHelper*/
1
+ /*global saltcorn */
2
+ import { apiCall } from "../../helpers/api";
3
+ import { setHasOfflineData } from "../../helpers/offline_mode";
4
+ import i18next from "i18next";
2
5
 
3
6
  // /toggle/:name/:id/:field_name
4
- const postToggleField = async (context) => {
7
+ export const postToggleField = async (context) => {
5
8
  const { name, id, field_name } = context.params;
6
9
  const table = await saltcorn.data.models.Table.findOne({ name });
7
10
  const state = saltcorn.data.state.getState();
8
- const { isOfflineMode, localTableIds, role_id } = state.mobileConfig;
11
+ const { isOfflineMode, localTableIds, user } = state.mobileConfig;
9
12
  if (isOfflineMode || localTableIds.indexOf(table.id) >= 0) {
10
- if (role_id > table.min_role_write)
13
+ if (user.role_id > table.min_role_write)
11
14
  throw new saltcorn.data.utils.NotAuthorized(i18next.t("Not authorized"));
12
- await table.toggleBool(+id, field_name); //TODO call with user
13
- if (isOfflineMode) await offlineHelper.setHasOfflineData(true);
15
+ await table.toggleBool(+id, field_name, user);
16
+ if (isOfflineMode) await setHasOfflineData(true);
14
17
  } else {
15
18
  await apiCall({
16
19
  method: "POST",
@@ -0,0 +1,6 @@
1
+ import { MobileRequest } from "../mocks/request";
2
+ import { wrapContents } from "../utils";
3
+
4
+ export const getErrorView = async (context) => {
5
+ return await wrapContents("", "Error", context, new MobileRequest());
6
+ };
@@ -1,6 +1,11 @@
1
- /*global saltcorn, apiCall, MobileRequest, MobileResponse, offlineHelper, parseQuery */
1
+ /*global saltcorn */
2
2
 
3
- const postShowCalculated = async (context) => {
3
+ import { MobileRequest } from "../mocks/request";
4
+ import { MobileResponse } from "../mocks/response";
5
+ import { parseQuery } from "../utils";
6
+ import { apiCall } from "../../helpers/api";
7
+
8
+ export const postShowCalculated = async (context) => {
4
9
  const { tableName, fieldName, fieldview } = context.params;
5
10
  const mobileConfig = saltcorn.data.state.getState().mobileConfig;
6
11
  const table = saltcorn.data.models.Table.findOne({ name: tableName });
@@ -1,13 +1,18 @@
1
- /*global window, MobileRequest, parseQuery, MobileResponse, wrapContents, saltcorn, loadFileAsText*/
1
+ /*global saltcorn */
2
+
3
+ import { MobileRequest } from "../mocks/request";
4
+ import { MobileResponse } from "../mocks/response";
5
+ import { parseQuery, wrapContents } from "../utils";
6
+ import { loadFileAsText } from "../../helpers/common";
2
7
 
3
8
  // post/page/:pagename/action/:rndid
4
- const postPageAction = async (context) => {
5
- const state = saltcorn.data.state.getState();
9
+ export const postPageAction = async (context) => {
10
+ const { user } = saltcorn.data.state.getState().mobileConfig;
6
11
  const req = new MobileRequest({ xhr: context.xhr });
7
12
  const { page_name, rndid } = context.params;
8
13
  const page = await saltcorn.data.models.Page.findOne({ name: page_name });
9
14
  if (!page) throw new Error(req.__("Page %s not found", page_name));
10
- if (state.mobileConfig.role_id > page.min_role) {
15
+ if (user.role_id > page.min_role) {
11
16
  throw new saltcorn.data.utils.NotAuthorized(req.__("Not authorized"));
12
17
  }
13
18
  let col;
@@ -37,8 +42,8 @@ const findPageOrGroup = (pagename) => {
37
42
  };
38
43
 
39
44
  const runPage = async (page, state, context, { req, res }) => {
40
- if (state.mobileConfig.role_id > page.min_role) {
41
- const additionalInfos = `: your role: ${state.mobileConfig.role_id}, page min_role: ${page.min_role}`;
45
+ if (state.mobileConfig.user.role_id > page.min_role) {
46
+ const additionalInfos = `: your role: ${state.mobileConfig.user.role_id}, page min_role: ${page.min_role}`;
42
47
  throw new saltcorn.data.utils.NotAuthorized(
43
48
  req.__("Not authorized") + additionalInfos
44
49
  );
@@ -65,8 +70,8 @@ const getEligiblePage = async (pageGroup, req) => {
65
70
  };
66
71
 
67
72
  const runPageGroup = async (pageGroup, state, context, { req, res }) => {
68
- if (state.mobileConfig.role_id > pageGroup.min_role) {
69
- const additionalInfos = `: your role: ${state.mobileConfig.role_id}, pagegroup min_role: ${pageGroup.min_role}`;
73
+ if (state.mobileConfig.user.role_id > pageGroup.min_role) {
74
+ const additionalInfos = `: your role: ${state.mobileConfig.user.role_id}, pagegroup min_role: ${pageGroup.min_role}`;
70
75
  throw new saltcorn.data.utils.NotAuthorized(
71
76
  req.__("Not authorized") + additionalInfos
72
77
  );
@@ -79,7 +84,7 @@ const runPageGroup = async (pageGroup, state, context, { req, res }) => {
79
84
  };
80
85
 
81
86
  // get/page/pagename
82
- const getPage = async (context) => {
87
+ export const getPage = async (context) => {
83
88
  const state = saltcorn.data.state.getState();
84
89
  const query = context.query ? parseQuery(context.query) : {};
85
90
  const req = new MobileRequest({ xhr: context.xhr, query: query });
@@ -103,6 +108,6 @@ const getPage = async (context) => {
103
108
  return { content, title: "title", replaceIframe: true, isFile: true };
104
109
  } else {
105
110
  const title = "title"; // TODO
106
- return wrapContents(contents, title, context, req);
111
+ return await wrapContents(contents, title, context, req);
107
112
  }
108
113
  };
@@ -1,7 +1,10 @@
1
- /*global saltcorn, wrapContents, MobileRequest, */
1
+ /*global saltcorn */
2
+
3
+ import { MobileRequest } from "../mocks/request";
4
+ import { wrapContents } from "../utils";
2
5
 
3
6
  // get/sync/sync_settings
4
- const getSyncSettingsView = (context) => {
7
+ export const getSyncSettingsView = async (context) => {
5
8
  const state = saltcorn.data.state.getState();
6
9
  const { isOfflineMode } = state.mobileConfig;
7
10
  const content = saltcorn.markup.div(
@@ -95,11 +98,16 @@ const getSyncSettingsView = (context) => {
95
98
  ),
96
99
  saltcorn.markup.hr()
97
100
  );
98
- return wrapContents(content, "Sync Settings", context, new MobileRequest());
101
+ return await wrapContents(
102
+ content,
103
+ "Sync Settings",
104
+ context,
105
+ new MobileRequest()
106
+ );
99
107
  };
100
108
 
101
109
  // get/sync/ask_upload_not_ended
102
- const getAskUploadNotEnded = (context) => {
110
+ export const getAskUploadNotEnded = async (context) => {
103
111
  const content = saltcorn.markup.div(
104
112
  saltcorn.markup.div(
105
113
  { class: "mb-3 h6" },
@@ -123,11 +131,11 @@ const getAskUploadNotEnded = (context) => {
123
131
  "Upload anyway"
124
132
  )
125
133
  );
126
- return wrapContents(content, "Warning", context, new MobileRequest());
134
+ return await wrapContents(content, "Warning", context, new MobileRequest());
127
135
  };
128
136
 
129
137
  // get/sync/ask_delete_offline_data
130
- const getAskDeleteOfflineData = (context) => {
138
+ export const getAskDeleteOfflineData = async (context) => {
131
139
  const content = saltcorn.markup.div(
132
140
  saltcorn.markup.div(
133
141
  { class: "mb-3 h6" },
@@ -145,10 +153,11 @@ const getAskDeleteOfflineData = (context) => {
145
153
  {
146
154
  class: "btn btn-primary close",
147
155
  type: "button",
148
- onClick: "closeModal(); deleteOfflineData()",
156
+ onClick:
157
+ "closeModal(); parent.saltcorn.mobileApp.offlineMode.deleteOfflineData()",
149
158
  },
150
159
  "Delete"
151
160
  )
152
161
  );
153
- return wrapContents(content, "Warning", context, new MobileRequest());
162
+ return await wrapContents(content, "Warning", context, new MobileRequest());
154
163
  };
@@ -1,11 +1,16 @@
1
- /*global MobileRequest, MobileResponse, parseQuery, wrapContents, saltcorn, offlineHelper, routingHistory*/
1
+ /*global saltcorn */
2
2
 
3
+ import { MobileRequest } from "../mocks/request";
4
+ import { MobileResponse } from "../mocks/response";
5
+ import { parseQuery, wrapContents } from "../utils";
6
+ import { setHasOfflineData } from "../../helpers/offline_mode";
7
+ import { routingHistory } from "../../helpers/navigation";
3
8
  /**
4
9
  *
5
10
  * @param {*} context
6
11
  * @returns
7
12
  */
8
- const postView = async (context) => {
13
+ export const postView = async (context) => {
9
14
  let body = {};
10
15
  let redirect = undefined;
11
16
  for (const [k, v] of new URLSearchParams(context.query).entries()) {
@@ -30,7 +35,7 @@ const postView = async (context) => {
30
35
  const state = saltcorn.data.state.getState();
31
36
  const mobileCfg = state.mobileConfig;
32
37
  if (
33
- mobileCfg.role_id > view.min_role &&
38
+ mobileCfg.user.role_id > view.min_role &&
34
39
  !(await view.authorise_post({ body, req, ...view }))
35
40
  ) {
36
41
  throw new saltcorn.data.utils.NotAuthorized(req.__("Not authorized"));
@@ -45,10 +50,10 @@ const postView = async (context) => {
45
50
  },
46
51
  view.isRemoteTable()
47
52
  );
48
- if (mobileCfg.isOfflineMode) await offlineHelper.setHasOfflineData(true);
53
+ if (mobileCfg.isOfflineMode) await setHasOfflineData(true);
49
54
  const wrapped = res.getWrapHtml();
50
55
  if (wrapped) {
51
- return wrapContents(
56
+ return await wrapContents(
52
57
  wrapped,
53
58
  res.getWrapViewName() || "viewname",
54
59
  context,
@@ -64,7 +69,7 @@ const postView = async (context) => {
64
69
  *
65
70
  * @param {*} context
66
71
  */
67
- const postViewRoute = async (context) => {
72
+ export const postViewRoute = async (context) => {
68
73
  const query = context.query ? parseQuery(context.query) : {};
69
74
  const refererRoute =
70
75
  routingHistory?.length > 1
@@ -82,8 +87,8 @@ const postViewRoute = async (context) => {
82
87
  throw new Error(req.__("No such view: %s", context.params.viewname));
83
88
  const res = new MobileResponse();
84
89
  const state = saltcorn.data.state.getState();
85
- const { role_id, isOfflineMode } = state.mobileConfig;
86
- if (role_id > view.min_role)
90
+ const { user, isOfflineMode } = state.mobileConfig;
91
+ if (user.role_id > view.min_role)
87
92
  throw new saltcorn.data.utils.NotAuthorized(req.__("Not authorized"));
88
93
  await view.runRoute(
89
94
  context.params.route,
@@ -92,10 +97,10 @@ const postViewRoute = async (context) => {
92
97
  { req, res },
93
98
  view.isRemoteTable()
94
99
  );
95
- if (isOfflineMode) await offlineHelper.setHasOfflineData(true);
100
+ if (isOfflineMode) await setHasOfflineData(true);
96
101
  const wrapped = res.getWrapHtml();
97
102
  if (wrapped)
98
- return wrapContents(
103
+ return await wrapContents(
99
104
  wrapped,
100
105
  res.getWrapViewName() || "viewname",
101
106
  context,
@@ -116,7 +121,7 @@ const postViewRoute = async (context) => {
116
121
  * @param {*} context
117
122
  * @returns
118
123
  */
119
- const getView = async (context) => {
124
+ export const getView = async (context) => {
120
125
  const state = saltcorn.data.state.getState();
121
126
  const query = context.query ? parseQuery(context.query) : {};
122
127
  const refererRoute =
@@ -129,10 +134,10 @@ const getView = async (context) => {
129
134
  if (!view) throw new Error(req.__("No such view: %s", viewname));
130
135
  const res = new MobileResponse();
131
136
  if (
132
- state.mobileConfig.role_id > view.min_role &&
137
+ state.mobileConfig.user.role_id > view.min_role &&
133
138
  !(await view.authorise_get({ query, req, ...view }))
134
139
  ) {
135
- const additionalInfos = `: your role: ${state.mobileConfig.role_id}, view min_role: ${view.min_role}`;
140
+ const additionalInfos = `: your role: ${state.mobileConfig.user.role_id}, view min_role: ${view.min_role}`;
136
141
  throw new saltcorn.data.utils.NotAuthorized(
137
142
  req.__("Not authorized") + additionalInfos
138
143
  );
@@ -151,7 +156,7 @@ const getView = async (context) => {
151
156
  }
152
157
  const wrapped = res.getWrapHtml();
153
158
  if (wrapped)
154
- return wrapContents(
159
+ return await wrapContents(
155
160
  wrapped,
156
161
  res.getWrapViewName() || "viewname",
157
162
  context,
@@ -176,6 +181,6 @@ const getView = async (context) => {
176
181
  )
177
182
  : contents0;
178
183
 
179
- return wrapContents(contents, viewname, context, req);
184
+ return await wrapContents(contents, viewname, context, req);
180
185
  }
181
186
  };
@@ -1,6 +1,9 @@
1
- /*global saltcorn, offlineHelper*/
1
+ /*global saltcorn */
2
2
 
3
- const getHeaders = () => {
3
+ import { getOfflineMsg } from "../helpers/offline_mode";
4
+ import { getScreenOrientation } from "../helpers/common";
5
+
6
+ export const getHeaders = () => {
4
7
  const state = saltcorn.data.state.getState();
5
8
  const config = state.mobileConfig;
6
9
  const versionTag = config.version_tag;
@@ -8,7 +11,7 @@ const getHeaders = () => {
8
11
  { css: `static_assets/${versionTag}/saltcorn.css` },
9
12
  { script: `static_assets/${versionTag}/saltcorn-common.js` },
10
13
  { script: `static_assets/${versionTag}/dayjs.min.js` },
11
- { script: "js/utils/iframe_view_utils.js" },
14
+ { script: "js/iframe_view_utils.js" },
12
15
  ];
13
16
 
14
17
  let from_cfg = [];
@@ -19,7 +22,7 @@ const getHeaders = () => {
19
22
  return [...stdHeaders, ...config.pluginHeaders, ...from_cfg];
20
23
  };
21
24
 
22
- const parseQuery = (queryStr) => {
25
+ export const parseQuery = (queryStr) => {
23
26
  let result = {};
24
27
  const parsedQuery =
25
28
  typeof queryStr === "string" ? new URLSearchParams(queryStr) : undefined;
@@ -33,17 +36,17 @@ const parseQuery = (queryStr) => {
33
36
 
34
37
  const layout = () => {
35
38
  const state = saltcorn.data.state.getState();
36
- return state.getLayout({ role_id: state.mobileConfig.role_id || 100 });
39
+ return state.getLayout({ role_id: state.mobileConfig.user.role_id || 100 });
37
40
  };
38
41
 
39
- const sbAdmin2Layout = () => {
42
+ export const sbAdmin2Layout = () => {
40
43
  return saltcorn.data.state.getState().layouts["sbadmin2"];
41
44
  };
42
45
 
43
46
  const getMenu = (req) => {
44
47
  const state = saltcorn.data.state.getState();
45
48
  const mobileCfg = state.mobileConfig;
46
- const role = mobileCfg.role_id || 100;
49
+ const role = mobileCfg.user.role_id || 100;
47
50
  const extraMenu = saltcorn.data.web_mobile_commons.get_extra_menu(
48
51
  role,
49
52
  req.__
@@ -58,7 +61,7 @@ const getMenu = (req) => {
58
61
  section: "Reload",
59
62
  items: [
60
63
  {
61
- link: `javascript:parent.gotoEntryView()`,
64
+ link: "javascript:parent.saltcorn.mobileApp.navigation.gotoEntryView()",
62
65
  icon: "fas fa-sync",
63
66
  label: "Reload",
64
67
  },
@@ -68,7 +71,7 @@ const getMenu = (req) => {
68
71
  : [];
69
72
  } else {
70
73
  const allowSignup = state.getConfig("allow_signup");
71
- const userName = mobileCfg.user_name;
74
+ const userName = mobileCfg.user.email;
72
75
  const authItems = mobileCfg.isPublicUser
73
76
  ? [
74
77
  {
@@ -129,14 +132,15 @@ const prepareAlerts = (context, req) => {
129
132
  return [...(context.alerts || []), ...req.flashMessages()];
130
133
  };
131
134
 
132
- const wrapContents = (contents, title, context, req) => {
135
+ export const wrapContents = async (contents, title, context, req) => {
136
+ const orientation = await getScreenOrientation();
133
137
  const state = saltcorn.data.state.getState();
134
138
  const body = {
135
139
  above: [
136
140
  saltcorn.markup.div(
137
141
  { id: "top-alert" },
138
142
  state.mobileConfig.isOfflineMode
139
- ? saltcorn.markup.alert("info", offlineHelper.getOfflineMsg())
143
+ ? saltcorn.markup.alert("info", getOfflineMsg())
140
144
  : ""
141
145
  ),
142
146
  contents,
@@ -147,7 +151,7 @@ const wrapContents = (contents, title, context, req) => {
147
151
  title: title,
148
152
  body,
149
153
  alerts: prepareAlerts(context, req),
150
- role: state.mobileConfig.role_id,
154
+ role: state.mobileConfig.user.role_id || 100,
151
155
  menu: getMenu(req),
152
156
  req,
153
157
  headers: getHeaders(),
@@ -157,13 +161,14 @@ const wrapContents = (contents, title, context, req) => {
157
161
  },
158
162
  bodyClass: "",
159
163
  currentUrl: "",
164
+ orientation: orientation?.type,
160
165
  })
161
166
  : layout().renderBody({
162
167
  title: title,
163
168
  body,
164
169
  req,
165
170
  alerts: prepareAlerts(context, req),
166
- role: state.mobileConfig.role_id,
171
+ role: state.mobileConfig.user.role_id || 100,
167
172
  });
168
173
  return {
169
174
  content: wrappedContent,
Binary file
@@ -0,0 +1,31 @@
1
+ const path = require("path");
2
+
3
+ module.exports = {
4
+ entry: "./src/index.js",
5
+ output: {
6
+ path: path.resolve(__dirname, "www/dist"),
7
+ filename: "bundle.js",
8
+ library: {
9
+ type: "module",
10
+ },
11
+ },
12
+ experiments: {
13
+ outputModule: true,
14
+ },
15
+ module: {
16
+ rules: [
17
+ {
18
+ test: /\.js$/,
19
+ exclude: /node_modules/,
20
+ use: {
21
+ loader: "babel-loader",
22
+ options: {
23
+ presets: ["@babel/preset-env"],
24
+ },
25
+ },
26
+ },
27
+ ],
28
+ },
29
+ target: "web", // Use web target for browser compatibility
30
+ mode: "development",
31
+ };
@@ -0,0 +1 @@
1
+ var _sc_site_logo = "";