@firecms/core 3.0.1 → 3.1.0-canary.02232f4
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/EntityCollectionTable/internal/popup_field/useDraggable.d.ts +2 -2
- 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/CollectionDataErrorBanner.d.ts +4 -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 +44 -0
- package/dist/components/EntityCollectionView/board_types.d.ts +105 -0
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +60 -0
- package/dist/components/ErrorBoundary.d.ts +4 -2
- package/dist/components/HomePage/DefaultHomePage.d.ts +0 -1
- package/dist/components/LanguageToggle.d.ts +1 -0
- package/dist/components/SelectableTable/SelectableTable.d.ts +5 -1
- package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
- package/dist/components/UnsavedChangesDialog.d.ts +1 -0
- package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -0
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +4 -1
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +17 -1
- package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -0
- package/dist/components/VirtualTable/types.d.ts +3 -0
- package/dist/components/index.d.ts +4 -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/editor/components/SlashCommandMenu.d.ts +6 -0
- package/dist/editor/components/editor-bubble-item.d.ts +8 -0
- package/dist/editor/components/editor-bubble.d.ts +8 -0
- package/dist/editor/components/image-bubble.d.ts +5 -0
- package/dist/editor/components/index.d.ts +16 -0
- package/dist/editor/components/table-bubble.d.ts +5 -0
- package/dist/editor/editor.d.ts +30 -0
- package/dist/editor/extensions/HighlightDecorationExtension.d.ts +24 -0
- package/dist/editor/extensions/Image/index.d.ts +6 -0
- package/dist/editor/extensions/Image.d.ts +6 -0
- package/dist/editor/extensions/TextLoadingDecorationExtension.d.ts +16 -0
- package/dist/editor/extensions/clipboard.d.ts +7 -0
- package/dist/editor/extensions/custom-keymap.d.ts +1 -0
- package/dist/editor/extensions/drag-and-drop.d.ts +9 -0
- package/dist/editor/hooks/useProseMirror.d.ts +13 -0
- package/dist/editor/hooks/useProseMirrorContext.d.ts +9 -0
- package/dist/editor/index.d.ts +2 -0
- package/dist/editor/markdown.d.ts +5 -0
- package/dist/editor/nodeViews/ImageComponent.d.ts +3 -0
- package/dist/editor/nodeViews/ReactNodeView.d.ts +29 -0
- package/dist/editor/nodeViews/TaskItemComponent.d.ts +3 -0
- package/dist/editor/nodeViews/index.d.ts +6 -0
- package/dist/editor/plugins/index.d.ts +2 -0
- package/dist/editor/plugins/inputrules.d.ts +6 -0
- package/dist/editor/plugins/placeholderPlugin.d.ts +3 -0
- package/dist/editor/plugins/slashCommandPlugin.d.ts +12 -0
- package/dist/editor/schema.d.ts +2 -0
- package/dist/editor/selectors/ai-selector.d.ts +0 -0
- package/dist/editor/selectors/color-selector.d.ts +10 -0
- package/dist/editor/selectors/link-selector.d.ts +8 -0
- package/dist/editor/selectors/node-selector.d.ts +15 -0
- package/dist/editor/selectors/text-buttons.d.ts +1 -0
- package/dist/editor/types.d.ts +5 -0
- package/dist/editor/useProseMirror.d.ts +16 -0
- package/dist/editor/utils/prosemirror-utils.d.ts +6 -0
- package/dist/editor/utils/remove_classes.d.ts +1 -0
- package/dist/editor/utils/useDebouncedCallback.d.ts +1 -0
- package/dist/form/components/ErrorFocus.d.ts +1 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +1 -1
- package/dist/form/validation.d.ts +3 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBreadcrumbsController.d.ts +16 -0
- package/dist/hooks/useBuildNavigationController.d.ts +0 -1
- package/dist/hooks/useCollapsedGroups.d.ts +6 -3
- package/dist/hooks/useTranslation.d.ts +17 -0
- package/dist/i18n/FireCMSi18nProvider.d.ts +33 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +30146 -15178
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +30032 -15085
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useRestoreScroll.d.ts +1 -1
- package/dist/locales/de.d.ts +2 -0
- package/dist/locales/en.d.ts +10 -0
- package/dist/locales/es.d.ts +10 -0
- package/dist/locales/fr.d.ts +2 -0
- package/dist/locales/hi.d.ts +2 -0
- package/dist/locales/it.d.ts +2 -0
- package/dist/locales/pt.d.ts +7 -0
- 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/analytics.d.ts +1 -1
- package/dist/types/collections.d.ts +88 -2
- package/dist/types/customization_controller.d.ts +2 -1
- package/dist/types/datasource.d.ts +0 -1
- package/dist/types/firecms.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/navigation.d.ts +2 -2
- package/dist/types/plugins.d.ts +69 -1
- package/dist/types/properties.d.ts +268 -12
- package/dist/types/storage.d.ts +1 -0
- package/dist/types/translations.d.ts +669 -0
- 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 +2 -3
- package/dist/util/index.d.ts +3 -1
- package/dist/util/lazy_eager.d.ts +7 -0
- package/dist/util/objects.d.ts +1 -0
- package/dist/util/property_utils.d.ts +2 -1
- package/dist/util/resolutions.d.ts +3 -3
- package/dist/util/useStorageUploadController.d.ts +10 -1
- package/package.json +51 -12
- package/src/app/Scaffold.tsx +20 -19
- package/src/components/AIIcon.tsx +41 -0
- package/src/components/ArrayContainer.tsx +7 -8
- package/src/components/ClearFilterSortButton.tsx +25 -19
- package/src/components/ConfirmationDialog.tsx +4 -4
- package/src/components/DeleteEntityDialog.tsx +12 -11
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +82 -43
- 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/fields/TableReferenceField.tsx +6 -3
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +24 -44
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +90 -49
- package/src/components/EntityCollectionTable/internal/EntityTableCellActions.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +3 -2
- package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +11 -11
- 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 +174 -0
- package/src/components/EntityCollectionView/CollectionDataErrorBanner.tsx +43 -0
- package/src/components/EntityCollectionView/EntityBoardCard.tsx +212 -0
- package/src/components/EntityCollectionView/EntityCard.tsx +235 -0
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +706 -0
- package/src/components/EntityCollectionView/EntityCollectionCardView.tsx +236 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +531 -209
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +35 -22
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +86 -15
- package/src/components/EntityCollectionView/FiltersDialog.tsx +252 -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/EntityJsonPreview.tsx +2 -1
- package/src/components/EntityView.tsx +3 -2
- package/src/components/ErrorBoundary.tsx +27 -15
- package/src/components/ErrorTooltip.tsx +2 -1
- package/src/components/HomePage/DefaultHomePage.tsx +65 -22
- package/src/components/HomePage/HomePageDnD.tsx +59 -42
- package/src/components/HomePage/NavigationCard.tsx +20 -18
- package/src/components/HomePage/NavigationGroup.tsx +20 -17
- package/src/components/HomePage/RenameGroupDialog.tsx +15 -15
- package/src/components/HomePage/SmallNavigationCard.tsx +10 -9
- package/src/components/LanguageToggle.tsx +66 -0
- package/src/components/NotFoundPage.tsx +5 -3
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +12 -17
- package/src/components/ReferenceWidget.tsx +5 -6
- package/src/components/SearchIconsView.tsx +3 -1
- package/src/components/SelectableTable/SelectableTable.tsx +75 -67
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +7 -6
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +50 -40
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +53 -40
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +60 -58
- package/src/components/UnsavedChangesDialog.tsx +6 -6
- package/src/components/UserDisplay.tsx +4 -4
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +1 -0
- package/src/components/VirtualTable/VirtualTable.tsx +275 -119
- package/src/components/VirtualTable/VirtualTableCell.tsx +18 -2
- package/src/components/VirtualTable/VirtualTableHeader.tsx +76 -64
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +163 -42
- package/src/components/VirtualTable/VirtualTableProps.tsx +21 -2
- package/src/components/VirtualTable/VirtualTableRow.tsx +1 -1
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +3 -0
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +19 -6
- package/src/components/VirtualTable/types.tsx +3 -0
- package/src/components/common/default_entity_actions.tsx +4 -0
- package/src/components/common/useColumnsIds.tsx +95 -3
- package/src/components/common/useDataSourceTableController.tsx +12 -4
- package/src/components/index.tsx +5 -0
- package/src/contexts/BreacrumbsContext.tsx +15 -8
- package/src/contexts/index.ts +10 -0
- package/src/core/DefaultAppBar.tsx +49 -32
- package/src/core/DefaultDrawer.tsx +49 -57
- package/src/core/DrawerNavigationGroup.tsx +120 -0
- package/src/core/DrawerNavigationItem.tsx +4 -3
- package/src/core/EntityEditView.tsx +94 -50
- package/src/core/EntityEditViewFormActions.tsx +24 -17
- package/src/core/EntitySidePanel.tsx +34 -30
- package/src/core/FireCMS.tsx +33 -6
- package/src/core/SideDialogs.tsx +4 -2
- package/src/core/field_configs.tsx +18 -11
- package/src/core/index.tsx +1 -0
- package/src/editor/components/SlashCommandMenu.tsx +516 -0
- package/src/editor/components/editor-bubble-item.tsx +32 -0
- package/src/editor/components/editor-bubble.tsx +118 -0
- package/src/editor/components/image-bubble.tsx +156 -0
- package/src/editor/components/index.ts +14 -0
- package/src/editor/components/table-bubble.tsx +165 -0
- package/src/editor/editor.tsx +455 -0
- package/src/editor/extensions/HighlightDecorationExtension.ts +114 -0
- package/src/editor/extensions/Image/index.ts +133 -0
- package/src/editor/extensions/Image.ts +159 -0
- package/src/editor/extensions/TextLoadingDecorationExtension.tsx +107 -0
- package/src/editor/extensions/clipboard.ts +72 -0
- package/src/editor/extensions/custom-keymap.ts +24 -0
- package/src/editor/extensions/drag-and-drop.tsx +480 -0
- package/src/editor/hooks/useProseMirror.ts +124 -0
- package/src/editor/hooks/useProseMirrorContext.ts +15 -0
- package/src/editor/index.ts +2 -0
- package/src/editor/markdown.ts +172 -0
- package/src/editor/nodeViews/ImageComponent.tsx +20 -0
- package/src/editor/nodeViews/ReactNodeView.tsx +89 -0
- package/src/editor/nodeViews/TaskItemComponent.tsx +29 -0
- package/src/editor/nodeViews/index.ts +35 -0
- package/src/editor/plugins/index.ts +58 -0
- package/src/editor/plugins/inputrules.ts +82 -0
- package/src/editor/plugins/placeholderPlugin.ts +55 -0
- package/src/editor/plugins/slashCommandPlugin.ts +61 -0
- package/src/editor/schema.ts +240 -0
- package/src/editor/selectors/ai-selector.tsx +111 -0
- package/src/editor/selectors/color-selector.tsx +200 -0
- package/src/editor/selectors/link-selector.tsx +118 -0
- package/src/editor/selectors/node-selector.tsx +157 -0
- package/src/editor/selectors/text-buttons.tsx +86 -0
- package/src/editor/types.ts +6 -0
- package/src/editor/useProseMirror.ts +126 -0
- package/src/editor/utils/prosemirror-utils.ts +108 -0
- package/src/editor/utils/remove_classes.ts +17 -0
- package/src/editor/utils/useDebouncedCallback.ts +25 -0
- package/src/form/EntityForm.tsx +149 -67
- package/src/form/EntityFormActions.tsx +19 -12
- package/src/form/PropertyFieldBinding.tsx +68 -51
- package/src/form/components/ErrorFocus.tsx +3 -3
- package/src/form/components/LocalChangesMenu.tsx +13 -13
- package/src/form/components/StorageItemPreview.tsx +5 -3
- package/src/form/components/StorageUploadProgress.tsx +18 -3
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +22 -10
- package/src/form/field_bindings/BlockFieldBinding.tsx +26 -9
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +18 -17
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +46 -25
- package/src/form/field_bindings/MapFieldBinding.tsx +88 -70
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +93 -52
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
- package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
- package/src/form/field_bindings/RepeatFieldBinding.tsx +21 -6
- package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +110 -92
- package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
- package/src/form/field_bindings/TextFieldBinding.tsx +77 -38
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
- package/src/form/validation.ts +245 -160
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useBreadcrumbsController.tsx +18 -0
- package/src/hooks/useBuildNavigationController.tsx +91 -41
- package/src/hooks/useCollapsedGroups.ts +18 -9
- package/src/hooks/useTranslation.ts +31 -0
- package/src/hooks/useValidateAuthenticator.tsx +1 -1
- package/src/i18n/FireCMSi18nProvider.tsx +160 -0
- package/src/index.ts +5 -0
- package/src/internal/useBuildDataSource.ts +68 -34
- package/src/internal/useBuildSideDialogsController.tsx +11 -8
- package/src/internal/useBuildSideEntityController.tsx +24 -24
- package/src/internal/useRestoreScroll.tsx +26 -14
- package/src/locales/de.ts +718 -0
- package/src/locales/en.ts +730 -0
- package/src/locales/es.ts +730 -0
- package/src/locales/fr.ts +718 -0
- package/src/locales/hi.ts +718 -0
- package/src/locales/it.ts +718 -0
- package/src/locales/pt.ts +727 -0
- package/src/preview/PropertyPreview.tsx +43 -33
- 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/ReferencePreview.tsx +2 -1
- package/src/preview/components/StorageThumbnail.tsx +16 -12
- package/src/preview/components/UrlComponentPreview.tsx +32 -27
- package/src/preview/components/UserPreview.tsx +3 -1
- 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/MapPropertyPreview.tsx +49 -27
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +61 -56
- package/src/routes/CustomCMSRoute.tsx +1 -0
- package/src/routes/FireCMSRoute.tsx +87 -65
- package/src/types/analytics.ts +10 -0
- package/src/types/collections.ts +97 -3
- package/src/types/customization_controller.tsx +2 -1
- package/src/types/datasource.ts +54 -56
- package/src/types/firecms.tsx +2 -1
- package/src/types/index.ts +1 -0
- package/src/types/navigation.ts +2 -2
- package/src/types/plugins.tsx +77 -1
- package/src/types/properties.ts +359 -37
- package/src/types/storage.ts +2 -1
- package/src/types/translations.ts +752 -0
- 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 +29 -30
- package/src/util/entity_cache.ts +2 -1
- package/src/util/index.ts +3 -1
- package/src/util/join_collections.ts +10 -8
- package/src/util/lazy_eager.tsx +33 -0
- package/src/util/objects.ts +46 -13
- package/src/util/{references.ts → previews.ts} +16 -2
- package/src/util/property_utils.tsx +37 -11
- package/src/util/resolutions.ts +62 -58
- package/src/util/useStorageUploadController.tsx +23 -29
- /package/dist/util/{references.d.ts → previews.d.ts} +0 -0
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Entity, EntityCollection, FilterValues } from "../../types";
|
|
3
|
+
import { useDataSource, useFireCMSContext, useNavigationController } from "../../hooks";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Data state for a single board column
|
|
9
|
+
*/
|
|
10
|
+
export interface BoardColumnData<M extends Record<string, any> = any> {
|
|
11
|
+
/** Entities loaded for this column */
|
|
12
|
+
entities: Entity<M>[];
|
|
13
|
+
/** Whether the column is currently loading data */
|
|
14
|
+
loading: boolean;
|
|
15
|
+
/** Whether there are more items to load */
|
|
16
|
+
hasMore: boolean;
|
|
17
|
+
/** Error if loading failed */
|
|
18
|
+
error?: Error;
|
|
19
|
+
/** Total count of entities in this column */
|
|
20
|
+
totalCount?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Controller for managing per-column data in a Kanban board
|
|
25
|
+
*/
|
|
26
|
+
export interface BoardDataController<M extends Record<string, any> = any, COLUMN extends string = string> {
|
|
27
|
+
/** Data state for each column */
|
|
28
|
+
columnData: Record<COLUMN, BoardColumnData<M>>;
|
|
29
|
+
/** Load more items for a specific column */
|
|
30
|
+
loadMoreColumn: (column: COLUMN) => void;
|
|
31
|
+
/** Refresh data for a specific column */
|
|
32
|
+
refreshColumn: (column: COLUMN) => void;
|
|
33
|
+
/** Refresh all columns */
|
|
34
|
+
refreshAll: () => void;
|
|
35
|
+
/** Update counts for columns (for optimistic updates when moving items) */
|
|
36
|
+
updateColumnCounts: (sourceColumn: COLUMN, targetColumn: COLUMN) => void;
|
|
37
|
+
/** Decrement column counts (for optimistic updates when deleting items) */
|
|
38
|
+
decrementColumnCounts: (columnDeltas: Record<COLUMN, number>) => void;
|
|
39
|
+
/** Whether any column is loading */
|
|
40
|
+
loading: boolean;
|
|
41
|
+
/** Any error from any column */
|
|
42
|
+
error?: Error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface UseBoardDataControllerProps<M extends Record<string, any> = any> {
|
|
46
|
+
/** Full path to the collection */
|
|
47
|
+
fullPath: string;
|
|
48
|
+
/** The entity collection configuration */
|
|
49
|
+
collection: EntityCollection<M>;
|
|
50
|
+
/** Property key used for column assignment */
|
|
51
|
+
columnProperty: string;
|
|
52
|
+
/** Array of column values (enum values from columnProperty) */
|
|
53
|
+
columns: string[];
|
|
54
|
+
/** Property key used for ordering within columns */
|
|
55
|
+
orderProperty?: string;
|
|
56
|
+
/** Number of items to load per page per column */
|
|
57
|
+
pageSize?: number;
|
|
58
|
+
/** Text search string to filter entities */
|
|
59
|
+
searchString?: string;
|
|
60
|
+
/** Additional filter values */
|
|
61
|
+
filterValues?: FilterValues<string>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Hook that manages per-column data loading for the Kanban board.
|
|
66
|
+
* Each column gets its own independent query to the data source.
|
|
67
|
+
*/
|
|
68
|
+
export function useBoardDataController<M extends Record<string, any> = any, COLUMN extends string = string>({
|
|
69
|
+
fullPath,
|
|
70
|
+
collection,
|
|
71
|
+
columnProperty,
|
|
72
|
+
columns,
|
|
73
|
+
orderProperty,
|
|
74
|
+
pageSize = DEFAULT_PAGE_SIZE,
|
|
75
|
+
searchString,
|
|
76
|
+
filterValues
|
|
77
|
+
}: UseBoardDataControllerProps<M>): BoardDataController<M, COLUMN> {
|
|
78
|
+
|
|
79
|
+
const context = useFireCMSContext();
|
|
80
|
+
const dataSource = useDataSource(collection);
|
|
81
|
+
const navigation = useNavigationController();
|
|
82
|
+
const resolvedPath = useMemo(() => navigation.resolveIdsFrom(fullPath), [fullPath, navigation.resolveIdsFrom]);
|
|
83
|
+
|
|
84
|
+
// Stable refs for objects that shouldn't trigger re-subscriptions
|
|
85
|
+
const dataSourceRef = useRef(dataSource);
|
|
86
|
+
const collectionRef = useRef(collection);
|
|
87
|
+
const contextRef = useRef(context);
|
|
88
|
+
dataSourceRef.current = dataSource;
|
|
89
|
+
collectionRef.current = collection;
|
|
90
|
+
contextRef.current = context;
|
|
91
|
+
|
|
92
|
+
// Store filter/order params in refs so they're accessible without causing re-subscriptions
|
|
93
|
+
const filterValuesRef = useRef(filterValues);
|
|
94
|
+
const columnPropertyRef = useRef(columnProperty);
|
|
95
|
+
const orderPropertyRef = useRef(orderProperty);
|
|
96
|
+
const searchStringRef = useRef(searchString);
|
|
97
|
+
const resolvedPathRef = useRef(resolvedPath);
|
|
98
|
+
filterValuesRef.current = filterValues;
|
|
99
|
+
columnPropertyRef.current = columnProperty;
|
|
100
|
+
orderPropertyRef.current = orderProperty;
|
|
101
|
+
searchStringRef.current = searchString;
|
|
102
|
+
resolvedPathRef.current = resolvedPath;
|
|
103
|
+
|
|
104
|
+
// Track item count per column for pagination
|
|
105
|
+
const [columnItemCounts, setColumnItemCounts] = useState<Record<string, number>>(() => {
|
|
106
|
+
const initial: Record<string, number> = {};
|
|
107
|
+
columns.forEach(col => {
|
|
108
|
+
initial[col] = pageSize;
|
|
109
|
+
});
|
|
110
|
+
return initial;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Per-column data state
|
|
114
|
+
const [columnData, setColumnData] = useState<Record<string, BoardColumnData<M>>>(() => {
|
|
115
|
+
const initial: Record<string, BoardColumnData<M>> = {};
|
|
116
|
+
columns.forEach(col => {
|
|
117
|
+
initial[col] = {
|
|
118
|
+
entities: [],
|
|
119
|
+
loading: true,
|
|
120
|
+
hasMore: true,
|
|
121
|
+
error: undefined,
|
|
122
|
+
totalCount: undefined
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
return initial;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Track cleanup functions for subscriptions
|
|
129
|
+
const unsubscribersRef = useRef<Record<string, () => void>>({});
|
|
130
|
+
|
|
131
|
+
// Flag to prevent race conditions during cleanup
|
|
132
|
+
const isCleaningUpRef = useRef(false);
|
|
133
|
+
|
|
134
|
+
// Stable keys for dependency comparison
|
|
135
|
+
const columnsKey = useMemo(() => [...columns].sort().join(","), [columns]);
|
|
136
|
+
const filterKey = useMemo(() => JSON.stringify(filterValues), [filterValues]);
|
|
137
|
+
|
|
138
|
+
// Track previous column item counts to detect which column changed
|
|
139
|
+
const prevColumnItemCountsRef = useRef<Record<string, number>>(columnItemCounts);
|
|
140
|
+
|
|
141
|
+
// Version counter to trigger full re-subscription when params change (not just load-more)
|
|
142
|
+
const [subscriptionVersion, setSubscriptionVersion] = useState(0);
|
|
143
|
+
|
|
144
|
+
// Trigger full re-subscription when key params change
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
setSubscriptionVersion(v => v + 1);
|
|
147
|
+
}, [columnsKey, resolvedPath, columnProperty, orderProperty, searchString, filterKey, pageSize]);
|
|
148
|
+
|
|
149
|
+
// Cleanup subscriptions on unmount
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
return () => {
|
|
152
|
+
isCleaningUpRef.current = true;
|
|
153
|
+
Object.values(unsubscribersRef.current).forEach(unsub => unsub?.());
|
|
154
|
+
unsubscribersRef.current = {};
|
|
155
|
+
};
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
// Helper function to subscribe to a single column - uses refs to avoid dependency issues
|
|
159
|
+
const subscribeToColumn = useCallback((column: string, itemCount: number) => {
|
|
160
|
+
// Skip if we're in the middle of cleanup
|
|
161
|
+
if (isCleaningUpRef.current) return;
|
|
162
|
+
|
|
163
|
+
const currentDataSource = dataSourceRef.current;
|
|
164
|
+
const currentCollection = collectionRef.current;
|
|
165
|
+
const currentContext = contextRef.current;
|
|
166
|
+
const currentFilterValues = filterValuesRef.current;
|
|
167
|
+
const currentColumnProperty = columnPropertyRef.current;
|
|
168
|
+
const currentOrderProperty = orderPropertyRef.current;
|
|
169
|
+
const currentSearchString = searchStringRef.current;
|
|
170
|
+
const currentResolvedPath = resolvedPathRef.current;
|
|
171
|
+
|
|
172
|
+
// Build filter for this column
|
|
173
|
+
const columnFilter: FilterValues<string> = {
|
|
174
|
+
...currentFilterValues,
|
|
175
|
+
[currentColumnProperty]: ["==", column]
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Mark column as loading
|
|
179
|
+
setColumnData(prev => ({
|
|
180
|
+
...prev,
|
|
181
|
+
[column]: {
|
|
182
|
+
...prev[column],
|
|
183
|
+
loading: true,
|
|
184
|
+
error: undefined
|
|
185
|
+
}
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
// onUpdate callback
|
|
189
|
+
const onUpdate = async (entities: Entity<M>[]) => {
|
|
190
|
+
// Skip updates if we're cleaning up
|
|
191
|
+
if (isCleaningUpRef.current) return;
|
|
192
|
+
|
|
193
|
+
// When text search is active, the data source returns ALL matching entities
|
|
194
|
+
// regardless of the column filter. We need to filter in memory to only show
|
|
195
|
+
// entities that belong to this specific column.
|
|
196
|
+
let processed = currentSearchString
|
|
197
|
+
? entities.filter(e => e.values?.[currentColumnProperty] === column)
|
|
198
|
+
: entities;
|
|
199
|
+
|
|
200
|
+
// Apply onFetch callbacks if any
|
|
201
|
+
if (currentCollection.callbacks?.onFetch) {
|
|
202
|
+
try {
|
|
203
|
+
processed = await Promise.all(
|
|
204
|
+
processed.map(entity =>
|
|
205
|
+
currentCollection.callbacks!.onFetch!({
|
|
206
|
+
collection: currentCollection,
|
|
207
|
+
path: currentResolvedPath,
|
|
208
|
+
entity,
|
|
209
|
+
context: currentContext
|
|
210
|
+
})
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
console.error("Error in onFetch callback:", e);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
setColumnData(prev => ({
|
|
219
|
+
...prev,
|
|
220
|
+
[column]: {
|
|
221
|
+
entities: processed,
|
|
222
|
+
loading: false,
|
|
223
|
+
hasMore: entities.length >= itemCount,
|
|
224
|
+
error: undefined,
|
|
225
|
+
totalCount: prev[column]?.totalCount // Keep existing count
|
|
226
|
+
}
|
|
227
|
+
}));
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const onError = (error: Error) => {
|
|
231
|
+
// Skip error handling if we're cleaning up
|
|
232
|
+
if (isCleaningUpRef.current) return;
|
|
233
|
+
|
|
234
|
+
console.error(`Error loading column ${column}:`, error);
|
|
235
|
+
setColumnData(prev => ({
|
|
236
|
+
...prev,
|
|
237
|
+
[column]: {
|
|
238
|
+
...prev[column],
|
|
239
|
+
entities: [],
|
|
240
|
+
loading: false,
|
|
241
|
+
hasMore: false,
|
|
242
|
+
error
|
|
243
|
+
}
|
|
244
|
+
}));
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Set up listener or fetch
|
|
248
|
+
if (currentDataSource.listenCollection) {
|
|
249
|
+
const unsubscribe = currentDataSource.listenCollection<M>({
|
|
250
|
+
path: currentResolvedPath,
|
|
251
|
+
collection: currentCollection,
|
|
252
|
+
onUpdate,
|
|
253
|
+
onError,
|
|
254
|
+
searchString: currentSearchString,
|
|
255
|
+
filter: columnFilter,
|
|
256
|
+
limit: itemCount,
|
|
257
|
+
startAfter: undefined,
|
|
258
|
+
orderBy: currentOrderProperty,
|
|
259
|
+
order: currentOrderProperty ? "asc" : undefined
|
|
260
|
+
});
|
|
261
|
+
unsubscribersRef.current[column] = unsubscribe;
|
|
262
|
+
} else {
|
|
263
|
+
currentDataSource.fetchCollection<M>({
|
|
264
|
+
path: currentResolvedPath,
|
|
265
|
+
collection: currentCollection,
|
|
266
|
+
searchString: currentSearchString,
|
|
267
|
+
filter: columnFilter,
|
|
268
|
+
limit: itemCount,
|
|
269
|
+
startAfter: undefined,
|
|
270
|
+
orderBy: currentOrderProperty,
|
|
271
|
+
order: currentOrderProperty ? "asc" : undefined
|
|
272
|
+
})
|
|
273
|
+
.then(onUpdate)
|
|
274
|
+
.catch(onError);
|
|
275
|
+
}
|
|
276
|
+
}, []); // No dependencies - uses refs for all values
|
|
277
|
+
|
|
278
|
+
// Main effect for all column subscriptions - runs when subscriptionVersion changes (i.e., key params change)
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
// Mark that we're setting up new subscriptions
|
|
281
|
+
isCleaningUpRef.current = false;
|
|
282
|
+
|
|
283
|
+
// Clean up all existing subscriptions synchronously
|
|
284
|
+
const existingUnsubscribers = { ...unsubscribersRef.current };
|
|
285
|
+
unsubscribersRef.current = {};
|
|
286
|
+
Object.values(existingUnsubscribers).forEach(unsub => {
|
|
287
|
+
try {
|
|
288
|
+
unsub?.();
|
|
289
|
+
} catch (e) {
|
|
290
|
+
// Ignore cleanup errors
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
const currentDataSource = dataSourceRef.current;
|
|
295
|
+
const currentCollection = collectionRef.current;
|
|
296
|
+
const currentFilterValues = filterValuesRef.current;
|
|
297
|
+
const currentColumnProperty = columnPropertyRef.current;
|
|
298
|
+
const currentSearchString = searchStringRef.current;
|
|
299
|
+
const currentResolvedPath = resolvedPathRef.current;
|
|
300
|
+
const currentColumns = columns;
|
|
301
|
+
const currentColumnItemCounts = columnItemCounts;
|
|
302
|
+
|
|
303
|
+
// Small delay to ensure Firestore has cleaned up previous listeners
|
|
304
|
+
const timeoutId = setTimeout(() => {
|
|
305
|
+
if (isCleaningUpRef.current) return;
|
|
306
|
+
|
|
307
|
+
currentColumns.forEach(column => {
|
|
308
|
+
const itemCount = currentColumnItemCounts[column] ?? pageSize;
|
|
309
|
+
subscribeToColumn(column, itemCount);
|
|
310
|
+
|
|
311
|
+
// Count query for column (for display in column header)
|
|
312
|
+
if (currentDataSource.countEntities) {
|
|
313
|
+
const columnFilter: FilterValues<string> = {
|
|
314
|
+
...currentFilterValues,
|
|
315
|
+
[currentColumnProperty]: ["==", column]
|
|
316
|
+
};
|
|
317
|
+
currentDataSource.countEntities({
|
|
318
|
+
path: currentResolvedPath,
|
|
319
|
+
collection: currentCollection,
|
|
320
|
+
filter: columnFilter,
|
|
321
|
+
searchString: currentSearchString
|
|
322
|
+
}).then(count => {
|
|
323
|
+
if (isCleaningUpRef.current) return;
|
|
324
|
+
setColumnData(prev => ({
|
|
325
|
+
...prev,
|
|
326
|
+
[column]: {
|
|
327
|
+
...prev[column],
|
|
328
|
+
totalCount: count
|
|
329
|
+
}
|
|
330
|
+
}));
|
|
331
|
+
}).catch(e => {
|
|
332
|
+
console.warn(`Failed to get count for column ${column}:`, e);
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Update the ref after subscribing all
|
|
338
|
+
prevColumnItemCountsRef.current = { ...currentColumnItemCounts };
|
|
339
|
+
}, 0);
|
|
340
|
+
|
|
341
|
+
return () => {
|
|
342
|
+
clearTimeout(timeoutId);
|
|
343
|
+
isCleaningUpRef.current = true;
|
|
344
|
+
const unsubscribers = { ...unsubscribersRef.current };
|
|
345
|
+
unsubscribersRef.current = {};
|
|
346
|
+
Object.values(unsubscribers).forEach(unsub => {
|
|
347
|
+
try {
|
|
348
|
+
unsub?.();
|
|
349
|
+
} catch (e) {
|
|
350
|
+
// Ignore cleanup errors
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
355
|
+
}, [subscriptionVersion, subscribeToColumn, pageSize]);
|
|
356
|
+
|
|
357
|
+
// Track which subscription version last updated the counts
|
|
358
|
+
const lastProcessedVersionRef = useRef(subscriptionVersion);
|
|
359
|
+
|
|
360
|
+
// Separate effect to handle individual column load-more WITHOUT triggering full re-subscription
|
|
361
|
+
useEffect(() => {
|
|
362
|
+
// If subscriptionVersion changed, the main effect will handle everything
|
|
363
|
+
// Skip this effect to avoid race conditions
|
|
364
|
+
if (subscriptionVersion !== lastProcessedVersionRef.current) {
|
|
365
|
+
lastProcessedVersionRef.current = subscriptionVersion;
|
|
366
|
+
prevColumnItemCountsRef.current = { ...columnItemCounts };
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const prevCounts = prevColumnItemCountsRef.current;
|
|
371
|
+
|
|
372
|
+
columns.forEach(column => {
|
|
373
|
+
const prevCount = prevCounts[column] ?? pageSize;
|
|
374
|
+
const newCount = columnItemCounts[column] ?? pageSize;
|
|
375
|
+
|
|
376
|
+
// Only re-subscribe if this specific column's count increased (load more)
|
|
377
|
+
if (newCount > prevCount && !isCleaningUpRef.current) {
|
|
378
|
+
// Unsubscribe only this column
|
|
379
|
+
if (unsubscribersRef.current[column]) {
|
|
380
|
+
try {
|
|
381
|
+
unsubscribersRef.current[column]();
|
|
382
|
+
} catch (e) {
|
|
383
|
+
// Ignore cleanup errors
|
|
384
|
+
}
|
|
385
|
+
delete unsubscribersRef.current[column];
|
|
386
|
+
}
|
|
387
|
+
// Re-subscribe with new limit after a small delay
|
|
388
|
+
setTimeout(() => {
|
|
389
|
+
if (!isCleaningUpRef.current) {
|
|
390
|
+
subscribeToColumn(column, newCount);
|
|
391
|
+
}
|
|
392
|
+
}, 0);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Update the ref
|
|
397
|
+
prevColumnItemCountsRef.current = { ...columnItemCounts };
|
|
398
|
+
}, [columnItemCounts, columns, pageSize, subscribeToColumn, subscriptionVersion]);
|
|
399
|
+
|
|
400
|
+
const loadMoreColumn = useCallback((column: COLUMN) => {
|
|
401
|
+
setColumnItemCounts(prev => ({
|
|
402
|
+
...prev,
|
|
403
|
+
[column]: (prev[column] ?? pageSize) + pageSize
|
|
404
|
+
}));
|
|
405
|
+
}, [pageSize]);
|
|
406
|
+
|
|
407
|
+
const refreshColumn = useCallback((column: COLUMN) => {
|
|
408
|
+
// Force re-subscribe by resetting to initial count
|
|
409
|
+
setColumnItemCounts(prev => ({
|
|
410
|
+
...prev,
|
|
411
|
+
[column]: pageSize
|
|
412
|
+
}));
|
|
413
|
+
}, [pageSize]);
|
|
414
|
+
|
|
415
|
+
const refreshAll = useCallback(() => {
|
|
416
|
+
const reset: Record<string, number> = {};
|
|
417
|
+
columns.forEach(col => {
|
|
418
|
+
reset[col] = pageSize;
|
|
419
|
+
});
|
|
420
|
+
setColumnItemCounts(reset);
|
|
421
|
+
}, [columns, pageSize]);
|
|
422
|
+
|
|
423
|
+
// Optimistic update for column counts when moving an item between columns
|
|
424
|
+
const updateColumnCounts = useCallback((sourceColumn: COLUMN, targetColumn: COLUMN) => {
|
|
425
|
+
if (sourceColumn === targetColumn) return;
|
|
426
|
+
|
|
427
|
+
setColumnData(prev => {
|
|
428
|
+
const updated = { ...prev };
|
|
429
|
+
|
|
430
|
+
// Decrease source column count
|
|
431
|
+
if (updated[sourceColumn]?.totalCount !== undefined) {
|
|
432
|
+
updated[sourceColumn] = {
|
|
433
|
+
...updated[sourceColumn],
|
|
434
|
+
totalCount: Math.max(0, (updated[sourceColumn].totalCount ?? 0) - 1)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Increase target column count
|
|
439
|
+
if (updated[targetColumn]?.totalCount !== undefined) {
|
|
440
|
+
updated[targetColumn] = {
|
|
441
|
+
...updated[targetColumn],
|
|
442
|
+
totalCount: (updated[targetColumn].totalCount ?? 0) + 1
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return updated;
|
|
447
|
+
});
|
|
448
|
+
}, []);
|
|
449
|
+
|
|
450
|
+
// Optimistic update for column counts when deleting items
|
|
451
|
+
const decrementColumnCounts = useCallback((columnDeltas: Record<COLUMN, number>) => {
|
|
452
|
+
setColumnData(prev => {
|
|
453
|
+
const updated = { ...prev };
|
|
454
|
+
|
|
455
|
+
for (const [column, delta] of Object.entries(columnDeltas) as [COLUMN, number][]) {
|
|
456
|
+
if (updated[column]?.totalCount !== undefined) {
|
|
457
|
+
updated[column] = {
|
|
458
|
+
...updated[column],
|
|
459
|
+
totalCount: Math.max(0, (updated[column].totalCount ?? 0) - delta)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
return updated;
|
|
465
|
+
});
|
|
466
|
+
}, []);
|
|
467
|
+
|
|
468
|
+
// Aggregate loading and error state
|
|
469
|
+
const loading = useMemo(() => {
|
|
470
|
+
return Object.values(columnData).some((col) => col.loading);
|
|
471
|
+
}, [columnData]);
|
|
472
|
+
|
|
473
|
+
const error = useMemo(() => {
|
|
474
|
+
const errors = Object.values(columnData)
|
|
475
|
+
.map((col) => col.error)
|
|
476
|
+
.filter(Boolean);
|
|
477
|
+
return errors[0];
|
|
478
|
+
}, [columnData]);
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
columnData: columnData as Record<COLUMN, BoardColumnData<M>>,
|
|
482
|
+
loadMoreColumn,
|
|
483
|
+
refreshColumn,
|
|
484
|
+
refreshAll,
|
|
485
|
+
updateColumnCounts,
|
|
486
|
+
decrementColumnCounts,
|
|
487
|
+
loading,
|
|
488
|
+
error
|
|
489
|
+
};
|
|
490
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useRef } from "react";
|
|
2
2
|
import { Highlight, themes } from "prism-react-renderer";
|
|
3
3
|
import { useModeController } from "../hooks";
|
|
4
|
+
import { jsonStringifyReplacer } from "../util/objects";
|
|
4
5
|
|
|
5
6
|
export function EntityJsonPreview({ values }: { values: object }) {
|
|
6
|
-
const code = JSON.stringify(values,
|
|
7
|
+
const code = JSON.stringify(values, jsonStringifyReplacer, "\t");
|
|
7
8
|
const { mode } = useModeController();
|
|
8
9
|
const preRef = useRef<HTMLPreElement>(null);
|
|
9
10
|
|
|
@@ -4,7 +4,7 @@ import { resolveCollection } from "../util";
|
|
|
4
4
|
import { cls, defaultBorderMixin, IconButton, OpenInNewIcon, Typography } from "@firecms/ui";
|
|
5
5
|
import { CustomizationController } from "../types/customization_controller";
|
|
6
6
|
import { useCustomizationController } from "../hooks/useCustomizationController";
|
|
7
|
-
import { useAuthController } from "../hooks";
|
|
7
|
+
import { useAuthController, useTranslation } from "../hooks";
|
|
8
8
|
import { PropertyCollectionView } from "./PropertyCollectionView";
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -26,6 +26,7 @@ export function EntityView<M extends Record<string, any>>(
|
|
|
26
26
|
}: EntityViewProps<M>) {
|
|
27
27
|
|
|
28
28
|
const authController = useAuthController();
|
|
29
|
+
const { t } = useTranslation();
|
|
29
30
|
const customizationController: CustomizationController = useCustomizationController();
|
|
30
31
|
const resolvedCollection: ResolvedEntityCollection<M> = useMemo(() => resolveCollection<M>({
|
|
31
32
|
collection,
|
|
@@ -48,7 +49,7 @@ export function EntityView<M extends Record<string, any>>(
|
|
|
48
49
|
color={"secondary"}
|
|
49
50
|
component={"span"}
|
|
50
51
|
className="break-words">
|
|
51
|
-
|
|
52
|
+
{t("id")}
|
|
52
53
|
</Typography>
|
|
53
54
|
</div>
|
|
54
55
|
<div className="col-span-8">
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import React, { ErrorInfo, PropsWithChildren } from "react";
|
|
2
|
+
import { useTranslation } from "../hooks/useTranslation";
|
|
2
3
|
|
|
3
4
|
import { ErrorIcon, Typography } from "@firecms/ui";
|
|
4
5
|
|
|
5
6
|
export class ErrorBoundary extends React.Component<PropsWithChildren<Record<string, unknown>>, {
|
|
6
|
-
|
|
7
|
+
hasError: boolean,
|
|
8
|
+
error?: Error
|
|
7
9
|
}> {
|
|
8
10
|
constructor(props: any) {
|
|
9
11
|
super(props);
|
|
10
|
-
this.state = {
|
|
12
|
+
this.state = { hasError: false };
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
// eslint-disable-next-line n/handle-callback-err
|
|
14
16
|
static getDerivedStateFromError(error: Error) {
|
|
15
|
-
return { error };
|
|
17
|
+
return { hasError: true, error };
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
@@ -21,20 +23,30 @@ export class ErrorBoundary extends React.Component<PropsWithChildren<Record<stri
|
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
render() {
|
|
24
|
-
if (this.state.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<div className="flex items-center m-2">
|
|
28
|
-
<ErrorIcon color={"error"} size={"small"}/>
|
|
29
|
-
<div className="ml-4">Error</div>
|
|
30
|
-
</div>
|
|
31
|
-
<Typography variant={"caption"}>
|
|
32
|
-
{this.state.error?.message ?? "See the error in the console"}
|
|
33
|
-
</Typography>
|
|
34
|
-
</div>
|
|
35
|
-
);
|
|
26
|
+
if (this.state.hasError) {
|
|
27
|
+
// You can render any custom fallback UI
|
|
28
|
+
return <FallbackView message={this.state.error?.message}/>;
|
|
36
29
|
}
|
|
37
30
|
|
|
38
31
|
return this.props.children;
|
|
39
32
|
}
|
|
40
33
|
}
|
|
34
|
+
|
|
35
|
+
function FallbackView({ message }: { message?: string }) {
|
|
36
|
+
const { t } = useTranslation();
|
|
37
|
+
return (
|
|
38
|
+
<div className="h-full w-full bg-slate-100 dark:bg-surface-900 flex items-center justify-center p-4">
|
|
39
|
+
<div
|
|
40
|
+
className="flex flex-col items-center justify-center m-4 bg-white dark:bg-surface-800 p-8 rounded-lg shadow-sm border border-gray-200 dark:border-surface-700">
|
|
41
|
+
<div className="flex items-center mb-4 text-red-500 dark:text-red-400">
|
|
42
|
+
<ErrorIcon/>
|
|
43
|
+
<div className="ml-4">{t("error")}</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div className="flex justify-center text-gray-500 dark:text-gray-400">
|
|
46
|
+
{/* Error message is purposely removed since it's hard to access state here, but typical ErrorBoundary fallback doesn't always show the raw message */}
|
|
47
|
+
{t("see_console_details")}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -4,7 +4,8 @@ import { Tooltip, TooltipProps } from "@firecms/ui";
|
|
|
4
4
|
export function ErrorTooltip(props: TooltipProps) {
|
|
5
5
|
return (
|
|
6
6
|
<Tooltip {...props}
|
|
7
|
-
|
|
7
|
+
className={props.className}
|
|
8
|
+
tooltipClassName={"!text-red-500 bg-red-50"}>
|
|
8
9
|
{props.children}
|
|
9
10
|
</Tooltip>
|
|
10
11
|
);
|