@firecms/core 3.0.0-alpha.20 → 3.0.0-alpha.24
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/components/TextareaAutosize.d.ts +2 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/core/components/EntityCollectionTable/EntityCollectionTableProps.d.ts +7 -1
- package/dist/core/components/EntityCollectionTable/fields/TableReferenceField.d.ts +4 -2
- package/dist/core/components/EntityCollectionTable/useEntityCollectionTableController.d.ts +1 -1
- package/dist/core/components/VirtualTable/VirtualTableHeader.d.ts +1 -0
- package/dist/core/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/core/components/VirtualTable/VirtualTableProps.d.ts +12 -0
- package/dist/core/components/VirtualTable/types.d.ts +2 -0
- package/dist/core/form_field_configs.d.ts +1 -1
- package/dist/core/util/index.d.ts +1 -0
- package/dist/core/util/make_properties_editable.d.ts +3 -0
- package/dist/core/util/property_utils.d.ts +2 -1
- package/dist/index.es.js +8605 -8489
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +105 -105
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +1 -0
- package/dist/types/firecms.d.ts +14 -5
- package/dist/types/firecms_context.d.ts +8 -0
- package/dist/types/plugins.d.ts +22 -0
- package/package.json +137 -136
- package/src/components/BooleanSwitch.tsx +1 -1
- package/src/components/BooleanSwitchWithLabel.tsx +2 -2
- package/src/components/CircularProgress.tsx +1 -1
- package/src/components/Select.tsx +0 -12
- package/src/components/TextField.tsx +5 -6
- package/src/components/TextareaAutosize.tsx +8 -4
- package/src/components/index.tsx +2 -0
- package/src/core/FireCMS.tsx +5 -3
- package/src/core/components/EntityCollectionTable/EntityCollectionTable.tsx +11 -3
- package/src/core/components/EntityCollectionTable/EntityCollectionTableProps.tsx +11 -1
- package/src/core/components/EntityCollectionTable/fields/TableReferenceField.tsx +25 -12
- package/src/core/components/EntityCollectionTable/internal/PropertyTableCell.tsx +4 -2
- package/src/core/components/EntityCollectionTable/useEntityCollectionTableController.tsx +1 -1
- package/src/core/components/EntityCollectionView/EntityCollectionView.tsx +55 -3
- package/src/core/components/VirtualTable/VirtualTable.tsx +19 -7
- package/src/core/components/VirtualTable/VirtualTableHeader.tsx +26 -24
- package/src/core/components/VirtualTable/VirtualTableHeaderRow.tsx +7 -2
- package/src/core/components/VirtualTable/VirtualTableProps.tsx +14 -0
- package/src/core/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/core/components/VirtualTable/types.tsx +2 -0
- package/src/core/util/index.ts +1 -0
- package/src/core/util/make_properties_editable.ts +28 -0
- package/src/core/util/property_utils.tsx +6 -1
- package/src/form/components/LabelWithIcon.tsx +2 -3
- package/src/form/field_bindings/TextFieldBinding.tsx +1 -2
- package/src/preview/components/ReferencePreview.tsx +23 -3
- package/src/types/collections.ts +2 -0
- package/src/types/firecms.tsx +18 -6
- package/src/types/firecms_context.tsx +9 -0
- package/src/types/plugins.tsx +24 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import React, { useCallback, useState } from "react";
|
|
2
2
|
import { ReferencePreview, ReferencePreviewContainer } from "../../../../preview";
|
|
3
|
-
import { CollectionSize, Entity, EntityReference, FilterValues } from "../../../../types";
|
|
3
|
+
import { CollectionSize, Entity, EntityCollection, EntityReference, FilterValues } from "../../../../types";
|
|
4
4
|
|
|
5
5
|
import { getPreviewSizeFrom } from "../../../../preview/util";
|
|
6
6
|
import { getReferenceFrom } from "../../../util";
|
|
7
|
-
import { useNavigationContext, useReferenceDialog } from "../../../../hooks";
|
|
7
|
+
import { useFireCMSContext, useNavigationContext, useReferenceDialog } from "../../../../hooks";
|
|
8
8
|
import { ErrorView } from "../../ErrorView";
|
|
9
9
|
import { Button } from "../../../../components/Button";
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
type TableReferenceFieldProps = {
|
|
12
12
|
name: string;
|
|
13
13
|
disabled: boolean;
|
|
14
14
|
internalValue: EntityReference | EntityReference[] | undefined | null;
|
|
@@ -19,8 +19,27 @@ export function TableReferenceField(props: {
|
|
|
19
19
|
title?: string;
|
|
20
20
|
path: string;
|
|
21
21
|
forceFilter?: FilterValues<string>;
|
|
22
|
-
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function TableReferenceField(props: TableReferenceFieldProps) {
|
|
25
|
+
|
|
26
|
+
const context = useFireCMSContext();
|
|
27
|
+
const navigationContext = useNavigationContext();
|
|
28
|
+
const { path } = props;
|
|
29
|
+
const collection = navigationContext.getCollection<EntityCollection>(path);
|
|
30
|
+
if (!collection) {
|
|
31
|
+
if (context.components?.missingReference) {
|
|
32
|
+
return <context.components.missingReference path={path}/>;
|
|
33
|
+
} else {
|
|
34
|
+
throw Error(`Couldn't find the corresponding collection view for the path: ${path}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return <TableReferenceFieldSuccess {...props} collection={collection}/>;
|
|
38
|
+
}
|
|
23
39
|
|
|
40
|
+
function TableReferenceFieldSuccess(props: TableReferenceFieldProps & {
|
|
41
|
+
collection: EntityCollection;
|
|
42
|
+
}) {
|
|
24
43
|
const {
|
|
25
44
|
name,
|
|
26
45
|
internalValue,
|
|
@@ -31,7 +50,8 @@ export function TableReferenceField(props: {
|
|
|
31
50
|
previewProperties,
|
|
32
51
|
title,
|
|
33
52
|
disabled,
|
|
34
|
-
forceFilter
|
|
53
|
+
forceFilter,
|
|
54
|
+
collection
|
|
35
55
|
} = props;
|
|
36
56
|
|
|
37
57
|
const [onHover, setOnHover] = useState(false);
|
|
@@ -39,13 +59,6 @@ export function TableReferenceField(props: {
|
|
|
39
59
|
const hoverTrue = useCallback(() => setOnHover(true), []);
|
|
40
60
|
const hoverFalse = useCallback(() => setOnHover(false), []);
|
|
41
61
|
|
|
42
|
-
const navigationContext = useNavigationContext();
|
|
43
|
-
|
|
44
|
-
const collection = navigationContext.getCollection(path);
|
|
45
|
-
if (!collection) {
|
|
46
|
-
throw Error(`Couldn't find the corresponding collection view for the path: ${path}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
62
|
const onSingleEntitySelected = useCallback((entity: Entity<any>) => {
|
|
50
63
|
updateValue(entity ? getReferenceFrom(entity) : null);
|
|
51
64
|
}, [updateValue]);
|
|
@@ -128,11 +128,14 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any, any>>(
|
|
|
128
128
|
);
|
|
129
129
|
|
|
130
130
|
const saveValues = (value: any) => {
|
|
131
|
+
if (equal(value, internalValueRef.current))
|
|
132
|
+
return;
|
|
131
133
|
setSaved(false);
|
|
132
134
|
validation
|
|
133
135
|
.validate(value)
|
|
134
136
|
.then(() => {
|
|
135
137
|
setError(undefined);
|
|
138
|
+
internalValueRef.current = value;
|
|
136
139
|
if (onValueChange) {
|
|
137
140
|
onValueChange({
|
|
138
141
|
value,
|
|
@@ -167,7 +170,6 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any, any>>(
|
|
|
167
170
|
} else {
|
|
168
171
|
updatedValue = newValue;
|
|
169
172
|
}
|
|
170
|
-
internalValueRef.current = updatedValue;
|
|
171
173
|
setInternalValue(updatedValue);
|
|
172
174
|
saveValues(updatedValue);
|
|
173
175
|
};
|
|
@@ -234,7 +236,7 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any, any>>(
|
|
|
234
236
|
propertyKey={propertyKey}
|
|
235
237
|
property={property}
|
|
236
238
|
entity={entity}
|
|
237
|
-
value={
|
|
239
|
+
value={internalValue}
|
|
238
240
|
size={getPreviewSizeFrom(size)}
|
|
239
241
|
/>
|
|
240
242
|
</EntityTableCell>;
|
|
@@ -33,7 +33,7 @@ export function useEntityCollectionTableController<M extends Record<string, any>
|
|
|
33
33
|
collection,
|
|
34
34
|
entitiesDisplayedFirst,
|
|
35
35
|
lastDeleteTimestamp,
|
|
36
|
-
forceFilter: forceFilterFromProps
|
|
36
|
+
forceFilter: forceFilterFromProps,
|
|
37
37
|
}: EntityCollectionTableControllerProps<M>)
|
|
38
38
|
: TableController<M> {
|
|
39
39
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
FilterValues,
|
|
11
11
|
PartialEntityCollection,
|
|
12
12
|
PropertyOrBuilder,
|
|
13
|
+
ResolvedProperty,
|
|
13
14
|
SaveEntityProps,
|
|
14
15
|
SelectionController
|
|
15
16
|
} from "../../../types";
|
|
@@ -228,6 +229,17 @@ export const EntityCollectionView = React.memo(
|
|
|
228
229
|
setLastDeleteTimestamp(Date.now());
|
|
229
230
|
}, [setSelectedEntities]);
|
|
230
231
|
|
|
232
|
+
let AddColumnComponent: React.ComponentType<{
|
|
233
|
+
fullPath: string,
|
|
234
|
+
parentPathSegments: string[],
|
|
235
|
+
collection: EntityCollection;
|
|
236
|
+
}> | undefined
|
|
237
|
+
|
|
238
|
+
// we are only using the first plugin that implements this
|
|
239
|
+
if (context?.plugins) {
|
|
240
|
+
AddColumnComponent = context.plugins.find(plugin => plugin.collectionView?.AddColumnComponent)?.collectionView?.AddColumnComponent;
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
const onCollectionModifiedForUser = useCallback((path: string, partialCollection: PartialEntityCollection<M>) => {
|
|
232
244
|
if (userConfigPersistence) {
|
|
233
245
|
const currentStoredConfig = userConfigPersistence.getCollectionConfig(path);
|
|
@@ -305,7 +317,11 @@ export const EntityCollectionView = React.memo(
|
|
|
305
317
|
fields: context.fields
|
|
306
318
|
}), [collection, fullPath]);
|
|
307
319
|
|
|
308
|
-
const getPropertyFor = useCallback(({
|
|
320
|
+
const getPropertyFor = useCallback(({
|
|
321
|
+
propertyKey,
|
|
322
|
+
propertyValue,
|
|
323
|
+
entity
|
|
324
|
+
}: GetPropertyForProps<M>) => {
|
|
309
325
|
let propertyOrBuilder: PropertyOrBuilder<any, M> | undefined = getPropertyInPath<M>(collection.properties, propertyKey);
|
|
310
326
|
|
|
311
327
|
// we might not find the property in the collection if combining property builders and map spread
|
|
@@ -485,6 +501,42 @@ export const EntityCollectionView = React.memo(
|
|
|
485
501
|
|
|
486
502
|
</Popover>;
|
|
487
503
|
|
|
504
|
+
function buildAdditionalHeaderWidget({
|
|
505
|
+
property,
|
|
506
|
+
propertyKey,
|
|
507
|
+
onHover
|
|
508
|
+
}: {
|
|
509
|
+
property: ResolvedProperty,
|
|
510
|
+
propertyKey: string,
|
|
511
|
+
onHover: boolean
|
|
512
|
+
}) {
|
|
513
|
+
if (!context.plugins)
|
|
514
|
+
return null;
|
|
515
|
+
return <>
|
|
516
|
+
{context.plugins.filter(plugin => plugin.collectionView?.HeaderAction)
|
|
517
|
+
.map((plugin, i) => {
|
|
518
|
+
const HeaderAction = plugin.collectionView!.HeaderAction!;
|
|
519
|
+
return <HeaderAction
|
|
520
|
+
key={`plugin_header_action_${i}`}
|
|
521
|
+
propertyKey={propertyKey}
|
|
522
|
+
property={property}
|
|
523
|
+
onHover={onHover}
|
|
524
|
+
fullPath={fullPath}
|
|
525
|
+
parentPathSegments={parentPathSegments ?? []}/>;
|
|
526
|
+
})}
|
|
527
|
+
</>;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const addColumnComponentInternal = AddColumnComponent
|
|
531
|
+
? function () {
|
|
532
|
+
if (typeof AddColumnComponent === "function")
|
|
533
|
+
return <AddColumnComponent fullPath={fullPath}
|
|
534
|
+
parentPathSegments={parentPathSegments ?? []}
|
|
535
|
+
collection={collection}/>;
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
: undefined;
|
|
539
|
+
|
|
488
540
|
return (
|
|
489
541
|
<div className={cn("overflow-hidden h-full w-full", className)}
|
|
490
542
|
ref={containerRef}>
|
|
@@ -519,6 +571,8 @@ export const EntityCollectionView = React.memo(
|
|
|
519
571
|
/>}
|
|
520
572
|
hoverRow={hoverRow}
|
|
521
573
|
inlineEditing={checkInlineEditing()}
|
|
574
|
+
additionalHeaderWidget={buildAdditionalHeaderWidget}
|
|
575
|
+
AddColumnComponent={addColumnComponentInternal}
|
|
522
576
|
/>
|
|
523
577
|
|
|
524
578
|
<PopupFormField
|
|
@@ -632,5 +686,3 @@ function buildPropertyWidthOverwrite(key: string, width: number): PartialEntityC
|
|
|
632
686
|
}
|
|
633
687
|
return { properties: { [key]: { columnWidth: width } } } as PartialEntityCollection;
|
|
634
688
|
}
|
|
635
|
-
|
|
636
|
-
|
|
@@ -26,7 +26,10 @@ import { AssignmentIcon } from "../../../icons";
|
|
|
26
26
|
const VirtualListContext = createContext<VirtualTableContextProps<any>>({} as any);
|
|
27
27
|
VirtualListContext.displayName = "VirtualListContext";
|
|
28
28
|
|
|
29
|
-
type InnerElementProps = {
|
|
29
|
+
type InnerElementProps = {
|
|
30
|
+
children: React.ReactNode,
|
|
31
|
+
style: any
|
|
32
|
+
};
|
|
30
33
|
|
|
31
34
|
// eslint-disable-next-line react/display-name
|
|
32
35
|
const innerElementType = forwardRef<HTMLDivElement, InnerElementProps>(({
|
|
@@ -107,7 +110,8 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
107
110
|
createFilterField,
|
|
108
111
|
rowClassName,
|
|
109
112
|
className,
|
|
110
|
-
endAdornment
|
|
113
|
+
endAdornment,
|
|
114
|
+
AddColumnComponent
|
|
111
115
|
}: VirtualTableProps<T>) {
|
|
112
116
|
|
|
113
117
|
const sortByProperty: string | undefined = sortBy ? sortBy[0] : undefined;
|
|
@@ -186,11 +190,11 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
186
190
|
|
|
187
191
|
const maxScroll = Math.max((data?.length ?? 0) * getRowHeight(size) - bounds.height, 0);
|
|
188
192
|
const onEndReachedInternal = useCallback((scrollOffset: number) => {
|
|
189
|
-
if (onEndReached && (data?.length ?? 0) > 0 && scrollOffset > endReachCallbackThreshold.current +
|
|
193
|
+
if (onEndReached && (data?.length ?? 0) > 0 && scrollOffset > endReachCallbackThreshold.current + 600) {
|
|
190
194
|
endReachCallbackThreshold.current = scrollOffset;
|
|
191
195
|
onEndReached();
|
|
192
196
|
}
|
|
193
|
-
}, [data, onEndReached]);
|
|
197
|
+
}, [data?.length, onEndReached]);
|
|
194
198
|
|
|
195
199
|
const onScroll = useCallback(({
|
|
196
200
|
scrollOffset,
|
|
@@ -200,7 +204,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
200
204
|
scrollOffset: number,
|
|
201
205
|
scrollUpdateWasRequested: boolean;
|
|
202
206
|
}) => {
|
|
203
|
-
if (!scrollUpdateWasRequested && (scrollOffset >= maxScroll -
|
|
207
|
+
if (!scrollUpdateWasRequested && (scrollOffset >= maxScroll - 600))
|
|
204
208
|
onEndReachedInternal(scrollOffset);
|
|
205
209
|
}, [maxScroll, onEndReachedInternal]);
|
|
206
210
|
|
|
@@ -278,7 +282,8 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
278
282
|
hoverRow: hoverRow ?? false,
|
|
279
283
|
createFilterField,
|
|
280
284
|
rowClassName,
|
|
281
|
-
endAdornment
|
|
285
|
+
endAdornment,
|
|
286
|
+
AddColumnComponent
|
|
282
287
|
}}>
|
|
283
288
|
|
|
284
289
|
<MemoizedList
|
|
@@ -288,6 +293,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
288
293
|
height={bounds.height}
|
|
289
294
|
itemCount={(data?.length ?? 0) + (endAdornment ? 1 : 0)}
|
|
290
295
|
onScroll={onScroll}
|
|
296
|
+
includeAddColumn={Boolean(AddColumnComponent)}
|
|
291
297
|
itemSize={getRowHeight(size)}/>
|
|
292
298
|
|
|
293
299
|
</VirtualListContext.Provider>
|
|
@@ -303,7 +309,8 @@ function MemoizedList({
|
|
|
303
309
|
height,
|
|
304
310
|
itemCount,
|
|
305
311
|
onScroll,
|
|
306
|
-
itemSize
|
|
312
|
+
itemSize,
|
|
313
|
+
includeAddColumn
|
|
307
314
|
}: {
|
|
308
315
|
outerRef: RefObject<HTMLDivElement>;
|
|
309
316
|
width: number;
|
|
@@ -315,6 +322,7 @@ function MemoizedList({
|
|
|
315
322
|
scrollUpdateWasRequested: boolean;
|
|
316
323
|
}) => void;
|
|
317
324
|
itemSize: number;
|
|
325
|
+
includeAddColumn?: boolean;
|
|
318
326
|
}) {
|
|
319
327
|
|
|
320
328
|
const Row = useCallback(({
|
|
@@ -360,6 +368,7 @@ function MemoizedList({
|
|
|
360
368
|
top: `calc(${style.top}px + 48px)`
|
|
361
369
|
}}
|
|
362
370
|
size={size}>
|
|
371
|
+
|
|
363
372
|
{columns.map((column: VirtualTableColumn, columnIndex: number) => {
|
|
364
373
|
const cellData = rowData && rowData[column.key];
|
|
365
374
|
return <VirtualTableCell
|
|
@@ -373,6 +382,9 @@ function MemoizedList({
|
|
|
373
382
|
rowIndex={index}
|
|
374
383
|
columnIndex={columnIndex}/>;
|
|
375
384
|
})}
|
|
385
|
+
|
|
386
|
+
{includeAddColumn && <div className={"w-20"}/>}
|
|
387
|
+
|
|
376
388
|
</VirtualTableRow>
|
|
377
389
|
);
|
|
378
390
|
}}
|
|
@@ -3,12 +3,9 @@ import equal from "react-fast-compare";
|
|
|
3
3
|
|
|
4
4
|
import { VirtualTableColumn, VirtualTableSort, VirtualTableWhereFilterOp } from "./VirtualTableProps";
|
|
5
5
|
import { ErrorBoundary } from "../ErrorBoundary";
|
|
6
|
-
import { Button, IconButton } from "../../../components";
|
|
6
|
+
import { Badge, Button, cn, IconButton, Popover } from "../../../components";
|
|
7
7
|
import { defaultBorderMixin } from "../../../styles";
|
|
8
|
-
import { ArrowUpwardIcon, ExpandMoreIcon } from "../../../icons";
|
|
9
|
-
import { Popover } from "../../../components/Popover";
|
|
10
|
-
import { Badge } from "../../../components/Badge";
|
|
11
|
-
import { cn } from "../../../components/util/cn";
|
|
8
|
+
import { ArrowUpwardIcon, ExpandMoreIcon, FilterIcon, FilterListIcon } from "../../../icons";
|
|
12
9
|
|
|
13
10
|
interface FilterFormProps<T> {
|
|
14
11
|
column: VirtualTableColumn<T>;
|
|
@@ -40,6 +37,7 @@ type VirtualTableHeaderProps<M extends Record<string, any>> = {
|
|
|
40
37
|
onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, any]) => void;
|
|
41
38
|
onClickResizeColumn?: (columnIndex: number, column: VirtualTableColumn) => void;
|
|
42
39
|
createFilterField?: (props: FilterFormFieldProps<any>) => React.ReactNode;
|
|
40
|
+
additionalHeaderWidget?: (onHover: boolean) => React.ReactNode;
|
|
43
41
|
};
|
|
44
42
|
|
|
45
43
|
export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
|
|
@@ -53,7 +51,8 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
|
|
|
53
51
|
filter,
|
|
54
52
|
column,
|
|
55
53
|
onClickResizeColumn,
|
|
56
|
-
createFilterField
|
|
54
|
+
createFilterField,
|
|
55
|
+
additionalHeaderWidget
|
|
57
56
|
}: VirtualTableHeaderProps<M>) {
|
|
58
57
|
|
|
59
58
|
const [onHover, setOnHover] = useState(false);
|
|
@@ -115,23 +114,26 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
|
|
|
115
114
|
</div>
|
|
116
115
|
|
|
117
116
|
{column.sortable && (sort || hovered || openFilter) &&
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
<>
|
|
118
|
+
{additionalHeaderWidget && additionalHeaderWidget(hovered || openFilter)}
|
|
119
|
+
<Badge color="secondary"
|
|
120
|
+
invisible={!sort}>
|
|
121
|
+
<IconButton
|
|
122
|
+
size={"small"}
|
|
123
|
+
className={onHover || openFilter ? "bg-white dark:bg-gray-950" : undefined}
|
|
124
|
+
onClick={() => {
|
|
125
|
+
onColumnSort(column.key as Extract<keyof M, string>);
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
{!sort &&
|
|
129
|
+
<ArrowUpwardIcon/>}
|
|
130
|
+
{sort === "asc" &&
|
|
131
|
+
<ArrowUpwardIcon/>}
|
|
132
|
+
{sort === "desc" &&
|
|
133
|
+
<ArrowUpwardIcon className={"rotate-180"}/>}
|
|
134
|
+
</IconButton>
|
|
135
|
+
</Badge>
|
|
136
|
+
</>
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
{column.filter && createFilterField && <div>
|
|
@@ -146,7 +148,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
|
|
|
146
148
|
className={onHover || openFilter ? "bg-white dark:bg-gray-950" : undefined}
|
|
147
149
|
size={"small"}
|
|
148
150
|
onClick={handleSettingsClick}>
|
|
149
|
-
<
|
|
151
|
+
<FilterListIcon size={"small"}/>
|
|
150
152
|
</IconButton>}
|
|
151
153
|
>
|
|
152
154
|
{column.filter &&
|
|
@@ -16,7 +16,8 @@ export const VirtualTableHeaderRow = ({
|
|
|
16
16
|
filter,
|
|
17
17
|
onColumnResize,
|
|
18
18
|
onColumnResizeEnd,
|
|
19
|
-
createFilterField
|
|
19
|
+
createFilterField,
|
|
20
|
+
AddColumnComponent
|
|
20
21
|
}: VirtualTableContextProps<any>) => {
|
|
21
22
|
|
|
22
23
|
const columnRefs = columns.map(() => createRef<HTMLDivElement>());
|
|
@@ -119,9 +120,13 @@ export const VirtualTableHeaderRow = ({
|
|
|
119
120
|
onColumnSort={onColumnSort}
|
|
120
121
|
onClickResizeColumn={onClickResizeColumn}
|
|
121
122
|
column={column}
|
|
122
|
-
createFilterField={createFilterField}
|
|
123
|
+
createFilterField={createFilterField}
|
|
124
|
+
additionalHeaderWidget={column.additionalHeaderWidget}/>
|
|
123
125
|
</ErrorBoundary>;
|
|
124
126
|
})}
|
|
127
|
+
|
|
128
|
+
{AddColumnComponent && <AddColumnComponent/>}
|
|
129
|
+
|
|
125
130
|
</div>
|
|
126
131
|
);
|
|
127
132
|
};
|
|
@@ -136,6 +136,12 @@ export interface VirtualTableProps<T extends Record<string, any>> {
|
|
|
136
136
|
*/
|
|
137
137
|
endAdornment?: React.ReactNode;
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* If adding this callback, a button to add a new column is displayed.
|
|
141
|
+
* @param column
|
|
142
|
+
*/
|
|
143
|
+
AddColumnComponent?: React.ComponentType;
|
|
144
|
+
|
|
139
145
|
}
|
|
140
146
|
|
|
141
147
|
export type CellRendererParams<T extends any> = {
|
|
@@ -206,7 +212,15 @@ export interface VirtualTableColumn<CustomProps extends any = any> {
|
|
|
206
212
|
*/
|
|
207
213
|
resizable?: boolean;
|
|
208
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Custom props passed to the cell renderer
|
|
217
|
+
*/
|
|
209
218
|
custom?: CustomProps;
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Additional children to be rendered in the header when hovering
|
|
222
|
+
*/
|
|
223
|
+
additionalHeaderWidget?: (onHover:boolean) => React.ReactNode;
|
|
210
224
|
}
|
|
211
225
|
|
|
212
226
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import {
|
|
2
3
|
CellRendererParams,
|
|
3
4
|
OnRowClickParams,
|
|
@@ -39,4 +40,5 @@ export type VirtualTableContextProps<T extends any> = {
|
|
|
39
40
|
createFilterField?: (props: FilterFormFieldProps<any>) => React.ReactNode;
|
|
40
41
|
rowClassName?: (rowData: T) => string | undefined;
|
|
41
42
|
endAdornment?: React.ReactNode;
|
|
43
|
+
AddColumnComponent?: React.ComponentType;
|
|
42
44
|
};
|
package/src/core/util/index.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Properties, PropertiesOrBuilders } from "../../types";
|
|
2
|
+
import { isPropertyBuilder } from "./entities";
|
|
3
|
+
|
|
4
|
+
export function makePropertiesEditable(properties: Properties) {
|
|
5
|
+
Object.keys(properties).forEach((key) => {
|
|
6
|
+
const property = properties[key];
|
|
7
|
+
property.editable = true;
|
|
8
|
+
if (property.dataType === "map" && property.properties) {
|
|
9
|
+
makePropertiesEditable(property.properties as Properties);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function makePropertiesNonEditable(properties: PropertiesOrBuilders): PropertiesOrBuilders {
|
|
15
|
+
return Object.entries(properties).reduce((acc, [key, property]) => {
|
|
16
|
+
if (!isPropertyBuilder(property) && property.dataType === "map" && property.properties) {
|
|
17
|
+
const updated = { ...property, properties: makePropertiesNonEditable(property.properties as PropertiesOrBuilders) };
|
|
18
|
+
acc[key] = updated;
|
|
19
|
+
}
|
|
20
|
+
if (isPropertyBuilder(property)) {
|
|
21
|
+
acc[key] = property;
|
|
22
|
+
} else {
|
|
23
|
+
acc[key] = { ...property, editable: false };
|
|
24
|
+
}
|
|
25
|
+
return acc;
|
|
26
|
+
}, {} as PropertiesOrBuilders);
|
|
27
|
+
|
|
28
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { FieldConfig, PropertiesOrBuilders, PropertyOrBuilder, ResolvedProperty } from "../../types";
|
|
3
|
+
import { EntityCollection, FieldConfig, PropertiesOrBuilders, PropertyOrBuilder, ResolvedProperty } from "../../types";
|
|
4
4
|
import { getFieldConfig } from "../form_field_configs";
|
|
5
5
|
import { isPropertyBuilder } from "./entities";
|
|
6
6
|
import { resolveProperty } from "./resolutions";
|
|
@@ -123,3 +123,8 @@ export function getPropertiesWithPropertiesOrder<M extends Record<string, any>>(
|
|
|
123
123
|
});
|
|
124
124
|
return result;
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
export function getDefaultPropertiesOrder(collection: EntityCollection<any>): string[] {
|
|
128
|
+
if (collection.propertiesOrder) return collection.propertiesOrder;
|
|
129
|
+
return [...Object.keys(collection.properties), ...(collection.additionalFields ?? []).map(field => field.id)];
|
|
130
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Typography } from "../../components/Typography";
|
|
3
2
|
|
|
4
3
|
interface LabelWithIconProps {
|
|
5
4
|
icon: React.ReactNode;
|
|
@@ -27,8 +26,8 @@ export function LabelWithIcon({
|
|
|
27
26
|
|
|
28
27
|
{icon}
|
|
29
28
|
|
|
30
|
-
<
|
|
31
|
-
|
|
29
|
+
<span
|
|
30
|
+
className={`font-medium text-${small ? "base" : "sm"} origin-top-left transform ${small ? "translate-x-2 scale-75" : ""}`}>{(title ?? "") + (required ? " *" : "")}</span>
|
|
32
31
|
|
|
33
32
|
</span>
|
|
34
33
|
);
|
|
@@ -4,9 +4,8 @@ import { FieldProps, PreviewType } from "../../types";
|
|
|
4
4
|
import { FieldHelperText, LabelWithIcon } from "../components";
|
|
5
5
|
import { useClearRestoreValue } from "../../hooks";
|
|
6
6
|
import { getIconForProperty } from "../../core";
|
|
7
|
-
import { IconButton, TextField } from "../../components";
|
|
7
|
+
import { Collapse, IconButton, TextField } from "../../components";
|
|
8
8
|
import { PropertyPreview } from "../../preview";
|
|
9
|
-
import { Collapse } from "../../components/Collapse";
|
|
10
9
|
import { ClearIcon } from "../../icons";
|
|
11
10
|
|
|
12
11
|
interface TextFieldProps<T extends string | number> extends FieldProps<T> {
|
|
@@ -61,15 +61,35 @@ function ReferencePreviewInternal<M extends Record<string, any>>({
|
|
|
61
61
|
}: ReferencePreviewProps) {
|
|
62
62
|
|
|
63
63
|
const context = useFireCMSContext();
|
|
64
|
-
|
|
65
64
|
const navigationContext = useNavigationContext();
|
|
66
|
-
const sideEntityController = useSideEntityController();
|
|
67
65
|
|
|
68
66
|
const collection = navigationContext.getCollection<EntityCollection<M>>(reference.path);
|
|
69
67
|
if (!collection) {
|
|
70
|
-
|
|
68
|
+
if (context.components?.missingReference) {
|
|
69
|
+
return <context.components.missingReference path={reference.path}/>;
|
|
70
|
+
} else {
|
|
71
|
+
throw Error(`Couldn't find the corresponding collection view for the path: ${reference.path}`);
|
|
72
|
+
}
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
return <ReferencePreviewExisting
|
|
76
|
+
reference={reference}
|
|
77
|
+
collection={collection}
|
|
78
|
+
previewProperties={previewProperties}
|
|
79
|
+
size={size}
|
|
80
|
+
disabled={disabled}
|
|
81
|
+
allowEntityNavigation={allowEntityNavigation}
|
|
82
|
+
onClick={onClick}
|
|
83
|
+
onHover={onHover}/>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ReferencePreviewExisting<M extends Record<string, any> = any>({ reference, collection, previewProperties, size, disabled, allowEntityNavigation, onClick, onHover }: ReferencePreviewProps & {
|
|
87
|
+
collection: EntityCollection<M>
|
|
88
|
+
}) {
|
|
89
|
+
|
|
90
|
+
const context = useFireCMSContext();
|
|
91
|
+
const sideEntityController = useSideEntityController();
|
|
92
|
+
|
|
73
93
|
const {
|
|
74
94
|
entity,
|
|
75
95
|
dataLoading,
|
package/src/types/collections.ts
CHANGED
|
@@ -477,6 +477,8 @@ export type TableController<M extends Record<string, any> = any> = {
|
|
|
477
477
|
sortBy?: [string, "asc" | "desc"]) => boolean;
|
|
478
478
|
popupCell?: SelectedCellProps<M>;
|
|
479
479
|
setPopupCell?: (popupCell?: SelectedCellProps<M>) => void;
|
|
480
|
+
|
|
481
|
+
onAddColumn?: (column: string) => void;
|
|
480
482
|
}
|
|
481
483
|
|
|
482
484
|
export type SelectedCellProps<M extends Record<string, any>> = {
|
package/src/types/firecms.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from "react";
|
|
1
2
|
import { User } from "./user";
|
|
2
3
|
import { AuthController } from "./auth";
|
|
3
4
|
import { DataSource } from "./datasource";
|
|
@@ -123,12 +124,6 @@ export type FireCMSProps<UserType extends User> = {
|
|
|
123
124
|
*/
|
|
124
125
|
authController: AuthController<UserType>;
|
|
125
126
|
|
|
126
|
-
/**
|
|
127
|
-
* Optional link builder you can add to generate a button in your entity forms.
|
|
128
|
-
* The function must return a URL that gets opened when the button is clicked
|
|
129
|
-
*/
|
|
130
|
-
entityLinkBuilder?: EntityLinkBuilder;
|
|
131
|
-
|
|
132
127
|
/**
|
|
133
128
|
* Path under the navigation routes of the CMS will be created. Defaults to `/`.
|
|
134
129
|
* Internally FireCMS uses `react-router` to create the routes, the base path is attached to the
|
|
@@ -160,4 +155,21 @@ export type FireCMSProps<UserType extends User> = {
|
|
|
160
155
|
*/
|
|
161
156
|
onAnalyticsEvent?: (event: CMSAnalyticsEvent, data?: object) => void;
|
|
162
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Optional link builder you can add to generate a button in your entity forms.
|
|
160
|
+
* The function must return a URL that gets opened when the button is clicked
|
|
161
|
+
*/
|
|
162
|
+
entityLinkBuilder?: EntityLinkBuilder;
|
|
163
|
+
|
|
164
|
+
components?: {
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Component to render when a reference is missing
|
|
168
|
+
*/
|
|
169
|
+
missingReference?: React.ComponentType<{
|
|
170
|
+
path: string,
|
|
171
|
+
}>;
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
163
175
|
};
|
|
@@ -108,4 +108,13 @@ export type FireCMSContext<UserType extends User = User, AuthControllerType exte
|
|
|
108
108
|
* You can also define an entity view from the UI.
|
|
109
109
|
*/
|
|
110
110
|
entityViews?: EntityCustomView[];
|
|
111
|
+
|
|
112
|
+
components?: {
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Component to render when a reference is missing
|
|
116
|
+
*/
|
|
117
|
+
missingReference?: React.ComponentType<{ path: string }>;
|
|
118
|
+
|
|
119
|
+
}
|
|
111
120
|
};
|