Spaces:
Running
Running
File size: 6,223 Bytes
dc0212e 5b693bc 0234ec8 5b693bc 0234ec8 dc0212e 5fe7b6c 5b693bc 5fe7b6c 5b693bc 5fe7b6c 5b693bc 5fe7b6c 5b693bc 5fe7b6c 5b693bc dc0212e cd2383d 5b693bc 5fe7b6c cd2383d 5fe7b6c da1f2d0 0234ec8 d460634 a6087b8 d460634 cd2383d d460634 cd2383d d460634 cd2383d d460634 cd2383d d460634 cd2383d d460634 cd2383d d460634 cd2383d d460634 cd2383d d460634 a112474 d460634 aac34ad d460634 cd2383d d460634 cd2383d d460634 0234ec8 d460634 cfe232b d460634 cd2383d d460634 cd2383d d460634 cd2383d a70995e cd2383d d460634 5fe7b6c |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
import { useState } from "react";
// The directory browser.
import { Link, useNavigate } from "react-router";
import useSWR from "swr";
import type { DirectoryEntry } from "./apiTypes.ts";
import { usePath } from "./common.ts";
// @ts-ignore
import File from "~icons/tabler/file";
// @ts-ignore
import FilePlus from "~icons/tabler/file-plus";
// @ts-ignore
import Folder from "~icons/tabler/folder";
// @ts-ignore
import FolderPlus from "~icons/tabler/folder-plus";
// @ts-ignore
import Home from "~icons/tabler/home";
// @ts-ignore
import LayoutGrid from "~icons/tabler/layout-grid";
// @ts-ignore
import LayoutGridAdd from "~icons/tabler/layout-grid-add";
// @ts-ignore
import Trash from "~icons/tabler/trash";
import logo from "./assets/logo.png";
function EntryCreator(props: {
label: string;
icon: JSX.Element;
onCreate: (name: string) => void;
}) {
const [isCreating, setIsCreating] = useState(false);
return (
<>
{isCreating ? (
<form
onSubmit={(e) => {
e.preventDefault();
props.onCreate((e.target as HTMLFormElement).entryName.value.trim());
}}
>
<input
className="input input-ghost w-full"
autoFocus
type="text"
name="entryName"
onBlur={() => setIsCreating(false)}
placeholder={`${props.label} name`}
/>
</form>
) : (
<button type="button" onClick={() => setIsCreating(true)}>
{props.icon} {props.label}
</button>
)}
</>
);
}
const fetcher = (url: string) => fetch(url).then((res) => res.json());
export default function Directory() {
const path = usePath().replace(/^[/]$|^[/]dir$|^[/]dir[/]/, "");
const encodedPath = encodeURIComponent(path || "");
const list = useSWR(`/api/dir/list?path=${encodedPath}`, fetcher, {
dedupingInterval: 0,
});
const navigate = useNavigate();
function link(item: DirectoryEntry) {
if (item.type === "directory") {
return `/dir/${item.name}`;
}
if (item.type === "workspace") {
return `/edit/${item.name}`;
}
return `/code/${item.name}`;
}
function shortName(item: DirectoryEntry) {
return item.name
.split("/")
.pop()
?.replace(/[.]lynxkite[.]json$/, "");
}
function newWorkspaceIn(path: string, workspaceName: string) {
const pathSlash = path ? `${path}/` : "";
navigate(`/edit/${pathSlash}${workspaceName}.lynxkite.json`, { replace: true });
}
function newCodeFile(path: string, name: string) {
const pathSlash = path ? `${path}/` : "";
navigate(`/code/${pathSlash}${name}`, { replace: true });
}
async function newFolderIn(path: string, folderName: string) {
const pathSlash = path ? `${path}/` : "";
const res = await fetch("/api/dir/mkdir", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path: pathSlash + folderName }),
});
if (res.ok) {
navigate(`/dir/${pathSlash}${folderName}`);
} else {
alert("Failed to create folder.");
}
}
async function deleteItem(item: DirectoryEntry) {
if (!window.confirm(`Are you sure you want to delete "${item.name}"?`)) return;
const apiPath = item.type === "directory" ? "/api/dir/delete" : "/api/delete";
await fetch(apiPath, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path: item.name }),
});
}
return (
<div className="directory">
<div className="logo">
<a href="https://lynxkite.com/">
<img src={logo} className="logo-image" alt="LynxKite logo" />
</a>
<div className="tagline">The Complete Graph Data Science Platform</div>
</div>
<div className="entry-list">
{list.error && <p className="error">{list.error.message}</p>}
{list.isLoading && (
<output className="loading spinner-border">
<span className="visually-hidden">Loading...</span>
</output>
)}
{list.data && (
<>
<div className="actions">
<EntryCreator
onCreate={(name) => {
newWorkspaceIn(path || "", name);
}}
icon={<LayoutGridAdd />}
label="New workspace"
/>
<EntryCreator
onCreate={(name) => {
newCodeFile(path || "", name);
}}
icon={<FilePlus />}
label="New code file"
/>
<EntryCreator
onCreate={(name: string) => {
newFolderIn(path || "", name);
}}
icon={<FolderPlus />}
label="New folder"
/>
</div>
{path ? (
<div className="breadcrumbs">
<Link to="/dir/" aria-label="home">
<Home />
</Link>{" "}
<span className="current-folder">{path}</span>
<title>{path}</title>
</div>
) : (
<title>LynxKite 2000:MM</title>
)}
{list.data.map(
(item: DirectoryEntry) =>
!shortName(item)?.startsWith("__") && (
<div key={item.name} className="entry">
<Link key={link(item)} to={link(item)}>
{item.type === "directory" ? (
<Folder />
) : item.type === "workspace" ? (
<LayoutGrid />
) : (
<File />
)}
<span className="entry-name">{shortName(item)}</span>
</Link>
<button
type="button"
onClick={() => {
deleteItem(item);
}}
>
<Trash />
</button>
</div>
),
)}
</>
)}
</div>{" "}
</div>
);
}
|