This commit is contained in:
2026-07-27 16:05:44 +08:00
parent aab0b7849b
commit 21143f9ff5
6 changed files with 471 additions and 448 deletions
@@ -154,100 +154,100 @@ export function ImageCanvasAssetLibraryPanelView({
}: ImageCanvasAssetLibraryPanelViewProps) {
return (
<div className="image-canvas-editor__asset-library">
<div
ref={assetListRef}
className="image-canvas-editor__asset-list"
onPointerDown={onAssetMarqueePointerDown}
onPointerMove={onAssetMarqueePointerMove}
onPointerUp={onAssetMarqueePointerUp}
onPointerCancel={onAssetMarqueePointerUp}
>
{pinnedAssetMoveFolderId ? (
<div
className="image-canvas-editor__asset-folder-sticky-target"
aria-hidden="true"
>
<Folder className="h-4 w-4" />
<span>
<div
ref={assetListRef}
className="image-canvas-editor__asset-list"
onPointerDown={onAssetMarqueePointerDown}
onPointerMove={onAssetMarqueePointerMove}
onPointerUp={onAssetMarqueePointerUp}
onPointerCancel={onAssetMarqueePointerUp}
>
{pinnedAssetMoveFolderId ? (
<div
className="image-canvas-editor__asset-folder-sticky-target"
aria-hidden="true"
>
<Folder className="h-4 w-4" />
<span>
{assetFolders.find(
(folder) => folder.id === pinnedAssetMoveFolderId,
)?.label ?? '目标文件夹'}
</span>
</div>
) : null}
{creatingFolder ? (
<form
className="image-canvas-editor__folder-create"
onSubmit={(event) => {
event.preventDefault();
void commitNewAssetFolder();
}}
>
<PlatformTextField
aria-label="素材文件夹名称"
value={newFolderName}
autoFocus
size="xs"
density="compact"
className="image-canvas-editor__folder-create-input"
onChange={(event) => setNewFolderName(event.target.value)}
onKeyDown={(event) => {
if (event.key === 'Escape') {
event.preventDefault();
setCreatingFolder(false);
setNewFolderName('');
}
</span>
</div>
) : null}
{creatingFolder ? (
<form
className="image-canvas-editor__folder-create"
onSubmit={(event) => {
event.preventDefault();
void commitNewAssetFolder();
}}
/>
>
<PlatformTextField
aria-label="素材文件夹名称"
value={newFolderName}
autoFocus
size="xs"
density="compact"
className="image-canvas-editor__folder-create-input"
onChange={(event) => setNewFolderName(event.target.value)}
onKeyDown={(event) => {
if (event.key === 'Escape') {
event.preventDefault();
setCreatingFolder(false);
setNewFolderName('');
}
}}
/>
<EditorIconButton
type="submit"
label="保存素材文件夹"
icon={Check}
/>
<EditorIconButton
label="取消新建素材文件夹"
icon={X}
onClick={() => {
setCreatingFolder(false);
setNewFolderName('');
}}
/>
</form>
) : null}
{groupedAssets.map((folder) => (
<ImageCanvasAssetFolderSectionView
key={folder.id}
folder={folder}
assetPointerDragRef={assetPointerDragRef}
suppressAssetClickRef={suppressAssetClickRef}
isAssetSelectionMode={isAssetSelectionMode}
selectedAssetIds={selectedAssetIds}
<EditorIconButton
label="取消新建素材文件夹"
icon={X}
onClick={() => {
setCreatingFolder(false);
setNewFolderName('');
}}
/>
</form>
) : null}
{groupedAssets.map((folder) => (
<ImageCanvasAssetFolderSectionView
key={folder.id}
folder={folder}
assetPointerDragRef={assetPointerDragRef}
suppressAssetClickRef={suppressAssetClickRef}
isAssetSelectionMode={isAssetSelectionMode}
selectedAssetIds={selectedAssetIds}
visibleAssetIds={visibleAssetIds}
assetMoveDropFolderId={assetMoveDropFolderId}
renamingFolder={renamingFolder}
renamingAsset={renamingAsset}
setRenamingFolder={setRenamingFolder}
setRenamingAsset={setRenamingAsset}
setActiveUploadFolderId={setActiveUploadFolderId}
setUploadDropTarget={setUploadDropTarget}
setAssetPointerDrag={setAssetPointerDrag}
updateAssetMoveDropFolder={updateAssetMoveDropFolder}
addUploadedFiles={addUploadedFiles}
requestUpload={requestUpload}
moveAssetToFolder={moveAssetToFolder}
toggleAssetFolder={toggleAssetFolder}
startRenamingFolder={startRenamingFolder}
commitFolderRename={commitFolderRename}
deleteAssetFolder={deleteAssetFolder}
startRenamingAsset={startRenamingAsset}
commitAssetRename={commitAssetRename}
deleteUploadedAsset={deleteUploadedAsset}
submitAssetToShowcase={submitAssetToShowcase}
toggleAssetSelected={toggleAssetSelected}
addAssetLayer={addAssetLayer}
onDownloadAsset={onDownloadAsset}
/>
))}
assetMoveDropFolderId={assetMoveDropFolderId}
renamingFolder={renamingFolder}
renamingAsset={renamingAsset}
setRenamingFolder={setRenamingFolder}
setRenamingAsset={setRenamingAsset}
setActiveUploadFolderId={setActiveUploadFolderId}
setUploadDropTarget={setUploadDropTarget}
setAssetPointerDrag={setAssetPointerDrag}
updateAssetMoveDropFolder={updateAssetMoveDropFolder}
addUploadedFiles={addUploadedFiles}
requestUpload={requestUpload}
moveAssetToFolder={moveAssetToFolder}
toggleAssetFolder={toggleAssetFolder}
startRenamingFolder={startRenamingFolder}
commitFolderRename={commitFolderRename}
deleteAssetFolder={deleteAssetFolder}
startRenamingAsset={startRenamingAsset}
commitAssetRename={commitAssetRename}
deleteUploadedAsset={deleteUploadedAsset}
submitAssetToShowcase={submitAssetToShowcase}
toggleAssetSelected={toggleAssetSelected}
addAssetLayer={addAssetLayer}
onDownloadAsset={onDownloadAsset}
/>
))}
{assetMarquee ? (
<div
className="image-canvas-editor__asset-marquee"
@@ -279,9 +279,9 @@ export function ImageCanvasAssetLibraryPanelView({
<Square className="h-4 w-4" />
)}
<span className="min-w-0 truncate">
{selectedAssetIds.size > 0
{selectedAssetIds.size > 0
? `${allVisibleAssetsSelected ? '取消全选' : '全选'} · 已选 ${selectedAssetIds.size}`
: '全选'}
: '全选'}
</span>
</PlatformActionButton>
<PlatformActionButton
@@ -403,8 +403,8 @@ export function ImageCanvasAssetRowView({
event.pointerType === 'touch' || event.pointerType === 'pen'
? event.pointerType
: 'mouse';
return;
}
return;
}
if (event.button !== 0) {
return;
}
@@ -559,11 +559,11 @@ describe('ImageCanvasEditorView asset library integration', () => {
const originalCreateElement = document.createElement.bind(document);
vi.spyOn(document, 'createElement').mockImplementation(
(tagName, options) => {
const element = originalCreateElement(tagName, options);
if (tagName === 'video' || tagName === 'audio') {
createdMediaElements.push(element as HTMLMediaElement);
}
return element;
const element = originalCreateElement(tagName, options);
if (tagName === 'video' || tagName === 'audio') {
createdMediaElements.push(element as HTMLMediaElement);
}
return element;
},
);
Object.defineProperty(URL, 'createObjectURL', {
@@ -1017,13 +1017,13 @@ describe('ImageCanvasEditorView asset library integration', () => {
await waitFor(() => {
const savedLayoutWithoutDeletedLayer =
saveEditorProjectLayoutMock.mock.calls
.map((call) => call[1])
.find((layout) =>
layout.layers.every(
(item: { layerId?: unknown }) =>
item.layerId !== 'layer-generated-a',
),
);
.map((call) => call[1])
.find((layout) =>
layout.layers.every(
(item: { layerId?: unknown }) =>
item.layerId !== 'layer-generated-a',
),
);
expect(savedLayoutWithoutDeletedLayer).toBeTruthy();
});
expect(deleteEditorAssetMock).toHaveBeenCalledWith('asset-generated-a');
File diff suppressed because it is too large Load Diff
@@ -322,21 +322,21 @@ export function useImageCanvasAssetLibrary({
);
setAssetFolders(
(currentFolders) =>
replaceLocalAssetFolder({
folders: currentFolders,
assets: [],
localFolderId: folderId,
persistedFolder: folder,
}).folders,
replaceLocalAssetFolder({
folders: currentFolders,
assets: [],
localFolderId: folderId,
persistedFolder: folder,
}).folders,
);
setAssets(
(currentAssets) =>
replaceLocalAssetFolder({
folders: [],
assets: currentAssets,
localFolderId: folderId,
persistedFolder: folder,
}).assets,
replaceLocalAssetFolder({
folders: [],
assets: currentAssets,
localFolderId: folderId,
persistedFolder: folder,
}).assets,
);
setActiveUploadFolderId(folder.folderId);
} catch {
@@ -362,58 +362,58 @@ export function useImageCanvasAssetLibrary({
);
const submitAssetToShowcase = useCallback((asset: EditorAsset) => {
if (!resolveAssetShowcaseSubmissionState(asset).canSubmit) {
return;
}
const previousAsset = asset;
setAssets((currentAssets) =>
currentAssets.map((currentAsset) =>
currentAsset.id === asset.id
? {
...currentAsset,
publicShowcaseEnabled: false,
showcaseReviewStatus: 'pending',
showcaseDisplayEnabled: false,
showcaseSubmitError: undefined,
}
: currentAsset,
),
);
submitEditorAssetShowcase(asset.id)
.then((showcaseAsset) => {
setAssets((currentAssets) =>
currentAssets.map((currentAsset) =>
currentAsset.id === asset.id
? {
...currentAsset,
publicShowcaseEnabled: false,
if (!resolveAssetShowcaseSubmissionState(asset).canSubmit) {
return;
}
const previousAsset = asset;
setAssets((currentAssets) =>
currentAssets.map((currentAsset) =>
currentAsset.id === asset.id
? {
...currentAsset,
publicShowcaseEnabled: false,
showcaseReviewStatus: 'pending',
showcaseDisplayEnabled: false,
showcaseSubmitError: undefined,
}
: currentAsset,
),
);
submitEditorAssetShowcase(asset.id)
.then((showcaseAsset) => {
setAssets((currentAssets) =>
currentAssets.map((currentAsset) =>
currentAsset.id === asset.id
? {
...currentAsset,
publicShowcaseEnabled: false,
showcaseId:
showcaseAsset.showcaseId ?? showcaseAsset.resourceId,
showcaseReviewStatus: showcaseAsset.reviewStatus ?? 'pending',
showcaseDisplayEnabled: showcaseAsset.displayEnabled ?? false,
showcaseLikeCount: showcaseAsset.likeCount ?? 0,
generationCostMudPoints:
showcaseAsset.generationCostMudPoints ??
currentAsset.generationCostMudPoints,
showcaseSubmitError: undefined,
}
: currentAsset,
),
);
})
.catch((error: unknown) => {
const showcaseSubmitError = formatShowcaseSubmitError(error);
setAssets((currentAssets) =>
currentAssets.map((currentAsset) =>
currentAsset.id === asset.id
? {
...previousAsset,
showcaseSubmitError,
}
: currentAsset,
),
);
});
showcaseReviewStatus: showcaseAsset.reviewStatus ?? 'pending',
showcaseDisplayEnabled: showcaseAsset.displayEnabled ?? false,
showcaseLikeCount: showcaseAsset.likeCount ?? 0,
generationCostMudPoints:
showcaseAsset.generationCostMudPoints ??
currentAsset.generationCostMudPoints,
showcaseSubmitError: undefined,
}
: currentAsset,
),
);
})
.catch((error: unknown) => {
const showcaseSubmitError = formatShowcaseSubmitError(error);
setAssets((currentAssets) =>
currentAssets.map((currentAsset) =>
currentAsset.id === asset.id
? {
...previousAsset,
showcaseSubmitError,
}
: currentAsset,
),
);
});
}, []);
const startRenamingFolder = useCallback((folder: EditorAssetFolder) => {
@@ -454,21 +454,21 @@ export function useImageCanvasAssetLibrary({
}
setAssetFolders(
(currentFolders) =>
deleteAssetFolderLocally({
folders: currentFolders,
assets: [],
folderId: folder.id,
defaultFolderId: defaultFolder.id,
}).folders,
deleteAssetFolderLocally({
folders: currentFolders,
assets: [],
folderId: folder.id,
defaultFolderId: defaultFolder.id,
}).folders,
);
setAssets(
(currentAssets) =>
deleteAssetFolderLocally({
folders: [],
assets: currentAssets,
folderId: folder.id,
defaultFolderId: defaultFolder.id,
}).assets,
deleteAssetFolderLocally({
folders: [],
assets: currentAssets,
folderId: folder.id,
defaultFolderId: defaultFolder.id,
}).assets,
);
if (folder.persisted) {
deleteEditorAssetFolder(folder.id)
@@ -497,7 +497,7 @@ export function useImageCanvasAssetLibrary({
});
},
[],
);
);
const toggleAllAssetsSelected = useCallback((visibleAssetIds: string[]) => {
setSelectedAssetIds((currentIds) =>
@@ -580,9 +580,9 @@ export function useImageCanvasAssetLibrary({
bottom: number;
}) => {
const marqueeSelection = selectUploadedAssetsInRect({
assets,
assetTargets: readAssetHitTargets(assetListRef.current),
selectionRect,
assets,
assetTargets: readAssetHitTargets(assetListRef.current),
selectionRect,
});
setSelectedAssetIds(
new Set([...marqueeBaseSelectionRef.current, ...marqueeSelection]),
+32 -9
View File
@@ -16,10 +16,16 @@ function readPlatformThemeCss() {
function getCssBlock(source: string, selector: string) {
const selectorIndex = source.indexOf(selector);
expect(selectorIndex, `${selector} should exist in src/index.css`).toBeGreaterThanOrEqual(0);
expect(
selectorIndex,
`${selector} should exist in src/index.css`,
).toBeGreaterThanOrEqual(0);
const openBraceIndex = source.indexOf('{', selectorIndex);
expect(openBraceIndex, `${selector} should open a CSS block`).toBeGreaterThanOrEqual(0);
expect(
openBraceIndex,
`${selector} should open a CSS block`,
).toBeGreaterThanOrEqual(0);
let depth = 0;
for (let index = openBraceIndex; index < source.length; index += 1) {
@@ -111,7 +117,9 @@ describe('index stylesheet unread dots', () => {
);
expect(generatingBlock).toContain('cursor: progress;');
expect(generatingScanBlock).toContain('animation: image-canvas-generation-scan');
expect(generatingScanBlock).toContain(
'animation: image-canvas-generation-scan',
);
expect(iconBlock).toContain('background: linear-gradient(');
expect(iconDecorationBlock).toContain('width: 42%;');
expect(css).not.toContain(
@@ -129,11 +137,16 @@ describe('index stylesheet unread dots', () => {
css,
'.project-gallery__canvas-cover-loading::before,\n.image-canvas-editor__layer-image-loading::before',
);
const keyframesBlock = getCssBlock(css, '@keyframes image-canvas-generation-scan');
const keyframesBlock = getCssBlock(
css,
'@keyframes image-canvas-generation-scan',
);
expect(loadingBlock).toContain('overflow: hidden;');
expect(loadingBlock).toContain('background: rgba(226, 232, 240, 0.72);');
expect(loadingBlock).not.toContain('animation: image-canvas-generation-scan');
expect(loadingBlock).not.toContain(
'animation: image-canvas-generation-scan',
);
expect(shimmerBlock).toContain('left: -64%;');
expect(shimmerBlock).toContain('width: 64%;');
@@ -215,7 +228,9 @@ describe('index stylesheet unread dots', () => {
);
expect(platformShellBlock).toContain('--platform-layout-viewport-height');
expect(platformShellBlock).not.toContain('translate3d');
expect(platformShellBlock).not.toContain('--platform-keyboard-focus-offset');
expect(platformShellBlock).not.toContain(
'--platform-keyboard-focus-offset',
);
});
it('uses warm brown tokens for draft unread markers instead of red literals', () => {
@@ -248,7 +263,9 @@ describe('index stylesheet unread dots', () => {
const css = readIndexCss();
const themeCss = readPlatformThemeCss();
const titleRuleIndex = css.indexOf('.selection-hero-brand__title');
const platformThemeRuleIndex = css.indexOf('.platform-theme,\n.platform-theme *');
const platformThemeRuleIndex = css.indexOf(
'.platform-theme,\n.platform-theme *',
);
expect(titleRuleIndex).toBeGreaterThanOrEqual(0);
expect(platformThemeRuleIndex).toBeGreaterThan(titleRuleIndex);
@@ -313,7 +330,10 @@ describe('index stylesheet draft mobile cards', () => {
const draftPanelSelector =
'#platform-tab-panel-saves,\n #platform-tab-panel-saves *';
const draftPanelSelectorIndex = css.indexOf(draftPanelSelector, mobileQueryIndex);
const draftPanelSelectorIndex = css.indexOf(
draftPanelSelector,
mobileQueryIndex,
);
expect(draftPanelSelectorIndex).toBeGreaterThanOrEqual(0);
const draftPanelBlock = getCssBlock(css, draftPanelSelector);
@@ -323,7 +343,10 @@ describe('index stylesheet draft mobile cards', () => {
const editableSelector =
"#platform-tab-panel-saves :is(input, textarea, [contenteditable='true'])";
const editableSelectorIndex = css.indexOf(editableSelector, mobileQueryIndex);
const editableSelectorIndex = css.indexOf(
editableSelector,
mobileQueryIndex,
);
expect(editableSelectorIndex).toBeGreaterThanOrEqual(0);
const editableBlock = getCssBlock(css, editableSelector);