@firecms/core 3.0.1 → 3.1.0-canary.1df3b2c
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/README.md +1 -1
- package/dist/components/AIIcon.d.ts +16 -0
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +7 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +14 -0
- package/dist/components/EntityCollectionTable/PropertyTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +5 -4
- package/dist/components/EntityCollectionTable/internal/EntityTableCell.d.ts +6 -0
- package/dist/components/EntityCollectionView/Board.d.ts +2 -0
- package/dist/components/EntityCollectionView/BoardColumn.d.ts +42 -0
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +9 -0
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +14 -0
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +26 -0
- package/dist/components/EntityCollectionView/EntityCard.d.ts +19 -0
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +20 -0
- package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +31 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -2
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +7 -3
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +14 -0
- package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +49 -0
- package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
- package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +2 -0
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -0
- package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
- package/dist/components/VirtualTable/types.d.ts +2 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/contexts/index.d.ts +10 -0
- package/dist/core/DrawerNavigationGroup.d.ts +45 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/form/validation.d.ts +3 -2
- package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
- package/dist/hooks/useCollapsedGroups.d.ts +4 -1
- package/dist/index.es.js +5239 -1590
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5233 -1585
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/DatePreview.d.ts +13 -3
- package/dist/preview/components/ImagePreview.d.ts +5 -1
- package/dist/preview/components/StorageThumbnail.d.ts +2 -1
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +1 -1
- package/dist/types/collections.d.ts +42 -2
- package/dist/types/datasource.d.ts +0 -1
- package/dist/types/plugins.d.ts +46 -1
- package/dist/types/properties.d.ts +259 -4
- package/dist/util/__tests__/conditions.test.d.ts +1 -0
- package/dist/util/__tests__/objects.test.d.ts +1 -0
- package/dist/util/conditions.d.ts +26 -0
- package/dist/util/entities.d.ts +1 -2
- package/dist/util/index.d.ts +2 -1
- package/dist/util/property_utils.d.ts +2 -1
- package/dist/util/resolutions.d.ts +1 -1
- package/package.json +10 -7
- package/src/app/Scaffold.tsx +14 -15
- package/src/components/AIIcon.tsx +39 -0
- package/src/components/ArrayContainer.tsx +1 -4
- package/src/components/ClearFilterSortButton.tsx +19 -16
- package/src/components/ConfirmationDialog.tsx +0 -2
- package/src/components/DeleteEntityDialog.tsx +2 -4
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +74 -41
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +130 -79
- package/src/components/EntityCollectionTable/EntityCollectionTableProps.tsx +121 -104
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +132 -103
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +20 -42
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
- package/src/components/EntityCollectionView/Board.tsx +324 -0
- package/src/components/EntityCollectionView/BoardColumn.tsx +158 -0
- package/src/components/EntityCollectionView/BoardColumnTitle.tsx +45 -0
- package/src/components/EntityCollectionView/BoardSortableList.tsx +172 -0
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
- package/src/components/EntityCollectionView/EntityCard.tsx +231 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +713 -0
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +244 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +485 -203
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +31 -19
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +84 -15
- package/src/components/EntityCollectionView/FiltersDialog.tsx +249 -0
- package/src/components/EntityCollectionView/ViewModeToggle.tsx +202 -0
- package/src/components/EntityCollectionView/board_types.ts +113 -0
- package/src/components/EntityCollectionView/useBoardDataController.tsx +490 -0
- package/src/components/ErrorTooltip.tsx +2 -1
- package/src/components/HomePage/DefaultHomePage.tsx +47 -10
- package/src/components/HomePage/HomePageDnD.tsx +56 -41
- package/src/components/HomePage/NavigationCard.tsx +20 -18
- package/src/components/HomePage/NavigationGroup.tsx +17 -16
- package/src/components/HomePage/RenameGroupDialog.tsx +0 -2
- package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -10
- package/src/components/ReferenceWidget.tsx +2 -4
- package/src/components/SelectableTable/SelectableTable.tsx +75 -67
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +39 -40
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +38 -38
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +49 -58
- package/src/components/UnsavedChangesDialog.tsx +0 -2
- package/src/components/UserDisplay.tsx +4 -4
- package/src/components/VirtualTable/VirtualTable.tsx +170 -19
- package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
- package/src/components/VirtualTable/VirtualTableHeader.tsx +20 -11
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +158 -42
- package/src/components/VirtualTable/VirtualTableProps.tsx +14 -1
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +17 -4
- package/src/components/VirtualTable/types.tsx +2 -0
- package/src/components/common/useColumnsIds.tsx +95 -3
- package/src/components/index.tsx +4 -0
- package/src/contexts/BreacrumbsContext.tsx +15 -8
- package/src/contexts/index.ts +10 -0
- package/src/core/DefaultAppBar.tsx +39 -26
- package/src/core/DefaultDrawer.tsx +42 -56
- package/src/core/DrawerNavigationGroup.tsx +118 -0
- package/src/core/DrawerNavigationItem.tsx +4 -3
- package/src/core/EntityEditView.tsx +41 -43
- package/src/core/SideDialogs.tsx +4 -2
- package/src/core/index.tsx +1 -0
- package/src/form/PropertyFieldBinding.tsx +58 -43
- package/src/form/components/StorageItemPreview.tsx +2 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -1
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +17 -16
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +69 -67
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +21 -17
- package/src/form/field_bindings/TextFieldBinding.tsx +71 -35
- package/src/form/validation.ts +245 -160
- package/src/hooks/useBreadcrumbsController.tsx +18 -0
- package/src/hooks/useBuildNavigationController.tsx +42 -19
- package/src/hooks/useCollapsedGroups.ts +12 -4
- package/src/internal/useBuildDataSource.ts +69 -34
- package/src/internal/useBuildSideDialogsController.tsx +11 -8
- package/src/internal/useBuildSideEntityController.tsx +2 -4
- package/src/internal/useRestoreScroll.tsx +26 -14
- package/src/preview/PropertyPreview.tsx +40 -32
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/DatePreview.tsx +72 -4
- package/src/preview/components/EmptyValue.tsx +1 -1
- package/src/preview/components/ImagePreview.tsx +37 -21
- package/src/preview/components/StorageThumbnail.tsx +16 -12
- package/src/preview/components/UrlComponentPreview.tsx +28 -25
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +9 -7
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +11 -9
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +26 -24
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
- package/src/routes/CustomCMSRoute.tsx +1 -0
- package/src/routes/FireCMSRoute.tsx +26 -13
- package/src/types/collections.ts +48 -3
- package/src/types/datasource.ts +54 -56
- package/src/types/plugins.tsx +51 -1
- package/src/types/properties.ts +347 -27
- package/src/util/__tests__/conditions.test.ts +506 -0
- package/src/util/__tests__/objects.test.ts +196 -0
- package/src/util/callbacks.ts +6 -3
- package/src/util/collections.ts +51 -6
- package/src/util/conditions.ts +339 -0
- package/src/util/entities.ts +28 -29
- package/src/util/entity_cache.ts +2 -1
- package/src/util/index.ts +2 -1
- package/src/util/objects.ts +31 -13
- package/src/util/{references.ts → previews.ts} +14 -0
- package/src/util/property_utils.tsx +36 -10
- package/src/util/resolutions.ts +57 -55
- /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
|
@@ -49,7 +49,8 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
49
49
|
size,
|
|
50
50
|
height,
|
|
51
51
|
width,
|
|
52
|
-
interactive
|
|
52
|
+
interactive,
|
|
53
|
+
fill
|
|
53
54
|
} = props;
|
|
54
55
|
|
|
55
56
|
const property = resolveProperty({
|
|
@@ -60,7 +61,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
60
61
|
});
|
|
61
62
|
|
|
62
63
|
if (property === null) {
|
|
63
|
-
content = <EmptyValue/>;
|
|
64
|
+
content = <EmptyValue />;
|
|
64
65
|
} else if (property.Preview) {
|
|
65
66
|
content = createElement(property.Preview as React.ComponentType<PropertyPreviewProps>,
|
|
66
67
|
{
|
|
@@ -74,7 +75,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
74
75
|
customProps: property.customProps
|
|
75
76
|
});
|
|
76
77
|
} else if (value === undefined || value === null) {
|
|
77
|
-
content = <EmptyValue/>;
|
|
78
|
+
content = <EmptyValue />;
|
|
78
79
|
} else if (property.dataType === "string") {
|
|
79
80
|
const stringProperty = property as ResolvedStringProperty;
|
|
80
81
|
if (typeof value === "string") {
|
|
@@ -84,20 +85,23 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
84
85
|
interactive={interactive}
|
|
85
86
|
storeUrl={property.storage?.storeUrl ?? false}
|
|
86
87
|
size={props.size}
|
|
87
|
-
|
|
88
|
+
fill={fill}
|
|
89
|
+
storagePathOrDownloadUrl={filePath} />;
|
|
88
90
|
} else if (stringProperty.url) {
|
|
89
91
|
if (typeof stringProperty.url === "boolean")
|
|
90
92
|
content =
|
|
91
93
|
<UrlComponentPreview size={props.size}
|
|
92
|
-
|
|
94
|
+
url={value}
|
|
95
|
+
fill={fill} />;
|
|
93
96
|
else if (typeof stringProperty.url === "string")
|
|
94
97
|
content =
|
|
95
98
|
<UrlComponentPreview size={props.size}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
url={value}
|
|
100
|
+
interactive={interactive}
|
|
101
|
+
fill={fill}
|
|
102
|
+
previewType={stringProperty.url} />;
|
|
99
103
|
} else if (stringProperty.markdown) {
|
|
100
|
-
content = <Markdown source={value} size={"small"}/>;
|
|
104
|
+
content = <Markdown source={value} size={"small"} />;
|
|
101
105
|
} else if (stringProperty.userSelect) {
|
|
102
106
|
content = <UserPreview
|
|
103
107
|
value={value}
|
|
@@ -116,13 +120,13 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
116
120
|
reference={new EntityReference(value, stringProperty.reference.path)}
|
|
117
121
|
/>;
|
|
118
122
|
} else {
|
|
119
|
-
content = <EmptyValue/>;
|
|
123
|
+
content = <EmptyValue />;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
126
|
} else {
|
|
123
127
|
content = <StringPropertyPreview {...props}
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
property={stringProperty}
|
|
129
|
+
value={value} />;
|
|
126
130
|
}
|
|
127
131
|
} else {
|
|
128
132
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
@@ -137,43 +141,43 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
137
141
|
if (arrayProperty.of) {
|
|
138
142
|
if (Array.isArray(arrayProperty.of)) {
|
|
139
143
|
content = <ArrayPropertyPreview {...props}
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
value={value}
|
|
145
|
+
property={property as ResolvedArrayProperty} />;
|
|
142
146
|
} else if (arrayProperty.of.dataType === "reference") {
|
|
143
147
|
content = <ArrayOfReferencesPreview {...props}
|
|
144
|
-
|
|
145
|
-
|
|
148
|
+
value={value}
|
|
149
|
+
property={property as ResolvedArrayProperty} />;
|
|
146
150
|
} else if (arrayProperty.of.dataType === "string") {
|
|
147
151
|
if (arrayProperty.of.enumValues) {
|
|
148
152
|
content = <ArrayPropertyEnumPreview
|
|
149
153
|
{...props}
|
|
150
154
|
value={value as string[]}
|
|
151
|
-
property={property as ResolvedArrayProperty}/>;
|
|
155
|
+
property={property as ResolvedArrayProperty} />;
|
|
152
156
|
} else if (arrayProperty.of.storage) {
|
|
153
157
|
content = <ArrayOfStorageComponentsPreview
|
|
154
158
|
{...props}
|
|
155
159
|
value={value}
|
|
156
|
-
property={property as ResolvedArrayProperty}/>;
|
|
160
|
+
property={property as ResolvedArrayProperty} />;
|
|
157
161
|
} else {
|
|
158
162
|
content = <ArrayOfStringsPreview
|
|
159
163
|
{...props}
|
|
160
164
|
value={value as string[]}
|
|
161
|
-
property={property as ResolvedArrayProperty}/>;
|
|
165
|
+
property={property as ResolvedArrayProperty} />;
|
|
162
166
|
}
|
|
163
167
|
} else if (arrayProperty.of.dataType === "number" && arrayProperty.of.enumValues) {
|
|
164
168
|
content = <ArrayPropertyEnumPreview
|
|
165
169
|
{...props}
|
|
166
170
|
value={value as string[]}
|
|
167
|
-
property={property as ResolvedArrayProperty}/>;
|
|
171
|
+
property={property as ResolvedArrayProperty} />;
|
|
168
172
|
} else {
|
|
169
173
|
content = <ArrayPropertyPreview {...props}
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
value={value}
|
|
175
|
+
property={property as ResolvedArrayProperty} />;
|
|
172
176
|
}
|
|
173
177
|
} else if (arrayProperty.oneOf) {
|
|
174
178
|
content = <ArrayOneOfPreview {...props}
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
value={value}
|
|
180
|
+
property={property as ResolvedArrayProperty} />;
|
|
177
181
|
}
|
|
178
182
|
} else {
|
|
179
183
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
@@ -182,13 +186,17 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
182
186
|
if (typeof value === "object") {
|
|
183
187
|
content =
|
|
184
188
|
<MapPropertyPreview {...props}
|
|
185
|
-
|
|
189
|
+
property={property as ResolvedMapProperty} />;
|
|
186
190
|
} else {
|
|
187
191
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
188
192
|
}
|
|
189
193
|
} else if (property.dataType === "date") {
|
|
190
194
|
if (value instanceof Date) {
|
|
191
|
-
content = <DatePreview
|
|
195
|
+
content = <DatePreview
|
|
196
|
+
date={value}
|
|
197
|
+
mode={property.mode}
|
|
198
|
+
timezone={property.timezone}
|
|
199
|
+
/>;
|
|
192
200
|
} else {
|
|
193
201
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
194
202
|
}
|
|
@@ -207,20 +215,20 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
207
215
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
208
216
|
}
|
|
209
217
|
} else {
|
|
210
|
-
content = <EmptyValue/>;
|
|
218
|
+
content = <EmptyValue />;
|
|
211
219
|
}
|
|
212
220
|
|
|
213
221
|
} else if (property.dataType === "boolean") {
|
|
214
222
|
if (typeof value === "boolean") {
|
|
215
|
-
content = <BooleanPreview value={value} size={size} property={property}/>;
|
|
223
|
+
content = <BooleanPreview value={value} size={size} property={property} />;
|
|
216
224
|
} else {
|
|
217
225
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
218
226
|
}
|
|
219
227
|
} else if (property.dataType === "number") {
|
|
220
228
|
if (typeof value === "number") {
|
|
221
229
|
content = <NumberPropertyPreview {...props}
|
|
222
|
-
|
|
223
|
-
|
|
230
|
+
value={value}
|
|
231
|
+
property={property as ResolvedNumberProperty} />;
|
|
224
232
|
} else {
|
|
225
233
|
content = buildWrongValueType(propertyKey, property.dataType, value);
|
|
226
234
|
}
|
|
@@ -229,7 +237,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
229
237
|
}
|
|
230
238
|
|
|
231
239
|
return content === undefined || content === null || (Array.isArray(content) && content.length === 0)
|
|
232
|
-
? <EmptyValue/>
|
|
240
|
+
? <EmptyValue />
|
|
233
241
|
: content;
|
|
234
242
|
}, equal);
|
|
235
243
|
|
|
@@ -237,6 +245,6 @@ function buildWrongValueType(name: string | undefined, dataType: string, value:
|
|
|
237
245
|
console.warn(`Unexpected value for property ${name}, of type ${dataType}`, value);
|
|
238
246
|
return (
|
|
239
247
|
<ErrorView title={"Unexpected value"}
|
|
240
|
-
|
|
248
|
+
error={`${JSON.stringify(value)}`} />
|
|
241
249
|
);
|
|
242
250
|
}
|
|
@@ -52,4 +52,10 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
|
52
52
|
*/
|
|
53
53
|
interactive?: boolean;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* If true, image previews will fill their container completely.
|
|
57
|
+
* Only applies to image type properties.
|
|
58
|
+
*/
|
|
59
|
+
fill?: boolean;
|
|
60
|
+
|
|
55
61
|
}
|
|
@@ -5,18 +5,86 @@ import * as locales from "date-fns/locale";
|
|
|
5
5
|
import { useCustomizationController } from "../../hooks";
|
|
6
6
|
import { defaultDateFormat } from "../../util";
|
|
7
7
|
|
|
8
|
+
export interface DatePreviewProps {
|
|
9
|
+
date: Date;
|
|
10
|
+
/**
|
|
11
|
+
* Display mode: "date" for date-only, "date_time" for date and time
|
|
12
|
+
*/
|
|
13
|
+
mode?: "date" | "date_time";
|
|
14
|
+
/**
|
|
15
|
+
* IANA timezone identifier (e.g., "America/New_York")
|
|
16
|
+
* When specified, the date will be displayed in this timezone
|
|
17
|
+
*/
|
|
18
|
+
timezone?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
8
21
|
/**
|
|
9
22
|
* @group Preview components
|
|
10
23
|
*/
|
|
11
24
|
export function DatePreview({
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
date,
|
|
26
|
+
mode = "date_time",
|
|
27
|
+
timezone
|
|
28
|
+
}: DatePreviewProps): React.ReactElement {
|
|
14
29
|
|
|
15
30
|
const customizationController = useCustomizationController();
|
|
16
31
|
// @ts-ignore
|
|
17
32
|
const dateUtilsLocale = customizationController?.locale ? locales[customizationController?.locale] : undefined;
|
|
18
|
-
|
|
19
|
-
|
|
33
|
+
|
|
34
|
+
if (!date) {
|
|
35
|
+
return <></>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// If timezone is specified, format in that timezone using Intl.DateTimeFormat
|
|
39
|
+
if (timezone) {
|
|
40
|
+
const options: Intl.DateTimeFormatOptions = {
|
|
41
|
+
year: "numeric",
|
|
42
|
+
month: "short",
|
|
43
|
+
day: "numeric",
|
|
44
|
+
timeZone: timezone
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (mode === "date_time") {
|
|
48
|
+
options.hour = "2-digit";
|
|
49
|
+
options.minute = "2-digit";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const formatter = new Intl.DateTimeFormat(customizationController?.locale ?? "en-US", options);
|
|
53
|
+
const formattedDate = formatter.format(date);
|
|
54
|
+
|
|
55
|
+
// Get timezone abbreviation
|
|
56
|
+
const tzFormatter = new Intl.DateTimeFormat("en-US", {
|
|
57
|
+
timeZone: timezone,
|
|
58
|
+
timeZoneName: "short"
|
|
59
|
+
});
|
|
60
|
+
const parts = tzFormatter.formatToParts(date);
|
|
61
|
+
const tzAbbrev = parts.find(p => p.type === "timeZoneName")?.value ?? "";
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<span className="flex items-center gap-1">
|
|
65
|
+
{formattedDate}
|
|
66
|
+
{tzAbbrev && (
|
|
67
|
+
<span className="text-xs text-slate-500 dark:text-slate-400">
|
|
68
|
+
({tzAbbrev})
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
</span>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// No timezone specified: use local formatting with date-fns
|
|
76
|
+
let dateFormat: string;
|
|
77
|
+
if (mode === "date") {
|
|
78
|
+
// Date-only format (no time)
|
|
79
|
+
dateFormat = customizationController?.dateTimeFormat
|
|
80
|
+
? customizationController.dateTimeFormat.replace(/[, ]*[HhKk].*$/, "").trim()
|
|
81
|
+
: "PP"; // e.g., "Apr 29, 2024"
|
|
82
|
+
} else {
|
|
83
|
+
// Full date-time format
|
|
84
|
+
dateFormat = customizationController?.dateTimeFormat ?? defaultDateFormat;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const formattedDate = format(date, dateFormat, { locale: dateUtilsLocale });
|
|
20
88
|
|
|
21
89
|
return (
|
|
22
90
|
<>
|
|
@@ -6,5 +6,5 @@ import React from "react";
|
|
|
6
6
|
export function EmptyValue() {
|
|
7
7
|
|
|
8
8
|
return <div
|
|
9
|
-
className="rounded-full bg-surface-200 bg-opacity-30 dark:bg-opacity-20 w-5 h-2 inline-block"/>;
|
|
9
|
+
className="rounded-full bg-surface-200 bg-opacity-30 bg-surface-200/30 dark:bg-opacity-20 dark:bg-surface-200/20 w-5 h-2 inline-block"/>;
|
|
10
10
|
}
|
|
@@ -9,39 +9,55 @@ import { ContentCopyIcon, IconButton, OpenInNewIcon, Tooltip } from "@firecms/ui
|
|
|
9
9
|
*/
|
|
10
10
|
export interface ImagePreviewProps {
|
|
11
11
|
size: PreviewSize,
|
|
12
|
-
url: string
|
|
12
|
+
url: string,
|
|
13
|
+
/**
|
|
14
|
+
* If true, image fills its container completely with object-fit cover
|
|
15
|
+
*/
|
|
16
|
+
fill?: boolean
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* @group Preview components
|
|
17
21
|
*/
|
|
18
22
|
export function ImagePreview({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
size,
|
|
24
|
+
url,
|
|
25
|
+
fill
|
|
26
|
+
}: ImagePreviewProps) {
|
|
22
27
|
|
|
23
28
|
const imageSize = useMemo(() => getThumbnailMeasure(size), [size]);
|
|
24
29
|
|
|
30
|
+
// Fill mode - image fills its container completely
|
|
31
|
+
if (fill) {
|
|
32
|
+
return (
|
|
33
|
+
<img src={url}
|
|
34
|
+
className={"w-full h-full object-cover"}
|
|
35
|
+
key={"fill_image_preview_" + url}
|
|
36
|
+
loading="lazy"
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
25
41
|
if (size === "small") {
|
|
26
42
|
return (
|
|
27
43
|
<img src={url}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
className={"rounded-md"}
|
|
45
|
+
key={"tiny_image_preview_" + url}
|
|
46
|
+
style={{
|
|
47
|
+
position: "relative",
|
|
48
|
+
objectFit: "cover",
|
|
49
|
+
width: imageSize,
|
|
50
|
+
height: imageSize,
|
|
51
|
+
maxHeight: "100%"
|
|
52
|
+
}} />
|
|
37
53
|
);
|
|
38
54
|
}
|
|
39
55
|
|
|
40
56
|
const imageStyle: CSSProperties =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
{
|
|
58
|
+
maxWidth: "100%",
|
|
59
|
+
maxHeight: "100%"
|
|
60
|
+
};
|
|
45
61
|
|
|
46
62
|
return (
|
|
47
63
|
<div
|
|
@@ -53,8 +69,8 @@ export function ImagePreview({
|
|
|
53
69
|
key={"image_preview_" + url}>
|
|
54
70
|
|
|
55
71
|
<img src={url}
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
className={"rounded-md"}
|
|
73
|
+
style={imageStyle} />
|
|
58
74
|
|
|
59
75
|
<div className={"flex flex-row gap-2 absolute bottom-[-4px] right-[-4px] invisible group-hover:visible"}>
|
|
60
76
|
{navigator && <Tooltip
|
|
@@ -69,7 +85,7 @@ export function ImagePreview({
|
|
|
69
85
|
return navigator.clipboard.writeText(url);
|
|
70
86
|
}}>
|
|
71
87
|
<ContentCopyIcon className={"text-surface-700 dark:text-surface-300"}
|
|
72
|
-
|
|
88
|
+
size={"smallest"} />
|
|
73
89
|
</IconButton>
|
|
74
90
|
</Tooltip>}
|
|
75
91
|
|
|
@@ -85,7 +101,7 @@ export function ImagePreview({
|
|
|
85
101
|
onClick={(e: any) => e.stopPropagation()}
|
|
86
102
|
>
|
|
87
103
|
<OpenInNewIcon className={"text-surface-700 dark:text-surface-300"}
|
|
88
|
-
|
|
104
|
+
size={"smallest"} />
|
|
89
105
|
</IconButton>
|
|
90
106
|
</Tooltip>
|
|
91
107
|
</div>
|
|
@@ -12,6 +12,7 @@ type StorageThumbnailProps = {
|
|
|
12
12
|
storeUrl: boolean;
|
|
13
13
|
size: PreviewSize;
|
|
14
14
|
interactive?: boolean;
|
|
15
|
+
fill?: boolean;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -22,18 +23,20 @@ export const StorageThumbnail = React.memo<StorageThumbnailProps>(StorageThumbna
|
|
|
22
23
|
function areEqual(prevProps: StorageThumbnailProps, nextProps: StorageThumbnailProps) {
|
|
23
24
|
return prevProps.size === nextProps.size &&
|
|
24
25
|
prevProps.storagePathOrDownloadUrl === nextProps.storagePathOrDownloadUrl &&
|
|
25
|
-
prevProps.storeUrl === nextProps.storeUrl&&
|
|
26
|
-
prevProps.interactive === nextProps.interactive
|
|
26
|
+
prevProps.storeUrl === nextProps.storeUrl &&
|
|
27
|
+
prevProps.interactive === nextProps.interactive &&
|
|
28
|
+
prevProps.fill === nextProps.fill;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
const URL_CACHE: Record<string, DownloadConfig> = {};
|
|
30
32
|
|
|
31
33
|
export function StorageThumbnailInternal({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
storeUrl,
|
|
35
|
+
interactive,
|
|
36
|
+
storagePathOrDownloadUrl,
|
|
37
|
+
size,
|
|
38
|
+
fill
|
|
39
|
+
}: StorageThumbnailProps) {
|
|
37
40
|
|
|
38
41
|
const [error, setError] = React.useState<Error | undefined>(undefined);
|
|
39
42
|
const storage = useStorageSource();
|
|
@@ -70,11 +73,12 @@ export function StorageThumbnailInternal({
|
|
|
70
73
|
|
|
71
74
|
return downloadConfig?.url
|
|
72
75
|
? <UrlComponentPreview previewType={previewType}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
url={downloadConfig.url}
|
|
77
|
+
interactive={interactive}
|
|
78
|
+
size={size}
|
|
79
|
+
fill={fill}
|
|
80
|
+
hint={storagePathOrDownloadUrl} />
|
|
81
|
+
: renderSkeletonImageThumbnail(size, fill);
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
function getFiletype(input: string): FileType {
|
|
@@ -11,31 +11,33 @@ import { EmptyValue } from "./EmptyValue";
|
|
|
11
11
|
* @group Preview components
|
|
12
12
|
*/
|
|
13
13
|
export function UrlComponentPreview({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
url,
|
|
15
|
+
previewType,
|
|
16
|
+
size,
|
|
17
|
+
hint,
|
|
18
|
+
interactive = true,
|
|
19
|
+
fill
|
|
20
|
+
}: {
|
|
20
21
|
url: string,
|
|
21
22
|
previewType?: PreviewType,
|
|
22
23
|
size: PreviewSize,
|
|
23
24
|
hint?: string,
|
|
24
25
|
// for video controls
|
|
25
|
-
interactive?: boolean
|
|
26
|
+
interactive?: boolean,
|
|
27
|
+
fill?: boolean
|
|
26
28
|
}): React.ReactElement {
|
|
27
29
|
|
|
28
30
|
if (!previewType) {
|
|
29
|
-
if (!url || !url.trim()) return <EmptyValue/>;
|
|
31
|
+
if (!url || !url.trim()) return <EmptyValue />;
|
|
30
32
|
return (
|
|
31
33
|
<a className="flex gap-4 break-words items-center font-medium text-primary visited:text-primary dark:visited:text-primary dark:text-primary"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<OpenInNewIcon size={"small"}/>
|
|
34
|
+
href={url}
|
|
35
|
+
rel="noopener noreferrer"
|
|
36
|
+
onMouseDown={(e: React.MouseEvent) => {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
}}
|
|
39
|
+
target="_blank">
|
|
40
|
+
<OpenInNewIcon size={"small"} />
|
|
39
41
|
{url}
|
|
40
42
|
</a>
|
|
41
43
|
);
|
|
@@ -43,16 +45,17 @@ export function UrlComponentPreview({
|
|
|
43
45
|
|
|
44
46
|
if (previewType === "image") {
|
|
45
47
|
return <ImagePreview url={url}
|
|
46
|
-
|
|
48
|
+
size={size}
|
|
49
|
+
fill={fill} />;
|
|
47
50
|
} else if (previewType === "audio") {
|
|
48
51
|
return <audio controls
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
className={"max-w-100%"}
|
|
53
|
+
src={url}>
|
|
51
54
|
Your browser does not support the
|
|
52
55
|
<code>audio</code> element.
|
|
53
56
|
</audio>;
|
|
54
57
|
} else if (previewType === "video") {
|
|
55
|
-
return <VideoPreview size={size} src={url} interactive={interactive}/>;
|
|
58
|
+
return <VideoPreview size={size} src={url} interactive={interactive} />;
|
|
56
59
|
} else {
|
|
57
60
|
return (
|
|
58
61
|
<Tooltip title={hint}>
|
|
@@ -66,7 +69,7 @@ export function UrlComponentPreview({
|
|
|
66
69
|
width: getThumbnailMeasure(size),
|
|
67
70
|
height: getThumbnailMeasure(size)
|
|
68
71
|
}}>
|
|
69
|
-
<DescriptionIcon className="text-surface-700 dark:text-surface-300"/>
|
|
72
|
+
<DescriptionIcon className="text-surface-700 dark:text-surface-300" />
|
|
70
73
|
{hint && <Typography
|
|
71
74
|
className="max-w-full truncate rtl text-left"
|
|
72
75
|
variant={"caption"}>{hint}</Typography>}
|
|
@@ -77,10 +80,10 @@ export function UrlComponentPreview({
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
function VideoPreview({
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
83
|
+
size,
|
|
84
|
+
src,
|
|
85
|
+
interactive
|
|
86
|
+
}: { size: PreviewSize, src: string, interactive: boolean }) {
|
|
84
87
|
|
|
85
88
|
const imageSize = useMemo(() => {
|
|
86
89
|
if (size === "small")
|
|
@@ -106,6 +109,6 @@ function VideoPreview({
|
|
|
106
109
|
}}
|
|
107
110
|
{...videoProps}
|
|
108
111
|
className={cls("max-w-100% rounded", { "pointer-events-none": !interactive })}>
|
|
109
|
-
<source src={src}/>
|
|
112
|
+
<source src={src} />
|
|
110
113
|
</video>;
|
|
111
114
|
}
|
|
@@ -12,12 +12,12 @@ import { ErrorBoundary } from "../../components";
|
|
|
12
12
|
* @group Preview components
|
|
13
13
|
*/
|
|
14
14
|
export function ArrayOfStorageComponentsPreview({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
propertyKey,
|
|
16
|
+
// entity,
|
|
17
|
+
value,
|
|
18
|
+
property: inputProperty,
|
|
19
|
+
size
|
|
20
|
+
}: PropertyPreviewProps<any[]>) {
|
|
21
21
|
|
|
22
22
|
const authController = useAuthController();
|
|
23
23
|
const customizationController = useCustomizationController();
|
|
@@ -28,6 +28,8 @@ export function ArrayOfStorageComponentsPreview({
|
|
|
28
28
|
authController
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
if (!property) return null;
|
|
32
|
+
|
|
31
33
|
if (Array.isArray(property.of)) {
|
|
32
34
|
throw Error("Using array properties instead of single one in `of` in ArrayProperty");
|
|
33
35
|
}
|
|
@@ -47,7 +49,7 @@ export function ArrayOfStorageComponentsPreview({
|
|
|
47
49
|
value={v}
|
|
48
50
|
// entity={entity}
|
|
49
51
|
property={property.of as ResolvedProperty<string>}
|
|
50
|
-
size={childSize}/>
|
|
52
|
+
size={childSize} />
|
|
51
53
|
</ErrorBoundary>
|
|
52
54
|
)}
|
|
53
55
|
</div>
|
|
@@ -10,12 +10,12 @@ import { ErrorBoundary } from "../../components";
|
|
|
10
10
|
* @group Preview components
|
|
11
11
|
*/
|
|
12
12
|
export function ArrayOfStringsPreview({
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
propertyKey,
|
|
14
|
+
value,
|
|
15
|
+
property: inputProperty,
|
|
16
|
+
// entity,
|
|
17
|
+
size
|
|
18
|
+
}: PropertyPreviewProps<string[]>) {
|
|
19
19
|
const authController = useAuthController();
|
|
20
20
|
const customizationController = useCustomizationController();
|
|
21
21
|
const property = resolveArrayProperty({
|
|
@@ -25,6 +25,8 @@ export function ArrayOfStringsPreview({
|
|
|
25
25
|
authController
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
if (!property) return null;
|
|
29
|
+
|
|
28
30
|
if (Array.isArray(property.of)) {
|
|
29
31
|
throw Error("Using array properties instead of single one in `of` in ArrayProperty");
|
|
30
32
|
}
|
|
@@ -43,10 +45,10 @@ export function ArrayOfStringsPreview({
|
|
|
43
45
|
<div key={`preview_array_strings_${propertyKey}_${index}`}>
|
|
44
46
|
<ErrorBoundary>
|
|
45
47
|
<StringPropertyPreview propertyKey={propertyKey}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
property={stringProperty}
|
|
49
|
+
value={v}
|
|
48
50
|
// entity={entity}
|
|
49
|
-
|
|
51
|
+
size={size} />
|
|
50
52
|
</ErrorBoundary>
|
|
51
53
|
</div>
|
|
52
54
|
)}
|