wip fix
This commit is contained in:
parent
9ba10f56d1
commit
61da09e69b
File diff suppressed because one or more lines are too long
|
|
@ -27,6 +27,25 @@ export const apiProxy = (api_url: string) => {
|
|||
{},
|
||||
{
|
||||
get: (_, actionName: string) => {
|
||||
if (actionName === "_url") {
|
||||
return (pathname: string) => {
|
||||
const to_url = new URL(base_url);
|
||||
to_url.pathname = pathname;
|
||||
|
||||
const cur_url = new URL(location.href);
|
||||
let final_url = "";
|
||||
|
||||
if (to_url.host === cur_url.host) {
|
||||
final_url = to_url.toString();
|
||||
} else {
|
||||
final_url = `${cur_url.protocol}//${
|
||||
cur_url.host
|
||||
}/_proxy/${encodeURIComponent(to_url.toString())}`;
|
||||
}
|
||||
return final_url;
|
||||
};
|
||||
}
|
||||
|
||||
const createFn = (actionName: string) => {
|
||||
return function (
|
||||
this: { api_url: string } | undefined,
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ export const EDGlobal = {
|
|||
tree: [] as NodeModel<FEntry>[],
|
||||
renaming: "",
|
||||
ctx_path: "",
|
||||
selected: new Set<string>(),
|
||||
ctx_menu_event: null as null | React.MouseEvent<
|
||||
HTMLElement,
|
||||
MouseEvent
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { useCallback, useEffect } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
|
||||
import { useGlobal } from "web-utils";
|
||||
import { apiProxy } from "../../../../base/load/api/api-proxy";
|
||||
import { Modal } from "../../../../utils/ui/modal";
|
||||
import { EDGlobal } from "../../logic/ed-global";
|
||||
import { EdFileTree, reloadFileTree } from "./file-tree";
|
||||
import { FEntry } from "./type";
|
||||
import { EdFileList } from "./file-list";
|
||||
import { EdFileTop } from "./file-top";
|
||||
import { EdFileTree, reloadFileTree } from "./file-tree";
|
||||
import { uploadFile } from "./file-upload";
|
||||
import { FEntry } from "./type";
|
||||
|
||||
export const EdFileBrowser = () => {
|
||||
const p = useGlobal(EDGlobal, "EDITOR");
|
||||
|
||||
|
|
@ -23,12 +25,14 @@ export const EdFileBrowser = () => {
|
|||
p.script.api._raw(`/_file/?dir`).then((e: FEntry[]) => {
|
||||
if (Array.isArray(e)) {
|
||||
p.ui.popup.file.entry = { "/": e };
|
||||
|
||||
if (p.ui.popup.file.open) {
|
||||
reloadFileTree(p);
|
||||
}
|
||||
p.ui.popup.file.enabled = true;
|
||||
p.render();
|
||||
}
|
||||
});
|
||||
|
||||
reloadFileTree(p);
|
||||
}, []);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
|
|
@ -45,6 +49,7 @@ export const EdFileBrowser = () => {
|
|||
onClick={() => {
|
||||
p.ui.popup.file.open = true;
|
||||
p.render();
|
||||
reloadFileTree(p);
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
|
|
@ -102,6 +107,7 @@ export const EdFileBrowser = () => {
|
|||
className={cx("flex-1 flex h-full outline-none relative")}
|
||||
{...getRootProps()}
|
||||
>
|
||||
<EdFileList />
|
||||
<input {...getInputProps()} />
|
||||
{isDragActive && (
|
||||
<div className="absolute inset-0 flex items-center justify-center flex-col bg-blue-50 border-4 border-blue-500">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
import {
|
||||
Tree as DNDTree,
|
||||
MultiBackend,
|
||||
NodeModel,
|
||||
getBackendOptions,
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { FC, useCallback, useEffect } from "react";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { useGlobal, useLocal } from "web-utils";
|
||||
import { EDGlobal, PG } from "../../logic/ed-global";
|
||||
import { FEntry } from "./type";
|
||||
|
||||
const Tree = DNDTree<FEntry>;
|
||||
|
||||
export const EdFileList = () => {
|
||||
const p = useGlobal(EDGlobal, "EDITOR");
|
||||
const f = p.ui.popup.file;
|
||||
const list = f.entry[f.path] || [];
|
||||
const local = useLocal({
|
||||
multi: false,
|
||||
square: {
|
||||
started: false,
|
||||
start: { x: 0, y: 0 },
|
||||
cur: { x: 0, y: 0 },
|
||||
box: { x: 0, y: 0, w: 0, h: 0 },
|
||||
},
|
||||
});
|
||||
|
||||
const onKeyDown = useCallback((e: KeyboardEvent) => {
|
||||
if (e.shiftKey || e.ctrlKey || e.metaKey) {
|
||||
local.multi = true;
|
||||
local.render();
|
||||
}
|
||||
}, []);
|
||||
const onKeyUp = useCallback(() => {
|
||||
local.multi = false;
|
||||
local.render();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
window.addEventListener("keyup", onKeyUp);
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
window.removeEventListener("keyup", onKeyUp);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const tree: NodeModel<FEntry>[] = (f.entry[f.path] || [])
|
||||
.filter((e) => e.type === "file")
|
||||
.map((e) => {
|
||||
return { id: e.name, parent: "", text: e.name, data: e };
|
||||
});
|
||||
|
||||
const sq = local.square;
|
||||
return (
|
||||
<div
|
||||
className="flex-1 select-none relative overflow-y-auto"
|
||||
onPointerMove={(e) => {
|
||||
const el = e.currentTarget;
|
||||
if (sq.started) {
|
||||
const box = el.getBoundingClientRect();
|
||||
sq.cur.x = e.clientX - box.x;
|
||||
sq.cur.y = e.clientY + el.scrollTop - box.y;
|
||||
|
||||
if (sq.start.x < sq.cur.x) {
|
||||
sq.box.x = sq.start.x;
|
||||
sq.box.w = sq.cur.x - sq.start.x;
|
||||
} else {
|
||||
sq.box.x = sq.cur.x;
|
||||
sq.box.w = sq.start.x - sq.cur.x;
|
||||
}
|
||||
|
||||
if (sq.start.y < sq.cur.y) {
|
||||
sq.box.y = sq.start.y;
|
||||
sq.box.h = sq.cur.y + -sq.start.y;
|
||||
} else {
|
||||
sq.box.y = sq.cur.y;
|
||||
sq.box.h = sq.start.y - sq.cur.y;
|
||||
}
|
||||
|
||||
if (sq.cur.y - el.scrollTop > box.height * 0.8) {
|
||||
el.scrollTop += 5;
|
||||
} else if (sq.cur.y - el.scrollTop < 50) {
|
||||
el.scrollTop -= 5;
|
||||
}
|
||||
|
||||
local.render();
|
||||
}
|
||||
}}
|
||||
onPointerDown={(e) => {
|
||||
const el = e.currentTarget;
|
||||
const box = el.getBoundingClientRect();
|
||||
sq.started = true;
|
||||
sq.start.x = e.clientX - box.x;
|
||||
sq.start.y = el.scrollTop + e.clientY - box.y;
|
||||
sq.box = { x: 0, y: 0, w: 0, h: 0 };
|
||||
local.render();
|
||||
}}
|
||||
onPointerUp={() => {
|
||||
sq.started = false;
|
||||
local.render();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
"bg-blue-200 border border-blue-500 absolute z-10 bg-opacity-30 transition-opacity pointer-events-none",
|
||||
css`
|
||||
left: ${sq.box.x}px;
|
||||
top: ${sq.box.y}px;
|
||||
width: ${sq.box.w}px;
|
||||
height: ${sq.box.h}px;
|
||||
`,
|
||||
sq.started ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
></div>
|
||||
|
||||
<div
|
||||
className={cx(
|
||||
"absolute inset-0 flex flex-wrap items-start content-start",
|
||||
css`
|
||||
ul {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
`
|
||||
)}
|
||||
onClick={() => {
|
||||
f.selected.clear();
|
||||
local.render();
|
||||
}}
|
||||
>
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<Tree
|
||||
tree={tree}
|
||||
dragPreviewRender={() => <></>}
|
||||
rootId=""
|
||||
onDrop={() => {}}
|
||||
render={(node, {}) => {
|
||||
if (node.data) {
|
||||
return <FileItem p={p} e={node.data} local={local} />;
|
||||
}
|
||||
return <></>;
|
||||
}}
|
||||
onDragStart={() => {
|
||||
local.square.started = false;
|
||||
local.render();
|
||||
}}
|
||||
/>
|
||||
</DndProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FileItem: FC<{
|
||||
p: PG;
|
||||
e: FEntry;
|
||||
local: { multi: boolean; render: () => void };
|
||||
}> = ({ e, local, p }) => {
|
||||
const f = p.ui.popup.file;
|
||||
const ext = e.name.split(".").pop() || "";
|
||||
const item = useLocal({ no_image: false });
|
||||
return (
|
||||
<div
|
||||
key={e.name}
|
||||
className={cx(
|
||||
"flex items-stretch flex-col p-1 m-2 border-2",
|
||||
css`
|
||||
width: 100px;
|
||||
`,
|
||||
f.selected.has(e.name)
|
||||
? "bg-blue-100 border-blue-600"
|
||||
: "border-transparent"
|
||||
)}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
if (!local.multi) {
|
||||
f.selected.clear();
|
||||
}
|
||||
if (!f.selected.has(e.name)) {
|
||||
f.selected.add(e.name);
|
||||
} else {
|
||||
f.selected.delete(e.name);
|
||||
}
|
||||
local.render();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
"flex items-center justify-center flex-1 border",
|
||||
css`
|
||||
min-height: 80px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
{!item.no_image ? (
|
||||
<>
|
||||
{isImage(ext) ? (
|
||||
<img
|
||||
draggable={false}
|
||||
src={p.script.api._url(`/_img/${f.path}/${e.name}?w=100`)}
|
||||
alt={e.name + " thumbnail (100px)"}
|
||||
onError={() => {
|
||||
item.no_image = true;
|
||||
item.render();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="uppercase font-bold text-lg text-slate-300">
|
||||
{ext}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="uppercase font-bold text-lg text-slate-300">
|
||||
NO IMG
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="px-1 mt-2 text-ellipsis overflow-ellipsis whitespace-break-spaces break-words">
|
||||
{e.name.length > 25 ? e.name.substring(0, 25) + "..." : e.name}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const isImage = (ext: string) => {
|
||||
if (["gif", "jpeg", "jpg", "png", "svg", "webp"].includes(ext)) return true;
|
||||
};
|
||||
|
|
@ -43,9 +43,17 @@ export const EdFileTree: FC<{}> = ({}) => {
|
|||
|
||||
return true;
|
||||
}}
|
||||
onDrop={async (newTree, { dropTargetId, dragSourceId }) => {
|
||||
await p.script.api._raw(`/_file${dragSourceId}?move=${dropTargetId}`);
|
||||
await reloadFileTree(p);
|
||||
onDrop={async (newTree, { dropTargetId, dragSourceId, dragSource }) => {
|
||||
if (dragSource) {
|
||||
if (dragSource.data?.type === "file") {
|
||||
|
||||
} else {
|
||||
await p.script.api._raw(
|
||||
`/_file${dragSourceId}?move=${dropTargetId}`
|
||||
);
|
||||
await reloadFileTree(p);
|
||||
}
|
||||
}
|
||||
}}
|
||||
render={(
|
||||
node,
|
||||
|
|
@ -84,6 +92,16 @@ const TreeItem: FC<{
|
|||
}
|
||||
|
||||
f.path = path;
|
||||
|
||||
if (!f.entry[path]) {
|
||||
p.script.api._raw(`/_file${path}/?dir`).then((fe: FEntry[]) => {
|
||||
if (Array.isArray(fe)) {
|
||||
f.entry[path] = fe;
|
||||
p.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
p.render();
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
|
|
@ -274,7 +292,12 @@ export const reloadFileTree = async (p: PG) => {
|
|||
|
||||
if (exp) {
|
||||
for (const [k, v] of Object.entries(p.ui.popup.file.entry)) {
|
||||
if (p.ui.popup.file.entry[k] && !exp.includes(k) && k !== "/") {
|
||||
if (
|
||||
p.ui.popup.file.entry[k] &&
|
||||
!exp.includes(k) &&
|
||||
k !== "/" &&
|
||||
k !== p.ui.popup.file.path
|
||||
) {
|
||||
delete p.ui.popup.file.entry[k];
|
||||
}
|
||||
}
|
||||
|
|
@ -296,6 +319,16 @@ export const reloadFileTree = async (p: PG) => {
|
|||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
const f = p.ui.popup.file;
|
||||
if (!f.entry[f.path]) {
|
||||
p.script.api._raw(`/_file${f.path}/?dir`).then((fe: FEntry[]) => {
|
||||
if (Array.isArray(fe)) {
|
||||
f.entry[f.path] = fe;
|
||||
p.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const tree: NodeModel<FEntry>[] = p.ui.popup.file.tree;
|
||||
|
||||
tree.length = 0;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { fetchViaProxy } from "../../../../base/load/proxy";
|
||||
import { PG } from "../../logic/ed-global";
|
||||
import { reloadFileTree } from "./file-tree";
|
||||
|
||||
export const uploadFile = async (p: PG, files: File[]) => {
|
||||
await p.script.api._raw(`/_upload?to=${p.ui.popup.file.path}`, ...files);
|
||||
|
||||
reloadFileTree(p);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import { apiContext } from "service-srv";
|
||||
import { gzipAsync } from "../../../app/srv/ws/sync/entity/zlib";
|
||||
import { CORS_HEADERS } from "../server/serve-api";
|
||||
import brotliPromise from "brotli-wasm";
|
||||
import { apiContext } from "service-srv";
|
||||
|
||||
const brotli = await brotliPromise;
|
||||
|
||||
|
|
@ -12,11 +10,22 @@ export const _ = {
|
|||
const { req } = apiContext(this);
|
||||
|
||||
try {
|
||||
const url = new URL(decodeURIComponent(req.params["_"]));
|
||||
const url = new URL(
|
||||
decodeURIComponent(decodeURIComponent(req.params["_"]))
|
||||
);
|
||||
const body = await req.arrayBuffer();
|
||||
const headers = {} as Record<string, string>;
|
||||
req.headers.forEach((v, k) => {
|
||||
if (k.startsWith("sec-")) return;
|
||||
if (k.startsWith("connection")) return;
|
||||
if (k.startsWith("dnt")) return;
|
||||
if (k.startsWith("host")) return;
|
||||
headers[k] = v;
|
||||
});
|
||||
|
||||
return await fetch(url, {
|
||||
method: req.method || "POST",
|
||||
headers: req.headers,
|
||||
headers,
|
||||
body,
|
||||
});
|
||||
} catch (e: any) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue