enhance(client): improve files page of control panel
This commit is contained in:
		
							parent
							
								
									fdba255b9a
								
							
						
					
					
						commit
						4a55425fdb
					
				
					 5 changed files with 105 additions and 96 deletions
				
			
		|  | @ -13,6 +13,7 @@ You should also include the user name that made the change. | |||
| 
 | ||||
| ### Improvements | ||||
| - Server: Add rate limit to i/notifications @tamaina | ||||
| - Client: Improve files page of control panel @syuilo | ||||
| 
 | ||||
| ### Bugfixes | ||||
| - Server: Fix GenerateVideoThumbnail failed @mei23 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <div ref="thumbnail" class="zdjebgpv"> | ||||
| 	<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :style="`object-fit: ${ fit }`"/> | ||||
| 	<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/> | ||||
| 	<i v-else-if="is === 'image'" class="fas fa-file-image icon"></i> | ||||
| 	<i v-else-if="is === 'video'" class="fas fa-file-video icon"></i> | ||||
| 	<i v-else-if="is === 'audio' || is === 'midi'" class="fas fa-music icon"></i> | ||||
|  | @ -33,16 +33,16 @@ const is = computed(() => { | |||
| 	if (props.file.type.endsWith('/pdf')) return 'pdf'; | ||||
| 	if (props.file.type.startsWith('text/')) return 'textfile'; | ||||
| 	if ([ | ||||
| 			"application/zip", | ||||
| 			"application/x-cpio", | ||||
| 			"application/x-bzip", | ||||
| 			"application/x-bzip2", | ||||
| 			"application/java-archive", | ||||
| 			"application/x-rar-compressed", | ||||
| 			"application/x-tar", | ||||
| 			"application/gzip", | ||||
| 			"application/x-7z-compressed" | ||||
| 		].some(archiveType => archiveType === props.file.type)) return 'archive'; | ||||
| 		'application/zip', | ||||
| 		'application/x-cpio', | ||||
| 		'application/x-bzip', | ||||
| 		'application/x-bzip2', | ||||
| 		'application/java-archive', | ||||
| 		'application/x-rar-compressed', | ||||
| 		'application/x-tar', | ||||
| 		'application/gzip', | ||||
| 		'application/x-7z-compressed', | ||||
| 	].some(archiveType => archiveType === props.file.type)) return 'archive'; | ||||
| 	return 'unknown'; | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,7 +1,10 @@ | |||
| <template> | ||||
| <transition :name="$store.state.animation ? 'tooltip' : ''" appear @after-leave="emit('closed')"> | ||||
| 	<div v-show="showing" ref="el" class="buebdbiu _acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }"> | ||||
| 		<slot>{{ text }}</slot> | ||||
| 		<slot> | ||||
| 			<Mfm v-if="asMfm" :text="text"/> | ||||
| 			<span v-else>{{ text }}</span> | ||||
| 		</slot> | ||||
| 	</div> | ||||
| </transition> | ||||
| </template> | ||||
|  | @ -16,6 +19,7 @@ const props = withDefaults(defineProps<{ | |||
| 	x?: number; | ||||
| 	y?: number; | ||||
| 	text?: string; | ||||
| 	asMfm?: boolean; | ||||
| 	maxWidth?: number; | ||||
| 	direction?: 'top' | 'bottom' | 'right' | 'left'; | ||||
| 	innerMargin?: number; | ||||
|  | @ -170,8 +174,6 @@ const setPosition = () => { | |||
| 				return { left, top, transformOrigin: 'left center' }; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		return null as never; | ||||
| 	}; | ||||
| 
 | ||||
| 	const { left, top, transformOrigin } = calc(); | ||||
|  |  | |||
|  | @ -48,6 +48,7 @@ export default { | |||
| 			popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), { | ||||
| 				showing, | ||||
| 				text: self.text, | ||||
| 				asMfm: binding.modifiers.mfm, | ||||
| 				targetElement: el, | ||||
| 			}, {}, 'closed'); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,61 +1,50 @@ | |||
| <template> | ||||
| <div class="xrmjdkdw"> | ||||
| 	<MkContainer :foldable="true" class="lookup"> | ||||
| 		<template #header><i class="fas fa-search"></i> {{ $ts.lookup }}</template> | ||||
| 		<div class="xrmjdkdw-lookup"> | ||||
| 			<MkInput v-model="q" class="item" type="text" @enter="find()"> | ||||
| 				<template #label>{{ $ts.fileIdOrUrl }}</template> | ||||
| 	<div> | ||||
| 		<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> | ||||
| 			<MkSelect v-model="origin" style="margin: 0; flex: 1;"> | ||||
| 				<template #label>{{ $ts.instance }}</template> | ||||
| 				<option value="combined">{{ $ts.all }}</option> | ||||
| 				<option value="local">{{ $ts.local }}</option> | ||||
| 				<option value="remote">{{ $ts.remote }}</option> | ||||
| 			</MkSelect> | ||||
| 			<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'"> | ||||
| 				<template #label>{{ $ts.host }}</template> | ||||
| 			</MkInput> | ||||
| 			<MkButton primary @click="find()"><i class="fas fa-search"></i> {{ $ts.lookup }}</MkButton> | ||||
| 		</div> | ||||
| 	</MkContainer> | ||||
| 
 | ||||
| 	<div class="_section"> | ||||
| 		<div class="_content"> | ||||
| 			<div class="inputs" style="display: flex;"> | ||||
| 				<MkSelect v-model="origin" style="margin: 0; flex: 1;"> | ||||
| 					<template #label>{{ $ts.instance }}</template> | ||||
| 					<option value="combined">{{ $ts.all }}</option> | ||||
| 					<option value="local">{{ $ts.local }}</option> | ||||
| 					<option value="remote">{{ $ts.remote }}</option> | ||||
| 				</MkSelect> | ||||
| 				<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'"> | ||||
| 					<template #label>{{ $ts.host }}</template> | ||||
| 				</MkInput> | ||||
| 			</div> | ||||
| 			<div class="inputs" style="display: flex; padding-top: 1.2em;"> | ||||
| 				<MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;"> | ||||
| 					<template #label>MIME type</template> | ||||
| 				</MkInput> | ||||
| 			</div> | ||||
| 			<MkPagination v-slot="{items}" :pagination="pagination" class="urempief"> | ||||
| 				<button v-for="file in items" :key="file.id" class="file _panel _button _gap" @click="show(file, $event)"> | ||||
| 					<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> | ||||
| 					<div class="body"> | ||||
| 						<div> | ||||
| 							<small style="opacity: 0.7;">{{ file.name }}</small> | ||||
| 						</div> | ||||
| 						<div> | ||||
| 							<MkAcct v-if="file.user" :user="file.user"/> | ||||
| 							<div v-else>{{ $ts.system }}</div> | ||||
| 						</div> | ||||
| 						<div> | ||||
| 							<span style="margin-right: 1em;">{{ file.type }}</span> | ||||
| 							<span>{{ bytes(file.size) }}</span> | ||||
| 						</div> | ||||
| 						<div> | ||||
| 							<span>{{ $ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span> | ||||
| 						</div> | ||||
| 		<div class="inputs" style="display: flex; padding-top: 1.2em;"> | ||||
| 			<MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;"> | ||||
| 				<template #label>MIME type</template> | ||||
| 			</MkInput> | ||||
| 		</div> | ||||
| 		<MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }"> | ||||
| 			<button v-for="file in items" :key="file.id" v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${new Date(file.createdAt).toLocaleString()}\nby ${file.user ? '@' + Acct.toString(file.user) : 'system'}`" class="file _panel _button" @click="show(file, $event)"> | ||||
| 				<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/> | ||||
| 				<div v-if="viewMode === 'list'" class="body"> | ||||
| 					<div> | ||||
| 						<small style="opacity: 0.7;">{{ file.name }}</small> | ||||
| 					</div> | ||||
| 				</button> | ||||
| 			</MkPagination> | ||||
| 		</div> | ||||
| 					<div> | ||||
| 						<MkAcct v-if="file.user" :user="file.user"/> | ||||
| 						<div v-else>{{ $ts.system }}</div> | ||||
| 					</div> | ||||
| 					<div> | ||||
| 						<span style="margin-right: 1em;">{{ file.type }}</span> | ||||
| 						<span>{{ bytes(file.size) }}</span> | ||||
| 					</div> | ||||
| 					<div> | ||||
| 						<span>{{ $ts.registeredDate }}: <MkTime :time="file.createdAt" mode="detail"/></span> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</button> | ||||
| 		</MkPagination> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { computed, defineAsyncComponent } from 'vue'; | ||||
| import * as Acct from 'misskey-js/built/acct'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import MkInput from '@/components/form/input.vue'; | ||||
| import MkSelect from '@/components/form/select.vue'; | ||||
|  | @ -67,10 +56,10 @@ import * as os from '@/os'; | |||
| import * as symbols from '@/symbols'; | ||||
| import { i18n } from '@/i18n'; | ||||
| 
 | ||||
| let q = $ref(null); | ||||
| let origin = $ref('local'); | ||||
| let type = $ref(null); | ||||
| let searchHost = $ref(''); | ||||
| let viewMode = $ref('grid'); | ||||
| const pagination = { | ||||
| 	endpoint: 'admin/drive/files' as const, | ||||
| 	limit: 10, | ||||
|  | @ -94,18 +83,24 @@ function clear() { | |||
| 
 | ||||
| function show(file) { | ||||
| 	os.popup(defineAsyncComponent(() => import('./file-dialog.vue')), { | ||||
| 		fileId: file.id | ||||
| 		fileId: file.id, | ||||
| 	}, {}, 'closed'); | ||||
| } | ||||
| 
 | ||||
| function find() { | ||||
| async function find() { | ||||
| 	const { canceled, result: q } = await os.inputText({ | ||||
| 		title: i18n.ts.fileIdOrUrl, | ||||
| 		allowEmpty: false, | ||||
| 	}); | ||||
| 	if (canceled) return; | ||||
| 
 | ||||
| 	os.api('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => { | ||||
| 		show(file); | ||||
| 	}).catch(err => { | ||||
| 		if (err.code === 'NO_SUCH_FILE') { | ||||
| 			os.alert({ | ||||
| 				type: 'error', | ||||
| 				text: i18n.ts.notFound | ||||
| 				text: i18n.ts.notFound, | ||||
| 			}); | ||||
| 		} | ||||
| 	}); | ||||
|  | @ -117,6 +112,10 @@ defineExpose({ | |||
| 		icon: 'fas fa-cloud', | ||||
| 		bg: 'var(--bg)', | ||||
| 		actions: [{ | ||||
| 			text: i18n.ts.lookup, | ||||
| 			icon: 'fas fa-search', | ||||
| 			handler: find, | ||||
| 		}, { | ||||
| 			text: i18n.ts.clearCachedFiles, | ||||
| 			icon: 'fas fa-trash-alt', | ||||
| 			handler: clear, | ||||
|  | @ -129,47 +128,53 @@ defineExpose({ | |||
| .xrmjdkdw { | ||||
| 	margin: var(--margin); | ||||
| 
 | ||||
| 	> .lookup { | ||||
| 		margin-bottom: 16px; | ||||
| 	} | ||||
| 
 | ||||
| 	.urempief { | ||||
| 		margin-top: var(--margin); | ||||
| 
 | ||||
| 		> .file { | ||||
| 			display: flex; | ||||
| 			width: 100%; | ||||
| 			box-sizing: border-box; | ||||
| 			text-align: left; | ||||
| 			align-items: center; | ||||
| 		&.list { | ||||
| 			> .file { | ||||
| 				display: flex; | ||||
| 				width: 100%; | ||||
| 				box-sizing: border-box; | ||||
| 				text-align: left; | ||||
| 				align-items: center; | ||||
| 
 | ||||
| 			&:hover { | ||||
| 				color: var(--accent); | ||||
| 				&: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; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 			> .thumbnail { | ||||
| 				width: 128px; | ||||
| 				height: 128px; | ||||
| 			} | ||||
| 		&.grid { | ||||
| 			display: grid; | ||||
| 			grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); | ||||
| 			grid-gap: 12px; | ||||
| 			margin: var(--margin) 0; | ||||
| 
 | ||||
| 			> .body { | ||||
| 				margin-left: 0.3em; | ||||
| 				padding: 8px; | ||||
| 				flex: 1; | ||||
| 
 | ||||
| 				@media (max-width: 500px) { | ||||
| 					font-size: 14px; | ||||
| 			> .file { | ||||
| 				aspect-ratio: 1; | ||||
| 			 | ||||
| 				> .thumbnail { | ||||
| 					width: 100%; | ||||
| 					height: 100%; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| .xrmjdkdw-lookup { | ||||
| 	padding: 16px; | ||||
| 
 | ||||
| 	> .item { | ||||
| 		margin-bottom: 16px; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue