fix: upload manager — URL imports, active count, board reset
1. URL imports now participate in upload manager pipeline: addUrlJob() creates a job row, uploadComplete/setFailed called on success/failure — consistent with local file uploads 2. activeCount only counts uploading/processing jobs, not failed. Failed jobs no longer inflate the "Uploads (n)" header count. 3. uploadManager.clear() called on boardId change so stale jobs from a previous board don't linger in the next board's panel.
This commit is contained in:
@@ -119,14 +119,22 @@ export function setupDragDrop(
|
|||||||
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
|
const world = viewport.toWorld(e.clientX - rect.left, e.clientY - rect.top);
|
||||||
const placeholder = createPlaceholder(viewport, world.x, world.y);
|
const placeholder = createPlaceholder(viewport, world.x, world.y);
|
||||||
|
|
||||||
|
// Derive a filename from the URL for the upload manager
|
||||||
|
const urlFilename = url.split('/').pop()?.split('?')[0] || 'url-import';
|
||||||
|
const isVideoUrl = /\.(mp4|webm|mov)(\?|$)/i.test(url);
|
||||||
|
const jobId = uploads?.addUrlJob(urlFilename, isVideoUrl ? 'video' : 'image');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await uploadImageFromUrl(boardId, url);
|
const res = await uploadImageFromUrl(boardId, url);
|
||||||
removePlaceholder(viewport, placeholder);
|
removePlaceholder(viewport, placeholder);
|
||||||
const { id } = handleUploadResult(res, viewport, sceneManager, world.x, world.y, onChange);
|
const { id } = handleUploadResult(res, viewport, sceneManager, world.x, world.y, onChange);
|
||||||
selection?.selectOnly(id);
|
selection?.selectOnly(id);
|
||||||
|
const imgData = res.data.image || res.data;
|
||||||
|
if (jobId) uploads?.uploadComplete(jobId, imgData.id);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('URL image upload failed:', err);
|
console.error('URL image upload failed:', err);
|
||||||
removePlaceholder(viewport, placeholder);
|
removePlaceholder(viewport, placeholder);
|
||||||
|
if (jobId) uploads?.setFailed(jobId, err.response?.data?.error || err.message || 'URL import failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ export default function Editor({ isPublicView }: EditorProps) {
|
|||||||
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
const canvasContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const [uploadManager] = useState(() => new UploadManager());
|
const [uploadManager] = useState(() => new UploadManager());
|
||||||
|
|
||||||
|
// Reset upload manager when switching boards
|
||||||
|
useEffect(() => {
|
||||||
|
uploadManager.clear();
|
||||||
|
}, [boardId, uploadManager]);
|
||||||
|
|
||||||
// UI state
|
// UI state
|
||||||
const [activeTool, setActiveTool] = useState<ToolType>(ToolType.SELECT);
|
const [activeTool, setActiveTool] = useState<ToolType>(ToolType.SELECT);
|
||||||
const [color, setColor] = useState('#ffffff');
|
const [color, setColor] = useState('#ffffff');
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class UploadManager {
|
|||||||
for (const fn of this._listeners) fn();
|
for (const fn of this._listeners) fn();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new upload job. Returns the job ID. */
|
/** Create a new upload job from a local file. Returns the job ID. */
|
||||||
addJob(file: File, boardId: string): string {
|
addJob(file: File, boardId: string): string {
|
||||||
const id = crypto.randomUUID();
|
const id = crypto.randomUUID();
|
||||||
const isVideo = file.type.startsWith('video/');
|
const isVideo = file.type.startsWith('video/');
|
||||||
@@ -51,6 +51,22 @@ export class UploadManager {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create a job for a URL-based import (no File object, unknown size). */
|
||||||
|
addUrlJob(fileName: string, mediaType: 'image' | 'video'): string {
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
this.jobs.set(id, {
|
||||||
|
id,
|
||||||
|
fileName,
|
||||||
|
fileSize: 0,
|
||||||
|
mediaType,
|
||||||
|
status: 'uploading',
|
||||||
|
progress: 0,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
this._notify();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
/** Update upload progress (0-1). */
|
/** Update upload progress (0-1). */
|
||||||
setProgress(jobId: string, progress: number) {
|
setProgress(jobId: string, progress: number) {
|
||||||
const job = this.jobs.get(jobId);
|
const job = this.jobs.get(jobId);
|
||||||
@@ -116,6 +132,13 @@ export class UploadManager {
|
|||||||
this._notify();
|
this._notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Remove all jobs (used on board change). */
|
||||||
|
clear() {
|
||||||
|
for (const id of this._dismissTimers.keys()) this._clearDismissTimer(id);
|
||||||
|
this.jobs.clear();
|
||||||
|
this._notify();
|
||||||
|
}
|
||||||
|
|
||||||
/** Remove all completed/failed jobs. */
|
/** Remove all completed/failed jobs. */
|
||||||
clearFinished() {
|
clearFinished() {
|
||||||
for (const [id, job] of this.jobs) {
|
for (const [id, job] of this.jobs) {
|
||||||
@@ -127,11 +150,11 @@ export class UploadManager {
|
|||||||
this._notify();
|
this._notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get active (non-done) job count. */
|
/** Get active (uploading or processing) job count. */
|
||||||
get activeCount(): number {
|
get activeCount(): number {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (const job of this.jobs.values()) {
|
for (const job of this.jobs.values()) {
|
||||||
if (job.status !== 'done') count++;
|
if (job.status === 'uploading' || job.status === 'processing') count++;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user