Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション #8216

Merged
merged 49 commits into from
Apr 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
4d4ac5d
wip
mei23 Jan 22, 2022
bb11a37
Update packages/client/src/os.ts
mei23 Jan 29, 2022
ed5805e
メニューをComposition API化、switchアイテム追加
tamaina Jan 29, 2022
0a788d1
メニュー型定義を分離 (TypeScriptの型支援が効かないので)
tamaina Jan 29, 2022
8da8329
disabled
tamaina Jan 29, 2022
bb67923
make keepOriginal to follow setting value
tamaina Jan 29, 2022
0ed6f4d
Merge branch 'v12-8173' into better-8176
tamaina Jan 29, 2022
323d020
:v:
tamaina Jan 29, 2022
0802306
fix
tamaina Jan 29, 2022
32dd3bf
fix
tamaina Jan 29, 2022
1ce9e92
:v:
tamaina Jan 29, 2022
8fcf86c
Merge branch 'menu-switch' into better-8176
tamaina Jan 29, 2022
3236eba
WEBP
tamaina Jan 29, 2022
35845bc
aaa
tamaina Jan 29, 2022
0f8f7de
:v:
tamaina Jan 29, 2022
8a32ced
webp
tamaina Jan 29, 2022
38f84a9
lazy load browser-image-resizer
tamaina Jan 29, 2022
4fdec40
rename
tamaina Jan 29, 2022
d42c985
rename 2
tamaina Jan 29, 2022
af42693
Fix
tamaina Jan 29, 2022
0133902
Merge branch 'menu-switch' into better-8176
tamaina Jan 29, 2022
8a5fdd1
clean up
tamaina Jan 29, 2022
7b4f5ac
add comment
tamaina Jan 29, 2022
2b7ec30
Merge branch 'menu-switch' into better-8176
tamaina Jan 29, 2022
4905299
Merge branch 'develop' into better-8176
tamaina Jan 30, 2022
39f0eb1
clean up
tamaina Jan 30, 2022
6a000b3
jpeg, pngにもどす
tamaina Jan 30, 2022
599510c
fix
tamaina Jan 30, 2022
ee6e163
fix name
tamaina Feb 4, 2022
e87e97f
Merge branch 'develop' into better-8176
tamaina Feb 4, 2022
e7d9221
webpでなくする ただしサムネやプレビューはwebpのまま (テスト)
tamaina Feb 6, 2022
fdb17e0
動画サムネイルはjpegに
tamaina Feb 6, 2022
7102e43
エラーハンドリング
tamaina Feb 6, 2022
c015001
Merge branch 'develop' into better-8176
tamaina Feb 16, 2022
8979dbc
:v:
tamaina Feb 16, 2022
211bdb0
v2.2.1-misskey-beta.2
tamaina Feb 16, 2022
40dfef9
browser-image-resizer#v2.2.1-misskey.1
tamaina Feb 16, 2022
60d7d03
:v:
tamaina Feb 16, 2022
673361f
fix alert
tamaina Feb 19, 2022
50b7ef7
Merge branch 'develop' into better-8176
tamaina Feb 19, 2022
05c011a
Merge branch 'develop' into better-8176
tamaina Feb 19, 2022
3401cf8
Merge branch 'develop' into better-8176
tamaina Feb 20, 2022
03b254a
Merge branch 'develop' into better-8176
tamaina Feb 21, 2022
2326cbb
Merge branch 'develop' into better-8176
tamaina Mar 4, 2022
2adcd2b
Merge branch 'develop' into better-8176
tamaina Mar 12, 2022
c27892c
update browser-image-resizer to v2.2.1-misskey.2
tamaina Mar 12, 2022
0215c61
lockfile
tamaina Mar 12, 2022
7ef1302
Merge branch 'develop' into better-8176
tamaina Mar 27, 2022
7351365
Merge branch 'develop' into better-8176
tamaina Apr 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/backend/src/misc/populate-emojis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu

