Merge pull request #2 from zanllp/feature/path-refresh-and-sorting

支持刷新当前路径下的文件,支持多种排序方法
pull/3/head
zanllp 2023-03-21 01:22:14 +08:00 committed by GitHub
commit e825f7ec50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 182 additions and 70 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-f6acfa37.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-2ba4dd52.css">
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-3277bb8e.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-241ec83f.css">
</head>
<body>
<div id="zanllp_dev_gradio_fe"></div>

View File

@ -1,4 +1,5 @@
import os
import time
from scripts.tool import human_readable_size
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
@ -25,7 +26,7 @@ from scripts.bin import (
bin_file_name,
is_win,
)
from scripts.tool import get_windows_drives
from scripts.tool import get_windows_drives, convert_to_bytes
import functools
from scripts.logger import logger
@ -78,11 +79,14 @@ def list_file(cwd="/"):
match = re.match(pattern, line)
if match:
name = match.group(4).strip()
f_type = "dir" if name.endswith("/") else "file"
size = match.group(2)
file_info = {
"size": match.group(2),
# "date": match.group(3),
"size": size,
"date": match.group(3),
"name": name.strip("/"),
"type": "dir" if name.endswith("/") else "file",
"type": f_type,
"bytes": convert_to_bytes(size) if size != "-" else size
}
files.append(file_info)
return files
@ -292,11 +296,26 @@ def baidu_netdisk_api(_: gr.Blocks, app: FastAPI):
else:
for item in os.listdir(folder_path):
path = os.path.join(folder_path, item)
mod_time = os.path.getmtime(path)
date = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(mod_time)
)
if os.path.isfile(path):
size = human_readable_size(os.path.getsize(path))
files.append({"type": "file", "size": size, "name": item})
bytes = os.path.getsize(path)
size = human_readable_size(bytes)
files.append(
{
"type": "file",
"date": date,
"size": size,
"name": item,
"bytes": bytes,
}
)
elif os.path.isdir(path):
files.append({"type": "dir", "size": "-", "name": item})
files.append(
{"type": "dir", "date": date, "size": "-", "name": item}
)
else:
files = list_file(folder_path)

View File

@ -1,4 +1,5 @@
import os
import re
def human_readable_size(size_bytes):
@ -26,4 +27,23 @@ def get_windows_drives():
drives.append(drive_name)
return drives
pattern = re.compile(r'(\d+\.?\d*)([KMGT]?B)', re.IGNORECASE)
def convert_to_bytes(file_size_str):
match = re.match(pattern, file_size_str)
if match:
size_str, unit_str = match.groups()
size = float(size_str)
unit = unit_str.upper()
if unit == "KB":
size *= 1024
elif unit == "MB":
size *= 1024**2
elif unit == "GB":
size *= 1024**3
elif unit == "TB":
size *= 1024**4
return int(size)
else:
raise ValueError(f"Invalid file size string '{file_size_str}'")
is_dev = "APP_ENV" in os.environ and os.environ["APP_ENV"] == "dev"

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-f6acfa37.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-2ba4dd52.css">
<script type="module" crossorigin src="/baidu_netdisk/fe-static/assets/index-3277bb8e.js"></script>
<link rel="stylesheet" href="/baidu_netdisk/fe-static/assets/index-241ec83f.css">
</head>
<body>
<div id="zanllp_dev_gradio_fe"></div>

View File

