@saltcorn/builder 0.9.4-beta.1 → 0.9.4-beta.10

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.9.4-beta.1",
3
+ "version": "0.9.4-beta.10",
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",
@@ -20,6 +20,7 @@
20
20
  "@babel/preset-react": "7.9.4",
21
21
  "@craftjs/core": "0.1.0-beta.20",
22
22
  "@craftjs/utils": "0.1.0-beta.20",
23
+ "@saltcorn/common-code": "0.9.4-beta.10",
23
24
  "saltcorn-craft-layers-noeye": "0.1.0-beta.22",
24
25
  "@fonticonpicker/react-fonticonpicker": "1.2.0",
25
26
  "@fortawesome/fontawesome-svg-core": "1.2.34",
@@ -52,7 +52,12 @@ import {
52
52
  faRedo,
53
53
  faTrashAlt,
54
54
  faSave,
55
+ faExclamationTriangle,
55
56
  } from "@fortawesome/free-solid-svg-icons";
57
+ import {
58
+ faCaretSquareLeft,
59
+ faCaretSquareRight,
60
+ } from "@fortawesome/free-regular-svg-icons";
56
61
  import {
57
62
  Accordion,
58
63
  ErrorBoundary,
@@ -390,9 +395,10 @@ const Builder = ({ options, layout, mode }) => {
390
395
  const [previews, setPreviews] = useState({});
391
396
  const [uploadedFiles, setUploadedFiles] = useState([]);
392
397
  const nodekeys = useRef([]);
393
- const [isSaving, setIsSaving] = useState(false);
398
+ const [savingState, setSavingState] = useState({ isSaving: false });
399
+ const [isEnlarged, setIsEnlarged] = useState(false);
400
+ const [isLeftEnlarged, setIsLeftEnlarged] = useState(false);
394
401
  const [relationsCache, setRelationsCache] = useState({});
395
-
396
402
  return (
397
403
  <ErrorBoundary>
398
404
  <Editor onRender={RenderNode}>
@@ -407,28 +413,50 @@ const Builder = ({ options, layout, mode }) => {
407
413
  }}
408
414
  >
409
415
  <div className="row" style={{ marginTop: "-5px" }}>
410
- <div className="col-sm-auto left-builder-col">
416
+ <div
417
+ className={`col-sm-auto left-builder-col ${
418
+ isLeftEnlarged
419
+ ? "builder-left-enlarged"
420
+ : "builder-left-shrunk"
421
+ }`}
422
+ >
411
423
  <div className="componets-and-library-accordion toolbox-card">
412
424
  <InitNewElement
413
425
  nodekeys={nodekeys}
414
- setIsSaving={setIsSaving}
426
+ setSavingState={setSavingState}
427
+ savingState={savingState}
415
428
  />
416
429
  <Accordion>
417
430
  <div className="card mt-1" accordiontitle="Components">
418
431
  {{
419
- show: <ToolboxShow />,
420
- edit: <ToolboxEdit />,
421
- page: <ToolboxPage />,
422
- filter: <ToolboxFilter />,
432
+ show: <ToolboxShow expanded={isLeftEnlarged} />,
433
+ edit: <ToolboxEdit expanded={isLeftEnlarged} />,
434
+ page: <ToolboxPage expanded={isLeftEnlarged} />,
435
+ filter: <ToolboxFilter expanded={isLeftEnlarged} />,
423
436
  }[mode] || <div>Missing mode</div>}
424
437
  </div>
425
438
  <div accordiontitle="Library">
426
- <Library />
439
+ <Library expanded={isLeftEnlarged} />
427
440
  </div>
428
441
  </Accordion>
429
442
  </div>
430
- <div className="card toolbox-card pe-0">
431
- <div className="card-header">Layers</div>
443
+ <div
444
+ className="card toolbox-card pe-0"
445
+ style={isLeftEnlarged ? { width: "12.35rem" } : {}}
446
+ >
447
+ <div className="card-header p-2 d-flex justify-content-between">
448
+ <div>Layers</div>
449
+ <FontAwesomeIcon
450
+ icon={
451
+ isLeftEnlarged
452
+ ? faCaretSquareLeft
453
+ : faCaretSquareRight
454
+ }
455
+ className={"float-end fa-lg"}
456
+ onClick={() => setIsLeftEnlarged(!isLeftEnlarged)}
457
+ title={isLeftEnlarged ? "Shrink" : "Enlarge"}
458
+ />
459
+ </div>
432
460
  {showLayers && (
433
461
  <div className="card-body p-0 builder-layers">
434
462
  <Layers expandRootOnLoad={true} />
@@ -472,13 +500,31 @@ const Builder = ({ options, layout, mode }) => {
472
500
  </div>
473
501
  </div>
474
502
  <div className="col-sm-auto builder-sidebar">
475
- <div style={{ width: "16rem" }}>
503
+ <div style={{ width: isEnlarged ? "28rem" : "16rem" }}>
476
504
  <NextButton layout={layout} />
477
505
  <HistoryPanel />
478
506
  <FontAwesomeIcon
479
507
  icon={faSave}
480
- className={isSaving ? "d-inline" : "d-none"}
508
+ className={savingState.isSaving ? "d-inline" : "d-none"}
481
509
  />
510
+ <FontAwesomeIcon
511
+ icon={faExclamationTriangle}
512
+ color="#ff0033"
513
+ className={savingState.error ? "d-inline" : "d-none"}
514
+ />
515
+ <FontAwesomeIcon
516
+ icon={isEnlarged ? faCaretSquareRight : faCaretSquareLeft}
517
+ className={"float-end me-2 mt-1 fa-lg"}
518
+ onClick={() => setIsEnlarged(!isEnlarged)}
519
+ title={isEnlarged ? "Shrink" : "Enlarge"}
520
+ />
521
+ <div
522
+ className={` ${
523
+ savingState.error ? "d-block" : "d-none"
524
+ } my-2 fw-bold`}
525
+ >
526
+ your work is not being saved
527
+ </div>
482
528
  <SettingsPanel />
483
529
  </div>
484
530
  </div>
@@ -20,18 +20,7 @@ import faIcons from "./elements/faicons";
20
20
  import { craftToSaltcorn, layoutToNodes } from "./storage";
21
21
  import optionsCtx from "./context";
22
22
  import { WrapElem } from "./Toolbox";
23
- import { isEqual, throttle } from "lodash";
24
-
25
- /**
26
- *
27
- * @param {object[]} xs
28
- * @returns {object[]}
29
- */
30
- const twoByTwos = (xs) => {
31
- if (xs.length <= 2) return [xs];
32
- const [x, y, ...rest] = xs;
33
- return [[x, y], ...twoByTwos(rest)];
34
- };
23
+ import { isEqual, throttle, chunk } from "lodash";
35
24
 
36
25
  export /**
37
26
  * @param {object} props
@@ -94,7 +83,7 @@ export /**
94
83
  * @subcategory components
95
84
  * @namespace
96
85
  */
97
- const InitNewElement = ({ nodekeys, setIsSaving }) => {
86
+ const InitNewElement = ({ nodekeys, savingState, setSavingState }) => {
98
87
  const [saveTimeout, setSaveTimeout] = useState(false);
99
88
  const savedData = useRef(false);
100
89
  const { actions, query, connectors } = useEditor((state, query) => {
@@ -114,7 +103,7 @@ const InitNewElement = ({ nodekeys, setIsSaving }) => {
114
103
  }
115
104
  if (isEqual(savedData.current, JSON.stringify(data.layout))) return;
116
105
  savedData.current = JSON.stringify(data.layout);
117
- setIsSaving(true);
106
+ setSavingState({ isSaving: true });
118
107
 
119
108
  fetch(`/${urlroot}/savebuilder/${options.page_id || options.view_id}`, {
120
109
  method: "POST", // or 'PUT'
@@ -123,9 +112,29 @@ const InitNewElement = ({ nodekeys, setIsSaving }) => {
123
112
  "CSRF-Token": options.csrfToken,
124
113
  },
125
114
  body: JSON.stringify(data),
126
- }).then(() => {
127
- setIsSaving(false);
128
- });
115
+ })
116
+ .then((response) => {
117
+ response.json().then((data) => {
118
+ if (typeof data?.error === "string") {
119
+ // don't log duplicates
120
+ if (!savingState.error)
121
+ window.notifyAlert({ type: "danger", text: data.error });
122
+ setSavingState({ isSaving: false, error: data.error });
123
+ } else setSavingState({ isSaving: false });
124
+ });
125
+ })
126
+ .catch((e) => {
127
+ const text = e || "Unable to save";
128
+ // don't log duplicates
129
+ if (savingState.error) setSavingState({ isSaving: false, error: text });
130
+ else {
131
+ window.notifyAlert({ type: "danger", text: text });
132
+ setSavingState({
133
+ isSaving: false,
134
+ error: text,
135
+ });
136
+ }
137
+ });
129
138
  };
130
139
  const throttledSave = useThrottle(() => {
131
140
  doSave(query);
@@ -184,7 +193,7 @@ export /**
184
193
  * @subcategory components
185
194
  * @namespace
186
195
  */
187
- const Library = () => {
196
+ const Library = ({ expanded }) => {
188
197
  const { actions, selected, query, connectors } = useEditor((state, query) => {
189
198
  return {
190
199
  selected: state.events.selected,
@@ -216,7 +225,10 @@ const Library = () => {
216
225
  setRecent([...recent, data]);
217
226
  };
218
227
 
219
- const elemRows = twoByTwos([...(options.library || []), ...recent]);
228
+ const elemRows = chunk(
229
+ [...(options.library || []), ...recent],
230
+ expanded ? 3 : 2
231
+ );
220
232
  return (
221
233
  <div className="builder-library">
222
234
  <div className="dropdown">
@@ -35,6 +35,7 @@ import {
35
35
  SegmentedNav,
36
36
  TextareaT,
37
37
  } from "react-bootstrap-icons";
38
+ import { chunk } from "lodash";
38
39
 
39
40
  /**
40
41
  *
@@ -510,13 +511,24 @@ const TableElem = ({ connectors }) => (
510
511
  </WrapElem>
511
512
  );
512
513
 
514
+ const chunkToolBox = (elems, expanded) => {
515
+ const chunks = chunk(elems, expanded ? 3 : 2);
516
+ return chunks.map((es, ix) => (
517
+ <div className="toolbar-row" key={ix}>
518
+ {es.map((e, j) => (
519
+ <Fragment key={j}>{e}</Fragment>
520
+ ))}
521
+ </div>
522
+ ));
523
+ };
524
+
513
525
  export /**
514
526
  * @returns {Fragment}
515
527
  * @category saltcorn-builder
516
528
  * @subcategory components / Toolbox
517
529
  * @namespace
518
530
  */
519
- const ToolboxShow = () => {
531
+ const ToolboxShow = ({ expanded }) => {
520
532
  const { connectors, query } = useEditor();
521
533
  const options = useContext(optionsCtx);
522
534
  const {
@@ -527,52 +539,35 @@ const ToolboxShow = () => {
527
539
  views,
528
540
  images,
529
541
  } = options;
530
- return (
531
- <Fragment>
532
- <div className="toolbar-row">
533
- <TextElem connectors={connectors} />
534
- <ColumnsElem connectors={connectors} />
535
- </div>
536
- <div className="toolbar-row">
537
- <FieldElem
538
- connectors={connectors}
539
- fields={fields}
540
- field_view_options={field_view_options}
541
- />
542
- <LineBreakElem connectors={connectors} />
543
- </div>
544
- <div className="toolbar-row">
545
- <JoinFieldElem connectors={connectors} options={options} />
546
- <ViewLinkElem connectors={connectors} options={options} />
547
- </div>
548
- <div className="toolbar-row">
549
- <ActionElem connectors={connectors} options={options} />
550
- <LinkElem connectors={connectors} />
551
- </div>
552
- <div className="toolbar-row">
553
- <AggregationElem
554
- connectors={connectors}
555
- child_field_list={child_field_list}
556
- agg_field_opts={agg_field_opts}
557
- />
558
- <ViewElem connectors={connectors} views={views} />
559
- </div>
560
- <div className="toolbar-row">
561
- <ContainerElem connectors={connectors} />
562
- <CardElem connectors={connectors} />
563
- </div>
564
- <div className="toolbar-row">
565
- <TabsElem connectors={connectors} />
566
- <ImageElem connectors={connectors} images={images} />
567
- </div>
568
- <div className="toolbar-row">
569
- <HTMLElem connectors={connectors} />
570
- <DropMenuElem connectors={connectors} />
571
- </div>
572
- <div className="toolbar-row">
573
- <TableElem connectors={connectors} />
574
- </div>
575
- </Fragment>
542
+ return chunkToolBox(
543
+ [
544
+ <TextElem connectors={connectors} />,
545
+ <ColumnsElem connectors={connectors} />,
546
+ <FieldElem
547
+ connectors={connectors}
548
+ fields={fields}
549
+ field_view_options={field_view_options}
550
+ />,
551
+ <LineBreakElem connectors={connectors} />,
552
+ <JoinFieldElem connectors={connectors} options={options} />,
553
+ <ViewLinkElem connectors={connectors} options={options} />,
554
+ <ActionElem connectors={connectors} options={options} />,
555
+ <LinkElem connectors={connectors} />,
556
+ <AggregationElem
557
+ connectors={connectors}
558
+ child_field_list={child_field_list}
559
+ agg_field_opts={agg_field_opts}
560
+ />,
561
+ <ViewElem connectors={connectors} views={views} />,
562
+ <ContainerElem connectors={connectors} />,
563
+ <CardElem connectors={connectors} />,
564
+ <TabsElem connectors={connectors} />,
565
+ <ImageElem connectors={connectors} images={images} />,
566
+ <HTMLElem connectors={connectors} />,
567
+ <DropMenuElem connectors={connectors} />,
568
+ <TableElem connectors={connectors} />,
569
+ ],
570
+ expanded
576
571
  );
577
572
  };
578
573
 
@@ -582,50 +577,34 @@ export /**
582
577
  * @subcategory components / Toolbox
583
578
  * @namespace
584
579
  */
585
- const ToolboxFilter = () => {
580
+ const ToolboxFilter = ({ expanded }) => {
586
581
  const { connectors, query } = useEditor();
587
582
  const options = useContext(optionsCtx);
588
583
  const { fields, views, field_view_options } = options;
589
- return (
590
- <Fragment>
591
- <div className="toolbar-row">
592
- <TextElem connectors={connectors} />
593
- <ColumnsElem connectors={connectors} />
594
- </div>
595
- <div className="toolbar-row">
596
- <FieldElem
597
- connectors={connectors}
598
- fields={fields}
599
- field_view_options={field_view_options}
600
- />
601
- <LineBreakElem connectors={connectors} />
602
- </div>
603
- <div className="toolbar-row">
604
- <DropDownFilterElem connectors={connectors} fields={fields} />
605
- <ToggleFilterElem connectors={connectors} fields={fields} />
606
- </div>
607
- <div className="toolbar-row">
608
- <SearchElem connectors={connectors} />
609
- <ActionElem connectors={connectors} options={options} />
610
- </div>
611
- <div className="toolbar-row">
612
- <ContainerElem connectors={connectors} />
613
-
614
- <CardElem connectors={connectors} />
615
- </div>
616
- <div className="toolbar-row">
617
- <TabsElem connectors={connectors} />
618
- <ViewElem connectors={connectors} views={views} />
619
- </div>
620
- <div className="toolbar-row">
621
- <HTMLElem connectors={connectors} />
622
- <LinkElem connectors={connectors} />
623
- </div>
624
- <div className="toolbar-row">
625
- <TableElem connectors={connectors} />
626
- <DropMenuElem connectors={connectors} />
627
- </div>
628
- </Fragment>
584
+ return chunkToolBox(
585
+ [
586
+ <TextElem connectors={connectors} />,
587
+ <ColumnsElem connectors={connectors} />,
588
+ <FieldElem
589
+ connectors={connectors}
590
+ fields={fields}
591
+ field_view_options={field_view_options}
592
+ />,
593
+ <LineBreakElem connectors={connectors} />,
594
+ <DropDownFilterElem connectors={connectors} fields={fields} />,
595
+ <ToggleFilterElem connectors={connectors} fields={fields} />,
596
+ <SearchElem connectors={connectors} />,
597
+ <ActionElem connectors={connectors} options={options} />,
598
+ <ContainerElem connectors={connectors} />,
599
+ <CardElem connectors={connectors} />,
600
+ <TabsElem connectors={connectors} />,
601
+ <ViewElem connectors={connectors} views={views} />,
602
+ <HTMLElem connectors={connectors} />,
603
+ <LinkElem connectors={connectors} />,
604
+ <TableElem connectors={connectors} />,
605
+ <DropMenuElem connectors={connectors} />,
606
+ ],
607
+ expanded
629
608
  );
630
609
  };
631
610
 
@@ -635,49 +614,34 @@ export /**
635
614
  * @subcategory components / Toolbox
636
615
  * @namespace
637
616
  */
638
- const ToolboxEdit = () => {
617
+ const ToolboxEdit = ({ expanded }) => {
639
618
  const { connectors, query } = useEditor();
640
619
  const options = useContext(optionsCtx);
641
620
  const { fields, field_view_options, images, views } = options;
642
- return (
643
- <Fragment>
644
- <div className="toolbar-row">
645
- <TextElem connectors={connectors} />
646
- <ColumnsElem connectors={connectors} />
647
- </div>
648
- <div className="toolbar-row">
649
- <FieldElem
650
- connectors={connectors}
651
- fields={fields}
652
- field_view_options={field_view_options}
653
- />
654
- <LineBreakElem connectors={connectors} />
655
- </div>
656
- <div className="toolbar-row">
657
- <ActionElem connectors={connectors} options={options} />
658
- <ContainerElem connectors={connectors} />
659
- </div>
660
- <div className="toolbar-row">
661
- <CardElem connectors={connectors} />
662
- <TabsElem connectors={connectors} />
663
- </div>
664
- <div className="toolbar-row">
665
- <LinkElem connectors={connectors} />
666
- <ImageElem connectors={connectors} images={images} />
667
- </div>
668
- <div className="toolbar-row">
669
- <HTMLElem connectors={connectors} />
670
- <ViewElem connectors={connectors} views={views} />
671
- </div>
672
- <div className="toolbar-row">
673
- <JoinFieldElem connectors={connectors} options={options} />
674
- <DropMenuElem connectors={connectors} />
675
- </div>
676
- <div className="toolbar-row">
677
- <TableElem connectors={connectors} />
678
- <ViewLinkElem connectors={connectors} options={options} />
679
- </div>
680
- </Fragment>
621
+ return chunkToolBox(
622
+ [
623
+ <TextElem connectors={connectors} />,
624
+ <ColumnsElem connectors={connectors} />,
625
+ <FieldElem
626
+ connectors={connectors}
627
+ fields={fields}
628
+ field_view_options={field_view_options}
629
+ />,
630
+ <LineBreakElem connectors={connectors} />,
631
+ <ActionElem connectors={connectors} options={options} />,
632
+ <ContainerElem connectors={connectors} />,
633
+ <CardElem connectors={connectors} />,
634
+ <TabsElem connectors={connectors} />,
635
+ <LinkElem connectors={connectors} />,
636
+ <ImageElem connectors={connectors} images={images} />,
637
+ <HTMLElem connectors={connectors} />,
638
+ <ViewElem connectors={connectors} views={views} />,
639
+ <JoinFieldElem connectors={connectors} options={options} />,
640
+ <DropMenuElem connectors={connectors} />,
641
+ <TableElem connectors={connectors} />,
642
+ <ViewLinkElem connectors={connectors} options={options} />,
643
+ ],
644
+ expanded
681
645
  );
682
646
  };
683
647
 
@@ -687,43 +651,28 @@ export /**
687
651
  * @subcategory components / Toolbox
688
652
  * @namespace
689
653
  */
690
- const ToolboxPage = () => {
654
+ const ToolboxPage = ({ expanded }) => {
691
655
  const { connectors, query } = useEditor();
692
656
  const options = useContext(optionsCtx);
693
657
  const { views, pages, images } = options;
694
- return (
695
- <Fragment>
696
- <div className="toolbar-row">
697
- <TextElem connectors={connectors} />
698
- <ColumnsElem connectors={connectors} />
699
- </div>
700
- <div className="toolbar-row">
701
- <LineBreakElem connectors={connectors} />
702
- <HTMLElem connectors={connectors} />
703
- </div>
704
- <div className="toolbar-row">
705
- <CardElem connectors={connectors} />
706
- <ImageElem connectors={connectors} images={images} />{" "}
707
- </div>
708
- <div className="toolbar-row">
709
- <LinkElem connectors={connectors} />
710
- <ViewElem connectors={connectors} views={views} isPageEdit={true} />
711
- </div>
712
- <div className="toolbar-row">
713
- <SearchElem connectors={connectors} />
714
- <ActionElem connectors={connectors} options={options} />
715
- </div>
716
- <div className="toolbar-row">
717
- <ContainerElem connectors={connectors} />
718
- <TabsElem connectors={connectors} />
719
- </div>
720
- <div className="toolbar-row">
721
- <DropMenuElem connectors={connectors} />
722
- <PageElem connectors={connectors} pages={pages} />
723
- </div>
724
- <div className="toolbar-row">
725
- <TableElem connectors={connectors} />
726
- </div>
727
- </Fragment>
658
+ return chunkToolBox(
659
+ [
660
+ <TextElem connectors={connectors} />,
661
+ <ColumnsElem connectors={connectors} />,
662
+ <LineBreakElem connectors={connectors} />,
663
+ <HTMLElem connectors={connectors} />,
664
+ <CardElem connectors={connectors} />,
665
+ <ImageElem connectors={connectors} images={images} />,
666
+ <LinkElem connectors={connectors} />,
667
+ <ViewElem connectors={connectors} views={views} isPageEdit={true} />,
668
+ <SearchElem connectors={connectors} />,
669
+ <ActionElem connectors={connectors} options={options} />,
670
+ <ContainerElem connectors={connectors} />,
671
+ <TabsElem connectors={connectors} />,
672
+ <DropMenuElem connectors={connectors} />,
673
+ <PageElem connectors={connectors} pages={pages} />,
674
+ <TableElem connectors={connectors} />,
675
+ ],
676
+ expanded
728
677
  );
729
678
  };
@@ -250,7 +250,7 @@ const ColumnsSettings = () => {
250
250
  setProp={setProp}
251
251
  props={node}
252
252
  ></ConfigField>
253
- <table>
253
+ <table className="w-100">
254
254
  <tbody>
255
255
  <SettingsSectionHeaderRow title="Align" />
256
256
  <SettingsRow
@@ -738,7 +738,7 @@ const ContainerSettings = () => {
738
738
  )}
739
739
  {["show", "edit", "filter"].includes(options.mode) && (
740
740
  <tr>
741
- <td>
741
+ <td colSpan={2}>
742
742
  <input
743
743
  type="text"
744
744
  className="form-control text-to-display"