diff --git a/Client/Browser.py b/Client/Browser.py index 06f1b68..b4f4f1d 100644 --- a/Client/Browser.py +++ b/Client/Browser.py @@ -68,11 +68,13 @@ class Browser(ft.Column): ), items=[], ) + # Shown with the sidebar tree, above or below it (Filetree.SetEntryButtons). + self.EntryButtons = ft.Container( + ft.Row([self.NewFileButton, self.NewDirectoryButton, self.UploadButton], wrap=True, spacing=6, run_spacing=6), + padding=ft.Padding.symmetric(horizontal=8, vertical=8), + ) self.ActionsToolbar = ft.Row( [ - self.NewFileButton, - self.NewDirectoryButton, - self.UploadButton, ft.IconButton(icon=ft.Icons.HUB_OUTLINED, icon_color=Palette.Mauve, tooltip="Graph (Ctrl+G)", on_click=lambda e: self.ShowGraph()), ft.IconButton(icon=ft.Icons.REFRESH, icon_color=Palette.Mauve, tooltip="Refresh (Ctrl+R)", diff --git a/Client/Core.py b/Client/Core.py index c12369d..8d088d4 100644 --- a/Client/Core.py +++ b/Client/Core.py @@ -173,6 +173,9 @@ class AppConfig: ShowSidebar: bool = True SidebarWidth: int = 240 ShowFilesInTree: bool = True # False: the sidebar tree shows directories only + # New File / New Directory / Upload: above the sidebar tree, or below it + # (the web view's setting of the same name). + NewEntryButtonsAtTop: bool = False FontSize: int = 14 # file list and viewer text # "Separate": Markdown opens rendered, with Edit/View to switch to the raw # text. "Live": rendered, and clicking a block shows its Markdown to edit diff --git a/Client/Filetree.py b/Client/Filetree.py index 69ee1f4..6c4dbd8 100644 --- a/Client/Filetree.py +++ b/Client/Filetree.py @@ -124,15 +124,22 @@ class Filetree(ft.Container): self.PathNodes: list[_FiletreeNode] = [] # the directories you're in, marked self.TreeList = ft.ListView(expand=True) self.SourceLabel = ft.Text("", size=12, color=Palette.Overlay0, overflow=ft.TextOverflow.ELLIPSIS, max_lines=1) + self.Header = ft.Container(self.SourceLabel, padding=ft.Padding.only(left=12, right=12, top=10, bottom=4)) + self.Layout = ft.Column([self.Header, self.TreeList], spacing=0, expand=True) super().__init__( - content=ft.Column([ - ft.Container(self.SourceLabel, padding=ft.Padding.only(left=12, right=12, top=10, bottom=4)), - self.TreeList, - ], spacing=0, expand=True), + content=self.Layout, width=TheApp.Config.SidebarWidth, bgcolor=Palette.Mantle, ) + def SetEntryButtons(self, Buttons: ft.Control, AtTop: bool, Update: bool = True) -> None: + """New File / New Directory / Upload, above or below the tree (Config.NewEntryButtonsAtTop).""" + Divider = ft.Divider(height=1, color=Palette.Surface1) + self.Layout.controls = ([self.Header, Buttons, Divider, self.TreeList] if AtTop + else [self.Header, self.TreeList, Divider, Buttons]) + if Update: + self.update() + def Reload(self) -> None: """Rebuilds the tree from the current source (after connecting, logging in or out, or a change that moved things around).""" diff --git a/Client/Main.py b/Client/Main.py index db8da2c..7aa0c54 100644 --- a/Client/Main.py +++ b/Client/Main.py @@ -66,6 +66,7 @@ def Main(Page: ft.Page) -> None: def OnSettingsSaved(Reconnect: bool) -> None: Tree.visible = Divider.visible = Config.ShowSidebar + Tree.SetEntryButtons(TheBrowser.EntryButtons, Config.NewEntryButtonsAtTop, Update=False) if Reconnect: TheApp.Connect() # its listeners reload the tree and the browser else: @@ -92,6 +93,7 @@ def Main(Page: ft.Page) -> None: lambda Category=None: ShowSettings(Category or ("Connection" if TheApp.ConnectError else "General")), ToggleSidebar) Tree = Filetree(TheApp, TheBrowser.NavigateTo, TheBrowser.OpenFile) + Tree.SetEntryButtons(TheBrowser.EntryButtons, Config.NewEntryButtonsAtTop, Update=False) Divider = ResizeHandle(OnDrag, OnDragEnd) Tree.visible = Divider.visible = Config.ShowSidebar # STRETCH so the divider gets the full height to be dragged along. diff --git a/Client/Settings.py b/Client/Settings.py index c77f58b..c2a17eb 100644 --- a/Client/Settings.py +++ b/Client/Settings.py @@ -101,6 +101,8 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego ConfirmDeleteSwitch = ft.Switch(label="Ask before deleting", value=Config.ConfirmDelete) SidebarSwitch = ft.Switch(label="Show the directory tree (Ctrl+B)", value=Config.ShowSidebar) TreeFilesSwitch = ft.Switch(label="Show files in the tree (off: directories only)", value=Config.ShowFilesInTree) + ButtonsAtTopSwitch = ft.Switch(label="New File, New Directory and Upload above the tree (off: below)", + value=Config.NewEntryButtonsAtTop) MarkdownEditingPicker = ft.RadioGroup( ft.Column([ ft.Radio(value="Live", label="Live: click a part of the rendered file to edit its Markdown"), @@ -224,7 +226,7 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego ft.Row([ChipButton("Export", OnExport), ChipButton("Import", OnImport)]), ], spacing=12, scroll=ft.ScrollMode.AUTO), "General": ft.Column([ - OpenInAppSwitch, ConfirmDeleteSwitch, SidebarSwitch, TreeFilesSwitch, FontSizeField, MenuButton, + OpenInAppSwitch, ConfirmDeleteSwitch, SidebarSwitch, TreeFilesSwitch, ButtonsAtTopSwitch, FontSizeField, MenuButton, ft.Divider(height=1, color=Palette.Surface1), ft.Text("Markdown editing"), MarkdownEditingPicker, @@ -278,6 +280,7 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego Config.ConfirmDelete = ConfirmDeleteSwitch.value Config.ShowSidebar = SidebarSwitch.value Config.ShowFilesInTree = TreeFilesSwitch.value + Config.NewEntryButtonsAtTop = ButtonsAtTopSwitch.value Config.MarkdownEditing = MarkdownEditingPicker.value or Config.MarkdownEditing RemoveDiagramTools = Config.LocalDiagrams and not LocalDiagramsSwitch.value Config.LocalDiagrams = LocalDiagramsSwitch.value diff --git a/Frontend/src/Components/DirectoryPane.jsx b/Frontend/src/Components/DirectoryPane.jsx index eadeaa8..948efba 100644 --- a/Frontend/src/Components/DirectoryPane.jsx +++ b/Frontend/src/Components/DirectoryPane.jsx @@ -1,9 +1,7 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useState } from "react"; import { - CreateEntry, DeleteEntry, - FetchInheritedLock, FetchLock, FetchTree, GetDownloadUrl, @@ -35,7 +33,6 @@ export default function DirectoryPane({ const [ModalConfig, SetModalConfig] = useState(null); const [ErrorMessage, SetErrorMessage] = useState(null); const [IsDraggingOver, SetIsDraggingOver] = useState(false); - const FileInputRef = useRef(null); async function Reload() { try { @@ -58,30 +55,6 @@ export default function DirectoryPane({ return DirectoryPath ? `${DirectoryPath}/${Name}` : Name; } - async function OpenCreateFileModal() { - const Inherited = await FetchInheritedLock(RootName, DirectoryPath).catch(() => ({})); - SetModalConfig({ - Mode: "createFile", - InheritedUsers: Inherited.Users || [], - InheritedGroups: Inherited.Groups || [], - }); - } - - async function OpenCreateDirectoryModal() { - const Inherited = await FetchInheritedLock(RootName, DirectoryPath).catch(() => ({})); - SetModalConfig({ - Mode: "createDirectory", - InheritedUsers: Inherited.Users || [], - InheritedGroups: Inherited.Groups || [], - }); - } - - function SetsEqual(FirstList, SecondList) { - if (FirstList.length !== SecondList.length) return false; - const FirstSet = new Set(FirstList); - return SecondList.every((Item) => FirstSet.has(Item)); - } - async function HandleRequestLockEdit(Event, Entry) { Event.stopPropagation(); const FullPath = JoinPath(Entry.Name); @@ -151,23 +124,7 @@ export default function DirectoryPane({ async function HandleModalConfirm(Name, Users, Groups) { try { - if (ModalConfig.Mode === "createFile" || ModalConfig.Mode === "createDirectory") { - if (!Name || !Name.trim()) return; - - const IsDirectory = ModalConfig.Mode === "createDirectory"; - const FullPath = JoinPath(Name.trim()); - await CreateEntry(RootName, FullPath, IsDirectory); - - const MatchesInherited = - SetsEqual(Users, ModalConfig.InheritedUsers) && SetsEqual(Groups, ModalConfig.InheritedGroups); - - if (!MatchesInherited && (Users.length > 0 || Groups.length > 0)) { - await SetLockApi(RootName, FullPath, Users, Groups); - } - - await Reload(); - OnEntryCreated(); - } else if (ModalConfig.Mode === "editLock") { + if (ModalConfig.Mode === "editLock") { await SetLockApi(RootName, ModalConfig.TargetPath, Users, Groups); await Reload(); } @@ -190,15 +147,6 @@ export default function DirectoryPane({ OnEntryCreated(); } - function HandleUploadButtonClick() { - FileInputRef.current?.click(); - } - - function HandleFileInputChange(Event) { - HandleUpload(Event.target.files); - Event.target.value = ""; - } - function HandleDragOver(Event) { if (IsGuest) return; Event.preventDefault(); @@ -219,20 +167,6 @@ export default function DirectoryPane({ return (