@firecms/core 3.0.0-canary.76 → 3.0.0-canary.78
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/form/components/StorageItemPreview.d.ts +3 -1
- package/dist/index.es.js +3237 -3188
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +5 -5
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/PropertyPreviewProps.d.ts +5 -0
- package/dist/preview/components/StorageThumbnail.d.ts +2 -1
- package/dist/preview/components/UrlComponentPreview.d.ts +2 -1
- package/dist/types/auth.d.ts +1 -1
- package/dist/types/plugins.d.ts +6 -0
- package/dist/types/properties.d.ts +5 -0
- package/package.json +4 -13
- package/src/components/common/useTableSearchHelper.ts +29 -19
- package/src/contexts/DialogsProvider.tsx +2 -2
- package/src/core/DefaultAppBar.tsx +8 -2
- package/src/form/components/StorageItemPreview.tsx +19 -6
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +40 -35
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +1 -0
- package/src/form/field_bindings/SwitchFieldBinding.tsx +1 -1
- package/src/preview/PropertyPreview.tsx +5 -2
- package/src/preview/PropertyPreviewProps.tsx +6 -0
- package/src/preview/components/ImagePreview.tsx +5 -13
- package/src/preview/components/StorageThumbnail.tsx +5 -1
- package/src/preview/components/UrlComponentPreview.tsx +44 -11
- package/src/types/auth.tsx +1 -1
- package/src/types/plugins.tsx +7 -0
- package/src/types/properties.ts +5 -0
|
@@ -37,4 +37,9 @@ export interface PropertyPreviewProps<T extends CMSType = any, CustomProps = any
|
|
|
37
37
|
* Additional properties set by the developer
|
|
38
38
|
*/
|
|
39
39
|
customProps?: CustomProps;
|
|
40
|
+
/**
|
|
41
|
+
* If the preview should be interactive or not.
|
|
42
|
+
* This applies only to videos.
|
|
43
|
+
*/
|
|
44
|
+
interactive?: boolean;
|
|
40
45
|
}
|
|
@@ -4,10 +4,11 @@ type StorageThumbnailProps = {
|
|
|
4
4
|
storagePathOrDownloadUrl: string;
|
|
5
5
|
storeUrl: boolean;
|
|
6
6
|
size: PreviewSize;
|
|
7
|
+
interactive?: boolean;
|
|
7
8
|
};
|
|
8
9
|
/**
|
|
9
10
|
* @group Preview components
|
|
10
11
|
*/
|
|
11
12
|
export declare const StorageThumbnail: React.FunctionComponent<StorageThumbnailProps>;
|
|
12
|
-
export declare function StorageThumbnailInternal({ storeUrl, storagePathOrDownloadUrl, size }: StorageThumbnailProps): import("react/jsx-runtime").JSX.Element | null;
|
|
13
|
+
export declare function StorageThumbnailInternal({ storeUrl, interactive, storagePathOrDownloadUrl, size }: StorageThumbnailProps): import("react/jsx-runtime").JSX.Element | null;
|
|
13
14
|
export {};
|
|
@@ -4,9 +4,10 @@ import { PreviewSize } from "../PropertyPreviewProps";
|
|
|
4
4
|
/**
|
|
5
5
|
* @group Preview components
|
|
6
6
|
*/
|
|
7
|
-
export declare function UrlComponentPreview({ url, previewType, size, hint }: {
|
|
7
|
+
export declare function UrlComponentPreview({ url, previewType, size, hint, interactive }: {
|
|
8
8
|
url: string;
|
|
9
9
|
previewType?: PreviewType;
|
|
10
10
|
size: PreviewSize;
|
|
11
11
|
hint?: string;
|
|
12
|
+
interactive?: boolean;
|
|
12
13
|
}): React.ReactElement;
|
package/dist/types/auth.d.ts
CHANGED
package/dist/types/plugins.d.ts
CHANGED
|
@@ -80,6 +80,12 @@ export type FireCMSPlugin<PROPS = any, FORM_PROPS = any, EC extends EntityCollec
|
|
|
80
80
|
collectionActionsProps?: COL_ACTIONS_PROPS;
|
|
81
81
|
CollectionActionsStart?: React.ComponentType<CollectionActionsProps<any, any, EC> & COL_ACTIONS_START__PROPS> | React.ComponentType<CollectionActionsProps<any, any, EC> & COL_ACTIONS_START__PROPS>[];
|
|
82
82
|
collectionActionsStartProps?: COL_ACTIONS_START__PROPS;
|
|
83
|
+
blockSearch?: (props: {
|
|
84
|
+
context: FireCMSContext;
|
|
85
|
+
path: string;
|
|
86
|
+
collection: EC;
|
|
87
|
+
parentCollectionIds?: string[];
|
|
88
|
+
}) => boolean;
|
|
83
89
|
showTextSearchBar?: (props: {
|
|
84
90
|
context: FireCMSContext;
|
|
85
91
|
path: string;
|
|
@@ -628,6 +628,11 @@ export type StorageConfig = {
|
|
|
628
628
|
* after it has been resolved.
|
|
629
629
|
*/
|
|
630
630
|
postProcess?: (pathOrUrl: string) => Promise<string>;
|
|
631
|
+
/**
|
|
632
|
+
* You can use this prop in order to provide a custom preview URL.
|
|
633
|
+
* Useful when the file's path is different from the original field value
|
|
634
|
+
*/
|
|
635
|
+
previewUrl?: (fileName: string) => string;
|
|
631
636
|
};
|
|
632
637
|
/**
|
|
633
638
|
* @group Entity properties
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@firecms/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.0-canary.
|
|
4
|
+
"version": "3.0.0-canary.78",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/firecmsco"
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"./package.json": "./package.json"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@firecms/formex": "^3.0.0-canary.
|
|
50
|
-
"@firecms/ui": "^3.0.0-canary.
|
|
49
|
+
"@firecms/formex": "^3.0.0-canary.78",
|
|
50
|
+
"@firecms/ui": "^3.0.0-canary.78",
|
|
51
51
|
"@fontsource/jetbrains-mono": "^5.0.20",
|
|
52
52
|
"@hello-pangea/dnd": "^16.6.0",
|
|
53
53
|
"@radix-ui/react-portal": "^1.1.1",
|
|
@@ -85,17 +85,8 @@
|
|
|
85
85
|
"@types/react": "^18.3.3",
|
|
86
86
|
"@types/react-dom": "^18.3.0",
|
|
87
87
|
"@types/react-measure": "^2.0.12",
|
|
88
|
-
"@typescript-eslint/eslint-plugin": "^7.15.0",
|
|
89
|
-
"@typescript-eslint/parser": "^7.15.0",
|
|
90
88
|
"@vitejs/plugin-react": "^4.3.1",
|
|
91
89
|
"cross-env": "^7.0.3",
|
|
92
|
-
"eslint": "^9.6.0",
|
|
93
|
-
"eslint-config-standard": "^17.1.0",
|
|
94
|
-
"eslint-plugin-import": "^2.29.1",
|
|
95
|
-
"eslint-plugin-n": "^16.6.2",
|
|
96
|
-
"eslint-plugin-promise": "^6.4.0",
|
|
97
|
-
"eslint-plugin-react": "^7.34.3",
|
|
98
|
-
"eslint-plugin-react-hooks": "^4.6.2",
|
|
99
90
|
"firebase": "^10.12.2",
|
|
100
91
|
"jest": "^29.7.0",
|
|
101
92
|
"npm-run-all": "^4.1.5",
|
|
@@ -111,7 +102,7 @@
|
|
|
111
102
|
"dist",
|
|
112
103
|
"src"
|
|
113
104
|
],
|
|
114
|
-
"gitHead": "
|
|
105
|
+
"gitHead": "ae1d73091cbbf9b0091e667e71fa981ab142b513",
|
|
115
106
|
"publishConfig": {
|
|
116
107
|
"access": "public"
|
|
117
108
|
},
|
|
@@ -21,33 +21,43 @@ export function useTableSearchHelper<M extends Record<string, any>>({
|
|
|
21
21
|
|
|
22
22
|
const [textSearchLoading, setTextSearchLoading] = useState<boolean>(false);
|
|
23
23
|
const [textSearchInitialised, setTextSearchInitialised] = useState<boolean>(false);
|
|
24
|
+
|
|
24
25
|
let onTextSearchClick: (() => void) | undefined;
|
|
25
26
|
let textSearchEnabled = Boolean(collection.textSearchEnabled);
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
const props = {
|
|
29
|
+
context,
|
|
30
|
+
path: fullPath,
|
|
31
|
+
collection,
|
|
32
|
+
parentCollectionIds
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const searchBlocked = customizationController.plugins?.find(p => {
|
|
36
|
+
return p.collectionView?.blockSearch?.(props);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const addTextSearchClickListener = Boolean(dataSource?.initTextSearch) || customizationController.plugins?.find(p => Boolean(p.collectionView?.onTextSearchClick));
|
|
40
|
+
|
|
41
|
+
if (addTextSearchClickListener) {
|
|
28
42
|
|
|
29
43
|
onTextSearchClick = addTextSearchClickListener
|
|
30
44
|
? () => {
|
|
31
45
|
setTextSearchLoading(true);
|
|
32
46
|
const promises: Promise<boolean>[] = [];
|
|
33
|
-
if (dataSource?.initTextSearch) {
|
|
34
|
-
promises.push(dataSource.initTextSearch(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
if (dataSource?.initTextSearch && !searchBlocked) {
|
|
48
|
+
promises.push(dataSource.initTextSearch(props));
|
|
49
|
+
}
|
|
50
|
+
if (searchBlocked) {
|
|
51
|
+
customizationController.plugins?.forEach(p => {
|
|
52
|
+
if (p.collectionView?.onTextSearchClick)
|
|
53
|
+
promises.push(p.collectionView.onTextSearchClick({
|
|
54
|
+
context,
|
|
55
|
+
path: fullPath,
|
|
56
|
+
collection,
|
|
57
|
+
parentCollectionIds
|
|
58
|
+
}));
|
|
59
|
+
})
|
|
40
60
|
}
|
|
41
|
-
customizationController.plugins?.forEach(p => {
|
|
42
|
-
if (p.collectionView?.onTextSearchClick)
|
|
43
|
-
promises.push(p.collectionView.onTextSearchClick({
|
|
44
|
-
context,
|
|
45
|
-
path: fullPath,
|
|
46
|
-
collection,
|
|
47
|
-
parentCollectionIds
|
|
48
|
-
}));
|
|
49
|
-
return Promise.resolve(true);
|
|
50
|
-
})
|
|
51
61
|
return Promise.all(promises)
|
|
52
62
|
.then((res: boolean[]) => {
|
|
53
63
|
if (res.every(Boolean)) setTextSearchInitialised(true);
|
|
@@ -18,14 +18,14 @@ export const DialogsProvider: React.FC<PropsWithChildren<{}>> = ({ children }) =
|
|
|
18
18
|
if (dialogEntries.length === 0)
|
|
19
19
|
return;
|
|
20
20
|
|
|
21
|
-
const updatedPanels = [...
|
|
21
|
+
const updatedPanels = [...dialogEntriesRef.current.slice(0, -1)];
|
|
22
22
|
updateDialogEntries(updatedPanels);
|
|
23
23
|
|
|
24
24
|
}, [dialogEntries]);
|
|
25
25
|
|
|
26
26
|
const open = useCallback((dialogEntry: DialogControllerEntryProps) => {
|
|
27
27
|
|
|
28
|
-
const updatedPanels = [...
|
|
28
|
+
const updatedPanels = [...dialogEntriesRef.current, dialogEntry];
|
|
29
29
|
updateDialogEntries(updatedPanels);
|
|
30
30
|
|
|
31
31
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { Link as ReactLink } from "react-router-dom";
|
|
3
|
+
import { Link as ReactLink, useNavigate } from "react-router-dom";
|
|
4
4
|
import { ErrorBoundary, FireCMSLogo } from "../components";
|
|
5
5
|
import {
|
|
6
6
|
Avatar,
|
|
@@ -71,6 +71,8 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
71
71
|
toggleMode
|
|
72
72
|
} = useModeController();
|
|
73
73
|
|
|
74
|
+
const navigate = useNavigate();
|
|
75
|
+
|
|
74
76
|
const largeLayout = useLargeLayout();
|
|
75
77
|
|
|
76
78
|
const user = userProp ?? authController.user;
|
|
@@ -161,7 +163,11 @@ export const DefaultAppBar = function DefaultAppBar({
|
|
|
161
163
|
|
|
162
164
|
{dropDownActions}
|
|
163
165
|
|
|
164
|
-
{!dropDownActions && <MenuItem onClick={
|
|
166
|
+
{!dropDownActions && <MenuItem onClick={async () => {
|
|
167
|
+
await authController.signOut();
|
|
168
|
+
// replace current route with home
|
|
169
|
+
navigate("/");
|
|
170
|
+
}}>
|
|
165
171
|
<LogoutIcon/>
|
|
166
172
|
Log Out
|
|
167
173
|
</MenuItem>}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ResolvedStringProperty } from "../../types";
|
|
4
4
|
import { PreviewSize, PropertyPreview } from "../../preview";
|
|
5
5
|
|
|
6
|
-
import { cls, IconButton, paperMixin, RemoveIcon, Tooltip } from "@firecms/ui";
|
|
6
|
+
import { cls, DescriptionIcon, IconButton, paperMixin, RemoveIcon, Tooltip } from "@firecms/ui";
|
|
7
7
|
import { ErrorBoundary } from "../../components";
|
|
8
8
|
|
|
9
9
|
interface StorageItemPreviewProps {
|
|
@@ -13,6 +13,8 @@ interface StorageItemPreviewProps {
|
|
|
13
13
|
onRemove: (value: string) => void;
|
|
14
14
|
size: PreviewSize;
|
|
15
15
|
disabled: boolean;
|
|
16
|
+
placeholder?: boolean;
|
|
17
|
+
className?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export function StorageItemPreview({
|
|
@@ -22,14 +24,17 @@ export function StorageItemPreview({
|
|
|
22
24
|
onRemove,
|
|
23
25
|
disabled,
|
|
24
26
|
size,
|
|
27
|
+
placeholder,
|
|
28
|
+
className
|
|
25
29
|
}: StorageItemPreviewProps) {
|
|
26
30
|
|
|
27
31
|
return (
|
|
28
32
|
<div className={cls(paperMixin,
|
|
29
33
|
"relative m-4 border-box flex items-center justify-center",
|
|
30
|
-
size === "medium" ? "min-w-[220px] min-h-[220px] max-w-[220px]" : "min-w-[118px] min-h-[118px] max-w-[118px]"
|
|
34
|
+
size === "medium" ? "min-w-[220px] min-h-[220px] max-w-[220px]" : "min-w-[118px] min-h-[118px] max-w-[118px]",
|
|
35
|
+
className)}>
|
|
31
36
|
|
|
32
|
-
{!disabled &&
|
|
37
|
+
{!placeholder && !disabled &&
|
|
33
38
|
<div
|
|
34
39
|
className="absolute rounded-full -top-2 -right-2 z-10 bg-white dark:bg-gray-900">
|
|
35
40
|
|
|
@@ -47,16 +52,24 @@ export function StorageItemPreview({
|
|
|
47
52
|
</div>
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
{value &&
|
|
55
|
+
{!placeholder && value &&
|
|
51
56
|
<ErrorBoundary>
|
|
52
57
|
<PropertyPreview propertyKey={name}
|
|
53
58
|
value={value}
|
|
54
59
|
property={property}
|
|
55
|
-
|
|
60
|
+
interactive={false}
|
|
56
61
|
size={size}/>
|
|
57
62
|
</ErrorBoundary>
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
{placeholder &&
|
|
66
|
+
<div
|
|
67
|
+
onClick={(e) => e.stopPropagation()}
|
|
68
|
+
className="flex flex-col items-center justify-center w-full h-full">
|
|
69
|
+
<DescriptionIcon className="text-gray-700 dark:text-gray-300"/>
|
|
70
|
+
</div>
|
|
71
|
+
}
|
|
72
|
+
|
|
60
73
|
|
|
61
74
|
</div>
|
|
62
75
|
);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "@firecms/ui";
|
|
22
22
|
import { getDefaultValueForDataType, getIconForProperty } from "../../util";
|
|
23
23
|
import { useCustomizationController } from "../../hooks";
|
|
24
|
+
import { getIn } from "@firecms/formex";
|
|
24
25
|
|
|
25
26
|
type MapEditViewRowState = [number, {
|
|
26
27
|
key: string,
|
|
@@ -52,9 +53,13 @@ export function KeyValueFieldBinding({
|
|
|
52
53
|
if (!property.keyValue) {
|
|
53
54
|
throw Error(`Your property ${propertyKey} needs to have the 'keyValue' prop in order to use this field binding`);
|
|
54
55
|
}
|
|
56
|
+
|
|
57
|
+
const initialValues = getIn(context.formex.initialValues, propertyKey);
|
|
58
|
+
|
|
55
59
|
const mapFormView = <MapEditView value={value}
|
|
56
60
|
setValue={setValue}
|
|
57
61
|
disabled={disabled}
|
|
62
|
+
initialValue={initialValues}
|
|
58
63
|
fieldName={property.name ?? propertyKey}/>;
|
|
59
64
|
|
|
60
65
|
const title = <LabelWithIcon
|
|
@@ -84,6 +89,7 @@ export function KeyValueFieldBinding({
|
|
|
84
89
|
|
|
85
90
|
interface MapEditViewParams<T extends Record<string, any>> {
|
|
86
91
|
value?: T;
|
|
92
|
+
initialValue?: T;
|
|
87
93
|
setValue: (value: (T | null)) => void;
|
|
88
94
|
fieldName?: string,
|
|
89
95
|
disabled?: boolean
|
|
@@ -91,14 +97,15 @@ interface MapEditViewParams<T extends Record<string, any>> {
|
|
|
91
97
|
|
|
92
98
|
function MapEditView<T extends Record<string, any>>({
|
|
93
99
|
value,
|
|
100
|
+
initialValue,
|
|
94
101
|
setValue,
|
|
95
102
|
fieldName,
|
|
96
103
|
disabled
|
|
97
104
|
}: MapEditViewParams<T>) {
|
|
98
105
|
const [internalState, setInternalState] = React.useState<MapEditViewRowState[]>(
|
|
99
|
-
Object.keys(
|
|
106
|
+
Object.keys(initialValue ?? {}).map((key) => [getRandomId(), {
|
|
100
107
|
key,
|
|
101
|
-
dataType: getDataType(
|
|
108
|
+
dataType: getDataType(initialValue?.[key]) ?? "string"
|
|
102
109
|
}])
|
|
103
110
|
);
|
|
104
111
|
|
|
@@ -121,8 +128,6 @@ function MapEditView<T extends Record<string, any>>({
|
|
|
121
128
|
setInternalState(newRowIds);
|
|
122
129
|
}, [value]);
|
|
123
130
|
|
|
124
|
-
const originalValue = React.useRef<T>(value ?? {} as T);
|
|
125
|
-
|
|
126
131
|
const updateDataType = (rowId: number, dataType: DataType) => {
|
|
127
132
|
if (!rowId) {
|
|
128
133
|
console.warn("No key selected for data type update");
|
|
@@ -168,7 +173,7 @@ function MapEditView<T extends Record<string, any>>({
|
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
const newValue = { ...(value ?? {}) } as T;
|
|
171
|
-
if (typeof
|
|
176
|
+
if (typeof initialValue === "object" && fieldKey in initialValue) {
|
|
172
177
|
// @ts-ignore
|
|
173
178
|
newValue[fieldKey] = undefined; // set to undefined to remove from the object, the datasource will remove it from the backend
|
|
174
179
|
} else {
|
|
@@ -186,7 +191,7 @@ function MapEditView<T extends Record<string, any>>({
|
|
|
186
191
|
value={value ?? {} as T}
|
|
187
192
|
onDeleteClick={() => {
|
|
188
193
|
const newValue = { ...(value ?? {}) as T };
|
|
189
|
-
if (
|
|
194
|
+
if (initialValue && fieldKey in initialValue) {
|
|
190
195
|
// @ts-ignore
|
|
191
196
|
newValue[fieldKey] = undefined;
|
|
192
197
|
} else {
|
|
@@ -305,7 +310,7 @@ function MapKeyValueRow<T extends Record<string, any>>({
|
|
|
305
310
|
}}/>;
|
|
306
311
|
} else if (dataType === "boolean") {
|
|
307
312
|
return <BooleanSwitchWithLabel value={entryValue}
|
|
308
|
-
size={"
|
|
313
|
+
size={"medium"}
|
|
309
314
|
position={"start"}
|
|
310
315
|
disabled={disabled || !fieldKey}
|
|
311
316
|
onValueChange={(newValue) => {
|
|
@@ -375,7 +380,7 @@ function MapKeyValueRow<T extends Record<string, any>>({
|
|
|
375
380
|
<Typography key={rowId.toString()}
|
|
376
381
|
component={"div"}
|
|
377
382
|
className="font-mono flex flex-row gap-1">
|
|
378
|
-
<div className="w-[
|
|
383
|
+
<div className="w-[300px] max-w-[30%]">
|
|
379
384
|
<TextField
|
|
380
385
|
value={fieldKey}
|
|
381
386
|
placeholder={"key"}
|
|
@@ -389,32 +394,32 @@ function MapKeyValueRow<T extends Record<string, any>>({
|
|
|
389
394
|
<div className="flex-grow">
|
|
390
395
|
{(dataType !== "map" && dataType !== "array") && buildInput(entryValue, fieldKey, dataType)}
|
|
391
396
|
</div>
|
|
392
|
-
<
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
</
|
|
397
|
+
<div className={"flex flex-col"}>
|
|
398
|
+
<Menu
|
|
399
|
+
trigger={<IconButton size={"smallest"}>
|
|
400
|
+
<ArrowDropDownIcon size={"small"}/>
|
|
401
|
+
</IconButton>}
|
|
402
|
+
>
|
|
403
|
+
<MenuItem dense
|
|
404
|
+
onClick={() => doUpdateDataType("string")}>string</MenuItem>
|
|
405
|
+
<MenuItem dense
|
|
406
|
+
onClick={() => doUpdateDataType("number")}>number</MenuItem>
|
|
407
|
+
<MenuItem dense
|
|
408
|
+
onClick={() => doUpdateDataType("boolean")}>boolean</MenuItem>
|
|
409
|
+
<MenuItem dense
|
|
410
|
+
onClick={() => doUpdateDataType("date")}>date</MenuItem>
|
|
411
|
+
<MenuItem dense
|
|
412
|
+
onClick={() => doUpdateDataType("map")}>map</MenuItem>
|
|
413
|
+
<MenuItem dense
|
|
414
|
+
onClick={() => doUpdateDataType("array")}>array</MenuItem>
|
|
415
|
+
</Menu>
|
|
416
|
+
|
|
417
|
+
<IconButton aria-label="delete"
|
|
418
|
+
size={"smallest"}
|
|
419
|
+
onClick={onDeleteClick}>
|
|
420
|
+
<RemoveIcon size={"smallest"}/>
|
|
421
|
+
</IconButton>
|
|
422
|
+
</div>
|
|
418
423
|
</Typography>
|
|
419
424
|
|
|
420
425
|
{(dataType === "map" || dataType === "array") && buildInput(entryValue, fieldKey, dataType)}
|
|
@@ -472,7 +477,7 @@ function ArrayKeyValueRow<T>({
|
|
|
472
477
|
}}/>;
|
|
473
478
|
} else if (dataType === "boolean") {
|
|
474
479
|
return <BooleanSwitchWithLabel value={entryValue}
|
|
475
|
-
size={"
|
|
480
|
+
size={"medium"}
|
|
476
481
|
position={"start"}
|
|
477
482
|
onValueChange={(v) => {
|
|
478
483
|
setValue(v as T);
|
|
@@ -46,7 +46,7 @@ export const SwitchFieldBinding = React.forwardRef(function SwitchFieldBinding({
|
|
|
46
46
|
title={property.name}/>}
|
|
47
47
|
disabled={disabled}
|
|
48
48
|
autoFocus={autoFocus}
|
|
49
|
-
size={"
|
|
49
|
+
size={"large"}
|
|
50
50
|
/>
|
|
51
51
|
|
|
52
52
|
<FieldHelperText includeDescription={includeDescription}
|
|
@@ -47,7 +47,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
47
47
|
size,
|
|
48
48
|
height,
|
|
49
49
|
width,
|
|
50
|
-
|
|
50
|
+
interactive
|
|
51
51
|
} = props;
|
|
52
52
|
|
|
53
53
|
const property = resolveProperty({
|
|
@@ -84,12 +84,15 @@ export const PropertyPreview = React.memo(function PropertyPreview<T extends CMS
|
|
|
84
84
|
content =
|
|
85
85
|
<UrlComponentPreview size={props.size}
|
|
86
86
|
url={value}
|
|
87
|
+
interactive={interactive}
|
|
87
88
|
previewType={stringProperty.url}/>;
|
|
88
89
|
} else if (stringProperty.storage) {
|
|
90
|
+
const filePath = stringProperty.storage.previewUrl ? stringProperty.storage.previewUrl(value) : value;
|
|
89
91
|
content = <StorageThumbnail
|
|
92
|
+
interactive={interactive}
|
|
90
93
|
storeUrl={property.storage?.storeUrl ?? false}
|
|
91
94
|
size={props.size}
|
|
92
|
-
storagePathOrDownloadUrl={
|
|
95
|
+
storagePathOrDownloadUrl={filePath}/>;
|
|
93
96
|
} else if (stringProperty.markdown) {
|
|
94
97
|
content = <Markdown source={value} size={"small"}/>;
|
|
95
98
|
} else {
|
|
@@ -20,8 +20,6 @@ export function ImagePreview({
|
|
|
20
20
|
url
|
|
21
21
|
}: ImagePreviewProps) {
|
|
22
22
|
|
|
23
|
-
const [onHover, setOnHover] = useState(false);
|
|
24
|
-
|
|
25
23
|
const imageSize = useMemo(() => getThumbnailMeasure(size), [size]);
|
|
26
24
|
|
|
27
25
|
if (size === "tiny") {
|
|
@@ -47,25 +45,21 @@ export function ImagePreview({
|
|
|
47
45
|
|
|
48
46
|
return (
|
|
49
47
|
<div
|
|
50
|
-
className="relative flex items-center justify-center max-w-full max-h-full"
|
|
48
|
+
className="relative flex items-center justify-center max-w-full max-h-full group"
|
|
51
49
|
style={{
|
|
52
50
|
width: imageSize,
|
|
53
51
|
height: imageSize
|
|
54
52
|
}}
|
|
55
|
-
key={"image_preview_" + url}
|
|
56
|
-
onMouseEnter={() => setOnHover(true)}
|
|
57
|
-
onMouseMove={() => setOnHover(true)}
|
|
58
|
-
onMouseLeave={() => setOnHover(false)}>
|
|
53
|
+
key={"image_preview_" + url}>
|
|
59
54
|
|
|
60
55
|
<img src={url}
|
|
61
56
|
className={"rounded-md"}
|
|
62
57
|
style={imageStyle}/>
|
|
63
58
|
|
|
64
|
-
{onHover && <>
|
|
65
59
|
|
|
66
60
|
{navigator && <Tooltip title="Copy url to clipboard">
|
|
67
61
|
<div
|
|
68
|
-
className="rounded-full absolute bottom-[-4px] right-8">
|
|
62
|
+
className="rounded-full absolute bottom-[-4px] right-8 invisible group-hover:visible">
|
|
69
63
|
<IconButton
|
|
70
64
|
variant={"filled"}
|
|
71
65
|
size={"small"}
|
|
@@ -74,7 +68,7 @@ export function ImagePreview({
|
|
|
74
68
|
e.preventDefault();
|
|
75
69
|
return navigator.clipboard.writeText(url);
|
|
76
70
|
}}>
|
|
77
|
-
<ContentCopyIcon className={"text-gray-
|
|
71
|
+
<ContentCopyIcon className={"text-gray-700 dark:text-gray-300"}
|
|
78
72
|
size={"small"}/>
|
|
79
73
|
</IconButton>
|
|
80
74
|
</div>
|
|
@@ -95,12 +89,10 @@ export function ImagePreview({
|
|
|
95
89
|
size={"small"}
|
|
96
90
|
onClick={(e: any) => e.stopPropagation()}
|
|
97
91
|
>
|
|
98
|
-
<OpenInNewIcon className={"text-gray-
|
|
92
|
+
<OpenInNewIcon className={"text-gray-700 dark:text-gray-300 invisible group-hover:visible"}
|
|
99
93
|
size={"small"}/>
|
|
100
94
|
</IconButton>
|
|
101
95
|
</Tooltip>
|
|
102
|
-
</>
|
|
103
|
-
}
|
|
104
96
|
</div>
|
|
105
97
|
);
|
|
106
98
|
}
|
|
@@ -11,6 +11,7 @@ type StorageThumbnailProps = {
|
|
|
11
11
|
storagePathOrDownloadUrl: string;
|
|
12
12
|
storeUrl: boolean;
|
|
13
13
|
size: PreviewSize;
|
|
14
|
+
interactive?: boolean;
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -21,13 +22,15 @@ export const StorageThumbnail = React.memo<StorageThumbnailProps>(StorageThumbna
|
|
|
21
22
|
function areEqual(prevProps: StorageThumbnailProps, nextProps: StorageThumbnailProps) {
|
|
22
23
|
return prevProps.size === nextProps.size &&
|
|
23
24
|
prevProps.storagePathOrDownloadUrl === nextProps.storagePathOrDownloadUrl &&
|
|
24
|
-
prevProps.storeUrl === nextProps.storeUrl
|
|
25
|
+
prevProps.storeUrl === nextProps.storeUrl&&
|
|
26
|
+
prevProps.interactive === nextProps.interactive;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
const URL_CACHE: Record<string, DownloadConfig> = {};
|
|
28
30
|
|
|
29
31
|
export function StorageThumbnailInternal({
|
|
30
32
|
storeUrl,
|
|
33
|
+
interactive,
|
|
31
34
|
storagePathOrDownloadUrl,
|
|
32
35
|
size
|
|
33
36
|
}: StorageThumbnailProps) {
|
|
@@ -68,6 +71,7 @@ export function StorageThumbnailInternal({
|
|
|
68
71
|
return downloadConfig?.url
|
|
69
72
|
? <UrlComponentPreview previewType={previewType}
|
|
70
73
|
url={downloadConfig.url}
|
|
74
|
+
interactive={interactive}
|
|
71
75
|
size={size}
|
|
72
76
|
hint={storagePathOrDownloadUrl}/>
|
|
73
77
|
: renderSkeletonImageThumbnail(size);
|