mirror of
https://git.v0l.io/florian/bouquet.git
synced 2025-03-18 01:13:00 +01:00
feat: Added daisyUI
This commit is contained in:
parent
820eb340cf
commit
5b399482ed
79
package-lock.json
generated
79
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
@ -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>
|
||||
|
@ -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}
|
||||
|
71
src/components/FileEventEditor/FileEventEditor.tsx
Normal file
71
src/components/FileEventEditor/FileEventEditor.tsx
Normal 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;
|
@ -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;
|
||||
}
|
||||
|
@ -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} />
|
||||
|
@ -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} % {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>
|
||||
|
@ -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)}
|
||||
|
@ -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 {
|
||||
|
24
src/components/ThemeSwitcher.tsx
Normal file
24
src/components/ThemeSwitcher.tsx
Normal 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;
|
@ -1,7 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
@apply bg-zinc-900 text-zinc-500 box-border;
|
||||
}
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
body h2 {
|
||||
@apply text-2xl text-white py-4;
|
||||
@apply text-2xl text-base-content;
|
||||
}
|
||||
|
||||
body h2 svg {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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
45
tailwind.config.ts
Normal 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
|
||||
},
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user