fix: file paths

This commit is contained in:
Kieran 2023-11-27 13:39:30 +00:00
parent d2b8484697
commit 0e04cf827d
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
2 changed files with 224 additions and 220 deletions

View File

@ -7,7 +7,8 @@
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"deploy": "yarn build && yarn dlx wrangler pages deploy dist"
},
"dependencies": {
"@noble/hashes": "^1.3.2",

View File

@ -8,238 +8,241 @@ import * as bencode from "../bencode";
import { sha1 } from "@noble/hashes/sha1";
import { bytesToHex } from "@noble/hashes/utils";
import { SnortContext } from "@snort/system-react";
import { useNavigate } from "react-router-dom";
async function openFile(): Promise<File | undefined> {
return new Promise((resolve) => {
const elm = document.createElement("input");
let lock = false;
elm.type = "file";
elm.accept = ".torrent";
const handleInput = (e: Event) => {
lock = true;
const elm = e.target as HTMLInputElement;
if ((elm.files?.length ?? 0) > 0) {
resolve(elm.files![0]);
} else {
resolve(undefined);
}
};
return new Promise((resolve) => {
const elm = document.createElement("input");
let lock = false;
elm.type = "file";
elm.accept = ".torrent";
const handleInput = (e: Event) => {
lock = true;
const elm = e.target as HTMLInputElement;
if ((elm.files?.length ?? 0) > 0) {
resolve(elm.files![0]);
} else {
resolve(undefined);
}
};
elm.onchange = (e) => handleInput(e);
elm.click();
window.addEventListener(
"focus",
() => {
setTimeout(() => {
if (!lock) {
console.debug("FOCUS WINDOW UPLOAD");
resolve(undefined);
}
}, 300);
},
{ once: true },
);
});
elm.onchange = (e) => handleInput(e);
elm.click();
window.addEventListener(
"focus",
() => {
setTimeout(() => {
if (!lock) {
console.debug("FOCUS WINDOW UPLOAD");
resolve(undefined);
}
}, 300);
},
{ once: true },
);
});
}
export function NewPage() {
const login = useLogin();
const system = useContext(SnortContext);
const login = useLogin();
const system = useContext(SnortContext);
const navigate = useNavigate();
const [obj, setObj] = useState({
name: "",
desc: "",
btih: "",
tags: [] as Array<string>,
files: [] as Array<{
name: string;
size: number;
}>,
});
async function loadTorrent() {
const f = await openFile();
if (f) {
const buf = await f.arrayBuffer();
const torrent = bencode.decode(new Uint8Array(buf)) as Record<string, bencode.BencodeValue>;
const infoBuf = bencode.encode(torrent["info"]);
console.debug(torrent);
const dec = new TextDecoder();
const info = torrent["info"] as {
files?: Array<{ length: number; path: Array<Uint8Array> }>;
length: number;
name: Uint8Array;
};
setObj({
name: dec.decode(info.name),
desc: dec.decode(torrent["comment"] as Uint8Array | undefined) ?? "",
btih: bytesToHex(sha1(infoBuf)),
tags: [],
files: (info.files ?? [{ length: info.length, path: [info.name] }]).map((a) => ({
size: a.length,
name: dec.decode(a.path[0]),
})),
});
}
}
async function publish() {
if (!login) return;
const signer = new Nip7Signer();
const builder = new EventPublisher(signer, login.publicKey);
const ev = await builder.generic((eb) => {
const v = eb
.kind(TorrentKind)
.content(obj.desc)
.tag(["title", obj.name])
.tag(["size", String(obj.files.reduce((acc, v) => (acc += v.size), 0))])
.tag(["btih", obj.btih]);
obj.tags.forEach((t) => v.tag(["t", t]));
obj.files.forEach((f) => v.tag(["file", f.name, String(f.size)]));
return v;
const [obj, setObj] = useState({
name: "",
desc: "",
btih: "",
tags: [] as Array<string>,
files: [] as Array<{
name: string;
size: number;
}>,
});
console.debug(ev);
if (ev) {
await system.BroadcastEvent(ev);
async function loadTorrent() {
const f = await openFile();
if (f) {
const buf = await f.arrayBuffer();
const torrent = bencode.decode(new Uint8Array(buf)) as Record<string, bencode.BencodeValue>;
const infoBuf = bencode.encode(torrent["info"]);
console.debug(torrent);
const dec = new TextDecoder();
const info = torrent["info"] as {
files?: Array<{ length: number; path: Array<Uint8Array> }>;
length: number;
name: Uint8Array;
};
setObj({
name: dec.decode(info.name),
desc: dec.decode(torrent["comment"] as Uint8Array | undefined) ?? "",
btih: bytesToHex(sha1(infoBuf)),
tags: [],
files: (info.files ?? [{ length: info.length, path: [info.name] }]).map((a) => ({
size: a.length,
name: a.path.map(b => dec.decode(b)).join("/"),
})),
});
}
}
}
function renderCategories(a: Category, tags: Array<string>): ReactNode {
return (
<>
<div className="flex gap-1 bg-slate-500 p-1 rounded">
<input
type="radio"
value={tags.join(",")}
name="category"
checked={obj.tags.join(",") === tags.join(",")}
onChange={(e) =>
setObj((o) => ({
...o,
tags: e.target.checked ? dedupe(e.target.value.split(",")) : [],
}))
}
/>
<label>{a?.name}</label>
</div>
{a.sub_category?.map((b) => renderCategories(b, [...tags, b.tag]))}
</>
);
}
async function publish() {
if (!login) return;
const signer = new Nip7Signer();
const builder = new EventPublisher(signer, login.publicKey);
return (
<>
<h1>New</h1>
<div className="flex gap-1">
<Button onClick={loadTorrent}>Import from Torrent</Button>
<Button>Import from Magnet</Button>
</div>
<h2>Torrent Info</h2>
<form className="flex flex-col gap-2">
<div className="flex gap-2">
<div className="flex-1 flex flex-col gap-1">
<label>Title</label>
<input
type="text"
placeholder="raw noods"
value={obj.name}
onChange={(e) => setObj((o) => ({ ...o, name: e.target.value }))}
/>
<label>Info Hash</label>
<input
type="text"
placeholder="hex"
value={obj.btih}
onChange={(e) => setObj((o) => ({ ...o, btih: e.target.value }))}
/>
<label>Category</label>
<div className="flex flex-col gap-1">
{Categories.map((a) => (
<div className="flex flex-col gap-1">
<div className="font-bold bg-slate-800 p-1">{a.name}</div>
<div className="flex gap-1 flex-wrap">{renderCategories(a, [a.tag])}</div>
const ev = await builder.generic((eb) => {
const v = eb
.kind(TorrentKind)
.content(obj.desc)
.tag(["title", obj.name])
.tag(["size", String(obj.files.reduce((acc, v) => (acc += v.size), 0))])
.tag(["btih", obj.btih]);
obj.tags.forEach((t) => v.tag(["t", t]));
obj.files.forEach((f) => v.tag(["file", f.name, String(f.size)]));
return v;
});
console.debug(ev);
if (ev) {
await system.BroadcastEvent(ev);
}
navigate("/")
}
function renderCategories(a: Category, tags: Array<string>): ReactNode {
return (
<>
<div className="flex gap-1 bg-slate-500 p-1 rounded">
<input
type="radio"
value={tags.join(",")}
name="category"
checked={obj.tags.join(",") === tags.join(",")}
onChange={(e) =>
setObj((o) => ({
...o,
tags: e.target.checked ? dedupe(e.target.value.split(",")) : [],
}))
}
/>
<label>{a?.name}</label>
</div>
))}
</div>
</div>
<div className="flex-1 flex flex-col gap-1">
<label>Description</label>
<textarea
rows={20}
className="font-mono text-xs"
value={obj.desc}
onChange={(e) => setObj((o) => ({ ...o, desc: e.target.value }))}
></textarea>
</div>
</div>
<h2>Files</h2>
<div className="flex flex-col gap-2">
{obj.files.map((a, i) => (
{a.sub_category?.map((b) => renderCategories(b, [...tags, b.tag]))}
</>
);
}
return (
<>
<h1>New</h1>
<div className="flex gap-1">
<input
type="text"
value={a.name}
className="flex-1"
placeholder="collection1/IMG_00001.jpg"
onChange={(e) =>
setObj((o) => ({
...o,
files: o.files.map((f, ii) => {
if (ii === i) {
return { ...f, name: e.target.value };
}
return f;
}),
}))
}
/>
<input
type="number"
value={a.size}
min={0}
placeholder="69000"
onChange={(e) =>
setObj((o) => ({
...o,
files: o.files.map((f, ii) => {
if (ii === i) {
return { ...f, size: Number(e.target.value) };
}
return f;
}),
}))
}
/>
<Button
onClick={() =>
setObj((o) => ({
...o,
files: o.files.filter((_, ii) => i !== ii),
}))
}
>
Remove
</Button>
<Button onClick={loadTorrent}>Import from Torrent</Button>
<Button>Import from Magnet</Button>
</div>
))}
</div>
<Button
onClick={() =>
setObj((o) => ({
...o,
files: [...o.files, { name: "", size: 0 }],
}))
}
>
Add File
</Button>
<Button onClick={publish}>Publish</Button>
</form>
</>
);
<h2>Torrent Info</h2>
<form className="flex flex-col gap-2">
<div className="flex gap-2">
<div className="flex-1 flex flex-col gap-1">
<label>Title</label>
<input
type="text"
placeholder="raw noods"
value={obj.name}
onChange={(e) => setObj((o) => ({ ...o, name: e.target.value }))}
/>
<label>Info Hash</label>
<input
type="text"
placeholder="hex"
value={obj.btih}
onChange={(e) => setObj((o) => ({ ...o, btih: e.target.value }))}
/>
<label>Category</label>
<div className="flex flex-col gap-1">
{Categories.map((a) => (
<div className="flex flex-col gap-1">
<div className="font-bold bg-slate-800 p-1">{a.name}</div>
<div className="flex gap-1 flex-wrap">{renderCategories(a, [a.tag])}</div>
</div>
))}
</div>
</div>
<div className="flex-1 flex flex-col gap-1">
<label>Description</label>
<textarea
rows={20}
className="font-mono text-xs"
value={obj.desc}
onChange={(e) => setObj((o) => ({ ...o, desc: e.target.value }))}
></textarea>
</div>
</div>
<h2>Files</h2>
<div className="flex flex-col gap-2">
{obj.files.map((a, i) => (
<div className="flex gap-1">
<input
type="text"
value={a.name}
className="flex-1"
placeholder="collection1/IMG_00001.jpg"
onChange={(e) =>
setObj((o) => ({
...o,
files: o.files.map((f, ii) => {
if (ii === i) {
return { ...f, name: e.target.value };
}
return f;
}),
}))
}
/>
<input
type="number"
value={a.size}
min={0}
placeholder="69000"
onChange={(e) =>
setObj((o) => ({
...o,
files: o.files.map((f, ii) => {
if (ii === i) {
return { ...f, size: Number(e.target.value) };
}
return f;
}),
}))
}
/>
<Button
onClick={() =>
setObj((o) => ({
...o,
files: o.files.filter((_, ii) => i !== ii),
}))
}
>
Remove
</Button>
</div>
))}
</div>
<Button
onClick={() =>
setObj((o) => ({
...o,
files: [...o.files, { name: "", size: 0 }],
}))
}
>
Add File
</Button>
<Button onClick={publish}>Publish</Button>
</form>
</>
);
}