const isLocal = emoji.host == null;
const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため
const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`;
const url = isLocal ? emojiUrl : `${config.url}/proxy/${encodeURIComponent((new URL(emojiUrl)).pathname)}?${query({ url: emojiUrl })}`;

return {
name: emojiName,
Expand Down
8 changes: 3 additions & 5 deletions packages/backend/src/server/file/send-drive-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { DriveFiles } from '@/models/index.js';
import { InternalStorage } from '@/services/drive/internal-storage.js';
import { downloadUrl } from '@/misc/download-url.js';
import { detectType } from '@/misc/get-file-info.js';
import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor.js';
import { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.js';
import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail.js';
import { StatusError } from '@/misc/fetch.js';
import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
Expand Down Expand Up @@ -64,10 +64,8 @@ export default async function(ctx: Koa.Context) {

const convertFile = async () => {
if (isThumbnail) {
if (['image/jpeg', 'image/webp'].includes(mime)) {
return await convertToJpeg(path, 498, 280);
} else if (['image/png', 'image/svg+xml'].includes(mime)) {
return await convertToPngOrJpeg(path, 498, 280);
if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) {
return await convertToWebp(path, 498, 280);
} else if (mime.startsWith('video/')) {
return await GenerateVideoThumbnail(path);
}
Expand Down
8 changes: 4 additions & 4 deletions packages/backend/src/server/proxy/proxy-media.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as fs from 'node:fs';
import Koa from 'koa';
import { serverLogger } from '../index.js';
import { IImage, convertToPng, convertToJpeg } from '@/services/drive/image-processor.js';
import { IImage, convertToWebp } from '@/services/drive/image-processor.js';
import { createTemp } from '@/misc/create-temp.js';
import { downloadUrl } from '@/misc/download-url.js';
import { detectType } from '@/misc/get-file-info.js';
Expand All @@ -27,11 +27,11 @@ export async function proxyMedia(ctx: Koa.Context) {
let image: IImage;

if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) {
image = await convertToPng(path, 498, 280);
image = await convertToWebp(path, 498, 280);
} else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) {
image = await convertToJpeg(path, 200, 200);
image = await convertToWebp(path, 200, 200);
} else if (['image/svg+xml'].includes(mime)) {
image = await convertToPng(path, 2048, 2048);
image = await convertToWebp(path, 2048, 2048, 1);
} else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) {
throw new StatusError('Rejected type', 403, 'Rejected type');
} else {
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/server/web/url-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => {
function wrap(url?: string): string | null {
return url != null
? url.match(/^https?:\/\//)
? `${config.url}/proxy/preview.jpg?${query({
? `${config.url}/proxy/preview.webp?${query({
url,
preview: '1',
})}`
Expand Down
29 changes: 18 additions & 11 deletions packages/backend/src/services/drive/add-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { deleteFile } from './delete-file.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
import { GenerateVideoThumbnail } from './generate-video-thumbnail.js';
import { driveLogger } from './logger.js';
import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng, convertSharpToPngOrJpeg } from './image-processor.js';
import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng } from './image-processor.js';
import { contentDisposition } from '@/misc/content-disposition.js';
import { getFileInfo } from '@/misc/get-file-info.js';
import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '@/models/index.js';
Expand Down Expand Up @@ -179,6 +179,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
}

let img: sharp.Sharp | null = null;
let satisfyWebpublic: boolean;

try {
img = sharp(path);
Expand All @@ -192,6 +193,13 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
thumbnail: null,
};
}

