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 GraphPane from "./Components/GraphPane.jsx"; import LoginScreen from "./Components/LoginScreen.jsx"; import ErrorToast from "./Components/ErrorToast.jsx"; import { EnsureDirectoryNote, FetchAuthStatus, FetchMe, FetchRoots, FetchSettings, FetchTheme, FetchTree, } 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("/")) : ""; } function BuildLocationPath(RootName, ViewPath, ViewIsDirectory) { const Segments = [RootName, ...ViewPath.split("/").filter(Boolean)]; const Encoded = "/" + Segments.map(encodeURIComponent).join("/"); return ViewIsDirectory ? `${Encoded}/` : Encoded; } function ParseLocationPath(Pathname) { const IsDirectory = Pathname.endsWith("/"); const Segments = Pathname.split("/").filter(Boolean).map(decodeURIComponent); const [RootName, ...RestSegments] = Segments; return { RootName: RootName || null, Path: RestSegments.join("/"), IsDirectory }; } const GuestUser = { Username: null, IsAdmin: false, IsGuest: true, Colour: "Purple" }; export default function App() { const [AuthState, SetAuthState] = useState("Loading"); 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 [ViewNoteFilePath, SetViewNoteFilePath] = useState(null); const [CurrentDirectoryPath, SetCurrentDirectoryPath] = useState(""); const [SidebarKey, SetSidebarKey] = useState(0); const [Settings, SetSettings] = useState(null); const [IsSettingsOpen, SetIsSettingsOpen] = useState(false); const [IsGraphOpen, SetIsGraphOpen] = useState(false); const [ErrorMessage, SetErrorMessage] = useState(null); useEffect(() => { FetchAuthStatus().then((Status) => { if (!Status.SetupComplete) { SetAuthState("Bootstrap"); return; } FetchMe() .then((User) => { SetCurrentUser(User); SetAuthState("LoggedIn"); }) .catch(() => { SetCurrentUser(GuestUser); SetAuthState("Guest"); }); }); }, []); const IsUsable = AuthState === "LoggedIn" || AuthState === "Guest"; useEffect(() => { if (!IsUsable) return; FetchRoots().then((Fetched) => { SetRoots(Fetched); RestoreFromLocation(Fetched); }); FetchSettings().then((FetchedSettings) => { SetSettings(FetchedSettings); ApplyFont(FetchedSettings); FetchTheme(FetchedSettings.ThemeName).then(ApplyTheme); }); }, [AuthState]); useEffect(() => { if (!IsUsable || !ActiveRoot) return; const Url = BuildLocationPath(ActiveRoot, ViewPath, ViewIsDirectory); if (window.location.pathname !== Url) { window.history.pushState({}, "", Url); } }, [AuthState, ActiveRoot, ViewPath, ViewIsDirectory]); useEffect(() => { if (!IsUsable) return; function HandlePopState() { RestoreFromLocation(Roots); } window.addEventListener("popstate", HandlePopState); return () => window.removeEventListener("popstate", HandlePopState); }, [AuthState, Roots]); async function RestoreFromLocation(RootsList) { const { RootName, Path, IsDirectory } = ParseLocationPath(window.location.pathname); const MatchedRoot = RootsList.find((Root) => Root.Name === RootName); if (!MatchedRoot) { if (RootsList.length > 0) { SetActiveRoot(RootsList[0].Name); } return; } SetActiveRoot(RootName); if (!Path) { HandleNavigateDirectory(""); return; } const LastSlash = Path.lastIndexOf("/"); const ParentPath = LastSlash === -1 ? "" : Path.slice(0, LastSlash); const EntryName = LastSlash === -1 ? Path : Path.slice(LastSlash + 1); try { const Entries = await FetchTree(RootName, ParentPath); const Entry = Entries.find((Candidate) => Candidate.Name === EntryName); if (!Entry) { HandleNavigateDirectory(""); return; } if (Entry.IsDirectory) { if (IsDirectory) { HandleNavigateDirectory(Path); } else { OpenDirectoryNote(Path, Entry.Note); } } else { HandleSelectFile(Path, Entry.FileType); } } catch (Error) { HandleNavigateDirectory(""); } } function HandleAuthenticated(User) { SetCurrentUser(User); SetAuthState("LoggedIn"); } function HandleLoggedOut() { SetCurrentUser(null); SetAuthState("LoggedOut"); } function HandleContinueAsGuest() { SetCurrentUser(GuestUser); SetAuthState("Guest"); } function HandleRequestLogin() { 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); SetViewNoteFilePath(null); SetCurrentDirectoryPath(GetParentPath(FilePath)); } function HandleNavigateDirectory(DirectoryPath) { SetViewPath(DirectoryPath); SetViewIsDirectory(true); SetViewFileType(null); SetViewNoteFilePath(null); SetCurrentDirectoryPath(DirectoryPath); } function HandleBrowseDirectory(DirectoryPath) { SetCurrentDirectoryPath(DirectoryPath); } function HandleSelectDirectoryNote(DirectoryPath, NoteName, NoteFileType) { SetViewPath(DirectoryPath); SetViewIsDirectory(false); SetViewFileType(NoteFileType); SetViewNoteFilePath(`${DirectoryPath}/${NoteName}`); SetCurrentDirectoryPath(DirectoryPath); } async function OpenDirectoryNote(DirectoryPath, Note) { if (Note) { HandleSelectDirectoryNote(DirectoryPath, Note.Name, Note.FileType); return; } try { const Created = await EnsureDirectoryNote(ActiveRoot, DirectoryPath); HandleSelectDirectoryNote(DirectoryPath, Created.Name, Created.FileType); } catch (Error) { HandleNavigateDirectory(DirectoryPath); } } function HandleBreadcrumbNavigate(Path) { if (!Path) { HandleNavigateDirectory(""); } else { OpenDirectoryNote(Path, null); } } function HandleOpenGraphNode(Node) { SetIsGraphOpen(false); if (Node.IsDirectory) { HandleBreadcrumbNavigate(Node.Id); } else { HandleSelectFile(Node.Id, Node.FileType); } } function HandleEntrySelect(EntryPath, IsDirectory, FileType, Note) { if (IsDirectory) { OpenDirectoryNote(EntryPath, Note); } else { HandleSelectFile(EntryPath, FileType); } } function RefreshSidebar() { SetSidebarKey((Key) => Key + 1); } function HandleEntryRenamed(OldPath, NewPath, IsDirectory) { RefreshSidebar(); if (ViewNoteFilePath && ViewPath === OldPath) { OpenDirectoryNote(NewPath, null); } else 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 ; } if (AuthState === "LoggedOut") { return ( ); } const IsGuest = AuthState === "Guest"; const ShowTopBar = (Settings && Settings.ShowBreadcrumbs) || Roots.length > 1; const ConfirmBeforeDelete = Settings ? Settings.ConfirmBeforeDelete : true; return (
SetIsSettingsOpen(true)} OnToggleGraph={() => SetIsGraphOpen(!IsGraphOpen)} IsGraphOpen={IsGraphOpen} OnRequestLogin={HandleRequestLogin} OnEntryCreated={RefreshSidebar} UseHyphenBullets={Settings ? Settings.UseHyphenBullets : true} UseAsciiFolderIcons={Settings ? Settings.UseAsciiFolderIcons : true} NavigateIntoDirectories={Settings ? Settings.NavigateIntoDirectories : false} NewEntryButtonsAtTop={Settings ? Settings.NewEntryButtonsAtTop : false} ConfirmBeforeDelete={ConfirmBeforeDelete} />
{ShowTopBar && (
{Settings && Settings.ShowBreadcrumbs ? ( ) : ( )} {Roots.length > 1 && ( )}
)}
{IsGraphOpen ? ( SetIsGraphOpen(false)} /> ) : ViewIsDirectory ? ( ) : ( )} {!IsGraphOpen && !ViewIsDirectory && !ViewNoteFilePath && ( )}
{IsSettingsOpen && ( SetIsSettingsOpen(false)} OnSettingsChanged={HandleSettingsChanged} OnLoggedOut={HandleLoggedOut} OnProfileUpdated={HandleProfileUpdated} /> )} SetErrorMessage(null)} />
); }