Merge pull request #256 from 11cafe/feat/upload-thumbnail
Feat/upload thumbnail
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
import folder_paths
|
||||
from datetime import datetime
|
||||
import platform
|
||||
import tempfile
|
||||
@@ -241,9 +242,6 @@ def count_files_sync(reqJson):
|
||||
if file.is_file():
|
||||
file_count += 1
|
||||
return {"success": True, "count": file_count}
|
||||
|
||||
|
||||
|
||||
# Begin: Copy all files to the new directory when switching the save directory
|
||||
@server.PromptServer.instance.routes.post('/workspace/copy_json_files')
|
||||
async def copy_json_files(request):
|
||||
@@ -281,4 +279,33 @@ async def get_unique_dir_name(path):
|
||||
path = f"{original_path}_{counter}"
|
||||
counter += 1
|
||||
return path
|
||||
# End: Copy all files to the new directory when switching the save directory
|
||||
# End: Copy all files to the new directory when switching the save directory
|
||||
|
||||
@server.PromptServer.instance.routes.post('/workspace/images/save')
|
||||
async def save_images(request):
|
||||
reader = await request.multipart()
|
||||
subfolder = "workspace_manager" # 默认子文件夹,实际上你可能会从表单中动态获取
|
||||
output_path = folder_paths.get_output_directory()
|
||||
safe_dir = Path(os.path.join(output_path, subfolder))
|
||||
if not safe_dir.exists():
|
||||
safe_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 用于存储保存的文件路径
|
||||
files_saved = []
|
||||
# 处理multipart/form-data中的每一部分
|
||||
async for part in reader:
|
||||
if part.name.startswith('images'):
|
||||
filename = part.filename
|
||||
if not filename:
|
||||
continue
|
||||
file_path = safe_dir / filename
|
||||
loop = asyncio.get_running_loop()
|
||||
# 异步写入文件
|
||||
with open(file_path, 'wb') as f:
|
||||
while True:
|
||||
chunk = await part.read_chunk() # 默认8192 bytes
|
||||
if not chunk:
|
||||
break
|
||||
await loop.run_in_executor(None, f.write, chunk)
|
||||
files_saved.append(str(file_path))
|
||||
return web.Response(text=json.dumps({"imgPaths": files_saved}), content_type='application/json')
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
import { useState, useContext } from "react";
|
||||
import { ChangeEvent, useContext, useRef, useState } from "react";
|
||||
import {
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
useToast,
|
||||
MenuList,
|
||||
Tooltip,
|
||||
useToast,
|
||||
} from "@chakra-ui/react";
|
||||
import {
|
||||
IconDotsVertical,
|
||||
IconLockOpen,
|
||||
IconLock,
|
||||
IconCopy,
|
||||
IconDotsVertical,
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconUpload,
|
||||
} from "@tabler/icons-react";
|
||||
import AddTagToWorkflowPopover from "./AddTagToWorkflowPopover";
|
||||
import { Workflow } from "../types/dbTypes";
|
||||
import { workflowsTable } from "../db-tables/WorkspaceDB";
|
||||
import { mediaTable, workflowsTable } from "../db-tables/WorkspaceDB";
|
||||
import { WorkspaceContext } from "../WorkspaceContext";
|
||||
|
||||
type Props = {
|
||||
workflow: Workflow;
|
||||
};
|
||||
export default function MoreActionMenu({ workflow }: Props) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const toast = useToast();
|
||||
const { onDuplicateWorkflow } = useContext(WorkspaceContext);
|
||||
|
||||
@@ -41,6 +43,28 @@ export default function MoreActionMenu({ workflow }: Props) {
|
||||
});
|
||||
};
|
||||
|
||||
const handleFileChange = async (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const files = event.target.files;
|
||||
if (!files) return;
|
||||
|
||||
const body = new FormData();
|
||||
|
||||
Array.from(files).forEach((file, index) => {
|
||||
body.append(`images[${index}]`, file, file.name);
|
||||
});
|
||||
body.append("subfolder", "workspace_manager");
|
||||
await fetch("/workspace/images/save", {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
Array.from(files).forEach((file) => {
|
||||
mediaTable?.create({
|
||||
workflowID: workflow.id,
|
||||
localPath: `workspace_manager/${file.name}`,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menu isLazy closeOnSelect={false}>
|
||||
@@ -78,8 +102,27 @@ export default function MoreActionMenu({ workflow }: Props) {
|
||||
{isLocked ? "Unlock" : "Lock"}
|
||||
</MenuItem>
|
||||
</Tooltip>
|
||||
<MenuItem
|
||||
icon={<IconUpload />}
|
||||
onClick={() => {
|
||||
fileInputRef.current?.click();
|
||||
}}
|
||||
>
|
||||
Upload Thumbnail Image
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<input
|
||||
style={{ display: "none" }}
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".json,image/png"
|
||||
multiple
|
||||
onChange={handleFileChange}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,9 +57,11 @@ export default memo(function WorkflowListItem({ workflow }: Props) {
|
||||
}}
|
||||
>
|
||||
<HStack>
|
||||
{workflow.coverMediaPath != null && (
|
||||
{(workflow.coverMediaPath != null || workflow.latestImage !== null) && (
|
||||
<MediaPreview
|
||||
mediaLocalPath={workflow.coverMediaPath}
|
||||
mediaLocalPath={
|
||||
workflow.coverMediaPath ?? workflow.latestImage ?? ""
|
||||
}
|
||||
size={60}
|
||||
hideBrokenImage
|
||||
isPreview
|
||||
|
||||
@@ -36,7 +36,7 @@ export class MediaTable extends TableBase<Media> {
|
||||
const newMedia = new Set(workflow?.mediaIDs ?? []).add(md.id);
|
||||
await workflowsTable?.updateMetaInfo(input.workflowID, {
|
||||
mediaIDs: Array.from(newMedia),
|
||||
coverMediaPath: md.localPath,
|
||||
latestImage: md.localPath,
|
||||
});
|
||||
// save indexdb
|
||||
indexdb.media.add(md);
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface Workflow extends SortableItem {
|
||||
cloudID?: string; // TODO will be deprecated
|
||||
cloudOrigin?: string;
|
||||
saveLock?: boolean;
|
||||
latestImage?: string;
|
||||
}
|
||||
|
||||
export interface TableBaseModel {
|
||||
|
||||
Reference in New Issue
Block a user