@ -3,7 +3,9 @@ import { axiosInst } from '.'
export interface FileNodeInfo {
size: string
type: 'file' | 'dir'
name: string
name: string,
date: string,
bytes: number
}
export const getTargetFolderFiles = async (target: 'local' | 'netdisk' , folder_path: string) => {

View File

@ -5,7 +5,7 @@ import { ref, computed, onMounted, toRaw } from 'vue'
import { FileOutlined, FolderOpenOutlined, DownOutlined } from '@/icon'
import path from 'path-browserify'
import { useGlobalStore } from '@/store/useGlobalStore'
import { copy2clipboard, ok } from 'vue3-ts-util'
import { copy2clipboard, ok, type SearchSelectConv, SearchSelect } from 'vue3-ts-util'
// @ts-ignore
import NProgress from 'multi-nprogress'
import 'multi-nprogress/nprogress.css'
@ -19,34 +19,66 @@ const props = defineProps<{
target: 'local' | 'netdisk'
}>()
interface Page {
files: FileNodeInfo[],
files: FileNodeInfo[]
curr: string
}
const stack = ref<Page[]>([])
const global = useGlobalStore()
const currPage = computed(() => last(stack.value))
type SortMethod = 'date-asc' | 'date-desc' | 'name-asc' | 'name-desc' | 'size-asc' | 'size-desc'
const sortFile = (files: FileNodeInfo[]) => {
return files.sort((a, b) => {
const sa = a.type === 'dir' ? 1 : 0
const sb = b.type === 'dir' ? 1 : 0
return sb - sa
})
const sortMethodMap: Record<SortMethod, string> = {
'date-asc': '日期升序',
'date-desc': '日期降序',
'name-asc': '名称升序',
'name-desc': '名称降序',
'size-asc': '大小升序',
'size-desc': '大小降序'
}
const sortMethodConv: SearchSelectConv<SortMethod> = {
value: (v) => v,
text: (v) => '按' + sortMethodMap[v]
}
const sortMethod = ref<SortMethod>('date-desc')
onMounted(async () => {
const resp = await getTargetFolderFiles(props.target, '/')
stack.value.push({
files: sortFile(resp.files),
files: resp.files,
curr: '/'
})
np.value = new NProgress()
np.value!.configure({ parent: el.value as any })
})
const getBasePath = () => stack.value.map(v => v.curr).slice(global.conf?.is_win && props.target === 'local' ? 1 : 0)
const getBasePath = () =>
stack.value.map((v) => v.curr).slice(global.conf?.is_win && props.target === 'local' ? 1 : 0)
const copyLocation = () => copy2clipboard(path.join(...getBasePath()))
const filesSort = (files: FileNodeInfo[]) => {
return files.slice().sort((a, b) => {
const method = sortMethod.value
const sa = a.type === 'dir' ? 1 : 0
const sb = b.type === 'dir' ? 1 : 0
const typeCompare = sb - sa
if (typeCompare !== 0) {
return typeCompare
}
if (method === 'date-asc' || method === 'date-desc') {
const da = Date.parse(a.date)
const db = Date.parse(b.date)
return method === 'date-asc' ? da - db : db - da
} else if (method === 'name-asc' || method == 'name-desc') {
const an = a.name.toLowerCase()
const bn = b.name.toLowerCase()
return method === 'name-asc' ? an.localeCompare(bn) : bn.localeCompare(an)
} else {
return method === 'size-asc' ? a.bytes - b.bytes : b.bytes - a.bytes
}
})
}
const openNext = async (file: FileNodeInfo) => {
if (file.type !== 'dir') {
return
@ -54,9 +86,12 @@ const openNext = async (file: FileNodeInfo) => {
try {
np.value?.start()
const prev = getBasePath()
const { files } = await getTargetFolderFiles(props.target, path.normalize(path.join(...prev, file.name)))
const { files } = await getTargetFolderFiles(
props.target,
path.normalize(path.join(...prev, file.name))
)
stack.value.push({
files: sortFile(files),
files,
curr: file.name
})
} finally {
@ -71,7 +106,8 @@ const back = (idx: number) => {
}
const to = async (dir: string) => {
if (!/^((\w:)|\/)/.test(dir)) { //
if (!/^((\w:)|\/)/.test(dir)) {
//
dir = path.join(global.conf?.sd_cwd ?? '/', dir)
}
const frags = dir.split(/\\|\//)
@ -82,14 +118,17 @@ const to = async (dir: string) => {
}
back(0) //
for (const frag of frags) {
const target = currPage.value?.files.find(v => v.name === frag)
const target = currPage.value?.files.find((v) => v.name === frag)
ok(target)
await openNext(target)
}
}
const onDrop = async (e: DragEvent) => {
const data = JSON.parse(e.dataTransfer?.getData("text") || '{}') as FileNodeInfo & { from: typeof props.target, path: string }
const data = JSON.parse(e.dataTransfer?.getData('text') || '{}') as FileNodeInfo & {
from: typeof props.target
path: string
}
if (data.from && data.path && data.type) {
if (data.from === props.target) {
return
@ -99,22 +138,42 @@ const onDrop = async (e: DragEvent) => {
const toPath = path.join(...getBasePath())
Modal.confirm({
title: `确定创建${typeZH}任务${data.type === 'dir' ? ', 这是文件夹!' : ''}`,
content: `${props.target !== 'local' ? '本地' : '云盘'} ${data.path} ${typeZH} ${props.target === 'local' ? '本地' : '云盘'} ${toPath}`,
content: `${props.target !== 'local' ? '本地' : '云盘'} ${data.path} ${typeZH} ${props.target === 'local' ? '本地' : '云盘'
} ${toPath}`,
maskClosable: true,
async onOk () {
global.eventEmitter.emit('createNewTask', { send_dirs: data.path, recv_dir: toPath, type })
},
}
})
}
}
const refresh = async () => {
if (stack.value.length === 1) {
const resp = await getTargetFolderFiles(props.target, '/')
stack.value = [
{
files: resp.files,
curr: '/'
}
]
} else {
const last = currPage.value
stack.value.pop()
await openNext(currPage.value?.files.find((v) => v.name === last?.curr)!)
}
}
</script>
<template>
<div ref="el" @dragover.prevent @drop.prevent="onDrop($event)" class="container">
<div class="location">
<a-breadcrumb style="flex: 1;">
<a-breadcrumb-item v-for="item, idx in stack" :key="idx"><a @click.prevent="back(idx)">{{ item.curr === "/" ? "根"
: item.curr.replace(/:\/$/, '盘') }}</a></a-breadcrumb-item>
<a-breadcrumb style="flex: 1">
<a-breadcrumb-item v-for="(item, idx) in stack" :key="idx"><a @click.prevent="back(idx)">{{
item.curr === '/' ? '根' : item.curr.replace(/:\/$/, '盘')
}}</a></a-breadcrumb-item>
</a-breadcrumb>
<SearchSelect v-model:value="sortMethod" :conv="sortMethodConv" :options="Object.keys(sortMethodMap)" />
<a class="opt" @click.prevent="refresh"> 刷新 </a>
<a-dropdown v-if="props.target === 'local'">
<a class="ant-dropdown-link opt" @click.prevent>
快速移动
@ -132,17 +191,29 @@ const onDrop = async (e: DragEvent) => {
</div>
<div v-if="currPage" class="view">
<ul class="file-list">
<li class="file" v-for="file in currPage.files" :class="{ 'clickable': file.type === 'dir' }" :key="file.name"
draggable="true"
@dragstart="$event.dataTransfer!.setData('text/plain', JSON.stringify({ from: props.target, path: path.join(...getBasePath(), file.name), ...toRaw(file) }))"
@click="openNext(file)">
<li class="file" v-for="file in filesSort(currPage.files)" :class="{ clickable: file.type === 'dir' }"
:key="file.name" draggable="true" @dragstart="
$event.dataTransfer!.setData(
'text/plain',
JSON.stringify({
from: props.target,
path: path.join(...getBasePath(), file.name),
...toRaw(file)
})
)
" @click="openNext(file)">
<file-outlined v-if="file.type === 'file'" />
<folder-open-outlined v-else />
<div class="name">
{{ file.name }}
</div>
<div class="size">
{{ file.size }}
<div class="basic-info">
<div>
{{ file.size }}
</div>
<div>
{{ file.date }}
</div>
</div>
</li>
</ul>
@ -168,15 +239,12 @@ const onDrop = async (e: DragEvent) => {
.view {
padding: 8px;
.file-list {
list-style: none;
padding: 8px;
height: 900px;
overflow: auto;
.file {
padding: 8px 16px;
margin: 8px;
@ -195,9 +263,12 @@ const onDrop = async (e: DragEvent) => {
padding: 8px;
}
.size {}
.basic-info {
display: flex;
flex-direction: column;
align-items: flex-end;
}
}
}
}
</style>
</style>