mirror of
https://github.com/OpenListTeam/OpenList-Frontend.git
synced 2026-03-13 11:20:24 +00:00
feat(preview): add support preview in the archive (#329)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { objStore, selectedObjs, State, me } from "~/store"
|
||||
import { Obj } from "~/types"
|
||||
import { Obj, ArchiveObj } from "~/types"
|
||||
import {
|
||||
base_path,
|
||||
api,
|
||||
@@ -36,6 +36,12 @@ export const getLinkByDirAndObj = (
|
||||
if (!api.startsWith(location.origin + base_path))
|
||||
host = location.origin + base_path
|
||||
}
|
||||
const { inner_path, archive } = obj as ArchiveObj
|
||||
if (archive) {
|
||||
prefix = "/ae"
|
||||
path = `${dir}/${archive.name}`
|
||||
path = encodePath(path, encodeAll)
|
||||
}
|
||||
let ans = `${host}${prefix}${path}`
|
||||
if (type !== "preview" && !isShare && obj.sign) {
|
||||
ans += `?sign=${obj.sign}`
|
||||
@@ -46,6 +52,10 @@ export const getLinkByDirAndObj = (
|
||||
ans += `?pwd=${pwd}`
|
||||
}
|
||||
}
|
||||
if (archive) {
|
||||
let inner = `${inner_path}/${obj.name}`
|
||||
ans += `${ans.includes("?") ? "&" : "?"}inner=${encodePath(inner, encodeAll)}`
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
|
||||
@@ -20,16 +20,35 @@ import {
|
||||
Match,
|
||||
Show,
|
||||
Switch,
|
||||
Suspense,
|
||||
onCleanup,
|
||||
} from "solid-js"
|
||||
import { getMainColor, local, me, OrderBy, password } from "~/store"
|
||||
import { Obj, ObjTree, UserMethods, UserPermissions } from "~/types"
|
||||
import { useFetch, useRouter, useT, useUtil } from "~/hooks"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import {
|
||||
getMainColor,
|
||||
local,
|
||||
me,
|
||||
OrderBy,
|
||||
password,
|
||||
objStore,
|
||||
ObjStore,
|
||||
} from "~/store"
|
||||
import {
|
||||
Obj,
|
||||
ObjTree,
|
||||
UserMethods,
|
||||
UserPermissions,
|
||||
ObjType,
|
||||
ArchiveObj,
|
||||
} from "~/types"
|
||||
import { useFetch, useRouter, useT, useUtil, useLink } from "~/hooks"
|
||||
import { ListTitle } from "~/pages/home/folder/List"
|
||||
import { cols } from "~/pages/home/folder/ListItem"
|
||||
import { Error, MaybeLoading } from "~/components"
|
||||
import { Error, MaybeLoading, FullLoading, SelectWrapper } from "~/components"
|
||||
import { OpenWith } from "../file/open-with"
|
||||
import { getPreviews } from "."
|
||||
import {
|
||||
bus,
|
||||
encodePath,
|
||||
formatDate,
|
||||
fsArchiveList,
|
||||
fsArchiveMeta,
|
||||
@@ -58,6 +77,7 @@ type ListItemProps = {
|
||||
innerPath: string
|
||||
url?: string
|
||||
pass: string
|
||||
onFileClick?: () => void
|
||||
}
|
||||
|
||||
const ListItem = (props: ListItemProps) => {
|
||||
@@ -88,8 +108,8 @@ const ListItem = (props: ListItemProps) => {
|
||||
on:click={(_: MouseEvent) => {
|
||||
if (props.obj.is_dir) {
|
||||
props.jumpCallback()
|
||||
} else if (props.url) {
|
||||
download(props.url)
|
||||
} else if (!props.obj.is_dir && props.onFileClick) {
|
||||
props.onFileClick()
|
||||
}
|
||||
}}
|
||||
onContextMenu={(e: MouseEvent) => {
|
||||
@@ -219,6 +239,7 @@ type List = {
|
||||
const Preview = () => {
|
||||
const t = useT()
|
||||
const { pathname } = useRouter()
|
||||
const { rawLink } = useLink()
|
||||
const [metaLoading, fetchMeta] = useFetch(fsArchiveMeta)
|
||||
const [listLoading, fetchList] = useFetch(fsArchiveList)
|
||||
const loading = createMemo(() => {
|
||||
@@ -238,6 +259,8 @@ const Preview = () => {
|
||||
const [extractFolder, setExtractFolder] = createSignal<"" | "front" | "back">(
|
||||
"",
|
||||
)
|
||||
const [selectedFile, setSelectedFile] = createSignal<string>("")
|
||||
const [selectedPreviewName, setSelectedPreviewName] = createSignal("")
|
||||
const getObjsMutex = createMutex()
|
||||
const toList = (tree: ObjTree[] | Obj[]): List => {
|
||||
let l: List = {}
|
||||
@@ -361,6 +384,20 @@ const Preview = () => {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
// Build inner file url for current path by filename
|
||||
const buildInnerUrl = (name: string) => {
|
||||
const innerPath =
|
||||
(innerPaths().length > 0 ? "/" + innerPaths().join("/") : "") + "/" + name
|
||||
return innerPath
|
||||
}
|
||||
// Build obj with inner property
|
||||
const buildObjWithInner = (obj: Obj): ArchiveObj => {
|
||||
const innerPath =
|
||||
innerPaths().length > 0 ? "/" + innerPaths().join("/") : ""
|
||||
|
||||
return { ...obj, sign: sign, inner_path: innerPath, archive: originalObj }
|
||||
}
|
||||
|
||||
const sortObjs = (orderBy: OrderBy, reverse?: boolean) => {
|
||||
batch(() => {
|
||||
setExtractFolder("")
|
||||
@@ -370,13 +407,79 @@ const Preview = () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Get all files for navigation
|
||||
const files = createMemo(() =>
|
||||
sortedObjs()
|
||||
.filter((obj) => !obj.is_dir)
|
||||
.map((f) => buildObjWithInner(f)),
|
||||
)
|
||||
|
||||
const previews = createMemo(() => {
|
||||
const file = files().find((f) => f.name === selectedFile())
|
||||
if (!file) return []
|
||||
|
||||
return getPreviews({ ...file, provider: objStore.provider })
|
||||
})
|
||||
|
||||
const currentPreview = createMemo(() => {
|
||||
const p = previews()
|
||||
if (p.length === 0) return null
|
||||
if (selectedPreviewName()) {
|
||||
const found = p.find((item) => item.name === selectedPreviewName())
|
||||
if (found) return found
|
||||
}
|
||||
return p[0]
|
||||
})
|
||||
|
||||
// Cast to ArchiveObj to make sure onCleanup can delete archive property correctly
|
||||
const originalObj: ArchiveObj = {
|
||||
...objStore.obj,
|
||||
inner_path: undefined,
|
||||
archive: undefined,
|
||||
}
|
||||
const originalRawUrl = objStore.raw_url
|
||||
|
||||
const changeFile = (name: string) => {
|
||||
batch(() => {
|
||||
if (name === "") {
|
||||
// Restore
|
||||
ObjStore.setObj(originalObj)
|
||||
ObjStore.setRawUrl(originalRawUrl)
|
||||
setSelectedFile("")
|
||||
} else {
|
||||
// Set new
|
||||
const file = files().find((f) => f.name === name)
|
||||
if (file) {
|
||||
const innerUrl = rawLink(file)
|
||||
ObjStore.setObj(file)
|
||||
ObjStore.setRawUrl(innerUrl)
|
||||
setSelectedFile(name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onCleanup(() => {
|
||||
// Restore original values
|
||||
ObjStore.setObj(originalObj)
|
||||
ObjStore.setRawUrl(originalRawUrl)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
selectedFile()
|
||||
setSelectedPreviewName("")
|
||||
})
|
||||
return (
|
||||
<VStack spacing="$2" w="$full">
|
||||
<Breadcrumb pl="$2" pr="$2" w="$full">
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink
|
||||
currentPage={innerPaths().length === 0}
|
||||
on:click={() => setInnerPaths([])}
|
||||
currentPage={innerPaths().length === 0 && !selectedFile()}
|
||||
on:click={() => {
|
||||
setInnerPaths([])
|
||||
changeFile("")
|
||||
}}
|
||||
>
|
||||
.
|
||||
</BreadcrumbLink>
|
||||
@@ -386,14 +489,23 @@ const Preview = () => {
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbLink
|
||||
currentPage={innerPaths().length === i() + 1}
|
||||
on:click={() => setInnerPaths(innerPaths().slice(0, i() + 1))}
|
||||
currentPage={innerPaths().length === i() + 1 && !selectedFile()}
|
||||
on:click={() => {
|
||||
setInnerPaths(innerPaths().slice(0, i() + 1))
|
||||
changeFile("")
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
)}
|
||||
</For>
|
||||
<Show when={selectedFile()}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
<BreadcrumbLink currentPage={true}>{selectedFile()}</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</Show>
|
||||
</Breadcrumb>
|
||||
<Switch>
|
||||
<Match when={error() !== ""}>
|
||||
@@ -414,47 +526,75 @@ const Preview = () => {
|
||||
</Password>
|
||||
</Match>
|
||||
<Match when={!requiringPassword() && error() === ""}>
|
||||
<MaybeLoading loading={loading()}>
|
||||
<VStack class="list" w="$full" spacing="$1">
|
||||
<ListTitle sortCallback={sortObjs} disableCheckbox />
|
||||
<For each={sortedObjs()}>
|
||||
{(obj, i) => {
|
||||
let url = undefined
|
||||
let innerPath =
|
||||
(innerPaths().length > 0
|
||||
? "/" + innerPaths().join("/")
|
||||
: "") +
|
||||
"/" +
|
||||
obj.name
|
||||
if (!obj.is_dir) {
|
||||
const hasQuery = raw_url.includes("?")
|
||||
url =
|
||||
raw_url +
|
||||
`${hasQuery ? "&" : "?"}inner=${encodePath(innerPath, true)}`
|
||||
if (archive_pass !== "") {
|
||||
url = url + `&pass=${encodeURIComponent(archive_pass)}`
|
||||
}
|
||||
if (sign !== "") {
|
||||
url = url + `&sign=${sign}`
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ListItem
|
||||
obj={obj}
|
||||
index={i()}
|
||||
jumpCallback={() =>
|
||||
setInnerPaths(innerPaths().concat(obj.name))
|
||||
}
|
||||
innerPath={innerPath}
|
||||
url={url}
|
||||
pass={archive_pass}
|
||||
<Show
|
||||
when={selectedFile()}
|
||||
fallback={
|
||||
<MaybeLoading loading={loading()}>
|
||||
<VStack class="list" w="$full" spacing="$1">
|
||||
<ListTitle sortCallback={sortObjs} disableCheckbox />
|
||||
<For each={sortedObjs()}>
|
||||
{(obj, i) => {
|
||||
const objWithInner = buildObjWithInner(obj)
|
||||
// Use rawLink to construct the URL for the object
|
||||
let url = !obj.is_dir ? rawLink(objWithInner) : undefined
|
||||
let innerPath = buildInnerUrl(obj.name)
|
||||
return (
|
||||
<ListItem
|
||||
obj={obj}
|
||||
index={i()}
|
||||
jumpCallback={() =>
|
||||
setInnerPaths(innerPaths().concat(obj.name))
|
||||
}
|
||||
innerPath={innerPath}
|
||||
url={url}
|
||||
pass={archive_pass}
|
||||
onFileClick={() => changeFile(obj.name)}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
<ContextMenu />
|
||||
</VStack>
|
||||
</MaybeLoading>
|
||||
}
|
||||
>
|
||||
<Show when={selectedFile()} fallback={<FullLoading />}>
|
||||
<VStack w="$full" spacing="$2" alignItems="center">
|
||||
<Show when={currentPreview()}>
|
||||
<Suspense fallback={<FullLoading />}>
|
||||
<Dynamic
|
||||
component={currentPreview()?.component}
|
||||
images={files().filter((f) => f.type === ObjType.IMAGE)}
|
||||
navigate={(name) => {
|
||||
changeFile(name)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
<ContextMenu />
|
||||
</VStack>
|
||||
</MaybeLoading>
|
||||
</Suspense>
|
||||
</Show>
|
||||
<HStack w="$full" justifyContent="center" spacing="$2" p="$2">
|
||||
<Show when={previews().length > 1}>
|
||||
<SelectWrapper
|
||||
value={currentPreview()?.name || ""}
|
||||
onChange={(value) =>
|
||||
setSelectedPreviewName(String(value))
|
||||
}
|
||||
options={previews().map((p) => ({
|
||||
value: p.name,
|
||||
label: p.name,
|
||||
}))}
|
||||
/>
|
||||
</Show>
|
||||
<OpenWith
|
||||
file={{
|
||||
name: selectedFile(),
|
||||
raw_url: objStore.raw_url,
|
||||
d_url: objStore.raw_url,
|
||||
}}
|
||||
/>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Show>
|
||||
</Show>
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={comment() !== ""}>
|
||||
|
||||
@@ -1,22 +1,50 @@
|
||||
import { Error, FullLoading, ImageWithError } from "~/components"
|
||||
import { useRouter, useT } from "~/hooks"
|
||||
import { objStore } from "~/store"
|
||||
import { ObjType } from "~/types"
|
||||
import { Obj, ObjType } from "~/types"
|
||||
import { onCleanup, onMount } from "solid-js"
|
||||
|
||||
const Preview = () => {
|
||||
interface PreviewProps {
|
||||
images?: Obj[]
|
||||
navigate?: (name: string) => void
|
||||
}
|
||||
|
||||
const Preview = (props: PreviewProps) => {
|
||||
const t = useT()
|
||||
const { replace } = useRouter()
|
||||
let images = objStore.objs.filter((obj) => obj.type === ObjType.IMAGE)
|
||||
let images =
|
||||
props.images || objStore.objs.filter((obj) => obj.type === ObjType.IMAGE)
|
||||
if (images.length === 0) {
|
||||
images = [objStore.obj]
|
||||
}
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
|
||||
const prev = () => {
|
||||
const index = images.findIndex((f) => f.name === objStore.obj.name)
|
||||
if (e.key === "ArrowLeft" && index > 0) {
|
||||
replace(images[index - 1].name)
|
||||
} else if (e.key === "ArrowRight" && index < images.length - 1) {
|
||||
replace(images[index + 1].name)
|
||||
if (index > 0) {
|
||||
if (props.navigate) {
|
||||
props.navigate(images[index - 1].name)
|
||||
} else {
|
||||
replace(images[index - 1].name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const next = () => {
|
||||
const index = images.findIndex((f) => f.name === objStore.obj.name)
|
||||
if (index < images.length - 1) {
|
||||
if (props.navigate) {
|
||||
props.navigate(images[index + 1].name)
|
||||
} else {
|
||||
replace(images[index + 1].name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onKeydown = (e: KeyboardEvent) => {
|
||||
if (e.key === "ArrowLeft") {
|
||||
prev()
|
||||
} else if (e.key === "ArrowRight") {
|
||||
next()
|
||||
}
|
||||
}
|
||||
onMount(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, lazy } from "solid-js"
|
||||
import { getIframePreviews, me, getSettingBool, isArchive } from "~/store"
|
||||
import { Obj, ObjType, UserMethods, UserPermissions } from "~/types"
|
||||
import { Obj, ObjType, UserMethods, UserPermissions, ArchiveObj } from "~/types"
|
||||
import { ext } from "~/utils"
|
||||
import { generateIframePreview } from "./iframe"
|
||||
import { useRouter } from "~/hooks"
|
||||
@@ -34,6 +34,7 @@ export interface Preview {
|
||||
provider?: RegExp
|
||||
component: Component
|
||||
prior: Prior
|
||||
availableInArchive?: boolean
|
||||
}
|
||||
|
||||
export type PreviewComponent = Pick<Preview, "name" | "component">
|
||||
@@ -82,6 +83,7 @@ const previews: Preview[] = [
|
||||
exts: ["url"],
|
||||
component: lazy(() => import("./text-editor")),
|
||||
prior: true,
|
||||
availableInArchive: false,
|
||||
},
|
||||
{
|
||||
name: "Image",
|
||||
@@ -172,6 +174,7 @@ const previews: Preview[] = [
|
||||
!getSettingBool("share_preview_download_by_default"))
|
||||
)
|
||||
},
|
||||
availableInArchive: false,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -186,6 +189,7 @@ export const getPreviews = (
|
||||
const downloadPrior =
|
||||
(!isShare() && getSettingBool("preview_download_by_default")) ||
|
||||
(isShare() && getSettingBool("share_preview_download_by_default"))
|
||||
const isInArchive = !!(file as ArchiveObj).archive
|
||||
// internal previews
|
||||
if (!isShare() || getSettingBool("share_preview")) {
|
||||
previews.forEach((preview) => {
|
||||
@@ -198,6 +202,10 @@ export const getPreviews = (
|
||||
extsContains(preview.exts, file.name)
|
||||
) {
|
||||
const r = { name: preview.name, component: preview.component }
|
||||
// Skip previews that are not available in archive when file is in archive
|
||||
if (isInArchive && preview.availableInArchive === false) {
|
||||
return
|
||||
}
|
||||
if (!downloadPrior && isPrior(preview.prior)) {
|
||||
res.push(r)
|
||||
} else {
|
||||
|
||||
@@ -26,6 +26,11 @@ export type StoreObj = Obj & {
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
export type ArchiveObj = Obj & {
|
||||
inner_path?: string
|
||||
archive?: Obj
|
||||
}
|
||||
|
||||
export type RenameObj = {
|
||||
src_name: string
|
||||
new_name: string
|
||||
|
||||
Reference in New Issue
Block a user