feat: Added daisyUI

This commit is contained in:
florian 2024-04-15 18:55:38 +02:00
parent 820eb340cf
commit 5b399482ed
19 changed files with 374 additions and 136 deletions

79
package-lock.json generated
View File

@ -34,6 +34,7 @@
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"autoprefixer": "^10.4.19",
"daisyui": "latest",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
@ -2478,6 +2479,16 @@
"node": ">= 8"
}
},
"node_modules/css-selector-tokenizer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -2496,6 +2507,15 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true
},
"node_modules/culori": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
"integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/d": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
@ -2508,6 +2528,25 @@
"node": ">=0.12"
}
},
"node_modules/daisyui": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.10.1.tgz",
"integrity": "sha512-Ds0Z0Fv+Xf6ZEqV4Q5JIOeKfg83xxnww0Lzid0V94vPtlQ0yYmucEa33zSctsX2VEgBALtmk5zVEqd59pnUbuQ==",
"dev": true,
"dependencies": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
"picocolors": "^1",
"postcss-js": "^4"
},
"engines": {
"node": ">=16.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@ -3015,6 +3054,12 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
},
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@ -7313,6 +7358,16 @@
"which": "^2.0.1"
}
},
"css-selector-tokenizer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -7325,6 +7380,12 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true
},
"culori": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
"integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
"dev": true
},
"d": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
@ -7334,6 +7395,18 @@
"type": "^2.7.2"
}
},
"daisyui": {
"version": "4.10.1",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.10.1.tgz",
"integrity": "sha512-Ds0Z0Fv+Xf6ZEqV4Q5JIOeKfg83xxnww0Lzid0V94vPtlQ0yYmucEa33zSctsX2VEgBALtmk5zVEqd59pnUbuQ==",
"dev": true,
"requires": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
"picocolors": "^1",
"postcss-js": "^4"
}
},
"data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
@ -7729,6 +7802,12 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
},
"fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",

View File

@ -38,6 +38,7 @@
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.6.0",
"autoprefixer": "^10.4.19",
"daisyui": "latest",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",

View File

@ -2,16 +2,12 @@
@apply w-5 inline align-text-bottom mr-1;
}
.blob-list a {
@apply text-pink-500 hover:text-white;
}
.blob-list {
@apply bg-zinc-800 p-4 text-zinc-300 rounded-lg;
@apply bg-base-200 p-4 text-neutral-content rounded-lg;
}
.blob-list .blob {
@apply p-1 hover:bg-zinc-700 rounded-md grid pr-4;
@apply p-1 hover:bg-base-200 rounded-md grid pr-4;
grid-template-columns: 2em auto /*auto*/ 2em 6em 10em 7em 3em;
}
@ -19,12 +15,8 @@
@apply overflow-ellipsis overflow-hidden text-nowrap;
}
.blob-list .blob a {
@apply cursor-pointer;
}
.blog-list-header {
@apply flex flex-row mt-4;
@apply flex flex-row py-2 items-center;
}
.blog-list-header h2 {
@ -32,11 +24,11 @@
}
.blog-list-header button {
@apply bg-zinc-800 hover:bg-zinc-700 p-2 ml-2 my-2 text-white rounded-lg disabled:text-zinc-700 disabled:bg-zinc-900;
@apply btn p-2 ml-2 my-2 text-neutral-content rounded-lg;
}
.blog-list-header button.selected {
@apply bg-pink-700 text-white;
@apply btn-primary text-primary-content;
}
.blog-list-header svg {
@ -44,5 +36,5 @@
}
.blob-list .blob span a.pill {
@apply bg-zinc-700 p-1 px-2 rounded-2xl text-white;
@apply bg-base-200 p-1 px-2 rounded-2xl text-white;
}

View File

