mirror of
https://github.com/smolgrrr/TAO.git
synced 2024-09-20 09:21:25 +00:00
clean up post form
This commit is contained in:
parent
26bf02e771
commit
9c540eda8c
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.0.18",
|
"@heroicons/react": "^2.0.18",
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.2",
|
||||||
|
@ -1,211 +0,0 @@
|
|||||||
import CardContainer from "../Modals/CardContainer";
|
|
||||||
import {
|
|
||||||
ArrowUpTrayIcon,
|
|
||||||
CpuChipIcon,
|
|
||||||
ArrowPathIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { XCircleIcon } from "@heroicons/react/24/solid";
|
|
||||||
import { useState, useEffect, useMemo } from "react";
|
|
||||||
import { generatePrivateKey, getPublicKey, finishEvent } from "nostr-tools";
|
|
||||||
import { publish } from "../../utils/relays";
|
|
||||||
import FileUpload from "../../utils/FileUpload";
|
|
||||||
import { renderMedia } from "../../utils/FileUpload";
|
|
||||||
|
|
||||||
const NewThreadCard: React.FC = () => {
|
|
||||||
const [comment, setComment] = useState("");
|
|
||||||
const [file, setFile] = useState("");
|
|
||||||
const [sk, setSk] = useState(generatePrivateKey());
|
|
||||||
const [difficulty, setDifficulty] = useState(
|
|
||||||
localStorage.getItem("difficulty") || "21"
|
|
||||||
);
|
|
||||||
const [uploadingFile, setUploadingFile] = useState(false);
|
|
||||||
const [messageFromWorker, setMessageFromWorker] = useState(null);
|
|
||||||
const [doingWorkProp, setDoingWorkProp] = useState(false);
|
|
||||||
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
|
|
||||||
|
|
||||||
// Initialize the worker outside of any effects
|
|
||||||
const numCores = navigator.hardwareConcurrency || 4;
|
|
||||||
|
|
||||||
const workers = useMemo(
|
|
||||||
() => Array(numCores).fill(null).map(() => new Worker(new URL("../../powWorker", import.meta.url))),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
workers.forEach((worker) => {
|
|
||||||
worker.onmessage = (event) => {
|
|
||||||
if (event.data.status === 'progress') {
|
|
||||||
console.log(`Worker progress: Checked ${event.data.currentNonce} nonces.`);
|
|
||||||
setDoingWorkProgress(event.data.currentNonce);
|
|
||||||
} else if (event.data.found) {
|
|
||||||
setMessageFromWorker(event.data.event);
|
|
||||||
// Terminate all workers once a solution is found
|
|
||||||
workers.forEach(w => w.terminate());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDifficultyChange = (event: Event) => {
|
|
||||||
const customEvent = event as CustomEvent;
|
|
||||||
const { difficulty, filterDifficulty } = customEvent.detail;
|
|
||||||
setDifficulty(difficulty);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("difficultyChanged", handleDifficultyChange);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("difficultyChanged", handleDifficultyChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
workers.forEach((worker, index) => {
|
|
||||||
worker.postMessage({
|
|
||||||
unsigned: {
|
|
||||||
kind: 1,
|
|
||||||
tags: [],
|
|
||||||
content: comment + " " + file,
|
|
||||||
created_at: Math.floor(Date.now() / 1000),
|
|
||||||
pubkey: getPublicKey(sk),
|
|
||||||
},
|
|
||||||
difficulty,
|
|
||||||
nonceStart: index, // Each worker starts from its index
|
|
||||||
nonceStep: numCores // Each worker increments by the total number of workers
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setDoingWorkProp(false);
|
|
||||||
if (messageFromWorker) {
|
|
||||||
try {
|
|
||||||
const signedEvent = finishEvent(messageFromWorker, sk);
|
|
||||||
publish(signedEvent);
|
|
||||||
|
|
||||||
setComment("");
|
|
||||||
setFile("");
|
|
||||||
setSk(generatePrivateKey());
|
|
||||||
setMessageFromWorker(null);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
setComment(error + " " + comment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [messageFromWorker]);
|
|
||||||
|
|
||||||
async function attachFile(file_input: File | null) {
|
|
||||||
setUploadingFile(true); // start loading
|
|
||||||
try {
|
|
||||||
if (file_input) {
|
|
||||||
const rx = await FileUpload(file_input);
|
|
||||||
setUploadingFile(false); // stop loading
|
|
||||||
if (rx.url) {
|
|
||||||
setFile(rx.url);
|
|
||||||
} else if (rx?.error) {
|
|
||||||
setFile(rx.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
setUploadingFile(false); // stop loading
|
|
||||||
if (error instanceof Error) {
|
|
||||||
setFile(error?.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
name="post"
|
|
||||||
method="post"
|
|
||||||
encType="multipart/form-data"
|
|
||||||
className=""
|
|
||||||
onSubmit={(event) => {
|
|
||||||
handleSubmit(event);
|
|
||||||
setDoingWorkProp(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={4194304} />
|
|
||||||
<div
|
|
||||||
id="togglePostFormLink"
|
|
||||||
className="text-lg text-neutral-500 text-center mb-2 font-semibold"
|
|
||||||
>
|
|
||||||
Start a New Thread
|
|
||||||
</div>
|
|
||||||
<div className="px-4 pt-4 flex flex-col bg-neutral-900 rounded-lg">
|
|
||||||
<textarea
|
|
||||||
name="com"
|
|
||||||
wrap="soft"
|
|
||||||
className="shadow-lg w-full px-4 py-3 h-28 rounded-md outline-none focus:outline-none bg-neutral-800 border border-neutral-700 text-white placeholder:text-neutral-500"
|
|
||||||
placeholder="Shitpost here..."
|
|
||||||
value={comment}
|
|
||||||
onChange={(e) => setComment(e.target.value)}
|
|
||||||
/>
|
|
||||||
<div className="relative">
|
|
||||||
{file !== "" && (
|
|
||||||
<button onClick={() => setFile("")}>
|
|
||||||
<XCircleIcon className="h-10 w-10 absolute shadow z-100 text-blue-500" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{renderMedia(file)}
|
|
||||||
</div>
|
|
||||||
<div className="h-14 flex items-center justify-between">
|
|
||||||
<div className="inline-flex items-center gap-2 bg-neutral-800 px-1.5 py-1 rounded-lg">
|
|
||||||
<div className="inline-flex items-center gap-1.5 text-neutral-300">
|
|
||||||
<CpuChipIcon className="h-4 w-4" />
|
|
||||||
</div>
|
|
||||||
<p className="text-xs font-medium text-neutral-400">
|
|
||||||
{difficulty} PoW
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<ArrowUpTrayIcon
|
|
||||||
className="h-4 w-4 text-neutral-400 cursor-pointer"
|
|
||||||
onClick={() => document.getElementById("file_input")?.click()}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
name="file_input"
|
|
||||||
id="file_input"
|
|
||||||
style={{ display: "none" }}
|
|
||||||
onChange={(e) => {
|
|
||||||
const file_input = e.target.files?.[0];
|
|
||||||
if (file_input) {
|
|
||||||
attachFile(file_input);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{uploadingFile ? (
|
|
||||||
<div className="flex animate-spin text-sm text-gray-300">
|
|
||||||
<ArrowPathIcon className="h-4 w-4 ml-auto" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="h-9 inline-flex items-center justify-center px-4 bg-blue-500 hover:bg-blue-600 rounded-lg text-white font-medium text-sm"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{doingWorkProp ? (
|
|
||||||
<div className="flex animate-pulse text-sm text-gray-300">
|
|
||||||
<CpuChipIcon className="h-4 w-4 ml-auto" />
|
|
||||||
<span>Generating Proof-of-Work:</span>
|
|
||||||
<span>iteration {doingWorkProgress}</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<div id="postFormError" className="text-red-500" />
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NewThreadCard;
|
|
214
client/src/components/Forms/PostFormCard.tsx
Normal file
214
client/src/components/Forms/PostFormCard.tsx
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import {
|
||||||
|
ArrowUpTrayIcon,
|
||||||
|
CpuChipIcon,
|
||||||
|
ArrowPathIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
|
import { XCircleIcon } from "@heroicons/react/24/solid";
|
||||||
|
import { useState, useEffect, useMemo } from "react";
|
||||||
|
import { generatePrivateKey, getPublicKey, finishEvent, UnsignedEvent } from "nostr-tools";
|
||||||
|
import { publish } from "../../utils/relays";
|
||||||
|
import { renderMedia, attachFile } from "../../utils/FileUpload";
|
||||||
|
|
||||||
|
const useWorkers = (numCores: number, unsigned: UnsignedEvent, difficulty: string, deps: any[]) => {
|
||||||
|
const [messageFromWorker, setMessageFromWorker] = useState(null);
|
||||||
|
const [doingWorkProgress, setDoingWorkProgress] = useState(0);
|
||||||
|
|
||||||
|
const startWork = () => {
|
||||||
|
const workers = Array(numCores).fill(null).map(() => new Worker(new URL("../../powWorker", import.meta.url)));
|
||||||
|
|
||||||
|
workers.forEach((worker, index) => {
|
||||||
|
worker.onmessage = (event) => {
|
||||||
|
if (event.data.status === 'progress') {
|
||||||
|
console.log(`Worker progress: Checked ${event.data.currentNonce} nonces.`);
|
||||||
|
setDoingWorkProgress(event.data.currentNonce);
|
||||||
|
} else if (event.data.found) {
|
||||||
|
setMessageFromWorker(event.data.event);
|
||||||
|
// Terminate all workers once a solution is found
|
||||||
|
workers.forEach(w => w.terminate());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.postMessage({
|
||||||
|
unsigned,
|
||||||
|
difficulty,
|
||||||
|
nonceStart: index, // Each worker starts from its index
|
||||||
|
nonceStep: numCores // Each worker increments by the total number of workers
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { startWork, messageFromWorker, doingWorkProgress };
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewNoteCard: React.FC = () => {
|
||||||
|
const [comment, setComment] = useState("");
|
||||||
|
const [file, setFile] = useState("");
|
||||||
|
const [sk, setSk] = useState(generatePrivateKey());
|
||||||
|
const [unsigned, setUnsigned] = useState<UnsignedEvent>({
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: "",
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
pubkey: getPublicKey(sk),
|
||||||
|
});
|
||||||
|
const [difficulty, setDifficulty] = useState(
|
||||||
|
localStorage.getItem("difficulty") || "21"
|
||||||
|
);
|
||||||
|
|
||||||
|
const [uploadingFile, setUploadingFile] = useState(false);
|
||||||
|
const [doingWorkProp, setDoingWorkProp] = useState(false);
|
||||||
|
|
||||||
|
// Initialize the worker outside of any effects
|
||||||
|
const numCores = navigator.hardwareConcurrency || 4;
|
||||||
|
|
||||||
|
const { startWork, messageFromWorker, doingWorkProgress } = useWorkers(numCores, unsigned, difficulty, [unsigned]);
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleDifficultyChange = (event: Event) => {
|
||||||
|
const customEvent = event as CustomEvent;
|
||||||
|
const { difficulty } = customEvent.detail;
|
||||||
|
setDifficulty(difficulty);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("difficultyChanged", handleDifficultyChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("difficultyChanged", handleDifficultyChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUnsigned(
|
||||||
|
{
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: comment + " " + file,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
pubkey: getPublicKey(sk),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, [comment, file]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDoingWorkProp(false);
|
||||||
|
if (messageFromWorker) {
|
||||||
|
try {
|
||||||
|
const signedEvent = finishEvent(messageFromWorker, sk);
|
||||||
|
publish(signedEvent);
|
||||||
|
|
||||||
|
setComment("");
|
||||||
|
setFile("");
|
||||||
|
setSk(generatePrivateKey());
|
||||||
|
setUnsigned(
|
||||||
|
{
|
||||||
|
kind: 1,
|
||||||
|
tags: [],
|
||||||
|
content: "",
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
pubkey: getPublicKey(sk),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
setComment(error + " " + comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [messageFromWorker]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
name="post"
|
||||||
|
method="post"
|
||||||
|
encType="multipart/form-data"
|
||||||
|
className=""
|
||||||
|
onSubmit={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
startWork();
|
||||||
|
setDoingWorkProp(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input type="hidden" name="MAX_FILE_SIZE" defaultValue={4194304} />
|
||||||
|
<div
|
||||||
|
id="togglePostFormLink"
|
||||||
|
className="text-lg text-neutral-500 text-center mb-2 font-semibold"
|
||||||
|
>
|
||||||
|
Start a New Thread
|
||||||
|
</div>
|
||||||
|
<div className="px-4 pt-4 flex flex-col bg-neutral-900 rounded-lg">
|
||||||
|
<textarea
|
||||||
|
name="com"
|
||||||
|
wrap="soft"
|
||||||
|
className="shadow-lg w-full px-4 py-3 h-28 rounded-md outline-none focus:outline-none bg-neutral-800 border border-neutral-700 text-white placeholder:text-neutral-500"
|
||||||
|
placeholder="Shitpost here..."
|
||||||
|
value={comment}
|
||||||
|
onChange={(e) => setComment(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="relative">
|
||||||
|
{file !== "" && (
|
||||||
|
<button onClick={() => setFile("")}>
|
||||||
|
<XCircleIcon className="h-10 w-10 absolute shadow z-100 text-blue-500" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{renderMedia(file)}
|
||||||
|
</div>
|
||||||
|
<div className="h-14 flex items-center justify-between">
|
||||||
|
<div className="inline-flex items-center gap-2 bg-neutral-800 px-1.5 py-1 rounded-lg">
|
||||||
|
<div className="inline-flex items-center gap-1.5 text-neutral-300">
|
||||||
|
<CpuChipIcon className="h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
<p className="text-xs font-medium text-neutral-400">
|
||||||
|
{difficulty} PoW
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<ArrowUpTrayIcon
|
||||||
|
className="h-4 w-4 text-neutral-400 cursor-pointer"
|
||||||
|
onClick={() => document.getElementById("file_input")?.click()}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name="file_input"
|
||||||
|
id="file_input"
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={async (e) => {
|
||||||
|
const file_input = e.target.files?.[0];
|
||||||
|
if (file_input) {
|
||||||
|
setUploadingFile(true);
|
||||||
|
const attachedFile = await attachFile(file_input);
|
||||||
|
setFile(attachedFile);
|
||||||
|
setUploadingFile(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{uploadingFile ? (
|
||||||
|
<div className="flex animate-spin text-sm text-gray-300">
|
||||||
|
<ArrowPathIcon className="h-4 w-4 ml-auto" />
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="h-9 inline-flex items-center justify-center px-4 bg-blue-500 hover:bg-blue-600 rounded-lg text-white font-medium text-sm"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{doingWorkProp ? (
|
||||||
|
<div className="flex animate-pulse text-sm text-gray-300">
|
||||||
|
<CpuChipIcon className="h-4 w-4 ml-auto" />
|
||||||
|
<span>Generating Proof-of-Work:</span>
|
||||||
|
<span>iteration {doingWorkProgress}</span>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div id="postFormError" className="text-red-500" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NewNoteCard;
|
@ -1,10 +1,10 @@
|
|||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import PostCard from "./Modals/Card";
|
import PostCard from "./Modals/Card";
|
||||||
import NewThreadCard from "./Forms/NewThreadCard";
|
|
||||||
import { uniqBy } from "../utils/utils"; // Assume getPow is a correct import now
|
import { uniqBy } from "../utils/utils"; // Assume getPow is a correct import now
|
||||||
import { subGlobalFeed } from "../utils/subscriptions";
|
import { subGlobalFeed } from "../utils/subscriptions";
|
||||||
import { verifyPow } from "../utils/mine";
|
import { verifyPow } from "../utils/mine";
|
||||||
import { Event } from "nostr-tools";
|
import { Event } from "nostr-tools";
|
||||||
|
import NewNoteCard from "./Forms/PostFormCard";
|
||||||
|
|
||||||
const DEFAULT_DIFFICULTY = 20;
|
const DEFAULT_DIFFICULTY = 20;
|
||||||
|
|
||||||
@ -53,7 +53,7 @@ const Home = () => {
|
|||||||
return (
|
return (
|
||||||
<main className="text-white mb-20">
|
<main className="text-white mb-20">
|
||||||
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
<div className="w-full px-4 sm:px-0 sm:max-w-xl mx-auto my-2">
|
||||||
<NewThreadCard />
|
<NewNoteCard />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center w-full py-4">
|
<div className="flex items-center justify-center w-full py-4">
|
||||||
<label htmlFor="toggleB" className="flex items-center cursor-pointer">
|
<label htmlFor="toggleB" className="flex items-center cursor-pointer">
|
||||||
|
@ -3,7 +3,7 @@ import { FolderIcon, CpuChipIcon } from "@heroicons/react/24/outline";
|
|||||||
import { parseContent } from "../../utils/content";
|
import { parseContent } from "../../utils/content";
|
||||||
import { Event, nip19 } from "nostr-tools";
|
import { Event, nip19 } from "nostr-tools";
|
||||||
import { getMetadata } from "../../utils/utils";
|
import { getMetadata } from "../../utils/utils";
|
||||||
import ContentPreview from "./TextModal";
|
import ContentPreview from "./CardModals/TextModal";
|
||||||
import { renderMedia } from "../../utils/FileUpload";
|
import { renderMedia } from "../../utils/FileUpload";
|
||||||
import { getIconFromHash } from "../../utils/deterministicProfileIcon";
|
import { getIconFromHash } from "../../utils/deterministicProfileIcon";
|
||||||
import { verifyPow } from "../../utils/mine";
|
import { verifyPow } from "../../utils/mine";
|
||||||
|
@ -15,6 +15,7 @@ const LinkModal = ({ url }: { url: string }) => {
|
|||||||
})
|
})
|
||||||
.then((preview) => setLinkPreview(preview as LinkPreview))
|
.then((preview) => setLinkPreview(preview as LinkPreview))
|
||||||
.catch((error) => console.error("Error fetching URL with proxy:", error));
|
.catch((error) => console.error("Error fetching URL with proxy:", error));
|
||||||
|
|
||||||
}, [url]);
|
}, [url]);
|
||||||
|
|
||||||
if (!linkPreview) {
|
if (!linkPreview) {
|
@ -1,8 +1,8 @@
|
|||||||
import { parseContent } from "../../utils/content";
|
import { parseContent } from "../../../utils/content";
|
||||||
import { Event } from "nostr-tools";
|
import { Event } from "nostr-tools";
|
||||||
import { getMetadata, uniqBy } from "../../utils/utils";
|
import { getMetadata, uniqBy } from "../../../utils/utils";
|
||||||
import ContentPreview from "./TextModal";
|
import ContentPreview from "./TextModal";
|
||||||
import { renderMedia } from "../../utils/FileUpload";
|
import { renderMedia } from "../../../utils/FileUpload";
|
||||||
|
|
||||||
const colorCombos = [
|
const colorCombos = [
|
||||||
"from-red-400 to-yellow-500",
|
"from-red-400 to-yellow-500",
|
@ -1,7 +1,7 @@
|
|||||||
import QuoteEmbed from "./QuoteEmbed";
|
import QuoteEmbed from "./QuoteEmbed";
|
||||||
import { Event } from "nostr-tools";
|
import { Event } from "nostr-tools";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { subNoteOnce } from "../../utils/subscriptions";
|
import { subNoteOnce } from "../../../utils/subscriptions";
|
||||||
import { nip19 } from "nostr-tools";
|
import { nip19 } from "nostr-tools";
|
||||||
import LinkModal from "./LinkPreview";
|
import LinkModal from "./LinkPreview";
|
||||||
|
|
@ -59,6 +59,28 @@ export const renderMedia = (file: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export async function attachFile(file_input: File | null): Promise<string> {
|
||||||
|
if (!file_input) {
|
||||||
|
throw new Error("No file provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rx = await FileUpload(file_input);
|
||||||
|
|
||||||
|
if (rx.error) {
|
||||||
|
throw new Error(rx.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rx.url || "No URL returned from FileUpload";
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
throw new Error(`File upload failed: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unknown error occurred during file upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface UploadResult {
|
export interface UploadResult {
|
||||||
url?: string;
|
url?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
42
client/src/utils/useWorkers.ts
Normal file
42
client/src/utils/useWorkers.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { useMemo, useEffect } from 'react';
|
||||||
|
|
||||||
|
interface Dispatch {
|
||||||
|
(arg: { type: string, payload?: any }): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const useWorkers = (dispatch: Dispatch) => {
|
||||||
|
const numCores = navigator.hardwareConcurrency || 4;
|
||||||
|
|
||||||
|
const workers = useMemo(
|
||||||
|
() => Array(numCores).fill(null).map(() => new Worker(new URL("../../powWorker", import.meta.url))),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
workers.forEach((worker) => {
|
||||||
|
worker.onmessage = (event) => {
|
||||||
|
if (event.data.status === 'progress') {
|
||||||
|
dispatch({ type: 'SET_WORK_PROGRESS', payload: event.data.currentNonce });
|
||||||
|
} else if (event.data.found) {
|
||||||
|
dispatch({ type: 'SET_MESSAGE_FROM_WORKER', payload: event.data.event });
|
||||||
|
workers.forEach(w => w.terminate());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDifficultyChange = (event: Event) => {
|
||||||
|
const customEvent = event as CustomEvent;
|
||||||
|
dispatch({ type: 'SET_DIFFICULTY', payload: customEvent.detail });
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('difficultyChanged', handleDifficultyChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('difficultyChanged', handleDifficultyChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return workers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useWorkers;
|
Loading…
Reference in New Issue
Block a user