@plusscommunities/pluss-feature-builder-web-b 1.0.2-beta.7 → 1.0.4-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/dist/index.cjs.js +123 -96
- package/package.json +11 -11
- package/src/actions/formActions.js +148 -148
- package/src/actions/wizardActions.js +166 -166
- package/src/components/BaseFieldConfig.jsx +3 -3
- package/src/components/ListingEditor.jsx +2 -2
- package/src/components/SidebarLayout.jsx +4 -4
- package/src/components/listing/ListingFileInput.jsx +98 -80
- package/src/components/listing/ListingFileInput.module.css +1 -4
- package/src/components/listing/ListingGalleryInput.module.css +2 -1
- package/src/images/full.png +0 -0
- package/src/images/fullNoTitle.png +0 -0
- package/src/images/previewWidget.png +0 -0
- package/src/images/widget.png +0 -0
- package/src/reducers/featureBuilderReducer.js +12 -6
- package/src/screens/FormLayoutStep.jsx +421 -420
- package/src/screens/FormOverviewStep.jsx +349 -349
- package/src/selectors/featureBuilderSelectors.js +1 -6
- package/src/values.config.default.js +49 -0
|
@@ -14,27 +14,26 @@ const ListingFileInput = ({
|
|
|
14
14
|
errorMessage,
|
|
15
15
|
disabled = false,
|
|
16
16
|
}) => {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const [inputs, setInputs] = useState([{ id: 0, fileUrl: null }]);
|
|
18
|
+
const nextIdRef = useRef(1);
|
|
19
19
|
|
|
20
|
-
//
|
|
20
|
+
// Initialize inputs from value prop - map each existing file to an input
|
|
21
21
|
useEffect(() => {
|
|
22
22
|
if (value) {
|
|
23
|
+
let fileData = [];
|
|
24
|
+
|
|
23
25
|
// Handle multiple files - could be array of URLs or array of file objects
|
|
24
26
|
if (Array.isArray(value)) {
|
|
25
|
-
|
|
27
|
+
fileData = value
|
|
26
28
|
.map((file) => {
|
|
27
29
|
if (typeof file === "string") {
|
|
28
|
-
// Backward compatibility: array of URLs
|
|
29
30
|
const fileName = file.split("/").pop() || "Unknown File";
|
|
30
31
|
return {
|
|
31
32
|
url: file,
|
|
32
|
-
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
33
|
+
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
33
34
|
originalName: fileName,
|
|
34
|
-
uploading: false,
|
|
35
35
|
};
|
|
36
36
|
} else if (file && typeof file === "object" && file.url) {
|
|
37
|
-
// Enhanced structure
|
|
38
37
|
return {
|
|
39
38
|
url: file.url,
|
|
40
39
|
name: file.name || file.originalName || "Unknown File",
|
|
@@ -42,27 +41,22 @@ const ListingFileInput = ({
|
|
|
42
41
|
file.originalName ||
|
|
43
42
|
file.url.split("/").pop() ||
|
|
44
43
|
"Unknown File",
|
|
45
|
-
uploading: false,
|
|
46
44
|
};
|
|
47
45
|
}
|
|
48
46
|
return null;
|
|
49
47
|
})
|
|
50
48
|
.filter(Boolean);
|
|
51
|
-
setInternalFiles(normalizedFiles);
|
|
52
49
|
} else {
|
|
53
|
-
// Handle single file values
|
|
50
|
+
// Handle single file values
|
|
54
51
|
let singleFile = null;
|
|
55
52
|
if (typeof value === "string") {
|
|
56
|
-
// Backward compatibility: single URL
|
|
57
53
|
const fileName = value.split("/").pop() || "Unknown File";
|
|
58
54
|
singleFile = {
|
|
59
55
|
url: value,
|
|
60
56
|
name: fileName.replace(/\.[^/.]+$/, ""),
|
|
61
57
|
originalName: fileName,
|
|
62
|
-
uploading: false,
|
|
63
58
|
};
|
|
64
59
|
} else if (value && typeof value === "object" && value.url) {
|
|
65
|
-
// Enhanced structure - preserve empty name if explicitly set
|
|
66
60
|
singleFile = {
|
|
67
61
|
url: value.url,
|
|
68
62
|
name:
|
|
@@ -73,60 +67,104 @@ const ListingFileInput = ({
|
|
|
73
67
|
value.originalName ||
|
|
74
68
|
value.url.split("/").pop() ||
|
|
75
69
|
"Unknown File",
|
|
76
|
-
uploading: false,
|
|
77
70
|
};
|
|
78
71
|
}
|
|
79
|
-
|
|
72
|
+
fileData = singleFile ? [singleFile] : [];
|
|
80
73
|
}
|
|
74
|
+
|
|
75
|
+
// Create inputs for each existing file
|
|
76
|
+
const existingInputs = fileData.map((file, index) => ({
|
|
77
|
+
id: index,
|
|
78
|
+
fileUrl: file.url,
|
|
79
|
+
name: file.name,
|
|
80
|
+
originalName: file.originalName,
|
|
81
|
+
}));
|
|
82
|
+
// Add one empty input at the end
|
|
83
|
+
nextIdRef.current = fileData.length;
|
|
84
|
+
setInputs([...existingInputs, { id: nextIdRef.current, fileUrl: null }]);
|
|
85
|
+
nextIdRef.current++;
|
|
81
86
|
} else {
|
|
82
|
-
|
|
87
|
+
setInputs([{ id: 0, fileUrl: null }]);
|
|
83
88
|
}
|
|
84
89
|
}, [value]);
|
|
85
90
|
|
|
86
91
|
// Handle new file upload from FileInput component
|
|
87
|
-
const handleFileUpload = (uploadedUrl) => {
|
|
88
|
-
// Handle case where uploadedUrl might be undefined
|
|
92
|
+
const handleFileUpload = (inputId, uploadedUrl) => {
|
|
89
93
|
if (!uploadedUrl) {
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
const fileName = uploadedUrl.split("/").pop() || "Unknown File";
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
|
|
99
|
+
// Find the input that triggered this upload and update it
|
|
100
|
+
setInputs((prevInputs) => {
|
|
101
|
+
const updatedInputs = prevInputs.map((input) => {
|
|
102
|
+
if (input.id === inputId) {
|
|
103
|
+
return {
|
|
104
|
+
...input,
|
|
105
|
+
fileUrl: uploadedUrl,
|
|
106
|
+
name: "",
|
|
107
|
+
originalName: fileName,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return input;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Add a new empty input for the next upload
|
|
114
|
+
updatedInputs.push({ id: nextIdRef.current, fileUrl: null });
|
|
115
|
+
nextIdRef.current++;
|
|
116
|
+
|
|
117
|
+
return updatedInputs;
|
|
118
|
+
});
|
|
105
119
|
};
|
|
106
120
|
|
|
107
121
|
// Handle file name change
|
|
108
122
|
const handleNameChange = (url, newName) => {
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
setInputs((prevInputs) =>
|
|
124
|
+
prevInputs.map((input) =>
|
|
125
|
+
input.fileUrl === url ? { ...input, name: newName } : input,
|
|
126
|
+
),
|
|
111
127
|
);
|
|
112
|
-
setInternalFiles(updatedFiles);
|
|
113
|
-
notifyParent(updatedFiles);
|
|
114
128
|
};
|
|
115
129
|
|
|
116
130
|
// Handle file removal
|
|
117
131
|
const handleFileRemove = (url) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
132
|
+
setInputs((prevInputs) => {
|
|
133
|
+
return prevInputs.map((input) => {
|
|
134
|
+
if (input.fileUrl === url) {
|
|
135
|
+
return { ...input, fileUrl: null };
|
|
136
|
+
}
|
|
137
|
+
return input;
|
|
138
|
+
});
|
|
139
|
+
});
|
|
121
140
|
};
|
|
122
141
|
|
|
123
|
-
//
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
142
|
+
// Get all uploaded files from inputs
|
|
143
|
+
const getUploadedFiles = () => {
|
|
144
|
+
return inputs
|
|
145
|
+
.filter((input) => input.fileUrl !== null)
|
|
146
|
+
.map((input) => ({
|
|
147
|
+
url: input.fileUrl,
|
|
148
|
+
name: input.name,
|
|
149
|
+
originalName: input.originalName,
|
|
150
|
+
}));
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Track previous uploaded files to prevent infinite loops
|
|
154
|
+
const prevFilesRef = useRef([]);
|
|
155
|
+
|
|
156
|
+
// Sync with parent when uploaded files actually change
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const files = getUploadedFiles();
|
|
159
|
+
const currentUrls = files.map((f) => f.url).join(",");
|
|
160
|
+
const prevUrls = prevFilesRef.current.map((f) => f.url).join(",");
|
|
161
|
+
|
|
162
|
+
// Only call onChange if the file list actually changed
|
|
163
|
+
if (currentUrls !== prevUrls && onChange) {
|
|
164
|
+
prevFilesRef.current = files;
|
|
127
165
|
onChange(field.id, files);
|
|
128
166
|
}
|
|
129
|
-
};
|
|
167
|
+
}, [inputs]);
|
|
130
168
|
|
|
131
169
|
// Define acceptable file types for documents and common formats
|
|
132
170
|
const acceptTypes = {
|
|
@@ -145,7 +183,11 @@ const ListingFileInput = ({
|
|
|
145
183
|
"image/png": [".png"],
|
|
146
184
|
};
|
|
147
185
|
|
|
148
|
-
//
|
|
186
|
+
// Get the last empty input (the one to show)
|
|
187
|
+
const emptyInput = inputs.find((input) => input.fileUrl === null);
|
|
188
|
+
|
|
189
|
+
// Get uploaded files for display
|
|
190
|
+
const uploadedFiles = getUploadedFiles();
|
|
149
191
|
|
|
150
192
|
return (
|
|
151
193
|
<div className={styles.listingFileInput}>
|
|
@@ -169,25 +211,26 @@ const ListingFileInput = ({
|
|
|
169
211
|
)}
|
|
170
212
|
</Text>
|
|
171
213
|
|
|
172
|
-
{/* Drop zone */}
|
|
214
|
+
{/* Drop zone - always show exactly one empty input */}
|
|
173
215
|
<div
|
|
174
216
|
className={`${styles.listingFileInput__dropZone} ${showError ? styles["listingFileInput__dropZone--error"] : ""}`}
|
|
175
217
|
>
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
218
|
+
{emptyInput && (
|
|
219
|
+
<FileInput
|
|
220
|
+
key={emptyInput.id}
|
|
221
|
+
refreshCallback={(url) => handleFileUpload(emptyInput.id, url)}
|
|
222
|
+
hasDefault={null}
|
|
223
|
+
accept={acceptTypes}
|
|
224
|
+
multiple={false}
|
|
225
|
+
disabled={disabled}
|
|
226
|
+
/>
|
|
227
|
+
)}
|
|
185
228
|
</div>
|
|
186
229
|
|
|
187
230
|
{/* File list */}
|
|
188
|
-
{
|
|
231
|
+
{uploadedFiles.length > 0 && (
|
|
189
232
|
<div className={styles.listingFileInput__fileList}>
|
|
190
|
-
{
|
|
233
|
+
{uploadedFiles.map((file) => (
|
|
191
234
|
<FileListItem
|
|
192
235
|
key={file.url}
|
|
193
236
|
file={file}
|
|
@@ -199,31 +242,6 @@ const ListingFileInput = ({
|
|
|
199
242
|
</div>
|
|
200
243
|
)}
|
|
201
244
|
|
|
202
|
-
{/* Add more files button - always show when files exist */}
|
|
203
|
-
{internalFiles.length > 0 && (
|
|
204
|
-
<div>
|
|
205
|
-
<FileInput
|
|
206
|
-
ref={fileInputRef}
|
|
207
|
-
className="imageInputOuter-single"
|
|
208
|
-
refreshCallback={handleFileUpload}
|
|
209
|
-
hasDefault={null}
|
|
210
|
-
accept={acceptTypes}
|
|
211
|
-
multiple={false}
|
|
212
|
-
disabled={disabled}
|
|
213
|
-
customButton={
|
|
214
|
-
<Button
|
|
215
|
-
buttonType="secondary"
|
|
216
|
-
className={styles.listingFileInput__addMoreButton}
|
|
217
|
-
disabled={disabled}
|
|
218
|
-
>
|
|
219
|
-
<FontAwesomeIcon icon={iconImports.plus} />
|
|
220
|
-
Add More Files
|
|
221
|
-
</Button>
|
|
222
|
-
}
|
|
223
|
-
/>
|
|
224
|
-
</div>
|
|
225
|
-
)}
|
|
226
|
-
|
|
227
245
|
{showError && errorMessage && (
|
|
228
246
|
<Text
|
|
229
247
|
type="help"
|
|
@@ -38,9 +38,7 @@
|
|
|
38
38
|
display: flex;
|
|
39
39
|
align-items: center;
|
|
40
40
|
gap: 0.75rem;
|
|
41
|
-
padding: 0.75rem;
|
|
42
41
|
background-color: var(--bg-white);
|
|
43
|
-
margin-bottom: 0.5rem;
|
|
44
42
|
transition: all var(--transition-base) ease-in-out;
|
|
45
43
|
}
|
|
46
44
|
|
|
@@ -50,9 +48,8 @@
|
|
|
50
48
|
|
|
51
49
|
.fileListItem__icon {
|
|
52
50
|
color: var(--text-bluegrey);
|
|
53
|
-
font-size:
|
|
51
|
+
font-size: 2rem;
|
|
54
52
|
flex-shrink: 0;
|
|
55
|
-
width: 1.5rem;
|
|
56
53
|
text-align: center;
|
|
57
54
|
}
|
|
58
55
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
/* Gallery container styles */
|
|
4
4
|
.galleryContainer {
|
|
5
5
|
margin-top: 0.75rem;
|
|
6
|
+
max-width: 200px;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
/* Error border wrapper */
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
|
|
21
22
|
.imageGrid {
|
|
22
23
|
display: grid;
|
|
23
|
-
grid-template-columns: repeat(
|
|
24
|
+
grid-template-columns: repeat(4, 160px);
|
|
24
25
|
gap: 1rem;
|
|
25
26
|
margin-top: 0.5rem;
|
|
26
27
|
}
|
package/src/images/full.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/images/widget.png
CHANGED
|
Binary file
|
|
@@ -86,7 +86,7 @@ const DEFAULT_NEW_FIELD = {
|
|
|
86
86
|
allowCaption: false, // Default to not allowing captions for image fields
|
|
87
87
|
useAsSummary: false, // Default to not using as summary for description fields
|
|
88
88
|
minImages: 1, // Gallery-specific default
|
|
89
|
-
maxImages:
|
|
89
|
+
maxImages: 16, // Gallery-specific default
|
|
90
90
|
maxFileSize: "5MB", // Gallery-specific default
|
|
91
91
|
allowedTypes: [], // Gallery-specific default
|
|
92
92
|
},
|
|
@@ -138,7 +138,7 @@ function createNewField(id, type = "text", existingFields = []) {
|
|
|
138
138
|
baseField.values.label = "Image Gallery";
|
|
139
139
|
baseField.values.helpText = "Upload multiple images to create a gallery";
|
|
140
140
|
baseField.values.minImages = 1;
|
|
141
|
-
baseField.values.maxImages =
|
|
141
|
+
baseField.values.maxImages = 16;
|
|
142
142
|
baseField.values.maxFileSize = "5MB";
|
|
143
143
|
baseField.values.allowedTypes = ["image/jpeg", "image/png", "image/webp"];
|
|
144
144
|
const {
|
|
@@ -277,8 +277,11 @@ const formReducer = (state = INITIAL_FORM_STATE, action) => {
|
|
|
277
277
|
switch (type) {
|
|
278
278
|
case formActionTypes.SET_INITIAL_VALUES: {
|
|
279
279
|
// The actual definition data is in payload.featureDefinition.definition
|
|
280
|
-
const definitionWrapper =
|
|
281
|
-
|
|
280
|
+
const definitionWrapper =
|
|
281
|
+
(payload && payload.featureDefinition) || payload;
|
|
282
|
+
const definition =
|
|
283
|
+
(definitionWrapper && definitionWrapper.definition) ||
|
|
284
|
+
definitionWrapper;
|
|
282
285
|
|
|
283
286
|
// Validate and map definition data to form state structure
|
|
284
287
|
const mappedValues = {
|
|
@@ -513,8 +516,11 @@ const definitionReducer = (state = INITIAL_STATE.definition, action) => {
|
|
|
513
516
|
const featureDefinitionWrapper =
|
|
514
517
|
(data && data.featureDefinition) || data;
|
|
515
518
|
definition =
|
|
516
|
-
(featureDefinitionWrapper && featureDefinitionWrapper.definition) ||
|
|
517
|
-
|
|
519
|
+
(featureDefinitionWrapper && featureDefinitionWrapper.definition) ||
|
|
520
|
+
featureDefinitionWrapper;
|
|
521
|
+
definitionId =
|
|
522
|
+
(featureDefinitionWrapper && featureDefinitionWrapper.id) ||
|
|
523
|
+
values.featureId;
|
|
518
524
|
|
|
519
525
|
// Ensure fields array exists and preserves order property
|
|
520
526
|
if (definition?.fields) {
|