add file manager

This commit is contained in:
Qing
2022-12-31 21:07:08 +08:00
parent b847ded828
commit 2dd95be90d
17 changed files with 5402 additions and 5872 deletions

View File

@@ -30,7 +30,8 @@
"react-dom": "^17.0.2",
"react-feather": "^2.0.10",
"react-hotkeys-hook": "^3.4.7",
"react-scripts": "4.0.3",
"react-photo-album": "^2.0.0",
"react-scripts": "5.0.1",
"react-use": "^17.3.1",
"react-zoom-pan-pinch": "^2.1.3",
"recoil": "^0.6.1",
@@ -38,7 +39,7 @@
"typescript": "4.x"
},
"scripts": {
"start": "react-scripts start",
"start": "cross-env GENERATE_SOURCEMAP=false react-scripts start",
"build": "cross-env GENERATE_SOURCEMAP=false react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
@@ -65,8 +66,8 @@
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-react": "^7.27.1",
"eslint-plugin-react-hooks": "^4.3.0",
"prettier": "^2.4.1",
"sass": "^1.49.9"
}

View File

@@ -164,3 +164,16 @@ export async function postInteractiveSeg(
throw new Error(`Something went wrong: ${error}`)
}
}
export async function getMediaFile(filename: string) {
const res = await fetch(`${API_ENDPOINT}/media/${filename}`, {
method: 'GET',
})
if (res.ok) {
const blob = await res.blob()
const file = new File([blob], filename)
return file
}
const errMsg = await res.text()
throw new Error(errMsg)
}

View File

@@ -0,0 +1,27 @@
.file-manager-modal {
color: var(--text-color);
height: 90%;
width: 80%;
}
.file-manager {
overflow: auto;
border-radius: 8px;
}
.react-photo-album.react-photo-album--columns {
height: 80vh;
}
.react-photo-album--photo {
border-radius: 8px;
border: 1px solid transparent;
// transform-origin: 0 0;
transition: transform 0.25s, visibility 0.25s ease-in;
&:hover {
border: 1px solid var(--border-color);
transform: scale(1.01);
}
}

View File

@@ -0,0 +1,90 @@
import React, { ReactNode, useEffect, useMemo, useState } from 'react'
import PhotoAlbum, { RenderPhoto } from 'react-photo-album'
import Modal from '../shared/Modal'
interface Photo {
src: string
height: number
width: number
}
interface Filename {
name: string
height: number
width: number
}
const renderPhoto: RenderPhoto = ({
layout,
layoutOptions,
imageProps: { alt, style, ...restImageProps },
}) => (
<div
style={{
boxSizing: 'content-box',
alignItems: 'center',
}}
>
<img
alt={alt}
style={{ ...style, width: '100%', padding: 0 }}
{...restImageProps}
/>
</div>
)
interface Props {
show: boolean
onClose: () => void
onPhotoClick: (filename: string) => void
}
export default function FileManager(props: Props) {
const { show, onClose, onPhotoClick } = props
const [filenames, setFileNames] = useState<Filename[]>([])
const onClick = ({ index }: { index: number }) => {
onPhotoClick(filenames[index].name)
}
useEffect(() => {
const fetchData = async () => {
const res = await fetch('/medias')
if (res.ok) {
const newFilenames = await res.json()
setFileNames(newFilenames)
}
}
fetchData()
}, [])
const photos = useMemo(() => {
return filenames.map((filename: Filename) => {
const width = 256
const height = filename.height * (width / filename.width)
const src = `/media_thumbnail/${filename.name}?width=${width}&height=${height}`
return { src, height, width }
})
}, [filenames])
return (
<Modal
onClose={onClose}
title="Files"
className="file-manager-modal"
show={show}
>
<div className="file-manager">
<PhotoAlbum
layout="columns"
photos={photos}
renderPhoto={renderPhoto}
spacing={6}
padding={4}
onClick={onClick}
/>
</div>
</Modal>
)
}

View File

