New/Upload buttons with the file tree, above or below

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
sarahandClaude Opus 5.5 committed 2026-10-13 17:10:21 +02:00
1 parent 6750f7927f
commit 0450fee42b
6 files changed
+27 -100

No files matched your search

+5 -3
View File
@@ -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)",
+3
View File
@@ -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
+11 -4
View File
@@ -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)."""
+2
View File
@@ -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.
+4 -1
View File
@@ -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
+2 -92
View File
@@ -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 (
<div className="DirectoryPane">
{!IsGuest && (
<div className="PreviewToolbar">
<button onClick={OpenCreateFileModal}>New File</button>
<button onClick={OpenCreateDirectoryModal}>New Directory</button>
<button onClick={HandleUploadButtonClick}>Upload</button>
<input
ref={FileInputRef}
type="file"
multiple
style={{ display: "none" }}
onChange={HandleFileInputChange}
/>
</div>
)}
{LoadError && <div className="SidebarError">{LoadError}</div>}
<div
className={`DirectoryList${IsDraggingOver ? " DraggingOver" : ""}`}
@@ -296,30 +230,6 @@ export default function DirectoryPane({
)}
</div>
{ModalConfig && ModalConfig.Mode === "createFile" && (
<AccessModal
Title={`New file in ${DirectoryPath || "root"}`}
ShowNameField
NamePlaceholder="Notes.md"
InitialUsers={ModalConfig.InheritedUsers}
InitialGroups={ModalConfig.InheritedGroups}
ConfirmLabel="Create"
OnConfirm={HandleModalConfirm}
OnCancel={() => SetModalConfig(null)}
/>
)}
{ModalConfig && ModalConfig.Mode === "createDirectory" && (
<AccessModal
Title={`New directory in ${DirectoryPath || "root"}`}
ShowNameField
NamePlaceholder="MyFolder"
InitialUsers={ModalConfig.InheritedUsers}
InitialGroups={ModalConfig.InheritedGroups}
ConfirmLabel="Create"
OnConfirm={HandleModalConfirm}
OnCancel={() => SetModalConfig(null)}
/>
)}
{ModalConfig && ModalConfig.Mode === "editLock" && (
<AccessModal
Title={`Access for "${ModalConfig.EntryName}"`}