wip
This commit is contained in:
parent
012213fc64
commit
171632365d
|
@ -9584,6 +9584,10 @@ export interface Locale extends ILocale {
|
||||||
"disableFederationDescription": string;
|
"disableFederationDescription": string;
|
||||||
};
|
};
|
||||||
"_postForm": {
|
"_postForm": {
|
||||||
|
/**
|
||||||
|
* アップロードされていないファイルがありますが、破棄してフォームを閉じますか?
|
||||||
|
*/
|
||||||
|
"quitInspiteOfThereAreUnuploadedFilesConfirm": string;
|
||||||
/**
|
/**
|
||||||
* このノートに返信...
|
* このノートに返信...
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2522,6 +2522,7 @@ _visibility:
|
||||||
disableFederationDescription: "他サーバーへの配信を行いません"
|
disableFederationDescription: "他サーバーへの配信を行いません"
|
||||||
|
|
||||||
_postForm:
|
_postForm:
|
||||||
|
quitInspiteOfThereAreUnuploadedFilesConfirm: "アップロードされていないファイルがありますが、破棄してフォームを閉じますか?"
|
||||||
replyPlaceholder: "このノートに返信..."
|
replyPlaceholder: "このノートに返信..."
|
||||||
quotePlaceholder: "このノートを引用..."
|
quotePlaceholder: "このノートを引用..."
|
||||||
channelPlaceholder: "チャンネルに投稿..."
|
channelPlaceholder: "チャンネルに投稿..."
|
||||||
|
|
|
@ -214,6 +214,11 @@ const uploader = useUploader({
|
||||||
features: props.features,
|
features: props.features,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
uploader.events.on('itemUploaded', ctx => {
|
||||||
|
files.value.push(ctx.item.uploaded!);
|
||||||
|
uploader.removeItem(ctx.item);
|
||||||
|
});
|
||||||
|
|
||||||
const draftKey = computed((): string => {
|
const draftKey = computed((): string => {
|
||||||
let key = props.channel ? `channel:${props.channel.id}` : '';
|
let key = props.channel ? `channel:${props.channel.id}` : '';
|
||||||
|
|
||||||
|
@ -1151,8 +1156,23 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function canClose() {
|
||||||
|
if (!uploader.allItemsUploaded.value) {
|
||||||
|
const { canceled } = await os.confirm({
|
||||||
|
type: 'question',
|
||||||
|
text: i18n.ts._postForm.quitInspiteOfThereAreUnuploadedFilesConfirm,
|
||||||
|
okText: i18n.ts.yes,
|
||||||
|
cancelText: i18n.ts.no,
|
||||||
|
});
|
||||||
|
if (canceled) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
clear,
|
clear,
|
||||||
|
canClose,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkModal
|
<MkModal
|
||||||
ref="modal"
|
ref="modal"
|
||||||
:preferType="'dialog'"
|
:preferType="'dialog'"
|
||||||
@click="modal?.close()"
|
@click="_close()"
|
||||||
@closed="onModalClosed()"
|
@closed="onModalClosed()"
|
||||||
@esc="modal?.close()"
|
@esc="_close()"
|
||||||
>
|
>
|
||||||
<MkPostForm
|
<MkPostForm
|
||||||
ref="form"
|
ref="form"
|
||||||
|
@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
autofocus
|
autofocus
|
||||||
freezeAfterPosted
|
freezeAfterPosted
|
||||||
@posted="onPosted"
|
@posted="onPosted"
|
||||||
@cancel="modal?.close()"
|
@cancel="_close()"
|
||||||
@esc="modal?.close()"
|
@esc="_close()"
|
||||||
/>
|
/>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -43,6 +43,7 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const modal = useTemplateRef('modal');
|
const modal = useTemplateRef('modal');
|
||||||
|
const form = useTemplateRef('form');
|
||||||
|
|
||||||
function onPosted() {
|
function onPosted() {
|
||||||
modal.value?.close({
|
modal.value?.close({
|
||||||
|
@ -50,6 +51,12 @@ function onPosted() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function _close() {
|
||||||
|
const canClose = await form.value?.canClose();
|
||||||
|
if (!canClose) return;
|
||||||
|
modal.value?.close();
|
||||||
|
}
|
||||||
|
|
||||||
function onModalClosed() {
|
function onModalClosed() {
|
||||||
emit('closed');
|
emit('closed');
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ onMounted(() => {
|
||||||
const items = uploader.items;
|
const items = uploader.items;
|
||||||
|
|
||||||
const firstUploadAttempted = ref(false);
|
const firstUploadAttempted = ref(false);
|
||||||
const canRetry = computed(() => firstUploadAttempted.value && !items.value.some(item => item.uploading || item.preprocessing) && items.value.some(item => item.uploaded == null));
|
const canRetry = computed(() => firstUploadAttempted.value && uploader.readyForUpload.value);
|
||||||
const canDone = computed(() => items.value.some(item => item.uploaded != null));
|
const canDone = computed(() => items.value.some(item => item.uploaded != null));
|
||||||
const overallProgress = computed(() => {
|
const overallProgress = computed(() => {
|
||||||
const max = items.value.length;
|
const max = items.value.length;
|
||||||
|
@ -151,7 +151,7 @@ async function abortWithConfirm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function done() {
|
async function done() {
|
||||||
if (items.value.some(item => item.uploaded == null)) {
|
if (!uploader.allItemsUploaded.value) {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'question',
|
type: 'question',
|
||||||
text: i18n.ts._uploader.doneConfirm,
|
text: i18n.ts._uploader.doneConfirm,
|
||||||
|
|
|
@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.itemActionWrapper">
|
<div :class="$style.itemActionWrapper">
|
||||||
<MkButton :iconOnly="true" rounded @click="emit('showMenu', item, $event)"><i class="ti ti-dots"></i></MkButton>
|
<MkButton :iconOnly="true" rounded @click="emit('showMenu', item, $event)"><i class="ti ti-dots"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ item.thumbnail })` }"></div>
|
<div :class="$style.itemThumbnail" :style="{ backgroundImage: `url(${ item.thumbnail })` }" @click="onThumbnailClick(item, $event)"></div>
|
||||||
<div :class="$style.itemBody">
|
<div :class="$style.itemBody">
|
||||||
<div><MkCondensedLine :minScale="2 / 3">{{ item.name }}</MkCondensedLine></div>
|
<div><MkCondensedLine :minScale="2 / 3">{{ item.name }}</MkCondensedLine></div>
|
||||||
<div :class="$style.itemInfo">
|
<div :class="$style.itemInfo">
|
||||||
|
@ -60,6 +60,10 @@ function onContextmenu(item: UploaderItem, ev: MouseEvent) {
|
||||||
|
|
||||||
emit('showMenuViaContextmenu', item, ev);
|
emit('showMenuViaContextmenu', item, ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onThumbnailClick(item: UploaderItem, ev: MouseEvent) {
|
||||||
|
// TODO: preview when item is image
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
|
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
|
||||||
import isAnimated from 'is-file-animated';
|
import isAnimated from 'is-file-animated';
|
||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import { computed, markRaw, onMounted, onUnmounted, ref, triggerRef } from 'vue';
|
import { computed, markRaw, onMounted, onUnmounted, ref, triggerRef } from 'vue';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import { genId } from '@/utility/id.js';
|
import { genId } from '@/utility/id.js';
|
||||||
|
@ -97,6 +98,10 @@ export function useUploader(options: {
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const $i = ensureSignin();
|
const $i = ensureSignin();
|
||||||
|
|
||||||
|
const events = new EventEmitter<{
|
||||||
|
'itemUploaded': (ctx: { item: UploaderItem; }) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
const uploaderFeatures = computed<Required<UploaderFeatures>>(() => {
|
const uploaderFeatures = computed<Required<UploaderFeatures>>(() => {
|
||||||
return {
|
return {
|
||||||
effect: options.features?.effect ?? true,
|
effect: options.features?.effect ?? true,
|
||||||
|
@ -145,6 +150,11 @@ export function useUploader(options: {
|
||||||
function getMenu(item: UploaderItem): MenuItem[] {
|
function getMenu(item: UploaderItem): MenuItem[] {
|
||||||
const menu: MenuItem[] = [];
|
const menu: MenuItem[] = [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
!item.preprocessing &&
|
||||||
|
!item.uploading &&
|
||||||
|
!item.uploaded
|
||||||
|
) {
|
||||||
menu.push({
|
menu.push({
|
||||||
icon: 'ti ti-cursor-text',
|
icon: 'ti ti-cursor-text',
|
||||||
text: i18n.ts.rename,
|
text: i18n.ts.rename,
|
||||||
|
@ -161,6 +171,7 @@ export function useUploader(options: {
|
||||||
item.name = result;
|
item.name = result;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
uploaderFeatures.value.crop &&
|
uploaderFeatures.value.crop &&
|
||||||
|
@ -332,6 +343,12 @@ export function useUploader(options: {
|
||||||
if (!item.preprocessing && !item.uploading && !item.uploaded) {
|
if (!item.preprocessing && !item.uploading && !item.uploaded) {
|
||||||
menu.push({
|
menu.push({
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
|
}, {
|
||||||
|
icon: 'ti ti-upload',
|
||||||
|
text: i18n.ts.upload,
|
||||||
|
action: () => {
|
||||||
|
uploadOne(item);
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
icon: 'ti ti-x',
|
icon: 'ti ti-x',
|
||||||
text: i18n.ts.remove,
|
text: i18n.ts.remove,
|
||||||
|
@ -357,20 +374,7 @@ export function useUploader(options: {
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload() { // エラーハンドリングなどを考慮してシーケンシャルにやる
|
async function uploadOne(item: UploaderItem): Promise<void> {
|
||||||
items.value = items.value.map(item => ({
|
|
||||||
...item,
|
|
||||||
aborted: false,
|
|
||||||
uploadFailed: false,
|
|
||||||
uploading: false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
for (const item of items.value.filter(item => item.uploaded == null)) {
|
|
||||||
// アップロード処理途中で値が変わる場合(途中で全キャンセルされたりなど)もあるので、Array filterではなくここでチェック
|
|
||||||
if (item.aborted) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.uploadFailed = false;
|
item.uploadFailed = false;
|
||||||
item.uploading = true;
|
item.uploading = true;
|
||||||
|
|
||||||
|
@ -397,6 +401,7 @@ export function useUploader(options: {
|
||||||
await filePromise.then((file) => {
|
await filePromise.then((file) => {
|
||||||
item.uploaded = file;
|
item.uploaded = file;
|
||||||
item.abort = null;
|
item.abort = null;
|
||||||
|
events.emit('itemUploaded', { item });
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
item.uploadFailed = true;
|
item.uploadFailed = true;
|
||||||
item.progress = null;
|
item.progress = null;
|
||||||
|
@ -407,6 +412,23 @@ export function useUploader(options: {
|
||||||
item.uploading = false;
|
item.uploading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function upload() { // エラーハンドリングなどを考慮してシーケンシャルにやる
|
||||||
|
items.value = items.value.map(item => ({
|
||||||
|
...item,
|
||||||
|
aborted: false,
|
||||||
|
uploadFailed: false,
|
||||||
|
uploading: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const item of items.value.filter(item => item.uploaded == null)) {
|
||||||
|
// アップロード処理途中で値が変わる場合(途中で全キャンセルされたりなど)もあるので、Array filterではなくここでチェック
|
||||||
|
if (item.aborted) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await uploadOne(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function abortAll() {
|
function abortAll() {
|
||||||
|
@ -505,7 +527,9 @@ export function useUploader(options: {
|
||||||
upload,
|
upload,
|
||||||
getMenu,
|
getMenu,
|
||||||
uploading: computed(() => items.value.some(item => item.uploading)),
|
uploading: computed(() => items.value.some(item => item.uploading)),
|
||||||
readyForUpload: computed(() => items.value.length > 0 && !items.value.some(item => item.uploading || item.preprocessing)),
|
readyForUpload: computed(() => items.value.length > 0 && items.value.some(item => item.uploaded == null) && !items.value.some(item => item.uploading || item.preprocessing)),
|
||||||
|
allItemsUploaded: computed(() => items.value.every(item => item.uploaded != null)),
|
||||||
|
events,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue