wip
This commit is contained in:
parent
566f729300
commit
37f846af8a
|
@ -33,27 +33,23 @@ import { onMounted, useTemplateRef, ref } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import Cropper from 'cropperjs';
|
import Cropper from 'cropperjs';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import { apiUrl } from '@@/js/config.js';
|
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { $i } from '@/i.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { getProxiedImageUrl } from '@/utility/media-proxy.js';
|
|
||||||
import { prefer } from '@/preferences.js';
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
|
|
||||||
(ev: 'cancel'): void;
|
|
||||||
(ev: 'closed'): void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
file: Misskey.entities.DriveFile;
|
imageFile: File;
|
||||||
aspectRatio: number;
|
aspectRatio: number;
|
||||||
uploadFolder?: string | null;
|
uploadFolder?: string | null;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
|
const emit = defineEmits<{
|
||||||
|
(ev: 'ok', cropped: File | Blob): void;
|
||||||
|
(ev: 'cancel'): void;
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const imgUrl = URL.createObjectURL(props.imageFile);
|
||||||
const dialogEl = useTemplateRef('dialogEl');
|
const dialogEl = useTemplateRef('dialogEl');
|
||||||
const imgEl = useTemplateRef('imgEl');
|
const imgEl = useTemplateRef('imgEl');
|
||||||
let cropper: Cropper | null = null;
|
let cropper: Cropper | null = null;
|
||||||
|
@ -71,26 +67,7 @@ const ok = async () => {
|
||||||
const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender });
|
const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender });
|
||||||
croppedCanvas?.toBlob(blob => {
|
croppedCanvas?.toBlob(blob => {
|
||||||
if (!blob) return;
|
if (!blob) return;
|
||||||
const formData = new FormData();
|
res(blob);
|
||||||
formData.append('file', blob);
|
|
||||||
formData.append('name', `cropped_${props.file.name}`);
|
|
||||||
formData.append('isSensitive', props.file.isSensitive ? 'true' : 'false');
|
|
||||||
if (props.file.comment) { formData.append('comment', props.file.comment);}
|
|
||||||
formData.append('i', $i!.token);
|
|
||||||
if (props.uploadFolder) {
|
|
||||||
formData.append('folderId', props.uploadFolder);
|
|
||||||
} else if (props.uploadFolder !== null && prefer.s.uploadFolder) {
|
|
||||||
formData.append('folderId', prefer.s.uploadFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.fetch(apiUrl + '/drive/files/create', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(f => {
|
|
||||||
res(f);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ import { useStream } from '@/stream.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { claimAchievement } from '@/utility/achievements.js';
|
import { claimAchievement } from '@/utility/achievements.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
import { chooseFileFromPc } from '@/utility/select-file.js';
|
import { chooseFileFromPcAndUpload } from '@/utility/select-file.js';
|
||||||
import { store } from '@/store.js';
|
import { store } from '@/store.js';
|
||||||
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
||||||
import { usePagination } from '@/composables/use-pagination.js';
|
import { usePagination } from '@/composables/use-pagination.js';
|
||||||
|
@ -555,7 +555,7 @@ function getMenu() {
|
||||||
text: i18n.ts.upload,
|
text: i18n.ts.upload,
|
||||||
icon: 'ti ti-upload',
|
icon: 'ti ti-upload',
|
||||||
action: () => {
|
action: () => {
|
||||||
chooseFileFromPc(true, { uploadFolder: folder.value?.id, keepOriginal: true });
|
chooseFileFromPcAndUpload(true, { uploadFolder: folder.value?.id, keepOriginal: true });
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.fromUrl,
|
text: i18n.ts.fromUrl,
|
||||||
|
|
|
@ -145,7 +145,7 @@ async function describe(file: Misskey.entities.DriveFile) {
|
||||||
async function crop(file: Misskey.entities.DriveFile): Promise<void> {
|
async function crop(file: Misskey.entities.DriveFile): Promise<void> {
|
||||||
if (mock) return;
|
if (mock) return;
|
||||||
|
|
||||||
const newFile = await os.cropImage(file, { aspectRatio: NaN });
|
const newFile = await os.createCroppedImageDriveFileFromImageDriveFile(file, { aspectRatio: NaN });
|
||||||
emit('replaceFile', file, newFile);
|
emit('replaceFile', file, newFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ import MkInput from '@/components/MkInput.vue';
|
||||||
import MkTextarea from '@/components/MkTextarea.vue';
|
import MkTextarea from '@/components/MkTextarea.vue';
|
||||||
import FormSlot from '@/components/form/slot.vue';
|
import FormSlot from '@/components/form/slot.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { chooseFileFromPc } from '@/utility/select-file.js';
|
import { chooseFileFromPcAndUpload } from '@/utility/select-file.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { ensureSignin } from '@/i.js';
|
import { ensureSignin } from '@/i.js';
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ const description = ref($i.description ?? '');
|
||||||
watch(name, () => {
|
watch(name, () => {
|
||||||
os.apiWithDialog('i/update', {
|
os.apiWithDialog('i/update', {
|
||||||
// 空文字列をnullにしたいので??は使うな
|
// 空文字列をnullにしたいので??は使うな
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
||||||
name: name.value || null,
|
name: name.value || null,
|
||||||
}, undefined, {
|
}, undefined, {
|
||||||
'0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191': {
|
'0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191': {
|
||||||
|
@ -62,13 +62,13 @@ watch(name, () => {
|
||||||
watch(description, () => {
|
watch(description, () => {
|
||||||
os.apiWithDialog('i/update', {
|
os.apiWithDialog('i/update', {
|
||||||
// 空文字列をnullにしたいので??は使うな
|
// 空文字列をnullにしたいので??は使うな
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
||||||
description: description.value || null,
|
description: description.value || null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setAvatar(ev) {
|
function setAvatar(ev) {
|
||||||
chooseFileFromPc(false).then(async (files) => {
|
os.chooseFileFromPc({ multiple: false }).then(async (files) => {
|
||||||
const file = files[0];
|
const file = files[0];
|
||||||
|
|
||||||
let originalOrCropped = file;
|
let originalOrCropped = file;
|
||||||
|
@ -81,13 +81,15 @@ function setAvatar(ev) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
originalOrCropped = await os.cropImage(file, {
|
originalOrCropped = await os.cropImageFile(file, {
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const driveFile = (await os.launchUploader([originalOrCropped], {}))[0];
|
||||||
|
|
||||||
const i = await os.apiWithDialog('i/update', {
|
const i = await os.apiWithDialog('i/update', {
|
||||||
avatarId: originalOrCropped.id,
|
avatarId: driveFile.id,
|
||||||
});
|
});
|
||||||
$i.avatarId = i.avatarId;
|
$i.avatarId = i.avatarId;
|
||||||
$i.avatarUrl = i.avatarUrl;
|
$i.avatarUrl = i.avatarUrl;
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
||||||
import { EventEmitter } from 'eventemitter3';
|
import { EventEmitter } from 'eventemitter3';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { getProxiedImageUrl } from './utility/media-proxy.js';
|
||||||
import type { Component, Ref } from 'vue';
|
import type { Component, Ref } from 'vue';
|
||||||
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
||||||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
import type { Form, GetFormResultType } from '@/utility/form.js';
|
||||||
|
@ -653,15 +654,13 @@ export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof Mk
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cropImage(image: Misskey.entities.DriveFile, options: {
|
export async function cropImageFile(imageFile: File, options: {
|
||||||
aspectRatio: number;
|
aspectRatio: number;
|
||||||
uploadFolder?: string | null;
|
}): Promise<File> {
|
||||||
}): Promise<Misskey.entities.DriveFile> {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkCropperDialog.vue')), {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkCropperDialog.vue')), {
|
||||||
file: image,
|
imageFile: imageFile,
|
||||||
aspectRatio: options.aspectRatio,
|
aspectRatio: options.aspectRatio,
|
||||||
uploadFolder: options.uploadFolder,
|
|
||||||
}, {
|
}, {
|
||||||
ok: x => {
|
ok: x => {
|
||||||
resolve(x);
|
resolve(x);
|
||||||
|
@ -671,6 +670,29 @@ export async function cropImage(image: Misskey.entities.DriveFile, options: {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createCroppedImageDriveFileFromImageDriveFile(imageDriveFile: Misskey.entities.DriveFile, options: {
|
||||||
|
aspectRatio: number;
|
||||||
|
uploadFolder?: string | 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);
|
||||||
|
const imageFile = new File([canvas.toBlob()], imageDriveFile.name, { type: imageDriveFile.type });
|
||||||
|
|
||||||
|
cropImageFile(imageFile).then(croppedImageFile => {
|
||||||
|
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | null, options?: {
|
export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | null, options?: {
|
||||||
align?: string;
|
align?: string;
|
||||||
width?: number;
|
width?: number;
|
||||||
|
@ -774,6 +796,32 @@ export function checkExistence(fileData: ArrayBuffer): Promise<any> {
|
||||||
});
|
});
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
export function chooseFileFromPc(
|
||||||
|
options: {
|
||||||
|
multiple?: boolean;
|
||||||
|
} = {},
|
||||||
|
): Promise<File[]> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
const input = window.document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.multiple = options.multiple ?? false;
|
||||||
|
input.onchange = () => {
|
||||||
|
if (!input.files) return res([]);
|
||||||
|
|
||||||
|
res(Array.from(input.files));
|
||||||
|
|
||||||
|
// 一応廃棄
|
||||||
|
(window as any).__misskey_input_ref__ = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
|
||||||
|
// iOS Safari で正常に動かす為のおまじない
|
||||||
|
(window as any).__misskey_input_ref__ = input;
|
||||||
|
|
||||||
|
input.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function launchUploader(
|
export function launchUploader(
|
||||||
files: File[],
|
files: File[],
|
||||||
options?: {
|
options?: {
|
||||||
|
@ -781,6 +829,7 @@ export function launchUploader(
|
||||||
},
|
},
|
||||||
): Promise<Misskey.entities.DriveFile[]> {
|
): Promise<Misskey.entities.DriveFile[]> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
|
if (files.length === 0) return;
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUploaderDialog.vue')), {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUploaderDialog.vue')), {
|
||||||
files: markRaw(files),
|
files: markRaw(files),
|
||||||
folderId: options?.folderId,
|
folderId: options?.folderId,
|
||||||
|
|
|
@ -94,7 +94,7 @@ import MkFolder from '@/components/MkFolder.vue';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { validators } from '@/components/grid/cell-validators.js';
|
import { validators } from '@/components/grid/cell-validators.js';
|
||||||
import { chooseFileFromDrive, chooseFileFromPc } from '@/utility/select-file.js';
|
import { chooseFileFromDrive, chooseFileFromPcAndUpload } from '@/utility/select-file.js';
|
||||||
import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
|
import { extractDroppedItems, flattenDroppedFiles } from '@/utility/file-drop.js';
|
||||||
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
import XRegisterLogs from '@/pages/admin/custom-emojis-manager.logs.vue';
|
||||||
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
import { copyGridDataToClipboard } from '@/components/grid/grid-utils.js';
|
||||||
|
@ -364,7 +364,7 @@ async function onDrop(ev: DragEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onFileSelectClicked() {
|
async function onFileSelectClicked() {
|
||||||
const driveFiles = await chooseFileFromPc(
|
const driveFiles = await chooseFileFromPcAndUpload(
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
uploadFolder: selectedFolderId.value,
|
uploadFolder: selectedFolderId.value,
|
||||||
|
|
|
@ -130,7 +130,7 @@ function postThis() {
|
||||||
function crop() {
|
function crop() {
|
||||||
if (!file.value) return;
|
if (!file.value) return;
|
||||||
|
|
||||||
os.cropImage(file.value, {
|
os.createCroppedImageDriveFileFromImageDriveFile(file.value, {
|
||||||
aspectRatio: NaN,
|
aspectRatio: NaN,
|
||||||
uploadFolder: file.value.folderId ?? null,
|
uploadFolder: file.value.folderId ?? null,
|
||||||
});
|
});
|
||||||
|
|
|
@ -268,7 +268,7 @@ function changeAvatar(ev) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
originalOrCropped = await os.cropImage(file, {
|
originalOrCropped = await os.createCroppedImageDriveFileFromImageDriveFile(file, {
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -294,7 +294,7 @@ function changeBanner(ev) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!canceled) {
|
if (!canceled) {
|
||||||
originalOrCropped = await os.cropImage(file, {
|
originalOrCropped = await os.createCroppedImageDriveFileFromImageDriveFile(file, {
|
||||||
aspectRatio: 2,
|
aspectRatio: 2,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
|
||||||
menuItems.push({
|
menuItems.push({
|
||||||
text: i18n.ts.cropImage,
|
text: i18n.ts.cropImage,
|
||||||
icon: 'ti ti-crop',
|
icon: 'ti ti-crop',
|
||||||
action: () => os.cropImage(file, {
|
action: () => os.createCroppedImageDriveFileFromImageDriveFile(file, {
|
||||||
aspectRatio: NaN,
|
aspectRatio: NaN,
|
||||||
uploadFolder: folder ? folder.id : folder,
|
uploadFolder: folder ? folder.id : folder,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -11,34 +11,21 @@ import { useStream } from '@/stream.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
|
||||||
export function chooseFileFromPc(
|
export function chooseFileFromPcAndUpload(
|
||||||
options: {
|
options: {
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
folderId?: string | null;
|
folderId?: string | null;
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<Misskey.entities.DriveFile[]> {
|
): Promise<Misskey.entities.DriveFile[]> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const input = window.document.createElement('input');
|
os.chooseFileFromPc({ multiple: options.multiple }).then(files => {
|
||||||
input.type = 'file';
|
if (files.length === 0) return;
|
||||||
input.multiple = options.multiple ?? false;
|
os.launchUploader(files, {
|
||||||
input.onchange = () => {
|
|
||||||
if (!input.files) return res([]);
|
|
||||||
|
|
||||||
os.launchUploader(Array.from(input.files), {
|
|
||||||
folderId: options.folderId,
|
folderId: options.folderId,
|
||||||
}).then(driveFiles => {
|
}).then(driveFiles => {
|
||||||
res(driveFiles);
|
res(driveFiles);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
// 一応廃棄
|
|
||||||
(window as any).__misskey_input_ref__ = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
|
|
||||||
// iOS Safari で正常に動かす為のおまじない
|
|
||||||
(window as any).__misskey_input_ref__ = input;
|
|
||||||
|
|
||||||
input.click();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +78,7 @@ function select(src: HTMLElement | EventTarget | null, label: string | null, mul
|
||||||
} : undefined, {
|
} : undefined, {
|
||||||
text: i18n.ts.upload,
|
text: i18n.ts.upload,
|
||||||
icon: 'ti ti-upload',
|
icon: 'ti ti-upload',
|
||||||
action: () => chooseFileFromPc(multiple, { keepOriginal: true }).then(files => res(files)),
|
action: () => chooseFileFromPcAndUpload({ multiple }).then(files => res(files)),
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.fromDrive,
|
text: i18n.ts.fromDrive,
|
||||||
icon: 'ti ti-cloud',
|
icon: 'ti ti-cloud',
|
||||||
|
|
Loading…
Reference in New Issue