satisfyWebpublic = !!(
type !== 'image/svg+xml' && type !== 'image/webp' &&
!(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) &&
metadata.width && metadata.width <= 2048 &&
metadata.height && metadata.height <= 2048
);
} catch (err) {
logger.warn(`sharp failed: ${err}`);
return {
Expand All @@ -203,15 +211,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
// #region webpublic
let webpublic: IImage | null = null;

if (generateWeb) {
if (generateWeb && !satisfyWebpublic) {
logger.info(`creating web image`);

try {
if (['image/jpeg'].includes(type)) {
if (['image/jpeg', 'image/webp'].includes(type)) {
webpublic = await convertSharpToJpeg(img, 2048, 2048);
} else if (['image/webp'].includes(type)) {
webpublic = await convertSharpToWebp(img, 2048, 2048);
} else if (['image/png', 'image/svg+xml'].includes(type)) {
} else if (['image/png'].includes(type)) {
webpublic = await convertSharpToPng(img, 2048, 2048);
} else if (['image/svg+xml'].includes(type)) {
webpublic = await convertSharpToPng(img, 2048, 2048);
} else {
logger.debug(`web image not created (not an required image)`);
Expand All @@ -220,18 +228,17 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
logger.warn(`web image not created (an error occured)`, err as Error);
}
} else {
logger.info(`web image not created (from remote)`);
if (satisfyWebpublic) logger.info(`web image not created (original satisfies webpublic)`);
else logger.info(`web image not created (from remote)`);
}
// #endregion webpublic

// #region thumbnail
let thumbnail: IImage | null = null;

try {
if (['image/jpeg', 'image/webp'].includes(type)) {
thumbnail = await convertSharpToJpeg(img, 498, 280);
} else if (['image/png', 'image/svg+xml'].includes(type)) {
thumbnail = await convertSharpToPngOrJpeg(img, 498, 280);
if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) {
thumbnail = await convertSharpToWebp(img, 498, 280);
} else {
logger.debug(`thumbnail not created (not an required file)`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export async function GenerateVideoThumbnail(path: string): Promise<IImage> {

const outPath = `${outDir}/output.png`;

// JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

動画のサムネイルがリモートに送られることはなかった気がするわ

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bindThis
public renderDocument(file: DriveFile) {
return {
type: 'Document',
mediaType: file.type,
url: this.driveFileEntityService.getPublicUrl(file),
name: file.comment,
};
}

そうかも

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

うーん、ffmpegがwebpをサポートしてなかったのが原因か…?

Copy link
Contributor

@mei23 mei23 Dec 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

一度ffmpegで原寸のPNGを出してからJPEG変換+リサイズしているのは、ffmpegにいい感じの縮小メソッドがなかった (アス比保持で最大サイズに外接でするのがうまくいかなかった) からだった気がするのだわ。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #8825

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

多分ffmpeg周り触るたびに毎回言ってるぐらいの感じなんだろうけど、代案が見つからないのでIssue建てるのを躊躇したみたいな感じなのでは。

移行するなら https://github.com/ffmpegwasm/ffmpeg.wasm とか?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fluent-ffmpegはやめてないはずだわ
#5186

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

多分それ

移行するなら https://github.com/ffmpegwasm/ffmpeg.wasm とか?

基本的には生で支障ないから execa とかじゃない?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本題のコードの話だけど、これをwebpにする必要ある…?

Copy link
Contributor

@mei23 mei23 Dec 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本題のコードの話だけど、これをwebpにする必要ある…?

他のサムネがWebP化されてたからここは揃えないのかなって思ったのだわ

const thumbnail = await convertToJpeg(outPath, 498, 280);

// cleanup
Expand Down
28 changes: 4 additions & 24 deletions packages/backend/src/services/drive/image-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig
* Convert to WebP
* with resize, remove metadata, resolve orientation, stop animation
*/
export async function convertToWebp(path: string, width: number, height: number): Promise<IImage> {
return convertSharpToWebp(await sharp(path), width, height);
export async function convertToWebp(path: string, width: number, height: number, quality: number = 85): Promise<IImage> {
return convertSharpToWebp(await sharp(path), width, height, quality);
}

export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> {
export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality: number = 85): Promise<IImage> {
const data = await sharp
.resize(width, height, {
fit: 'inside',
withoutEnlargement: true,
})
.rotate()
.webp({
quality: 85,
quality,
})
.toBuffer();

Expand Down Expand Up @@ -85,23 +85,3 @@ export async function convertSharpToPng(sharp: sharp.Sharp, width: number, heigh
type: 'image/png',
};
}

/**
* Convert to PNG or JPEG
* with resize, remove metadata, resolve orientation, stop animation
*/
export async function convertToPngOrJpeg(path: string, width: number, height: number): Promise<IImage> {
return convertSharpToPngOrJpeg(await sharp(path), width, height);
}

export async function convertSharpToPngOrJpeg(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> {
const stats = await sharp.stats();
const metadata = await sharp.metadata();

// 不透明で300x300pxの範囲を超えていればJPEG
if (stats.isOpaque && ((metadata.width && metadata.width >= 300) || (metadata.height && metadata!.height >= 300))) {
return await convertSharpToJpeg(sharp, width, height);
} else {
return await convertSharpToPng(sharp, width, height);
}
}
1 change: 1 addition & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"autwh": "0.1.0",
"blurhash": "1.1.5",
"broadcast-channel": "4.11.0",
"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2",
"chart.js": "3.7.1",
"chartjs-adapter-date-fns": "2.0.0",
"chartjs-plugin-gradient": "0.2.2",
Expand Down
10 changes: 8 additions & 2 deletions packages/client/src/components/drive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import * as os from '@/os';
import { stream } from '@/stream';
import { defaultStore } from '@/store';
import { i18n } from '@/i18n';
import { uploadFile, uploads } from '@/scripts/upload';

const props = withDefaults(defineProps<{
initialFolder?: Misskey.entities.DriveFolder;
Expand Down Expand Up @@ -127,8 +128,9 @@ const moreFolders = ref(false);
const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]);
const selectedFiles = ref<Misskey.entities.DriveFile[]>([]);
const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
const uploadings = os.uploads;
const uploadings = uploads;
const connection = stream.useChannel('drive');
const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい

// ドロップされようとしているか
const draghover = ref(false);
Expand Down Expand Up @@ -355,7 +357,7 @@ function onChangeFileInput() {
}

function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) {
os.upload(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null).then(res => {
uploadFile(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => {
addFile(res, true);
});
}
Expand Down Expand Up @@ -562,6 +564,10 @@ function fetchMoreFiles() {

function getMenu() {
return [{
type: 'switch',
text: i18n.ts.keepOriginalUploading,
ref: keepOriginal,
}, null, {
text: i18n.ts.addFile,
type: 'label'
}, {
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/components/post-form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import MkInfo from '@/components/ui/info.vue';
import { i18n } from '@/i18n';
import { instance } from '@/instance';
import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
import { uploadFile } from '@/scripts/upload';

const modal = inject('modal');

Expand Down Expand Up @@ -372,7 +373,7 @@ function updateFileName(file, name) {
}

function upload(file: File, name?: string) {
os.upload(file, defaultStore.state.uploadFolder, name).then(res => {
uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
files.push(res);
});
}
Expand Down
75 changes: 1 addition & 74 deletions packages/client/src/os.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する

import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue';
import { Component, markRaw, Ref, ref } from 'vue';
import { EventEmitter } from 'eventemitter3';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as Misskey from 'misskey-js';
Expand All @@ -10,7 +10,6 @@ import MkWaitingDialog from '@/components/waiting-dialog.vue';
import { MenuItem } from '@/types/menu';
import { resolve } from '@/router';
import { $i } from '@/account';
import { defaultStore } from '@/store';

export const pendingApiRequestsCount = ref(0);

Expand Down Expand Up @@ -537,78 +536,6 @@ export function post(props: Record<string, any> = {}) {

export const deckGlobalEvents = new EventEmitter();

export const uploads = ref<{
id: string;
name: string;
progressMax: number | undefined;
progressValue: number | undefined;
img: string;
}[]>([]);

export function upload(file: File, folder?: any, name?: string, keepOriginal: boolean = defaultStore.state.keepOriginalUploading): Promise<Misskey.entities.DriveFile> {
if (folder && typeof folder === 'object') folder = folder.id;

return new Promise((resolve, reject) => {
const id = Math.random().toString();

const reader = new FileReader();
reader.onload = (e) => {
const ctx = reactive({
id: id,
name: name || file.name || 'untitled',
progressMax: undefined,
progressValue: undefined,
img: window.URL.createObjectURL(file)
});

uploads.value.push(ctx);

console.log(keepOriginal);

const data = new FormData();
data.append('i', $i.token);
data.append('force', 'true');
data.append('file', file);

if (folder) data.append('folderId', folder);
if (name) data.append('name', name);

const xhr = new XMLHttpRequest();
xhr.open('POST', apiUrl + '/drive/files/create', true);
xhr.onload = (ev) => {
if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
// TODO: 消すのではなくて再送できるようにしたい
uploads.value = uploads.value.filter(x => x.id != id);

alert({
type: 'error',
text: 'upload failed'
});

reject();
return;
}

const driveFile = JSON.parse(ev.target.response);

resolve(driveFile);

uploads.value = uploads.value.filter(x => x.id != id);
};

xhr.upload.onprogress = e => {
if (e.lengthComputable) {
ctx.progressMax = e.total;
ctx.progressValue = e.loaded;
}
};

xhr.send(data);
};
reader.readAsArrayBuffer(file);
});
}

/*
export function checkExistence(fileData: ArrayBuffer): Promise<any> {
return new Promise((resolve, reject) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/pages/messaging/messaging-room.form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import * as os from '@/os';
import { stream } from '@/stream';
import { Autocomplete } from '@/scripts/autocomplete';
import { throttle } from 'throttle-debounce';
import { uploadFile } from '@/scripts/upload';

export default defineComponent({
props: {
Expand Down Expand Up @@ -164,7 +165,7 @@ export default defineComponent({
},

upload(file: File, name?: string) {
os.upload(file, this.$store.state.uploadFolder, name).then(res => {
uploadFile(file, this.$store.state.uploadFolder, name).then(res => {
this.file = res;
});
},
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/scripts/select-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { stream } from '@/stream';
import { i18n } from '@/i18n';
import { defaultStore } from '@/store';
import { DriveFile } from 'misskey-js/built/entities';
import { uploadFile } from '@/scripts/upload';

function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> {
return new Promise((res, rej) => {
Expand All @@ -14,7 +15,7 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
input.type = 'file';
input.multiple = multiple;
input.onchange = () => {
const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value));
const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value));

Promise.all(promises).then(driveFiles => {
res(multiple ? driveFiles : driveFiles[0]);
Expand Down
Loading