feat: support upload page icon (#59)

This commit is contained in:
Kilu.He
2025-03-05 14:46:22 +08:00
committed by GitHub
parent 2c322d1dcc
commit 8ac85ebab5
10 changed files with 149 additions and 41 deletions

View File

@@ -996,6 +996,7 @@ export interface ViewMetaProps {
extra?: ViewExtra | null; extra?: ViewExtra | null;
readOnly?: boolean; readOnly?: boolean;
updatePage?: (viewId: string, data: UpdatePagePayload) => Promise<void>; updatePage?: (viewId: string, data: UpdatePagePayload) => Promise<void>;
uploadFile?: (file: File) => Promise<string>;
onEnter?: (text: string) => void; onEnter?: (text: string) => void;
maxWidth?: number; maxWidth?: number;
} }

View File

@@ -0,0 +1,26 @@
export default function LoadingDots({
className,
colors = ['#00b5ff', '#e3006d', '#f7931e'],
}: {
className?: string;
colors?: [string, string, string];
}) {
return (
<div className={className}>
<div
style={{
width: `30px`,
aspectRatio: '2',
background: `
radial-gradient(circle closest-side, ${colors[0]} 90%, transparent) 0% 50%,
radial-gradient(circle closest-side, ${colors[1]} 90%, transparent) 50% 50%,
radial-gradient(circle closest-side, ${colors[2]} 90%, transparent) 100% 50%
`,
backgroundSize: 'calc(100%/3) 50%',
backgroundRepeat: 'no-repeat',
animation: 'dots-loading 1s infinite linear',
}}
/>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import FileDropzone from '@/components/_shared/file-dropzone/FileDropzone'; import FileDropzone from '@/components/_shared/file-dropzone/FileDropzone';
import LoadingDots from '@/components/_shared/LoadingDots';
import { notify } from '@/components/_shared/notify'; import { notify } from '@/components/_shared/notify';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -10,24 +11,28 @@ export function UploadImage({ onDone, uploadAction }: {
uploadAction?: (file: File) => Promise<string> uploadAction?: (file: File) => Promise<string>
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const handleFileChange = useCallback(async (files: File[]) => { const [loading, setLoading] = React.useState(false);
const handleFileChange = useCallback(async(files: File[]) => {
setLoading(true);
const file = files[0]; const file = files[0];
if (!file) return; if(!file) return;
try { try {
const url = await uploadAction?.(file); const url = await uploadAction?.(file);
if (!url) { if(!url) {
onDone?.(URL.createObjectURL(file)); onDone?.(URL.createObjectURL(file));
return; return;
} }
onDone?.(url); onDone?.(url);
// eslint-disable-next-line // eslint-disable-next-line
} catch (e: any) { } catch(e: any) {
notify.error(e.message); notify.error(e.message);
onDone?.(URL.createObjectURL(file)); onDone?.(URL.createObjectURL(file));
} finally {
setLoading(false);
} }
}, [onDone, uploadAction]); }, [onDone, uploadAction]);
@@ -39,6 +44,10 @@ export function UploadImage({ onDone, uploadAction }: {
onChange={handleFileChange} onChange={handleFileChange}
accept={ALLOWED_IMAGE_EXTENSIONS.join(',')} accept={ALLOWED_IMAGE_EXTENSIONS.join(',')}
/> />
{loading &&
<div className={'absolute bg-bg-body z-10 opacity-90 flex items-center inset-0 justify-center w-full h-full'}>
<LoadingDots />
</div>}
</div> </div>
); );

View File

@@ -1,6 +1,7 @@
import { ViewIconType } from '@/application/types'; import { ViewIconType } from '@/application/types';
import { EmojiPicker } from '@/components/_shared/emoji-picker'; import { EmojiPicker } from '@/components/_shared/emoji-picker';
import IconPicker from '@/components/_shared/icon-picker/IconPicker'; import IconPicker from '@/components/_shared/icon-picker/IconPicker';
import { UploadImage } from '@/components/_shared/image-upload';
import { Popover } from '@/components/_shared/popover'; import { Popover } from '@/components/_shared/popover';
import { TabPanel, ViewTab, ViewTabs } from '@/components/_shared/tabs/ViewTabs'; import { TabPanel, ViewTab, ViewTabs } from '@/components/_shared/tabs/ViewTabs';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
@@ -8,7 +9,7 @@ import { PopoverProps } from '@mui/material/Popover';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
function ChangeIconPopover ({ function ChangeIconPopover({
open, open,
anchorEl, anchorEl,
onClose, onClose,
@@ -20,16 +21,20 @@ function ChangeIconPopover ({
removeIcon, removeIcon,
anchorPosition, anchorPosition,
hideRemove, hideRemove,
uploadEnabled,
onUploadFile,
}: { }: {
open: boolean, open: boolean,
anchorEl?: HTMLElement | null, anchorEl?: HTMLElement | null,
anchorPosition?: PopoverProps['anchorPosition'], anchorPosition?: PopoverProps['anchorPosition'],
onClose: () => void, onClose: () => void,
defaultType: 'emoji' | 'icon', defaultType: 'emoji' | 'icon' | 'upload',
emojiEnabled?: boolean, emojiEnabled?: boolean,
uploadEnabled?: boolean,
iconEnabled?: boolean, iconEnabled?: boolean,
popoverProps?: Partial<PopoverProps>, popoverProps?: Partial<PopoverProps>,
onSelectIcon?: (icon: { ty: ViewIconType, value: string, color?: string, content?: string }) => void, onSelectIcon?: (icon: { ty: ViewIconType, value: string, color?: string, content?: string }) => void,
onUploadFile?: (file: File) => Promise<string>,
removeIcon?: () => void, removeIcon?: () => void,
hideRemove?: boolean, hideRemove?: boolean,
}) { }) {
@@ -80,6 +85,16 @@ function ChangeIconPopover ({
/> />
) )
} }
{
uploadEnabled && (
<ViewTab
className={'flex items-center flex-row justify-center gap-1.5'}
value={'upload'}
label={'Upload'}
data-testid="upload-tab"
/>
)
}
</ViewTabs> </ViewTabs>
{!hideRemove && <Button {!hideRemove && <Button
@@ -126,6 +141,24 @@ function ChangeIconPopover ({
hideRemove hideRemove
/> />
</TabPanel>} </TabPanel>}
{uploadEnabled && <TabPanel
index={'upload'}
value={value}
>
<div className={'pt-4 relative pb-2'}>
<UploadImage
onDone={(url) => {
onSelectIcon?.({
ty: ViewIconType.URL,
value: url,
});
handleClose();
}}
uploadAction={onUploadFile}
/>
</div>
</TabPanel>}
</Popover> </Popover>
); );
} }

View File

@@ -16,17 +16,17 @@ import React, { Suspense, useCallback, useMemo } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import ViewMetaPreview from 'src/components/view-meta/ViewMetaPreview'; import ViewMetaPreview from 'src/components/view-meta/ViewMetaPreview';
function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) { function DatabaseView({ viewMeta, uploadFile, ...props }: ViewComponentProps) {
const [search, setSearch] = useSearchParams(); const [search, setSearch] = useSearchParams();
const outline = useAppOutline(); const outline = useAppOutline();
const iidIndex = viewMeta.viewId; const iidIndex = viewMeta.viewId;
const view = useMemo(() => { const view = useMemo(() => {
if (!outline || !iidIndex) return; if(!outline || !iidIndex) return;
return findView(outline || [], iidIndex); return findView(outline || [], iidIndex);
}, [outline, iidIndex]); }, [outline, iidIndex]);
const visibleViewIds = useMemo(() => { const visibleViewIds = useMemo(() => {
if (!view) return []; if(!view) return [];
return [view.view_id, ...(view.children?.map(v => v.view_id) || [])]; return [view.view_id, ...(view.children?.map(v => v.view_id) || [])];
}, [view]); }, [view]);
@@ -58,11 +58,11 @@ function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) {
const doc = props.doc; const doc = props.doc;
const database = doc?.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase; const database = doc?.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database) as YDatabase;
const skeleton = useMemo(() => { const skeleton = useMemo(() => {
if (rowId) { if(rowId) {
return <DocumentSkeleton />; return <DocumentSkeleton />;
} }
switch (viewMeta.layout) { switch(viewMeta.layout) {
case ViewLayout.Grid: case ViewLayout.Grid:
return <GridSkeleton includeTitle={false} />; return <GridSkeleton includeTitle={false} />;
case ViewLayout.Board: case ViewLayout.Board:
@@ -74,7 +74,7 @@ function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) {
} }
}, [rowId, viewMeta.layout]); }, [rowId, viewMeta.layout]);
if (!viewId || !doc || !database) return null; if(!viewId || !doc || !database) return null;
return ( return (
<div <div
@@ -87,6 +87,7 @@ function DatabaseView ({ viewMeta, ...props }: ViewComponentProps) {
{...viewMeta} {...viewMeta}
readOnly={props.readOnly} readOnly={props.readOnly}
updatePage={props.updatePage} updatePage={props.updatePage}
uploadFile={uploadFile}
/>} />}
<Suspense fallback={skeleton}> <Suspense fallback={skeleton}>

View File

@@ -40,7 +40,7 @@ function ViewItem({ view, width, level = 0, renderExtra, expandIds, toggleExpand
const selectedViewId = useAppViewId(); const selectedViewId = useAppViewId();
const viewId = view.view_id; const viewId = view.view_id;
const selected = selectedViewId === viewId; const selected = selectedViewId === viewId;
const { updatePage } = useAppHandlers(); const { updatePage, uploadFile } = useAppHandlers();
const isExpanded = expandIds.includes(viewId); const isExpanded = expandIds.includes(viewId);
const [hovered, setHovered] = React.useState<boolean>(false); const [hovered, setHovered] = React.useState<boolean>(false);
@@ -57,6 +57,11 @@ function ViewItem({ view, width, level = 0, renderExtra, expandIds, toggleExpand
/></span>; /></span>;
}, [isExpanded, level, toggleExpand, viewId]); }, [isExpanded, level, toggleExpand, viewId]);
const onUploadFile = useCallback(async(file: File) => {
if(!uploadFile) return Promise.reject();
return uploadFile(viewId, file);
}, [uploadFile, viewId]);
const renderItem = useMemo(() => { const renderItem = useMemo(() => {
if(!view) return null; if(!view) return null;
@@ -165,6 +170,8 @@ function ViewItem({ view, width, level = 0, renderExtra, expandIds, toggleExpand
onClose={() => { onClose={() => {
setIconPopoverAnchorEl(null); setIconPopoverAnchorEl(null);
}} }}
uploadEnabled
onUploadFile={onUploadFile}
popoverProps={popoverProps} popoverProps={popoverProps}
onSelectIcon={(icon) => { onSelectIcon={(icon) => {
if(icon.ty === ViewIconType.Icon) { if(icon.ty === ViewIconType.Icon) {

View File

@@ -38,20 +38,35 @@ function MorePageActions({ view, onClose }: {
const { const {
updatePage, updatePage,
uploadFile,
} = useAppHandlers(); } = useAppHandlers();
const { t } = useTranslation(); const { t } = useTranslation();
const handleChangeIcon = useCallback(async (icon: { ty: ViewIconType, value: string }) => { const viewId = view.view_id;
const onUploadFile = useCallback(async(file: File) => {
if(!uploadFile) return Promise.reject();
return uploadFile(viewId, file);
}, [uploadFile, viewId]);
const handleChangeIcon = useCallback(async(icon: { ty: ViewIconType, value: string, color?: string }) => {
try { try {
await updatePage?.(view.view_id, { await updatePage?.(view.view_id, {
icon: icon, icon: icon.ty === ViewIconType.Icon ? {
ty: ViewIconType.Icon,
value: JSON.stringify({
color: icon.color,
groupName: icon.value.split('/')[0],
iconName: icon.value.split('/')[1],
}),
} : icon,
name: view.name, name: view.name,
extra: view.extra || {}, extra: view.extra || {},
}); });
setIconPopoverAnchorEl(null); setIconPopoverAnchorEl(null);
onClose?.(); onClose?.();
// eslint-disable-next-line // eslint-disable-next-line
} catch (e: any) { } catch(e: any) {
notify.error(e); notify.error(e);
} }
}, [onClose, updatePage, view.extra, view.name, view.view_id]); }, [onClose, updatePage, view.extra, view.name, view.view_id]);
@@ -63,14 +78,14 @@ function MorePageActions({ view, onClose }: {
const actions = useMemo(() => { const actions = useMemo(() => {
return [{ return [{
label: t('button.rename'), label: t('button.rename'),
icon: <EditIcon/>, icon: <EditIcon />,
onClick: () => { onClick: () => {
setRenameModalOpen(true); setRenameModalOpen(true);
onClose?.(); onClose?.();
}, },
}, { }, {
label: t('disclosureAction.changeIcon'), label: t('disclosureAction.changeIcon'),
icon: <ChangeIcon/>, icon: <ChangeIcon />,
onClick: (e: React.MouseEvent<HTMLButtonElement>) => { onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
setIconPopoverAnchorEl(e.currentTarget); setIconPopoverAnchorEl(e.currentTarget);
}, },
@@ -96,25 +111,25 @@ function MorePageActions({ view, onClose }: {
viewId={view.view_id} viewId={view.view_id}
movePopoverOrigins={popoverProps} movePopoverOrigins={popoverProps}
/> />
<Divider className={'w-full'}/> <Divider className={'w-full'} />
<Button <Button
size={'small'} size={'small'}
className={'px-3 py-1 justify-start'} className={'px-3 py-1 justify-start'}
color={'inherit'} color={'inherit'}
onClick={() => { onClick={() => {
if (!currentWorkspaceId) return; if(!currentWorkspaceId) return;
onClose?.(); onClose?.();
window.open(`/app/${currentWorkspaceId}/${view.view_id}`, '_blank'); window.open(`/app/${currentWorkspaceId}/${view.view_id}`, '_blank');
}} }}
startIcon={<OpenInBrowserIcon className={'w-4 h-4'}/>} startIcon={<OpenInBrowserIcon className={'w-4 h-4'} />}
> >
{t('disclosureAction.openNewTab')} {t('disclosureAction.openNewTab')}
</Button> </Button>
<Suspense fallback={null}> <Suspense fallback={null}>
<ChangeIconPopover <ChangeIconPopover
iconEnabled={false} iconEnabled
defaultType={'emoji'} defaultType={'emoji'}
open={openIconPopover} open={openIconPopover}
anchorEl={iconPopoverAnchorEl} anchorEl={iconPopoverAnchorEl}
@@ -122,6 +137,8 @@ function MorePageActions({ view, onClose }: {
onClose?.(); onClose?.();
setIconPopoverAnchorEl(null); setIconPopoverAnchorEl(null);
}} }}
onUploadFile={onUploadFile}
uploadEnabled
popoverProps={popoverProps} popoverProps={popoverProps}
onSelectIcon={handleChangeIcon} onSelectIcon={handleChangeIcon}
removeIcon={handleRemoveIcon} removeIcon={handleRemoveIcon}

View File

@@ -24,6 +24,7 @@ export const Document = (props: DocumentProps) => {
updatePage, updatePage,
onRendered, onRendered,
onEditorConnected, onEditorConnected,
uploadFile,
} = props; } = props;
const blockId = search.get('blockId') || undefined; const blockId = search.get('blockId') || undefined;
@@ -76,6 +77,7 @@ export const Document = (props: DocumentProps) => {
updatePage={updatePage} updatePage={updatePage}
onEnter={readOnly ? undefined : handleEnter} onEnter={readOnly ? undefined : handleEnter}
maxWidth={988} maxWidth={988}
uploadFile={uploadFile}
/> />
<Suspense fallback={<EditorSkeleton />}> <Suspense fallback={<EditorSkeleton />}>
<div className={'flex justify-center w-full'}> <div className={'flex justify-center w-full'}>

View File

@@ -15,6 +15,7 @@ function AddIconCover({
setIconAnchorEl, setIconAnchorEl,
maxWidth, maxWidth,
visible, visible,
onUploadFile,
}: { }: {
visible: boolean; visible: boolean;
hasIcon: boolean; hasIcon: boolean;
@@ -24,6 +25,7 @@ function AddIconCover({
iconAnchorEl: HTMLElement | null; iconAnchorEl: HTMLElement | null;
setIconAnchorEl: (el: HTMLElement | null) => void; setIconAnchorEl: (el: HTMLElement | null) => void;
maxWidth?: number; maxWidth?: number;
onUploadFile: (file: File) => Promise<string>;
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -80,6 +82,8 @@ function AddIconCover({
setIconAnchorEl(null); setIconAnchorEl(null);
onUpdateIcon?.({ ty: ViewIconType.Emoji, value: '' }); onUpdateIcon?.({ ty: ViewIconType.Emoji, value: '' });
}} }}
uploadEnabled
onUploadFile={onUploadFile}
/> />
</> </>

View File

@@ -2,13 +2,13 @@ import { CoverType, ViewIconType, ViewLayout, ViewMetaCover, ViewMetaIcon, ViewM
import { notify } from '@/components/_shared/notify'; import { notify } from '@/components/_shared/notify';
import TitleEditable from '@/components/view-meta/TitleEditable'; import TitleEditable from '@/components/view-meta/TitleEditable';
import ViewCover from '@/components/view-meta/ViewCover'; import ViewCover from '@/components/view-meta/ViewCover';
import React, { lazy, Suspense, useEffect, useMemo } from 'react'; import React, { lazy, Suspense, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PageIcon from '@/components/_shared/view-icon/PageIcon'; import PageIcon from '@/components/_shared/view-icon/PageIcon';
const AddIconCover = lazy(() => import('@/components/view-meta/AddIconCover')); const AddIconCover = lazy(() => import('@/components/view-meta/AddIconCover'));
export function ViewMetaPreview ({ export function ViewMetaPreview({
icon: iconProp, icon: iconProp,
cover: coverProp, cover: coverProp,
name, name,
@@ -18,6 +18,7 @@ export function ViewMetaPreview ({
updatePage, updatePage,
onEnter, onEnter,
maxWidth, maxWidth,
uploadFile,
}: ViewMetaProps) { }: ViewMetaProps) {
const [iconAnchorEl, setIconAnchorEl] = React.useState<null | HTMLElement>(null); const [iconAnchorEl, setIconAnchorEl] = React.useState<null | HTMLElement>(null);
const [cover, setCover] = React.useState<ViewMetaCover | null>(coverProp || null); const [cover, setCover] = React.useState<ViewMetaCover | null>(coverProp || null);
@@ -32,21 +33,21 @@ export function ViewMetaPreview ({
}, [iconProp]); }, [iconProp]);
const coverType = useMemo(() => { const coverType = useMemo(() => {
if (cover && [CoverType.NormalColor, CoverType.GradientColor].includes(cover.type)) { if(cover && [CoverType.NormalColor, CoverType.GradientColor].includes(cover.type)) {
return 'color'; return 'color';
} }
if (CoverType.BuildInImage === cover?.type) { if(CoverType.BuildInImage === cover?.type) {
return 'built_in'; return 'built_in';
} }
if (cover && [CoverType.CustomImage, CoverType.UpsplashImage].includes(cover.type)) { if(cover && [CoverType.CustomImage, CoverType.UpsplashImage].includes(cover.type)) {
return 'custom'; return 'custom';
} }
}, [cover]); }, [cover]);
const coverValue = useMemo(() => { const coverValue = useMemo(() => {
if (coverType === CoverType.BuildInImage) { if(coverType === CoverType.BuildInImage) {
return { return {
1: '/covers/m_cover_image_1.png', 1: '/covers/m_cover_image_1.png',
2: '/covers/m_cover_image_2.png', 2: '/covers/m_cover_image_2.png',
@@ -63,8 +64,8 @@ export function ViewMetaPreview ({
const [isHover, setIsHover] = React.useState(false); const [isHover, setIsHover] = React.useState(false);
const handleUpdateIcon = React.useCallback(async (icon: { ty: ViewIconType, value: string }) => { const handleUpdateIcon = React.useCallback(async(icon: { ty: ViewIconType, value: string }) => {
if (!updatePage || !viewId) return; if(!updatePage || !viewId) return;
setIcon(icon); setIcon(icon);
try { try {
await updatePage(viewId, { await updatePage(viewId, {
@@ -73,15 +74,15 @@ export function ViewMetaPreview ({
extra: extra || {}, extra: extra || {},
}); });
// eslint-disable-next-line // eslint-disable-next-line
} catch (e: any) { } catch(e: any) {
notify.error(e.message); notify.error(e.message);
} }
}, [updatePage, viewId, name, extra]); }, [updatePage, viewId, name, extra]);
const handleUpdateName = React.useCallback(async (newName: string) => { const handleUpdateName = React.useCallback(async(newName: string) => {
if (!updatePage || !viewId) return; if(!updatePage || !viewId) return;
try { try {
if (name === newName) return; if(name === newName) return;
await updatePage(viewId, { await updatePage(viewId, {
icon: icon || { icon: icon || {
ty: ViewIconType.Emoji, ty: ViewIconType.Emoji,
@@ -91,16 +92,16 @@ export function ViewMetaPreview ({
extra: extra || {}, extra: extra || {},
}); });
// eslint-disable-next-line // eslint-disable-next-line
} catch (e: any) { } catch(e: any) {
notify.error(e.message); notify.error(e.message);
} }
}, [name, updatePage, viewId, icon, extra]); }, [name, updatePage, viewId, icon, extra]);
const handleUpdateCover = React.useCallback(async (cover?: { const handleUpdateCover = React.useCallback(async(cover?: {
type: CoverType; type: CoverType;
value: string; value: string;
}) => { }) => {
if (!updatePage || !viewId) return; if(!updatePage || !viewId) return;
setCover(cover ? cover : null); setCover(cover ? cover : null);
try { try {
@@ -116,11 +117,16 @@ export function ViewMetaPreview ({
}, },
}); });
// eslint-disable-next-line // eslint-disable-next-line
} catch (e: any) { } catch(e: any) {
notify.error(e.message); notify.error(e.message);
} }
}, [extra, icon, name, updatePage, viewId]); }, [extra, icon, name, updatePage, viewId]);
const onUploadFile = useCallback(async(file: File) => {
if(!uploadFile) return Promise.reject();
return uploadFile(file);
}, [uploadFile]);
const ref = React.useRef<HTMLDivElement>(null); const ref = React.useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
@@ -133,13 +139,13 @@ export function ViewMetaPreview ({
setIsHover(false); setIsHover(false);
}; };
if (el) { if(el) {
el.addEventListener('mouseenter', handleMouseEnter); el.addEventListener('mouseenter', handleMouseEnter);
el.addEventListener('mouseleave', handleMouseLeave); el.addEventListener('mouseleave', handleMouseLeave);
} }
return () => { return () => {
if (el) { if(el) {
el.removeEventListener('mouseenter', handleMouseEnter); el.removeEventListener('mouseenter', handleMouseEnter);
el.removeEventListener('mouseleave', handleMouseLeave); el.removeEventListener('mouseleave', handleMouseLeave);
} }
@@ -174,8 +180,10 @@ export function ViewMetaPreview ({
maxWidth={maxWidth} maxWidth={maxWidth}
iconAnchorEl={iconAnchorEl} iconAnchorEl={iconAnchorEl}
setIconAnchorEl={setIconAnchorEl} setIconAnchorEl={setIconAnchorEl}
onUploadFile={onUploadFile}
/></Suspense>} /></Suspense>}
</div> </div>
<div <div
className={`relative mb-6 flex items-center overflow-visible w-full justify-center`} className={`relative mb-6 flex items-center overflow-visible w-full justify-center`}
@@ -192,7 +200,7 @@ export function ViewMetaPreview ({
{icon?.value ? {icon?.value ?
<div <div
onClick={e => { onClick={e => {
if (readOnly) return; if(readOnly) return;
setIconAnchorEl(e.currentTarget); setIconAnchorEl(e.currentTarget);
}} }}
className={`view-icon flex h-[1.25em] px-1.5 items-center justify-center ${readOnly ? 'cursor-default' : 'cursor-pointer hover:bg-fill-list-hover '}`} className={`view-icon flex h-[1.25em] px-1.5 items-center justify-center ${readOnly ? 'cursor-default' : 'cursor-pointer hover:bg-fill-list-hover '}`}