Skip to content

Commit a5ee87e

Browse files
author
hjy
committed
fix
1 parent acfc04d commit a5ee87e

File tree

5 files changed

+43
-69
lines changed

5 files changed

+43
-69
lines changed

frontend/src/components/Files/FileViewContainer.tsx

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { FileGridView } from "./FileGridView"
4343
import { FileIcon } from "./FileIcon"
4444
import { FileTableView, type SortField, type SortOrder } from "./FileTableView"
4545

46-
type ViewMode = "grid" | "table" | "mixed"
46+
export type ViewMode = "grid" | "table" | "mixed"
4747

4848
type PaginationState = {
4949
page: number
@@ -69,7 +69,6 @@ export function FileViewContainer({
6969
onSortFieldChange,
7070
onSortOrderChange,
7171
pagination,
72-
storageKeyPrefix = "file-list",
7372
toolbarExtra,
7473
emptyText = "This folder is empty",
7574
}: {
@@ -84,45 +83,20 @@ export function FileViewContainer({
8483
onSortFieldChange?: (field: SortField) => void
8584
onSortOrderChange?: (order: SortOrder) => void
8685
pagination?: PaginationConfig
87-
storageKeyPrefix?: string
8886
toolbarExtra?: ReactNode
8987
emptyText?: string
9088
}) {
9189
const containerRef = useRef<HTMLDivElement>(null)
9290
const { t } = useTranslation()
9391

94-
// View mode & sort state
95-
const [viewMode, setViewMode] = useState<ViewMode>(() => {
96-
const saved = localStorage.getItem(`${storageKeyPrefix}-view-mode`)
97-
if (saved === "details") return "table"
98-
return (saved as ViewMode) || initialViewMode
99-
})
100-
const [internalSortField, setInternalSortField] = useState<SortField>(() => {
101-
const saved = localStorage.getItem(`${storageKeyPrefix}-sort-field`)
102-
return (saved as SortField) || initialSortField
103-
})
104-
const [internalSortOrder, setInternalSortOrder] = useState<SortOrder>(() => {
105-
const saved = localStorage.getItem(`${storageKeyPrefix}-sort-order`)
106-
return (saved as SortOrder) || initialSortOrder
107-
})
92+
// View mode & sort state — URL params / props are the sole source of truth
93+
const [viewMode, setViewMode] = useState<ViewMode>(initialViewMode)
94+
const [internalSortField, setInternalSortField] = useState<SortField>(initialSortField)
95+
const [internalSortOrder, setInternalSortOrder] = useState<SortOrder>(initialSortOrder)
10896

10997
const sortField = controlledSortField ?? internalSortField
11098
const sortOrder = controlledSortOrder ?? internalSortOrder
11199

112-
useEffect(() => {
113-
localStorage.setItem(`${storageKeyPrefix}-view-mode`, viewMode)
114-
}, [storageKeyPrefix, viewMode])
115-
useEffect(() => {
116-
if (controlledSortField === undefined) {
117-
localStorage.setItem(`${storageKeyPrefix}-sort-field`, internalSortField)
118-
}
119-
}, [storageKeyPrefix, internalSortField, controlledSortField])
120-
useEffect(() => {
121-
if (controlledSortOrder === undefined) {
122-
localStorage.setItem(`${storageKeyPrefix}-sort-order`, internalSortOrder)
123-
}
124-
}, [storageKeyPrefix, internalSortOrder, controlledSortOrder])
125-
126100
const setSortField = useCallback(
127101
(field: SortField) => {
128102
if (onSortFieldChange) {

frontend/src/routes/_layout/explorer.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
getParentPath,
3131
} from "@/lib/path-utils"
3232
import type { SortField, SortOrder } from "@/components/Files/FileTableView"
33+
import type { ViewMode } from "@/components/Files/FileViewContainer"
3334
import "./explorer.css"
3435

3536
export const Route = createFileRoute("/_layout/explorer")({
@@ -51,8 +52,10 @@ export const Route = createFileRoute("/_layout/explorer")({
5152
"last_read_at",
5253
]
5354
const sortOrderCandidates: SortOrder[] = ["asc", "desc"]
55+
const viewModeCandidates: ViewMode[] = ["grid", "table", "mixed"]
5456
const rawSortField = String(search.sortField || "")
5557
const rawSortOrder = String(search.sortOrder || "")
58+
const rawViewMode = String(search.viewMode || "")
5659

5760
return {
5861
path: (search.path as string) || "",
@@ -64,6 +67,9 @@ export const Route = createFileRoute("/_layout/explorer")({
6467
sortOrder: sortOrderCandidates.includes(rawSortOrder as SortOrder)
6568
? (rawSortOrder as SortOrder)
6669
: "desc",
70+
viewMode: viewModeCandidates.includes(rawViewMode as ViewMode)
71+
? (rawViewMode as ViewMode)
72+
: undefined,
6773
}
6874
},
6975
head: () => ({
@@ -98,7 +104,7 @@ function FilterCheckbox({ id, label, checked, onChange }: FilterCheckboxProps) {
98104
function Explorer() {
99105
const { t } = useTranslation()
100106
const navigate = useNavigate()
101-
const { path, page, pageSize, sortField, sortOrder } = Route.useSearch()
107+
const { path, page, pageSize, sortField, sortOrder, viewMode } = Route.useSearch()
102108
const folderName = path
103109
? getBaseName(path, t("nav.explorer"))
104110
: t("nav.explorer")
@@ -247,7 +253,7 @@ function Explorer() {
247253
items={filteredItems}
248254
isLoading={isLoading}
249255
currentPath={path}
250-
initialViewMode="mixed"
256+
initialViewMode={viewMode ?? "mixed"}
251257
sortField={sortField}
252258
sortOrder={sortOrder}
253259
onSortFieldChange={(nextSortField) =>
@@ -274,7 +280,6 @@ function Explorer() {
274280
replace: true,
275281
}),
276282
}}
277-
storageKeyPrefix="explorer"
278283
toolbarExtra={
279284
<>
280285
{/* 压缩包内容过滤:只显示含视频/音频的 zip */}

frontend/src/routes/_layout/read.tsx

Lines changed: 24 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/**
2-
* 图片阅读器 - 支持压缩包和文件夹图片浏览,带缩放、旋转、拖拽功能
3-
*/
2+
* 图片阅读�- 支持压缩包和文件夹图片浏览,带缩放、旋转、拖拽功� */
43
import { useMutation } from "@/shims/react-query"
54
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"
65
import {
@@ -64,10 +63,7 @@ export const Route = createFileRoute("/_layout/read")({
6463
path: (search.path as string) || "",
6564
page: Number(search.page) || 0,
6665
source: (search.source as "archive" | "folder") || "archive",
67-
// sourceFolderPath: 仅 source=folder 时有效。
68-
// 用于从外部(如 explorer 点击某张图片)跳转到阅读器时定位到特定图片。
69-
// 解析后会被 replace 成对应的 page 数字,之后 sourceFolderPath 置空。
70-
sourceFolderPath: (search.sourceFolderPath as string) || "",
66+
// sourceFolderPath: �source=folder 时有效� // 用于从外部(�explorer 点击某张图片)跳转到阅读器时定位到特定图片� // 解析后会�replace 成对应的 page 数字,之�sourceFolderPath 置空� sourceFolderPath: (search.sourceFolderPath as string) || "",
7167
mode: (search.mode as "audio") || undefined,
7268
}),
7369
head: () => ({
@@ -168,7 +164,7 @@ function ReadPage() {
168164
if (cancelled) return
169165
setFolderData(currentFolderData)
170166
} else {
171-
// archive: 打开时先解压一次,再拉 archive 列表与 parent 列表
167+
// archive: 打开时先解压一次,再拉 archive 列表�parent 列表
172168
setArchiveImageReady(false)
173169
const [extractResult, archiveData, parentData] = await Promise.all([
174170
FilesystemService.extractArchive({ path, page: 0 }),
@@ -235,8 +231,7 @@ function ReadPage() {
235231
}))
236232
}, [isFolderSource, folderData, listData])
237233

238-
// Audio tracks(仅 archive source)
239-
const audioTracks = useMemo(() => {
234+
// Audio tracks(仅 archive source� const audioTracks = useMemo(() => {
240235
if (isFolderSource) return []
241236
return (listData?.entries || [])
242237
.filter((e) => e.file_type === "audio")
@@ -427,7 +422,7 @@ function ReadPage() {
427422
const targetPath = isFolderSource ? path : (extractStatus?.cache_dir || path)
428423
navigate({
429424
to: "/explorer",
430-
search: { path: targetPath, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" },
425+
search: { path: targetPath, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" },
431426
})
432427
} else if (key === "v") {
433428
e.preventDefault()
@@ -499,7 +494,7 @@ function ReadPage() {
499494
const sizeText = currentPathMeta?.filesize
500495
? formatFileSize(currentPathMeta.filesize)
501496
: "-"
502-
// 优先用 extractMutation 实时返回的值,fallback 到父目录列表的 DB 缓存
497+
// 优先�extractMutation 实时返回的值,fallback 到父目录列表�DB 缓存
503498
const avgImageSize = extractStatus?.avg_image_size ?? currentPathMeta?.avg_image_size ?? null
504499
const avgImageSizeText = avgImageSize != null ? formatFileSize(avgImageSize) : "-"
505500
const archiveVideoCount = currentPathMeta?.video_count ?? 0
@@ -508,8 +503,7 @@ function ReadPage() {
508503
const cosers = parseMeta?.cosers ?? []
509504
const tags = parseMeta?.raw_tags ?? []
510505

511-
// 文件被移动后自动跳转新路径
512-
const hasError = loadError
506+
// 文件被移动后自动跳转新路� const hasError = loadError
513507
const { resolving, isNotFound, errorMessage } = useResolveMovedFile(
514508
path,
515509
hasError ? loadError : null,
@@ -571,7 +565,7 @@ function ReadPage() {
571565
showFolderIcon={false}
572566
collapseDirCrumbsAfter={2}
573567
currentTo="/explorer"
574-
currentSearch={{ path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" }}
568+
currentSearch={{ path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" }}
575569
currentLabel={fileName}
576570
currentClassName="reader-toolbar__current-link"
577571
/>
@@ -645,17 +639,16 @@ function ReadPage() {
645639
}
646640

647641
if (!currentEntry) {
648-
// 无图片提示
649-
return (
642+
// 无图片提� return (
650643
<div className="reader-empty-page">
651644
<PathBreadcrumb
652645
sourcePath={path}
653646
homeLabel={t("common.home")}
654647
separatorClassName="size-4 text-muted-foreground"
655648
currentTo="/explorer"
656649
currentSearch={isFolderSource
657-
? { path, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" }
658-
: { path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" }}
650+
? { path, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" }
651+
: { path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" }}
659652
currentLabel={fileName}
660653
/>
661654

@@ -666,7 +659,7 @@ function ReadPage() {
666659
<>
667660
<Link
668661
to="/explorer"
669-
search={{ path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" }}
662+
search={{ path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" }}
670663
className={buttonVariants({
671664
variant: "default",
672665
size: "sm",
@@ -727,15 +720,14 @@ function ReadPage() {
727720
: `${OpenAPI.BASE}/api/v1/fs/archive/file?path=${encodeURIComponent(path)}&entry=${encodeURIComponent(currentEntry.entryPath || "")}`
728721

729722
// 图片加载失败时的重试处理
730-
// 压缩包文件可能还在后台解压中,404 时自动重试(最多 5 次,递增延迟)
731-
const handleImageError = (e: React.SyntheticEvent<HTMLImageElement>) => {
723+
// 压缩包文件可能还在后台解压中�04 时自动重试(最�5 次,递增延迟� const handleImageError = (e: React.SyntheticEvent<HTMLImageElement>) => {
732724
const img = e.currentTarget
733725
setImageLoaded(false)
734726
const retryCount = Number(img.dataset.retry || 0)
735727
const maxRetries = 5
736728
if (retryCount < maxRetries) {
737729
img.dataset.retry = String(retryCount + 1)
738-
// 递增延迟:1s, 2s, 3s, 4s, 5s
730+
// 递增延迟�s, 2s, 3s, 4s, 5s
739731
setTimeout(
740732
() => {
741733
img.src = `${imageUrl}${imageUrl.includes("?") ? "&" : "?"}_t=${Date.now()}`
@@ -745,8 +737,7 @@ function ReadPage() {
745737
}
746738
}
747739

748-
// 图片加载成功时重置重试计数
749-
const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
740+
// 图片加载成功时重置重试计� const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
750741
e.currentTarget.dataset.retry = "0"
751742
setImageLoaded(true)
752743
}
@@ -772,7 +763,7 @@ function ReadPage() {
772763

773764
return (
774765
<div className="reader-page">
775-
{/* ── 顶部工具栏:面包屑导航 + 旋转/全屏/模式切换/文件操作菜单 ── */}
766+
{/* ── 顶部工具栏:面包屑导�+ 旋转/全屏/模式切换/文件操作菜单 ── */}
776767
<nav className="reader-toolbar">
777768
<div className="reader-toolbar__left">
778769
<PathBreadcrumb
@@ -788,14 +779,14 @@ function ReadPage() {
788779
collapseDirCrumbsAfter={2}
789780
currentTo="/explorer"
790781
currentSearch={isFolderSource
791-
? { path, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" }
792-
: { path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" }}
782+
? { path, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" }
783+
: { path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" }}
793784
currentLabel={fileName}
794785
currentClassName="reader-toolbar__current-link"
795786
/>
796787
</div>
797788

798-
{/* 右侧:工具 */}
789+
{/* 右侧:工�*/}
799790
<div className="reader-toolbar__right">
800791
<div className="reader-toolbar__actions">
801792
{/* <Button
@@ -805,7 +796,7 @@ function ReadPage() {
805796
onClick={zoomOut}
806797
title={t("reader.zoomOut")}
807798
>
808-
<span className="reader-toolbar__button-symbol">−</span>
799+
<span className="reader-toolbar__button-symbol">�/span>
809800
</Button>
810801
<Button
811802
variant="ghost"
@@ -859,7 +850,7 @@ function ReadPage() {
859850
<>
860851
<Link
861852
to="/explorer"
862-
search={{ path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "mtime", sortOrder: "desc" }}
853+
search={{ path: extractStatus?.cache_dir || path, page: 1, pageSize: 48, sortField: "name", sortOrder: "asc", viewMode: "table" }}
863854
className={buttonVariants({
864855
variant: "ghost",
865856
size: "sm",
@@ -960,7 +951,7 @@ function ReadPage() {
960951
</div>
961952
</nav>
962953

963-
{/* ── 图片主舞台:支持鼠标拖拽平移、滚轮缩放、左右翻页按钮、解压进度指示 ── */}
954+
{/* ── 图片主舞台:支持鼠标拖拽平移、滚轮缩放、左右翻页按钮、解压进度指�── */}
964955
<div
965956
className="reader-image-stage"
966957
onMouseMove={onMouseMove}
@@ -1020,11 +1011,11 @@ function ReadPage() {
10201011
)}
10211012
</div>
10221013

1023-
{/* ── 底部 meta 栏:文件信息(时间/大小/均图大小/视频音频数)+ 作者/coser/tag 可点击跳搜索 + 页码 ── */}
1014+
{/* ── 底部 meta 栏:文件信息(时�大小/均图大小/视频音频数)+ 作�coser/tag 可点击跳搜索 + 页码 ── */}
10241015
<div className="reader-meta-bar">
10251016
<div className="reader-meta-bar__left">
10261017
<div className="reader-meta-bar__row">
1027-
{/* 文件元数据:hover title 显示 label,只展示值 */}
1018+
{/* 文件元数据:hover title 显示 label,只展示�*/}
10281019
<span title={t("reader.mtime")} className="text-foreground cursor-default">{mtimeText}</span>
10291020
<span title={t("reader.size")} className="text-foreground cursor-default">{sizeText}</span>
10301021
<span title={t("reader.avgImageSize")} className="text-foreground cursor-default">{avgImageSizeText}</span>

frontend/src/routes/_layout/search.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,6 @@ function SearchPage() {
321321
pageSizeOptions: PAGE_SIZE_OPTIONS,
322322
pageSizeLabel: "Page size",
323323
}}
324-
storageKeyPrefix="search"
325324
emptyText={t("search.noResults")}
326325
/>
327326
</div>

todo.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@ audio
106106

107107
--------------
108108
1 启动服务需要删除解压的缓存
109-
需要log actitive和一个简单的log在console。
109+
需要log actitive和一个简单的log在console。
110+
111+
3 后端加压的文件夹是hash过的我没问题。就是现在文件夹名太长了,你缩短一点 10的length也不会冲突的。
112+
113+
2
114+
从reader打开exlorer要是默认table view。用文件名排序。如果explorer支持这mode的url参数,你要追加。

0 commit comments

Comments
 (0)