From 18180711c58c5ff215e7376477611fadd81196a3 Mon Sep 17 00:00:00 2001 From: awwit Date: Wed, 25 Sep 2024 17:04:48 +0300 Subject: [PATCH 1/3] refactor: add useMemo and useCallback to Editor components --- .../ComponentBuilder/ComponentBuilder.tsx | 1 + packages/core/src/resourcesUtils.ts | 3 +- packages/core/src/types.ts | 375 +++-- .../design-system/src/ThumbnailButton.tsx | 68 +- packages/design-system/src/Toaster.tsx | 53 +- .../src/ToggleGroup/ToggleGroup.tsx | 115 +- packages/editor/src/CanvasRoot/CanvasRoot.tsx | 8 +- packages/editor/src/EasyblocksEditor.tsx | 14 +- packages/editor/src/EasyblocksParent.tsx | 69 +- packages/editor/src/Editor.tsx | 1218 +++++++++-------- packages/editor/src/EditorContext.ts | 5 +- .../editor/src/EditorExternalDataProvider.tsx | 30 +- packages/editor/src/EditorIframe.tsx | 104 +- packages/editor/src/EditorSidebar.tsx | 44 +- packages/editor/src/EditorTopBar.tsx | 279 ++-- packages/editor/src/ModalPicker.tsx | 151 +- packages/editor/src/SidebarFooter.tsx | 10 +- packages/editor/src/inline-settings.tsx | 26 +- packages/editor/src/parseQueryParams.ts | 8 +- .../editor/src/selectionFrame/AddButton.tsx | 110 +- .../src/selectionFrame/SelectionFrame.tsx | 222 +-- .../src/tinacms/fields/plugins/LocalFIeld.tsx | 64 +- .../tinacms/form-builder/fields-builder.tsx | 69 +- .../utils/createFieldController.ts | 62 +- .../editor/src/tinacms/react-core/use-form.ts | 25 +- packages/editor/src/useDataSaver.ts | 88 +- packages/editor/src/useEditorHistory.ts | 94 +- packages/editor/src/useOnClickNTimes.ts | 29 +- packages/editor/src/useWindowKeyDown.ts | 45 +- packages/utils/src/hooks/useForceRerender.ts | 6 +- packages/utils/src/object/dotNotationGet.ts | 13 +- 31 files changed, 1827 insertions(+), 1581 deletions(-) diff --git a/packages/core/src/components/ComponentBuilder/ComponentBuilder.tsx b/packages/core/src/components/ComponentBuilder/ComponentBuilder.tsx index e81f8232..3aac1426 100644 --- a/packages/core/src/components/ComponentBuilder/ComponentBuilder.tsx +++ b/packages/core/src/components/ComponentBuilder/ComponentBuilder.tsx @@ -225,6 +225,7 @@ function getCompiledSubcomponents( let elements = compiledArray.map((compiledChild, index) => "_component" in compiledChild ? ( | undefined { const externalReferenceLocationKey = typeof value.id === "string" && value.id.startsWith("$.") ? value.id diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index bdc43b1a..023e9333 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -4,29 +4,29 @@ import { Locale } from "./locales"; export type ScalarOrCollection = T | Array; -export type PlaceholderAppearance = { +export interface PlaceholderAppearance { width?: number; height?: number; aspectRatio?: number; label?: string; -}; +} export interface ComponentConfigBase { _component: Identifier; _id: string; } -export type NoCodeComponentEntry = { +export interface NoCodeComponentEntry { _component: string; // instance of the component (unique id) _id: string; [key: string]: any; // props -}; +} -export type ThemeTokenValue = { +export interface ThemeTokenValue { value: T; isDefault: boolean; label?: string; -}; +} export type Spacing = string; // 10px, 0px, 10vw, etc @@ -34,10 +34,10 @@ export type Font = object; export type Color = string; -export type TrulyResponsiveValue = { +export interface TrulyResponsiveValue { [key: string]: T | true | undefined; $res: true; -}; +} export type ResponsiveValue = T | TrulyResponsiveValue; @@ -49,12 +49,12 @@ export type ThemeColor = ResponsiveValue; export type ThemeFont = ResponsiveValue<{ [key: string]: any }>; -export type ButtonCustomComponent = CustomComponentShared & { +export interface ButtonCustomComponent extends CustomComponentShared { builtinProps?: { label?: "on" | "off"; symbol?: "on" | "off" | "optional"; }; -}; +} export type StringSchemaProp = ValueSchemaProp<"string", string, "optional"> & SchemaPropParams<{ @@ -131,15 +131,13 @@ export type StringTokenSchemaProp = ValueSchemaProp< true >; -export type SpaceSchemaProp = ValueSchemaProp< - "space", - ResponsiveValue>, - "forced" -> & - SchemaPropParams<{ - prefix?: string; - autoConstant?: number; - }>; +export interface SpaceSchemaProp + extends ValueSchemaProp< + "space", + ResponsiveValue>, + "forced" + >, + SchemaPropParams<{ prefix?: string; autoConstant?: number }> {} export type FontSchemaProp = ValueSchemaProp< "font", @@ -159,24 +157,24 @@ export type PassedField = | { name: string; label: string; showWhen?: any; group?: string } | string; -export type ComponentSchemaProp = SchemaPropShared<"component"> & { +export interface ComponentSchemaProp extends SchemaPropShared<"component"> { accepts: string[]; picker?: ComponentPickerType; required?: boolean; noInline?: boolean; placeholderAppearance?: PlaceholderAppearance; -}; +} -export type ComponentCollectionSchemaProp = - SchemaPropShared<"component-collection"> & { - accepts: string[]; - picker?: ComponentPickerType; - hideFields?: string[]; - itemFields?: SchemaProp[]; - passFields?: PassedField[]; - noInline?: boolean; - placeholderAppearance?: PlaceholderAppearance; - }; +export interface ComponentCollectionSchemaProp + extends SchemaPropShared<"component-collection"> { + accepts: string[]; + picker?: ComponentPickerType; + hideFields?: string[]; + itemFields?: SchemaProp[]; + passFields?: PassedField[]; + noInline?: boolean; + placeholderAppearance?: PlaceholderAppearance; +} export type ComponentCollectionLocalisedSchemaProp = Omit< ComponentCollectionSchemaProp, @@ -187,14 +185,15 @@ export type ComponentCollectionLocalisedSchemaProp = Omit< placeholderAppearance?: PlaceholderAppearance; }; -export type TextSchemaProp = ValueSchemaProp< - "text", - LocalTextReference | ExternalReference | string, - "never" -> & { +export interface TextSchemaProp + extends ValueSchemaProp< + "text", + LocalTextReference | ExternalReference | string, + "never" + > { normalize?: (x: string) => string | null; [key: string]: any; -}; +} export type PositionVertical = "top" | "center" | "bottom"; @@ -208,14 +207,15 @@ export type PositionSchemaProp = ValueSchemaProp< "forced" >; -export type ExternalSchemaProp = ValueSchemaProp< - string & Record, - ExternalReference, - "optional" -> & { +export interface ExternalSchemaProp + extends ValueSchemaProp< + string & Record, + ExternalReference, + "optional" + > { params?: ExternalParams; optional?: boolean; -}; +} export type LocalSchemaProp = ValueSchemaProp< string & Record, @@ -223,14 +223,11 @@ export type LocalSchemaProp = ValueSchemaProp< "optional" >; -export type TokenSchemaProp = ValueSchemaProp< - string & Record, - any, - "forced" -> & - SchemaPropParams<{ - extraValues: Array; - }>; +export interface TokenSchemaProp + extends ValueSchemaProp, any, "forced">, + SchemaPropParams<{ + extraValues: Array; + }> {} export type SchemaProp = | StringSchemaProp @@ -267,31 +264,31 @@ export type AnyValueSchemaProp = | PositionSchemaProp | ExternalSchemaProp; -type CustomComponentShared = { +interface CustomComponentShared { id: string; hidden?: boolean; schema: SchemaProp[]; label?: string; previewImage?: string; -}; +} -export type ConfigDeviceRange = { +export interface ConfigDeviceRange { startsFrom?: number; w?: number; h?: number | null; hidden?: boolean; -}; +} -export type ConfigDevices = { +export interface ConfigDevices { xs?: ConfigDeviceRange; sm?: ConfigDeviceRange; md?: ConfigDeviceRange; lg?: ConfigDeviceRange; xl?: ConfigDeviceRange; "2xl"?: ConfigDeviceRange; -}; +} -export type UserDefinedTemplate = { +export interface UserDefinedTemplate { id: string; label: string; thumbnail?: string; @@ -300,9 +297,9 @@ export type UserDefinedTemplate = { isUserDefined: true; width?: number; widthAuto?: boolean; -}; +} -export type InternalTemplate = { +export interface InternalTemplate { id: string; label?: string; thumbnail?: string; @@ -311,71 +308,73 @@ export type InternalTemplate = { isUserDefined?: false; width?: number; widthAuto?: boolean; -}; +} export type Template = InternalTemplate | UserDefinedTemplate; -export type ConfigTokenValue = { +export interface ConfigTokenValue { id: string; label?: string; value: T; isDefault?: boolean; -}; +} export type ExternalParams = Record; -export type ContextParams = { +export interface ContextParams { locale: string; [key: string]: any; -}; +} -export type WidgetComponentProps = { +export interface WidgetComponentProps< + Identifier extends NonNullish = NonNullish +> { id: ExternalReference["id"]; onChange: (newId: ExternalReference["id"]) => void; params?: Record; -}; +} -export type InlineTypeWidgetComponentProps< +export interface InlineTypeWidgetComponentProps< Type extends NonNullish = NonNullish -> = { +> { value: Type; onChange: (newValue: Type) => void; params?: Record; -}; +} export type TokenTypeWidgetComponentProps< Type extends NonNullish = NonNullish > = InlineTypeWidgetComponentProps; -export type Widget = { +export interface Widget { id: string; label: string; -}; +} -export type ExternalDefinition = { +export interface ExternalDefinition { widgets: Array; -}; +} -export type LocalizedText = { +export interface LocalizedText { [locale: string]: string; -}; +} -export type NoCodeComponentStylesFunctionInput< +export interface NoCodeComponentStylesFunctionInput< Values extends Record = Record, Params extends Record = Record -> = { +> { values: Values; params: { $width: number; $widthAuto: boolean } & Params; device: DeviceRange; isEditing: boolean; -}; +} export type InferNoCodeComponentStylesFunctionInput = T extends NoCodeComponentDefinition ? NoCodeComponentStylesFunctionInput : never; -export type NoCodeComponentStylesFunctionResult = { +export interface NoCodeComponentStylesFunctionResult { props?: Record; components?: Record< string, @@ -388,7 +387,7 @@ export type NoCodeComponentStylesFunctionResult = { } >; styled?: Record>>; -}; +} export type NoCodeComponentStylesFunction< Values extends Record = Record, @@ -397,15 +396,15 @@ export type NoCodeComponentStylesFunction< input: NoCodeComponentStylesFunctionInput ) => NoCodeComponentStylesFunctionResult; -export type NoCodeComponentEditingFunctionInput< +export interface NoCodeComponentEditingFunctionInput< Values extends Record = Record, Params extends Record = Record -> = { +> { values: Values; params: Params; editingInfo: EditingInfo; device: DeviceRange; -}; +} export type NoCodeComponentEditingFunction< Values extends Record = Record, @@ -414,14 +413,14 @@ export type NoCodeComponentEditingFunction< input: NoCodeComponentEditingFunctionInput ) => NoCodeComponentEditingFunctionResult; -export type NoCodeComponentAutoFunctionInput< +export interface NoCodeComponentAutoFunctionInput< Values extends Record = Record, Params extends Record = Record -> = { +> { values: { [key in keyof Values]: ResponsiveValue }; params: { [key in keyof Params]: ResponsiveValue }; devices: Devices; -}; +} export type NoCodeComponentAutoFunction< Values extends Record = Record, @@ -432,16 +431,16 @@ export type NoCodeComponentAutoFunction< } ) => any; -export type RootParameter = { +export interface RootParameter { prop: string; label: string; widgets: Array; -}; +} -export type NoCodeComponentDefinition< +export interface NoCodeComponentDefinition< Values extends Record = Record, Params extends Record = Record -> = { +> { id: string; schema: Array; type?: string | string[]; @@ -458,12 +457,12 @@ export type NoCodeComponentDefinition< }) => SidebarPreviewVariant | undefined; allowSave?: boolean; rootParams?: RootParameter[]; -}; +} /** * @internal */ -type ConfigTokens = { +interface ConfigTokens { aspectRatios: string; boxShadows: string; containerWidths: number; @@ -471,23 +470,23 @@ type ConfigTokens = { fonts: ThemeFont; icons: string; space: ThemeSpace; -}; +} -export type InlineTypeDefinition = { +export interface InlineTypeDefinition { widget: Widget; responsiveness?: "always" | "optional" | "never"; type: "inline"; defaultValue: Value; validate?: (value: any) => boolean; -}; +} -export type ExternalTypeDefinition = { +export interface ExternalTypeDefinition { widgets?: Array; responsiveness?: "always" | "optional" | "never"; type: "external"; -}; +} -export type TokenTypeDefinition = { +export interface TokenTypeDefinition { widget?: Widget; responsiveness?: "always" | "optional" | "never"; type: "token"; @@ -496,7 +495,7 @@ export type TokenTypeDefinition = { extraValues?: Array; allowCustom?: boolean; validate?: (value: any) => boolean; -}; +} export type CustomTypeDefinition = | InlineTypeDefinition @@ -507,13 +506,13 @@ export type CustomTypeDefinition = * Backend types */ -export type Document = { +export interface Document { id: string; version: number; entry: NoCodeComponentEntry; -}; +} -export type Backend = { +export interface Backend { documents: { get: (payload: { id: string; locale?: string }) => Promise; create: (payload: Omit) => Promise; @@ -534,13 +533,13 @@ export type Backend = { }) => Promise>; delete: (payload: { id: string }) => Promise; }; -}; +} /** * Config */ -export type Config = { +export interface Config { backend: Backend; components?: Array>; devices?: ConfigDevices; @@ -554,9 +553,9 @@ export type Config = { } & { [key: string & Record]: Array>; }; -}; +} -export type SchemaPropShared = { +export interface SchemaPropShared { type: Type; prop: string; label?: string; @@ -572,7 +571,7 @@ export type SchemaPropShared = { description?: string; group?: string; layout?: "row" | "column"; -}; +} type ValueSchemaProp< Type extends string, @@ -593,13 +592,9 @@ type ValueSchemaProp< export type SchemaPropParams< T extends Record, Required extends boolean = false -> = Required extends true - ? { - params: T; - } - : { params?: T }; +> = Required extends true ? { params: T } : { params?: T }; -export type DeviceRange = { +export interface DeviceRange { id: string; w: number; h: number | null; @@ -607,7 +602,7 @@ export type DeviceRange = { hidden?: boolean; label?: string; isMain?: boolean; -}; +} export type Devices = DeviceRange[]; @@ -618,7 +613,7 @@ export type NoCodeComponentChangeFunction = (arg: { valuesAfterAuto: Record; }) => Record | undefined; -export type SidebarPreviewVariant = { +export interface SidebarPreviewVariant { description?: string; thumbnail?: | { @@ -626,9 +621,9 @@ export type SidebarPreviewVariant = { src: string; } | { type: "color"; color: string }; -}; +} -export type ComponentDefinitionShared = { +export interface ComponentDefinitionShared { id: Identifier; label?: string; type?: string | string[]; @@ -645,15 +640,15 @@ export type ComponentDefinitionShared = { hideTemplates?: boolean; allowSave?: boolean; -}; +} -export type NoCodeComponentProps = { +export interface NoCodeComponentProps { __easyblocks: { id: string; isEditing: boolean; isSelected?: boolean; }; -}; +} export type SerializedRenderableComponentDefinition = ComponentDefinitionShared & { @@ -666,36 +661,36 @@ export type SerializedLinkComponentDefinition = ComponentDefinitionShared; export type SerializedTextModifierDefinition = ComponentDefinitionShared; -export type SerializedComponentDefinitions = { +export interface SerializedComponentDefinitions { components: SerializedRenderableComponentDefinition[]; actions: SerializedActionComponentDefinition[]; links: SerializedLinkComponentDefinition[]; textModifiers: SerializedTextModifierDefinition[]; -}; +} export type SerializedComponentDefinition = | SerializedRenderableComponentDefinition | SerializedActionComponentDefinition | SerializedLinkComponentDefinition; -export type NonEmptyRenderableContent = { +export interface NonEmptyRenderableContent { renderableContent: CompiledShopstoryComponentConfig; configAfterAuto?: any; -}; +} -export type EmptyRenderableContent = { +export interface EmptyRenderableContent { renderableContent: null; -}; +} export type RenderableContent = | EmptyRenderableContent | NonEmptyRenderableContent; -export type RenderableDocument = { +export interface RenderableDocument { renderableContent: CompiledShopstoryComponentConfig | null; meta: CompilationMetadata; configAfterAuto?: NoCodeComponentEntry; -}; +} export type AnyField = Field & { [key: string]: any }; @@ -741,26 +736,27 @@ export type FieldPortal = hidden?: boolean; }; -export type CompiledComponentConfigBase< +export interface CompiledComponentConfigBase< Identifier extends string = string, Props extends Record = Record -> = { +> { _component: Identifier; // instance of the component (unique id) _id: string; props: Props; -}; +} -export type EditingInfoBase = { +export interface EditingInfoBase { direction?: "horizontal" | "vertical"; noInline?: boolean; -}; +} -export type WidthInfo = { +export interface WidthInfo { auto: TrulyResponsiveValue; width: TrulyResponsiveValue; -}; +} -export type CompiledCustomComponentConfig = CompiledComponentConfigBase & { +export interface CompiledCustomComponentConfig + extends CompiledComponentConfigBase { components: { [key: string]: (CompiledComponentConfig | ReactElement)[]; }; @@ -773,31 +769,30 @@ export type CompiledCustomComponentConfig = CompiledComponentConfigBase & { }; }; }; -}; +} -export type CompiledStyled = { +export interface CompiledStyled { [key: string]: any; -}; +} -export type CompiledShopstoryComponentConfig = CompiledCustomComponentConfig & { - styled: { - [key: string]: CompiledStyled; - }; -}; +export interface CompiledShopstoryComponentConfig + extends CompiledCustomComponentConfig { + styled: Record; +} export type CompiledComponentConfig = CompiledShopstoryComponentConfig; -export type ComponentPlaceholder = { +export interface ComponentPlaceholder { width: number; height: number; label?: string; -}; +} -export type EditorSidebarPreviewOptions = { +export interface EditorSidebarPreviewOptions { breakpointIndex: string; devices: Devices; contextParams: ContextParams; -}; +} export interface ConfigModel { id: string; @@ -807,22 +802,22 @@ export interface ConfigModel { created_at: string; } -export type ExternalWithSchemaProp = { +export interface ExternalWithSchemaProp { id: string; externalReference: ExternalReference; schemaProp: ExternalSchemaProp; -}; +} -export type CompilationMetadata = { +export interface CompilationMetadata { vars: { devices: Devices; locale: string; definitions: SerializedComponentDefinitions; [key: string]: any; }; -}; +} -export type CompilerModule = { +export interface CompilerModule { compile: ( content: NoCodeComponentEntry, config: Config, @@ -832,6 +827,7 @@ export type CompilerModule = { configAfterAuto?: any; meta: CompilationMetadata; }; + /** * We need findResources function that also comes from the cloud. * Without it we would have to analyse the config in ShopstoryClient in order to find resources to be fetched. @@ -842,56 +838,59 @@ export type CompilerModule = { config: Config, contextParams: ContextParams ) => ExternalWithSchemaProp[]; + validate: (input: unknown) => | { isValid: true; input: Document | NoCodeComponentEntry | null | undefined; } | { isValid: false }; -}; +} -export type EditingField = { +export interface EditingField { type: "field"; path: string; label?: string; group?: string; visible?: boolean; -}; +} -export type EditingComponentFields = { +export interface EditingComponentFields { type: "fields"; path: string; filters?: { group?: ScalarOrCollection; }; -}; +} export type AnyEditingField = EditingField | EditingComponentFields; -export type ChildComponentEditingInfo = { +export interface ChildComponentEditingInfo { selectable?: boolean; direction?: "horizontal" | "vertical"; fields: Array; -}; +} -export type EditingInfo = { +export interface EditingInfo { fields: Array; components: Record>; -}; +} -export type NoCodeComponentEditingFunctionResult = { +export interface NoCodeComponentEditingFunctionResult { fields?: Array; components?: { [field: string]: ScalarOrCollection>; }; -}; +} -type FetchResourceResolvedResult = { +export interface FetchResourceResolvedResult { type: string & Record; value: T; -}; +} -type FetchResourceRejectedResult = { error: Error }; +interface FetchResourceRejectedResult { + error: Error; +} // {} in TS means any non nullish value and it's used on purpose here // eslint-disable-next-line @typescript-eslint/ban-types @@ -903,14 +902,14 @@ export type FetchResourceResult = export type FetchOutputBasicResources = Record; -export type FetchCompoundResourceResultValue< +export interface FetchCompoundResourceResultValue< Type extends string = string, Value extends NonNullish = NonNullish -> = { +> { type: Type; value: Value; label?: string; -}; +} export type FetchCompoundTextResourceResultValue = FetchCompoundResourceResultValue<"text", string>; @@ -921,14 +920,14 @@ export type FetchCompoundResourceResultValues = Record< | FetchCompoundResourceResultValue, NonNullish> >; -export type ExternalDataCompoundResourceResolvedResult = { +export interface ExternalDataCompoundResourceResolvedResult { type: "object"; value: FetchCompoundResourceResultValues; -}; +} -export type ExternalDataCompoundResourceRejectedResult = { +export interface ExternalDataCompoundResourceRejectedResult { error: Error; -}; +} export type FetchOutputCompoundResources = Record< string, @@ -941,11 +940,11 @@ export type FetchOutputResources = Record< (FetchOutputBasicResources | FetchOutputCompoundResources)[string] >; -export type RequestedExternalDataValue = { +export interface RequestedExternalDataValue { id: ExternalReference["id"]; widgetId: string; params?: ExternalParams; -}; +} export type RequestedExternalData = Record; @@ -954,11 +953,11 @@ export type ExternalData = Record< (FetchOutputBasicResources | FetchOutputCompoundResources)[string] >; -export type LocalReference = { +export interface LocalReference { id: string; value: Value; widgetId: string; -}; +} export type LocalTextReference = Omit< LocalReference, @@ -970,30 +969,30 @@ export type CompiledLocalTextReference = Omit< "id" | "widgetId" > & { id: `local.${string}`; widgetId: "@easyblocks/local-text" }; -export type ExternalReferenceEmpty = { +export interface ExternalReferenceEmpty { id: null; widgetId: string; -}; +} -export type ExternalReferenceNonEmpty< +export interface ExternalReferenceNonEmpty< Identifier extends NonNullish = NonNullish -> = { +> { id: Identifier; widgetId: string; key?: string; -}; +} export type ExternalReference = | ExternalReferenceEmpty | ExternalReferenceNonEmpty; -export type LocalValue = { +export interface LocalValue { value: T; widgetId: string; -}; +} -export type TokenValue = { +export interface TokenValue { value: T; tokenId?: string; widgetId?: string; -}; +} diff --git a/packages/design-system/src/ThumbnailButton.tsx b/packages/design-system/src/ThumbnailButton.tsx index 321d0bae..c4071b2c 100644 --- a/packages/design-system/src/ThumbnailButton.tsx +++ b/packages/design-system/src/ThumbnailButton.tsx @@ -5,30 +5,30 @@ import { ButtonGhost } from "./buttons"; import { Icons } from "./icons"; import { Typography } from "./Typography"; -export type ColorThumbnail = { +export interface ColorThumbnail { type: "color"; color: string; -}; +} -export type ImageThumbnail = { +export interface ImageThumbnail { type: "image"; src: string; -}; +} -export type IconThumbnail = { +export interface IconThumbnail { type: "icon"; icon: "link" | "grid_3x3"; -}; +} export type ThumbnailType = ColorThumbnail | ImageThumbnail | IconThumbnail; -export type ThumbnailButtonProps = { +export interface ThumbnailButtonProps { onClick?: () => void; label: string; description?: string; thumbnail?: ThumbnailType; disabled?: boolean; -}; +} const Preview = styled.div` display: flex; @@ -77,6 +77,8 @@ const Labels = styled.div` display: grid; `; +const containerStyle = { display: "grid", width: "100%" }; + export function ThumbnailButton({ onClick, label, @@ -84,27 +86,9 @@ export function ThumbnailButton({ thumbnail, disabled, }: ThumbnailButtonProps) { - let preview: JSX.Element; - - if (thumbnail?.type === "image") { - preview =