This commit is contained in:
syuilo 2025-05-13 20:10:53 +09:00
parent 6f530b7cbf
commit 01976c02fb
26 changed files with 256 additions and 332 deletions

16
locales/index.d.ts vendored
View File

@ -11453,22 +11453,6 @@ export interface Locale extends ILocale {
* "category"
*/
"directoryToCategoryCaption": string;
/**
*
*/
"emojiInputAreaCaption": string;
/**
*
*/
"emojiInputAreaList1": string;
/**
* PCから選択する
*/
"emojiInputAreaList2": string;
/**
*
*/
"emojiInputAreaList3": string;
/**
* {count}
*/

View File

@ -148,7 +148,7 @@ import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { claimAchievement } from '@/utility/achievements.js';
import { prefer } from '@/preferences.js';
import { chooseFileFromPcAndUpload } from '@/utility/select-file.js';
import { chooseFileFromPcAndUpload } from '@/utility/drive.js';
import { store } from '@/store.js';
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
import { usePagination } from '@/composables/use-pagination.js';

View File

@ -15,7 +15,7 @@ import * as Misskey from 'misskey-js';
import { computed, ref } from 'vue';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import { misskeyApi } from '@/utility/misskey-api.js';
const props = defineProps<{

View File

@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
</div>
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName" @replaceFile="replaceFile"/>
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/>
<div v-if="showingOptions" style="padding: 8px 16px;">
@ -120,7 +120,7 @@ import { formatTimeString } from '@/utility/format-time-string.js';
import { Autocomplete } from '@/utility/autocomplete.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { selectFiles } from '@/utility/select-file.js';
import { selectFiles } from '@/utility/drive.js';
import { store } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue';
import { i18n } from '@/i18n.js';
@ -459,10 +459,6 @@ function updateFileName(file, name) {
files.value[files.value.findIndex(x => x.id === file.id)].name = name;
}
function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void {
files.value[files.value.findIndex(x => x.id === file.id)] = newFile;
}
function setVisibility() {
if (props.channel) {
visibility.value = 'public';

View File

@ -58,7 +58,6 @@ const emit = defineEmits<{
(ev: 'detach', id: string): void;
(ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void;
(ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void;
(ev: 'replaceFile', file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void;
}>();
let menuShowing = false;
@ -142,13 +141,6 @@ async function describe(file: Misskey.entities.DriveFile) {
});
}
async function crop(file: Misskey.entities.DriveFile): Promise<void> {
if (mock) return;
const newFile = await os.createCroppedImageDriveFileFromImageDriveFile(file, { aspectRatio: NaN });
emit('replaceFile', file, newFile);
}
function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | KeyboardEvent): void {
if (menuShowing) return;
@ -172,10 +164,6 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar
if (isImage) {
menuItems.push({
text: i18n.ts.cropImage,
icon: 'ti ti-crop',
action: () : void => { crop(file); },
}, {
text: i18n.ts.preview,
icon: 'ti ti-photo-search',
action: () => {

View File

@ -44,6 +44,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
import MkRadio from '@/components/MkRadio.vue';
import * as os from '@/os.js';
import { $i } from '@/i.js';
import { chooseDriveFile } from '@/utility/drive.js';
const text = ref('');
const flag = ref(true);
@ -79,7 +80,7 @@ const openForm = async () => {
};
const openDrive = async () => {
await os.selectDriveFile({
await chooseDriveFile({
multiple: false,
});
};

View File

@ -79,21 +79,17 @@ import { computed, markRaw, onMounted, ref, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { v4 as uuid } from 'uuid';
import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
import { apiUrl } from '@@/js/config.js';
import isAnimated from 'is-file-animated';
import type { BrowserImageResizerConfigWithConvertedOutput } from '@misskey-dev/browser-image-resizer';
import type { MenuItem } from '@/types/menu.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js';
import { ensureSignin } from '@/i.js';
import { instance } from '@/instance.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/upload.js';
import { uploadFile } from '@/utility/drive.js';
import * as os from '@/os.js';
const COMPRESSION_SUPPORTED_TYPES = [

View File

@ -9,7 +9,6 @@ import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
import { EventEmitter } from 'eventemitter3';
import * as Misskey from 'misskey-js';
import { getProxiedImageUrl } from './utility/media-proxy.js';
import { uploadFile } from './utility/upload.js';
import type { Component, Ref } from 'vue';
import type { ComponentProps as CP } from 'vue-component-type-helpers';
import type { Form, GetFormResultType } from '@/utility/form.js';
@ -594,21 +593,6 @@ export async function selectUser(opts: { includeSelf?: boolean; localOnly?: bool
});
}
export async function selectDriveFile(options: { multiple?: boolean; } = {}): Promise<Misskey.entities.DriveFile[]> {
return new Promise(resolve => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDriveFileSelectDialog.vue')), {
multiple: options.multiple,
}, {
done: files => {
if (files) {
resolve(files);
}
},
closed: () => dispose(),
});
});
}
export async function selectDriveFolder(initialFolder: Misskey.entities.DriveFolder['id'] | null): Promise<Misskey.entities.DriveFolder[]> {
return new Promise(resolve => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDriveFolderSelectDialog.vue')), {
@ -671,35 +655,6 @@ export async function cropImageFile(imageFile: File | Blob, options: {
});
}
export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: {
aspectRatio: number;
}): Promise<Misskey.entities.DriveFile> {
return new Promise(resolve => {
const imgUrl = getProxiedImageUrl(imageDriveFile.url, undefined, true);
const image = new Image();
image.src = imgUrl;
image.onload = () => {
const canvas = window.document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
canvas.toBlob(blob => {
cropImageFile(blob, {
aspectRatio: options.aspectRatio,
}).then(croppedImageFile => {
uploadFile(croppedImageFile, {
name: imageDriveFile.name,
folderId: imageDriveFile.folderId,
}).then(driveFile => {
resolve(driveFile);
});
});
});
};
});
}
export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | null, options?: {
align?: string;
width?: number;

View File

@ -87,7 +87,7 @@ import MkButton from '@/components/MkButton.vue';
import { validators } from '@/components/grid/cell-validators.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import MkPagingButtons from '@/components/MkPagingButtons.vue';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
import { useLoading } from '@/composables/use-loading.js';

View File

@ -83,7 +83,7 @@ import MkFolder from '@/components/MkFolder.vue';
import MkButton from '@/components/MkButton.vue';
import * as os from '@/os.js';
import { validators } from '@/components/grid/cell-validators.js';
import { chooseFileFromDrive, chooseFileFromPcAndUpload } from '@/utility/select-file.js';
import { chooseDriveFile, chooseFileFromPcAndUpload } from '@/utility/drive.js';
import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
@ -311,7 +311,7 @@ async function onFileSelectClicked() {
}
async function onDriveSelectClicked() {
const driveFiles = await chooseFileFromDrive({
const driveFiles = await chooseDriveFile({
multiple: true,
});
gridItems.value.push(...driveFiles.map(fromDriveFile));

View File

@ -73,7 +73,7 @@ import * as Misskey from 'misskey-js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
import MkColorInput from '@/components/MkColorInput.vue';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';

View File

@ -38,7 +38,7 @@ import { onMounted, watch, ref, shallowRef, computed, nextTick, readonly, onBefo
import * as Misskey from 'misskey-js';
//import insertTextAtCursor from 'insert-text-at-cursor';
import { formatTimeString } from '@/utility/format-time-string.js';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js';

View File

@ -78,7 +78,7 @@ import MkPagination from '@/components/MkPagination.vue';
import MkRemoteEmojiEditDialog from '@/components/MkRemoteEmojiEditDialog.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSplit from '@/components/form/split.vue';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { getProxiedImageUrl } from '@/utility/media-proxy.js';

View File

@ -20,9 +20,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<button v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
<i class="ti ti-pencil"></i>
</button>
<button v-if="isImage" v-tooltip="i18n.ts.cropImage" class="_button" :class="$style.fileQuickActionsOthersButton" @click="crop()">
<i class="ti ti-crop"></i>
</button>
<button v-if="file.isSensitive" v-tooltip="i18n.ts.unmarkAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
<i class="ti ti-eye"></i>
</button>
@ -127,15 +124,6 @@ function postThis() {
});
}
function crop() {
if (!file.value) return;
os.createCroppedImageDriveFileFromImageDriveFile(file.value, {
aspectRatio: NaN,
uploadFolder: file.value.folderId ?? null,
});
}
function move() {
if (!file.value) return;

View File

@ -91,7 +91,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { customEmojiCategories } from '@/custom-emojis.js';
import MkSwitch from '@/components/MkSwitch.vue';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import MkRolePreview from '@/components/MkRolePreview.vue';
const props = defineProps<{

View File

@ -44,7 +44,7 @@ import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSuspense from '@/components/form/suspense.vue';
import { selectFiles } from '@/utility/select-file.js';
import { selectFiles } from '@/utility/drive.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';

View File

@ -20,14 +20,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import XContainer from '../page-editor.container.vue';
import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js';
import { chooseDriveFile } from '@/utility/drive.js';
const props = defineProps<{
modelValue: Misskey.entities.PageBlock & { type: 'image' };
@ -41,7 +41,7 @@ const emit = defineEmits<{
const file = ref<Misskey.entities.DriveFile | null>(null);
async function choose() {
os.selectDriveFile({ multiple: false }).then((fileResponse) => {
chooseDriveFile({ multiple: false }).then((fileResponse) => {
file.value = fileResponse[0];
emit('update:modelValue', {
...props.modelValue,

View File

@ -71,7 +71,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkInput from '@/components/MkInput.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { $i } from '@/i.js';

View File

@ -164,7 +164,7 @@ import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
import { $i } from '@/i.js';

View File

@ -98,7 +98,7 @@ import { definePage } from '@/page.js';
import { prefer } from '@/preferences.js';
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
import { reloadAsk } from '@/utility/reload-ask.js';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
const navWindow = prefer.model('deck.navWindow');
const useSimpleUiForNonRootPages = prefer.model('deck.useSimpleUiForNonRootPages');

View File

@ -161,7 +161,7 @@ import MkSelect from '@/components/MkSelect.vue';
import FormSplit from '@/components/form/split.vue';
import MkFolder from '@/components/MkFolder.vue';
import FormSlot from '@/components/form/slot.vue';
import { selectFile } from '@/utility/select-file.js';
import { chooseDriveFile } from '@/utility/drive.js';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { ensureSignin } from '@/i.js';
@ -298,7 +298,7 @@ function changeAvatar(ev) {
text: i18n.ts.fromDrive,
icon: 'ti ti-cloud',
action: () => {
os.selectDriveFile({ multiple: false }).then(files => {
chooseDriveFile({ multiple: false }).then(files => {
done(files[0]);
});
},
@ -346,7 +346,7 @@ function changeBanner(ev) {
text: i18n.ts.fromDrive,
icon: 'ti ti-cloud',
action: () => {
os.selectDriveFile({ multiple: false }).then(files => {
chooseDriveFile({ multiple: false }).then(files => {
done(files[0]);
});
},

View File

@ -40,7 +40,7 @@ import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/utility/sound.js';
import { selectFile } from '@/utility/select-file.js';
import { selectFile } from '@/utility/drive.js';
const props = defineProps<{
type: SoundType;

View File

@ -0,0 +1,231 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
import { apiUrl } from '@@/js/config.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js';
import { $i } from '@/i.js';
import { instance } from '@/instance.js';
import { globalEvents } from '@/events.js';
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
export function uploadFile(file: File | Blob, options: {
name?: string;
folderId?: string | null;
onProgress?: (ctx: { total: number; loaded: number; }) => void;
} = {}): Promise<Misskey.entities.DriveFile> {
return new Promise((resolve, reject) => {
if ($i == null) return reject();
if ((file.size > instance.maxFileSize) || (file.size > ($i.policies.maxFileSizeMb * 1024 * 1024))) {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
});
return reject();
}
const xhr = new XMLHttpRequest();
xhr.open('POST', apiUrl + '/drive/files/create', true);
xhr.onload = ((ev: ProgressEvent<XMLHttpRequest>) => {
if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
if (xhr.status === 413) {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
});
} else if (ev.target?.response) {
const res = JSON.parse(ev.target.response);
if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseInappropriate,
});
} else if (res.error?.id === 'd08dbc37-a6a9-463a-8c47-96c32ab5f064') {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseNoFreeSpace,
});
} else {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: `${res.error?.message}\n${res.error?.code}\n${res.error?.id}`,
});
}
} else {
os.alert({
type: 'error',
title: 'Failed to upload',
text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`,
});
}
reject();
return;
}
const driveFile = JSON.parse(ev.target.response);
globalEvents.emit('driveFileCreated', driveFile);
resolve(driveFile);
}) as (ev: ProgressEvent<EventTarget>) => any;
if (options.onProgress) {
xhr.upload.onprogress = ev => {
if (ev.lengthComputable) {
options.onProgress({
total: ev.total,
loaded: ev.loaded,
});
}
};
}
const formData = new FormData();
formData.append('i', $i.token);
formData.append('force', 'true');
formData.append('file', file);
formData.append('name', options.name ?? file.name ?? 'untitled');
if (options.folderId) formData.append('folderId', options.folderId);
xhr.send(formData);
});
}
export function chooseFileFromPcAndUpload(
options: {
multiple?: boolean;
folderId?: string | null;
} = {},
): Promise<Misskey.entities.DriveFile[]> {
return new Promise((res, rej) => {
os.chooseFileFromPc({ multiple: options.multiple }).then(files => {
if (files.length === 0) return;
os.launchUploader(files, {
folderId: options.folderId,
}).then(driveFiles => {
res(driveFiles);
});
});
});
}
export function chooseDriveFile(options: {
multiple?: boolean;
} = {}): Promise<Misskey.entities.DriveFile[]> {
return new Promise(resolve => {
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkDriveFileSelectDialog.vue')), {
multiple: options.multiple,
}, {
done: files => {
if (files) {
resolve(files);
}
},
closed: () => dispose(),
});
});
}
export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
return new Promise((res, rej) => {
os.inputText({
title: i18n.ts.uploadFromUrl,
type: 'url',
placeholder: i18n.ts.uploadFromUrlDescription,
}).then(({ canceled, result: url }) => {
if (canceled) return;
const marker = Math.random().toString(); // TODO: UUIDとか使う
// TODO: no websocketモード対応
const connection = useStream().useChannel('main');
connection.on('urlUploadFinished', urlResponse => {
if (urlResponse.marker === marker) {
res(urlResponse.file);
connection.dispose();
}
});
misskeyApi('drive/files/upload-from-url', {
url: url,
folderId: prefer.s.uploadFolder,
marker,
});
os.alert({
title: i18n.ts.uploadFromUrlRequested,
text: i18n.ts.uploadFromUrlMayTakeTime,
});
});
});
}
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
return new Promise((res, rej) => {
os.popupMenu([label ? {
text: label,
type: 'label',
} : undefined, {
text: i18n.ts.upload,
icon: 'ti ti-upload',
action: () => chooseFileFromPcAndUpload({ multiple }).then(files => res(files)),
}, {
text: i18n.ts.fromDrive,
icon: 'ti ti-cloud',
action: () => chooseDriveFile({ multiple }).then(files => res(files)),
}, {
text: i18n.ts.fromUrl,
icon: 'ti ti-link',
action: () => chooseFileFromUrl().then(file => res([file])),
}], src);
});
}
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
return select(src, label, false).then(files => files[0]);
}
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
return select(src, label, true);
}
export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: {
aspectRatio: number | null;
}): Promise<Misskey.entities.DriveFile> {
return new Promise(resolve => {
const imgUrl = getProxiedImageUrl(imageDriveFile.url, undefined, true);
const image = new Image();
image.src = imgUrl;
image.onload = () => {
const canvas = window.document.createElement('canvas');
const ctx = canvas.getContext('2d')!;
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
canvas.toBlob(blob => {
os.cropImageFile(blob, {
aspectRatio: options.aspectRatio,
}).then(croppedImageFile => {
uploadFile(croppedImageFile, {
name: imageDriveFile.name,
folderId: imageDriveFile.folderId,
}).then(driveFile => {
resolve(driveFile);
});
});
});
};
});
}

View File

@ -112,17 +112,6 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
action: () => describe(file),
});
if (isImage) {
menuItems.push({
text: i18n.ts.cropImage,
icon: 'ti ti-crop',
action: () => os.createCroppedImageDriveFileFromImageDriveFile(file, {
aspectRatio: NaN,
uploadFolder: folder ? folder.id : folder,
}),
});
}
menuItems.push({ type: 'divider' }, {
text: i18n.ts.createNoteFromTheFile,
icon: 'ti ti-pencil',

View File

@ -1,105 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js';
export function chooseFileFromPcAndUpload(
options: {
multiple?: boolean;
folderId?: string | null;
} = {},
): Promise<Misskey.entities.DriveFile[]> {
return new Promise((res, rej) => {
os.chooseFileFromPc({ multiple: options.multiple }).then(files => {
if (files.length === 0) return;
os.launchUploader(files, {
folderId: options.folderId,
}).then(driveFiles => {
res(driveFiles);
});
});
});
}
export function chooseFileFromDrive(options: {
multiple?: boolean;
} = {}): Promise<Misskey.entities.DriveFile[]> {
return new Promise((res, rej) => {
os.selectDriveFile({
multiple: options.multiple,
}).then(files => {
res(files);
});
});
}
export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
return new Promise((res, rej) => {
os.inputText({
title: i18n.ts.uploadFromUrl,
type: 'url',
placeholder: i18n.ts.uploadFromUrlDescription,
}).then(({ canceled, result: url }) => {
if (canceled) return;
const marker = Math.random().toString(); // TODO: UUIDとか使う
// TODO: no websocketモード対応
const connection = useStream().useChannel('main');
connection.on('urlUploadFinished', urlResponse => {
if (urlResponse.marker === marker) {
res(urlResponse.file);
connection.dispose();
}
});
misskeyApi('drive/files/upload-from-url', {
url: url,
folderId: prefer.s.uploadFolder,
marker,
});
os.alert({
title: i18n.ts.uploadFromUrlRequested,
text: i18n.ts.uploadFromUrlMayTakeTime,
});
});
});
}
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
return new Promise((res, rej) => {
os.popupMenu([label ? {
text: label,
type: 'label',
} : undefined, {
text: i18n.ts.upload,
icon: 'ti ti-upload',
action: () => chooseFileFromPcAndUpload({ multiple }).then(files => res(files)),
}, {
text: i18n.ts.fromDrive,
icon: 'ti ti-cloud',
action: () => chooseFileFromDrive({ multiple }).then(files => res(files)),
}, {
text: i18n.ts.fromUrl,
icon: 'ti ti-link',
action: () => chooseFileFromUrl().then(file => res([file])),
}], src);
});
}
export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
return select(src, label, false).then(files => files[0]);
}
export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
return select(src, label, true);
}

View File

@ -1,99 +0,0 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';
import { apiUrl } from '@@/js/config.js';
import { $i } from '@/i.js';
import { instance } from '@/instance.js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
import { globalEvents } from '@/events.js';
export function uploadFile(file: File | Blob, options: {
name?: string;
folderId?: string | null;
onProgress?: (ctx: { total: number; loaded: number; }) => void;
} = {}): Promise<Misskey.entities.DriveFile> {
return new Promise((resolve, reject) => {
if ($i == null) return reject();
if ((file.size > instance.maxFileSize) || (file.size > ($i.policies.maxFileSizeMb * 1024 * 1024))) {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
});
return reject();
}
const xhr = new XMLHttpRequest();
xhr.open('POST', apiUrl + '/drive/files/create', true);
xhr.onload = ((ev: ProgressEvent<XMLHttpRequest>) => {
if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
if (xhr.status === 413) {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
});
} else if (ev.target?.response) {
const res = JSON.parse(ev.target.response);
if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseInappropriate,
});
} else if (res.error?.id === 'd08dbc37-a6a9-463a-8c47-96c32ab5f064') {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: i18n.ts.cannotUploadBecauseNoFreeSpace,
});
} else {
os.alert({
type: 'error',
title: i18n.ts.failedToUpload,
text: `${res.error?.message}\n${res.error?.code}\n${res.error?.id}`,
});
}
} else {
os.alert({
type: 'error',
title: 'Failed to upload',
text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`,
});
}
reject();
return;
}
const driveFile = JSON.parse(ev.target.response);
globalEvents.emit('driveFileCreated', driveFile);
resolve(driveFile);
}) as (ev: ProgressEvent<EventTarget>) => any;
if (options.onProgress) {
xhr.upload.onprogress = ev => {
if (ev.lengthComputable) {
options.onProgress({
total: ev.total,
loaded: ev.loaded,
});
}
};
}
const formData = new FormData();
formData.append('i', $i.token);
formData.append('force', 'true');
formData.append('file', file);
formData.append('name', options.name ?? file.name ?? 'untitled');
if (options.folderId) formData.append('folderId', options.folderId);
xhr.send(formData);
});
}