mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-07-27 14:14:36 +02:00
enhance(frontend): UploaderItemsの詳細メニューからもプレビューを起動できるように (#17811)
* enhance(frontend): UploaderItemsの詳細メニューからもプレビューを起動できるように * clean up
This commit is contained in:
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onUnmounted, watch } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { isLink } from '@@/js/is-link.js';
|
||||
import type { UploaderItem } from '@/composables/use-uploader.js';
|
||||
import { getUploadName } from '@/composables/use-uploader.js';
|
||||
@@ -57,6 +57,7 @@ import { i18n } from '@/i18n.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import bytes from '@/filters/bytes.js';
|
||||
import * as os from '@/os.js';
|
||||
import type { Content } from '@/components/MkLightbox.item.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
items: UploaderItem[];
|
||||
@@ -72,32 +73,6 @@ const emit = defineEmits<{
|
||||
(ev: 'showMenuViaContextmenu', item: UploaderItem, event: PointerEvent): void;
|
||||
}>();
|
||||
|
||||
//#region objectUrlMap
|
||||
const objectUrlMap = new Map<UploaderItem, string>();
|
||||
|
||||
watch(() => props.items, () => {
|
||||
for (const item of props.items) {
|
||||
if (!objectUrlMap.has(item)) {
|
||||
objectUrlMap.set(item, URL.createObjectURL(item.file));
|
||||
}
|
||||
}
|
||||
|
||||
for (const [item, url] of objectUrlMap.entries()) {
|
||||
if (!props.items.includes(item)) {
|
||||
URL.revokeObjectURL(url);
|
||||
objectUrlMap.delete(item);
|
||||
}
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
|
||||
onUnmounted(() => {
|
||||
for (const url of objectUrlMap.values()) {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
objectUrlMap.clear();
|
||||
});
|
||||
//#endregion
|
||||
|
||||
function getUploadNameParts(item: UploaderItem): {
|
||||
baseName: string;
|
||||
extension: string | null;
|
||||
@@ -127,13 +102,15 @@ function onContextmenu(item: UploaderItem, ev: PointerEvent) {
|
||||
|
||||
async function onThumbnailClick(item: UploaderItem, ev: PointerEvent) {
|
||||
if (item.file.type.startsWith('image') || item.file.type.startsWith('video')) {
|
||||
const contents = props.items.filter(item => item.file.type.startsWith('image') || item.file.type.startsWith('video')).map(item => ({
|
||||
id: item.id,
|
||||
type: (item.file.type.startsWith('video') ? 'video' as const : 'image' as const),
|
||||
url: objectUrlMap.get(item)!,
|
||||
thumbnailUrl: item.thumbnail,
|
||||
filename: getUploadName(item),
|
||||
}));
|
||||
const contents = props.items
|
||||
.filter(item => item.file.type.startsWith('image') || item.file.type.startsWith('video'))
|
||||
.map<Content>(item => ({
|
||||
id: item.id,
|
||||
type: (item.file.type.startsWith('video') ? 'video' : 'image'),
|
||||
url: item.objectUrl,
|
||||
thumbnailUrl: item.thumbnail,
|
||||
filename: getUploadName(item),
|
||||
}));
|
||||
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkLightbox.vue').then(x => x.default), {
|
||||
defaultIndex: contents.findIndex(content => content.id === item.id),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { isWebpSupported } from '@/utility/isWebpSupported.js';
|
||||
import { uploadFile, UploadAbortedError } from '@/utility/drive.js';
|
||||
import type { Content } from '@/components/MkLightbox.item.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
|
||||
@@ -74,6 +75,7 @@ export type UploaderItem = {
|
||||
compressedSize?: number | null;
|
||||
preprocessedFile?: Blob | null;
|
||||
file: File;
|
||||
objectUrl: string;
|
||||
watermarkPreset: WatermarkPreset | null;
|
||||
watermarkLayers: WatermarkLayers | null;
|
||||
imageFrameParams: ImageFrameParams | null;
|
||||
@@ -133,12 +135,13 @@ export function useUploader(options: {
|
||||
const filename = file.name ?? 'untitled';
|
||||
const extension = filename.split('.').length > 1 ? '.' + filename.split('.').pop() : '';
|
||||
const watermarkPreset = uploaderFeatures.value.watermark && $i.policies.watermarkAvailable ? (prefer.s.watermarkPresets.find(p => p.id === prefer.s.defaultWatermarkPresetId) ?? null) : null;
|
||||
const objectUrl = window.URL.createObjectURL(file);
|
||||
items.value.push({
|
||||
id,
|
||||
name: prefer.s.keepOriginalFilename ? filename : id + extension,
|
||||
suffix: '',
|
||||
progress: null,
|
||||
thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? window.URL.createObjectURL(file) : null,
|
||||
thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? objectUrl : null,
|
||||
preprocessing: false,
|
||||
preprocessProgress: null,
|
||||
uploading: false,
|
||||
@@ -150,6 +153,7 @@ export function useUploader(options: {
|
||||
watermarkLayers: watermarkPreset?.layers ?? null,
|
||||
imageFrameParams: null,
|
||||
file: markRaw(file),
|
||||
objectUrl,
|
||||
});
|
||||
const reactiveItem = items.value.at(-1)!;
|
||||
preprocess(reactiveItem).then(() => {
|
||||
@@ -163,8 +167,24 @@ export function useUploader(options: {
|
||||
}
|
||||
}
|
||||
|
||||
function removeItem(item: UploaderItem) {
|
||||
function revokeItemObjectUrls(item: UploaderItem) {
|
||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||
URL.revokeObjectURL(item.objectUrl);
|
||||
}
|
||||
|
||||
function createItemObjectUrl(item: UploaderItem, file: Blob | File): string {
|
||||
revokeItemObjectUrls(item);
|
||||
return window.URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
function updateItemObjectUrls(item: UploaderItem, file: Blob | File) {
|
||||
const newObjectUrl = createItemObjectUrl(item, file);
|
||||
item.objectUrl = newObjectUrl;
|
||||
item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? newObjectUrl : null;
|
||||
}
|
||||
|
||||
function removeItem(item: UploaderItem) {
|
||||
revokeItemObjectUrls(item);
|
||||
items.value.splice(items.value.indexOf(item), 1);
|
||||
}
|
||||
|
||||
@@ -214,6 +234,28 @@ export function useUploader(options: {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
}, {
|
||||
text: i18n.ts.preview,
|
||||
icon: 'ti ti-photo-search',
|
||||
action: async () => {
|
||||
const contents = items.value
|
||||
.filter(item => item.file.type.startsWith('image/') || item.file.type.startsWith('video/'))
|
||||
.map<Content>(item => ({
|
||||
id: item.id,
|
||||
type: item.file.type.startsWith('video/') ? 'video' : 'image',
|
||||
url: item.objectUrl,
|
||||
thumbnail: item.thumbnail,
|
||||
filename: getUploadName(item),
|
||||
caption: item.caption ?? null,
|
||||
}));
|
||||
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('@/components/MkLightbox.vue').then(x => x.default), {
|
||||
defaultIndex: contents.findIndex(x => x.id === item.id),
|
||||
contents,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
},
|
||||
}, {
|
||||
type: 'divider',
|
||||
});
|
||||
@@ -235,11 +277,12 @@ export function useUploader(options: {
|
||||
text: i18n.ts.cropImage,
|
||||
action: async () => {
|
||||
const cropped = await os.cropImageFile(item.file, { aspectRatio: null });
|
||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||
const newObjectUrl = createItemObjectUrl(item, cropped);
|
||||
items.value.splice(items.value.indexOf(item), 1, {
|
||||
...item,
|
||||
file: markRaw(cropped),
|
||||
thumbnail: window.URL.createObjectURL(cropped),
|
||||
thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(cropped.type) ? newObjectUrl : null,
|
||||
objectUrl: newObjectUrl,
|
||||
});
|
||||
const reactiveItem = items.value.find(x => x.id === item.id)!;
|
||||
preprocess(reactiveItem).then(() => {
|
||||
@@ -260,11 +303,12 @@ export function useUploader(options: {
|
||||
image: item.file,
|
||||
}, {
|
||||
ok: (file) => {
|
||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||
const newObjectUrl = createItemObjectUrl(item, file);
|
||||
items.value.splice(items.value.indexOf(item), 1, {
|
||||
...item,
|
||||
file: markRaw(file),
|
||||
thumbnail: window.URL.createObjectURL(file),
|
||||
thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? newObjectUrl : null,
|
||||
objectUrl: newObjectUrl,
|
||||
});
|
||||
const reactiveItem = items.value.find(x => x.id === item.id)!;
|
||||
preprocess(reactiveItem).then(() => {
|
||||
@@ -694,8 +738,7 @@ export function useUploader(options: {
|
||||
|
||||
imageBitmap.close();
|
||||
|
||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||
item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(preprocessedFile.type) ? window.URL.createObjectURL(preprocessedFile) : null;
|
||||
updateItemObjectUrls(item, preprocessedFile);
|
||||
item.preprocessedFile = markRaw(preprocessedFile);
|
||||
}
|
||||
|
||||
@@ -754,14 +797,13 @@ export function useUploader(options: {
|
||||
item.suffix = '';
|
||||
}
|
||||
|
||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||
item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(preprocessedFile.type) ? window.URL.createObjectURL(preprocessedFile) : null;
|
||||
updateItemObjectUrls(item, preprocessedFile);
|
||||
item.preprocessedFile = markRaw(preprocessedFile);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
for (const item of items.value) {
|
||||
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||
revokeItemObjectUrls(item);
|
||||
}
|
||||
|
||||
abortAll();
|
||||
|
||||
Reference in New Issue
Block a user