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", "@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react-swc": "^3.6.0", "@vitejs/plugin-react-swc": "^3.6.0",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"daisyui": "latest",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6", "eslint-plugin-react-refresh": "^0.4.6",
@ -2478,6 +2479,16 @@
"node": ">= 8" "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": { "node_modules/cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -2496,6 +2507,15 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true "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": { "node_modules/d": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
@ -2508,6 +2528,25 @@
"node": ">=0.12" "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": { "node_modules/data-uri-to-buffer": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "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==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "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": { "node_modules/fastq": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@ -7313,6 +7358,16 @@
"which": "^2.0.1" "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": { "cssesc": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -7325,6 +7380,12 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"devOptional": true "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": { "d": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
@ -7334,6 +7395,18 @@
"type": "^2.7.2" "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": { "data-uri-to-buffer": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "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==", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true "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": { "fastq": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",

View File

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

View File

@ -2,16 +2,12 @@
@apply w-5 inline align-text-bottom mr-1; @apply w-5 inline align-text-bottom mr-1;
} }
.blob-list a {
@apply text-pink-500 hover:text-white;
}
.blob-list { .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 { .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; grid-template-columns: 2em auto /*auto*/ 2em 6em 10em 7em 3em;
} }
@ -19,12 +15,8 @@
@apply overflow-ellipsis overflow-hidden text-nowrap; @apply overflow-ellipsis overflow-hidden text-nowrap;
} }
.blob-list .blob a {
@apply cursor-pointer;
}
.blog-list-header { .blog-list-header {
@apply flex flex-row mt-4; @apply flex flex-row py-2 items-center;
} }
.blog-list-header h2 { .blog-list-header h2 {
@ -32,11 +24,11 @@
} }
.blog-list-header button { .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 { .blog-list-header button.selected {
@apply bg-pink-700 text-white; @apply btn-primary text-primary-content;
} }
.blog-list-header svg { .blog-list-header svg {
@ -44,5 +36,5 @@
} }
.blob-list .blob span a.pill { .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}> <div className={className}>
<span> <span>
<a <a
className="cursor-pointer" className="link link-primary"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(blob.url); navigator.clipboard.writeText(blob.url);
}} }}
@ -113,7 +113,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
</span> </span>
{onDelete && ( {onDelete && (
<span> <span>
<a onClick={() => onDelete(blob)} className="cursor-pointer"> <a onClick={() => onDelete(blob)} className="link link-primary">
<TrashIcon title="Delete this blob" /> <TrashIcon title="Delete this blob" />
</a> </a>
</span> </span>
@ -125,49 +125,60 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
<> <>
<div className={`blog-list-header ${!title ? 'justify-end' : ''}`}> <div className={`blog-list-header ${!title ? 'justify-end' : ''}`}>
{title && <h2>{title}</h2>} {title && <h2>{title}</h2>}
<div className=" content-center"> <ul className="menu menu-horizontal menu-active bg-base-200 rounded-box">
<button onClick={() => setMode('list')} className={mode == 'list' ? 'selected' : ''} title="All content"> <li>
<ListBulletIcon /> <a
</button> className={' tooltip ' + (mode == 'list' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
<button data-tip="All content"
onClick={() => setMode('gallery')} onClick={() => setMode('list')}
disabled={images.length == 0} >
className={mode == 'gallery' ? 'selected' : ''} <ListBulletIcon />
title="Images" </a>
> </li>
<PhotoIcon /> <li className={images.length == 0 ? 'disabled' : ''}>
</button> <a
<button className={' tooltip ' + (mode == 'gallery' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
onClick={() => setMode('audio')} onClick={() => setMode('gallery')}
disabled={audioFiles.length == 0} data-tip="Images"
className={mode == 'audio' ? 'selected' : ''} >
title="Music" <PhotoIcon />
> </a>
<MusicalNoteIcon /> </li>
</button>
<button <li className={audioFiles.length == 0 ? 'disabled' : ''}>
onClick={() => setMode('video')} <a
disabled={videos.length == 0} className={' tooltip ' + (mode == 'audio' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
className={mode == 'video' ? 'selected' : ''} onClick={() => setMode('audio')}
title="Video" data-tip="Music"
> >
<FilmIcon /> <MusicalNoteIcon />
</button> </a>
<button </li>
onClick={() => setMode('docs')} <li className={videos.length == 0 ? 'disabled' : ''}>
disabled={videos.length == 0} <a
className={mode == 'docs' ? 'selected' : ''} className={' tooltip ' + (mode == 'video' ? 'bg-primary text-primary-content hover:bg-primary ' : '')}
title="PDF Documents" onClick={() => setMode('video')}
> data-tip="Video"
<DocumentIcon /> >
</button> <FilmIcon />
</div> </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> </div>
{mode == 'gallery' && ( {mode == 'gallery' && (
<div className="blob-list flex flex-wrap justify-center flex-grow"> <div className="blob-list flex flex-wrap justify-center flex-grow">
{images.map(blob => ( {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"> <a href={blob.url} target="_blank">
<div <div
className="" className=""
@ -198,7 +209,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
{videos.map(blob => ( {videos.map(blob => (
<div <div
key={blob.sha256} 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' }} style={{ width: '340px' }}
> >
<video src={blob.url} preload="metadata" width={320} controls playsInline></video> <video src={blob.url} preload="metadata" width={320} controls playsInline></video>
@ -219,7 +230,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
blob.isSuccess && ( blob.isSuccess && (
<div <div
key={blob.data.sha256} 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' }} style={{ width: '24em' }}
> >
{blob.data.id3 && ( {blob.data.id3 && (
@ -256,7 +267,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
{docs.map(blob => ( {docs.map(blob => (
<div <div
key={blob.sha256} 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' }} style={{ width: '22em' }}
> >
<a href={blob.url} target="_blank" className="block overflow-clip text-ellipsis py-2"> <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 /> <DocumentIcon />
</span> </span>
<span> <span>
<a href={blob.url} target="_blank"> <a className="link link-primary" href={blob.url} target="_blank">
{blob.sha256} {blob.sha256}
</a> </a>
</span> </span>
@ -299,11 +310,11 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
*/} */}
<span> <span>
{distribution[blob.sha256].servers.length == 1 ? ( {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>{formatFileSize(blob.size)}</span>
<span>{blob.type && `${blob.type}`}</span> <span>{blob.type && `${blob.type}`}</span>
<span>{formatDate(blob.created)}</span> <span>{formatDate(blob.created)}</span>

View File

@ -11,7 +11,7 @@ const CheckBox = ({
}) => ( }) => (
<> <>
<input <input
className="w-5 accent-pink-700 hover:accent-pink-600" className="checkbox checkbox-primary"
id={name} id={name}
type="checkbox" type="checkbox"
checked={checked} 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 { .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 { .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 { .title img {
@ -27,7 +27,7 @@
} }
.title svg { .title svg {
@apply w-8; @apply w-7;
} }
.avatar { .avatar {
@ -39,5 +39,5 @@
} }
.footer { .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 './Layout.css';
import { ArrowUpOnSquareIcon } from '@heroicons/react/24/outline'; import { ArrowUpOnSquareIcon } from '@heroicons/react/24/outline';
import { useEffect } from 'react'; import { useEffect } from 'react';
import ThemeSwitcher from '../ThemeSwitcher';
export const Layout = () => { export const Layout = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -19,9 +20,12 @@ export const Layout = () => {
<img src="/bouquet.png" /> <span>bouquet</span> <img src="/bouquet.png" /> <span>bouquet</span>
</a> </a>
<div> <div>
<a className="action" onClick={() => navigate('/upload')}> <ThemeSwitcher />
<ArrowUpOnSquareIcon /> <div className="tooltip tooltip-bottom" data-tip="Upload">
</a> <button className=" btn btn-square btn-ghost" onClick={() => navigate('/upload')}>
<ArrowUpOnSquareIcon />
</button>
</div>
</div> </div>
<div className="avatar"> <div className="avatar">
<img src={user?.profile?.image} /> <img src={user?.profile?.image} />

View File

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

View File

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

View File

@ -3,15 +3,19 @@
} }
.server { .server {
@apply bg-zinc-800 text-zinc-300 rounded-lg p-4 gap-4 flex flex-row items-center; @apply bg-base-200 text-neutral-content rounded-lg p-4 gap-4 flex flex-row items-center;
}
.server.selected {
@apply bg-pink-700 cursor-default;
} }
.server-name { .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 { .server-stats {
@ -55,9 +59,8 @@
animation: spin 3s linear infinite; animation: spin 3s linear infinite;
} }
.server-list-header { .server-list-header {
@apply flex flex-row mt-4; @apply flex flex-row py-4;
} }
.server-list-header h2 { .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 base;
@tailwind components; @tailwind components;
@tailwind utilities; @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() { function Check() {
/*
const { serverInfo } = useServerInfo(); const { serverInfo } = useServerInfo();
const navigate = useNavigate(); const navigate = useNavigate();
const { source } = useParams(); const { source } = useParams();
*/
return ( return (
<> <>
<h2>Check integrity</h2> <h2>Check integrity</h2>
{/*
<p className="py-4 text-zinc-400"> <p className="py-4 text-zinc-400">
Downloads all objects from the server and checks the integrity of the content. Downloads all objects from the server and checks the integrity of the content.
</p> </p>
@ -16,6 +16,8 @@ function Check() {
servers={Object.values(serverInfo).filter(s => s.name == source)} servers={Object.values(serverInfo).filter(s => s.name == source)}
onCancel={() => navigate('/')} onCancel={() => navigate('/')}
></ServerList> ></ServerList>
*/}
<button className="btn">dfgfdgdfg</button>
</> </>
); );
} }

View File

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

View File

@ -30,20 +30,3 @@
@apply overflow-ellipsis overflow-hidden text-nowrap; @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 { useNDK } from '../ndk';
import { useServerInfo } from '../utils/useServerInfo'; import { useServerInfo } from '../utils/useServerInfo';
import { useQueryClient } from '@tanstack/react-query'; 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 { removeExifData } from '../exif';
import CheckBox from '../components/CheckBox/CheckBox';
import axios, { AxiosProgressEvent } from 'axios'; 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 { formatFileSize } from '../utils';
import FileEventEditor, { FileEventData } from '../components/FileEventEditor/FileEventEditor';
type TransferStats = { type TransferStats = {
enabled: boolean; enabled: boolean;
@ -17,6 +18,21 @@ type TransferStats = {
transferred: number; 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() { function Upload() {
const servers = useServers(); const servers = useServers();
const { signEventTemplate } = useNDK(); const { signEventTemplate } = useNDK();
@ -27,6 +43,8 @@ function Upload() {
const [cleanPrivateData, setCleanPrivateData] = useState(true); const [cleanPrivateData, setCleanPrivateData] = useState(true);
const [transferSpeed, setTransferSpeed] = useState<number | undefined>(); const [transferSpeed, setTransferSpeed] = useState<number | undefined>();
const [fileEventsToPublish, setFileEventsToPublish] = useState<FileEventData[]>([]);
// const [resizeImages, setResizeImages] = useState(false); // const [resizeImages, setResizeImages] = useState(false);
// const [publishToNostr, setPublishToNostr] = useState(false); // const [publishToNostr, setPublishToNostr] = useState(false);
@ -80,10 +98,11 @@ function Upload() {
// TODO use https://github.com/davejm/client-compress // TODO use https://github.com/davejm/client-compress
// for image resizing // for image resizing
const fileDimensions: { [key: string]: ImageSize } = {};
for (const file of filesToUpload) { for (const file of filesToUpload) {
if (file.type.startsWith('image/')) { if (file.type.startsWith('image/')) {
const dimensions = await getImageSize(file); const dimensions = await getImageSize(file);
console.log(dimensions); fileDimensions[file.name] = dimensions;
} }
} }
@ -126,6 +145,22 @@ function Upload() {
})); }));
console.log(newBlob); 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] }); queryClient.invalidateQueries({ queryKey: ['blobs', server.name] });
setFiles([]); setFiles([]);
@ -136,6 +171,7 @@ function Upload() {
const clearTransfers = () => { const clearTransfers = () => {
setTransfers(servers.reduce((acc, s) => ({ ...acc, [s.name]: { enabled: true, size: 0, transferred: 0 } }), {})); setTransfers(servers.reduce((acc, s) => ({ ...acc, [s.name]: { enabled: true, size: 0, transferred: 0 } }), {}));
setFileEventsToPublish([]);
}; };
useEffect(() => { useEffect(() => {
@ -165,18 +201,18 @@ function Upload() {
return ( return (
<> <>
<h2>Upload</h2> <h2 className=" py-4">Upload</h2>
<div className=" bg-zinc-800 rounded-xl p-4 text-zinc-400 gap-4 flex flex-col"> <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} /> <input id="browse" type="file" hidden multiple onChange={handleFileChange} />
<label <label
htmlFor="browse" 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} onDrop={handleDrop}
onDragOver={event => event.preventDefault()} onDragOver={event => event.preventDefault()}
> >
<ArrowUpOnSquareIcon className="w-8 inline" /> Browse or drag & drop <ArrowUpOnSquareIcon className="w-8 inline" /> Browse or drag & drop
</label> </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' }}> <div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1.5em 20em auto' }}>
{servers.map(s => ( {servers.map(s => (
<> <>
@ -198,7 +234,7 @@ function Upload() {
</> </>
))} ))}
</div> </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' }}> <div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1.5em auto' }}>
<CheckBox <CheckBox
name="cleanData" name="cleanData"
@ -222,16 +258,13 @@ function Upload() {
*/} */}
</div> </div>
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<button <button className="btn btn-primary" onClick={() => upload()} disabled={files.length == 0}>
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}
>
Upload{files.length > 0 ? (files.length == 1 ? ` 1 file` : ` ${files.length} files`) : ''} /{' '} Upload{files.length > 0 ? (files.length == 1 ? ` 1 file` : ` ${files.length} files`) : ''} /{' '}
{formatFileSize(sizeOfFilesToUpload)} {formatFileSize(sizeOfFilesToUpload)}
</button> </button>
<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={() => { onClick={() => {
clearTransfers(); clearTransfers();
setFiles([]); setFiles([]);
@ -241,6 +274,14 @@ function Upload() {
</button> </button>
</div> </div>
</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
},
};