@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.
@@ -14,27 +14,26 @@ const ListingFileInput = ({
14
14
  errorMessage,
15
15
  disabled = false,
16
16
  }) => {
17
- const fileInputRef = useRef(null);
18
- const [internalFiles, setInternalFiles] = useState([]);
17
+ const [inputs, setInputs] = useState([{ id: 0, fileUrl: null }]);
18
+ const nextIdRef = useRef(1);
19
19
 
20
- // Normalize file data to enhanced structure - always handle as multiple files
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
- const normalizedFiles = value
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(/\.[^/.]+$/, ""), // Remove extension
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 - normalize to array
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
- setInternalFiles(singleFile ? [singleFile] : []);
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
- setInternalFiles([]);
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
- const newFile = {
95
- url: uploadedUrl,
96
- name: "", // Start with empty name as requested
97
- originalName: fileName,
98
- uploading: false,
99
- };
100
-
101
- // Always add new file to existing array for multiple file support
102
- const updatedFiles = [...internalFiles, newFile];
103
- setInternalFiles(updatedFiles);
104
- notifyParent(updatedFiles);
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
- const updatedFiles = internalFiles.map((file) =>
110
- file.url === url ? { ...file, name: newName } : file,
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
- const updatedFiles = internalFiles.filter((file) => file.url !== url);
119
- setInternalFiles(updatedFiles);
120
- notifyParent(updatedFiles);
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
- // Notify parent component of changes
124
- const notifyParent = (files) => {
125
- if (onChange) {
126
- // Always return array of files for multiple file support
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
- // Always show drop zone regardless of existing files
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
- <FileInput
177
- ref={fileInputRef}
178
- className="imageInputOuter-single"
179
- refreshCallback={handleFileUpload}
180
- hasDefault={null}
181
- accept={acceptTypes}
182
- multiple={false} // Always handle one file at a time in the upload callback
183
- disabled={disabled}
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
- {internalFiles.length > 0 && (
231
+ {uploadedFiles.length > 0 && (
189
232
  <div className={styles.listingFileInput__fileList}>
190
- {internalFiles.map((file) => (
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: var(--font-size-base);
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(var(--columns, 4), 1fr);
24
+ grid-template-columns: repeat(4, 160px);
24
25
  gap: 1rem;
25
26
  margin-top: 0.5rem;
26
27
  }
Binary file
Binary file
Binary file
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: 10, // Gallery-specific default
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 = 10;
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 = (payload && payload.featureDefinition) || payload;
281
- const definition = (definitionWrapper && definitionWrapper.definition) || definitionWrapper;
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) || featureDefinitionWrapper;
517
- definitionId = (featureDefinitionWrapper && featureDefinitionWrapper.id) || values.featureId;
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) {