mirror of
https://git.v0l.io/florian/bouquet.git
synced 2025-03-18 00:03:02 +01:00
feat: Added upload dialog
This commit is contained in:
parent
5bc7831222
commit
f7cb216c70
@ -3,7 +3,7 @@
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply flex flex-col self-center sm:w-10/12 lg:w-4/6 w-full;
|
||||
@apply flex flex-col self-center sm:w-10/12 lg:w-4/6 w-full min-h-[80vh]
|
||||
}
|
||||
|
||||
.title {
|
||||
@ -14,10 +14,22 @@
|
||||
@apply w-10;
|
||||
}
|
||||
|
||||
.title a.action {
|
||||
@apply flex flex-col text-sm items-center opacity-50 hover:opacity-100 cursor-pointer px-2;
|
||||
}
|
||||
|
||||
.title a.logo {
|
||||
@apply flex flex-row flex-grow items-center gap-2 cursor-pointer;
|
||||
}
|
||||
|
||||
.title span {
|
||||
@apply flex-grow;
|
||||
}
|
||||
|
||||
.title svg {
|
||||
@apply w-8;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
@apply flex-shrink;
|
||||
}
|
||||
|
@ -1,14 +1,23 @@
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import { useNDK } from '../../ndk';
|
||||
import './Layout.css';
|
||||
import { ArrowUpOnSquareIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
export const Layout = () => {
|
||||
const { user } = useNDK();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="main">
|
||||
<div className="title">
|
||||
<img src="/bouquet.png" /> <span>bouquet</span>
|
||||
<a className="logo" onClick={() => navigate('/')}>
|
||||
<img src="/bouquet.png" /> <span>bouquet</span>
|
||||
</a>
|
||||
<div>
|
||||
<a className='action' onClick={() => navigate('/upload')}>
|
||||
<ArrowUpOnSquareIcon />
|
||||
</a>
|
||||
</div>
|
||||
<div className="avatar">
|
||||
<img src={user?.profile?.image} />
|
||||
</div>
|
||||
|
@ -5,22 +5,23 @@ import {
|
||||
ClockIcon,
|
||||
CubeIcon,
|
||||
DocumentDuplicateIcon,
|
||||
ExclamationCircleIcon,
|
||||
ExclamationTriangleIcon,
|
||||
ServerIcon,
|
||||
ShieldExclamationIcon,
|
||||
XMarkIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { Server } from '../../utils/useServers';
|
||||
import { Server as ServerType } from '../../utils/useServers';
|
||||
import { ServerInfo } from '../../utils/useServerInfo';
|
||||
import { formatDate, formatFileSize } from '../../utils';
|
||||
|
||||
type ServerProps = {
|
||||
server: Server;
|
||||
server: ServerType;
|
||||
serverInfo: ServerInfo;
|
||||
selectedServer?: string | undefined;
|
||||
setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
onTransfer?: (server: string) => void;
|
||||
onCancel?: () => void;
|
||||
onCheck?: (server: string) => void;
|
||||
blobsOnlyOnThisServer: number;
|
||||
};
|
||||
|
||||
@ -31,6 +32,7 @@ const Server = ({
|
||||
serverInfo,
|
||||
onTransfer,
|
||||
onCancel,
|
||||
onCheck,
|
||||
blobsOnlyOnThisServer,
|
||||
}: ServerProps) => {
|
||||
return (
|
||||
@ -75,10 +77,19 @@ const Server = ({
|
||||
</div>
|
||||
{((selectedServer == server.name && onTransfer) || onCancel) && (
|
||||
<div className="server-actions">
|
||||
{selectedServer == server.name && onTransfer && (
|
||||
<a onClick={() => onTransfer(server.name)}>
|
||||
<ArrowUpOnSquareStackIcon /> Transfer
|
||||
</a>
|
||||
{selectedServer == server.name && (
|
||||
<>
|
||||
{onCheck && (
|
||||
<a onClick={() => onCheck(server.name)}>
|
||||
<ShieldExclamationIcon /> Check
|
||||
</a>
|
||||
)}
|
||||
{onTransfer && (
|
||||
<a onClick={() => onTransfer(server.name)}>
|
||||
<ArrowUpOnSquareStackIcon /> Transfer
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{onCancel && (
|
||||
<a onClick={() => onCancel()}>
|
||||
|
@ -32,8 +32,12 @@
|
||||
@apply w-10;
|
||||
}
|
||||
|
||||
.server-actions {
|
||||
@apply flex flex-row gap-4;
|
||||
}
|
||||
|
||||
.server-actions a {
|
||||
@apply cursor-pointer text-center flex flex-col items-center hover:text-white opacity-80 hover:opacity-100;
|
||||
@apply cursor-pointer text-center flex flex-col items-center hover:text-white opacity-80 hover:opacity-100 gap-1;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
|
@ -9,9 +9,10 @@ type ServerListProps = {
|
||||
setSelectedServer?: React.Dispatch<React.SetStateAction<string | undefined>>;
|
||||
onTransfer?: (server: string) => void;
|
||||
onCancel?: () => void;
|
||||
onCheck?: (server: string) => void;
|
||||
};
|
||||
|
||||
export const ServerList = ({ servers, selectedServer, setSelectedServer, onTransfer, onCancel }: ServerListProps) => {
|
||||
export const ServerList = ({ servers, selectedServer, setSelectedServer, onTransfer, onCancel, onCheck }: ServerListProps) => {
|
||||
const { serverInfo, distribution } = useServerInfo();
|
||||
const blobsWithOnlyOneOccurance = Object.values(distribution)
|
||||
.filter(d => d.servers.length == 1)
|
||||
@ -28,6 +29,7 @@ export const ServerList = ({ servers, selectedServer, setSelectedServer, onTrans
|
||||
setSelectedServer={setSelectedServer}
|
||||
onTransfer={onTransfer}
|
||||
onCancel={onCancel}
|
||||
/* onCheck={onCheck} */
|
||||
blobsOnlyOnThisServer={blobsWithOnlyOneOccurance.filter(b => b.server == server.name).length}
|
||||
></Server>
|
||||
))}
|
||||
|
@ -8,6 +8,8 @@ import { Route, RouterProvider, createBrowserRouter, createRoutesFromElements }
|
||||
import { Layout } from './components/Layout/Layout.tsx';
|
||||
import Home from './pages/Home.tsx';
|
||||
import { Transfer } from './pages/Transfer.tsx';
|
||||
import Upload from './pages/Upload.tsx';
|
||||
import Check from './pages/Check.tsx';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
@ -16,6 +18,8 @@ const router = createBrowserRouter(
|
||||
<Route element={<Layout />}>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="/transfer/:source" element={<Transfer />} />
|
||||
<Route path="/upload" element={<Upload/>} />
|
||||
<Route path="/check/:source" element={<Check/>} />
|
||||
</Route>
|
||||
)
|
||||
);
|
||||
|
24
src/pages/Check.tsx
Normal file
24
src/pages/Check.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { ServerList } from '../components/ServerList/ServerList';
|
||||
import { useServerInfo } from '../utils/useServerInfo';
|
||||
|
||||
function Check() {
|
||||
const { serverInfo } = useServerInfo();
|
||||
const navigate = useNavigate();
|
||||
const { source } = useParams();
|
||||
return (
|
||||
<>
|
||||
<h2>Check integrity</h2>
|
||||
<p className="py-4 text-neutral-400">
|
||||
Downloads all objects from the server and checks the integrity of the content.
|
||||
</p>
|
||||
<ServerList
|
||||
servers={Object.values(serverInfo).filter(s => s.name == source)}
|
||||
onCancel={() => navigate('/')}
|
||||
></ServerList>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Check;
|
@ -64,6 +64,7 @@ function Home() {
|
||||
selectedServer={selectedServer}
|
||||
setSelectedServer={setSelectedServer}
|
||||
onTransfer={() => navigate('/transfer/' + selectedServer)}
|
||||
onCheck={() => navigate('/check/' + selectedServer)}
|
||||
></ServerList>
|
||||
|
||||
{selectedServer && serverInfo[selectedServer] && selectedServerBlobs && (
|
||||
|
67
src/pages/Upload.tsx
Normal file
67
src/pages/Upload.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useServers } from '../utils/useServers';
|
||||
import { BlossomClient } from 'blossom-client-sdk';
|
||||
import { useNDK } from '../ndk';
|
||||
import { useServerInfo } from '../utils/useServerInfo';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
function Upload() {
|
||||
const servers = useServers();
|
||||
const { signEventTemplate } = useNDK();
|
||||
const { serverInfo } = useServerInfo();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const [uploadTarget, setUploadTarget] = useState<{ [key: string]: boolean }>({});
|
||||
|
||||
const upload = async () => {
|
||||
if (inputRef.current && inputRef.current.files) {
|
||||
for (const server of servers) {
|
||||
if (!uploadTarget[server.name]) {
|
||||
continue;
|
||||
}
|
||||
const serverUrl = serverInfo[server.name].url;
|
||||
for (const file of inputRef.current.files) {
|
||||
const uploadAuth = await BlossomClient.getUploadAuth(file, signEventTemplate, 'Upload Blob');
|
||||
const newBlob = await BlossomClient.uploadBlob(serverUrl, file, uploadAuth);
|
||||
console.log(newBlob);
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: ['blobs', server.name] });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setUploadTarget(servers.reduce((acc, s) => ({ ...acc, [s.name]: true }), {}));
|
||||
}, [servers]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Upload</h2>
|
||||
<div className=" bg-neutral-800 rounded-xl p-4 text-neutral-400 gap-4 flex flex-col">
|
||||
<input className=" cursor-pointer" type="file" ref={inputRef} multiple />
|
||||
|
||||
{servers.map(s => (
|
||||
<div className="cursor-pointer flex flex-row gap-2 " key={s.name}>
|
||||
<input
|
||||
className="w-5 accent-pink-700 hover:accent-pink-600"
|
||||
id={s.name}
|
||||
type="checkbox"
|
||||
checked={uploadTarget[s.name] || false}
|
||||
onChange={e => setUploadTarget(ut => ({ ...ut, [s.name]: e.target.checked }))}
|
||||
/>
|
||||
<label htmlFor={s.name} className="cursor-pointer">
|
||||
{s.name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button className="p-2 px-4 bg-neutral-600 hover:bg-pink-700 text-white rounded-lg w-2/6" onClick={() => upload()}>
|
||||
Upload
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Upload;
|
Loading…
x
Reference in New Issue
Block a user