diff --git a/package-lock.json b/package-lock.json index 6819d7b..87f7c43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 697e62b..608c397 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/BlobList/BlobList.css b/src/components/BlobList/BlobList.css index c1d6e4b..68fb1b4 100644 --- a/src/components/BlobList/BlobList.css +++ b/src/components/BlobList/BlobList.css @@ -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; } \ No newline at end of file diff --git a/src/components/BlobList/BlobList.tsx b/src/components/BlobList/BlobList.tsx index 367de2c..1c775e6 100644 --- a/src/components/BlobList/BlobList.tsx +++ b/src/components/BlobList/BlobList.tsx @@ -103,7 +103,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
{ navigator.clipboard.writeText(blob.url); }} @@ -113,7 +113,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { {onDelete && ( - onDelete(blob)} className="cursor-pointer"> + onDelete(blob)} className="link link-primary"> @@ -125,49 +125,60 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { <>
{title &&

{title}

} -
- - - - - -
+
{mode == 'gallery' && (
{images.map(blob => ( -
+
{ {videos.map(blob => (
@@ -219,7 +230,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { blob.isSuccess && (
{blob.data.id3 && ( @@ -256,7 +267,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { {docs.map(blob => (
@@ -288,7 +299,7 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { - + {blob.sha256} @@ -299,11 +310,11 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { */} {distribution[blob.sha256].servers.length == 1 ? ( - + ) : ( '' )} - + {formatFileSize(blob.size)} {blob.type && `${blob.type}`} {formatDate(blob.created)} diff --git a/src/components/CheckBox/CheckBox.tsx b/src/components/CheckBox/CheckBox.tsx index 7f9ade5..3cb6e6a 100644 --- a/src/components/CheckBox/CheckBox.tsx +++ b/src/components/CheckBox/CheckBox.tsx @@ -11,7 +11,7 @@ const CheckBox = ({ }) => ( <> { + 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 ( +
+
{JSON.stringify(fileEventData, null, 2)}
+ + {fileEventData.dim ? `(${fileEventData.dim})` : ''} +
+ + +
+
+ ); +}; + +export default FileEventEditor; diff --git a/src/components/Layout/Layout.css b/src/components/Layout/Layout.css index 509cd37..4c76ed1 100644 --- a/src/components/Layout/Layout.css +++ b/src/components/Layout/Layout.css @@ -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; } diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 13186a5..8b7a6b7 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -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 = () => { bouquet
- navigate('/upload')}> - - + +
+ +
diff --git a/src/components/ProgressBar/ProgressBar.tsx b/src/components/ProgressBar/ProgressBar.tsx index 6859d9f..fe9f622 100644 --- a/src/components/ProgressBar/ProgressBar.tsx +++ b/src/components/ProgressBar/ProgressBar.tsx @@ -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 ( -
+
{max !== undefined && value !== undefined && max > 0 && ( -
- {percent} % {showDescription ? description : ''} +
+ + {percent}% + {showDescription ? description : ''}
)}
diff --git a/src/components/ServerList/Server.tsx b/src/components/ServerList/Server.tsx index dd9798a..985c376 100644 --- a/src/components/ServerList/Server.tsx +++ b/src/components/ServerList/Server.tsx @@ -39,7 +39,7 @@ const Server = ({
setSelectedServer && setSelectedServer(server.name)} diff --git a/src/components/ServerList/ServerList.css b/src/components/ServerList/ServerList.css index 31c860f..5a8c0d4 100644 --- a/src/components/ServerList/ServerList.css +++ b/src/components/ServerList/ServerList.css @@ -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 { diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..fcba7dc --- /dev/null +++ b/src/components/ThemeSwitcher.tsx @@ -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 ( +
+ +
+ ); +}; + +export default ThemeSwitcher; diff --git a/src/index.css b/src/index.css index b546ec7..b5c61c9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,3 @@ @tailwind base; @tailwind components; @tailwind utilities; - -body { - @apply bg-zinc-900 text-zinc-500 box-border; -} diff --git a/src/pages/Check.tsx b/src/pages/Check.tsx index 682a4a4..8b29572 100644 --- a/src/pages/Check.tsx +++ b/src/pages/Check.tsx @@ -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 ( <>

Check integrity

+ {/*

Downloads all objects from the server and checks the integrity of the content.

@@ -16,6 +16,8 @@ function Check() { servers={Object.values(serverInfo).filter(s => s.name == source)} onCancel={() => navigate('/')} > + */} + ); } diff --git a/src/pages/Home.css b/src/pages/Home.css index cc80ead..542a2b0 100644 --- a/src/pages/Home.css +++ b/src/pages/Home.css @@ -1,5 +1,5 @@ body h2 { - @apply text-2xl text-white py-4; + @apply text-2xl text-base-content; } body h2 svg { diff --git a/src/pages/Transfer.css b/src/pages/Transfer.css index f5bcb3f..7b39d66 100644 --- a/src/pages/Transfer.css +++ b/src/pages/Transfer.css @@ -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; -} diff --git a/src/pages/Upload.tsx b/src/pages/Upload.tsx index d207e57..3b18eaa 100644 --- a/src/pages/Upload.tsx +++ b/src/pages/Upload.tsx @@ -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(); + const [fileEventsToPublish, setFileEventsToPublish] = useState([]); + // 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 ( <> -

Upload

-
+

Upload

+
-

Servers

+

Servers

{servers.map(s => ( <> @@ -198,7 +234,7 @@ function Upload() { ))}
-

Options

+

Options

-
+ {fileEventsToPublish.length > 0 && ( + <> +

Publish events

+ {fileEventsToPublish.map(fe => ( + + ))} + + )} ); } diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index d37737f..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -export default { - content: [ - "./index.html", - "./src/**/*.{js,ts,jsx,tsx}", - ], - theme: { - extend: {}, - }, - plugins: [], -} - diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..b93af0b --- /dev/null +++ b/tailwind.config.ts @@ -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 + }, +};