fix: file paths
This commit is contained in:
parent
d2b8484697
commit
0e04cf827d
@ -7,7 +7,8 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"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": {
|
"dependencies": {
|
||||||
"@noble/hashes": "^1.3.2",
|
"@noble/hashes": "^1.3.2",
|
||||||
|
441
src/page/new.tsx
441
src/page/new.tsx
@ -8,238 +8,241 @@ import * as bencode from "../bencode";
|
|||||||
import { sha1 } from "@noble/hashes/sha1";
|
import { sha1 } from "@noble/hashes/sha1";
|
||||||
import { bytesToHex } from "@noble/hashes/utils";
|
import { bytesToHex } from "@noble/hashes/utils";
|
||||||
import { SnortContext } from "@snort/system-react";
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
async function openFile(): Promise<File | undefined> {
|
async function openFile(): Promise<File | undefined> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const elm = document.createElement("input");
|
const elm = document.createElement("input");
|
||||||
let lock = false;
|
let lock = false;
|
||||||
elm.type = "file";
|
elm.type = "file";
|
||||||
elm.accept = ".torrent";
|
elm.accept = ".torrent";
|
||||||
const handleInput = (e: Event) => {
|
const handleInput = (e: Event) => {
|
||||||
lock = true;
|
lock = true;
|
||||||
const elm = e.target as HTMLInputElement;
|
const elm = e.target as HTMLInputElement;
|
||||||
if ((elm.files?.length ?? 0) > 0) {
|
if ((elm.files?.length ?? 0) > 0) {
|
||||||
resolve(elm.files![0]);
|
resolve(elm.files![0]);
|
||||||
} else {
|
} else {
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
elm.onchange = (e) => handleInput(e);
|
elm.onchange = (e) => handleInput(e);
|
||||||
elm.click();
|
elm.click();
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"focus",
|
"focus",
|
||||||
() => {
|
() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!lock) {
|
if (!lock) {
|
||||||
console.debug("FOCUS WINDOW UPLOAD");
|
console.debug("FOCUS WINDOW UPLOAD");
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
{ once: true },
|
{ once: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NewPage() {
|
export function NewPage() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [obj, setObj] = useState({
|
const [obj, setObj] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
desc: "",
|
desc: "",
|
||||||
btih: "",
|
btih: "",
|
||||||
tags: [] as Array<string>,
|
tags: [] as Array<string>,
|
||||||
files: [] as Array<{
|
files: [] as Array<{
|
||||||
name: string;
|
name: string;
|
||||||
size: number;
|
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;
|
|
||||||
});
|
});
|
||||||
console.debug(ev);
|
|
||||||
|
|
||||||
if (ev) {
|
async function loadTorrent() {
|
||||||
await system.BroadcastEvent(ev);
|
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 {
|
async function publish() {
|
||||||
return (
|
if (!login) return;
|
||||||
<>
|
const signer = new Nip7Signer();
|
||||||
<div className="flex gap-1 bg-slate-500 p-1 rounded">
|
const builder = new EventPublisher(signer, login.publicKey);
|
||||||
<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]))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const ev = await builder.generic((eb) => {
|
||||||
<>
|
const v = eb
|
||||||
<h1>New</h1>
|
.kind(TorrentKind)
|
||||||
<div className="flex gap-1">
|
.content(obj.desc)
|
||||||
<Button onClick={loadTorrent}>Import from Torrent</Button>
|
.tag(["title", obj.name])
|
||||||
<Button>Import from Magnet</Button>
|
.tag(["size", String(obj.files.reduce((acc, v) => (acc += v.size), 0))])
|
||||||
</div>
|
.tag(["btih", obj.btih]);
|
||||||
<h2>Torrent Info</h2>
|
|
||||||
<form className="flex flex-col gap-2">
|
obj.tags.forEach((t) => v.tag(["t", t]));
|
||||||
<div className="flex gap-2">
|
obj.files.forEach((f) => v.tag(["file", f.name, String(f.size)]));
|
||||||
<div className="flex-1 flex flex-col gap-1">
|
|
||||||
<label>Title</label>
|
return v;
|
||||||
<input
|
});
|
||||||
type="text"
|
console.debug(ev);
|
||||||
placeholder="raw noods"
|
|
||||||
value={obj.name}
|
if (ev) {
|
||||||
onChange={(e) => setObj((o) => ({ ...o, name: e.target.value }))}
|
await system.BroadcastEvent(ev);
|
||||||
/>
|
}
|
||||||
<label>Info Hash</label>
|
navigate("/")
|
||||||
<input
|
}
|
||||||
type="text"
|
|
||||||
placeholder="hex"
|
function renderCategories(a: Category, tags: Array<string>): ReactNode {
|
||||||
value={obj.btih}
|
return (
|
||||||
onChange={(e) => setObj((o) => ({ ...o, btih: e.target.value }))}
|
<>
|
||||||
/>
|
<div className="flex gap-1 bg-slate-500 p-1 rounded">
|
||||||
<label>Category</label>
|
<input
|
||||||
<div className="flex flex-col gap-1">
|
type="radio"
|
||||||
{Categories.map((a) => (
|
value={tags.join(",")}
|
||||||
<div className="flex flex-col gap-1">
|
name="category"
|
||||||
<div className="font-bold bg-slate-800 p-1">{a.name}</div>
|
checked={obj.tags.join(",") === tags.join(",")}
|
||||||
<div className="flex gap-1 flex-wrap">{renderCategories(a, [a.tag])}</div>
|
onChange={(e) =>
|
||||||
|
setObj((o) => ({
|
||||||
|
...o,
|
||||||
|
tags: e.target.checked ? dedupe(e.target.value.split(",")) : [],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label>{a?.name}</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
{a.sub_category?.map((b) => renderCategories(b, [...tags, b.tag]))}
|
||||||
</div>
|
</>
|
||||||
</div>
|
);
|
||||||
<div className="flex-1 flex flex-col gap-1">
|
}
|
||||||
<label>Description</label>
|
|
||||||
<textarea
|
return (
|
||||||
rows={20}
|
<>
|
||||||
className="font-mono text-xs"
|
<h1>New</h1>
|
||||||
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">
|
<div className="flex gap-1">
|
||||||
<input
|
<Button onClick={loadTorrent}>Import from Torrent</Button>
|
||||||
type="text"
|
<Button>Import from Magnet</Button>
|
||||||
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>
|
||||||
))}
|
<h2>Torrent Info</h2>
|
||||||
</div>
|
<form className="flex flex-col gap-2">
|
||||||
<Button
|
<div className="flex gap-2">
|
||||||
onClick={() =>
|
<div className="flex-1 flex flex-col gap-1">
|
||||||
setObj((o) => ({
|
<label>Title</label>
|
||||||
...o,
|
<input
|
||||||
files: [...o.files, { name: "", size: 0 }],
|
type="text"
|
||||||
}))
|
placeholder="raw noods"
|
||||||
}
|
value={obj.name}
|
||||||
>
|
onChange={(e) => setObj((o) => ({ ...o, name: e.target.value }))}
|
||||||
Add File
|
/>
|
||||||
</Button>
|
<label>Info Hash</label>
|
||||||
<Button onClick={publish}>Publish</Button>
|
<input
|
||||||
</form>
|
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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user