Spaces:
Running
Running
File size: 6,273 Bytes
938d45b d328dbf 90b31da d328dbf 90b31da 938d45b 524e7c0 d328dbf 524e7c0 d328dbf 524e7c0 d328dbf 524e7c0 d328dbf 524e7c0 d328dbf 938d45b 13ae1a3 d328dbf 524e7c0 13ae1a3 524e7c0 90b31da 7ed5764 cb6b8c4 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 1270bff 7ed5764 1270bff 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 90b31da 7ed5764 228c646 7ed5764 13ae1a3 7ed5764 13ae1a3 7ed5764 13ae1a3 5266fe9 13ae1a3 7ed5764 524e7c0 |
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 204 205 |
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 () {
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 pathSlash = path ? `${path}/` : "";
const apiPath = item.type === "directory" ? "/api/dir/delete" : "/api/delete";
await fetch(apiPath, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ path: pathSlash + 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>
);
}
|