diff options
| author | CyberRex <hspwinx86@gmail.com> | 2023-03-20 12:58:06 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-20 12:58:06 +0900 |
| commit | 1d6f43aa30a932b1b7539f417f19d0b239cde511 (patch) | |
| tree | eaf1b2e27a69c6ac2764ecfe0238b9519f6c9ac7 /packages | |
| parent | Update CHANGELOG.md (diff) | |
| download | misskey-1d6f43aa30a932b1b7539f417f19d0b239cde511.tar.gz misskey-1d6f43aa30a932b1b7539f417f19d0b239cde511.tar.bz2 misskey-1d6f43aa30a932b1b7539f417f19d0b239cde511.zip | |
feat: drive cleaner (#10366)
* feat: drive-cleaner
* Update CHANGELOG.md
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/backend/src/server/api/endpoints/drive/files.ts | 10 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/drive-cleaner.vue | 193 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/drive.vue | 3 | ||||
| -rw-r--r-- | packages/frontend/src/router.ts | 4 |
4 files changed, 210 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts index f6fad50fd9..4609307774 100644 --- a/packages/backend/src/server/api/endpoints/drive/files.ts +++ b/packages/backend/src/server/api/endpoints/drive/files.ts @@ -31,6 +31,7 @@ export const paramDef = { untilId: { type: 'string', format: 'misskey:id' }, folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null }, type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) }, + sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size'] }, }, required: [], } as const; @@ -63,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { } } + switch (ps.sort) { + case '+createdAt': query.orderBy('file.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('file.createdAt', 'ASC'); break; + case '+name': query.orderBy('file.name', 'DESC'); break; + case '-name': query.orderBy('file.name', 'ASC'); break; + case '+size': query.orderBy('file.size', 'DESC'); break; + case '-size': query.orderBy('file.size', 'ASC'); break; + } + const files = await query.take(ps.limit).getMany(); return await this.driveFileEntityService.packMany(files, { detail: false, self: true }); diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue new file mode 100644 index 0000000000..ce8ab214e4 --- /dev/null +++ b/packages/frontend/src/pages/settings/drive-cleaner.vue @@ -0,0 +1,193 @@ +<template> +<MkSelect v-model="sortModeSelect"> + <template #label>{{ i18n.ts.sort }}</template> + <option v-for="x in sortOptions" :key="x.value" :value="x.value">{{ x.displayName }}</option> +</MkSelect> +<br> +<div v-if="!fetching" class="_gap_m"> + <MkPagination v-slot="{items}" :pagination="pagination" class="driveitem list"> + <div + v-for="file in items" + :key="file.id" + > + <MkA + v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}`" + class="_button" + :to="`${file.url}`" + behavior="browser" + @contextmenu.stop="$event => onContextMenu($event, file.id)" + > + <div class="file"> + <div v-if="file.isSensitive" class="sensitive-label">{{ i18n.ts.sensitive }}</div> + <MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> + <div class="body"> + <div style="margin-bottom: 4px;"> + {{ file.name }} + </div> + <div> + <span style="margin-right: 1em;">{{ file.type }}</span> + <span>{{ bytes(file.size) }}</span> + </div> + <div> + <span>{{ i18n.ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span> + </div> + <div v-if="sortModeSelect === 'sizeDesc'"> + <div class="uawsfosz"> + <div class="meter"><div :style="genUsageBar(file.size)"></div></div> + </div> + </div> + </div> + </div> + </MkA> + </div> + </MkPagination> +</div> +<div v-else class="gap_m"> + {{ i18n.ts.checking }} <MkEllipsis/> +</div> +</template> + +<script setup lang="ts"> +import { ref, watch } from 'vue'; +import tinycolor from 'tinycolor2'; +import * as os from '@/os'; +import MkPagination from '@/components/MkPagination.vue'; +import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue'; +import { i18n } from '@/i18n'; +import bytes from '@/filters/bytes'; +import { dateString } from '@/filters/date'; +import { definePageMetadata } from '@/scripts/page-metadata'; +import MkSelect from '@/components/MkSelect.vue'; + +let sortMode = '+size'; +const pagination = { + endpoint: 'drive/files' as const, + limit: 10, + params: { sort: sortMode }, +}; + +const sortOptions = [ + { value: 'sizeDesc', displayName: i18n.ts._drivecleaner.orderBySizeDesc }, + { value: 'createdAtAsc', displayName: i18n.ts._drivecleaner.orderByCreatedAtAsc }, +]; + +const capacity = ref<number>(0); +const usage = ref<number>(0); +const fetching = ref(true); +const sortModeSelect = ref('sizeDesc'); + +fetchDriveInfo(); + +watch(fetching, () => { + if (fetching.value) { + fetchDriveInfo(); + } +}); + +watch(sortModeSelect, () => { + switch (sortModeSelect.value) { + case 'sizeDesc': + sortMode = '+size'; + fetching.value = true; + break; + + case 'createdAtAsc': + sortMode = '-createdAt'; + fetching.value = true; + break; + } +}); + +function fetchDriveInfo(): void { + os.api('drive').then(info => { + capacity.value = info.capacity; + usage.value = info.usage; + fetching.value = false; + }); +} + +function genUsageBar(fsize: number): object { + return { + width: `${fsize / usage.value * 100}%`, + background: tinycolor({ h: 180 - (fsize / usage.value * 180), s: 0.7, l: 0.5 }), + }; +} + +function onContextMenu(ev: MouseEvent, fileId: string): void { + const target = ev.target as HTMLElement; + const items = [ + { + text: i18n.ts.delete, + icon: 'ti ti-trash-x', + danger: true, + action: async (): Promise<void> => { + const res = await os.confirm({ + type: 'warning', + title: i18n.ts.delete, + text: i18n.ts.deleteConfirm, + }); + if (!res.canceled) { + await os.apiWithDialog('drive/files/delete', { fileId: fileId }); + fetching.value = true; + } + }, + }, + ]; + ev.preventDefault(); + os.popupMenu(items, target, { + viaKeyboard: false, + }); +} + +definePageMetadata({ + title: i18n.ts.drivecleaner, + icon: 'ti ti-trash', +}); +</script> + +<style lang="scss" scoped> + +@use "sass:math"; + +.file { + display: flex; + width: 100%; + box-sizing: border-box; + text-align: left; + align-items: center; + margin-bottom: 16px; + + &:hover { + color: var(--accent); + } + + > .thumbnail { + width: 128px; + height: 128px; + } + + > .body { + margin-left: 0.3em; + padding: 8px; + flex: 1; + + @media (max-width: 500px) { + font-size: 14px; + } + } +} + +.uawsfosz { + > .meter { + $size: 12px; + background: rgba(0, 0, 0, 0.1); + border-radius: math.div($size, 2); + overflow: hidden; + + > div { + height: $size; + border-radius: math.div($size, 2); + } + } +} +</style> diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index a23bdfe69e..d3fb422e01 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -32,6 +32,9 @@ <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template> <template #suffixIcon><i class="ti ti-folder"></i></template> </FormLink> + <FormLink to="/settings/drive/cleaner"> + {{ i18n.ts.drivecleaner }} + </FormLink> <MkSwitch v-model="keepOriginalUploading"> <template #label>{{ i18n.ts.keepOriginalUploading }}</template> <template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template> diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index 590c5765fd..c8077edd28 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -66,6 +66,10 @@ export const routes = [{ name: 'drive', component: page(() => import('./pages/settings/drive.vue')), }, { + path: '/drive/cleaner', + name: 'drive', + component: page(() => import('./pages/settings/drive-cleaner.vue')), + }, { path: '/notifications', name: 'notifications', component: page(() => import('./pages/settings/notifications.vue')), |