补齐图片编辑器素材和画布搜索

普通上传默认落入上传素材文件夹

缺少上传素材文件夹时首次上传前自动创建

素材库和图层侧栏增加前缀优先搜索

更新图片画布技术文档和定向测试
This commit is contained in:
2026-06-23 17:03:11 +08:00
parent 9ad2152816
commit 63cd92cc3f
12 changed files with 398 additions and 20 deletions
@@ -27,6 +27,7 @@ import { ImageCanvasAssetRowView } from './ImageCanvasAssetRowView';
import type {
GroupedEditorAssetFolder,
UploadFilesOptions,
UploadRequestOptions,
} from './ImageCanvasAssetLibraryPanelView';
type ImageCanvasAssetFolderSectionViewProps = {
@@ -50,7 +51,7 @@ type ImageCanvasAssetFolderSectionViewProps = {
setSelectedAssetIds: Dispatch<SetStateAction<Set<string>>>;
updateAssetMoveDropFolder: (folderId: string | null) => void;
addUploadedFiles: (files: FileList | File[], options?: UploadFilesOptions) => void;
requestUpload: (target: UploadTarget) => void;
requestUpload: (target: UploadTarget, options?: UploadRequestOptions) => void;
moveAssetToFolder: (assetId: string, folderId: string) => void;
toggleAssetFolder: (folderId: string) => void;
startRenamingFolder: (folder: EditorAssetFolder) => void;
@@ -220,7 +221,7 @@ export function ImageCanvasAssetFolderSectionView({
icon={ImagePlus}
onClick={() => {
setActiveUploadFolderId(folder.id);
requestUpload('asset');
requestUpload('asset', { folderId: folder.id });
}}
/>
</div>
@@ -10,6 +10,7 @@ import {
createAssetMarqueeSelectionRect,
createLocalAssetFolder,
deleteAssetFolderLocally,
filterGroupedAssetsBySearch,
getSelectableAssets,
groupAssetsByFolder,
moveAssetToFolderLocally,
@@ -22,7 +23,9 @@ import {
resolveAllAssetSelection,
resolveAssetFolderIdFromPoint,
resolveDefaultAssetFolder,
resolvePreferredAssetUploadFolder,
resolvePinnedAssetMoveFolderId,
resolveUploadAssetFolder,
selectUploadedAssetsInRect,
toggleAssetFolderCollapsed,
toggleAssetSelection,
@@ -86,6 +89,48 @@ describe('ImageCanvasAssetLibraryModel', () => {
]);
});
it('filters grouped assets by folder and asset search text', () => {
const groupedAssets = groupAssetsByFolder(
[
createFolder(),
createFolder({ id: 'folder-uploaded', label: '上传素材' }),
],
[
createAsset({ id: 'asset-a', label: '草地角色', folderId: 'project' }),
createAsset({
id: 'asset-b',
label: '音效',
folderId: 'folder-uploaded',
model: 'audio1.0',
}),
createAsset({
id: 'asset-c',
label: '角色音效',
folderId: 'folder-uploaded',
}),
],
);
expect(
filterGroupedAssetsBySearch(groupedAssets, 'audio').map((folder) => ({
id: folder.id,
assets: folder.assets.map((asset) => asset.id),
})),
).toEqual([{ id: 'folder-uploaded', assets: ['asset-b'] }]);
expect(
filterGroupedAssetsBySearch(groupedAssets, '音').map((folder) => ({
id: folder.id,
assets: folder.assets.map((asset) => asset.id),
})),
).toEqual([{ id: 'folder-uploaded', assets: ['asset-b', 'asset-c'] }]);
expect(
filterGroupedAssetsBySearch(groupedAssets, '上传').map((folder) => ({
id: folder.id,
assets: folder.assets.map((asset) => asset.id),
})),
).toEqual([{ id: 'folder-uploaded', assets: ['asset-b', 'asset-c'] }]);
});
it('renames assets, toggles folders and creates local folders', () => {
expect(renameAssetById([createAsset()], 'asset-a', '新名字')[0]).toMatchObject(
{ label: '新名字' },
@@ -147,6 +192,13 @@ describe('ImageCanvasAssetLibraryModel', () => {
'asset-b',
]);
expect(resolveDefaultAssetFolder(folders)?.id).toBe('project');
expect(
resolveUploadAssetFolder([
createFolder(),
createFolder({ id: 'folder-uploaded', label: '上传素材' }),
])?.id,
).toBe('folder-uploaded');
expect(resolvePreferredAssetUploadFolder(folders)?.id).toBe('project');
expect(
deleteAssetFolderLocally({
folders,
@@ -1,5 +1,6 @@
import type {
AssetMarqueeState,
CanvasLayer,
EditorAsset,
EditorAssetFolder,
} from './ImageCanvasEditorTypes';
@@ -25,6 +26,8 @@ export type GroupedAssetFolder = EditorAssetFolder & {
assets: EditorAsset[];
};
export const EDITOR_UPLOAD_ASSET_FOLDER_LABEL = '上传素材';
function isPointInRect(point: ClientPoint, rect: ClientRectLike) {
return (
point.clientX >= rect.left &&
@@ -183,6 +186,104 @@ export function groupAssetsByFolder(
}));
}
function normalizeSearchQuery(query: string) {
return query.trim().toLowerCase();
}
function matchesSearchText(query: string, values: Array<string | null | undefined>) {
if (!query) {
return true;
}
return values.some((value) => value?.toLowerCase().includes(query));
}
function startsWithSearchText(query: string, values: Array<string | null | undefined>) {
if (!query) {
return true;
}
return values.some((value) => value?.trim().toLowerCase().startsWith(query));
}
function buildAssetSearchValues(asset: EditorAsset) {
return [
asset.label,
asset.prompt,
asset.actualPrompt,
asset.model,
asset.provider,
asset.taskId,
asset.assetKind,
asset.mediaType,
];
}
function buildLayerSearchValues(layer: CanvasLayer) {
return [
layer.title,
layer.prompt,
layer.actualPrompt,
layer.model,
layer.provider,
layer.taskId,
layer.assetKind,
layer.mediaType,
];
}
function assetMatchesSearch(asset: EditorAsset, query: string) {
return matchesSearchText(query, buildAssetSearchValues(asset));
}
function assetStartsWithSearch(asset: EditorAsset, query: string) {
return startsWithSearchText(query, buildAssetSearchValues(asset));
}
export function layerMatchesSearch(layer: CanvasLayer, query: string) {
return matchesSearchText(query, buildLayerSearchValues(layer));
}
export function layerStartsWithSearch(layer: CanvasLayer, query: string) {
return startsWithSearchText(query, buildLayerSearchValues(layer));
}
export function filterGroupedAssetsBySearch(
groupedAssets: GroupedAssetFolder[],
query: string,
) {
const normalizedQuery = normalizeSearchQuery(query);
if (!normalizedQuery) {
return groupedAssets;
}
return groupedAssets
.map((folder) => {
const folderMatches =
startsWithSearchText(normalizedQuery, [folder.label]) ||
matchesSearchText(normalizedQuery, [folder.label]);
const prefixAssets = folder.assets.filter((asset) =>
assetStartsWithSearch(asset, normalizedQuery),
);
const matchingAssets = folder.assets.filter((asset) =>
assetMatchesSearch(asset, normalizedQuery),
);
return {
...folder,
assets: folderMatches
? folder.assets
: [
...prefixAssets,
...matchingAssets.filter(
(asset) =>
!prefixAssets.some(
(prefixAsset) => prefixAsset.id === asset.id,
),
),
],
};
})
.filter((folder) => folder.assets.length > 0);
}
export function getSelectableAssets(assets: EditorAsset[]) {
return assets.filter((asset) => asset.sourceKind === 'uploaded');
}
@@ -306,6 +407,20 @@ export function resolveDefaultAssetFolder(assetFolders: EditorAssetFolder[]) {
);
}
export function resolveUploadAssetFolder(assetFolders: EditorAssetFolder[]) {
return (
assetFolders.find(
(folder) => folder.label.trim() === EDITOR_UPLOAD_ASSET_FOLDER_LABEL,
) ?? null
);
}
export function resolvePreferredAssetUploadFolder(
assetFolders: EditorAssetFolder[],
) {
return resolveUploadAssetFolder(assetFolders) ?? resolveDefaultAssetFolder(assetFolders);
}
export function deleteAssetFolderLocally({
folders,
assets,
@@ -36,6 +36,11 @@ export type UploadFilesOptions = {
addToCanvas?: boolean;
};
export type UploadRequestOptions = {
folderId?: string;
addToCanvas?: boolean;
};
export type ImageCanvasAssetLibraryPanelViewProps = {
assetListRef: RefObject<HTMLDivElement | null>;
assetPointerDragRef: { current: AssetPointerDragState | null };
@@ -75,7 +80,7 @@ export type ImageCanvasAssetLibraryPanelViewProps = {
) => void;
updateAssetMoveDropFolder: (folderId: string | null) => void;
addUploadedFiles: (files: FileList | File[], options?: UploadFilesOptions) => void;
requestUpload: (target: UploadTarget) => void;
requestUpload: (target: UploadTarget, options?: UploadRequestOptions) => void;
moveAssetToFolder: (assetId: string, folderId: string) => void;
commitNewAssetFolder: () => void | Promise<void>;
toggleAssetFolder: (folderId: string) => void;
@@ -226,6 +226,37 @@ describe('ImageCanvasSidebarView', () => {
);
});
it('filters assets from the sidebar search field', () => {
const folder = createFolder();
render(
<ImageCanvasSidebarView
{...createSidebarProps({
assets: [
createAsset({ id: 'asset-a', label: '账号素材A' }),
createAsset({ id: 'asset-b', label: '火焰按钮' }),
],
groupedAssets: [
{
...folder,
assets: [
createAsset({ id: 'asset-a', label: '账号素材A' }),
createAsset({ id: 'asset-b', label: '火焰按钮' }),
],
},
],
})}
/>,
);
const sidebar = screen.getByRole('complementary', { name: '图片资源栏' });
fireEvent.change(within(sidebar).getByLabelText('搜索素材'), {
target: { value: '火焰' },
});
expect(within(sidebar).getByRole('button', { name: '添加火焰按钮' })).toBeTruthy();
expect(within(sidebar).queryByRole('button', { name: '添加账号素材A' })).toBeNull();
});
it('renders the layer panel with grouped and locked layer details', () => {
render(
<ImageCanvasSidebarView
@@ -255,6 +286,32 @@ describe('ImageCanvasSidebarView', () => {
).toBeTruthy();
});
it('filters canvas layers from the sidebar search field', () => {
render(
<ImageCanvasSidebarView
{...createSidebarProps({
activeSidebarPanel: 'layers',
layers: [
createLayer({ id: 'layer-a', title: '角色草图', zIndex: 3 }),
createLayer({ id: 'layer-b', title: '背景山脉', zIndex: 2 }),
],
})}
/>,
);
const sidebar = screen.getByRole('complementary', { name: '图片资源栏' });
fireEvent.change(within(sidebar).getByLabelText('搜索画布图层'), {
target: { value: '背景' },
});
expect(
within(sidebar).getByRole('button', { name: '选择图层背景山脉' }),
).toBeTruthy();
expect(
within(sidebar).queryByRole('button', { name: '选择图层角色草图' }),
).toBeNull();
});
it('keeps sidebar tab switching and repeated-click closing behavior', () => {
render(<SidebarTabsHarness />);
@@ -9,12 +9,20 @@ import type {
RefObject,
SetStateAction,
} from 'react';
import { useMemo, useState } from 'react';
import { PlatformTextField } from '../common/PlatformTextField';
import {
ImageCanvasAssetLibraryPanelView,
type GroupedEditorAssetFolder,
type UploadFilesOptions,
type UploadRequestOptions,
} from './ImageCanvasAssetLibraryPanelView';
import {
filterGroupedAssetsBySearch,
layerMatchesSearch,
layerStartsWithSearch,
} from './ImageCanvasAssetLibraryModel';
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
import type {
AssetMarqueeState,
@@ -78,7 +86,7 @@ export type ImageCanvasSidebarViewProps = {
) => void;
updateAssetMoveDropFolder: (folderId: string | null) => void;
addUploadedFiles: (files: FileList | File[], options?: UploadFilesOptions) => void;
requestUpload: (target: UploadTarget) => void;
requestUpload: (target: UploadTarget, options?: UploadRequestOptions) => void;
moveAssetToFolder: (assetId: string, folderId: string) => void;
commitNewAssetFolder: () => void | Promise<void>;
toggleAssetFolder: (folderId: string) => void;
@@ -111,7 +119,6 @@ export function ImageCanvasSidebarView({
assetListRef,
assetPointerDragRef,
suppressAssetClickRef,
assets,
groupedAssets,
assetFolders,
layers,
@@ -163,6 +170,38 @@ export function ImageCanvasSidebarView({
resolveContextMenuPosition,
getCanvasPointFromClient,
}: ImageCanvasSidebarViewProps) {
const [assetSearchQuery, setAssetSearchQuery] = useState('');
const [layerSearchQuery, setLayerSearchQuery] = useState('');
const filteredGroupedAssets = useMemo(
() => filterGroupedAssetsBySearch(groupedAssets, assetSearchQuery),
[assetSearchQuery, groupedAssets],
);
const filteredAssetsCount = useMemo(
() =>
filteredGroupedAssets.reduce(
(totalCount, folder) => totalCount + folder.assets.length,
0,
),
[filteredGroupedAssets],
);
const normalizedLayerSearchQuery = layerSearchQuery.trim().toLowerCase();
const filteredLayers = useMemo(
() =>
normalizedLayerSearchQuery
? [
...layers.filter((layer) =>
layerStartsWithSearch(layer, normalizedLayerSearchQuery),
),
...layers.filter(
(layer) =>
!layerStartsWithSearch(layer, normalizedLayerSearchQuery) &&
layerMatchesSearch(layer, normalizedLayerSearchQuery),
),
]
: layers,
[layers, normalizedLayerSearchQuery],
);
if (!activeSidebarPanel) {
return null;
}
@@ -175,7 +214,9 @@ export function ImageCanvasSidebarView({
{activeSidebarPanel === 'assets' ? '素材' : '图层'}
</h2>
<div className="image-canvas-editor__sidebar-count">
{activeSidebarPanel === 'assets' ? assets.length : layers.length}
{activeSidebarPanel === 'assets'
? filteredAssetsCount
: filteredLayers.length}
</div>
</div>
{activeSidebarPanel === 'assets' ? (
@@ -210,12 +251,34 @@ export function ImageCanvasSidebarView({
)}
</div>
<div className="image-canvas-editor__sidebar-search">
<PlatformTextField
aria-label={
activeSidebarPanel === 'assets' ? '搜索素材' : '搜索画布图层'
}
placeholder={activeSidebarPanel === 'assets' ? '搜索素材' : '搜索图层'}
value={
activeSidebarPanel === 'assets' ? assetSearchQuery : layerSearchQuery
}
size="xs"
density="compact"
className="image-canvas-editor__sidebar-search-input"
onChange={(event) => {
if (activeSidebarPanel === 'assets') {
setAssetSearchQuery(event.target.value);
} else {
setLayerSearchQuery(event.target.value);
}
}}
/>
</div>
{activeSidebarPanel === 'assets' ? (
<ImageCanvasAssetLibraryPanelView
assetListRef={assetListRef}
assetPointerDragRef={assetPointerDragRef}
suppressAssetClickRef={suppressAssetClickRef}
groupedAssets={groupedAssets}
groupedAssets={filteredGroupedAssets}
assetFolders={assetFolders}
isAssetSelectionMode={isAssetSelectionMode}
selectedAssetIds={selectedAssetIds}
@@ -258,7 +321,7 @@ export function ImageCanvasSidebarView({
/>
) : (
<ImageCanvasLayerPanelView
layers={layers}
layers={filteredLayers}
selectedLayerId={selectedLayerId}
selectedLayerIds={selectedLayerIds}
setImageContextMenu={setImageContextMenu}
@@ -105,6 +105,16 @@ describe('ImageCanvasUploadModel', () => {
activeUploadFolderId: 'characters',
}),
).toBe('project');
expect(
resolveUploadFolderId({
assetFolders: [
...folders,
createFolder({ id: 'uploaded', label: '上传素材', systemDefault: false }),
],
activeUploadFolderId: 'characters',
preferUploadFolder: true,
}),
).toBe('uploaded');
});
it('creates uploading asset placeholders', () => {
@@ -9,6 +9,7 @@ import type {
GenerateDialogState,
QuickEditPanelState,
} from './ImageCanvasEditorTypes';
import { resolvePreferredAssetUploadFolder } from './ImageCanvasAssetLibraryModel';
import { appendLimitedQuickEditReferences } from './ImageCanvasGenerationModel';
type CanvasSize = { width: number; height: number };
@@ -73,12 +74,19 @@ export function resolveUploadFolderId({
assetFolders,
requestedFolderId,
activeUploadFolderId,
preferUploadFolder = false,
}: {
assetFolders: EditorAssetFolder[];
requestedFolderId?: string;
activeUploadFolderId: string;
preferUploadFolder?: boolean;
}) {
const targetFolderId = requestedFolderId ?? activeUploadFolderId;
const targetFolderId =
requestedFolderId ??
(preferUploadFolder
? resolvePreferredAssetUploadFolder(assetFolders)?.id
: activeUploadFolderId) ??
activeUploadFolderId;
return assetFolders.some((folder) => folder.id === targetFolderId)
? targetFolderId
: 'project';
@@ -23,6 +23,7 @@ import type {
import { useImageCanvasUploadWorkflow } from './useImageCanvasUploadWorkflow';
const createEditorAssetMock = vi.hoisted(() => vi.fn());
const createEditorAssetFolderMock = vi.hoisted(() => vi.fn());
const createEditorProjectResourceMock = vi.hoisted(() => vi.fn());
const uploadEditorSeedanceReferenceFileMock = vi.hoisted(() => vi.fn());
const uploadEditorMediaAssetFileMock = vi.hoisted(() => vi.fn());
@@ -35,6 +36,7 @@ vi.mock('../../services/image-editor/editorProjectClient', async () => {
return {
...actual,
createEditorAsset: createEditorAssetMock,
createEditorAssetFolder: createEditorAssetFolderMock,
createEditorProjectResource: createEditorProjectResourceMock,
};
});
@@ -338,6 +340,12 @@ describe('useImageCanvasUploadWorkflow', () => {
objectKey: 'object-key-uploaded',
assetObjectId: 'asset-object-uploaded',
}));
createEditorAssetFolderMock.mockResolvedValue({
folderId: 'folder-uploaded',
label: '上传素材',
collapsed: false,
systemDefault: false,
});
createEditorProjectResourceMock.mockImplementation(
async (projectId, input) => ({
resourceId: `resource-${input.objectKey ?? input.imageSrc.slice(0, 8)}`,
@@ -421,9 +429,10 @@ describe('useImageCanvasUploadWorkflow', () => {
});
await waitFor(() => {
expect(screen.getByTestId('assets').textContent).toContain(
'persisted-上传素材.png:上传素材.png:project:ready:-',
'persisted-上传素材.png:上传素材.png:folder-uploaded:ready:-',
);
});
expect(createEditorAssetFolderMock).toHaveBeenCalledWith('上传素材', 101);
});
it('opens login instead of the asset file picker when protected data is unavailable', () => {
@@ -483,7 +492,7 @@ describe('useImageCanvasUploadWorkflow', () => {
await waitFor(() => {
expect(screen.getByTestId('assets').textContent).toContain(
'upload-1:画布素材.png:project:uploading:上传中',
'upload-1:画布素材.png:folder-uploaded:uploading:上传中',
);
expect(screen.getByTestId('layers').textContent).toContain(
'layer-upload-1:画布素材.png:upload-1:-160:-107.5',
@@ -496,7 +505,7 @@ describe('useImageCanvasUploadWorkflow', () => {
deferredAsset.resolve({
assetId: 'asset-persisted-canvas',
folderId: 'project',
folderId: 'folder-uploaded',
label: '画布素材.png',
imageSrc: 'data:image/png;base64,Y2FudmFz',
width: 420,
@@ -508,7 +517,7 @@ describe('useImageCanvasUploadWorkflow', () => {
await waitFor(() => {
expect(screen.getByTestId('assets').textContent).toContain(
'asset-persisted-canvas:画布素材.png:project:ready:-',
'asset-persisted-canvas:画布素材.png:folder-uploaded:ready:-',
);
expect(screen.getByTestId('layers').textContent).toContain(
'layer-upload-1:画布素材.png:asset-persisted-canvas:-160:-107.5',
@@ -533,7 +542,7 @@ describe('useImageCanvasUploadWorkflow', () => {
);
});
expect(screen.getByTestId('assets').textContent).toContain(
'persisted-原始尺寸素材.png:原始尺寸素材.png:project:ready:-:1536x1024',
'persisted-原始尺寸素材.png:原始尺寸素材.png:folder-uploaded:ready:-:1536x1024',
);
expect(screen.getByTestId('layers').textContent).toContain(
'layer-upload-1:原始尺寸素材.png:persisted-原始尺寸素材.png:-718:-462:1536x1024',
@@ -558,7 +567,7 @@ describe('useImageCanvasUploadWorkflow', () => {
await waitFor(() => {
expect(openEditorLoginModal).toHaveBeenCalledTimes(1);
expect(screen.getByTestId('assets').textContent).toContain(
'upload-1:上传素材.png:project:failed:请先登录',
'upload-1:上传素材.png:folder-uploaded:failed:请先登录',
);
});
});
@@ -11,6 +11,7 @@ import { ApiClientError } from '../../services/apiClient';
import { uploadEditorMediaAssetFile } from '../../services/image-editor/editorMediaAssetUploadClient';
import {
createEditorAsset,
createEditorAssetFolder,
createEditorProjectResource,
} from '../../services/image-editor/editorProjectClient';
import { uploadEditorSeedanceReferenceFile } from '../../services/image-editor/editorReferenceUploadClient';
@@ -26,6 +27,11 @@ import type {
QuickEditPanelState,
UploadTarget,
} from './ImageCanvasEditorTypes';
import {
EDITOR_UPLOAD_ASSET_FOLDER_LABEL,
resolvePreferredAssetUploadFolder,
resolveUploadAssetFolder,
} from './ImageCanvasAssetLibraryModel';
import {
SEEDANCE_REFERENCE_FILE_LIMITS,
isImageFile,
@@ -64,6 +70,7 @@ type UploadFilesOptions = {
type UploadRequestOptions = {
addToCanvas?: boolean;
folderId?: string;
};
type UploadAssetFileOptions = UploadFilesOptions & {
@@ -128,6 +135,7 @@ export function useImageCanvasUploadWorkflow({
const canAccessProtectedDataRef = useRef(canAccessProtectedData);
const uploadTargetRef = useRef<UploadTarget>('asset');
const uploadRequestOptionsRef = useRef<UploadRequestOptions>({});
const uploadAssetFolderCreatePromiseRef = useRef<Promise<string> | null>(null);
const [uploadTarget, setUploadTargetState] = useState<UploadTarget>('asset');
canAccessProtectedDataRef.current = canAccessProtectedData;
@@ -814,11 +822,48 @@ export function useImageCanvasUploadWorkflow({
return;
}
const uploadFolderId = resolveUploadFolderId({
let uploadFolderId = resolveUploadFolderId({
assetFolders,
requestedFolderId: options.folderId,
activeUploadFolderId,
preferUploadFolder: !options.folderId,
});
if (!options.folderId && !resolveUploadAssetFolder(assetFolders)) {
if (!uploadAssetFolderCreatePromiseRef.current) {
uploadAssetFolderCreatePromiseRef.current = createEditorAssetFolder(
EDITOR_UPLOAD_ASSET_FOLDER_LABEL,
assetFolders.length + 100,
)
.then((folder) => {
setAssetFolders((currentFolders) =>
currentFolders.some(
(currentFolder) => currentFolder.id === folder.folderId,
)
? currentFolders
: [
...currentFolders,
{
id: folder.folderId,
label: folder.label,
collapsed: folder.collapsed,
systemDefault: folder.systemDefault,
persisted: true,
},
],
);
return folder.folderId;
})
.catch(() => {
const fallbackFolder =
resolvePreferredAssetUploadFolder(assetFolders);
return fallbackFolder?.id ?? 'project';
})
.finally(() => {
uploadAssetFolderCreatePromiseRef.current = null;
});
}
uploadFolderId = await uploadAssetFolderCreatePromiseRef.current;
}
const uploadIndex = options.uploadIndex;
let imageDimensions: { width: number; height: number } | null = null;
if (mediaType === 'image') {
@@ -1072,6 +1117,7 @@ export function useImageCanvasUploadWorkflow({
} else {
const requestOptions = uploadRequestOptionsRef.current;
addUploadedFiles(files, {
folderId: requestOptions.folderId,
addToCanvas:
requestOptions.addToCanvas ?? activeTool === 'upload',
});
+12
View File
@@ -4255,6 +4255,18 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock {
gap: 0.35rem;
}
.image-canvas-editor__sidebar-search {
border-bottom: 1px solid var(--image-canvas-brand-border-soft);
padding: 0.55rem 0.65rem;
}
.image-canvas-editor__sidebar-search-input {
height: 2rem;
border-radius: 0.45rem;
font-size: 0.78rem;
font-weight: 780;
}
.image-canvas-editor__asset-list {
position: relative;
display: grid;