Initial commit

This commit is contained in:
sarah committed 2026-09-19 14:53:01 +02:00
commit 8aa223bc7b
40 files changed
+4320

No files matched your search

+258
View File
@@ -0,0 +1,258 @@
import React, { useEffect, useState } from "react";
import Sidebar from "./Components/Sidebar.jsx";
import Breadcrumb from "./Components/Breadcrumb.jsx";
import PreviewPane from "./Components/PreviewPane.jsx";
import DirectoryPane from "./Components/DirectoryPane.jsx";
import TagPanel from "./Components/TagPanel.jsx";
import SettingsPanel from "./Components/SettingsPanel.jsx";
import LoginScreen from "./Components/LoginScreen.jsx";
import { FetchAuthStatus, FetchMe, FetchRoots, FetchSettings, FetchTheme } from "./Api.js";
import { PaletteRoles } from "./ThemeRoles.js";
function ApplyTheme(Palette) {
const RootStyle = document.documentElement.style;
PaletteRoles.forEach((Role) => {
if (Palette[Role]) {
RootStyle.setProperty(`--${Role}`, Palette[Role]);
}
});
}
function ApplyFont(Settings) {
const RootStyle = document.documentElement.style;
RootStyle.setProperty(
"--FontFamily",
`"${Settings.FontFamily}", "Cascadia Mono", monospace`
);
RootStyle.setProperty("--FontSize", `${Settings.FontSize}px`);
}
function GetParentPath(FilePath) {
return FilePath.includes("/") ? FilePath.slice(0, FilePath.lastIndexOf("/")) : "";
}
export default function App() {
const [AuthState, SetAuthState] = useState("Loading"); // Loading | Bootstrap | LoggedOut | LoggedIn
const [CurrentUser, SetCurrentUser] = useState(null);
const [Roots, SetRoots] = useState([]);
const [ActiveRoot, SetActiveRoot] = useState(null);
const [ViewPath, SetViewPath] = useState("");
const [ViewIsDirectory, SetViewIsDirectory] = useState(true);
const [ViewFileType, SetViewFileType] = useState(null);
const [CurrentDirectoryPath, SetCurrentDirectoryPath] = useState("");
const [SidebarKey, SetSidebarKey] = useState(0);
const [Settings, SetSettings] = useState(null);
const [IsSettingsOpen, SetIsSettingsOpen] = useState(false);
useEffect(() => {
FetchAuthStatus().then((Status) => {
if (!Status.SetupComplete) {
SetAuthState("Bootstrap");
return;
}
FetchMe()
.then((User) => {
SetCurrentUser(User);
SetAuthState("LoggedIn");
})
.catch(() => SetAuthState("LoggedOut"));
});
}, []);
useEffect(() => {
if (AuthState !== "LoggedIn") return;
FetchRoots().then((Fetched) => {
SetRoots(Fetched);
if (Fetched.length > 0) {
SetActiveRoot(Fetched[0].Name);
}
});
FetchSettings().then((FetchedSettings) => {
SetSettings(FetchedSettings);
ApplyFont(FetchedSettings);
FetchTheme(FetchedSettings.ThemeName).then(ApplyTheme);
});
}, [AuthState]);
function HandleAuthenticated(User) {
SetCurrentUser(User);
SetAuthState("LoggedIn");
}
function HandleLoggedOut() {
SetCurrentUser(null);
SetAuthState("LoggedOut");
}
function HandleProfileUpdated(UpdatedUser) {
SetCurrentUser((Existing) => ({ ...Existing, ...UpdatedUser }));
}
function HandleSettingsChanged(NewSettings) {
SetSettings(NewSettings);
ApplyFont(NewSettings);
FetchTheme(NewSettings.ThemeName).then(ApplyTheme);
SetSidebarKey((Key) => Key + 1);
}
function HandleSelectFile(FilePath, FileType) {
SetViewPath(FilePath);
SetViewIsDirectory(false);
SetViewFileType(FileType);
SetCurrentDirectoryPath(GetParentPath(FilePath));
}
function HandleNavigateDirectory(DirectoryPath) {
SetViewPath(DirectoryPath);
SetViewIsDirectory(true);
SetViewFileType(null);
SetCurrentDirectoryPath(DirectoryPath);
}
function HandleBrowseDirectory(DirectoryPath) {
SetCurrentDirectoryPath(DirectoryPath);
}
function HandleEntrySelect(EntryPath, IsDirectory, FileType) {
if (IsDirectory) {
HandleNavigateDirectory(EntryPath);
} else {
HandleSelectFile(EntryPath, FileType);
}
}
function RefreshSidebar() {
SetSidebarKey((Key) => Key + 1);
}
function HandleEntryRenamed(OldPath, NewPath, IsDirectory) {
RefreshSidebar();
if (!ViewIsDirectory && ViewPath === OldPath) {
HandleSelectFile(NewPath, ViewFileType);
} else if (ViewIsDirectory && ViewPath === OldPath) {
HandleNavigateDirectory(NewPath);
} else if (CurrentDirectoryPath === OldPath) {
SetCurrentDirectoryPath(NewPath);
}
}
function HandleEntryDeleted(DeletedPath) {
RefreshSidebar();
if (ViewPath === DeletedPath || ViewPath.startsWith(`${DeletedPath}/`)) {
HandleNavigateDirectory(GetParentPath(DeletedPath));
} else if (CurrentDirectoryPath === DeletedPath || CurrentDirectoryPath.startsWith(`${DeletedPath}/`)) {
SetCurrentDirectoryPath(GetParentPath(DeletedPath));
}
}
if (AuthState === "Loading") {
return null;
}
if (AuthState === "Bootstrap") {
return <LoginScreen IsBootstrap OnAuthenticated={HandleAuthenticated} />;
}
if (AuthState === "LoggedOut") {
return <LoginScreen IsBootstrap={false} OnAuthenticated={HandleAuthenticated} />;
}
// Only take up a TopBar row at all if there's something to put in it —
// otherwise turning breadcrumbs off with a single root left a blank strip.
const ShowTopBar = (Settings && Settings.ShowBreadcrumbs) || Roots.length > 1;
const ConfirmBeforeDelete = Settings ? Settings.ConfirmBeforeDelete : true;
return (
<div className="AppShell">
<Sidebar
key={SidebarKey}
RootName={ActiveRoot}
SelectedFile={ViewIsDirectory ? null : ViewPath}
CurrentDirectoryPath={CurrentDirectoryPath}
CurrentUser={CurrentUser}
OnSelectFile={HandleSelectFile}
OnNavigateDirectory={HandleNavigateDirectory}
OnBrowseDirectory={HandleBrowseDirectory}
OnEntryRenamed={HandleEntryRenamed}
OnEntryDeleted={HandleEntryDeleted}
OnOpenSettings={() => SetIsSettingsOpen(true)}
OnEntryCreated={RefreshSidebar}
UseHyphenBullets={Settings ? Settings.UseHyphenBullets : true}
UseAsciiFolderIcons={Settings ? Settings.UseAsciiFolderIcons : true}
NavigateIntoDirectories={Settings ? Settings.NavigateIntoDirectories : false}
NewEntryButtonsAtTop={Settings ? Settings.NewEntryButtonsAtTop : false}
ConfirmBeforeDelete={ConfirmBeforeDelete}
/>
<div className="MainColumn">
{ShowTopBar && (
<div className="TopBar">
{Settings && Settings.ShowBreadcrumbs ? (
<Breadcrumb
RootName={ActiveRoot}
FilePath={ViewPath}
IsDirectory={ViewIsDirectory}
OnNavigate={HandleNavigateDirectory}
/>
) : (
<span />
)}
{Roots.length > 1 && (
<select
className="RootSwitcher"
value={ActiveRoot || ""}
onChange={(Event) => SetActiveRoot(Event.target.value)}
>
{Roots.map((Root) => (
<option key={Root.Name} value={Root.Name}>
{Root.Name}
</option>
))}
</select>
)}
</div>
)}
<div className="MainBody">
{ViewIsDirectory ? (
<DirectoryPane
RootName={ActiveRoot}
DirectoryPath={ViewPath}
OnEntrySelect={HandleEntrySelect}
OnEntryCreated={RefreshSidebar}
OnEntryRenamed={HandleEntryRenamed}
OnEntryDeleted={HandleEntryDeleted}
UseHyphenBullets={Settings ? Settings.UseHyphenBullets : true}
UseAsciiFolderIcons={Settings ? Settings.UseAsciiFolderIcons : true}
ConfirmBeforeDelete={ConfirmBeforeDelete}
/>
) : (
<PreviewPane
RootName={ActiveRoot}
FilePath={ViewPath}
FileType={ViewFileType}
OnNavigateLink={HandleSelectFile}
ShowGitBlame={Settings ? Settings.ShowGitBlame : false}
StickyToolbar={Settings ? Settings.StickyToolbar : true}
/>
)}
{!ViewIsDirectory && <TagPanel RootName={ActiveRoot} FilePath={ViewPath} />}
</div>
</div>
{IsSettingsOpen && (
<SettingsPanel
CurrentUser={CurrentUser}
OnClose={() => SetIsSettingsOpen(false)}
OnSettingsChanged={HandleSettingsChanged}
OnLoggedOut={HandleLoggedOut}
OnProfileUpdated={HandleProfileUpdated}
/>
)}
</div>
);
}