@saltcorn/builder 0.5.6-rc.0 → 0.6.0-beta.1

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.
@@ -29,8 +29,10 @@ export const ImageSettings = () => {
29
29
  fileid: node.data.props.fileid,
30
30
  field: node.data.props.field,
31
31
  url: node.data.props.url,
32
+ filepath: node.data.props.filepath,
32
33
  srctype: node.data.props.srctype,
33
- alt: node.data.props.fieldview,
34
+ alt: node.data.props.alt,
35
+ uploadedFiles: node.data.props.uploadedFiles,
34
36
  block: node.data.props.block,
35
37
  isFormula: node.data.props.isFormula,
36
38
  }));
@@ -43,8 +45,47 @@ export const ImageSettings = () => {
43
45
  alt,
44
46
  block,
45
47
  isFormula,
48
+ filepath,
49
+ uploadedFiles,
46
50
  } = node;
47
51
  const options = useContext(optionsCtx);
52
+ const handleUpload = (e) => {
53
+ if (e.target.files && e.target.files.length > 0) {
54
+ const formData = new FormData();
55
+
56
+ formData.append("file", e.target.files[0]);
57
+ formData.append("min_role_read", options.min_role || 1);
58
+
59
+ fetch("/files/upload", {
60
+ method: "POST",
61
+ body: formData,
62
+ headers: {
63
+ "X-Requested-With": "XMLHttpRequest",
64
+ "CSRF-Token": options.csrfToken,
65
+ },
66
+ })
67
+ .then((response) => response.json())
68
+ .then((result) => {
69
+ setProp((prop) => {
70
+ prop.fileid = result.success.id;
71
+ prop.srctype = "File";
72
+ prop.uploadedFiles = [
73
+ ...prop.uploadedFiles,
74
+ { id: result.success.id, filename: result.success.filename },
75
+ ];
76
+ });
77
+ })
78
+ .catch((error) => {
79
+ console.error("Error:", error);
80
+ });
81
+ }
82
+ };
83
+ const setAProp = (key) => (e) => {
84
+ if (e.target) {
85
+ const target_value = e.target.value;
86
+ setProp((prop) => (prop[key] = target_value));
87
+ }
88
+ };
48
89
  return (
49
90
  <table>
50
91
  <tbody>
@@ -63,12 +104,11 @@ export const ImageSettings = () => {
63
104
  <select
64
105
  value={srctype}
65
106
  className="form-control"
66
- onChange={(e) =>
67
- setProp((prop) => (prop.srctype = e.target.value))
68
- }
107
+ onChange={setAProp("srctype")}
69
108
  >
70
109
  <option>File</option>
71
110
  <option>URL</option>
111
+ <option>Upload</option>
72
112
  {options.mode === "show" && <option>Field</option>}
73
113
  </select>
74
114
  </td>
@@ -82,15 +122,18 @@ export const ImageSettings = () => {
82
122
  <select
83
123
  value={fileid}
84
124
  className="form-control"
85
- onChange={(e) =>
86
- setProp((prop) => (prop.fileid = e.target.value))
87
- }
125
+ onChange={setAProp("fileid")}
88
126
  >
89
127
  {options.images.map((f, ix) => (
90
128
  <option key={ix} value={f.id}>
91
129
  {f.filename}
92
130
  </option>
93
131
  ))}
132
+ {(uploadedFiles || []).map((uf, ix) => (
133
+ <option key={ix} value={uf.id}>
134
+ {uf.filename}
135
+ </option>
136
+ ))}
94
137
  </select>
95
138
  </td>
96
139
  </tr>
@@ -106,14 +149,27 @@ export const ImageSettings = () => {
106
149
  type="text"
107
150
  className="form-control"
108
151
  value={url}
109
- onChange={(e) =>
110
- setProp((prop) => (prop.url = e.target.value))
111
- }
152
+ onChange={setAProp("url")}
112
153
  />
113
154
  </OrFormula>
114
155
  </td>
115
156
  </tr>
116
157
  )}
158
+ {srctype === "Upload" && (
159
+ <tr>
160
+ <td>
161
+ <label>File</label>
162
+ </td>
163
+ <td>
164
+ <input
165
+ type="file"
166
+ className="form-control"
167
+ value={filepath}
168
+ onChange={handleUpload}
169
+ />
170
+ </td>
171
+ </tr>
172
+ )}
117
173
  {srctype === "Field" && (
118
174
  <tr>
119
175
  <td>
@@ -123,9 +179,7 @@ export const ImageSettings = () => {
123
179
  <select
124
180
  value={field}
125
181
  className="form-control"
126
- onChange={(e) =>
127
- setProp((prop) => (prop.field = e.target.value))
128
- }
182
+ onChange={setAProp("field")}
129
183
  >
130
184
  {options.fields
131
185
  .filter(
@@ -142,26 +196,30 @@ export const ImageSettings = () => {
142
196
  </td>
143
197
  </tr>
144
198
  )}
145
- <tr>
146
- <td>
147
- <label>Alt text</label>
148
- </td>
149
- <td>
150
- <OrFormula nodekey="alt" {...{ setProp, isFormula, node }}>
151
- <input
152
- type="text"
153
- className="form-control"
154
- value={alt}
155
- onChange={(e) => setProp((prop) => (prop.alt = e.target.value))}
156
- />
157
- </OrFormula>
158
- </td>
159
- </tr>
160
- <tr>
161
- <td colSpan="2">
162
- <BlockSetting block={block} setProp={setProp} />
163
- </td>
164
- </tr>
199
+ {srctype !== "Upload" && (
200
+ <tr>
201
+ <td>
202
+ <label>Alt text</label>
203
+ </td>
204
+ <td>
205
+ <OrFormula nodekey="alt" {...{ setProp, isFormula, node }}>
206
+ <input
207
+ type="text"
208
+ className="form-control"
209
+ value={alt}
210
+ onChange={setAProp("alt")}
211
+ />
212
+ </OrFormula>
213
+ </td>
214
+ </tr>
215
+ )}
216
+ {srctype !== "Upload" && (
217
+ <tr>
218
+ <td colSpan="2">
219
+ <BlockSetting block={block} setProp={setProp} />
220
+ </td>
221
+ </tr>
222
+ )}
165
223
  </tbody>
166
224
  </table>
167
225
  );
@@ -173,6 +231,7 @@ Image.craft = {
173
231
  alt: "",
174
232
  block: false,
175
233
  isFormula: {},
234
+ uploadedFiles: [],
176
235
  srctype: "File",
177
236
  },
178
237
  related: {
@@ -0,0 +1,253 @@
1
+ <style>
2
+ .boxmodel-container {
3
+ display: inline-flex;
4
+ flex-direction: column;
5
+ flex-flow: column wrap;
6
+ font-family: "Consolas", "Monaco", monospace;
7
+ font-size: 12px;
8
+ position: relative;
9
+ }
10
+ .boxmodel-container .boxmodel-info {
11
+ font-size: 10px;
12
+ font-family: Verdana;
13
+ font-style: normal;
14
+ text-align: left;
15
+ line-height: 14px;
16
+ margin: 2px 0 0 0;
17
+ padding: 0px;
18
+ margin-top: 2px;
19
+ }
20
+ .boxmodel-container div:not(.flex-row) {
21
+ box-sizing: border-box;
22
+ display: flex;
23
+ flex-direction: row;
24
+ align-items: center;
25
+ align-content: space-between;
26
+ padding: 0;
27
+ margin: 0;
28
+ }
29
+ .boxmodel-container span.boxmodel-text {
30
+ font-size: 8.5px;
31
+ font-family: Verdana, Geneva, sans-serif;
32
+ display: inline-block;
33
+ user-select: none;
34
+ }
35
+ .boxmodel-container span.boxmodel-text.boxmodel-header {
36
+ position: absolute;
37
+ top: 2px;
38
+ left: 2px;
39
+ color: #777;
40
+ }
41
+ .boxmodel-container .boxmodel-margin {
42
+ background: #ffffff;
43
+ border: 1px dashed #aaa;
44
+ position: relative;
45
+ }
46
+ .boxmodel-container .boxmodel-border {
47
+ background: #ddd;
48
+ border: 2px solid #aaa;
49
+ position: relative;
50
+ }
51
+ .boxmodel-container .boxmodel-padding {
52
+ background: #eee;
53
+ border: 1px dashed #aaa;
54
+ position: relative;
55
+ }
56
+ .boxmodel-container .boxmodel-content {
57
+ background: #fff;
58
+ height: 30px;
59
+ margin: 0;
60
+ border: 1px solid #aaa;
61
+ position: relative;
62
+ font-family: Verdana, Geneva, sans-serif;
63
+ font-size: 10px;
64
+ }
65
+ .boxmodel-container .flex-row {
66
+ text-align: center;
67
+ }
68
+ .boxmodel-container .boxmodel-input-direction-top,
69
+ .boxmodel-container .boxmodel-input-direction-bottom {
70
+ display: block;
71
+ padding: 0;
72
+ margin: 0;
73
+ line-height: 0;
74
+ }
75
+ .boxmodel-container .boxmodel-input-direction-right,
76
+ .boxmodel-container .boxmodel-input-direction-left {
77
+ position: relative;
78
+ }
79
+ .boxmodel-container input,
80
+ .boxmodel-container input:focus,
81
+ .boxmodel-container input:active {
82
+ background: transparent;
83
+ color: black;
84
+ font-size: 9px;
85
+ font-family: Verdana, Geneva, sans-serif;
86
+ box-shadow: none;
87
+ border: 1px solid transparent;
88
+ outline: none;
89
+ user-select: none;
90
+ margin: 0px;
91
+ padding: 1px;
92
+ text-align: center;
93
+ }
94
+ .boxmodel-chrome-skin .boxmodel-container .boxmodel-margin {
95
+ background: #f9cc9d;
96
+ border: 1px dashed #222;
97
+ }
98
+ .boxmodel-chrome-skin .boxmodel-container .boxmodel-border {
99
+ background: #ffeebc;
100
+ border: 1px solid #000;
101
+ }
102
+ .boxmodel-chrome-skin .boxmodel-container .boxmodel-padding {
103
+ background: #c3deb7;
104
+ border: 1px dashed #808080;
105
+ }
106
+ .boxmodel-chrome-skin .boxmodel-container .boxmodel-content {
107
+ background: #a0c5e8;
108
+ border: 1px solid #808080;
109
+ }
110
+ .boxmodel-chrome-skin .boxmodel-container .boxmodel-text {
111
+ color: black;
112
+ }
113
+ .boxmodel-chrome-skin .boxmodel-container .boxmodel-text.boxmodel-header {
114
+ color: black;
115
+ }
116
+ </style>
117
+
118
+ <div class="boxmodel-container boxmodel-chrome-skin">
119
+ <div class="boxmodel-container">
120
+ <div class="boxmodel-margin">
121
+ <span class="boxmodel-text boxmodel-header">Margin</span
122
+ ><span class="boxmodel-input-container boxmodel-input-direction-left"
123
+ ><input
124
+ disabled
125
+ type="text"
126
+ autocomplete="off"
127
+ name="boxmodel-ex-1_left_margin"
128
+ size="3"
129
+ /></span>
130
+ <div class="flex-row">
131
+ <span class="boxmodel-input-container boxmodel-input-direction-top"
132
+ ><input
133
+ disabled
134
+ type="text"
135
+ autocomplete="off"
136
+ name="boxmodel-ex-1_top_margin"
137
+ size="3"
138
+ /></span>
139
+ <div class="boxmodel-border">
140
+ <span class="boxmodel-text boxmodel-header">Border</span
141
+ ><span class="boxmodel-input-container boxmodel-input-direction-left"
142
+ ><input
143
+ disabled
144
+ type="text"
145
+ autocomplete="off"
146
+ name="boxmodel-ex-1_left_border"
147
+ size="3"
148
+ /></span>
149
+ <div class="flex-row">
150
+ <span class="boxmodel-input-container boxmodel-input-direction-top"
151
+ ><input
152
+ disabled
153
+ type="text"
154
+ autocomplete="off"
155
+ name="boxmodel-ex-1_top_border"
156
+ size="3"
157
+ /></span>
158
+ <div class="boxmodel-padding">
159
+ <span class="boxmodel-text boxmodel-header">Padding</span
160
+ ><span
161
+ class="boxmodel-input-container boxmodel-input-direction-left"
162
+ ><input
163
+ disabled
164
+ type="text"
165
+ autocomplete="off"
166
+ name="boxmodel-ex-1_left_padding"
167
+ size="3"
168
+ class=""
169
+ /></span>
170
+ <div class="flex-row">
171
+ <span
172
+ class="boxmodel-input-container boxmodel-input-direction-top"
173
+ ><input
174
+ disabled
175
+ type="text"
176
+ autocomplete="off"
177
+ name="boxmodel-ex-1_top_padding"
178
+ size="3"
179
+ /></span>
180
+ <div class="boxmodel-content">
181
+ <input
182
+ disabled
183
+ type="text"
184
+ autocomplete="off"
185
+ name="boxmodel-ex-1_width"
186
+ size="3"
187
+ />x<input
188
+ disabled
189
+ type="text"
190
+ autocomplete="off"
191
+ name="boxmodel-ex-1_height"
192
+ size="3"
193
+ />
194
+ </div>
195
+ <span
196
+ class="boxmodel-input-container boxmodel-input-direction-bottom"
197
+ ><input
198
+ disabled
199
+ type="text"
200
+ autocomplete="off"
201
+ name="boxmodel-ex-1_bottom_padding"
202
+ size="3"
203
+ /></span>
204
+ </div>
205
+ <span
206
+ class="boxmodel-input-container boxmodel-input-direction-right"
207
+ ><input
208
+ disabled
209
+ type="text"
210
+ autocomplete="off"
211
+ name="boxmodel-ex-1_right_padding"
212
+ size="3"
213
+ /></span>
214
+ </div>
215
+ <span
216
+ class="boxmodel-input-container boxmodel-input-direction-bottom"
217
+ ><input
218
+ disabled
219
+ type="text"
220
+ autocomplete="off"
221
+ name="boxmodel-ex-1_bottom_border"
222
+ size="3"
223
+ /></span>
224
+ </div>
225
+ <span class="boxmodel-input-container boxmodel-input-direction-right"
226
+ ><input
227
+ disabled
228
+ type="text"
229
+ autocomplete="off"
230
+ name="boxmodel-ex-1_right_border"
231
+ size="3"
232
+ /></span>
233
+ </div>
234
+ <span class="boxmodel-input-container boxmodel-input-direction-bottom"
235
+ ><input
236
+ disabled
237
+ type="text"
238
+ autocomplete="off"
239
+ name="boxmodel-ex-1_bottom_margin"
240
+ size="3"
241
+ /></span>
242
+ </div>
243
+ <span class="boxmodel-input-container boxmodel-input-direction-right"
244
+ ><input
245
+ disabled
246
+ type="text"
247
+ autocomplete="off"
248
+ name="boxmodel-ex-1_right_margin"
249
+ size="3"
250
+ /></span>
251
+ </div>
252
+ </div>
253
+ </div>
@@ -279,13 +279,14 @@ export const fetchViewPreview = (args = {}) => (changes = {}) => {
279
279
  });
280
280
  };
281
281
 
282
- export const SelectUnits = ({ vert, ...props }) => (
282
+ export const SelectUnits = ({ vert, autoable, ...props }) => (
283
283
  <select {...props}>
284
284
  <option>px</option>
285
285
  <option>%</option>
286
286
  <option>{vert ? "vh" : "vw"}</option>
287
287
  <option>em</option>
288
288
  <option>rem</option>
289
+ {autoable && <option>auto</option>}
289
290
  </select>
290
291
  );
291
292
 
@@ -309,6 +310,19 @@ export const parseStyles = (styles) =>
309
310
  }),
310
311
  {}
311
312
  );
313
+
314
+ export const reactifyStyles = (styles) => {
315
+ const toCamel = (s) => {
316
+ return s.replace(/([-][a-z])/gi, ($1) => {
317
+ return $1.toUpperCase().replace("-", "");
318
+ });
319
+ };
320
+ const reactified = {};
321
+ Object.keys(styles).forEach((k) => {
322
+ reactified[toCamel(k)] = styles[k];
323
+ });
324
+ return reactified;
325
+ };
312
326
  const isCheckbox = (f) =>
313
327
  f && f.type && (f.type === "Bool" || f.type.name === "Bool");
314
328
  export const setInitialConfig = (setProp, fieldview, fields) => {
@@ -320,6 +334,24 @@ export const setInitialConfig = (setProp, fieldview, fields) => {
320
334
  });
321
335
  });
322
336
  };
337
+
338
+ const ColorInput = ({ value, onChange }) =>
339
+ value ? (
340
+ <input
341
+ type="color"
342
+ value={value}
343
+ className="form-control"
344
+ onChange={(e) => e.target && onChange(e.target.value)}
345
+ />
346
+ ) : (
347
+ <button
348
+ className="btn btn-sm btn-outline-secondary"
349
+ onClick={() => onChange("#000000")}
350
+ >
351
+ <small>Set color</small>
352
+ </button>
353
+ );
354
+
323
355
  export const ConfigForm = ({
324
356
  fields,
325
357
  configuration,
@@ -365,18 +397,26 @@ export const ConfigField = ({
365
397
  setProp,
366
398
  onChange,
367
399
  props,
400
+ isStyle,
368
401
  }) => {
369
402
  const myOnChange = (v) => {
370
403
  setProp((prop) => {
371
404
  if (configuration) {
372
405
  if (!prop.configuration) prop.configuration = {};
373
406
  prop.configuration[field.name] = v;
407
+ } else if (isStyle) {
408
+ if (!prop.style) prop.style = {};
409
+ prop.style[field.name] = v;
374
410
  } else prop[field.name] = v;
375
411
  });
376
412
  onChange && onChange(field.name, v);
377
413
  };
378
414
  const value = or_if_undef(
379
- configuration ? configuration[field.name] : props[field.name],
415
+ configuration
416
+ ? configuration[field.name]
417
+ : isStyle
418
+ ? props.style[field.name]
419
+ : props[field.name],
380
420
  field.default
381
421
  );
382
422
  if (field.input_type === "fromtype") field.input_type = null;
@@ -412,14 +452,7 @@ export const ConfigField = ({
412
452
  onChange={(e) => e.target && myOnChange(e.target.value)}
413
453
  />
414
454
  ),
415
- Color: () => (
416
- <input
417
- type="color"
418
- value={value}
419
- className="form-control"
420
- onChange={(e) => e.target && myOnChange(e.target.value)}
421
- />
422
- ),
455
+ Color: () => <ColorInput value={value} onChange={(c) => myOnChange(c)} />,
423
456
  Bool: () => (
424
457
  <div className="form-check">
425
458
  <input
@@ -478,38 +511,74 @@ export const ConfigField = ({
478
511
  ))}
479
512
  </div>
480
513
  ),
481
- DimUnits: () => (
482
- <Fragment>
483
- <input
484
- type="number"
485
- value={value}
486
- step="1"
487
- min="0"
488
- max="9999"
489
- className="w-50 form-control-sm d-inline dimunit"
490
- onChange={(e) => myOnChange(e.target.value)}
491
- />
492
- <SelectUnits
493
- value={or_if_undef(
494
- configuration
495
- ? configuration[field.name + "Unit"]
496
- : props[field.name + "Unit"],
497
- "px"
514
+ DimUnits: () => {
515
+ let styleVal, styleDim;
516
+ if (isStyle && value === "auto") {
517
+ styleVal = "";
518
+ styleDim = "auto";
519
+ } else if (isStyle && value && typeof value === "string") {
520
+ const matches = value.match(/^([0-9]+\.?[0-9]*)(.*)/);
521
+ if (matches) {
522
+ styleVal = matches[1];
523
+ styleDim = matches[2];
524
+ }
525
+ }
526
+ return (
527
+ <Fragment>
528
+ {styleDim !== "auto" && (
529
+ <input
530
+ type="number"
531
+ value={(isStyle ? styleVal : value) || ""}
532
+ step="1"
533
+ min="0"
534
+ max="9999"
535
+ className="w-50 form-control-sm d-inline dimunit"
536
+ disabled={field.autoable && styleDim === "auto"}
537
+ onChange={(e) =>
538
+ myOnChange(
539
+ isStyle
540
+ ? `${e.target.value}${styleDim || "px"}`
541
+ : e.target.value
542
+ )
543
+ }
544
+ />
498
545
  )}
499
- className="w-50 form-control-sm d-inline dimunit"
500
- vert={true}
501
- onChange={(e) => {
502
- if (!e.target) return;
503
- const target_value = e.target.value;
504
- setProp((prop) => {
505
- if (configuration)
506
- prop.configuration[field.name + "Unit"] = target_value;
507
- else prop[field.name + "Unit"] = target_value;
508
- });
509
- }}
510
- />
511
- </Fragment>
512
- ),
546
+ <SelectUnits
547
+ value={or_if_undef(
548
+ configuration
549
+ ? configuration[field.name + "Unit"]
550
+ : isStyle
551
+ ? styleDim
552
+ : props[field.name + "Unit"],
553
+ "px"
554
+ )}
555
+ autoable={field.autoable}
556
+ className={`w-${
557
+ styleDim === "auto" ? 100 : 50
558
+ } form-control-sm d-inline dimunit`}
559
+ vert={true}
560
+ onChange={(e) => {
561
+ if (!e.target) return;
562
+ const target_value = e.target.value;
563
+ setProp((prop) => {
564
+ const myStyleVal =
565
+ target_value === "auto" && field.autoable && isStyle
566
+ ? ""
567
+ : styleVal;
568
+ if (configuration)
569
+ prop.configuration[field.name + "Unit"] = target_value;
570
+ else if (isStyle) {
571
+ prop.style[field.name] = `${or_if_undef(
572
+ myStyleVal,
573
+ 0
574
+ )}${target_value}`;
575
+ } else prop[field.name + "Unit"] = target_value;
576
+ });
577
+ }}
578
+ />
579
+ </Fragment>
580
+ );
581
+ },
513
582
  };
514
583
  const f = dispatch[field.input_type || field.type.name || field.type];
515
584
  return f ? f() : null;
@@ -546,7 +615,7 @@ export const SettingsSectionHeaderRow = ({ title }) => (
546
615
  </tr>
547
616
  );
548
617
 
549
- export const SettingsRow = ({ field, node, setProp, onChange }) => {
618
+ export const SettingsRow = ({ field, node, setProp, onChange, isStyle }) => {
550
619
  const fullWidth = ["String", "Bool", "textarea"].includes(field.type);
551
620
  const needLabel = field.type !== "Bool";
552
621
  const inner = field.canBeFormula ? (
@@ -568,6 +637,7 @@ export const SettingsRow = ({ field, node, setProp, onChange }) => {
568
637
  props={node}
569
638
  setProp={setProp}
570
639
  onChange={onChange}
640
+ isStyle={isStyle}
571
641
  />
572
642
  );
573
643
  return (
@@ -737,3 +807,17 @@ export const ButtonOrLinkSettingsRows = ({
737
807
  : []),
738
808
  ];
739
809
  };
810
+ export const bstyleopt = (style) => ({
811
+ value: style,
812
+ title: style,
813
+ label: (
814
+ <div
815
+ style={{
816
+ borderLeftStyle: style,
817
+ borderTopStyle: style,
818
+ height: "15px",
819
+ width: "6px",
820
+ }}
821
+ ></div>
822
+ ),
823
+ });