summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-05-21 21:13:19 +0900
committerGitHub <noreply@github.com>2025-05-21 21:13:19 +0900
commitccf5bd337e98a470f30639ceedc16501f57aee68 (patch)
tree1dfe0f192f2a98eaee8c9482b6e45f496fbde8d8 /packages/frontend/src/components
parentfix(frontend): 誤植を修正 (#16075) (diff)
downloadmisskey-ccf5bd337e98a470f30639ceedc16501f57aee68.tar.gz
misskey-ccf5bd337e98a470f30639ceedc16501f57aee68.tar.bz2
misskey-ccf5bd337e98a470f30639ceedc16501f57aee68.zip
enhance(frontend): ファイルのアップロードを中止できるように (#16069)
* enhance(frontend): ファイルのアップロードを中止できるように * Update Changelog * fix: ダイアログを閉じたり、中断ボタンが押されたりしたときはその後のアップロードをすべて中止するように * fix
Diffstat (limited to 'packages/frontend/src/components')
-rw-r--r--packages/frontend/src/components/MkModalWindow.vue2
-rw-r--r--packages/frontend/src/components/MkUploaderDialog.vue93
2 files changed, 77 insertions, 18 deletions
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index 6b81e353e6..fd4262c17d 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -130,7 +130,7 @@ defineExpose({
}
.footer {
- padding: 8px 16px;
+ padding: 12px 16px;
overflow: auto;
background: var(--MI_THEME-bg);
border-top: 1px solid var(--MI_THEME-divider);
diff --git a/packages/frontend/src/components/MkUploaderDialog.vue b/packages/frontend/src/components/MkUploaderDialog.vue
index 3a83247d4b..b171546854 100644
--- a/packages/frontend/src/components/MkUploaderDialog.vue
+++ b/packages/frontend/src/components/MkUploaderDialog.vue
@@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.root">
<div :class="[$style.overallProgress, canRetry ? $style.overallProgressError : null]" :style="{ '--op': `${overallProgress}%` }"></div>
- <div :class="$style.main" class="_gaps_s">
- <div :class="$style.items" class="_gaps_s">
+ <div class="_gaps_s _spacer">
+ <div class="_gaps_s">
<div
v-for="ctx in items"
:key="ctx.id"
@@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #footer>
<div class="_buttonsCenter">
- <MkButton v-if="isUploading" rounded @click="cancel()"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
+ <MkButton v-if="isUploading" rounded @click="abortWithConfirm()"><i class="ti ti-x"></i> {{ i18n.ts.abort }}</MkButton>
<MkButton v-else-if="!firstUploadAttempted" primary rounded @click="upload()"><i class="ti ti-upload"></i> {{ i18n.ts.upload }}</MkButton>
<MkButton v-if="canRetry" rounded @click="upload()"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
@@ -96,10 +96,9 @@ import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js';
import MkButton from '@/components/MkButton.vue';
import bytes from '@/filters/bytes.js';
-import MkSwitch from '@/components/MkSwitch.vue';
import MkSelect from '@/components/MkSelect.vue';
import { isWebpSupported } from '@/utility/isWebpSupported.js';
-import { uploadFile } from '@/utility/drive.js';
+import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
import * as os from '@/os.js';
import { ensureSignin } from '@/i.js';
@@ -138,7 +137,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
-const items = ref([] as {
+const items = ref<{
id: string;
name: string;
progress: { max: number; value: number } | null;
@@ -147,10 +146,12 @@ const items = ref([] as {
uploading: boolean;
uploaded: Misskey.entities.DriveFile | null;
uploadFailed: boolean;
+ aborted: boolean;
compressedSize?: number | null;
compressedImage?: Blob | null;
file: File;
-}[]);
+ abort?: (() => void) | null;
+}[]>([]);
const dialog = useTemplateRef('dialog');
@@ -213,10 +214,23 @@ async function cancel() {
});
if (canceled) return;
+ abortAll();
emit('canceled');
dialog.value?.close();
}
+async function abortWithConfirm() {
+ const { canceled } = await os.confirm({
+ type: 'question',
+ text: i18n.ts._uploader.abortConfirm,
+ okText: i18n.ts.yes,
+ cancelText: i18n.ts.no,
+ });
+ if (canceled) return;
+
+ abortAll();
+}
+
async function done() {
if (items.value.some(item => item.uploaded == null)) {
const { canceled } = await os.confirm({
@@ -258,6 +272,17 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
items.value.splice(items.value.indexOf(item), 1);
},
});
+ } else if (item.uploading) {
+ menu.push({
+ icon: 'ti ti-cloud-pause',
+ text: i18n.ts.abort,
+ danger: true,
+ action: () => {
+ if (item.abort != null) {
+ item.abort();
+ }
+ }
+ });
}
os.popupMenu(menu, ev.currentTarget ?? ev.target);
@@ -266,7 +291,20 @@ function showMenu(ev: MouseEvent, item: typeof items.value[0]) {
async function upload() { // エラーハンドリングなどを考慮してシーケンシャルにやる
firstUploadAttempted.value = true;
+ items.value = items.value.map(item => ({
+ ...item,
+ aborted: false,
+ uploadFailed: false,
+ waiting: false,
+ uploading: false,
+ }));
+
for (const item of items.value.filter(item => item.uploaded == null)) {
+ // アップロード処理途中で値が変わる場合(途中で全キャンセルされたりなど)もあるので、Array filterではなくここでチェック
+ if (item.aborted) {
+ continue;
+ }
+
item.waiting = true;
item.uploadFailed = false;
@@ -296,7 +334,7 @@ async function upload() { // エラーハンドリングなどを考慮してシ
item.uploading = true;
- const driveFile = await uploadFile(item.compressedImage ?? item.file, {
+ const { filePromise, abort } = uploadFile(item.compressedImage ?? item.file, {
name: item.name,
folderId: props.folderId,
onProgress: (progress) => {
@@ -308,16 +346,43 @@ async function upload() { // エラーハンドリングなどを考慮してシ
item.progress.max = progress.total;
}
},
+ });
+
+ item.abort = () => {
+ item.abort = null;
+ abort();
+ item.uploading = false;
+ item.waiting = false;
+ item.uploadFailed = true;
+ };
+
+ await filePromise.then((file) => {
+ item.uploaded = file;
+ item.abort = null;
}).catch(err => {
item.uploadFailed = true;
item.progress = null;
- throw err;
+ if (!(err instanceof UploadAbortedError)) {
+ throw err;
+ }
}).finally(() => {
item.uploading = false;
item.waiting = false;
});
+ }
+}
- item.uploaded = driveFile;
+function abortAll() {
+ for (const item of items.value) {
+ if (item.uploaded != null) {
+ continue;
+ }
+
+ if (item.abort != null) {
+ item.abort();
+ }
+ item.aborted = true;
+ item.uploadFailed = true;
}
}
@@ -340,6 +405,7 @@ function initializeFile(file: File) {
thumbnail: window.URL.createObjectURL(file),
waiting: false,
uploading: false,
+ aborted: false,
uploaded: null,
uploadFailed: false,
file: markRaw(file),
@@ -373,13 +439,6 @@ onMounted(() => {
}
}
-.main {
- padding: 12px;
-}
-
-.items {
-}
-
.item {
position: relative;
border-radius: 10px;