-
-
-
-
-
-
-
![]()
+
+
+
+
+
+
+
-
+
@@ -35,27 +33,23 @@ import { onMounted, useTemplateRef, ref } from 'vue';
import * as Misskey from 'misskey-js';
import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2';
-import { apiUrl } from '@@/js/config.js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import * as os from '@/os.js';
-import { $i } from '@/i.js';
import { i18n } from '@/i18n.js';
-import { getProxiedImageUrl } from '@/utility/media-proxy.js';
-import { prefer } from '@/preferences.js';
+
+const props = defineProps<{
+ imageFile: File | Blob;
+ aspectRatio: number | null;
+ uploadFolder?: string | null;
+}>();
const emit = defineEmits<{
- (ev: 'ok', cropped: Misskey.entities.DriveFile): void;
+ (ev: 'ok', cropped: File | Blob): void;
(ev: 'cancel'): void;
(ev: 'closed'): void;
}>();
-const props = defineProps<{
- file: Misskey.entities.DriveFile;
- aspectRatio: number;
- uploadFolder?: string | null;
-}>();
-
-const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
+const imgUrl = URL.createObjectURL(props.imageFile);
const dialogEl = useTemplateRef('dialogEl');
const imgEl = useTemplateRef('imgEl');
let cropper: Cropper | null = null;
@@ -73,31 +67,10 @@ const ok = async () => {
const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender });
croppedCanvas?.toBlob(blob => {
if (!blob) return;
- const formData = new FormData();
- 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);
- });
+ res(blob);
});
});
- os.promiseDialog(promise);
-
const f = await promise;
emit('ok', f);
@@ -126,8 +99,8 @@ onMounted(() => {
const selection = cropper.getCropperSelection()!;
selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
- selection.aspectRatio = props.aspectRatio;
- selection.initialAspectRatio = props.aspectRatio;
+ if (props.aspectRatio != null) selection.aspectRatio = props.aspectRatio;
+ selection.initialAspectRatio = props.aspectRatio ?? 1;
selection.outlined = true;
window.setTimeout(() => {
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index 70ab60cfae..0eca85b3a6 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -8,7 +8,6 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.root, { [$style.isSelected]: isSelected }]"
draggable="true"
:title="title"
- @click="onClick"
@contextmenu.stop="onContextmenu"
@dragstart="onDragstart"
@dragend="onDragend"
@@ -46,24 +45,18 @@ import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
-import { deviceKind } from '@/utility/device-kind.js';
-import { useRouter } from '@/router.js';
-
-const router = useRouter();
+import { setDragData } from '@/drag-and-drop.js';
const props = withDefaults(defineProps<{
file: Misskey.entities.DriveFile;
folder: Misskey.entities.DriveFolder | null;
isSelected?: boolean;
- selectMode?: boolean;
}>(), {
isSelected: false,
- selectMode: false,
});
const emit = defineEmits<{
- (ev: 'chosen', r: Misskey.entities.DriveFile): void;
- (ev: 'dragstart'): void;
+ (ev: 'dragstart', dragEvent: DragEvent): void;
(ev: 'dragend'): void;
}>();
@@ -71,18 +64,6 @@ const isDragging = ref(false);
const title = computed(() => `${props.file.name}\n${props.file.type} ${bytes(props.file.size)}`);
-function onClick(ev: MouseEvent) {
- if (props.selectMode) {
- emit('chosen', props.file);
- } else {
- if (deviceKind === 'desktop') {
- router.push(`/my/drive/file/${props.file.id}`);
- } else {
- os.popupMenu(getDriveFileMenu(props.file, props.folder), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
- }
- }
-}
-
function onContextmenu(ev: MouseEvent) {
os.contextMenu(getDriveFileMenu(props.file, props.folder), ev);
}
@@ -90,11 +71,11 @@ function onContextmenu(ev: MouseEvent) {
function onDragstart(ev: DragEvent) {
if (ev.dataTransfer) {
ev.dataTransfer.effectAllowed = 'move';
- ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file));
+ setDragData(ev, 'driveFiles', [props.file]);
}
isDragging.value = true;
- emit('dragstart');
+ emit('dragstart', ev);
}
function onDragend() {
@@ -114,7 +95,7 @@ function onDragend() {
&:hover {
background: rgba(#000, 0.05);
- > .label {
+ .label {
&::before,
&::after {
background: #0b65a5;
@@ -132,7 +113,7 @@ function onDragend() {
&:active {
background: rgba(#000, 0.1);
- > .label {
+ .label {
&::before,
&::after {
background: #0b588c;
@@ -158,19 +139,19 @@ function onDragend() {
background: hsl(from var(--MI_THEME-accent) h s calc(l - 10));
}
- > .label {
+ .label {
&::before,
&::after {
display: none;
}
}
- > .name {
- color: #fff;
+ .name {
+ color: var(--MI_THEME-fgOnAccent);
}
- > .thumbnail {
- color: #fff;
+ .thumbnail {
+ color: var(--MI_THEME-fgOnAccent);
}
}
}
@@ -240,8 +221,9 @@ function onDragend() {
.name {
display: block;
- margin: 4px 0 0 0;
- font-size: 0.8em;
+ margin: 8px 0 0 0;
+ padding: 0 2px;
+ font-size: 82%;
text-align: center;
word-break: break-all;
color: var(--MI_THEME-fg);
diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue
index 9c72691d21..8ba7520f35 100644
--- a/packages/frontend/src/components/MkDrive.folder.vue
+++ b/packages/frontend/src/components/MkDrive.folder.vue
@@ -8,7 +8,6 @@ SPDX-License-Identifier: AGPL-3.0-only
:class="[$style.root, { [$style.draghover]: draghover }]"
draggable="true"
:title="title"
- @click="onClick"
@contextmenu.stop="onContextmenu"
@mouseover="onMouseover"
@mouseout="onMouseout"
@@ -19,16 +18,15 @@ SPDX-License-Identifier: AGPL-3.0-only
@dragstart="onDragstart"
@dragend="onDragend"
>
-
-
-
- {{ folder.name }}
-
-
+
+
{{ folder.name }}
+
{{ i18n.ts.uploadFolder }}
-
+
@@ -43,6 +41,9 @@ import { i18n } from '@/i18n.js';
import { claimAchievement } from '@/utility/achievements.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { prefer } from '@/preferences.js';
+import { globalEvents } from '@/events.js';
+import { checkDragDataType, getDragData, setDragData } from '@/drag-and-drop.js';
+import { selectDriveFolder } from '@/utility/drive.js';
const props = withDefaults(defineProps<{
folder: Misskey.entities.DriveFolder;
@@ -56,10 +57,7 @@ const props = withDefaults(defineProps<{
const emit = defineEmits<{
(ev: 'chosen', v: Misskey.entities.DriveFolder): void;
(ev: 'unchose', v: Misskey.entities.DriveFolder): void;
- (ev: 'move', v: Misskey.entities.DriveFolder): void;
- (ev: 'upload', file: File, folder: Misskey.entities.DriveFolder);
- (ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void;
- (ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void;
+ (ev: 'upload', files: File[], folder: Misskey.entities.DriveFolder);
(ev: 'dragstart'): void;
(ev: 'dragend'): void;
}>();
@@ -78,10 +76,6 @@ function checkboxClicked() {
}
}
-function onClick() {
- emit('move', props.folder);
-}
-
function onMouseover() {
hover.value = true;
}
@@ -101,10 +95,7 @@ function onDragover(ev: DragEvent) {
}
const isFile = ev.dataTransfer.items[0].kind === 'file';
- const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
- const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_;
-
- if (isFile || isDriveFile || isDriveFolder) {
+ if (isFile || checkDragDataType(ev, ['driveFiles', 'driveFolders'])) {
switch (ev.dataTransfer.effectAllowed) {
case 'all':
case 'uninitialized':
@@ -141,55 +132,64 @@ function onDrop(ev: DragEvent) {
// ファイルだったら
if (ev.dataTransfer.files.length > 0) {
- for (const file of Array.from(ev.dataTransfer.files)) {
- emit('upload', file, props.folder);
- }
+ emit('upload', Array.from(ev.dataTransfer.files), props.folder);
return;
}
//#region ドライブのファイル
- const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
- if (driveFile != null && driveFile !== '') {
- const file = JSON.parse(driveFile);
- emit('removeFile', file.id);
- misskeyApi('drive/files/update', {
- fileId: file.id,
- folderId: props.folder.id,
- });
+ {
+ const droppedData = getDragData(ev, 'driveFiles');
+ if (droppedData != null) {
+ misskeyApi('drive/files/move-bulk', {
+ fileIds: droppedData.map(f => f.id),
+ folderId: props.folder.id,
+ }).then(() => {
+ globalEvents.emit('driveFilesUpdated', droppedData.map(x => ({
+ ...x,
+ folderId: props.folder.id,
+ folder: props.folder,
+ })));
+ });
+ }
}
//#endregion
//#region ドライブのフォルダ
- const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
- if (driveFolder != null && driveFolder !== '') {
- const folder = JSON.parse(driveFolder);
+ {
+ const droppedData = getDragData(ev, 'driveFolders');
+ if (droppedData != null) {
+ const droppedFolder = droppedData[0];
- // 移動先が自分自身ならreject
- if (folder.id === props.folder.id) return;
+ // 移動先が自分自身ならreject
+ if (droppedFolder.id === props.folder.id) return;
- emit('removeFolder', folder.id);
- misskeyApi('drive/folders/update', {
- folderId: folder.id,
- parentId: props.folder.id,
- }).then(() => {
- // noop
- }).catch(err => {
- switch (err.code) {
- case 'RECURSIVE_NESTING':
- claimAchievement('driveFolderCircularReference');
- os.alert({
- type: 'error',
- title: i18n.ts.unableToProcess,
- text: i18n.ts.circularReferenceFolder,
- });
- break;
- default:
- os.alert({
- type: 'error',
- text: i18n.ts.somethingHappened,
- });
- }
- });
+ misskeyApi('drive/folders/update', {
+ folderId: droppedFolder.id,
+ parentId: props.folder.id,
+ }).then(() => {
+ globalEvents.emit('driveFoldersUpdated', [droppedFolder].map(x => ({
+ ...x,
+ parentId: props.folder.id,
+ parent: props.folder,
+ })));
+ }).catch(err => {
+ switch (err.code) {
+ case 'RECURSIVE_NESTING':
+ claimAchievement('driveFolderCircularReference');
+ os.alert({
+ type: 'error',
+ title: i18n.ts.unableToProcess,
+ text: i18n.ts.circularReferenceFolder,
+ });
+ break;
+ default:
+ os.alert({
+ type: 'error',
+ text: i18n.ts.somethingHappened,
+ });
+ }
+ });
+ }
}
//#endregion
}
@@ -198,7 +198,7 @@ function onDragstart(ev: DragEvent) {
if (!ev.dataTransfer) return;
ev.dataTransfer.effectAllowed = 'move';
- ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FOLDER_, JSON.stringify(props.folder));
+ setDragData(ev, 'driveFolders', [props.folder]);
isDragging.value = true;
// 親ブラウザに対して、ドラッグが開始されたフラグを立てる
@@ -211,10 +211,6 @@ function onDragend() {
emit('dragend');
}
-function go() {
- emit('move', props.folder);
-}
-
function rename() {
os.inputText({
title: i18n.ts.renameFolder,
@@ -225,17 +221,28 @@ function rename() {
misskeyApi('drive/folders/update', {
folderId: props.folder.id,
name: name,
+ }).then(() => {
+ globalEvents.emit('driveFoldersUpdated', [{
+ ...props.folder,
+ name: name,
+ }]);
});
});
}
function move() {
- os.selectDriveFolder(false).then(folder => {
+ selectDriveFolder(null).then(folder => {
if (folder[0] && folder[0].id === props.folder.id) return;
misskeyApi('drive/folders/update', {
folderId: props.folder.id,
parentId: folder[0] ? folder[0].id : null,
+ }).then(() => {
+ globalEvents.emit('driveFoldersUpdated', [{
+ ...props.folder,
+ parentId: folder[0] ? folder[0].id : null,
+ parent: folder[0] ?? null,
+ }]);
});
});
}
@@ -247,6 +254,7 @@ function deleteFolder() {
if (prefer.s.uploadFolder === props.folder.id) {
prefer.commit('uploadFolder', null);
}
+ globalEvents.emit('driveFoldersDeleted', [props.folder]);
}).catch(err => {
switch (err.id) {
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
@@ -311,10 +319,9 @@ function onContextmenu(ev: MouseEvent) {
diff --git a/packages/frontend/src/components/MkDriveFolderSelectDialog.vue b/packages/frontend/src/components/MkDriveFolderSelectDialog.vue
new file mode 100644
index 0000000000..2ebab1088f
--- /dev/null
+++ b/packages/frontend/src/components/MkDriveFolderSelectDialog.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+ {{ multiple ? i18n.ts.selectFolders : i18n.ts.selectFolder }}
+ ({{ selected.length }})
+
+
+
+
+
+
diff --git a/packages/frontend/src/components/MkDriveWindow.vue b/packages/frontend/src/components/MkDriveWindow.vue
index c0142ec76e..0b8d0bfb8a 100644
--- a/packages/frontend/src/components/MkDriveWindow.vue
+++ b/packages/frontend/src/components/MkDriveWindow.vue
@@ -14,19 +14,19 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.drive }}
-
+
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index e86861c874..9f5bc8da6c 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -19,13 +19,42 @@ SPDX-License-Identifier: AGPL-3.0-only