diff --git a/frontend/portal/src/api/common.js b/frontend/portal/src/api/common.js index 951e31a..0389283 100644 --- a/frontend/portal/src/api/common.js +++ b/frontend/portal/src/api/common.js @@ -10,62 +10,84 @@ export const commonApi = { formData.append('type', type); return request('/upload', { method: 'POST', body: formData }); }, - uploadMultipart: async (file, hash, onProgress) => { - // 1. Check Hash - try { - const res = await commonApi.checkHash(hash); - if (res) { - if (onProgress) onProgress(100); - return res; + uploadMultipart: (file, hash, type, onProgress) => { + const controller = new AbortController(); + const signal = controller.signal; + + const promise = (async () => { + // 1. Check Hash + try { + const res = await commonApi.checkHash(hash); + if (res) { + if (onProgress) onProgress(100); + return res; + } + } catch (e) { + // Ignore hash check errors } - } catch(e) {} - // 2. Init - const initRes = await request('/upload/init', { - method: 'POST', - body: { - filename: file.name, - size: file.size, - mime_type: file.type, - hash: hash + if (signal.aborted) throw new Error('Aborted'); + + // 2. Init + const initRes = await request('/upload/init', { + method: 'POST', + body: { + filename: file.name, + size: file.size, + mime_type: file.type, + hash: hash, + type: type + }, + signal + }); + + const { upload_id, chunk_size } = initRes; + const totalChunks = Math.ceil(file.size / chunk_size); + + // 3. Upload Parts + for (let i = 0; i < totalChunks; i++) { + if (signal.aborted) throw new Error('Aborted'); + + const start = i * chunk_size; + const end = Math.min(start + chunk_size, file.size); + const chunk = file.slice(start, end); + + const formData = new FormData(); + formData.append('file', chunk); + formData.append('upload_id', upload_id); + formData.append('part_number', i + 1); + + // request helper with FormData handles content-type, but we need signal + await request('/upload/part', { + method: 'POST', + body: formData, + signal + }); + + if (onProgress) { + const percent = Math.round(((i + 1) / totalChunks) * 100); + onProgress(percent); + } } - }); - const { upload_id, chunk_size } = initRes; - const totalChunks = Math.ceil(file.size / chunk_size); + // 4. Complete + return request('/upload/complete', { + method: 'POST', + body: { upload_id }, + signal + }); + })(); - // 3. Upload Parts - for (let i = 0; i < totalChunks; i++) { - const start = i * chunk_size; - const end = Math.min(start + chunk_size, file.size); - const chunk = file.slice(start, end); - - const formData = new FormData(); - formData.append('file', chunk); - formData.append('upload_id', upload_id); - formData.append('part_number', i + 1); - - await request('/upload/part', { method: 'POST', body: formData }); - - if (onProgress) { - const percent = Math.round(((i + 1) / totalChunks) * 100); - onProgress(percent); - } - } - - // 4. Complete - return request('/upload/complete', { - method: 'POST', - body: { upload_id } - }); + return { promise, abort: () => controller.abort() }; }, uploadWithProgress: (file, type, onProgress) => { - return new Promise((resolve, reject) => { + let xhr; + const promise = new Promise((resolve, reject) => { const formData = new FormData(); formData.append('file', file); formData.append('type', type); - const xhr = new XMLHttpRequest(); + xhr = new XMLHttpRequest(); xhr.open('POST', '/v1/upload'); const token = localStorage.getItem('token'); @@ -94,7 +116,10 @@ export const commonApi = { }; xhr.onerror = () => reject(new Error('Network Error')); + xhr.onabort = () => reject(new Error('Aborted')); xhr.send(formData); }); + + return { promise, abort: () => xhr.abort() }; } }; diff --git a/frontend/portal/src/views/creator/ContentsEditView.vue b/frontend/portal/src/views/creator/ContentsEditView.vue index 1768ec3..4527689 100644 --- a/frontend/portal/src/views/creator/ContentsEditView.vue +++ b/frontend/portal/src/views/creator/ContentsEditView.vue @@ -25,7 +25,6 @@
-
@@ -98,9 +97,13 @@
- +