Browse Source

no message

pull/1/head
WIN-UGQIHHLSKBB\EDY 3 months ago
parent
commit
2b7f874c6b
  1. 48
      src/pages/CharactersCreate/index.tsx
  2. 2
      src/pages/Explore/index.tsx
  3. 55
      src/utils/imageDragTransfer.ts

48
src/pages/CharactersCreate/index.tsx

@ -162,7 +162,6 @@ export default function CharactersCreate() {
// 左侧角色风格库数据与选中项。
const [characterList, setCharacterList] = useState<any[]>([]);
const [diyCategoryList, setDiyCategoryList] = useState<Record<string, unknown>[]>([]);
const [selectedCharacterList, setSelectedCharacterList] = useState<any[]>([]);
// 生成能力相关:分类 -> 模型 -> 参数。
const [aiCategories, setAiCategories] = useState<any[]>([]);
@ -211,11 +210,21 @@ export default function CharactersCreate() {
const taskPageRef = useRef(1);
const loadMoreRequestIdRef = useRef(0);
const selectedCharacterIdSet = useMemo(() => {
const idSet = new Set<string>();
images.forEach((imageItem) => {
if (typeof imageItem === "object" && imageItem.type === "character" && imageItem.id != null) {
idSet.add(String(imageItem.id));
}
});
return idSet;
}, [images]);
const handleSelectCharacter = useCallback(
(item: any) => {
const id = item?.id;
if (id === undefined || id === null) return;
const inList = selectedCharacterList.some((c) => String(c?.id) === String(id));
const normalizedId = String(id);
const nextCharacterItem: CharacterImageItem = {
type: "character",
id: item?.id,
@ -223,36 +232,23 @@ export default function CharactersCreate() {
avatar: item?.avatar,
};
setImages((prev) => {
const inImages = prev.some(
const existingIndex = prev.findIndex(
(imageItem) =>
typeof imageItem === "object" &&
imageItem.type === "character" &&
String(imageItem.id) === String(id),
String(imageItem.id) === normalizedId,
);
if (inImages) {
setSelectedCharacterList((p) => p.filter((c) => String(c?.id) !== String(id)));
return prev.filter(
(imageItem) =>
!(
typeof imageItem === "object" &&
imageItem.type === "character" &&
String(imageItem.id) === String(id)
),
);
}
if (!inImages && inList) {
setSelectedCharacterList((p) => p.filter((c) => String(c?.id) !== String(id)));
return prev;
if (existingIndex >= 0) {
return prev.filter((_, idx) => idx !== existingIndex);
}
if (prev.length >= MAX_UPLOAD_IMAGES) {
Message.warning(t("composer.maxUploadReached", { max: MAX_UPLOAD_IMAGES }));
return prev;
}
setSelectedCharacterList((p) => (p.some((c) => String(c?.id) === String(id)) ? p : [...p, item]));
return [...prev, nextCharacterItem];
});
},
[selectedCharacterList, t],
[t],
);
const handleSelectMaterial = useCallback((item: any) => {
@ -626,14 +622,8 @@ export default function CharactersCreate() {
// 删除某一张已上传图片。
const handleRemoveImage = useCallback((targetIdx: number) => {
setImages((prev) => {
const removedItem = prev[targetIdx];
if (typeof removedItem === "object" && removedItem.type === "character") {
setSelectedCharacterList((list) => list.filter((c) => String(c?.id) !== String(removedItem.id)));
}
return prev.filter((_, idx) => idx !== targetIdx);
});
}, [t]);
setImages((prev) => prev.filter((_, idx) => idx !== targetIdx));
}, []);
const handleDownloadAsset = useCallback((asset: any, task: any, assetIdx: number) => {
const imageUrl = asset?.images?.[0];
@ -888,7 +878,7 @@ export default function CharactersCreate() {
type="button"
className={classNames(
"charactersCreate__styleCell",
selectedCharacterList.some((c) => String(c?.id) === String(item.id)) && "charactersCreate__styleCell_selected",
selectedCharacterIdSet.has(String(item.id)) && "charactersCreate__styleCell_selected",
)}
draggable
onMouseEnter={() => {

2
src/pages/Explore/index.tsx

@ -59,7 +59,7 @@ export default function Explore() {
useEffect(() => {
dragTransferManager.warmupPreviewImages(
officialRoles.map((item) => item?.threeViewImage ?? item?.threeView),
officialRoles.map((item) => item?.threeViewImage),
);
}, [dragTransferManager, officialRoles]);

55
src/utils/imageDragTransfer.ts

@ -58,6 +58,15 @@ function normalizeUrl(rawUrl?: unknown): string {
*/
export function createImageDragTransferManager(): ImageDragTransferManager {
const cache = new Map<string, HTMLImageElement>();
const preloadTasks = new Map<string, Promise<void>>();
const fallbackDragImage = document.createElement("div");
fallbackDragImage.style.width = "1px";
fallbackDragImage.style.height = "1px";
fallbackDragImage.style.position = "fixed";
fallbackDragImage.style.top = "-9999px";
fallbackDragImage.style.left = "-9999px";
fallbackDragImage.style.opacity = "0";
document.body.appendChild(fallbackDragImage);
const ensurePreviewImage = (rawUrl?: unknown): HTMLImageElement | null => {
const url = normalizeUrl(rawUrl);
@ -79,9 +88,46 @@ export function createImageDragTransferManager(): ImageDragTransferManager {
return img;
};
const ensurePreviewReady = (rawUrl?: unknown): Promise<void> => {
const url = normalizeUrl(rawUrl);
if (!url) return Promise.resolve();
const existedTask = preloadTasks.get(url);
if (existedTask) return existedTask;
const img = ensurePreviewImage(url);
if (!img) return Promise.resolve();
const task = new Promise<void>((resolve) => {
if (img.complete && img.naturalWidth > 0) {
resolve();
return;
}
const done = () => {
img.removeEventListener("load", done);
img.removeEventListener("error", done);
resolve();
};
img.addEventListener("load", done, { once: true });
img.addEventListener("error", done, { once: true });
}).then(async () => {
try {
if (typeof img.decode === "function") {
await img.decode();
}
} catch {
// decode 失败不影响拖拽流程,继续使用已有资源状态
}
});
preloadTasks.set(url, task);
return task;
};
const warmupPreviewImages = (urls: unknown[]) => {
urls.forEach((url) => {
ensurePreviewImage(url);
void ensurePreviewReady(url);
});
};
@ -97,6 +143,9 @@ export function createImageDragTransferManager(): ImageDragTransferManager {
const preview = ensurePreviewImage(previewUrl ?? dataUrl);
if (preview && preview.complete && preview.naturalWidth > 0) {
evt.dataTransfer.setDragImage(preview, 60, 60);
} else {
// 图片未就绪时使用极小占位图,避免浏览器回退截图拖拽源导致卡顿。
evt.dataTransfer.setDragImage(fallbackDragImage, 0, 0);
}
return true;
};
@ -108,6 +157,10 @@ export function createImageDragTransferManager(): ImageDragTransferManager {
}
});
cache.clear();
preloadTasks.clear();
if (fallbackDragImage.parentNode) {
fallbackDragImage.parentNode.removeChild(fallbackDragImage);
}
};
return {

Loading…
Cancel
Save