mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-15 02:43:17 +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=[],
|
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.ActionsToolbar = ft.Row(
|
||||||
[
|
[
|
||||||
self.NewFileButton,
|
|
||||||
self.NewDirectoryButton,
|
|
||||||
self.UploadButton,
|
|
||||||
ft.IconButton(icon=ft.Icons.HUB_OUTLINED, icon_color=Palette.Mauve, tooltip="Graph (Ctrl+G)",
|
ft.IconButton(icon=ft.Icons.HUB_OUTLINED, icon_color=Palette.Mauve, tooltip="Graph (Ctrl+G)",
|
||||||
on_click=lambda e: self.ShowGraph()),
|
on_click=lambda e: self.ShowGraph()),
|
||||||
ft.IconButton(icon=ft.Icons.REFRESH, icon_color=Palette.Mauve, tooltip="Refresh (Ctrl+R)",
|
ft.IconButton(icon=ft.Icons.REFRESH, icon_color=Palette.Mauve, tooltip="Refresh (Ctrl+R)",
|
||||||
|
|||||||
@@ -173,6 +173,9 @@ class AppConfig:
|
|||||||
ShowSidebar: bool = True
|
ShowSidebar: bool = True
|
||||||
SidebarWidth: int = 240
|
SidebarWidth: int = 240
|
||||||
ShowFilesInTree: bool = True # False: the sidebar tree shows directories only
|
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
|
FontSize: int = 14 # file list and viewer text
|
||||||
# "Separate": Markdown opens rendered, with Edit/View to switch to the raw
|
# "Separate": Markdown opens rendered, with Edit/View to switch to the raw
|
||||||
# text. "Live": rendered, and clicking a block shows its Markdown to edit
|
# 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.PathNodes: list[_FiletreeNode] = [] # the directories you're in, marked
|
||||||
self.TreeList = ft.ListView(expand=True)
|
self.TreeList = ft.ListView(expand=True)
|
||||||
self.SourceLabel = ft.Text("", size=12, color=Palette.Overlay0, overflow=ft.TextOverflow.ELLIPSIS, max_lines=1)
|
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__(
|
super().__init__(
|
||||||
content=ft.Column([
|
content=self.Layout,
|
||||||
ft.Container(self.SourceLabel, padding=ft.Padding.only(left=12, right=12, top=10, bottom=4)),
|
|
||||||
self.TreeList,
|
|
||||||
], spacing=0, expand=True),
|
|
||||||
width=TheApp.Config.SidebarWidth,
|
width=TheApp.Config.SidebarWidth,
|
||||||
bgcolor=Palette.Mantle,
|
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:
|
def Reload(self) -> None:
|
||||||
"""Rebuilds the tree from the current source (after connecting,
|
"""Rebuilds the tree from the current source (after connecting,
|
||||||
logging in or out, or a change that moved things around)."""
|
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:
|
def OnSettingsSaved(Reconnect: bool) -> None:
|
||||||
Tree.visible = Divider.visible = Config.ShowSidebar
|
Tree.visible = Divider.visible = Config.ShowSidebar
|
||||||
|
Tree.SetEntryButtons(TheBrowser.EntryButtons, Config.NewEntryButtonsAtTop, Update=False)
|
||||||
if Reconnect:
|
if Reconnect:
|
||||||
TheApp.Connect() # its listeners reload the tree and the browser
|
TheApp.Connect() # its listeners reload the tree and the browser
|
||||||
else:
|
else:
|
||||||
@@ -92,6 +93,7 @@ def Main(Page: ft.Page) -> None:
|
|||||||
lambda Category=None: ShowSettings(Category or ("Connection" if TheApp.ConnectError else "General")),
|
lambda Category=None: ShowSettings(Category or ("Connection" if TheApp.ConnectError else "General")),
|
||||||
ToggleSidebar)
|
ToggleSidebar)
|
||||||
Tree = Filetree(TheApp, TheBrowser.NavigateTo, TheBrowser.OpenFile)
|
Tree = Filetree(TheApp, TheBrowser.NavigateTo, TheBrowser.OpenFile)
|
||||||
|
Tree.SetEntryButtons(TheBrowser.EntryButtons, Config.NewEntryButtonsAtTop, Update=False)
|
||||||
Divider = ResizeHandle(OnDrag, OnDragEnd)
|
Divider = ResizeHandle(OnDrag, OnDragEnd)
|
||||||
Tree.visible = Divider.visible = Config.ShowSidebar
|
Tree.visible = Divider.visible = Config.ShowSidebar
|
||||||
# STRETCH so the divider gets the full height to be dragged along.
|
# 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)
|
ConfirmDeleteSwitch = ft.Switch(label="Ask before deleting", value=Config.ConfirmDelete)
|
||||||
SidebarSwitch = ft.Switch(label="Show the directory tree (Ctrl+B)", value=Config.ShowSidebar)
|
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)
|
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(
|
MarkdownEditingPicker = ft.RadioGroup(
|
||||||
ft.Column([
|
ft.Column([
|
||||||
ft.Radio(value="Live", label="Live: click a part of the rendered file to edit its Markdown"),
|
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)]),
|
ft.Row([ChipButton("Export", OnExport), ChipButton("Import", OnImport)]),
|
||||||
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
], spacing=12, scroll=ft.ScrollMode.AUTO),
|
||||||
"General": ft.Column([
|
"General": ft.Column([
|
||||||
OpenInAppSwitch, ConfirmDeleteSwitch, SidebarSwitch, TreeFilesSwitch, FontSizeField, MenuButton,
|
OpenInAppSwitch, ConfirmDeleteSwitch, SidebarSwitch, TreeFilesSwitch, ButtonsAtTopSwitch, FontSizeField, MenuButton,
|
||||||
ft.Divider(height=1, color=Palette.Surface1),
|
ft.Divider(height=1, color=Palette.Surface1),
|
||||||
ft.Text("Markdown editing"),
|
ft.Text("Markdown editing"),
|
||||||
MarkdownEditingPicker,
|
MarkdownEditingPicker,
|
||||||
@@ -278,6 +280,7 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego
|
|||||||
Config.ConfirmDelete = ConfirmDeleteSwitch.value
|
Config.ConfirmDelete = ConfirmDeleteSwitch.value
|
||||||
Config.ShowSidebar = SidebarSwitch.value
|
Config.ShowSidebar = SidebarSwitch.value
|
||||||
Config.ShowFilesInTree = TreeFilesSwitch.value
|
Config.ShowFilesInTree = TreeFilesSwitch.value
|
||||||
|
Config.NewEntryButtonsAtTop = ButtonsAtTopSwitch.value
|
||||||
Config.MarkdownEditing = MarkdownEditingPicker.value or Config.MarkdownEditing
|
Config.MarkdownEditing = MarkdownEditingPicker.value or Config.MarkdownEditing
|
||||||
RemoveDiagramTools = Config.LocalDiagrams and not LocalDiagramsSwitch.value
|
RemoveDiagramTools = Config.LocalDiagrams and not LocalDiagramsSwitch.value
|
||||||
Config.LocalDiagrams = LocalDiagramsSwitch.value
|
Config.LocalDiagrams = LocalDiagramsSwitch.value
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CreateEntry,
|
|
||||||
DeleteEntry,
|
DeleteEntry,
|
||||||
FetchInheritedLock,
|
|
||||||
FetchLock,
|
FetchLock,
|
||||||
FetchTree,
|
FetchTree,
|
||||||
GetDownloadUrl,
|
GetDownloadUrl,
|
||||||
@@ -35,7 +33,6 @@ export default function DirectoryPane({
|
|||||||
const [ModalConfig, SetModalConfig] = useState(null);
|
const [ModalConfig, SetModalConfig] = useState(null);
|
||||||
const [ErrorMessage, SetErrorMessage] = useState(null);
|
const [ErrorMessage, SetErrorMessage] = useState(null);
|
||||||
const [IsDraggingOver, SetIsDraggingOver] = useState(false);
|
const [IsDraggingOver, SetIsDraggingOver] = useState(false);
|
||||||
const FileInputRef = useRef(null);
|
|
||||||
|
|
||||||
async function Reload() {
|
async function Reload() {
|
||||||
try {
|
try {
|
||||||
@@ -58,30 +55,6 @@ export default function DirectoryPane({
|
|||||||
return DirectoryPath ? `${DirectoryPath}/${Name}` : Name;
|
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) {
|
async function HandleRequestLockEdit(Event, Entry) {
|
||||||
Event.stopPropagation();
|
Event.stopPropagation();
|
||||||
const FullPath = JoinPath(Entry.Name);
|
const FullPath = JoinPath(Entry.Name);
|
||||||
@@ -151,23 +124,7 @@ export default function DirectoryPane({
|
|||||||
|
|
||||||
async function HandleModalConfirm(Name, Users, Groups) {
|
async function HandleModalConfirm(Name, Users, Groups) {
|
||||||
try {
|
try {
|
||||||
if (ModalConfig.Mode === "createFile" || ModalConfig.Mode === "createDirectory") {
|
if (ModalConfig.Mode === "editLock") {
|
||||||
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") {
|
|
||||||
await SetLockApi(RootName, ModalConfig.TargetPath, Users, Groups);
|
await SetLockApi(RootName, ModalConfig.TargetPath, Users, Groups);
|
||||||
await Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
@@ -190,15 +147,6 @@ export default function DirectoryPane({
|
|||||||
OnEntryCreated();
|
OnEntryCreated();
|
||||||
}
|
}
|
||||||
|
|
||||||
function HandleUploadButtonClick() {
|
|
||||||
FileInputRef.current?.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
function HandleFileInputChange(Event) {
|
|
||||||
HandleUpload(Event.target.files);
|
|
||||||
Event.target.value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function HandleDragOver(Event) {
|
function HandleDragOver(Event) {
|
||||||
if (IsGuest) return;
|
if (IsGuest) return;
|
||||||
Event.preventDefault();
|
Event.preventDefault();
|
||||||
@@ -219,20 +167,6 @@ export default function DirectoryPane({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="DirectoryPane">
|
<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>}
|
{LoadError && <div className="SidebarError">{LoadError}</div>}
|
||||||
<div
|
<div
|
||||||
className={`DirectoryList${IsDraggingOver ? " DraggingOver" : ""}`}
|
className={`DirectoryList${IsDraggingOver ? " DraggingOver" : ""}`}
|
||||||
@@ -296,30 +230,6 @@ export default function DirectoryPane({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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" && (
|
{ModalConfig && ModalConfig.Mode === "editLock" && (
|
||||||
<AccessModal
|
<AccessModal
|
||||||
Title={`Access for "${ModalConfig.EntryName}"`}
|
Title={`Access for "${ModalConfig.EntryName}"`}
|
||||||
|
|||||||
Reference in new issue
Block a user