wip: file preview
This commit is contained in:
parent
6f15f82122
commit
3ff83f12b4
@ -4,6 +4,7 @@ import {makeStyles} from "@fluentui/react-components";
|
|||||||
import {useStoreBucket} from "../../store/bucket";
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
import {ListFileComponent} from "./list_file";
|
import {ListFileComponent} from "./list_file";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
|
import {PreviewFile} from "../preview/preview";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
content: {
|
content: {
|
||||||
@ -18,14 +19,17 @@ const useStyles = makeStyles({
|
|||||||
export function Content() {
|
export function Content() {
|
||||||
|
|
||||||
const styles = useStyles()
|
const styles = useStyles()
|
||||||
|
const [preview, set_preview] = useState<{ url: string, content_type: string }>({url: '', content_type: ''})
|
||||||
const {bucket_active} = useStoreBucket()
|
const {bucket_active} = useStoreBucket()
|
||||||
|
|
||||||
return <div className={styles.content}>
|
return <div className={styles.content}>
|
||||||
<Path/>
|
<Path/>
|
||||||
{
|
{
|
||||||
|
preview.url ? <PreviewFile url={preview.url} content_type={preview.content_type}/> :
|
||||||
|
(
|
||||||
bucket_active ?
|
bucket_active ?
|
||||||
<ListFileComponent/> :
|
<ListFileComponent set_preview_fn={set_preview}/> : <ListBucketComponent/>
|
||||||
<ListBucketComponent/>
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
@ -24,6 +24,7 @@ import {TrimSuffix} from "../../hook/strings";
|
|||||||
import {Dial} from "../../api";
|
import {Dial} from "../../api";
|
||||||
import {useToast} from "../../message";
|
import {useToast} from "../../message";
|
||||||
import {CanPreview} from "../../hook/preview";
|
import {CanPreview} from "../../hook/preview";
|
||||||
|
import {useStorePreview} from "../../store/preview";
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
container: {
|
container: {
|
||||||
@ -87,7 +88,11 @@ const useStyles = makeStyles({
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
export function ListFileComponent() {
|
export interface ListFileComponentProps {
|
||||||
|
set_preview_fn: React.Dispatch<React.SetStateAction<{url:string, content_type: string}>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ListFileComponent(props: ListFileComponentProps) {
|
||||||
|
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const {dispatchMessage} = useToast();
|
const {dispatchMessage} = useToast();
|
||||||
@ -95,6 +100,8 @@ export function ListFileComponent() {
|
|||||||
const {bucket_active} = useStoreBucket()
|
const {bucket_active} = useStoreBucket()
|
||||||
const {file_active, files_get, file_set, files_list} = useStoreFile()
|
const {file_active, files_get, file_set, files_list} = useStoreFile()
|
||||||
const {prefix, filter, prefix_set} = useStoreFileFilter()
|
const {prefix, filter, prefix_set} = useStoreFileFilter()
|
||||||
|
const [preview_content_type, set_preview_content_type] = useState('')
|
||||||
|
const {preview_get} = useStorePreview()
|
||||||
const [ctx_menu, set_ctx_menu] = useState<{
|
const [ctx_menu, set_ctx_menu] = useState<{
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -115,7 +122,7 @@ export function ListFileComponent() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
set_loading(true)
|
set_loading(true)
|
||||||
files_get(conn_active!, bucket_active!, prefix, filter).then(() => {
|
files_get(conn_active!, bucket_active!, prefix, filter).then(() => {
|
||||||
// set_loading(false)
|
set_loading(false)
|
||||||
})
|
})
|
||||||
}, [conn_active, bucket_active, prefix, filter]);
|
}, [conn_active, bucket_active, prefix, filter]);
|
||||||
|
|
||||||
@ -134,6 +141,7 @@ export function ListFileComponent() {
|
|||||||
async function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: S3File) {
|
async function handleRightClick(e: React.MouseEvent<HTMLDivElement>, item: S3File) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
await file_set(item.key)
|
await file_set(item.key)
|
||||||
|
set_preview_content_type(CanPreview(item.name))
|
||||||
const ele = document.querySelector('#list-file-container')
|
const ele = document.querySelector('#list-file-container')
|
||||||
let eleX = ele ? ele.clientWidth : 0
|
let eleX = ele ? ele.clientWidth : 0
|
||||||
let eleY = ele ? ele.clientHeight : 0
|
let eleY = ele ? ele.clientHeight : 0
|
||||||
@ -167,7 +175,16 @@ export function ListFileComponent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handlePreview() {
|
async function handlePreview() {
|
||||||
dispatchMessage('todo', 'warning')
|
const res = await Dial<{url:string, method: string}>('/api/file/get', {
|
||||||
|
conn_id: conn_active?.id,
|
||||||
|
bucket: bucket_active?.name,
|
||||||
|
key: file_active ?? "",
|
||||||
|
})
|
||||||
|
if (res.status !== 200) {
|
||||||
|
dispatchMessage('预览失败', 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
props.set_preview_fn({url: res.data.url, content_type: preview_content_type})
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={styles.container}>
|
return <div className={styles.container}>
|
||||||
@ -183,7 +200,7 @@ export function ListFileComponent() {
|
|||||||
}}
|
}}
|
||||||
icon={<ArrowDownloadFilled/>}>下载</MenuItem>
|
icon={<ArrowDownloadFilled/>}>下载</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
disabled={!CanPreview(file_active ?? '')}
|
disabled={!preview_content_type}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await handlePreview()
|
await handlePreview()
|
||||||
}}
|
}}
|
||||||
@ -194,47 +211,40 @@ export function ListFileComponent() {
|
|||||||
<div className={styles.loading} style={{display: loading ? 'flex' : 'none'}}>
|
<div className={styles.loading} style={{display: loading ? 'flex' : 'none'}}>
|
||||||
<Spinner appearance="primary" label="加载中..."/>
|
<Spinner appearance="primary" label="加载中..."/>
|
||||||
</div>
|
</div>
|
||||||
{
|
<div className={styles.no_data} style={{display: (!loading && !files_list.length) ? 'flex' : 'none'}}>
|
||||||
// (!loading) && (files_list.length === 0) ?
|
<div>
|
||||||
// <div className={styles.no_data}>
|
<DocumentDismissRegular/>
|
||||||
// <div>
|
</div>
|
||||||
// <DocumentDismissRegular/>
|
<Text size={900}>
|
||||||
// </div>
|
没有文件
|
||||||
// <Text size={900}>
|
</Text>
|
||||||
// 没有文件
|
</div>
|
||||||
// </Text>
|
<div style={{display: (!loading && files_list.length) ? 'block' : 'none'}}>
|
||||||
// </div>
|
<VirtualizerScrollView
|
||||||
// : <></>
|
numItems={files_list.length}
|
||||||
}
|
itemSize={32}
|
||||||
{
|
container={{role: 'list', style: {maxHeight: 'calc(100vh - 9rem)'}}}
|
||||||
// (!loading && files_list) ?
|
>
|
||||||
// <VirtualizerScrollView
|
{(idx) => {
|
||||||
// numItems={files_list.length}
|
return <div
|
||||||
// itemSize={32}
|
className={styles.row} key={idx}
|
||||||
// container={{role: 'list', style: {maxHeight: 'calc(100vh - 9rem)'}}}
|
onClick={async () => {
|
||||||
// >
|
await handleClick(files_list[idx])
|
||||||
// {(idx) => {
|
}}
|
||||||
// return <div
|
onContextMenu={async (e) => {
|
||||||
// className={styles.row} key={idx}
|
await handleRightClick(e, files_list[idx])
|
||||||
// onClick={async () => {
|
}}>
|
||||||
// await handleClick(files_list[idx])
|
<MenuItem className={styles.item}
|
||||||
// }}
|
icon={files_list[idx].type ? <FolderRegular/> :
|
||||||
// onContextMenu={async (e) => {
|
<FileIcon name={files_list[idx].name}/>}>
|
||||||
// await handleRightClick(e, files_list[idx])
|
<Text truncate wrap={false} className={styles.text}>
|
||||||
// }}>
|
{filename(files_list[idx].key)}
|
||||||
// <MenuItem className={styles.item}
|
</Text>
|
||||||
// icon={files_list[idx].type ? <FolderRegular/> :
|
</MenuItem>
|
||||||
// <FileIcon name={files_list[idx].name}/>}>
|
</div>
|
||||||
// <Text truncate wrap={false} className={styles.text}>
|
}}
|
||||||
// {filename(files_list[idx].key)}
|
</VirtualizerScrollView>
|
||||||
// </Text>
|
</div>
|
||||||
// </MenuItem>
|
|
||||||
// </div>
|
|
||||||
// }}
|
|
||||||
// </VirtualizerScrollView> : <></>
|
|
||||||
}
|
|
||||||
<MenuList className={styles.list}>
|
|
||||||
</MenuList>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import {useToast} from "../../message";
|
|||||||
import {Dial} from "../../api";
|
import {Dial} from "../../api";
|
||||||
import {useStoreConnection} from "../../store/connection";
|
import {useStoreConnection} from "../../store/connection";
|
||||||
import {useStoreBucket} from "../../store/bucket";
|
import {useStoreBucket} from "../../store/bucket";
|
||||||
import {useStoreFile} from "../../store/file";
|
import {useStoreFile, useStoreFileFilter} from "../../store/file";
|
||||||
import {MoreHorizontalRegular} from "@fluentui/react-icons";
|
import {MoreHorizontalRegular} from "@fluentui/react-icons";
|
||||||
|
|
||||||
const useStyle = makeStyles({
|
const useStyle = makeStyles({
|
||||||
@ -43,7 +43,8 @@ export function UploadFiles(props: UploadFilesProps) {
|
|||||||
|
|
||||||
const { conn_active} = useStoreConnection();
|
const { conn_active} = useStoreConnection();
|
||||||
const {bucket_active} = useStoreBucket();
|
const {bucket_active} = useStoreBucket();
|
||||||
const {prefix, files_get} = useStoreFile()
|
const {files_get} = useStoreFile()
|
||||||
|
const {prefix } = useStoreFileFilter()
|
||||||
|
|
||||||
const [selected, set_selected] = useState<string[]>([]);
|
const [selected, set_selected] = useState<string[]>([]);
|
||||||
|
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
export function PreviewFile() {
|
import {CardPreview, makeStyles} from "@fluentui/react-components";
|
||||||
|
|
||||||
|
const useStyle = makeStyles({
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export function PreviewFile(props: {url:string, content_type:string}) {
|
||||||
|
const styles = useStyle()
|
||||||
|
return <div className={styles.container}>
|
||||||
|
<CardPreview
|
||||||
|
>
|
||||||
|
<img src={props.url} />
|
||||||
|
</CardPreview>
|
||||||
|
</div>
|
||||||
}
|
}
|
@ -1,18 +1,33 @@
|
|||||||
import {create} from 'zustand'
|
import {create} from 'zustand'
|
||||||
|
import {Dial} from "../api";
|
||||||
import {Bucket, Connection} from "../interfaces/connection";
|
import {Bucket, Connection} from "../interfaces/connection";
|
||||||
|
|
||||||
interface StorePreview {
|
interface StorePreview {
|
||||||
preview_key: string;
|
|
||||||
preview_url: string;
|
preview_url: string;
|
||||||
preview_content_type: string;
|
preview_content_type: string;
|
||||||
preview_set: (key: string) => void;
|
preview_get: (conn:Connection,bucket: Bucket,key: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStorePreview = create<StorePreview>()((set) => ({
|
export const useStorePreview = create<StorePreview>()((set) => ({
|
||||||
preview_key: '',
|
|
||||||
preview_url: '',
|
preview_url: '',
|
||||||
preview_content_type: '',
|
preview_content_type: '',
|
||||||
preview_set: async (key: string) => set(state => {
|
preview_get: async (conn: Connection, bucket: Bucket,key: string) => {
|
||||||
return {preview_key: key}
|
if (key === '') {
|
||||||
}),
|
return set(()=>{return {preview_url: ''}})
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = await Dial<{url:string,method:string}>('/api/file/get', {
|
||||||
|
conn_id: conn.id,
|
||||||
|
bucket: bucket.name,
|
||||||
|
key: key
|
||||||
|
})
|
||||||
|
|
||||||
|
if(res.status!=200) {
|
||||||
|
return set(()=>{return {preview_url: ''}})
|
||||||
|
}
|
||||||
|
|
||||||
|
set(()=>{
|
||||||
|
return {preview_url:res.data.url}
|
||||||
|
})
|
||||||
|
},
|
||||||
}))
|
}))
|
||||||
|
@ -12,10 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ObjectInfo struct {
|
type ObjectInfo struct {
|
||||||
Bucket string
|
Bucket string `json:"bucket"`
|
||||||
Key string
|
Key string `json:"key"`
|
||||||
ContentType string
|
ContentType string `json:"content_type"`
|
||||||
Expire int64
|
Expire int64 `json:"expire"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetObjectInfo(ctx context.Context, bucket string, key string) (*ObjectInfo, error) {
|
func (c *Client) GetObjectInfo(ctx context.Context, bucket string, key string) (*ObjectInfo, error) {
|
||||||
@ -62,9 +62,9 @@ func (presigner *Presigner) GetObject(ctx context.Context, bucketName string, ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ObjectEntry struct {
|
type ObjectEntry struct {
|
||||||
URL string
|
URL string `json:"url"`
|
||||||
Method string
|
Method string `json:"method"`
|
||||||
Header http.Header
|
Header http.Header `json:"header"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetObjectEntry(ctx context.Context, bucket string, key string, lifetimes ...int64) (*ObjectEntry, error) {
|
func (c *Client) GetObjectEntry(ctx context.Context, bucket string, key string, lifetimes ...int64) (*ObjectEntry, error) {
|
||||||
@ -92,7 +92,7 @@ func (c *Client) GetObjectEntry(ctx context.Context, bucket string, key string,
|
|||||||
|
|
||||||
type ObjectEntity struct {
|
type ObjectEntity struct {
|
||||||
ObjectInfo
|
ObjectInfo
|
||||||
Body io.ReadCloser
|
Body io.ReadCloser `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetObject(ctx context.Context, bucket string, key string) (*ObjectEntity, error) {
|
func (c *Client) GetObject(ctx context.Context, bucket string, key string) (*ObjectEntity, error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user