forked from Kieran/snort
feat: file upload progress / imeta
This commit is contained in:
parent
a29d82bd56
commit
6448996529
47
packages/app/.yarn/.cache/webpack-dev-server/server.pem
Normal file
47
packages/app/.yarn/.cache/webpack-dev-server/server.pem
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEowIBAAKCAQEApyUVkYJVwV7XgluUnllgCtrsdq1ctRICm5gQy8nd+aEdDQjA
|
||||||
|
CKPOWh5miLl/fAQVZGZy/JxavzXulwXo8238E6n6bmNB1Us2nuw7a0aW4iUSQ1Pt
|
||||||
|
P4ZhPpcrqeqMf+hp7iBW0nAHFy/aa2UR84d7tBmSk5J3NNrfBsZdUex/7FqF1EVv
|
||||||
|
mEzlc8kepU9lRXWFQDtZCllEZ1kY3SBJPm10h0g9saI8YIVRxUuNII5GHDYAE3hb
|
||||||
|
EmoY6fuSEoiXA8u0Yt9soBQxgxIhQVKSRPPoIPjGFOxsGHY6h8R9nx1kxhHKFRuV
|
||||||
|
nwsn0uWl/7yjhwyHanogJu73/WgelPcgP/hMDQIDAQABAoIBAAru+xU0oGVwzcoi
|
||||||
|
MXuWPxkWrwcoWfsiPXduIBMklleg+WSD4QPvqyzr9isVb0huf/O8W+M4WxtM7NmG
|
||||||
|
MnHSDP5ATThxV7obHGyS6WQgDvimEibDU66nHK9adim8RQqM6nkANo23dE9I+xGx
|
||||||
|
X9Y9U5M5ZQQwPYoAkzw/N5WHUerk+cSEYWYV8jDtO7wJhYOMu5qliPeuNOaWZ1W6
|
||||||
|
1uwr8A4ih69WwzugPuBSgBrPAW1c84zWIFN+njAugqPF5x8xp2uM3tUO9s5UlHJC
|
||||||
|
FWEuU40KcDT2utSUY+2HXSHbycF4KLKT5jAKSa4sPziLfo+YifrlN0Y3rhofUlZT
|
||||||
|
jCaeZ8ECgYEA5/xpk8aVhCEvv5iCghv0p/IHcjdXjx5+PCWh3Adx0fF91UvU5oqn
|
||||||
|
okdyYZDShZMuLDfJ0lG+OMKZd01JapnbTtiVNceVRMnraIdoWEM2/4bTXTSZGtdA
|
||||||
|
8gh/Kc/PMbPf5ppVWwqTCbUkPOSyGHyGc7+DQquq1w6yZu04A3x9vHECgYEAuHJk
|
||||||
|
uz8YKY5ZUR7CZ3y7YFuwq5Lcpl43AfiiCasjRch0P8yLrITc/6fORsXyy64XW9fC
|
||||||
|
h3YmXvEPaM03W2dxw2aQDvXEvXiEITzmILs7SE3UmZR9m7OMy7Jeqr3+JOc0ckZe
|
||||||
|
Rz5FfuMt1IvNB6lrpfHVtoVrpCOXpzHgC/k/x10CgYA6lU18GfwL/+107uiWPsUL
|
||||||
|
3FzxBPTBmau7OK2lSOP/ZoKmaJ39Eiq/GlfSN6ZSQRa55+S5jhcBcnMa45OUrgHp
|
||||||
|
6VvU1u/lDTC7luZM07yBzuR1dyDq3Ez0Uhz6zBXAsXHrZDIF6ae0HeBm2EH5WQkD
|
||||||
|
Fevp3DwqTvXSdDle+AMwoQKBgQCBSlaH1rNmNc0wCsK07f8ejUcrDZgz2mjurc1P
|
||||||
|
v7HK8bdjHUtvE/ciEguLGqiV06O2EmjesZg2Bv4JNYivPrTFBrjGc8qEEd10uw6J
|
||||||
|
NRVaGoyDV04w/UwdYRvwzZs/XP4reF4PzHvEdRSkH5cJ3t2BhiKLfby1YumkHlbx
|
||||||
|
rbbiVQKBgB02jyZUiB6pPTCP8vXZCJbZELgqNyS04ALhBBpdfGMcU1+0hRLJFBaE
|
||||||
|
tClJPGARFXl+MPkY032vmJZOuH3LrcTCm8DmMLzM/hT1EWawQ8BJkkwiIokE4lqc
|
||||||
|
Bi8CrkvuQs2cuCStK6C3Nkyr1lTkDge46trsb7KTcfHdtLsS7EPj
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDWzCCAkOgAwIBAgIJDji8iiceMvQlMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||||
|
BAMTCWxvY2FsaG9zdDAeFw0yMzEwMTYwOTI0MThaFw0yMzExMTUxMDI0MThaMBQx
|
||||||
|
EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||||
|
ggEBAKclFZGCVcFe14JblJ5ZYAra7HatXLUSApuYEMvJ3fmhHQ0IwAijzloeZoi5
|
||||||
|
f3wEFWRmcvycWr817pcF6PNt/BOp+m5jQdVLNp7sO2tGluIlEkNT7T+GYT6XK6nq
|
||||||
|
jH/oae4gVtJwBxcv2mtlEfOHe7QZkpOSdzTa3wbGXVHsf+xahdRFb5hM5XPJHqVP
|
||||||
|
ZUV1hUA7WQpZRGdZGN0gST5tdIdIPbGiPGCFUcVLjSCORhw2ABN4WxJqGOn7khKI
|
||||||
|
lwPLtGLfbKAUMYMSIUFSkkTz6CD4xhTsbBh2OofEfZ8dZMYRyhUblZ8LJ9Llpf+8
|
||||||
|
o4cMh2p6ICbu9/1oHpT3ID/4TA0CAwEAAaOBrzCBrDAMBgNVHRMEBTADAQH/MAsG
|
||||||
|
A1UdDwQEAwIC9DAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUF
|
||||||
|
BwMDBggrBgEFBQcDCDBcBgNVHREEVTBTgglsb2NhbGhvc3SCFWxvY2FsaG9zdC5s
|
||||||
|
b2NhbGRvbWFpboIGbHZoLm1lgggqLmx2aC5tZYIFWzo6MV2HBH8AAAGHEP6AAAAA
|
||||||
|
AAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggEBABY0rgWuzLYvVtvoVvWKS9cg
|
||||||
|
8rVhBRIFvpYO814ocN1iaxYQ9t9uLRsJXj0K+z1BHWf0zBiw4mB3dD9VpiKpuliL
|
||||||
|
4tRT+vATA96OYCd9G5k7DFQascAau40H3jxckh9rimIWa45FUSd7FIcddo1jeciv
|
||||||
|
gdAdiNUuHBen82O8KHJb+1PCBdA8RYeO5EGKfJM2yrOovu7dAFilf1ZPkXWgXnfG
|
||||||
|
nN6YfDDo9rAVDbvNXImrkwmGqEcN3Pq909IHiM/VETlU5lP4AbTNgrDa/aaZ+I+b
|
||||||
|
1MC1p87MvnibyXs+rTlK5+j8E6noNcD7tsHNd4ufkVCqr+pvSpuA3OvnXjbbm54=
|
||||||
|
-----END CERTIFICATE-----
|
@ -1,14 +1,6 @@
|
|||||||
import { ProxyImg } from "Element/ProxyImg";
|
import { ProxyImg } from "Element/ProxyImg";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
/*
|
|
||||||
[
|
|
||||||
"imeta",
|
|
||||||
"url https://nostr.build/i/148e3e8cbe29ae268b0d6aad0065a086319d3c3b1fdf8b89f1e2327d973d2d05.jpg",
|
|
||||||
"blurhash e6A0%UE2t6D*R%?u?a9G?aM|~pM|%LR*RjR-%2NG%2t7_2R*%1IVWB",
|
|
||||||
"dim 3024x4032"
|
|
||||||
],
|
|
||||||
*/
|
|
||||||
interface MediaElementProps {
|
interface MediaElementProps {
|
||||||
mime: string;
|
mime: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
11
packages/app/src/Element/Event/FileUpload.tsx
Normal file
11
packages/app/src/Element/Event/FileUpload.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import Progress from "Element/Progress";
|
||||||
|
import { UploadProgress } from "Upload";
|
||||||
|
|
||||||
|
export default function FileUploadProgress({ progress }: { progress: Array<UploadProgress> }) {
|
||||||
|
return <div className="flex-column g8">
|
||||||
|
{progress.map(p => <div className="flex-column g2" id={p.id}>
|
||||||
|
{p.file.name}
|
||||||
|
<Progress value={p.progress} />
|
||||||
|
</div>)}
|
||||||
|
</div>
|
||||||
|
}
|
@ -20,6 +20,7 @@ import { fetchNip05Pubkey } from "@snort/shared";
|
|||||||
import { ZapTarget } from "Zapper";
|
import { ZapTarget } from "Zapper";
|
||||||
import { useNoteCreator } from "State/NoteCreator";
|
import { useNoteCreator } from "State/NoteCreator";
|
||||||
import { NoteBroadcaster } from "./NoteBroadcaster";
|
import { NoteBroadcaster } from "./NoteBroadcaster";
|
||||||
|
import FileUploadProgress from "./FileUpload";
|
||||||
|
|
||||||
export function NoteCreator() {
|
export function NoteCreator() {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
@ -114,6 +115,7 @@ export function NoteCreator() {
|
|||||||
}
|
}
|
||||||
const hk = (eb: EventBuilder) => {
|
const hk = (eb: EventBuilder) => {
|
||||||
extraTags?.forEach(t => eb.tag(t));
|
extraTags?.forEach(t => eb.tag(t));
|
||||||
|
note.extraTags?.forEach(t => eb.tag(t));
|
||||||
eb.kind(kind);
|
eb.kind(kind);
|
||||||
return eb;
|
return eb;
|
||||||
};
|
};
|
||||||
@ -170,6 +172,17 @@ export function NoteCreator() {
|
|||||||
v.otherEvents = [...(v.otherEvents ?? []), rx.header];
|
v.otherEvents = [...(v.otherEvents ?? []), rx.header];
|
||||||
} else if (rx.url) {
|
} else if (rx.url) {
|
||||||
v.note = `${v.note ? `${v.note}\n` : ""}${rx.url}`;
|
v.note = `${v.note ? `${v.note}\n` : ""}${rx.url}`;
|
||||||
|
if (rx.metadata) {
|
||||||
|
v.extraTags ??= [];
|
||||||
|
const imeta = ["imeta", `url ${rx.url}`];
|
||||||
|
if (rx.metadata.blurhash) {
|
||||||
|
imeta.push(`blurhash ${rx.metadata.blurhash}`);
|
||||||
|
}
|
||||||
|
if (rx.metadata.width && rx.metadata.height) {
|
||||||
|
imeta.push(`dim ${rx.metadata.width}x${rx.metadata.height}`);
|
||||||
|
}
|
||||||
|
v.extraTags.push(imeta);
|
||||||
|
}
|
||||||
} else if (rx?.error) {
|
} else if (rx?.error) {
|
||||||
v.error = rx.error;
|
v.error = rx.error;
|
||||||
}
|
}
|
||||||
@ -536,6 +549,7 @@ export function NoteCreator() {
|
|||||||
{renderPollOptions()}
|
{renderPollOptions()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{uploader.progress.length > 0 && <FileUploadProgress progress={uploader.progress} />}
|
||||||
{noteCreatorFooter()}
|
{noteCreatorFooter()}
|
||||||
{note.error && <span className="error">{note.error}</span>}
|
{note.error && <span className="error">{note.error}</span>}
|
||||||
{note.advanced && noteCreatorAdvanced()}
|
{note.advanced && noteCreatorAdvanced()}
|
||||||
|
@ -1,21 +1,3 @@
|
|||||||
.zap-goal {
|
|
||||||
}
|
|
||||||
|
|
||||||
.zap-goal h1 {
|
.zap-goal h1 {
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zap-goal .progress {
|
|
||||||
position: relative;
|
|
||||||
height: 1em;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: var(--gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
.zap-goal .progress > div {
|
|
||||||
position: absolute;
|
|
||||||
background-color: var(--success);
|
|
||||||
width: var(--progress);
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import "./ZapGoal.css";
|
import "./ZapGoal.css";
|
||||||
import { CSSProperties, useState } from "react";
|
import { useState } from "react";
|
||||||
import { NostrEvent, NostrLink } from "@snort/system";
|
import { NostrEvent, NostrLink } from "@snort/system";
|
||||||
import useZapsFeed from "Feed/ZapsFeed";
|
import useZapsFeed from "Feed/ZapsFeed";
|
||||||
import { formatShort } from "Number";
|
import { formatShort } from "Number";
|
||||||
@ -7,13 +7,15 @@ import { findTag } from "SnortUtils";
|
|||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import SendSats from "../SendSats";
|
import SendSats from "../SendSats";
|
||||||
import { Zapper } from "Zapper";
|
import { Zapper } from "Zapper";
|
||||||
|
import Progress from "Element/Progress";
|
||||||
|
import { FormattedNumber } from "react-intl";
|
||||||
|
|
||||||
export function ZapGoal({ ev }: { ev: NostrEvent }) {
|
export function ZapGoal({ ev }: { ev: NostrEvent }) {
|
||||||
const [zap, setZap] = useState(false);
|
const [zap, setZap] = useState(false);
|
||||||
const zaps = useZapsFeed(NostrLink.fromEvent(ev));
|
const zaps = useZapsFeed(NostrLink.fromEvent(ev));
|
||||||
const target = Number(findTag(ev, "amount"));
|
const target = Number(findTag(ev, "amount"));
|
||||||
const amount = zaps.reduce((acc, v) => (acc += v.amount * 1000), 0);
|
const amount = zaps.reduce((acc, v) => (acc += v.amount * 1000), 0);
|
||||||
const progress = 100 * (amount / target);
|
const progress = amount / target;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="zap-goal card">
|
<div className="zap-goal card">
|
||||||
@ -26,19 +28,14 @@ export function ZapGoal({ ev }: { ev: NostrEvent }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex f-space">
|
<div className="flex f-space">
|
||||||
<div>{progress.toFixed(1)}%</div>
|
<div>
|
||||||
|
<FormattedNumber value={progress} style="percent" />
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{formatShort(amount / 1000)}/{formatShort(target / 1000)}
|
{formatShort(amount / 1000)}/{formatShort(target / 1000)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="progress">
|
<Progress value={progress} />
|
||||||
<div
|
|
||||||
style={
|
|
||||||
{
|
|
||||||
"--progress": `${Math.min(100, progress)}%`,
|
|
||||||
} as CSSProperties
|
|
||||||
}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
23
packages/app/src/Element/Progress.css
Normal file
23
packages/app/src/Element/Progress.css
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.progress {
|
||||||
|
position: relative;
|
||||||
|
height: 1em;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress > div {
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--success);
|
||||||
|
width: var(--progress);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress > span {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: small;
|
||||||
|
line-height: 1em;
|
||||||
|
}
|
18
packages/app/src/Element/Progress.tsx
Normal file
18
packages/app/src/Element/Progress.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { FormattedNumber } from "react-intl";
|
||||||
|
import "./Progress.css";
|
||||||
|
import { CSSProperties } from "react";
|
||||||
|
|
||||||
|
export default function Progress({ value }: { value: number }) {
|
||||||
|
const v = Math.max(0.01, Math.min(1, value));
|
||||||
|
return <div className="progress">
|
||||||
|
<div
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--progress": `${v * 100}%`,
|
||||||
|
} as CSSProperties
|
||||||
|
}></div>
|
||||||
|
<span>
|
||||||
|
<FormattedNumber value={v} style="percent" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
@ -18,6 +18,7 @@ interface NoteCreatorDataSnapshot {
|
|||||||
sensitive?: string;
|
sensitive?: string;
|
||||||
pollOptions?: Array<string>;
|
pollOptions?: Array<string>;
|
||||||
otherEvents?: Array<NostrEvent>;
|
otherEvents?: Array<NostrEvent>;
|
||||||
|
extraTags?: Array<Array<string>>;
|
||||||
sending?: Array<NostrEvent>;
|
sending?: Array<NostrEvent>;
|
||||||
sendStarted: boolean;
|
sendStarted: boolean;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
@ -63,6 +64,7 @@ class NoteCreatorStore extends ExternalStore<NoteCreatorDataSnapshot> {
|
|||||||
d.pollOptions = undefined;
|
d.pollOptions = undefined;
|
||||||
d.otherEvents = undefined;
|
d.otherEvents = undefined;
|
||||||
d.sending = undefined;
|
d.sending = undefined;
|
||||||
|
d.extraTags = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
takeSnapshot(): NoteCreatorDataSnapshot {
|
takeSnapshot(): NoteCreatorDataSnapshot {
|
||||||
|
@ -30,17 +30,38 @@ export default async function NostrBuild(file: File | Blob, publisher?: EventPub
|
|||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
if (rsp.ok) {
|
if (rsp.ok) {
|
||||||
const data = (await rsp.json()) as {
|
const data = (await rsp.json()) as NostrBuildUploadResponse;
|
||||||
success: boolean;
|
const res = data.data[0];
|
||||||
data: Array<{
|
|
||||||
url: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
url: data.data[0].url,
|
url: res.url,
|
||||||
|
metadata: {
|
||||||
|
blurhash: res.blurhash,
|
||||||
|
width: res.dimensions.width,
|
||||||
|
height: res.dimensions.height,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
error: "Upload failed",
|
error: "Upload failed",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NostrBuildUploadResponse {
|
||||||
|
data: Array<NostrBuildUploadData>;
|
||||||
|
}
|
||||||
|
interface NostrBuildUploadData {
|
||||||
|
input_name: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
thumbnail: string;
|
||||||
|
blurhash: string;
|
||||||
|
sha256: string;
|
||||||
|
type: string;
|
||||||
|
mime: string;
|
||||||
|
size: number;
|
||||||
|
metadata: Record<string, string>;
|
||||||
|
dimensions: {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -13,6 +13,7 @@ export default async function VoidCatUpload(
|
|||||||
file: File | Blob,
|
file: File | Blob,
|
||||||
filename: string,
|
filename: string,
|
||||||
publisher?: EventPublisher,
|
publisher?: EventPublisher,
|
||||||
|
progress?: (n: number) => void,
|
||||||
): Promise<UploadResult> {
|
): Promise<UploadResult> {
|
||||||
const auth = publisher
|
const auth = publisher
|
||||||
? async (url: string, method: string) => {
|
? async (url: string, method: string) => {
|
||||||
@ -23,7 +24,9 @@ export default async function VoidCatUpload(
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const api = new VoidApi(VoidCatHost, auth);
|
const api = new VoidApi(VoidCatHost, auth);
|
||||||
const uploader = api.getUploader(file);
|
const uploader = api.getUploader(file, undefined, px => {
|
||||||
|
progress?.(px / file.size);
|
||||||
|
});
|
||||||
|
|
||||||
const rsp = await uploader.upload({
|
const rsp = await uploader.upload({
|
||||||
"V-Strip-Metadata": "true",
|
"V-Strip-Metadata": "true",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { useState } from "react";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { NostrEvent } from "@snort/system";
|
import { NostrEvent } from "@snort/system";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
import NostrBuild from "Upload/NostrBuild";
|
import NostrBuild from "Upload/NostrBuild";
|
||||||
import VoidCat from "Upload/VoidCat";
|
import VoidCat from "Upload/VoidCat";
|
||||||
@ -16,6 +18,15 @@ export interface UploadResult {
|
|||||||
* NIP-94 File Header
|
* NIP-94 File Header
|
||||||
*/
|
*/
|
||||||
header?: NostrEvent;
|
header?: NostrEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Media metadata
|
||||||
|
*/
|
||||||
|
metadata?: {
|
||||||
|
blurhash?: string;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,27 +49,102 @@ export const UploaderServices = [
|
|||||||
|
|
||||||
export interface Uploader {
|
export interface Uploader {
|
||||||
upload: (f: File | Blob, filename: string) => Promise<UploadResult>;
|
upload: (f: File | Blob, filename: string) => Promise<UploadResult>;
|
||||||
|
progress: Array<UploadProgress>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UploadProgress {
|
||||||
|
id: string;
|
||||||
|
file: File | Blob;
|
||||||
|
progress: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useFileUpload(): Uploader {
|
export default function useFileUpload(): Uploader {
|
||||||
const fileUploader = useLogin().preferences.fileUploader;
|
const fileUploader = useLogin().preferences.fileUploader;
|
||||||
const { publisher } = useEventPublisher();
|
const { publisher } = useEventPublisher();
|
||||||
|
const [progress, setProgress] = useState<Array<UploadProgress>>([]);
|
||||||
|
|
||||||
switch (fileUploader) {
|
switch (fileUploader) {
|
||||||
case "nostr.build": {
|
case "nostr.build": {
|
||||||
return {
|
return {
|
||||||
upload: f => NostrBuild(f, publisher),
|
upload: f => NostrBuild(f, publisher),
|
||||||
|
progress: [],
|
||||||
} as Uploader;
|
} as Uploader;
|
||||||
}
|
}
|
||||||
case "nostrimg.com": {
|
case "nostrimg.com": {
|
||||||
return {
|
return {
|
||||||
upload: NostrImg,
|
upload: NostrImg,
|
||||||
|
progress: [],
|
||||||
} as Uploader;
|
} as Uploader;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return {
|
return {
|
||||||
upload: (f, n) => VoidCat(f, n, publisher),
|
upload: async (f, n) => {
|
||||||
|
const id = uuid();
|
||||||
|
setProgress(s => [
|
||||||
|
...s,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
file: f,
|
||||||
|
progress: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const px = (n: number) => {
|
||||||
|
setProgress(s =>
|
||||||
|
s.map(v =>
|
||||||
|
v.id === id
|
||||||
|
? {
|
||||||
|
...v,
|
||||||
|
progress: n,
|
||||||
|
}
|
||||||
|
: v,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const ret = await VoidCat(f, n, publisher, px);
|
||||||
|
setProgress(s => s.filter(a => a.id !== id));
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
progress,
|
||||||
} as Uploader;
|
} as Uploader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ProgressStream = (file: File | Blob, progress: (n: number) => void) => {
|
||||||
|
let offset = 0;
|
||||||
|
const DefaultChunkSize = 1024 * 32;
|
||||||
|
|
||||||
|
const readChunk = async (offset: number, size: number) => {
|
||||||
|
if (offset > file.size) {
|
||||||
|
return new Uint8Array(0);
|
||||||
|
}
|
||||||
|
const end = Math.min(offset + size, file.size);
|
||||||
|
const blob = file.slice(offset, end, file.type);
|
||||||
|
const data = await blob.arrayBuffer();
|
||||||
|
return new Uint8Array(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rsBase = new ReadableStream(
|
||||||
|
{
|
||||||
|
start: async () => {},
|
||||||
|
pull: async controller => {
|
||||||
|
const chunk = await readChunk(offset, controller.desiredSize ?? DefaultChunkSize);
|
||||||
|
if (chunk.byteLength === 0) {
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progress((offset + chunk.byteLength) / file.size);
|
||||||
|
offset += chunk.byteLength;
|
||||||
|
controller.enqueue(chunk);
|
||||||
|
},
|
||||||
|
cancel: reason => {
|
||||||
|
console.log(reason);
|
||||||
|
},
|
||||||
|
type: "bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
highWaterMark: DefaultChunkSize,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return rsBase;
|
||||||
|
};
|
||||||
|
@ -52,6 +52,7 @@ const config = {
|
|||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
open: true,
|
open: true,
|
||||||
|
https: true,
|
||||||
host: "localhost",
|
host: "localhost",
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user