@ -103,7 +103,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
<div className={className}>
<span>
<a
className="cursor-pointer"
className="link link-primary"
onClick={() => {
navigator.clipboard.writeText(blob.url);
}}
@ -113,7 +113,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
</span>
{onDelete && (
<span>
<a onClick={() => onDelete(blob)} className="cursor-pointer">
<a onClick={() => onDelete(blob)} className="link link-primary">
<TrashIcon title="Delete this blob" />
</a>
</span>
@ -125,49 +125,60 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
<>
<div className={`blog-list-header ${!title ? 'justify-end' : ''}`}>
{title && <h2>{title}</h2>}
<div className=" content-center">
<button onClick={() => setMode('list')} className={mode == 'list' ? 'selected' : ''} title="All content">
<ListBulletIcon />
</button>
<button
onClick={() => setMode('gallery')}
disabled={images.length == 0}
className={mode == 'gallery' ? 'selected' : ''}
title="Images"
>
<PhotoIcon />
</button>
<button
onClick={() => setMode('audio')}
disabled={audioFiles.length == 0}
className={mode == 'audio' ? 'selected' : ''}
title="Music"
>
<MusicalNoteIcon />
</button>
<button
onClick={() => setMode('video')}
disabled={videos.length == 0}
className={mode == 'video' ? 'selected' : ''}
title="Video"
>
<FilmIcon />
</button>
<button
onClick={() => setMode('docs')}
disabled={videos.length == 0}
className={mode == 'docs' ? 'selected' : ''}
title="PDF Documents"
>
<DocumentIcon />
</button>
</div>
<ul className="menu menu-horizontal menu-active bg-base-200 rounded-box">
<li>
<a
className={' tooltip ' + (mode == 'list' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
data-tip="All content"
onClick={() => setMode('list')}
>
<ListBulletIcon />
</a>
</li>
<li className={images.length == 0 ? 'disabled' : ''}>
<a
className={' tooltip ' + (mode == 'gallery' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
onClick={() => setMode('gallery')}
data-tip="Images"
>
<PhotoIcon />
</a>
</li>
<li className={audioFiles.length == 0 ? 'disabled' : ''}>
<a
className={' tooltip ' + (mode == 'audio' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
onClick={() => setMode('audio')}
data-tip="Music"
>
<MusicalNoteIcon />
</a>
</li>
<li className={videos.length == 0 ? 'disabled' : ''}>
<a
className={' tooltip ' + (mode == 'video' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
onClick={() => setMode('video')}
data-tip="Video"
>
<FilmIcon />
</a>
</li>
<li className={docs.length == 0 ? 'disabled' : ''}>
<a
className={' tooltip ' + (mode == 'docs' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
onClick={() => setMode('docs')}
data-tip="PDF documents"
>
<DocumentIcon />
</a>
</li>
</ul>
</div>
{mode == 'gallery' && (
<div className="blob-list flex flex-wrap justify-center flex-grow">
{images.map(blob => (
<div key={blob.sha256} className="p-2 rounded-lg bg-zinc-900 m-2 relative inline-block text-center">
<div key={blob.sha256} className="p-2 rounded-lg bg-base-300 m-2 relative inline-block text-center">
<a href={blob.url} target="_blank">
<div
className=""
@ -198,7 +209,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
{videos.map(blob => (
<div
key={blob.sha256}
className="p-4 rounded-lg bg-zinc-900 m-2 relative flex flex-col"
className="p-4 rounded-lg bg-base-300 m-2 relative flex flex-col"
style={{ width: '340px' }}
>
<video src={blob.url} preload="metadata" width={320} controls playsInline></video>
@ -219,7 +230,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
blob.isSuccess && (
<div
key={blob.data.sha256}
className="p-4 rounded-lg bg-zinc-900 m-2 relative flex flex-col"
className="p-4 rounded-lg bg-base-300 m-2 relative flex flex-col"
style={{ width: '24em' }}
>
{blob.data.id3 && (
@ -256,7 +267,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
{docs.map(blob => (
<div
key={blob.sha256}
className="p-4 rounded-lg bg-zinc-900 m-2 relative flex flex-col"
className="p-4 rounded-lg bg-base-300 m-2 relative flex flex-col"
style={{ width: '22em' }}
>
<a href={blob.url} target="_blank" className="block overflow-clip text-ellipsis py-2">
@ -288,7 +299,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
<DocumentIcon />
</span>
<span>
<a href={blob.url} target="_blank">
<a className="link link-primary" href={blob.url} target="_blank">
{blob.sha256}
</a>
</span>
@ -299,11 +310,11 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
*/}
<span>
{distribution[blob.sha256].servers.length == 1 ? (
<ExclamationTriangleIcon title="Not distributed to any other server" />
<ExclamationTriangleIcon title="Not distributed to any other server" />
) : (
''
)}
</span>
</span>
<span>{formatFileSize(blob.size)}</span>
<span>{blob.type && `${blob.type}`}</span>
<span>{formatDate(blob.created)}</span>

View File

@ -11,7 +11,7 @@ const CheckBox = ({
}) => (
<>
<input
className="w-5 accent-pink-700 hover:accent-pink-600"
className="checkbox checkbox-primary"
id={name}
type="checkbox"
checked={checked}

View File

@ -0,0 +1,71 @@
import { NDKEvent, NostrEvent } from '@nostr-dev-kit/ndk';
import { useNDK } from '../../ndk';
import dayjs from 'dayjs';
import { useState } from 'react';
export type FileEventData = {
content: string;
url: string;
dim?: string;
x: string;
m?: string;
size?: string;
//summary: string;
//alt: string;
};
const FileEventEditor = ({ data }: { data: FileEventData }) => {
const [fileEventData, setFileEventData] = useState(data);
const { ndk, user } = useNDK();
const publishFileEvent = async (data: FileEventData) => {
const e: NostrEvent = {
created_at: dayjs().unix(),
content: data.content,
tags: [
['x', data.x],
['url', data.url],
//['summary', data.summary],
//['alt', data.alt],
],
kind: 1063,
pubkey: user?.pubkey || '',
};
if (data.size) {
e.tags.push(['size', data.size]);
}
if (data.dim) {
e.tags.push(['dim', data.dim]);
}
if (data.m) {
e.tags.push(['m', data.m]);
}
const ev = new NDKEvent(ndk, e);
await ev.sign();
console.log(ev.rawEvent());
await ev.publish();
};
return (
<div>
<pre>{JSON.stringify(fileEventData, null, 2)}</pre>
<img src={`https://images.slidestr.net/insecure/f:webp/rs:fill:300/plain/${fileEventData.url}`}></img>
{fileEventData.dim ? `(${fileEventData.dim})` : ''}
<div className="flex flex-col gap-4">
<textarea
value={fileEventData.content}
onChange={e => setFileEventData(ed => ({ ...ed, content: e.target.value }))}
className="textarea textarea-secondary"
placeholder="Caption"
></textarea>
<button className="btn btn-primary" onClick={() => publishFileEvent(fileEventData)}>
Publish
</button>
</div>
</div>
);
};
export default FileEventEditor;

View File

@ -3,11 +3,11 @@
}
.content {
@apply flex flex-col self-center sm:w-10/12 xl:w-4/6 w-full min-h-[80vh];
@apply flex flex-col self-center sm:w-10/12 w-full min-h-[80vh];
}
.title {
@apply text-white text-4xl flex flex-row items-center gap-2 p-4 sm:w-10/12 xl:w-4/6 w-full self-center;
@apply text-neutral-content text-4xl flex flex-row items-center gap-2 p-4 sm:w-10/12 w-full self-center;
}
.title img {
@ -27,7 +27,7 @@
}
.title svg {
@apply w-8;
@apply w-7;
}
.avatar {
@ -39,5 +39,5 @@
}
.footer {
@apply self-center text-zinc-600 pt-12 pb-6;
@apply justify-center gap-1 text-base-content pt-12 pb-6;
}

View File

@ -3,6 +3,7 @@ import { useNDK } from '../../ndk';
import './Layout.css';
import { ArrowUpOnSquareIcon } from '@heroicons/react/24/outline';
import { useEffect } from 'react';
import ThemeSwitcher from '../ThemeSwitcher';
export const Layout = () => {
const navigate = useNavigate();
@ -19,9 +20,12 @@ export const Layout = () => {
<img src="/bouquet.png" /> <span>bouquet</span>
</a>
<div>
<a className="action" onClick={() => navigate('/upload')}>
<ArrowUpOnSquareIcon />
</a>
<ThemeSwitcher />
<div className="tooltip tooltip-bottom" data-tip="Upload">
<button className=" btn btn-square btn-ghost" onClick={() => navigate('/upload')}>
<ArrowUpOnSquareIcon />
</button>
</div>
</div>
<div className="avatar">
<img src={user?.profile?.image} />

View File

@ -1,15 +1,13 @@
const ProgressBar = ({ value, max, description = '' }: { value: number; max: number; description?: string }) => {
//value=11;max=100;description="4,5 MB/s"
const percent = Math.floor((value * 100) / max);
const showDescription = percent > 10 && percent < 100;
return (
<div className="w-full bg-gray-200 rounded-lg dark:bg-zinc-900">
<div className="w-full bg-base-200 rounded-lg">
{max !== undefined && value !== undefined && max > 0 && (
<div
className="bg-pink-600 text-sm font-medium text-pink-100 text-center p-1 leading-none rounded-lg text-nowrap"
style={{ width: `${percent}%` }}
>
{percent}&nbsp;% {showDescription ? description : ''}
<div className="grid items-center gap-4" style={{gridTemplateColumns:'8fr 5em minmax(0, 1fr)'}}>
<progress className="progress w-full accent-primary" value={percent} max="100" />
<span>{percent}%</span>
<span>{showDescription ? description : ''}</span>
</div>
)}
</div>

View File

@ -39,7 +39,7 @@ const Server = ({
<div
className={
`server ${selectedServer == server.name ? 'selected' : ''} ` +
`${setSelectedServer ? ' hover:bg-zinc-700 cursor-pointer' : ''} `
`${setSelectedServer ? ' hover:bg-base-200 cursor-pointer' : ''} `
}
key={server.name}
onClick={() => setSelectedServer && setSelectedServer(server.name)}

View File

@ -3,15 +3,19 @@
}
.server {
@apply bg-zinc-800 text-zinc-300 rounded-lg p-4 gap-4 flex flex-row items-center;
}
.server.selected {
@apply bg-pink-700 cursor-default;
@apply bg-base-200 text-neutral-content rounded-lg p-4 gap-4 flex flex-row items-center;
}
.server-name {
@apply text-2xl mb-2 text-white;
@apply text-2xl mb-2 text-neutral-content;
}
.server.selected {
@apply bg-primary text-primary-content cursor-default;
}
.server.selected .server-name {
@apply text-white;
}
.server-stats {
@ -55,9 +59,8 @@
animation: spin 3s linear infinite;
}
.server-list-header {
@apply flex flex-row mt-4;
@apply flex flex-row py-4;
}
.server-list-header h2 {

View File

@ -0,0 +1,24 @@
import { MoonIcon, SunIcon } from '@heroicons/react/24/outline';
import React from 'react';
const ThemeSwitcher = () => {
const [theme, setTheme] = React.useState('mydark');
const toggleTheme = () => {
setTheme(theme === 'mydark' ? 'mylight' : 'mydark');
};
// initially set the theme and "listen" for changes to apply them to the HTML tag
React.useEffect(() => {
document.querySelector('html')?.setAttribute('data-theme', theme);
}, [theme]);
return (
<div className='tooltip tooltip-bottom' data-tip="Switch theme">
<label className="swap swap-rotate">
<input onClick={toggleTheme} type="checkbox" />
<MoonIcon className="tooltip swap-on" />
<SunIcon className="tooltip swap-off" />
</label>
</div>
);
};
export default ThemeSwitcher;

View File

@ -1,7 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply bg-zinc-900 text-zinc-500 box-border;
}

View File

@ -1,14 +1,14 @@
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-zinc-400">
Downloads all objects from the server and checks the integrity of the content.
</p>
@ -16,6 +16,8 @@ function Check() {
servers={Object.values(serverInfo).filter(s => s.name == source)}
onCancel={() => navigate('/')}
></ServerList>
*/}
<button className="btn">dfgfdgdfg</button>
</>
);
}

View File

@ -1,5 +1,5 @@
body h2 {
@apply text-2xl text-white py-4;
@apply text-2xl text-base-content;
}
body h2 svg {

View File

@ -30,20 +30,3 @@
@apply overflow-ellipsis overflow-hidden text-nowrap;
}
.blob-list svg {
@apply w-5 inline align-text-bottom mr-1;
}
.blob-list a {
@apply text-pink-500 hover:text-white;
}
.blob-list {
}
.blob-list .blob {
}
.blob-list .blob a {
@apply cursor-pointer;
}

View File

@ -4,12 +4,13 @@ import { BlobDescriptor, BlossomClient, SignedEvent } from 'blossom-client-sdk';
import { useNDK } from '../ndk';
import { useServerInfo } from '../utils/useServerInfo';
import { useQueryClient } from '@tanstack/react-query';
import { ArrowUpOnSquareIcon, TrashIcon } from '@heroicons/react/24/outline';
import ProgressBar from '../components/ProgressBar/ProgressBar';
import { removeExifData } from '../exif';
import CheckBox from '../components/CheckBox/CheckBox';
import axios, { AxiosProgressEvent } from 'axios';
import { ArrowUpOnSquareIcon, TrashIcon } from '@heroicons/react/24/outline';
import CheckBox from '../components/CheckBox/CheckBox';
import ProgressBar from '../components/ProgressBar/ProgressBar';
import { formatFileSize } from '../utils';
import FileEventEditor, { FileEventData } from '../components/FileEventEditor/FileEventEditor';
type TransferStats = {
enabled: boolean;
@ -17,6 +18,21 @@ type TransferStats = {
transferred: number;
};
/*
TODO
steps
- select files
- (preview/reisze/exif removal)
- images: size, blurimage, dimensions
- audio: id3 tag
- video: dimensions, bitrate
- upload
- server slection, progress bars, upload speed
-
*/
function Upload() {
const servers = useServers();
const { signEventTemplate } = useNDK();
@ -27,6 +43,8 @@ function Upload() {
const [cleanPrivateData, setCleanPrivateData] = useState(true);
const [transferSpeed, setTransferSpeed] = useState<number | undefined>();
const [fileEventsToPublish, setFileEventsToPublish] = useState<FileEventData[]>([]);
// const [resizeImages, setResizeImages] = useState(false);
// const [publishToNostr, setPublishToNostr] = useState(false);
@ -80,10 +98,11 @@ function Upload() {
// TODO use https://github.com/davejm/client-compress
// for image resizing
const fileDimensions: { [key: string]: ImageSize } = {};
for (const file of filesToUpload) {
if (file.type.startsWith('image/')) {
const dimensions = await getImageSize(file);
console.log(dimensions);
fileDimensions[file.name] = dimensions;
}
}
@ -126,6 +145,22 @@ function Upload() {
}));
console.log(newBlob);
const dim = fileDimensions[file.name];
const fed: FileEventData = {
content: file.name,
x: newBlob.sha256,
url: newBlob.url,
size: `${newBlob.size}`,
};
if (newBlob.type) {
fed.m = newBlob.type;
}
if (dim) {
fed.dim = `${dim.width}x${dim.height}`;
}
setFileEventsToPublish(fetp => [...fetp, fed]);
}
queryClient.invalidateQueries({ queryKey: ['blobs', server.name] });
setFiles([]);
@ -136,6 +171,7 @@ function Upload() {
const clearTransfers = () => {
setTransfers(servers.reduce((acc, s) => ({ ...acc, [s.name]: { enabled: true, size: 0, transferred: 0 } }), {}));
setFileEventsToPublish([]);
};
useEffect(() => {
@ -165,18 +201,18 @@ function Upload() {
return (
<>
<h2>Upload</h2>
<div className=" bg-zinc-800 rounded-xl p-4 text-zinc-400 gap-4 flex flex-col">
<h2 className=" py-4">Upload</h2>
<div className=" bg-base-200 rounded-xl p-4 text-neutral-content gap-4 flex flex-col">
<input id="browse" type="file" hidden multiple onChange={handleFileChange} />
<label
htmlFor="browse"
className="p-8 bg-zinc-700 rounded-lg hover:text-white text-zinc-400 border-dashed border-zinc-500 border-2 block cursor-pointer text-center"
className="p-8 bg-base-100 rounded-lg hover:text-primary text-neutral-content border-dashed border-neutral-content border-opacity-50 border-2 block cursor-pointer text-center"
onDrop={handleDrop}
onDragOver={event => event.preventDefault()}
>
<ArrowUpOnSquareIcon className="w-8 inline" /> Browse or drag & drop
</label>
<h3 className="text-lg text-white">Servers</h3>
<h3 className="text-lg">Servers</h3>
<div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1.5em 20em auto' }}>
{servers.map(s => (
<>
@ -198,7 +234,7 @@ function Upload() {
</>
))}
</div>
<h3 className="text-lg text-white">Options</h3>
<h3 className="text-lg text-neutral-content">Options</h3>
<div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1.5em auto' }}>
<CheckBox
name="cleanData"
@ -222,16 +258,13 @@ function Upload() {
*/}
</div>
<div className="flex flex-row gap-2">
<button
className="p-2 px-4 bg-zinc-600 hover:bg-pink-700 text-white rounded-lg w-3/12 disabled:text-zinc-800 disabled:bg-zinc-900 "
onClick={() => upload()}
disabled={files.length == 0}
>
<button className="btn btn-primary" onClick={() => upload()} disabled={files.length == 0}>
Upload{files.length > 0 ? (files.length == 1 ? ` 1 file` : ` ${files.length} files`) : ''} /{' '}
{formatFileSize(sizeOfFilesToUpload)}
</button>
<button
className="p-2 px-4 bg-zinc-600 hover:bg-pink-700 text-white rounded-lg "
className="btn btn-secondary "
disabled={files.length == 0}
onClick={() => {
clearTransfers();
setFiles([]);
@ -241,6 +274,14 @@ function Upload() {
</button>
</div>
</div>
{fileEventsToPublish.length > 0 && (
<>
<h2>Publish events</h2>
{fileEventsToPublish.map(fe => (
<FileEventEditor data={fe} />
))}
</>
)}
</>
);
}

View File

@ -1,12 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

45
tailwind.config.ts Normal file
View File

@ -0,0 +1,45 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [require('daisyui')],
daisyui: {
themes: [
{
mydark: {
...require('daisyui/src/theming/themes')['dark'],
primary: '#be185d',
secondary: '#2563eb',
accent: '#fb923c',
info: '#a5b4fc',
success: '#6ee7b7',
warning: '#facc15',
error: '#e11d48',
},
},
{
mylight: {
...require('daisyui/src/theming/themes')['cupcake'],
primary: '#be185d',
secondary: '#2563eb',
accent: '#fb923c',
neutral: '#e0e0e0',
info: '#a5b4fc',
success: '#6ee7b7',
warning: '#facc15',
error: '#e11d48',
},
},
], // false: only light + dark | true: all themes | array: specific themes like this ["light", "dark", "cupcake"]
darkTheme: 'mydark', // name of one of the included themes for dark mode
base: true, // applies background color and foreground color for root element by default
styled: true, // include daisyUI colors and design decisions for all components
utils: true, // adds responsive and modifier utility classes
prefix: '', // prefix for daisyUI classnames (components, modifiers and responsive class names. Not colors)
logs: true, // Shows info about daisyUI version and used config in the console when building your CSS
themeRoot: ':root', // The element that receives theme color CSS variables
},
};