@firecms/core 3.3.0 → 3.4.0-canary.1faa2a9
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 +4 -0
- package/dist/app/useApp.d.ts +1 -0
- package/dist/components/EntityCollectionTable/column_utils.d.ts +4 -3
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +6 -1
- package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +8 -0
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +3 -1
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +3 -1
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
- package/dist/components/EntityCollectionView/useBoardDataController.d.ts +3 -1
- package/dist/components/EntityPreview.d.ts +3 -1
- package/dist/components/ErrorBoundary.d.ts +1 -3
- package/dist/components/LicenseBanner.d.ts +23 -0
- package/dist/components/ReferenceTable/ReferenceSelectionTable.d.ts +10 -1
- package/dist/components/SelectableTable/filters/filter_text_input.d.ts +30 -0
- package/dist/components/SelectableTable/filters/filter_text_input.test.d.ts +1 -0
- package/dist/components/common/table_url_params.d.ts +24 -0
- package/dist/components/common/useDataSourceTableController.d.ts +9 -1
- package/dist/components/common/useTableSearchHelper.d.ts +9 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/contexts/LicenseStatusContext.d.ts +16 -0
- package/dist/core/DefaultDrawer.d.ts +10 -3
- package/dist/core/EntityEditView.d.ts +9 -1
- package/dist/core/EntityEditViewFormActions.d.ts +1 -1
- package/dist/core/field_configs.d.ts +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/plugin_translations.d.ts +38 -0
- package/dist/core/pro_plugins.d.ts +40 -0
- package/dist/de-KtBROUvS.js +700 -0
- package/dist/de-KtBROUvS.js.map +1 -0
- package/dist/editor/extensions/Image.d.ts +11 -0
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/lazy_editor.d.ts +6 -0
- package/dist/editor-BSo_aS1I.js +4563 -0
- package/dist/editor-BSo_aS1I.js.map +1 -0
- package/dist/form/EntityForm.d.ts +8 -1
- package/dist/form/EntityFormActions.d.ts +2 -0
- package/dist/form/field_bindings/GeopointFieldBinding.d.ts +5 -0
- package/dist/form/index.d.ts +1 -0
- package/dist/fr-BRFSBaVh.js +700 -0
- package/dist/fr-BRFSBaVh.js.map +1 -0
- package/dist/hi-DwCkga5X.js +700 -0
- package/dist/hi-DwCkga5X.js.map +1 -0
- package/dist/hooks/data/delete.d.ts +1 -1
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/data/useCollectionFetch.d.ts +7 -1
- package/dist/hooks/data/useEntityFetch.d.ts +9 -1
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useLicenseStatus.d.ts +16 -0
- package/dist/hooks/useProjectLog.d.ts +11 -6
- package/dist/hooks/useTranslation.d.ts +1 -1
- package/dist/i18n/__tests__/FireCMSi18nProvider.test.d.ts +1 -0
- package/dist/i18n/__tests__/nested_provider_lifecycle.test.d.ts +1 -0
- package/dist/index.es.js +4509 -10665
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +9378 -6771
- package/dist/index.umd.js.map +1 -1
- package/dist/it-B2YGgDsx.js +700 -0
- package/dist/it-B2YGgDsx.js.map +1 -0
- package/dist/locales/pl.d.ts +2 -0
- package/dist/pl-BxHkywK8.js +702 -0
- package/dist/pl-BxHkywK8.js.map +1 -0
- package/dist/preview/index.d.ts +1 -0
- package/dist/preview/property_previews/GeopointPropertyPreview.d.ts +4 -0
- package/dist/pt-q6UV4diT.js +702 -0
- package/dist/pt-q6UV4diT.js.map +1 -0
- package/dist/types/analytics.d.ts +1 -1
- package/dist/types/collections.d.ts +31 -0
- package/dist/types/datasource.d.ts +67 -4
- package/dist/types/entities.d.ts +21 -1
- package/dist/types/entity_actions.d.ts +6 -0
- package/dist/types/entity_callbacks.d.ts +21 -0
- package/dist/types/firecms.d.ts +18 -1
- package/dist/types/firecms_context.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/license.d.ts +54 -0
- package/dist/types/navigation.d.ts +34 -5
- package/dist/types/plugins.d.ts +22 -1
- package/dist/types/properties.d.ts +17 -1
- package/dist/types/side_entity_controller.d.ts +7 -0
- package/dist/types/translations.d.ts +37 -2
- package/dist/util/__tests__/collections.test.d.ts +1 -0
- package/dist/util/__tests__/urls.test.d.ts +1 -0
- package/dist/util/collections.d.ts +1 -1
- package/dist/util/date_fns_locales.d.ts +18 -0
- package/dist/util/entities.d.ts +1 -0
- package/dist/util/entity_cache.d.ts +11 -0
- package/dist/util/geopoint.d.ts +22 -0
- package/dist/util/index.d.ts +3 -0
- package/dist/util/lazy_eager.d.ts +3 -3
- package/dist/util/navigation_blocking.d.ts +22 -0
- package/dist/util/navigation_from_path.d.ts +17 -0
- package/dist/util/navigation_utils.d.ts +107 -3
- package/dist/util/parent_references_from_path.d.ts +16 -0
- package/dist/util/paths.d.ts +28 -0
- package/dist/util/permissions.d.ts +10 -4
- package/dist/util/urls.d.ts +22 -0
- package/package.json +17 -10
- package/src/app/Scaffold.tsx +41 -44
- package/src/app/useApp.tsx +1 -0
- package/src/components/DeleteEntityDialog.tsx +2 -0
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +2 -2
- package/src/components/EntityCollectionTable/EntityCollectionTable.tsx +3 -1
- package/src/components/EntityCollectionTable/column_utils.tsx +11 -19
- package/src/components/EntityCollectionTable/fields/TableReferenceField.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +7 -0
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +5 -1
- package/src/components/EntityCollectionView/EntityCollectionBoardView.tsx +12 -0
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +66 -15
- package/src/components/EntityCollectionView/EntityCollectionViewActions.tsx +6 -2
- package/src/components/EntityCollectionView/EntityCollectionViewStartActions.tsx +8 -1
- package/src/components/EntityCollectionView/FiltersDialog.tsx +39 -28
- package/src/components/EntityCollectionView/useBoardDataController.tsx +17 -1
- package/src/components/EntityPreview.tsx +45 -41
- package/src/components/ErrorBoundary.tsx +12 -18
- package/src/components/LicenseBanner.tsx +209 -0
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +19 -2
- package/src/components/ReferenceWidget.tsx +1 -1
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +2 -2
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +33 -7
- package/src/components/SelectableTable/filters/filter_text_input.test.ts +184 -0
- package/src/components/SelectableTable/filters/filter_text_input.ts +71 -0
- package/src/components/VirtualTable/VirtualTable.tsx +19 -8
- package/src/components/common/default_entity_actions.tsx +22 -2
- package/src/components/common/table_url_params.ts +172 -0
- package/src/components/common/useDataSourceTableController.tsx +126 -163
- package/src/components/common/useTableSearchHelper.ts +26 -2
- package/src/components/index.tsx +1 -0
- package/src/contexts/LicenseStatusContext.tsx +50 -0
- package/src/contexts/SnackbarProvider.tsx +14 -1
- package/src/core/DefaultAppBar.tsx +28 -16
- package/src/core/DefaultDrawer.tsx +150 -64
- package/src/core/DrawerNavigationGroup.tsx +27 -29
- package/src/core/DrawerNavigationItem.tsx +10 -12
- package/src/core/EntityEditView.tsx +77 -35
- package/src/core/EntityEditViewFormActions.tsx +3 -2
- package/src/core/EntitySidePanel.tsx +8 -4
- package/src/core/FireCMS.tsx +73 -47
- package/src/core/SideDialogs.tsx +44 -1
- package/src/core/field_configs.tsx +15 -0
- package/src/core/index.tsx +1 -0
- package/src/core/plugin_translations.ts +87 -0
- package/src/core/pro_plugins.ts +79 -0
- package/src/editor/components/SlashCommandMenu.tsx +4 -1
- package/src/editor/extensions/Image.ts +17 -1
- package/src/editor/hooks/useProseMirror.ts +5 -0
- package/src/editor/index.ts +11 -1
- package/src/editor/lazy_editor.tsx +33 -0
- package/src/form/EntityForm.tsx +31 -10
- package/src/form/EntityFormActions.tsx +2 -0
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +1 -1
- package/src/form/field_bindings/GeopointFieldBinding.tsx +139 -0
- package/src/form/index.tsx +1 -0
- package/src/hooks/data/delete.ts +8 -0
- package/src/hooks/data/save.ts +6 -1
- package/src/hooks/data/useCollectionFetch.tsx +21 -1
- package/src/hooks/data/useEntityFetch.tsx +35 -4
- package/src/hooks/index.tsx +2 -0
- package/src/hooks/useBuildNavigationController.tsx +70 -15
- package/src/hooks/useFireCMSContext.tsx +7 -3
- package/src/hooks/useLicenseStatus.tsx +32 -0
- package/src/hooks/useProjectLog.tsx +27 -9
- package/src/hooks/useResolvedNavigationFrom.tsx +3 -1
- package/src/hooks/useTranslation.ts +3 -1
- package/src/hooks/useValidateAuthenticator.tsx +4 -0
- package/src/i18n/FireCMSi18nProvider.tsx +199 -23
- package/src/i18n/__tests__/FireCMSi18nProvider.test.tsx +91 -0
- package/src/i18n/__tests__/nested_provider_lifecycle.test.tsx +67 -0
- package/src/internal/useBuildDataSource.ts +37 -15
- package/src/internal/useBuildSideEntityController.tsx +20 -5
- package/src/locales/de.ts +23 -4
- package/src/locales/en.ts +25 -4
- package/src/locales/es.ts +23 -4
- package/src/locales/fr.ts +23 -4
- package/src/locales/hi.ts +23 -4
- package/src/locales/it.ts +23 -4
- package/src/locales/pl.ts +749 -0
- package/src/locales/pt.ts +23 -4
- package/src/preview/PropertyPreview.tsx +12 -0
- package/src/preview/components/DatePreview.tsx +2 -4
- package/src/preview/components/ReferencePreview.tsx +7 -3
- package/src/preview/components/StorageThumbnail.tsx +24 -2
- package/src/preview/index.ts +1 -0
- package/src/preview/property_previews/GeopointPropertyPreview.tsx +23 -0
- package/src/routes/FireCMSRoute.tsx +54 -27
- package/src/types/analytics.ts +3 -0
- package/src/types/collections.ts +34 -0
- package/src/types/datasource.ts +67 -4
- package/src/types/entities.ts +24 -1
- package/src/types/entity_actions.tsx +6 -0
- package/src/types/entity_callbacks.ts +24 -0
- package/src/types/firecms.tsx +19 -1
- package/src/types/firecms_context.tsx +9 -0
- package/src/types/index.ts +1 -0
- package/src/types/license.ts +62 -0
- package/src/types/navigation.ts +35 -5
- package/src/types/plugins.tsx +23 -1
- package/src/types/properties.ts +17 -0
- package/src/types/side_entity_controller.tsx +7 -0
- package/src/types/translations.ts +37 -2
- package/src/util/__tests__/collections.test.ts +32 -0
- package/src/util/__tests__/urls.test.ts +131 -0
- package/src/util/collections.ts +1 -1
- package/src/util/date_fns_locales.ts +168 -0
- package/src/util/entities.ts +16 -1
- package/src/util/entity_cache.ts +18 -0
- package/src/util/geopoint.ts +81 -0
- package/src/util/index.ts +3 -0
- package/src/util/lazy_eager.tsx +30 -8
- package/src/util/navigation_blocking.ts +45 -0
- package/src/util/navigation_from_path.ts +47 -7
- package/src/util/navigation_utils.ts +244 -4
- package/src/util/parent_references_from_path.ts +45 -4
- package/src/util/paths.ts +40 -3
- package/src/util/permissions.ts +22 -10
- package/src/util/previews.ts +8 -1
- package/src/util/resolutions.ts +8 -0
- package/src/util/urls.ts +52 -0
- package/src/util/useStorageUploadController.tsx +6 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PreviewType } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Is the given value an absolute `http(s)` URL, as opposed to a relative
|
|
4
|
+
* storage path such as `images/my_image.png`?
|
|
5
|
+
*
|
|
6
|
+
* Values saved in the datasource for storage properties can be either, e.g.
|
|
7
|
+
* when the property is configured with `storeUrl: true`, or when the value was
|
|
8
|
+
* written directly pointing to an externally hosted file.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isAbsoluteHttpUrl(value?: string | null): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Extract the lowercase file extension of a URL or path, ignoring any query
|
|
13
|
+
* string or hash fragment. Returns `undefined` when there is no extension.
|
|
14
|
+
*/
|
|
15
|
+
export declare function getFileExtensionFromUrl(urlOrPath?: string | null): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Best effort guess of the preview type of a URL or path, based on its file
|
|
18
|
+
* extension. Used when no storage metadata (and therefore no content type) is
|
|
19
|
+
* available, e.g. for externally hosted files.
|
|
20
|
+
* Returns `undefined` when the extension is missing or not recognised.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getPreviewTypeFromUrl(urlOrPath?: string | null): PreviewType | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.4.0-canary.1faa2a9",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"@dnd-kit/core": "^6.3.1",
|
|
54
54
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
55
55
|
"@dnd-kit/sortable": "^10.0.0",
|
|
56
|
-
"@firecms/formex": "
|
|
57
|
-
"@firecms/ui": "
|
|
56
|
+
"@firecms/formex": "3.4.0-canary.1faa2a9",
|
|
57
|
+
"@firecms/ui": "3.4.0-canary.1faa2a9",
|
|
58
58
|
"@floating-ui/dom": "^1.7.4",
|
|
59
59
|
"@radix-ui/react-portal": "^1.1.10",
|
|
60
60
|
"@radix-ui/react-slot": "^1.2.4",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"date-fns": "^3.6.0",
|
|
64
64
|
"fuse.js": "^7.1.0",
|
|
65
65
|
"history": "^5.3.0",
|
|
66
|
-
"i18next": "^
|
|
66
|
+
"i18next": "^26.3.3",
|
|
67
67
|
"json-logic-js": "^2.0.5",
|
|
68
|
-
"markdown-it": "^14.
|
|
68
|
+
"markdown-it": "^14.2.0",
|
|
69
69
|
"markdown-it-ins": "^4.0.0",
|
|
70
70
|
"markdown-it-mark": "^4.0.0",
|
|
71
71
|
"markdown-it-task-lists": "^2.1.1",
|
|
@@ -89,14 +89,14 @@
|
|
|
89
89
|
"prosemirror-view": "^1.33.8",
|
|
90
90
|
"react-dropzone": "^14.3.8",
|
|
91
91
|
"react-fast-compare": "^3.2.2",
|
|
92
|
-
"react-i18next": "^
|
|
92
|
+
"react-i18next": "^17.0.11",
|
|
93
93
|
"react-image-crop": "^11.0.10",
|
|
94
94
|
"react-markdown": "^9.1.0",
|
|
95
95
|
"react-moveable": "^0.56.0",
|
|
96
96
|
"react-transition-group": "^4.4.5",
|
|
97
97
|
"react-use-measure": "^2.1.7",
|
|
98
98
|
"react-window": "^1.8.11",
|
|
99
|
-
"vite-plugin-static-copy": "
|
|
99
|
+
"vite-plugin-static-copy": "4.1.1",
|
|
100
100
|
"yup": "^1.7.1"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"@testing-library/user-event": "^14.6.1",
|
|
112
112
|
"@types/jest": "^29.5.14",
|
|
113
113
|
"@types/json-logic-js": "^2.0.8",
|
|
114
|
-
"@types/node": "^
|
|
114
|
+
"@types/node": "^26.1.1",
|
|
115
115
|
"@types/object-hash": "^3.0.6",
|
|
116
116
|
"@types/react": "^19.2.3",
|
|
117
117
|
"@types/react-dom": "^19.2.3",
|
|
@@ -123,12 +123,12 @@
|
|
|
123
123
|
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
|
|
124
124
|
"jest": "^29.7.0",
|
|
125
125
|
"jest-environment-jsdom": "^30.2.0",
|
|
126
|
-
"npm-run-
|
|
126
|
+
"npm-run-all2": "^6.2.6",
|
|
127
127
|
"react-router": "^6.30.2",
|
|
128
128
|
"react-router-dom": "^6.30.2",
|
|
129
129
|
"ts-jest": "^29.4.5",
|
|
130
130
|
"ts-node": "^10.9.2",
|
|
131
|
-
"tsd": "^0.
|
|
131
|
+
"tsd": "^0.33.0",
|
|
132
132
|
"typescript": "^5.9.3",
|
|
133
133
|
"vite": "^7.2.4"
|
|
134
134
|
},
|
|
@@ -163,6 +163,10 @@
|
|
|
163
163
|
"^.+\\.tsx?$": "ts-jest"
|
|
164
164
|
},
|
|
165
165
|
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
|
|
166
|
+
"testPathIgnorePatterns": [
|
|
167
|
+
"/node_modules/",
|
|
168
|
+
"/dist/"
|
|
169
|
+
],
|
|
166
170
|
"moduleFileExtensions": [
|
|
167
171
|
"ts",
|
|
168
172
|
"tsx",
|
|
@@ -172,6 +176,9 @@
|
|
|
172
176
|
"node"
|
|
173
177
|
],
|
|
174
178
|
"testEnvironment": "node",
|
|
179
|
+
"setupFiles": [
|
|
180
|
+
"<rootDir>/test/setup_jsdom.js"
|
|
181
|
+
],
|
|
175
182
|
"moduleNameMapper": {
|
|
176
183
|
"\\.(css|less)$": "<rootDir>/test/__mocks__/styleMock.js"
|
|
177
184
|
}
|
package/src/app/Scaffold.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { PropsWithChildren, useCallback } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { cls, defaultBorderMixin, IconButton, MenuIcon, Sheet } from "@firecms/ui";
|
|
3
3
|
import equal from "react-fast-compare"
|
|
4
4
|
import { useLargeLayout } from "../hooks";
|
|
5
5
|
import { ErrorBoundary } from "../components";
|
|
6
|
+
import { LicenseBanner } from "../components/LicenseBanner";
|
|
6
7
|
import { AppContext } from "./useApp";
|
|
7
8
|
import { useTranslation } from "../hooks/useTranslation";
|
|
8
9
|
|
|
@@ -80,7 +81,11 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
80
81
|
const [onHover, setOnHover] = React.useState(false);
|
|
81
82
|
|
|
82
83
|
const setOnHoverTrue = useCallback(() => setOnHover(true), []);
|
|
83
|
-
const setOnHoverFalse = useCallback(() =>
|
|
84
|
+
const setOnHoverFalse = useCallback(() => {
|
|
85
|
+
// Don't collapse the drawer while a popover/dropdown is open
|
|
86
|
+
if (typeof document !== "undefined" && document.querySelector("[data-radix-popper-content-wrapper]")) return;
|
|
87
|
+
setOnHover(false);
|
|
88
|
+
}, []);
|
|
84
89
|
|
|
85
90
|
const handleDrawerOpen = useCallback(() => {
|
|
86
91
|
setDrawerOpen(true);
|
|
@@ -96,17 +101,21 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
96
101
|
} catch { /* ignore */ }
|
|
97
102
|
}, []);
|
|
98
103
|
|
|
99
|
-
|
|
104
|
+
// Pinned-open state only; hover is tracked separately so it can *float*
|
|
105
|
+
// over the content instead of pushing it.
|
|
106
|
+
const computedDrawerOpen: boolean = drawerOpen;
|
|
107
|
+
const computedDrawerHovered: boolean = Boolean(largeLayout && onHover);
|
|
100
108
|
|
|
101
109
|
const hasAppBar = Boolean(appBarChildren.length > 0);
|
|
102
110
|
return (
|
|
103
111
|
<AppContext.Provider value={{
|
|
104
112
|
logo,
|
|
105
113
|
hasDrawer: includeDrawer,
|
|
106
|
-
drawerHovered:
|
|
114
|
+
drawerHovered: computedDrawerHovered,
|
|
107
115
|
drawerOpen: computedDrawerOpen,
|
|
108
116
|
closeDrawer: handleDrawerClose,
|
|
109
117
|
openDrawer: handleDrawerOpen,
|
|
118
|
+
closeHover: setOnHoverFalse,
|
|
110
119
|
autoOpenDrawer
|
|
111
120
|
}}>
|
|
112
121
|
<div
|
|
@@ -128,7 +137,7 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
128
137
|
onMouseMove={setOnHoverTrue}
|
|
129
138
|
onMouseLeave={setOnHoverFalse}
|
|
130
139
|
open={computedDrawerOpen}
|
|
131
|
-
hovered={
|
|
140
|
+
hovered={computedDrawerHovered}
|
|
132
141
|
setDrawerOpen={setDrawerOpen}>
|
|
133
142
|
{includeDrawer && drawerChildren}
|
|
134
143
|
</DrawerWrapper>
|
|
@@ -138,6 +147,12 @@ export const Scaffold = React.memo<PropsWithChildren<ScaffoldProps>>(
|
|
|
138
147
|
|
|
139
148
|
{hasAppBar && <DrawerHeader />}
|
|
140
149
|
|
|
150
|
+
<LicenseBanner
|
|
151
|
+
className={cls("flex-shrink-0", {
|
|
152
|
+
"px-2 pb-1 lg:px-4 lg:pb-2": padding,
|
|
153
|
+
"pt-2 lg:pt-4": !hasAppBar
|
|
154
|
+
})}/>
|
|
155
|
+
|
|
141
156
|
<div
|
|
142
157
|
className={cls(defaultBorderMixin, "flex-grow overflow-auto m-0 ", {
|
|
143
158
|
"lg:mt-4": !hasAppBar,
|
|
@@ -177,49 +192,30 @@ function DrawerWrapper(props: {
|
|
|
177
192
|
}) {
|
|
178
193
|
|
|
179
194
|
const { t } = useTranslation();
|
|
180
|
-
|
|
195
|
+
|
|
196
|
+
// Layout width is the space the drawer occupies in the flex row; the visual
|
|
197
|
+
// width is how wide the drawer actually renders. When hovered (but not
|
|
198
|
+
// pinned open) the layout width stays collapsed while the visual width
|
|
199
|
+
// expands, so the drawer *floats* over the content instead of pushing it.
|
|
200
|
+
const layoutWidth = !props.displayed ? 0 : (props.open ? DRAWER_WIDTH : 72);
|
|
201
|
+
const visualWidth = !props.displayed ? 0 : ((props.open || props.hovered) ? DRAWER_WIDTH : 72);
|
|
202
|
+
const isFloating = props.hovered && !props.open;
|
|
203
|
+
|
|
204
|
+
const transition = "left 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, opacity 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms, width 75ms cubic-bezier(0.4, 0, 0.6, 1) 0ms";
|
|
205
|
+
|
|
181
206
|
const innerDrawer = <div
|
|
182
|
-
className={"
|
|
207
|
+
className={cls("h-full overflow-hidden", defaultBorderMixin,
|
|
208
|
+
isFloating
|
|
209
|
+
? "absolute top-0 left-0 bottom-0 z-30 bg-surface-50 dark:bg-surface-900 shadow-lg border-r"
|
|
210
|
+
: "relative bg-surface-50 dark:bg-surface-900")}
|
|
183
211
|
style={{
|
|
184
|
-
width,
|
|
185
|
-
transition
|
|
212
|
+
width: visualWidth,
|
|
213
|
+
transition
|
|
186
214
|
}}
|
|
187
215
|
>
|
|
188
|
-
|
|
189
|
-
{!props.open && props.displayed && (
|
|
190
|
-
<Tooltip title={t("open_menu")}
|
|
191
|
-
side="right"
|
|
192
|
-
sideOffset={12}
|
|
193
|
-
asChild={true}>
|
|
194
|
-
<div
|
|
195
|
-
className="ml-2 fixed top-1 left-2 sm:top-2 sm:left-2 !bg-surface-50 dark:!bg-surface-900 rounded-full w-fit z-20">
|
|
196
|
-
<IconButton
|
|
197
|
-
color="inherit"
|
|
198
|
-
aria-label={t("open_menu")}
|
|
199
|
-
onClick={() => props.setDrawerOpen(true)}
|
|
200
|
-
size="large"
|
|
201
|
-
>
|
|
202
|
-
<MenuIcon size="small" />
|
|
203
|
-
</IconButton>
|
|
204
|
-
</div>
|
|
205
|
-
</Tooltip>
|
|
206
|
-
)}
|
|
207
|
-
|
|
208
|
-
<div
|
|
209
|
-
className={`z-20 absolute right-0 top-4 ${props.open ? "opacity-100" : "opacity-0 invisible"
|
|
210
|
-
} transition-opacity duration-200 ease-in-out`}>
|
|
211
|
-
<IconButton
|
|
212
|
-
aria-label={t("close_drawer")}
|
|
213
|
-
onClick={() => props.setDrawerOpen(false)}
|
|
214
|
-
>
|
|
215
|
-
<ChevronLeftIcon />
|
|
216
|
-
</IconButton>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
216
|
<div className={"flex flex-col h-full"}>
|
|
220
217
|
{props.children}
|
|
221
218
|
</div>
|
|
222
|
-
|
|
223
219
|
</div>;
|
|
224
220
|
|
|
225
221
|
const largeLayout = useLargeLayout();
|
|
@@ -250,13 +246,14 @@ function DrawerWrapper(props: {
|
|
|
250
246
|
|
|
251
247
|
return (
|
|
252
248
|
<div
|
|
253
|
-
className="z-20 relative"
|
|
249
|
+
className="z-20 relative flex-shrink-0 overflow-visible"
|
|
254
250
|
onMouseEnter={props.onMouseEnter}
|
|
255
251
|
onMouseMove={props.onMouseMove}
|
|
256
252
|
onMouseLeave={props.onMouseLeave}
|
|
257
253
|
style={{
|
|
258
|
-
width,
|
|
259
|
-
|
|
254
|
+
width: layoutWidth,
|
|
255
|
+
minWidth: layoutWidth,
|
|
256
|
+
transition
|
|
260
257
|
}}>
|
|
261
258
|
|
|
262
259
|
{innerDrawer}
|
package/src/app/useApp.tsx
CHANGED
|
@@ -96,6 +96,8 @@ export function DeleteEntityDialog<M extends Record<string, any>>({
|
|
|
96
96
|
deleteEntityWithCallbacks({
|
|
97
97
|
dataSource,
|
|
98
98
|
entity,
|
|
99
|
+
// The entity is the authority on where it lives; stated rather than left implicit.
|
|
100
|
+
pathSegments: entity.pathSegments,
|
|
99
101
|
collection: resolvedCollection,
|
|
100
102
|
callbacks,
|
|
101
103
|
onDeleteSuccess,
|
|
@@ -3,7 +3,7 @@ import React, { MouseEvent, useCallback } from "react";
|
|
|
3
3
|
import { CollectionSize, Entity, EntityAction, EntityCollection, SelectionController } from "../../types";
|
|
4
4
|
import { Badge, Checkbox, cls, IconButton, Menu, MenuItem, MoreVertIcon, Skeleton, Tooltip } from "@firecms/ui";
|
|
5
5
|
import { useFireCMSContext, useLargeLayout } from "../../hooks";
|
|
6
|
-
import { getEntityFromCache } from "../../util/entity_cache";
|
|
6
|
+
import { entityCacheKey, getEntityFromCache } from "../../util/entity_cache";
|
|
7
7
|
import { getLocalChangesBackup } from "../../util";
|
|
8
8
|
import { getChanges } from "../../form/EntityForm";
|
|
9
9
|
|
|
@@ -82,7 +82,7 @@ export const EntityCollectionRowActions = function EntityCollectionRowActions({
|
|
|
82
82
|
const collapsedActions = actions.filter(a => a.collapsed || a.collapsed === undefined);
|
|
83
83
|
const uncollapsedActions = actions.filter(a => a.collapsed === false);
|
|
84
84
|
const enableLocalChangesBackup = collection ? getLocalChangesBackup(collection) : false;
|
|
85
|
-
const cachedData = enableLocalChangesBackup ? getEntityFromCache(fullPath
|
|
85
|
+
const cachedData = enableLocalChangesBackup ? getEntityFromCache(entityCacheKey(fullPath, entity.id)) : undefined;
|
|
86
86
|
const hasDraft = (() => {
|
|
87
87
|
if (!cachedData || typeof cachedData !== "object" || Object.keys(cachedData).length === 0) return false;
|
|
88
88
|
const realChanges = getChanges(cachedData as any, (entity?.values ?? {}) as any);
|
|
@@ -245,7 +245,8 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
|
|
|
245
245
|
const columnsResult: VirtualTableColumn[] = propertiesToColumns({
|
|
246
246
|
properties,
|
|
247
247
|
sortable,
|
|
248
|
-
|
|
248
|
+
forcedFilters: tableController.forcedFilters,
|
|
249
|
+
allowedFilters: tableController.allowedFilters,
|
|
249
250
|
AdditionalHeaderWidget
|
|
250
251
|
});
|
|
251
252
|
|
|
@@ -353,6 +354,7 @@ export const EntityCollectionTable = function EntityCollectionTable<M extends Re
|
|
|
353
354
|
onTextSearch={textSearchEnabled ? onTextSearch : undefined}
|
|
354
355
|
textSearchLoading={textSearchLoading}
|
|
355
356
|
onTextSearchClick={textSearchEnabled ? onTextSearchClick : undefined}
|
|
357
|
+
initialSearchString={tableController.searchString}
|
|
356
358
|
title={title}
|
|
357
359
|
actionsStart={actionsStart}
|
|
358
360
|
actions={actions}
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { getTableCellAlignment, getTablePropertyColumnWidth } from "./internal/common";
|
|
3
3
|
import { FilterValues, ResolvedProperties, ResolvedProperty } from "../../types";
|
|
4
4
|
import { VirtualTableColumn } from "../VirtualTable";
|
|
5
|
-
import { getIconForProperty, getResolvedPropertyInPath } from "../../util";
|
|
5
|
+
import { getIconForProperty, getResolvedPropertyInPath, isDataTypeFilterable } from "../../util";
|
|
6
6
|
import { getColumnKeysForProperty } from "../common/useColumnsIds";
|
|
7
7
|
|
|
8
8
|
export function buildIdColumn(largeLayout?: boolean): VirtualTableColumn {
|
|
@@ -20,7 +20,8 @@ export function buildIdColumn(largeLayout?: boolean): VirtualTableColumn {
|
|
|
20
20
|
export interface PropertiesToColumnsParams<M extends Record<string, any>> {
|
|
21
21
|
properties: ResolvedProperties<M>;
|
|
22
22
|
sortable?: boolean;
|
|
23
|
-
|
|
23
|
+
forcedFilters?: (keyof M)[];
|
|
24
|
+
allowedFilters?: (keyof M)[];
|
|
24
25
|
AdditionalHeaderWidget?: React.ComponentType<{
|
|
25
26
|
property: ResolvedProperty,
|
|
26
27
|
propertyKey: string,
|
|
@@ -28,8 +29,7 @@ export interface PropertiesToColumnsParams<M extends Record<string, any>> {
|
|
|
28
29
|
}>;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
export function propertiesToColumns<M extends Record<string, any>>({ properties, sortable,
|
|
32
|
-
const disabledFilter = Boolean(forceFilter);
|
|
32
|
+
export function propertiesToColumns<M extends Record<string, any>>({ properties, sortable, forcedFilters, AdditionalHeaderWidget, allowedFilters }: PropertiesToColumnsParams<M>): VirtualTableColumn[] {
|
|
33
33
|
return Object.entries<ResolvedProperty>(properties)
|
|
34
34
|
.flatMap(([key, property]) => getColumnKeysForProperty(property, key))
|
|
35
35
|
.map(({
|
|
@@ -39,14 +39,19 @@ export function propertiesToColumns<M extends Record<string, any>>({ properties,
|
|
|
39
39
|
const property = getResolvedPropertyInPath(properties, key);
|
|
40
40
|
if (!property)
|
|
41
41
|
throw Error("Internal error: no property found in path " + key);
|
|
42
|
-
|
|
42
|
+
|
|
43
|
+
const filterable = property.dataType === 'array' ? isDataTypeFilterable(property.of?.dataType, true) : isDataTypeFilterable(property.dataType);
|
|
44
|
+
const isFilterForced = forcedFilters?.includes(key) ?? false;
|
|
45
|
+
const isFilterAllowed = allowedFilters ? allowedFilters.includes(key) : filterable;
|
|
46
|
+
|
|
47
|
+
const filterEnabled = filterable && isFilterAllowed && !isFilterForced;
|
|
43
48
|
return {
|
|
44
49
|
key: key as string,
|
|
45
50
|
align: getTableCellAlignment(property),
|
|
46
51
|
icon: getIconForProperty(property, "small"),
|
|
47
52
|
title: property.name ?? key as string,
|
|
48
53
|
sortable: sortable,
|
|
49
|
-
filter:
|
|
54
|
+
filter: filterEnabled,
|
|
50
55
|
width: getTablePropertyColumnWidth(property),
|
|
51
56
|
resizable: true,
|
|
52
57
|
custom: {
|
|
@@ -59,16 +64,3 @@ export function propertiesToColumns<M extends Record<string, any>>({ properties,
|
|
|
59
64
|
} satisfies VirtualTableColumn;
|
|
60
65
|
});
|
|
61
66
|
}
|
|
62
|
-
|
|
63
|
-
function filterableProperty(property: ResolvedProperty, partOfArray = false): boolean {
|
|
64
|
-
if (partOfArray) {
|
|
65
|
-
return ["string", "number", "date", "reference"].includes(property.dataType);
|
|
66
|
-
}
|
|
67
|
-
if (property.dataType === "array") {
|
|
68
|
-
if (property.of)
|
|
69
|
-
return filterableProperty(property.of, true);
|
|
70
|
-
else
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return ["string", "number", "boolean", "date", "reference", "array"].includes(property.dataType);
|
|
74
|
-
}
|
|
@@ -70,7 +70,7 @@ export const TableReferenceFieldInternal = React.memo(
|
|
|
70
70
|
}, [updateValue]);
|
|
71
71
|
|
|
72
72
|
const onMultipleEntitiesSelected = useCallback((entities: Entity<any>[]) => {
|
|
73
|
-
updateValue(entities.map((e) => getReferenceFrom(e)));
|
|
73
|
+
updateValue(entities.filter(Boolean).map((e) => getReferenceFrom(e)));
|
|
74
74
|
}, [updateValue]);
|
|
75
75
|
|
|
76
76
|
const selectedEntityIds = internalValue
|
|
@@ -20,6 +20,11 @@ interface CollectionTableToolbarProps {
|
|
|
20
20
|
onTextSearchClick?: () => void;
|
|
21
21
|
onTextSearch?: (searchString?: string) => void;
|
|
22
22
|
textSearchLoading?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Search term the search bar should start with, typically the one restored from the
|
|
25
|
+
* URL query params by the table controller.
|
|
26
|
+
*/
|
|
27
|
+
initialSearchString?: string;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
export function CollectionTableToolbar({
|
|
@@ -29,6 +34,7 @@ export function CollectionTableToolbar({
|
|
|
29
34
|
onTextSearch,
|
|
30
35
|
onTextSearchClick,
|
|
31
36
|
textSearchLoading,
|
|
37
|
+
initialSearchString,
|
|
32
38
|
title,
|
|
33
39
|
viewModeToggle
|
|
34
40
|
}: CollectionTableToolbarProps) {
|
|
@@ -79,6 +85,7 @@ export function CollectionTableToolbar({
|
|
|
79
85
|
disabled={Boolean(onTextSearchClick)}
|
|
80
86
|
onClick={onTextSearchClick}
|
|
81
87
|
onTextSearch={onTextSearchClick ? undefined : onTextSearch}
|
|
88
|
+
initialValue={initialSearchString}
|
|
82
89
|
placeholder={t("search")}
|
|
83
90
|
expandable={true} />}
|
|
84
91
|
|
|
@@ -26,6 +26,8 @@ import { OnCellValueChangeParams } from "../../../common";
|
|
|
26
26
|
interface PopupFormFieldProps<M extends Record<string, any>> {
|
|
27
27
|
customFieldValidator?: CustomFieldValidator;
|
|
28
28
|
path: string;
|
|
29
|
+
/** `path` split at its real segment boundaries; forwarded to the datasource. */
|
|
30
|
+
pathSegments?: string[];
|
|
29
31
|
entityId: string;
|
|
30
32
|
tableKey: string;
|
|
31
33
|
propertyKey?: Extract<keyof M, string>;
|
|
@@ -54,6 +56,7 @@ export function PopupFormFieldLoading<M extends Record<string, any>>({
|
|
|
54
56
|
propertyKey,
|
|
55
57
|
collection: inputCollection,
|
|
56
58
|
path,
|
|
59
|
+
pathSegments,
|
|
57
60
|
cellRect,
|
|
58
61
|
open,
|
|
59
62
|
onClose,
|
|
@@ -66,11 +69,12 @@ export function PopupFormFieldLoading<M extends Record<string, any>>({
|
|
|
66
69
|
if (entityId && inputCollection) {
|
|
67
70
|
dataSource.fetchEntity({
|
|
68
71
|
path,
|
|
72
|
+
pathSegments,
|
|
69
73
|
entityId,
|
|
70
74
|
collection: inputCollection
|
|
71
75
|
}).then(setEntity);
|
|
72
76
|
}
|
|
73
|
-
}, [entityId, inputCollection, dataSource, path]);
|
|
77
|
+
}, [entityId, inputCollection, dataSource, path, pathSegments]);
|
|
74
78
|
|
|
75
79
|
if (!entity) return null;
|
|
76
80
|
return <PopupFormFieldInternal {...{
|
|
@@ -42,6 +42,8 @@ export type EntityCollectionBoardViewProps<M extends Record<string, any> = any>
|
|
|
42
42
|
collection: EntityCollection<M>;
|
|
43
43
|
tableController: EntityTableController<M>;
|
|
44
44
|
fullPath: string;
|
|
45
|
+
/** `fullPath` split at its real segment boundaries; forwarded to the datasource. */
|
|
46
|
+
pathSegments?: string[];
|
|
45
47
|
parentCollectionIds?: string[];
|
|
46
48
|
columnProperty: string;
|
|
47
49
|
onEntityClick?: (entity: Entity<M>) => void;
|
|
@@ -60,6 +62,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
60
62
|
collection,
|
|
61
63
|
tableController,
|
|
62
64
|
fullPath,
|
|
65
|
+
pathSegments,
|
|
63
66
|
parentCollectionIds = [],
|
|
64
67
|
columnProperty,
|
|
65
68
|
onEntityClick,
|
|
@@ -169,6 +172,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
169
172
|
// Use the new per-column data controller
|
|
170
173
|
const boardDataController = useBoardDataController<M>({
|
|
171
174
|
fullPath,
|
|
175
|
+
pathSegments,
|
|
172
176
|
collection,
|
|
173
177
|
columnProperty,
|
|
174
178
|
columns,
|
|
@@ -270,6 +274,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
270
274
|
|
|
271
275
|
currentDataSource.countEntities({
|
|
272
276
|
path: fullPath,
|
|
277
|
+
pathSegments,
|
|
273
278
|
collection: currentCollection
|
|
274
279
|
}).then(count => {
|
|
275
280
|
totalCount = count;
|
|
@@ -281,6 +286,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
281
286
|
|
|
282
287
|
currentDataSource.countEntities({
|
|
283
288
|
path: fullPath,
|
|
289
|
+
pathSegments,
|
|
284
290
|
collection: currentCollection,
|
|
285
291
|
filter: { [orderProperty]: ["!=", null] } as FilterValues<string>
|
|
286
292
|
}).then(count => {
|
|
@@ -432,6 +438,8 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
432
438
|
|
|
433
439
|
const saveProps: SaveEntityProps = {
|
|
434
440
|
path: entity.path,
|
|
441
|
+
// Paired with `entity.path`: the segments the entity was loaded with.
|
|
442
|
+
pathSegments: entity.pathSegments,
|
|
435
443
|
entityId: entity.id,
|
|
436
444
|
values: updatedValues as M,
|
|
437
445
|
previousValues: entity.values,
|
|
@@ -472,6 +480,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
472
480
|
console.log("Fetching all documents from collection...");
|
|
473
481
|
const allDocs = await dataSource.fetchCollection<M>({
|
|
474
482
|
path: fullPath,
|
|
483
|
+
pathSegments,
|
|
475
484
|
collection,
|
|
476
485
|
limit: 10000 // Fetch all
|
|
477
486
|
});
|
|
@@ -492,6 +501,8 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
492
501
|
|
|
493
502
|
const saveProps: SaveEntityProps = {
|
|
494
503
|
path: entity.path,
|
|
504
|
+
// Paired with `entity.path`: the segments the entity was loaded with.
|
|
505
|
+
pathSegments: entity.pathSegments,
|
|
495
506
|
entityId: entity.id,
|
|
496
507
|
values: updatedValues as M,
|
|
497
508
|
previousValues: entity.values,
|
|
@@ -663,6 +674,7 @@ export function EntityCollectionBoardView<M extends Record<string, any> = any>({
|
|
|
663
674
|
});
|
|
664
675
|
sideEntityController.open({
|
|
665
676
|
path: fullPath,
|
|
677
|
+
pathSegments,
|
|
666
678
|
collection,
|
|
667
679
|
entityId: undefined,
|
|
668
680
|
updateUrl: true,
|