diff --git a/README.md b/README.md index 0d6babe..f90c355 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,48 @@ -# React + TypeScript + Vite +# Bouquet -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +A tool to manage your content on blossom severs (Upload, Distribution, Posting) -Currently, two official plugins are available: -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh -## Expanding the ESLint configuration +## TODO / Ideas -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: +Add Blurhash +https://github.com/verbiricha/filestr/blob/master/src/blur.tsx -- Configure the top-level `parserOptions` property like this: +Upload -```js -export default { - // other rules... - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['./tsconfig.json', './tsconfig.node.json'], - tsconfigRootDir: __dirname, - }, -} -``` + Audio + Audio Event 31337, maybe Podcast 31338 + Album Event 30029 + Read ID3 INfo + Display in Metadata Editor + Post Audio Events + Option to select "Full Album" and POst Album (Playlist) Event + Upload of Album Art from Disc + Usage of Album Art from ID3 + --> Album art as new blob + + + Video + FileMeta Data Event 1063 + Video Preview + + PDF + FileMeta Data Event 1063 + + Images + FileMeta Data Event 1063 + dimensions + blurhash + +Nav + Add AUdio Player like Soundcloud + +Blob List + - Selection -> Delete Selected + +Audio List + Audio List, mit Mini Thumnnail (Artitst / Title ) + Join von Album/year aus Album (Playlist) Event ???? + Display blob as "published" when in Audio Event, else as "unlisted" -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` -- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/bun.lockb b/bun.lockb index df827a5..9dada2b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package-lock.json b/package-lock.json index 87f7c43..7537715 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,9 @@ "blossom-client-sdk": "^0.4.0", "dayjs": "^1.11.10", "id3js": "^2.1.1", + "lodash": "^4.17.21", "nostr-tools": "^2.4.0", + "p-limit": "^5.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-pdf": "^7.7.1", @@ -28,6 +30,7 @@ }, "devDependencies": { "@tanstack/eslint-plugin-query": "^5.28.6", + "@types/lodash": "^4.17.0", "@types/react": "^18.2.74", "@types/react-dom": "^18.2.24", "@typescript-eslint/eslint-plugin": "^7.2.0", @@ -1667,6 +1670,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -3787,6 +3796,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4246,6 +4260,35 @@ } }, "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", @@ -4260,14 +4303,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/p-locate/node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, "engines": { "node": ">=10" }, @@ -5847,12 +5887,11 @@ } }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6809,6 +6848,12 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "@types/lodash": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", + "dev": true + }, "@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -8340,6 +8385,11 @@ "p-locate": "^5.0.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -8649,12 +8699,11 @@ } }, "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", "requires": { - "yocto-queue": "^0.1.0" + "yocto-queue": "^1.0.0" } }, "p-locate": { @@ -8664,6 +8713,23 @@ "dev": true, "requires": { "p-limit": "^3.0.2" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } } }, "parent-module": { @@ -9711,10 +9777,9 @@ "dev": true }, "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==" } } } diff --git a/package.json b/package.json index 608c397..d0588e2 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "blossom-client-sdk": "^0.4.0", "dayjs": "^1.11.10", "id3js": "^2.1.1", + "lodash": "^4.17.21", "nostr-tools": "^2.4.0", + "p-limit": "^5.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-pdf": "^7.7.1", @@ -32,6 +34,7 @@ }, "devDependencies": { "@tanstack/eslint-plugin-query": "^5.28.6", + "@types/lodash": "^4.17.0", "@types/react": "^18.2.74", "@types/react-dom": "^18.2.24", "@typescript-eslint/eslint-plugin": "^7.2.0", diff --git a/src/components/BlobList/BlobList.css b/src/components/BlobList/BlobList.css index 68fb1b4..e6a5604 100644 --- a/src/components/BlobList/BlobList.css +++ b/src/components/BlobList/BlobList.css @@ -3,16 +3,7 @@ } .blob-list { - @apply bg-base-200 p-4 text-neutral-content rounded-lg; -} - -.blob-list .blob { - @apply p-1 hover:bg-base-200 rounded-md grid pr-4; - grid-template-columns: 2em auto /*auto*/ 2em 6em 10em 7em 3em; -} - -.blob-list .blob span { - @apply overflow-ellipsis overflow-hidden text-nowrap; + @apply p-4 text-neutral-content rounded-lg; } .blog-list-header { @@ -23,18 +14,10 @@ @apply flex-grow; } -.blog-list-header button { - @apply btn p-2 ml-2 my-2 text-neutral-content rounded-lg; -} - -.blog-list-header button.selected { - @apply btn-primary text-primary-content; -} - .blog-list-header svg { @apply w-6 opacity-80 hover:opacity-100; } -.blob-list .blob span a.pill { - @apply bg-base-200 p-1 px-2 rounded-2xl text-white; -} \ No newline at end of file +.blob-list .table :where(th, td) { + padding: .25em; +} diff --git a/src/components/BlobList/BlobList.tsx b/src/components/BlobList/BlobList.tsx index 1c775e6..9f67b6e 100644 --- a/src/components/BlobList/BlobList.tsx +++ b/src/components/BlobList/BlobList.tsx @@ -17,6 +17,10 @@ import * as id3 from 'id3js'; import { ID3Tag, ID3TagV2 } from 'id3js/lib/id3Tag'; import { useQueries } from '@tanstack/react-query'; import { useServerInfo } from '../../utils/useServerInfo'; +import useFileMetaEventsByHash, { KIND_BLOSSOM_DRIVE, KIND_FILE_META } from '../../utils/useFileMetaEvents'; +import { nip19 } from 'nostr-tools'; +import { AddressPointer, EventPointer } from 'nostr-tools/nip19'; +import { NDKEvent } from '@nostr-dev-kit/ndk'; type ListMode = 'gallery' | 'list' | 'audio' | 'video' | 'docs'; @@ -31,6 +35,7 @@ type AudioBlob = BlobDescriptor & { id3?: ID3Tag; imageData?: string }; const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { const [mode, setMode] = useState('list'); const { distribution } = useServerInfo(); + const fileMetaEventsByHash = useFileMetaEventsByHash(); const images = useMemo( () => blobs.filter(b => b.type?.startsWith('image/')).sort((a, b) => (a.created > b.created ? -1 : 1)), // descending @@ -121,6 +126,46 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { ); + + const Badge = ({ ev }: { ev: NDKEvent }) => { + if (ev.kind == KIND_FILE_META) { + const nevent = nip19.neventEncode({ + kind: ev.kind, + id: ev.id, + author: ev.author.pubkey, + relays: ev.onRelays.map(r => r.url), + } as EventPointer); + return ( + +
published
+
+ ); + } + + if (ev.kind == KIND_BLOSSOM_DRIVE) { + const naddr = nip19.naddrEncode({ + kind: ev.kind, + identifier: ev.tagValue('d'), + pubkey: ev.author.pubkey, + relays: ev.onRelays.map(r => r.url), + } as AddressPointer); + return ( + + 🌸 drive + + ); + } + + return <>; + } + + const Badges = ({ blob }: { blob: BlobDescriptor }) => { + const events = fileMetaEventsByHash[blob.sha256]; + if (!events) return; + + return events.map(ev => ) + }; + return ( <>
@@ -293,34 +338,42 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => { {mode == 'list' && (
- {blobs.map((blob: BlobDescriptor) => ( -
- - - - - - {blob.sha256} - - - {/* - - 🌸 drive 📝 post - - */} - - {distribution[blob.sha256].servers.length == 1 ? ( - - ) : ( - '' - )} - - {formatFileSize(blob.size)} - {blob.type && `${blob.type}`} - {formatDate(blob.created)} - -
- ))} + + + + + + + + + + + + + {blobs.map((blob: BlobDescriptor) => ( + + + + + + + + + ))} + +
HashUsesSizeTypeDateActions
+ + + {blob.sha256.slice(0, 15)} + + + + + {distribution[blob.sha256].servers.length == 1 && } + + {formatFileSize(blob.size)}{blob.type && `${blob.type}`}{formatDate(blob.created)} + +
)} diff --git a/src/components/FileEventEditor/FileEventEditor.tsx b/src/components/FileEventEditor/FileEventEditor.tsx index deba828..8c0bdcb 100644 --- a/src/components/FileEventEditor/FileEventEditor.tsx +++ b/src/components/FileEventEditor/FileEventEditor.tsx @@ -2,14 +2,16 @@ import { NDKEvent, NostrEvent } from '@nostr-dev-kit/ndk'; import { useNDK } from '../../ndk'; import dayjs from 'dayjs'; import { useState } from 'react'; +import uniq from 'lodash/uniq'; +import { formatFileSize } from '../../utils'; export type FileEventData = { content: string; - url: string; + url: string[]; dim?: string; x: string; m?: string; - size?: string; + size: number; //summary: string; //alt: string; }; @@ -23,8 +25,8 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { created_at: dayjs().unix(), content: data.content, tags: [ + ...uniq(data.url).map(du => ['url', du]), ['x', data.x], - ['url', data.url], //['summary', data.summary], //['alt', data.alt], ], @@ -33,7 +35,7 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { }; if (data.size) { - e.tags.push(['size', data.size]); + e.tags.push(['size', `${data.size}`]); } if (data.dim) { e.tags.push(['dim', data.dim]); @@ -45,21 +47,45 @@ const FileEventEditor = ({ data }: { data: FileEventData }) => { const ev = new NDKEvent(ndk, e); await ev.sign(); console.log(ev.rawEvent()); - await ev.publish(); + // await ev.publish(); }; return ( -
-
{JSON.stringify(fileEventData, null, 2)}
- - {fileEventData.dim ? `(${fileEventData.dim})` : ''} -
+
+ {fileEventData.m?.startsWith('image/') && ( +
+ +
+ )} +
+ Type + {fileEventData.m} + + {fileEventData.dim && ( + <> + Dimensions + {fileEventData.dim} + + )} + + File size + {fileEventData.size ? formatFileSize(fileEventData.size) : 'unknown'} + Content / Description + URL +