feat: 支持使用虚拟列表时也能全屏预览内移动到任意图片

pull/6/head
zanllp 2023-03-28 23:21:55 +08:00
parent b943138aed
commit 6145bc3851
8 changed files with 331 additions and 221 deletions

View File

@ -6,8 +6,8 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-b24eca93.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-02b35684.css">
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-0e9928f4.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-b1903a85.css">
</head>
<body>
<div id="zanllp_dev_gradio_fe"></div>

201
vue/dist/assets/index-0e9928f4.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
vue/dist/index.html vendored
View File

@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-b24eca93.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-02b35684.css">
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-0e9928f4.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-b1903a85.css">
</head>
<body>
<div id="zanllp_dev_gradio_fe"></div>

View File

@ -16,7 +16,7 @@
"@types/nprogress": "^0.2.0",
"@types/path-browserify": "^1.0.0",
"@vueuse/core": "^9.13.0",
"ant-design-vue": "^3.2.15",
"ant-design-vue": "^3.2.16",
"axios": "^1.3.4",
"lodash-es": "^4.17.21",
"multi-nprogress": "^0.3.5",

View File

@ -3,7 +3,7 @@ import { getTargetFolderFiles, type FileNodeInfo } from '@/api/files'
import { setImgPath, genInfoCompleted, getImageGenerationInfo } from '@/api'
import { cloneDeep, debounce, last, range, uniq } from 'lodash'
import { ref, computed, onMounted, watch, h, reactive } from 'vue'
import { FileOutlined, FolderOpenOutlined, DownOutlined } from '@/icon'
import { FileOutlined, FolderOpenOutlined, DownOutlined, LeftCircleOutlined, RightCircleOutlined } from '@/icon'
import { sortMethodMap, sortFiles, SortMethod } from './fileSort'
import path from 'path-browserify'
import { useGlobalStore } from '@/store/useGlobalStore'
@ -38,15 +38,92 @@ const { currLocation, currPage, refresh, copyLocation, back, openNext, stack, to
const { gridItems, sortMethodConv, moreActionsDropdownShow, sortedFiles, sortMethod, viewMode, gridSize, viewModeMap, largeGridSize } = useFilesDisplay()
const { onDrop, onFileDragStart, multiSelectedIdxs } = useFileTransfer()
const { onFileItemClick, onContextMenuClick, showGenInfo, imageGenInfo, q } = useFileItemActions()
const { previewIdx, onPreviewVisibleChange, previewing, previewImgMove, canPreview } = usePreview()
const toRawFileUrl = (file: FileNodeInfoR, download = false) => `/baidu_netdisk/file?filename=${encodeURIComponent(file.fullpath)}${download ? `&disposition=${encodeURIComponent(file.name)}` : ''}`
const toImageThumbnailUrl = (file: FileNodeInfoR, size = '256,256') => `/baidu_netdisk/image-thumbnail?path=${encodeURIComponent(file.fullpath)}&size=${size}`
function usePreview () {
const previewIdx = ref(-1)
const previewing = ref(false)
let waitScrollTo = null as number | null
const onPreviewVisibleChange = (v: boolean, lv: boolean) => {
previewing.value = v
if (waitScrollTo != null && !v && lv) {//
scroller.value?.scrollToItem(waitScrollTo)
waitScrollTo = null
}
}
useWatchDocument('keydown', e => {
if (previewing.value) {
let next = previewIdx.value
if (['ArrowDown', 'ArrowRight'].includes(e.key)) {
next++
while (sortedFiles.value[next] && !isImageFile(sortedFiles.value[next].name)) {
next++
}
} else if (['ArrowUp', 'ArrowLeft'].includes(e.key)) {
next--
while (sortedFiles.value[next] && !isImageFile(sortedFiles.value[next].name)) {
next--
}
}
if (isImageFile(sortedFiles.value[next]?.name) ?? '') {
previewIdx.value = next
const s = scroller.value
if (s && !(next >= s.$_startIndex && next <= s.$_endIndex)) {
waitScrollTo = next //
}
}
}
})
const previewImgMove = (type: 'next' | 'prev') => {
let next = previewIdx.value
if (type === 'next') {
next++
while (sortedFiles.value[next] && !isImageFile(sortedFiles.value[next].name)) {
next++
}
} else if (type === 'prev') {
next--
while (sortedFiles.value[next] && !isImageFile(sortedFiles.value[next].name)) {
next--
}
}
if (isImageFile(sortedFiles.value[next]?.name) ?? '') {
previewIdx.value = next
const s = scroller.value
if (s && !(next >= s.$_startIndex && next <= s.$_endIndex)) {
waitScrollTo = next //
}
}
}
const canPreview = (type: 'next' | 'prev') => {
let next = previewIdx.value
if (type === 'next') {
next++
while (sortedFiles.value[next] && !isImageFile(sortedFiles.value[next].name)) {
next++
}
} else if (type === 'prev') {
next--
while (sortedFiles.value[next] && !isImageFile(sortedFiles.value[next].name)) {
next--
}
} return isImageFile(sortedFiles.value[next]?.name) ?? ''
}
return {
previewIdx,
onPreviewVisibleChange,
previewing,
previewImgMove,
canPreview
}
}
function useFilesDisplay () {
const moreActionsDropdownShow = ref(false)
const viewMode = ref<ViewMode>('line')
const viewMode = ref<ViewMode>('grid')
const viewModeMap: Record<ViewMode, string> = { line: '详情列表', 'grid': '预览网格', 'large-size-grid': '大尺寸预览网格' }
const sortMethodConv: SearchSelectConv<SortMethod> = {
value: (v) => v,
@ -79,7 +156,7 @@ function useFilesDisplay () {
function useLocation () {
const scroller = ref<any>()
const scroller = ref<{ $_startIndex: number, $_endIndex: number, scrollToItem (idx: number): void }>()
const np = ref<Progress.NProgress>()
const currPage = computed(() => last(stack.value))
const stack = ref<Page[]>([])
@ -87,7 +164,7 @@ function useLocation () {
watch(() => stack.value.length, debounce((v, lv) => {
if (v !== lv) {
scroller.value.scrollToItem(0)
scroller.value!.scrollToItem(0)
}
}, 300))
@ -282,6 +359,7 @@ function useFileItemActions () {
const onFileItemClick = async (e: MouseEvent, file: FileNodeInfo) => {
const files = sortedFiles.value
const idx = files.findIndex(v => v.name === file.name)
previewIdx.value = idx
if (e.shiftKey) {
multiSelectedIdxs.value.push(idx)
multiSelectedIdxs.value.sort((a, b) => a - b)
@ -342,10 +420,10 @@ function useFileItemActions () {
<div ref="el" @dragover.prevent @drop.prevent="onDrop($event)" class="container">
<AModal v-model:visible="showGenInfo" width="50vw">
<ASkeleton active :loading="!q.isIdle">
<pre style="width: 100%; word-break: break-all;white-space: pre-line;">
{{ imageGenInfo }}
</pre>
<pre style="width: 100%; word-break: break-all;white-space: pre-line;" @dblclick="copy2clipboard(imageGenInfo)">
双击复制
{{ imageGenInfo }}
</pre>
</ASkeleton>
</AModal>
<div class="location-bar">
@ -413,10 +491,11 @@ function useFileItemActions () {
:class="{ clickable: file.type === 'dir', selected: multiSelectedIdxs.includes(idx), grid: viewMode === 'grid', 'large-grid': viewMode === 'large-size-grid' }"
:key="file.name" draggable="true" @dragstart="onFileDragStart($event, idx)"
@click.capture="onFileItemClick($event, file)">
<a-image :key="file.fullpath"
<a-image ref="dd" :key="file.fullpath" :class="`idx-${idx}`"
v-if="props.target === 'local' && viewMode !== 'line' && isImageFile(file.name)"
:src="global.enableThumbnail ? toImageThumbnailUrl(file, viewMode === 'grid' ? void 0 : '512,512') : toRawFileUrl(file)"
:fallback="fallbackImage" :preview="{ src: toRawFileUrl(file) }">
:fallback="fallbackImage"
:preview="{ src: toRawFileUrl(sortedFiles[previewIdx]), onVisibleChange: onPreviewVisibleChange }">
</a-image>
<template v-else>
<file-outlined class="icon" v-if="file.type === 'file'" />
@ -451,10 +530,40 @@ function useFileItemActions () {
</a-dropdown>
</template>
</RecycleScroller>
<div v-if="previewing" class="preview-switch">
<LeftCircleOutlined @click="previewImgMove('prev')" :class="{ 'disable': !canPreview('prev') }" />
<RightCircleOutlined @click="previewImgMove('next')" :class="{ 'disable': !canPreview('next') }" />
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.preview-switch {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 11111;
pointer-events: none;
&>* {
margin: 16px;
font-size: 4em;
pointer-events: all;
cursor: pointer;
&.disable {
opacity: 0;
pointer-events: none;
cursor: none;
}
}
}
.container {
height: 100%;
}

View File

@ -733,10 +733,10 @@ ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
ant-design-vue@^3.2.15:
version "3.2.15"
resolved "https://registry.yarnpkg.com/ant-design-vue/-/ant-design-vue-3.2.15.tgz#eab52877fa08a9e4c8cb311ea479a90203dcb302"
integrity sha512-sJfE7LWimSdAPe4dzNyQBrmVMnOTNQTkG9oOyr+7W8qIYrX8sYWyC68Nn1uum4KBJUSZUa/BU6dohvTG0urBhA==
ant-design-vue@^3.2.16:
version "3.2.16"
resolved "https://registry.yarnpkg.com/ant-design-vue/-/ant-design-vue-3.2.16.tgz#d79209537f93d6b87edff712b9cd50fbbea128bc"
integrity sha512-kBGxk4csoEi2iaWO62DpNECTnBLIf/CNYW8RdNjLPWo6TBWLQNqLchxRcg8KatOkDRpdWRaqdqeD5P+F6MDC3Q==
dependencies:
"@ant-design/colors" "^6.0.0"
"@ant-design/icons-vue" "^6.1.0"