mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-13 18:28:14 +02:00
New/Upload buttons with the file tree, above or below
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
6750f7927f
commit
0450fee42b
6 files changed
+27
-100
No files matched your search
+5
-3
@@ -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)",
|
||||
|
||||
@@ -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
@@ -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)."""
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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}"`}
|
||||
|
||||
Reference in new issue
Block a user