@@ -1,4 +1,4 @@
import { ArrowUpTrayIcon } from '@heroicons/react/24/outline'
import { FolderIcon, PhotoIcon } from '@heroicons/react/24/outline'
import { PlayIcon } from '@radix-ui/react-icons'
import React, { useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
@@ -9,6 +9,7 @@ import {
isSDState,
maskState,
runManuallyState,
showFileManagerState,
} from '../../store/Atoms'
import Button from '../shared/Button'
import Shortcuts from '../Shortcuts/Shortcuts'
@@ -18,6 +19,7 @@ import PromptInput from './PromptInput'
import CoffeeIcon from '../CoffeeIcon/CoffeeIcon'
import emitter, { EVENT_CUSTOM_MASK } from '../../event'
import { useImage } from '../../utils'
import useHotKey from '../../hooks/useHotkey'
const Header = () => {
const isInpainting = useRecoilValue(isInpaintingState)
@@ -29,6 +31,17 @@ const Header = () => {
const isSD = useRecoilValue(isSDState)
const runManually = useRecoilValue(runManuallyState)
const [openMaskPopover, setOpenMaskPopover] = useState(false)
const [showFileManager, setShowFileManager] =
useRecoilState(showFileManagerState)
useHotKey(
'f',
() => {
setShowFileManager(!showFileManager)
},
{},
[showFileManager]
)
const renderHeader = () => {
return (
@@ -41,10 +54,20 @@ const Header = () => {
gap: 8,
}}
>
<Button
icon={<FolderIcon />}
style={{ border: 0 }}
toolTip="Open File Manager"
tooltipPosition="bottom"
onClick={() => {
setShowFileManager(true)
}}
/>
<label htmlFor={uploadElemId}>
<Button
icon={<ArrowUpTrayIcon />}
style={{ border: 0 }}
icon={<PhotoIcon />}
style={{ border: 0, gap: 0 }}
disabled={isInpainting}
toolTip="Upload image"
tooltipPosition="bottom"
@@ -62,7 +85,6 @@ const Header = () => {
}}
accept="image/png, image/jpeg"
/>
Image
</Button>
</label>

View File

@@ -67,6 +67,7 @@ export default function ShortcutsModal() {
<ShortCut content="Toggle Dark Mode" keys={['Shift', 'D']} />
<ShortCut content="Toggle Hotkeys Dialog" keys={['H']} />
<ShortCut content="Toggle Settings Dialog" keys={['S']} />
<ShortCut content="Toggle File Manager" keys={['F']} />
</div>
</Modal>
)

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { useRecoilState, useRecoilValue } from 'recoil'
import Editor from './Editor/Editor'
import ShortcutsModal from './Shortcuts/ShortcutsModal'
@@ -10,15 +10,18 @@ import {
isPaintByExampleState,
isSDState,
settingState,
showFileManagerState,
toastState,
} from '../store/Atoms'
import {
currentModel,
getMediaFile,
modelDownloaded,
switchModel,
} from '../adapters/inpainting'
import SidePanel from './SidePanel/SidePanel'
import PESidePanel from './SidePanel/PESidePanel'
import FileManager from './FileManager/FileManager'
const Workspace = () => {
const [file, setFile] = useRecoilState(fileState)
@@ -27,6 +30,9 @@ const Workspace = () => {
const isSD = useRecoilValue(isSDState)
const isPaintByExample = useRecoilValue(isPaintByExampleState)
const [showFileManager, setShowFileManager] =
useRecoilState(showFileManagerState)
const onSettingClose = async () => {
const curModel = await currentModel().then(res => res.text())
if (curModel === settings.model) {
@@ -92,6 +98,17 @@ const Workspace = () => {
<>
{isSD ? <SidePanel /> : <></>}
{isPaintByExample ? <PESidePanel /> : <></>}
<FileManager
show={showFileManager}
onClose={() => {
setShowFileManager(false)
}}
onPhotoClick={async (filename: string) => {
const newFile = await getMediaFile(filename)
setFile(newFile)
setShowFileManager(false)
}}
/>
<Editor />
<SettingModal onClose={onSettingClose} />
<ShortcutsModal />

View File

@@ -32,7 +32,7 @@
grid-auto-rows: max-content;
row-gap: 1rem;
place-self: center;
padding: 2rem;
padding: 25px;
border-radius: 0.95rem;
&:focus {

View File

@@ -41,6 +41,7 @@ interface AppState {
isInteractiveSeg: boolean
isInteractiveSegRunning: boolean
interactiveSegClicks: number[][]
showFileManager: boolean
}
export const appState = atom<AppState>({
@@ -53,6 +54,7 @@ export const appState = atom<AppState>({
isInteractiveSeg: false,
isInteractiveSegRunning: false,
interactiveSegClicks: [],
showFileManager: false,
},
})
@@ -78,6 +80,18 @@ export const isInpaintingState = selector({
},
})
export const showFileManagerState = selector({
key: 'showFileManager',
get: ({ get }) => {
const app = get(appState)
return app.showFileManager
},
set: ({ get, set }, newValue: any) => {
const app = get(appState)
set(appState, { ...app, showFileManager: newValue })
},
})
export const fileState = selector({
key: 'fileState',
get: ({ get }) => {

View File

@@ -7,6 +7,7 @@
// App
@use './App';
@use '../components/Editor/Editor';
@use '../components/FileManager/FileManager';
@use '../components/LandingPage/LandingPage';
@use '../components/Header/Header';
@use '../components/Header/PromptInput';

File diff suppressed because it is too large Load Diff