813 lines
22 KiB
Vue
813 lines
22 KiB
Vue
<!--
|
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
-->
|
|
|
|
<template>
|
|
<MkStickyContainer>
|
|
<template #header>
|
|
<nav :class="$style.nav">
|
|
<div :class="$style.navPath" @contextmenu.prevent.stop="() => {}">
|
|
<XNavFolder
|
|
:class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]"
|
|
:parentFolder="folder"
|
|
@click="cd(null)"
|
|
@upload="upload"
|
|
/>
|
|
<template v-for="f in hierarchyFolders">
|
|
<span :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
|
|
<XNavFolder
|
|
:folder="f"
|
|
:parentFolder="folder"
|
|
:class="[$style.navPathItem]"
|
|
@click="cd(f)"
|
|
@upload="upload"
|
|
/>
|
|
</template>
|
|
<span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
|
|
<span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span>
|
|
</div>
|
|
<button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
|
|
</nav>
|
|
</template>
|
|
|
|
<div>
|
|
<div v-if="select === 'folder'">
|
|
<template v-if="folder == null">
|
|
<MkButton v-if="!isRootSelected" @click="isRootSelected = true">
|
|
<i class="ti ti-square"></i> {{ i18n.ts.selectThisFolder }}
|
|
</MkButton>
|
|
<MkButton v-else @click="isRootSelected = false">
|
|
<i class="ti ti-checkbox"></i> {{ i18n.ts.unselectThisFolder }}
|
|
</MkButton>
|
|
</template>
|
|
<template v-else>
|
|
<MkButton v-if="!selectedFolders.some(f => f.id === folder!.id)" @click="selectedFolders.push(folder)">
|
|
<i class="ti ti-square"></i> {{ i18n.ts.selectThisFolder }}
|
|
</MkButton>
|
|
<MkButton v-else @click="selectedFolders = selectedFolders.filter(f => f.id !== folder!.id)">
|
|
<i class="ti ti-checkbox"></i> {{ i18n.ts.unselectThisFolder }}
|
|
</MkButton>
|
|
</template>
|
|
</div>
|
|
|
|
<div
|
|
ref="main"
|
|
:class="[$style.main, { [$style.uploading]: uploadings.length > 0, [$style.fetching]: fetching }]"
|
|
@dragover.prevent.stop="onDragover"
|
|
@dragenter="onDragenter"
|
|
@dragleave="onDragleave"
|
|
@drop.prevent.stop="onDrop"
|
|
@contextmenu.stop="onContextmenu"
|
|
>
|
|
<MkInfo v-if="!store.r.readDriveTip.value" closable @close="closeTip()"><div v-html="i18n.ts.driveAboutTip"></div></MkInfo>
|
|
<div v-show="foldersPaginator.items.value.length > 0">
|
|
<div :class="$style.folders">
|
|
<XFolder
|
|
v-for="(f, i) in foldersPaginator.items.value"
|
|
:key="f.id"
|
|
v-anim="i"
|
|
:class="$style.folder"
|
|
:folder="f"
|
|
:selectMode="select === 'folder'"
|
|
:isSelected="selectedFolders.some(x => x.id === f.id)"
|
|
@chosen="chooseFolder"
|
|
@unchose="unchoseFolder"
|
|
@click="cd(f)"
|
|
@upload="upload"
|
|
@dragstart="isDragSource = true"
|
|
@dragend="isDragSource = false"
|
|
/>
|
|
</div>
|
|
<MkButton v-if="foldersPaginator.canFetchOlder.value" primary rounded @click="foldersPaginator.fetchOlder()">{{ i18n.ts.loadMore }}</MkButton>
|
|
</div>
|
|
|
|
<div v-show="filesPaginator.items.value.length > 0">
|
|
<MkStickyContainer v-for="(item, i) in filesTimeline" :key="item.date.toISOString()">
|
|
<template #header>
|
|
<div :class="$style.date">
|
|
<span><i class="ti ti-chevron-down"></i> {{ item.date.getFullYear() }}/{{ item.date.getMonth() + 1 }}</span>
|
|
</div>
|
|
</template>
|
|
|
|
<div :class="$style.files">
|
|
<XFile
|
|
v-for="file in item.items" :key="file.id"
|
|
:class="$style.file"
|
|
:file="file"
|
|
:folder="folder"
|
|
:selectMode="select === 'file' || isEditMode"
|
|
:isSelected="selectedFiles.some(x => x.id === file.id)"
|
|
@chosen="onChooseFile"
|
|
@dragstart="onFileDragstart(file, $event)"
|
|
@dragend="isDragSource = false"
|
|
/>
|
|
</div>
|
|
</MkStickyContainer>
|
|
<MkButton v-show="filesPaginator.canFetchOlder.value" primary rounded @click="filesPaginator.fetchOlder()">{{ i18n.ts.loadMore }}</MkButton>
|
|
</div>
|
|
|
|
<div v-if="filesPaginator.items.value.length == 0 && foldersPaginator.items.value.length == 0 && !fetching" :class="$style.empty">
|
|
<div v-if="draghover">{{ i18n.ts['empty-draghover'] }}</div>
|
|
<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.ts['empty-drive-description'] }}</div>
|
|
<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
|
|
</div>
|
|
</div>
|
|
<MkLoading v-if="fetching"/>
|
|
<div v-if="draghover" :class="$style.dropzone"></div>
|
|
</div>
|
|
|
|
<template #footer>
|
|
<div v-if="isEditMode" :class="$style.footer">
|
|
<MkButton primary rounded @click="moveFilesBulk()"><i class="ti ti-folder-symlink"></i> {{ i18n.ts.move }}...</MkButton>
|
|
</div>
|
|
</template>
|
|
</MkStickyContainer>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch, computed } from 'vue';
|
|
import * as Misskey from 'misskey-js';
|
|
import MkButton from './MkButton.vue';
|
|
import MkInfo from './MkInfo.vue';
|
|
import type { MenuItem } from '@/types/menu.js';
|
|
import XNavFolder from '@/components/MkDrive.navFolder.vue';
|
|
import XFolder from '@/components/MkDrive.folder.vue';
|
|
import XFile from '@/components/MkDrive.file.vue';
|
|
import * as os from '@/os.js';
|
|
import { misskeyApi } from '@/utility/misskey-api.js';
|
|
import { useStream } from '@/stream.js';
|
|
import { i18n } from '@/i18n.js';
|
|
import { uploadFile, uploads } from '@/utility/upload.js';
|
|
import { claimAchievement } from '@/utility/achievements.js';
|
|
import { prefer } from '@/preferences.js';
|
|
import { chooseFileFromPc } from '@/utility/select-file.js';
|
|
import { store } from '@/store.js';
|
|
import { isSeparatorNeeded, getSeparatorInfo, makeDateGroupedTimelineComputedRef } from '@/utility/timeline-date-separate.js';
|
|
import { usePagination } from '@/composables/use-pagination.js';
|
|
import { globalEvents, useGlobalEvent } from '@/events.js';
|
|
import { DATA_TRANSFER_DRIVE_FILE, DATA_TRANSFER_DRIVE_FILES, DATA_TRANSFER_DRIVE_FOLDER } from '@/consts.js';
|
|
|
|
const props = withDefaults(defineProps<{
|
|
initialFolder?: Misskey.entities.DriveFolder['id'] | null;
|
|
type?: string;
|
|
multiple?: boolean;
|
|
select?: 'file' | 'folder' | null;
|
|
}>(), {
|
|
multiple: false,
|
|
select: null,
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
(ev: 'changeSelectedFiles', v: Misskey.entities.DriveFile[]): void;
|
|
(ev: 'changeSelectedFolders', v: (Misskey.entities.DriveFolder | null)[]): void;
|
|
(ev: 'move-root'): void;
|
|
(ev: 'cd', v: Misskey.entities.DriveFolder | null): void;
|
|
(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
|
|
}>();
|
|
|
|
const folder = ref<Misskey.entities.DriveFolder | null>(null);
|
|
const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]);
|
|
const uploadings = uploads;
|
|
|
|
// ドロップされようとしているか
|
|
const draghover = ref(false);
|
|
|
|
// 自身の所有するアイテムがドラッグをスタートさせたか
|
|
// (自分自身の階層にドロップできないようにするためのフラグ)
|
|
const isDragSource = ref(false);
|
|
|
|
const isEditMode = ref(false);
|
|
|
|
const selectedFiles = ref<Misskey.entities.DriveFile[]>([]);
|
|
const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
|
|
const isRootSelected = ref(false);
|
|
|
|
watch(selectedFiles, () => {
|
|
emit('changeSelectedFiles', selectedFiles.value);
|
|
});
|
|
|
|
watch([selectedFolders, isRootSelected], () => {
|
|
emit('changeSelectedFolders', isRootSelected.value ? [null, ...selectedFolders.value] : selectedFolders.value);
|
|
});
|
|
|
|
const fetching = ref(true);
|
|
|
|
const sortModeSelect = ref<NonNullable<Misskey.entities.DriveFilesRequest['sort']>>('+createdAt');
|
|
|
|
const filesPaginator = usePagination({
|
|
ctx: {
|
|
endpoint: 'drive/files',
|
|
limit: 30,
|
|
canFetchDetection: 'limit',
|
|
params: computed(() => ({
|
|
folderId: folder.value ? folder.value.id : null,
|
|
type: props.type,
|
|
sort: sortModeSelect.value,
|
|
})),
|
|
},
|
|
autoInit: false,
|
|
autoReInit: false,
|
|
});
|
|
|
|
const foldersPaginator = usePagination({
|
|
ctx: {
|
|
endpoint: 'drive/folders',
|
|
limit: 30,
|
|
canFetchDetection: 'limit',
|
|
params: computed(() => ({
|
|
folderId: folder.value ? folder.value.id : null,
|
|
})),
|
|
},
|
|
autoInit: false,
|
|
autoReInit: false,
|
|
});
|
|
|
|
const filesTimeline = makeDateGroupedTimelineComputedRef(filesPaginator.items, 'month');
|
|
|
|
watch(folder, () => emit('cd', folder.value));
|
|
watch(sortModeSelect, () => {
|
|
initialize();
|
|
});
|
|
|
|
async function initialize() {
|
|
fetching.value = true;
|
|
await Promise.all([
|
|
foldersPaginator.init(),
|
|
filesPaginator.init(),
|
|
]);
|
|
fetching.value = false;
|
|
}
|
|
|
|
function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) {
|
|
if (file.folderId === (folder.value?.id ?? null)) {
|
|
filesPaginator.prepend(file);
|
|
}
|
|
}
|
|
|
|
function onFileDragstart(file: Misskey.entities.DriveFile, ev: DragEvent) {
|
|
if (isEditMode.value) {
|
|
if (!selectedFiles.value.some(f => f.id === file.id)) {
|
|
selectedFiles.value.push(file);
|
|
}
|
|
|
|
if (ev.dataTransfer) {
|
|
ev.dataTransfer.effectAllowed = 'move';
|
|
ev.dataTransfer.setData(DATA_TRANSFER_DRIVE_FILES, JSON.stringify(selectedFiles.value));
|
|
}
|
|
}
|
|
|
|
isDragSource.value = true;
|
|
}
|
|
|
|
function onDragover(ev: DragEvent) {
|
|
if (!ev.dataTransfer) return;
|
|
|
|
// ドラッグ元が自分自身の所有するアイテムだったら
|
|
if (isDragSource.value) {
|
|
// 自分自身にはドロップさせない
|
|
ev.dataTransfer.dropEffect = 'none';
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
switch (ev.dataTransfer.effectAllowed) {
|
|
case 'all':
|
|
case 'uninitialized':
|
|
case 'copy':
|
|
case 'copyLink':
|
|
case 'copyMove':
|
|
ev.dataTransfer.dropEffect = 'copy';
|
|
break;
|
|
case 'linkMove':
|
|
case 'move':
|
|
ev.dataTransfer.dropEffect = 'move';
|
|
break;
|
|
default:
|
|
ev.dataTransfer.dropEffect = 'none';
|
|
break;
|
|
}
|
|
} else {
|
|
ev.dataTransfer.dropEffect = 'none';
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function onDragenter() {
|
|
if (!isDragSource.value) draghover.value = true;
|
|
}
|
|
|
|
function onDragleave() {
|
|
draghover.value = false;
|
|
}
|
|
|
|
function onDrop(ev: DragEvent) {
|
|
draghover.value = false;
|
|
|
|
if (!ev.dataTransfer) return;
|
|
|
|
// ドロップされてきたものがファイルだったら
|
|
if (ev.dataTransfer.files.length > 0) {
|
|
for (const file of Array.from(ev.dataTransfer.files)) {
|
|
upload(file, folder.value);
|
|
}
|
|
return;
|
|
}
|
|
|
|
//#region ドライブのファイル
|
|
{
|
|
const driveFile = ev.dataTransfer.getData(DATA_TRANSFER_DRIVE_FILE);
|
|
if (driveFile != null && driveFile !== '') {
|
|
const file = JSON.parse(driveFile);
|
|
if (filesPaginator.items.value.some(f => f.id === file.id)) return;
|
|
misskeyApi('drive/files/update', {
|
|
fileId: file.id,
|
|
folderId: folder.value ? folder.value.id : null,
|
|
});
|
|
}
|
|
}
|
|
//#endregion
|
|
|
|
//#region ドライブのファイル(複数)
|
|
{
|
|
const driveFiles = ev.dataTransfer.getData(DATA_TRANSFER_DRIVE_FILES);
|
|
if (driveFiles != null && driveFiles !== '') {
|
|
const files = JSON.parse(driveFiles);
|
|
misskeyApi('drive/files/move-bulk', {
|
|
fileIds: files.map(f => f.id),
|
|
folderId: folder.value ? folder.value.id : null,
|
|
}).then(() => {
|
|
globalEvents.emit('driveFilesMoved', files, folder.value);
|
|
});
|
|
}
|
|
}
|
|
//#endregion
|
|
|
|
//#region ドライブのフォルダ
|
|
{
|
|
const driveFolder = ev.dataTransfer.getData(DATA_TRANSFER_DRIVE_FOLDER);
|
|
if (driveFolder != null && driveFolder !== '') {
|
|
const droppedFolder = JSON.parse(driveFolder);
|
|
|
|
// 移動先が自分自身ならreject
|
|
if (folder.value && droppedFolder.id === folder.value.id) return false;
|
|
if (foldersPaginator.items.value.some(f => f.id === droppedFolder.id)) return false;
|
|
misskeyApi('drive/folders/update', {
|
|
folderId: droppedFolder.id,
|
|
parentId: folder.value ? folder.value.id : null,
|
|
}).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,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
//#endregion
|
|
}
|
|
|
|
async function urlUpload() {
|
|
const { canceled, result: url } = await os.inputText({
|
|
title: i18n.ts.uploadFromUrl,
|
|
type: 'url',
|
|
placeholder: i18n.ts.uploadFromUrlDescription,
|
|
});
|
|
if (canceled || !url) return;
|
|
|
|
await os.apiWithDialog('drive/files/upload-from-url', {
|
|
url: url,
|
|
folderId: folder.value ? folder.value.id : undefined,
|
|
});
|
|
|
|
os.alert({
|
|
title: i18n.ts.uploadFromUrlRequested,
|
|
text: i18n.ts.uploadFromUrlMayTakeTime,
|
|
});
|
|
}
|
|
|
|
async function createFolder() {
|
|
const { canceled, result: name } = await os.inputText({
|
|
title: i18n.ts.createFolder,
|
|
placeholder: i18n.ts.folderName,
|
|
});
|
|
if (canceled || name == null) return;
|
|
|
|
const createdFolder = await os.apiWithDialog('drive/folders/create', {
|
|
name: name,
|
|
parentId: folder.value ? folder.value.id : undefined,
|
|
});
|
|
|
|
foldersPaginator.prepend(createdFolder);
|
|
}
|
|
|
|
async function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
|
|
const { canceled, result: name } = await os.inputText({
|
|
title: i18n.ts.renameFolder,
|
|
placeholder: i18n.ts.inputNewFolderName,
|
|
default: folderToRename.name,
|
|
});
|
|
if (canceled) return;
|
|
|
|
const updatedFolder = await os.apiWithDialog('drive/folders/update', {
|
|
folderId: folderToRename.id,
|
|
name: name,
|
|
});
|
|
|
|
// FIXME: 画面を更新するために自分自身に移動
|
|
cd(updatedFolder);
|
|
}
|
|
|
|
function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
|
|
misskeyApi('drive/folders/delete', {
|
|
folderId: folderToDelete.id,
|
|
}).then(() => {
|
|
// 削除時に親フォルダに移動
|
|
cd(folderToDelete.parentId);
|
|
}).catch(err => {
|
|
switch (err.id) {
|
|
case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
|
|
os.alert({
|
|
type: 'error',
|
|
title: i18n.ts.unableToDelete,
|
|
text: i18n.ts.hasChildFilesOrFolders,
|
|
});
|
|
break;
|
|
default:
|
|
os.alert({
|
|
type: 'error',
|
|
text: i18n.ts.unableToDelete,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null, keepOriginal?: boolean) {
|
|
uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal).then(res => {
|
|
if (res.folderId === (folder.value?.id ?? null)) {
|
|
filesPaginator.prepend(res);
|
|
}
|
|
});
|
|
}
|
|
|
|
function onChooseFile(file: Misskey.entities.DriveFile) {
|
|
const isAlreadySelected = selectedFiles.value.some(f => f.id === file.id);
|
|
|
|
if (isEditMode.value) {
|
|
if (isAlreadySelected) {
|
|
selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id);
|
|
} else {
|
|
selectedFiles.value.push(file);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (props.multiple) {
|
|
if (isAlreadySelected) {
|
|
selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id);
|
|
} else {
|
|
selectedFiles.value.push(file);
|
|
}
|
|
} else {
|
|
if (isAlreadySelected) {
|
|
//emit('selected', file);
|
|
} else {
|
|
selectedFiles.value = [file];
|
|
}
|
|
}
|
|
}
|
|
|
|
function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
|
|
const isAlreadySelected = selectedFolders.value.some(f => f.id === folderToChoose.id);
|
|
if (props.multiple) {
|
|
if (isAlreadySelected) {
|
|
selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToChoose.id);
|
|
} else {
|
|
selectedFolders.value.push(folderToChoose);
|
|
}
|
|
} else {
|
|
if (isAlreadySelected) {
|
|
//emit('selected', folderToChoose);
|
|
} else {
|
|
selectedFolders.value = [folderToChoose];
|
|
}
|
|
}
|
|
}
|
|
|
|
function unchoseFolder(folderToUnchose: Misskey.entities.DriveFolder) {
|
|
selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToUnchose.id);
|
|
}
|
|
|
|
function cd(target?: Misskey.entities.DriveFolder | Misskey.entities.DriveFolder['id' | 'parentId']) {
|
|
if (!target) {
|
|
goRoot();
|
|
return;
|
|
} else if (typeof target === 'object') {
|
|
target = target.id;
|
|
}
|
|
|
|
fetching.value = true;
|
|
|
|
misskeyApi('drive/folders/show', {
|
|
folderId: target,
|
|
}).then(folderToMove => {
|
|
folder.value = folderToMove;
|
|
hierarchyFolders.value = [];
|
|
|
|
const dive = folderToDive => {
|
|
hierarchyFolders.value.unshift(folderToDive);
|
|
if (folderToDive.parent) dive(folderToDive.parent);
|
|
};
|
|
|
|
if (folderToMove.parent) dive(folderToMove.parent);
|
|
|
|
emit('open-folder', folderToMove);
|
|
initialize();
|
|
});
|
|
}
|
|
|
|
async function moveFilesBulk() {
|
|
if (selectedFiles.value.length === 0) return;
|
|
|
|
const toFolder = await os.selectDriveFolder(folder.value ? folder.value.id : null);
|
|
|
|
await os.apiWithDialog('drive/files/move-bulk', {
|
|
fileIds: selectedFiles.value.map(f => f.id),
|
|
folderId: toFolder[0] ? toFolder[0].id : null,
|
|
});
|
|
|
|
globalEvents.emit('driveFilesMoved', selectedFiles.value, toFolder[0]);
|
|
}
|
|
|
|
function goRoot() {
|
|
// 既にrootにいるなら何もしない
|
|
if (folder.value == null) return;
|
|
|
|
folder.value = null;
|
|
hierarchyFolders.value = [];
|
|
emit('move-root');
|
|
initialize();
|
|
}
|
|
|
|
function getMenu() {
|
|
const menu: MenuItem[] = [];
|
|
|
|
menu.push({
|
|
text: i18n.ts.addFile,
|
|
type: 'label',
|
|
}, {
|
|
text: i18n.ts.upload + ' (' + i18n.ts.compress + ')',
|
|
icon: 'ti ti-upload',
|
|
action: () => {
|
|
chooseFileFromPc(true, { uploadFolder: folder.value?.id, keepOriginal: false });
|
|
},
|
|
}, {
|
|
text: i18n.ts.upload,
|
|
icon: 'ti ti-upload',
|
|
action: () => {
|
|
chooseFileFromPc(true, { uploadFolder: folder.value?.id, keepOriginal: true });
|
|
},
|
|
}, {
|
|
text: i18n.ts.fromUrl,
|
|
icon: 'ti ti-link',
|
|
action: () => { urlUpload(); },
|
|
}, { type: 'divider' }, {
|
|
text: folder.value ? folder.value.name : i18n.ts.drive,
|
|
type: 'label',
|
|
});
|
|
|
|
menu.push({
|
|
type: 'parent',
|
|
text: i18n.ts.sort,
|
|
icon: 'ti ti-arrows-sort',
|
|
children: [{
|
|
text: `${i18n.ts.registeredDate} (${i18n.ts.descendingOrder})`,
|
|
icon: 'ti ti-sort-descending-letters',
|
|
action: () => { sortModeSelect.value = '+createdAt'; },
|
|
active: sortModeSelect.value === '+createdAt',
|
|
}, {
|
|
text: `${i18n.ts.registeredDate} (${i18n.ts.ascendingOrder})`,
|
|
icon: 'ti ti-sort-ascending-letters',
|
|
action: () => { sortModeSelect.value = '-createdAt'; },
|
|
active: sortModeSelect.value === '-createdAt',
|
|
}, {
|
|
text: `${i18n.ts.size} (${i18n.ts.descendingOrder})`,
|
|
icon: 'ti ti-sort-descending-letters',
|
|
action: () => { sortModeSelect.value = '+size'; },
|
|
active: sortModeSelect.value === '+size',
|
|
}, {
|
|
text: `${i18n.ts.size} (${i18n.ts.ascendingOrder})`,
|
|
icon: 'ti ti-sort-ascending-letters',
|
|
action: () => { sortModeSelect.value = '-size'; },
|
|
active: sortModeSelect.value === '-size',
|
|
}, {
|
|
text: `${i18n.ts.name} (${i18n.ts.descendingOrder})`,
|
|
icon: 'ti ti-sort-descending-letters',
|
|
action: () => { sortModeSelect.value = '+name'; },
|
|
active: sortModeSelect.value === '+name',
|
|
}, {
|
|
text: `${i18n.ts.name} (${i18n.ts.ascendingOrder})`,
|
|
icon: 'ti ti-sort-ascending-letters',
|
|
action: () => { sortModeSelect.value = '-name'; },
|
|
active: sortModeSelect.value === '-name',
|
|
}],
|
|
});
|
|
|
|
if (folder.value) {
|
|
menu.push({
|
|
text: i18n.ts.renameFolder,
|
|
icon: 'ti ti-forms',
|
|
action: () => { if (folder.value) renameFolder(folder.value); },
|
|
}, {
|
|
text: i18n.ts.deleteFolder,
|
|
icon: 'ti ti-trash',
|
|
action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); },
|
|
});
|
|
}
|
|
|
|
menu.push({
|
|
text: i18n.ts.createFolder,
|
|
icon: 'ti ti-folder-plus',
|
|
action: () => { createFolder(); },
|
|
}, { type: 'divider' }, {
|
|
type: 'switch',
|
|
text: i18n.ts.edit,
|
|
icon: 'ti ti-pointer',
|
|
ref: isEditMode,
|
|
});
|
|
|
|
return menu;
|
|
}
|
|
|
|
function showMenu(ev: MouseEvent) {
|
|
os.popupMenu(getMenu(), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
|
|
}
|
|
|
|
function onContextmenu(ev: MouseEvent) {
|
|
os.contextMenu(getMenu(), ev);
|
|
}
|
|
|
|
function closeTip() {
|
|
store.set('readDriveTip', true);
|
|
}
|
|
|
|
useGlobalEvent('driveFilesMoved', (files, to) => {
|
|
for (const f of files) {
|
|
filesPaginator.removeItem(f.id);
|
|
}
|
|
if ((to?.id ?? null) === (folder.value?.id ?? null)) {
|
|
filesPaginator.unshiftItems(files);
|
|
}
|
|
});
|
|
|
|
let connection: Misskey.ChannelConnection<Misskey.Channels['drive']> | null = null;
|
|
|
|
onMounted(() => {
|
|
if (store.s.realtimeMode) {
|
|
connection = useStream().useChannel('drive');
|
|
connection.on('fileCreated', onStreamDriveFileCreated);
|
|
}
|
|
|
|
if (props.initialFolder) {
|
|
cd(props.initialFolder);
|
|
} else {
|
|
initialize();
|
|
}
|
|
});
|
|
|
|
onActivated(() => {
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
if (connection != null) {
|
|
connection.dispose();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" module>
|
|
.nav {
|
|
display: flex;
|
|
width: 100%;
|
|
padding: 0 8px;
|
|
box-sizing: border-box;
|
|
overflow: auto;
|
|
font-size: 0.9em;
|
|
background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
|
|
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
|
backdrop-filter: var(--MI-blur, blur(15px));
|
|
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
|
}
|
|
|
|
.navPath {
|
|
display: inline-block;
|
|
vertical-align: bottom;
|
|
line-height: 42px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.navPathItem {
|
|
display: inline-block;
|
|
margin: 0;
|
|
padding: 0 8px;
|
|
line-height: 42px;
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
&.navCurrent {
|
|
font-weight: bold;
|
|
cursor: default;
|
|
|
|
&:hover {
|
|
text-decoration: none;
|
|
}
|
|
}
|
|
|
|
&.navSeparator {
|
|
margin: 0;
|
|
padding: 0;
|
|
opacity: 0.5;
|
|
cursor: default;
|
|
}
|
|
}
|
|
|
|
.navMenu {
|
|
margin-left: auto;
|
|
padding: 0 12px;
|
|
}
|
|
|
|
.main {
|
|
user-select: none;
|
|
|
|
&.fetching {
|
|
cursor: wait !important;
|
|
opacity: 0.5;
|
|
pointer-events: none;
|
|
}
|
|
|
|
&.uploading {
|
|
height: calc(100% - 38px - 100px);
|
|
}
|
|
}
|
|
|
|
.folders,
|
|
.files {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
grid-gap: 12px;
|
|
padding: var(--MI-margin);
|
|
}
|
|
|
|
.date {
|
|
padding: 8px 16px;
|
|
font-size: 90%;
|
|
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
backdrop-filter: var(--MI-blur, blur(8px));
|
|
background-color: color(from var(--MI_THEME-bg) srgb r g b / 0.85);
|
|
}
|
|
|
|
.footer {
|
|
padding: 8px 16px;
|
|
font-size: 90%;
|
|
-webkit-backdrop-filter: var(--MI-blur, blur(8px));
|
|
backdrop-filter: var(--MI-blur, blur(8px));
|
|
background-color: color(from var(--MI_THEME-bg) srgb r g b / 0.85);
|
|
}
|
|
|
|
.empty {
|
|
padding: 16px;
|
|
text-align: center;
|
|
pointer-events: none;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.dropzone {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 38px;
|
|
width: 100%;
|
|
height: calc(100% - 38px);
|
|
border: dashed 2px var(--MI_THEME-focus);
|
|
pointer-events: none;
|
|
}
|
|
</style>
|