@saltcorn/builder 0.8.6-beta.2 → 0.8.6-beta.4

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/builder",
3
- "version": "0.8.6-beta.2",
3
+ "version": "0.8.6-beta.4",
4
4
  "description": "Drag and drop view builder for Saltcorn, open-source no-code platform",
5
5
  "main": "index.js",
6
6
  "homepage": "https://saltcorn.com",
@@ -0,0 +1,97 @@
1
+ import React, { Fragment, useContext, useState } from "react";
2
+ import {
3
+ parseRelationPath,
4
+ parseLegacyRelation,
5
+ removeWhitespaces,
6
+ } from "./utils";
7
+
8
+ const buildBadgeCfgs = (parsed, parentTbl) => {
9
+ const result = [];
10
+ let currentCfg = null;
11
+ for (const { type, table, key } of parsed) {
12
+ if (type === "Inbound") {
13
+ if (currentCfg) result.push(currentCfg);
14
+ currentCfg = { up: key, table };
15
+ } else {
16
+ if (!currentCfg) result.push({ down: key, table: parentTbl });
17
+ else {
18
+ currentCfg.down = key;
19
+ result.push(currentCfg);
20
+ }
21
+ currentCfg = { table };
22
+ }
23
+ }
24
+ if (
25
+ currentCfg &&
26
+ !result.find(
27
+ ({ down, table, up }) =>
28
+ down === currentCfg.down &&
29
+ table === currentCfg.table &&
30
+ up === currentCfg.up
31
+ )
32
+ )
33
+ result.push(currentCfg);
34
+ return result;
35
+ };
36
+
37
+ const buildBadge = ({ up, table, down }, index) => {
38
+ return (
39
+ <div
40
+ key={removeWhitespaces(`badge_${table}_${index}`)}
41
+ className="my-1 d-flex"
42
+ >
43
+ <div className="m-auto badge bg-primary">
44
+ {up ? (
45
+ <div className="mt-1 d-flex justify-content-center">
46
+ <span className="pe-2">{up}</span>
47
+ <i className="fas fa-arrow-up"></i>
48
+ </div>
49
+ ) : (
50
+ ""
51
+ )}
52
+ <div className="m-1 fw-bolder">{table}</div>
53
+ {down ? (
54
+ <div className="mb-1 d-flex justify-content-center">
55
+ <span className="pe-2">{down}</span>
56
+ <i className="fas fa-arrow-down" style={{ marginTop: "-1px" }}></i>
57
+ </div>
58
+ ) : (
59
+ ""
60
+ )}
61
+ </div>
62
+ </div>
63
+ );
64
+ };
65
+
66
+ export const RelationBadges = ({ view, relation, parentTbl, fk_options }) => {
67
+ if (relation) {
68
+ const parsed = parseRelationPath(relation, fk_options);
69
+ return (
70
+ <div className="overflow-scroll">
71
+ {parsed.length > 0
72
+ ? buildBadgeCfgs(parsed, parentTbl).map(buildBadge)
73
+ : buildBadge({ table: "invalid relation" }, 0)}
74
+ </div>
75
+ );
76
+ } else {
77
+ const [prefix, rest] = view.split(":");
78
+ const parsed = parseLegacyRelation(prefix, rest, parentTbl);
79
+ if (parsed.length === 0)
80
+ return buildBadge({ table: "invalid relation" }, 0);
81
+ else if (
82
+ parsed.length === 1 &&
83
+ (parsed[0].type === "Independent" || parsed[0].type === "Own")
84
+ )
85
+ return (
86
+ <div className="overflow-scroll">
87
+ {buildBadge({ table: parsed[0].table }, 0)}
88
+ </div>
89
+ );
90
+ else
91
+ return (
92
+ <div className="overflow-scroll">
93
+ {buildBadgeCfgs(parsed, parentTbl).map(buildBadge)}
94
+ </div>
95
+ );
96
+ }
97
+ };
@@ -0,0 +1,251 @@
1
+ /*global $ */
2
+ import React, { Fragment, useContext, useState } from "react";
3
+ import {
4
+ parseRelationPath,
5
+ parseLegacyRelation,
6
+ removeWhitespaces,
7
+ } from "./utils";
8
+
9
+ const parseRelations = (allOptions, viewname, parentTbl) => {
10
+ const viewtable = allOptions.view_name_opts
11
+ ? allOptions.view_name_opts.find((opt) => opt.name === viewname)?.table
12
+ : undefined;
13
+ const options = allOptions.view_relation_opts[viewname] || [];
14
+ const result = { table: parentTbl, inboundKeys: [], fkeys: [] };
15
+ if (!viewtable) return result;
16
+ for (const { value } of options) {
17
+ let path = null;
18
+ if (value.startsWith(".")) {
19
+ path = parseRelationPath(value, allOptions.fk_options);
20
+ } else {
21
+ const [prefix, rest] = value.split(":");
22
+ path = parseLegacyRelation(prefix, rest, parentTbl);
23
+ }
24
+ if (path.length > 0) buildLevels(value, path, result, parentTbl);
25
+ else console.log(`The relation path '${value}' is invalid`);
26
+ }
27
+ return result;
28
+ };
29
+
30
+ const buildLevels = (path, parsed, result, parentTbl) => {
31
+ let currentLevel = result;
32
+ for (const relation of parsed) {
33
+ if (relation.type === "Inbound") {
34
+ const existing = currentLevel.inboundKeys.find(
35
+ (key) => key.name === relation.key && key.table === relation.table
36
+ );
37
+ if (existing) {
38
+ currentLevel = existing;
39
+ } else {
40
+ const nextLevel = {
41
+ name: relation.key,
42
+ table: relation.table,
43
+ inboundKeys: [],
44
+ fkeys: [],
45
+ };
46
+ currentLevel.inboundKeys.push(nextLevel);
47
+ currentLevel = nextLevel;
48
+ }
49
+ } else if (relation.type === "Foreign") {
50
+ const existing = currentLevel.fkeys.find(
51
+ (key) => key.name === relation.key
52
+ );
53
+ if (existing) {
54
+ currentLevel = existing;
55
+ } else {
56
+ const nextLevel = {
57
+ name: relation.key,
58
+ table: relation.table,
59
+ inboundKeys: [],
60
+ fkeys: [],
61
+ };
62
+ currentLevel.fkeys.push(nextLevel);
63
+ currentLevel = nextLevel;
64
+ }
65
+ } else if (relation.type === "Independent") {
66
+ result.fkeys.push({
67
+ name: "None (no relation)",
68
+ table: "",
69
+ inboundKeys: [],
70
+ fkeys: [],
71
+ relPath: path,
72
+ });
73
+ } else if (relation.type === "Own") {
74
+ result.fkeys.push({
75
+ name: `${parentTbl} (same table)`,
76
+ table: "",
77
+ inboundKeys: [],
78
+ fkeys: [],
79
+ relPath: path,
80
+ });
81
+ } else if (relation.type === "OneToOneShow") {
82
+ result.inboundKeys.push({
83
+ name: relation.key,
84
+ table: relation.table,
85
+ inboundKeys: [],
86
+ fkeys: [],
87
+ relPath: path,
88
+ });
89
+ }
90
+ }
91
+ currentLevel.relPath = path;
92
+ };
93
+
94
+ export const RelationPicker = ({ options, viewname, update }) => {
95
+ const maxRecLevelDefault = 10;
96
+ const [maxRecLevel, setMaxRecLevel] = useState(maxRecLevelDefault);
97
+ const relationLevels = parseRelations(options, viewname, options.tableName);
98
+
99
+ const levelClasses = (level) => {
100
+ const classes = [];
101
+ for (let i = level; i < maxRecLevel; i++) {
102
+ classes.push(`.dropdown_level_${i}.show`);
103
+ }
104
+ return classes.join(",");
105
+ };
106
+
107
+ const activeClasses = (level) => {
108
+ const classes = [];
109
+ for (let i = level; i < maxRecLevel; i++) {
110
+ classes.push(`.item_level_${i}.active`);
111
+ }
112
+ return classes.join(",");
113
+ };
114
+
115
+ const buildPicker = (level, reclevel) => {
116
+ return (
117
+ <div className="dropdown-menu">
118
+ <h5 className="join-table-header text-center">{level.table}</h5>
119
+ <ul className="ps-0 mb-0">
120
+ {
121
+ // foreign keys
122
+ level.fkeys.map((fkey) => {
123
+ const hasSubLevels =
124
+ fkey.fkeys.length > 0 || fkey.inboundKeys.length > 0;
125
+ const identifier = removeWhitespaces(
126
+ `${fkey.name}_${fkey.table}_${level.table}_${reclevel}`
127
+ );
128
+
129
+ return hasSubLevels ? (
130
+ <li
131
+ key={`${identifier}_fkey_key`}
132
+ className={`dropdown-item ${identifier}_fkey_item item_level_${reclevel} ${
133
+ reclevel < 5 ? "dropstart" : "dropdown"
134
+ }`}
135
+ role="button"
136
+ >
137
+ <div
138
+ className={`dropdown-toggle ${identifier}_fkey_toggle dropdown_level_${reclevel}`}
139
+ role="button"
140
+ aria-expanded="false"
141
+ onClick={() => {
142
+ $(
143
+ `.${identifier}_fkey_toggle,${levelClasses(reclevel)}`
144
+ ).dropdown("toggle");
145
+ if (reclevel > maxRecLevel) setMaxRecLevel(reclevel);
146
+ $(activeClasses(reclevel)).removeClass("active");
147
+ $(`.${identifier}_fkey_item`).addClass(() => "active");
148
+ }}
149
+ >
150
+ {fkey.name}
151
+ </div>
152
+ {buildPicker(fkey, reclevel + 1)}
153
+ </li>
154
+ ) : (
155
+ <li
156
+ key={`${identifier}_fkey_key`}
157
+ className="dropdown-item"
158
+ role="button"
159
+ onClick={() => {
160
+ update(fkey.relPath);
161
+ $(".dropdown-item.active").removeClass("active");
162
+ $(`${levelClasses(0)}`).dropdown("toggle");
163
+ setMaxRecLevel(maxRecLevelDefault);
164
+ }}
165
+ >
166
+ {fkey.name}
167
+ </li>
168
+ );
169
+ })
170
+ }
171
+ {
172
+ // inbound keys
173
+ level.inboundKeys.map((inboundKey) => {
174
+ const hasSubLevels =
175
+ inboundKey.fkeys.length > 0 ||
176
+ inboundKey.inboundKeys.length > 0;
177
+ const identifier = removeWhitespaces(
178
+ `${inboundKey.name}_${inboundKey.table}_${level.table}_${reclevel}`
179
+ );
180
+ return hasSubLevels ? (
181
+ <li
182
+ key={`${identifier}_inboundkey_key`}
183
+ className={`dropdown-item ${identifier}_inbound_item item_level_${reclevel} ${
184
+ reclevel < 5 ? "dropstart" : "dropdown"
185
+ }`}
186
+ role="button"
187
+ >
188
+ <div
189
+ className={`dropdown-toggle ${identifier}_inbound_toggle dropdown_level_${reclevel}`}
190
+ role="button"
191
+ aria-expanded="false"
192
+ onClick={() => {
193
+ $(
194
+ `.${identifier}_inbound_toggle,${levelClasses(
195
+ reclevel
196
+ )}`
197
+ ).dropdown("toggle");
198
+ if (reclevel > maxRecLevel) setMaxRecLevel(reclevel);
199
+ $(activeClasses(reclevel)).removeClass("active");
200
+ $(`.${identifier}_inbound_item`).addClass(() => "active");
201
+ }}
202
+ >
203
+ {inboundKey.name} (from {inboundKey.table})
204
+ </div>
205
+ {buildPicker(inboundKey, reclevel + 1)}
206
+ </li>
207
+ ) : (
208
+ <li
209
+ key={`${identifier}_inboundkey_key`}
210
+ className="dropdown-item"
211
+ role="button"
212
+ onClick={() => {
213
+ update(inboundKey.relPath);
214
+ $(".dropdown-item.active").removeClass("active");
215
+ $(`${levelClasses(0)}`).dropdown("toggle");
216
+ setMaxRecLevel(maxRecLevelDefault);
217
+ }}
218
+ >
219
+ {inboundKey.name} (from {inboundKey.table})
220
+ </li>
221
+ );
222
+ })
223
+ }
224
+ </ul>
225
+ </div>
226
+ );
227
+ };
228
+
229
+ return (
230
+ <div>
231
+ <label>Relation</label>
232
+ <div style={{ zIndex: 10000 }} className="dropstart">
233
+ <button
234
+ id="_relation_picker_toggle_"
235
+ className="btn btn-outline-primary dropdown-toggle dropdown_level_0 mb-1"
236
+ aria-expanded="false"
237
+ onClick={() => {
238
+ $(".dropdown-item.active").removeClass("active");
239
+ $(`#_relation_picker_toggle_,${levelClasses(0)}`).dropdown(
240
+ "toggle"
241
+ );
242
+ setMaxRecLevel(maxRecLevelDefault);
243
+ }}
244
+ >
245
+ Select
246
+ </button>
247
+ {buildPicker(relationLevels, 1)}
248
+ </div>
249
+ </div>
250
+ );
251
+ };