mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-13 18:28:14 +02:00
Client: links and images open, line breaks like the web, directory notes setting
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
55ee8e2f23
commit
0513d94c6a
8 files changed
+134
-23
No files matched your search
+43
-3
@@ -12,7 +12,7 @@ import flet as ft
|
||||
|
||||
from App import App
|
||||
from Core import ChipButtonStyle, FileIcons, HumanDuration, HumanSize, OpenExternally, Palette
|
||||
from Sources import BaseName, Entry, JoinPath, MoveDestination, ParentPath, SourceError
|
||||
from Sources import BaseName, ClassifyFile, Entry, JoinPath, MoveDestination, ParentPath, SourceError
|
||||
from Graph import GraphView
|
||||
from Viewer import FileView
|
||||
|
||||
@@ -438,24 +438,64 @@ class Browser(ft.Column):
|
||||
return
|
||||
Root, RelativePath = self._EntryPath(Name)
|
||||
if Item.IsDirectory:
|
||||
self.NavigateTo(Root, RelativePath)
|
||||
self.OpenDirectory(Root, RelativePath, Item.Note)
|
||||
return
|
||||
if InApp is None:
|
||||
InApp = self.Config.OpenFilesInApp
|
||||
if not InApp:
|
||||
self._OpenExternally(Root, RelativePath)
|
||||
return
|
||||
self._ShowFile(Root, RelativePath, Item)
|
||||
|
||||
def _ShowFile(self, Root: str, RelativePath: str, Item: Entry) -> None:
|
||||
"""The file view, for a file in the current directory (it may be one
|
||||
the list hides, like a directory's .sv_ note)."""
|
||||
self.OpenView = FileView(
|
||||
self.TheApp, Root, RelativePath, Item,
|
||||
OnClose=self._CloseFile,
|
||||
OnOpenExternally=lambda: self._OpenExternally(Root, RelativePath),
|
||||
OnDownload=(lambda: self._Download(Name)) if self.TheApp.Server else None,
|
||||
OnDownload=(lambda: self._Download(Item.Name)) if self.TheApp.Server else None,
|
||||
OnFocusChange=self._SetShortcutsPaused,
|
||||
OnOpenLink=self.OpenLink,
|
||||
)
|
||||
self.FileArea.content = self.OpenView
|
||||
self.FileArea.update()
|
||||
self.OnPathChanged(Root, RelativePath, False) # highlights the file in the tree
|
||||
|
||||
def OpenDirectory(self, Root: str, RelativePath: str, Note: Optional[dict] = None) -> None:
|
||||
"""A directory clicked (in the list or the tree): go there, and with
|
||||
Config.NavigateIntoDirectories open its .sv_ note too."""
|
||||
if self.OpenView is not None and self.OpenView.IsDirty:
|
||||
self.OpenView.RequestClose(Then=lambda: self.OpenDirectory(Root, RelativePath, Note))
|
||||
return
|
||||
self.NavigateTo(Root, RelativePath)
|
||||
if self.Config.NavigateIntoDirectories and Note and RelativePath:
|
||||
self._ShowFile(Root, JoinPath(RelativePath, Note["Name"]),
|
||||
Entry(Note["Name"], False, Note.get("FileType") or ClassifyFile(Note["Name"])))
|
||||
|
||||
def OpenLink(self, Root: str, RelativePath: str) -> None:
|
||||
"""A link in a note (or an image in one) clicked: open what it points at."""
|
||||
if self.OpenView is not None and self.OpenView.IsDirty:
|
||||
self.OpenView.RequestClose(Then=lambda: self.OpenLink(Root, RelativePath))
|
||||
return
|
||||
Directory, Name = ParentPath(RelativePath), BaseName(RelativePath)
|
||||
if not Name:
|
||||
self.NavigateTo(Root, "")
|
||||
return
|
||||
try:
|
||||
Listed = {Item.Name: Item for Item in self.TheApp.Source.List(Root, Directory)}
|
||||
except SourceError as Error:
|
||||
self.TheApp.Notify(f"Couldn't open {RelativePath}: {Error}", "error")
|
||||
return
|
||||
Item = Listed.get(Name)
|
||||
if Item is not None and Item.IsDirectory:
|
||||
self.OpenDirectory(Root, RelativePath, Item.Note)
|
||||
return
|
||||
if (Root, Directory) != (self.Root, self.Path) or self.OpenView is not None:
|
||||
self.NavigateTo(Root, Directory)
|
||||
# Not listed may still be there (a hidden .sv_ note); the view says if not.
|
||||
self._ShowFile(Root, RelativePath, Item or Entry(Name, False, ClassifyFile(Name)))
|
||||
|
||||
def _CloseFile(self) -> None:
|
||||
self.OpenView = None
|
||||
self.ShortcutsPaused = False
|
||||
|
||||
@@ -193,6 +193,9 @@ class AppConfig:
|
||||
# Double-click on a file: True opens it in SaraView's own viewer, False
|
||||
# hands it to the system's default program. The menu always offers the other.
|
||||
OpenFilesInApp: bool = True
|
||||
# Clicking a directory (in the tree or the list) also opens its .sv_ note,
|
||||
# like the web view's "Open directory file on click".
|
||||
NavigateIntoDirectories: bool = False
|
||||
# Draw ```mermaid diagrams in local folders (LocalDiagrams.py); off removes
|
||||
# what it downloaded. On a server, the server draws them either way.
|
||||
LocalDiagrams: bool = True
|
||||
@@ -438,6 +441,21 @@ def CodeStyleSheet() -> ft.MarkdownStyleSheet:
|
||||
return ft.MarkdownStyleSheet(code_text_style=ft.TextStyle(font_family=ActiveFontFamily))
|
||||
|
||||
|
||||
def HardLineBreaks(Text: str) -> str:
|
||||
"""Every line break shows, like the web view's Markdown (breaks: true):
|
||||
Flet's joins a paragraph's lines into one. Code blocks stay as they are."""
|
||||
Lines = Text.split("\n")
|
||||
InFence = False
|
||||
for Index, Line in enumerate(Lines[:-1]):
|
||||
if Line.lstrip().startswith(("```", "~~~")):
|
||||
InFence = not InFence
|
||||
continue
|
||||
if InFence or not Line.strip() or not Lines[Index + 1].strip() or Line.endswith((" ", "\\")):
|
||||
continue
|
||||
Lines[Index] = Line + " "
|
||||
return "\n".join(Lines)
|
||||
|
||||
|
||||
def MarkdownSheet(FontSize: int) -> ft.MarkdownStyleSheet:
|
||||
"""Rendered Markdown's colours, the same roles as the web view's
|
||||
.MarkdownBody (Frontend/src/Styles/Theme.css): rainbow headings, coloured
|
||||
|
||||
+5
-4
@@ -55,8 +55,8 @@ class _FileLeaf:
|
||||
|
||||
|
||||
class _FiletreeNode:
|
||||
def __init__(self, TheApp: App, Root: str, RelativePath: str, OnNavigate: Callable[[str, str], None],
|
||||
OnOpenFile: Callable[[str, str], None], Depth: int):
|
||||
def __init__(self, TheApp: App, Root: str, RelativePath: str, OnNavigate: Callable[..., None],
|
||||
OnOpenFile: Callable[[str, str], None], Depth: int, Note: Optional[dict] = None):
|
||||
self.TheApp = TheApp
|
||||
self.Root = Root
|
||||
self.RelativePath = RelativePath
|
||||
@@ -68,7 +68,8 @@ class _FiletreeNode:
|
||||
self.FileLeaves: dict[str, _FileLeaf] = {}
|
||||
|
||||
Name = RelativePath.rsplit("/", 1)[-1] if RelativePath else Root
|
||||
self.NameLabel, self.NameContainer = _TreeLabel(Name, lambda: self.OnNavigate(self.Root, self.RelativePath))
|
||||
# OnNavigate(Root, Path, Note): the browser's OpenDirectory, which may open the .sv_ note too.
|
||||
self.NameLabel, self.NameContainer = _TreeLabel(Name, lambda: self.OnNavigate(self.Root, self.RelativePath, Note))
|
||||
self.Tile = ft.ExpansionTile(
|
||||
title=self.NameContainer,
|
||||
leading=ft.Icon(ft.Icons.STORAGE_OUTLINED if not RelativePath else ft.Icons.FOLDER_OUTLINED,
|
||||
@@ -98,7 +99,7 @@ class _FiletreeNode:
|
||||
ChildPath = JoinPath(self.RelativePath, Item.Name)
|
||||
if Item.IsDirectory:
|
||||
self.ChildNodes[Item.Name] = _FiletreeNode(self.TheApp, self.Root, ChildPath, self.OnNavigate,
|
||||
self.OnOpenFile, self.Depth + 1)
|
||||
self.OnOpenFile, self.Depth + 1, Item.Note)
|
||||
elif self.TheApp.Config.ShowFilesInTree:
|
||||
self.FileLeaves[Item.Name] = _FileLeaf(self.Root, ChildPath, Item.FileType, self.OnOpenFile, self.Depth + 1)
|
||||
# Directories first, then files, like the file list.
|
||||
|
||||
@@ -20,7 +20,7 @@ from typing import Callable, Optional
|
||||
import flet as ft
|
||||
|
||||
import Core
|
||||
from Core import CodeStyleSheet, MarkdownCodeTheme, MarkdownSheet, Palette
|
||||
from Core import CodeStyleSheet, HardLineBreaks, MarkdownCodeTheme, MarkdownSheet, Palette
|
||||
from Properties import ParseProperties, PropertiesView, ToggleLine, WithCheckboxes
|
||||
from Variables import Substitute
|
||||
|
||||
@@ -117,7 +117,8 @@ class LiveMarkdown(ft.Column):
|
||||
def __init__(self, Document: str, FontSize: int, OnChange: Callable[[], None],
|
||||
OnFocusChange: Callable[[bool], None], OnTapLink: Callable,
|
||||
MakeDiagram: Optional[Callable[[str, int], Optional[ft.Control]]] = None,
|
||||
GetSpans: Optional[Callable[[str], list[dict]]] = None):
|
||||
GetSpans: Optional[Callable[[str], list[dict]]] = None,
|
||||
MakeImage: Optional[Callable[[str, str], Optional[ft.Control]]] = None):
|
||||
super().__init__(scroll=ft.ScrollMode.AUTO, expand=True, spacing=0,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.STRETCH)
|
||||
self.Document = Document
|
||||
@@ -127,6 +128,7 @@ class LiveMarkdown(ft.Column):
|
||||
self.OnTapLink = OnTapLink
|
||||
self.MakeDiagram = MakeDiagram
|
||||
self.GetSpans = GetSpans
|
||||
self.MakeImage = MakeImage
|
||||
self.SpansFor: tuple[str, list[dict]] = ("", []) # (document, its spans)
|
||||
self.Blocks: list[Block] = []
|
||||
self.BlockControls: list[ft.Control] = []
|
||||
@@ -237,7 +239,7 @@ class LiveMarkdown(ft.Column):
|
||||
|
||||
def _Markdown(self, Text: str) -> ft.Control:
|
||||
return ft.Markdown(
|
||||
value=Text,
|
||||
value=HardLineBreaks(Text),
|
||||
extension_set=ft.MarkdownExtensionSet.GITHUB_FLAVORED,
|
||||
md_style_sheet=MarkdownSheet(self.FontSize),
|
||||
code_theme=MarkdownCodeTheme(),
|
||||
@@ -263,7 +265,8 @@ class LiveMarkdown(ft.Column):
|
||||
elif Kind == "Properties" and (Found := ParseProperties(Text)) is not None:
|
||||
Rendered.content = PropertiesView(Found, self.FontSize, self._Markdown, Toggle)
|
||||
elif Kind == "Text":
|
||||
Parts = WithCheckboxes(Shown if Shown is not None else Text, 0, self._Markdown, Toggle, self.FontSize, LineMap)
|
||||
Parts = WithCheckboxes(Shown if Shown is not None else Text, 0, self._Markdown, Toggle, self.FontSize, LineMap,
|
||||
self.MakeImage)
|
||||
Rendered.content = Parts[0] if len(Parts) == 1 else ft.Column(Parts, spacing=0, tight=True)
|
||||
else:
|
||||
Rendered.content = self._Markdown(Shown if Shown is not None and Kind == "Heading" else Text)
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ def Main(Page: ft.Page) -> None:
|
||||
TheBrowser = Browser(TheApp, OnPathChanged,
|
||||
lambda Category=None: ShowSettings(Category or ("Connection" if TheApp.ConnectError else "General")),
|
||||
ToggleSidebar)
|
||||
Tree = Filetree(TheApp, TheBrowser.NavigateTo, TheBrowser.OpenFile)
|
||||
Tree = Filetree(TheApp, TheBrowser.OpenDirectory, TheBrowser.OpenFile)
|
||||
Tree.SetEntryButtons(TheBrowser.EntryButtons, Config.NewEntryButtonsAtTop, Update=False)
|
||||
Divider = ResizeHandle(OnDrag, OnDragEnd)
|
||||
Tree.visible = Divider.visible = Config.ShowSidebar
|
||||
|
||||
+13
-3
@@ -22,6 +22,7 @@ from Core import Palette
|
||||
OpenOrClose = re.compile(r"^---\s*$")
|
||||
PropertyLine = re.compile(r"^([^:\s][^:]*?)\s*:\s*(.*)$")
|
||||
TaskLine = re.compile(r"^(\s*(?:[-*+]|\d+[.)])\s+)\[( |x|X)\]")
|
||||
ImageLine = re.compile(r"^\s*!\[([^\]]*)\]\(<?([^)>]+?)>?\)\s*$")
|
||||
BooleanValue = re.compile(r"^(true|false)$", re.IGNORECASE)
|
||||
Quoted = re.compile(r"""^(["']).*\1$""")
|
||||
IntegerValue = re.compile(r"^[+-]?\d+$")
|
||||
@@ -169,9 +170,10 @@ def PropertiesView(Found: Parsed, FontSize: int, MakeMarkdown: Callable[[str], f
|
||||
|
||||
|
||||
def WithCheckboxes(Text: str, FirstLine: int, MakeMarkdown: Callable[[str], ft.Control],
|
||||
Toggle: OnToggle, FontSize: int, LineMap: Optional[list[Optional[int]]] = None) -> list[ft.Control]:
|
||||
"""Markdown with its task lines as real checkboxes: the lines between
|
||||
them render as usual. FirstLine is where Text starts in the note; LineMap
|
||||
Toggle: OnToggle, FontSize: int, LineMap: Optional[list[Optional[int]]] = None,
|
||||
MakeImage: Optional[Callable[[str, str], Optional[ft.Control]]] = None) -> list[ft.Control]:
|
||||
"""Markdown with its task lines as real checkboxes, and lines that are
|
||||
only an image as the image (MakeImage): the lines between render as usual. FirstLine is where Text starts in the note; LineMap
|
||||
(Variables.Substitute) says which of its lines each shown line is, None
|
||||
for one from a variable's result (its checkbox can't be ticked)."""
|
||||
Controls: list[ft.Control] = []
|
||||
@@ -186,6 +188,14 @@ def WithCheckboxes(Text: str, FirstLine: int, MakeMarkdown: Callable[[str], ft.C
|
||||
for Offset, Line in enumerate(Text.split("\n")):
|
||||
if re.match(r"^ {0,3}(`{3,}|~{3,})", Line):
|
||||
InFence = not InFence
|
||||
# A line that's only an image: shown and clickable (MakeImage), as
|
||||
# Flet's Markdown has no click for images.
|
||||
Picture = ImageLine.match(Line) if MakeImage is not None and not InFence else None
|
||||
Shown = MakeImage(Picture.group(2), Picture.group(1)) if Picture else None
|
||||
if Shown is not None:
|
||||
Flush()
|
||||
Controls.append(Shown)
|
||||
continue
|
||||
Task = None if InFence else TaskLine.match(Line)
|
||||
if Task is None:
|
||||
Pending.append(Line)
|
||||
|
||||
+5
-1
@@ -103,6 +103,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)
|
||||
DirectoryNotesSwitch = ft.Switch(label="Clicking a directory also opens its .sv_ note",
|
||||
value=Config.NavigateIntoDirectories)
|
||||
ButtonsAtTopSwitch = ft.Switch(label="New File, New Directory and Upload above the tree (off: below)",
|
||||
value=Config.NewEntryButtonsAtTop)
|
||||
MarkdownEditingPicker = ft.RadioGroup(
|
||||
@@ -228,7 +230,8 @@ 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, ButtonsAtTopSwitch, FontSizeField, MenuButton,
|
||||
OpenInAppSwitch, ConfirmDeleteSwitch, SidebarSwitch, TreeFilesSwitch, DirectoryNotesSwitch, ButtonsAtTopSwitch,
|
||||
FontSizeField, MenuButton,
|
||||
ft.Divider(height=1, color=Palette.Surface1),
|
||||
ft.Text("Markdown editing"),
|
||||
MarkdownEditingPicker,
|
||||
@@ -304,6 +307,7 @@ def ShowSettingsDialog(TheApp: App, OnSaved: Callable[[bool], None], StartCatego
|
||||
Config.ShowSidebar = SidebarSwitch.value
|
||||
Config.ShowFilesInTree = TreeFilesSwitch.value
|
||||
Config.NewEntryButtonsAtTop = ButtonsAtTopSwitch.value
|
||||
Config.NavigateIntoDirectories = DirectoryNotesSwitch.value
|
||||
Config.MarkdownEditing = MarkdownEditingPicker.value or Config.MarkdownEditing
|
||||
RemoveDiagramTools = Config.LocalDiagrams and not LocalDiagramsSwitch.value
|
||||
Config.LocalDiagrams = LocalDiagramsSwitch.value
|
||||
|
||||
+42
-7
@@ -18,17 +18,18 @@ EditorView/ImageView idea, with SaraView's View/Edit/Save toolbar).
|
||||
- SaraTag tags (from the server's Books) show as chips under the toolbar."""
|
||||
|
||||
import re
|
||||
from urllib.parse import unquote
|
||||
from typing import Callable, Optional
|
||||
|
||||
import flet as ft
|
||||
|
||||
from App import App
|
||||
from Core import ChipButton, CodeStyleSheet, HumanSize, MarkdownCodeTheme, MarkdownSheet, Palette
|
||||
from Core import ChipButton, CodeStyleSheet, HardLineBreaks, HumanSize, MarkdownCodeTheme, MarkdownSheet, Palette
|
||||
from Diagrams import MaybeDiagram, WithDiagrams
|
||||
from Properties import ParseProperties, PropertiesView, ToggleLine, WithCheckboxes
|
||||
from Variables import FindSpans, Substitute
|
||||
from LiveMarkdown import LiveMarkdown
|
||||
from Sources import Entry, SourceError, TextFileTypes
|
||||
from Sources import Entry, NormalizeRelativePath, ParentPath, SourceError, TextFileTypes
|
||||
|
||||
LanguageByExtension = {
|
||||
".json": "json", ".toml": "toml", ".yaml": "yaml", ".yml": "yaml", ".cfg": "ini", ".ini": "ini",
|
||||
@@ -58,6 +59,7 @@ class FileView(ft.Container):
|
||||
OnOpenExternally: Callable[[], None],
|
||||
OnDownload: Optional[Callable[[], None]],
|
||||
OnFocusChange: Callable[[bool], None],
|
||||
OnOpenLink: Optional[Callable[[str, str], None]] = None,
|
||||
):
|
||||
self.TheApp = TheApp
|
||||
self.Root = Root
|
||||
@@ -66,6 +68,8 @@ class FileView(ft.Container):
|
||||
self.FileType = Item.FileType or "unknown"
|
||||
self.OnClose = OnClose
|
||||
self.OnFocusChange = OnFocusChange
|
||||
self.OnOpenLink = OnOpenLink # (Root, RelativePath): a link to another note, or an image, clicked
|
||||
self.Pictures: dict[str, Optional[bytes]] = {} # images by path, read once
|
||||
self.IsText = self.FileType in TextFileTypes
|
||||
self.IsLive = (self.FileType == "markdown" and TheApp.Config.MarkdownEditing == "Live"
|
||||
and TheApp.Source.CanWrite)
|
||||
@@ -130,7 +134,8 @@ class FileView(ft.Container):
|
||||
self.Live = LiveMarkdown(self.Content, self.TheApp.Config.FontSize, self._OnTextChanged,
|
||||
self.OnFocusChange, self._OnTapLink,
|
||||
MakeDiagram=lambda Text, Size: MaybeDiagram(self.TheApp, Text, Size),
|
||||
GetSpans=lambda Document: FindSpans(self.TheApp, self.Root, self.RelativePath, Document))
|
||||
GetSpans=lambda Document: FindSpans(self.TheApp, self.Root, self.RelativePath, Document),
|
||||
MakeImage=self._Image)
|
||||
return self.Live
|
||||
return self._RenderedView()
|
||||
if self.FileType == "image":
|
||||
@@ -152,7 +157,7 @@ class FileView(ft.Container):
|
||||
|
||||
def MakeMarkdown(Part: str) -> ft.Control:
|
||||
return ft.Markdown(
|
||||
value=Part,
|
||||
value=HardLineBreaks(Part),
|
||||
selectable=True,
|
||||
extension_set=ft.MarkdownExtensionSet.GITHUB_FLAVORED,
|
||||
md_style_sheet=MarkdownSheet(FontSize),
|
||||
@@ -164,7 +169,7 @@ class FileView(ft.Container):
|
||||
def MakeWithCheckboxes(Part: str, Start: int) -> ft.Control:
|
||||
FirstLine = Text[:BodyStart + Start].count("\n")
|
||||
Shown, LineMap = Substitute(Part, BodyStart + Start, Spans) # variables as their results
|
||||
Parts = WithCheckboxes(Shown, FirstLine, MakeMarkdown, Toggle, FontSize, LineMap)
|
||||
Parts = WithCheckboxes(Shown, FirstLine, MakeMarkdown, Toggle, FontSize, LineMap, self._Image)
|
||||
return Parts[0] if len(Parts) == 1 else ft.Column(Parts, spacing=0, tight=True)
|
||||
|
||||
Views = WithDiagrams(self.TheApp, Text[BodyStart:], FontSize, MakeWithCheckboxes)
|
||||
@@ -220,9 +225,39 @@ class FileView(ft.Container):
|
||||
return ft.Row(Chips, wrap=True, spacing=4, run_spacing=4)
|
||||
|
||||
def _OnTapLink(self, e) -> None:
|
||||
Target = str(e.data or "")
|
||||
if Target.startswith(("http://", "https://", "mailto:")):
|
||||
self.OpenTarget(str(e.data or ""))
|
||||
|
||||
def OpenTarget(self, Target: str) -> None:
|
||||
"""A clicked link or image: web addresses in the browser, a note (or
|
||||
directory, or image) relative to this one in SaraView."""
|
||||
if not Target or Target.startswith("#"):
|
||||
return
|
||||
if re.match(r"^[a-z][a-z0-9+.-]*:", Target, re.IGNORECASE):
|
||||
self.TheApp.Page.run_task(ft.UrlLauncher().launch_url, Target)
|
||||
elif self.OnOpenLink is not None:
|
||||
Path = NormalizeRelativePath(ParentPath(self.RelativePath), unquote(Target.split("#", 1)[0]))
|
||||
self.OnOpenLink(self.Root, Path)
|
||||
|
||||
def _Image(self, Src: str, Alt: str) -> Optional[ft.Control]:
|
||||
"""A line that's only an image (Properties.WithCheckboxes): the image,
|
||||
opened when clicked. None if it can't be read: then it shows as written."""
|
||||
if re.match(r"^https?://", Src):
|
||||
Picture: ft.Image = ft.Image(src=Src, fit=ft.BoxFit.CONTAIN, tooltip=Alt or None)
|
||||
else:
|
||||
Path = NormalizeRelativePath(ParentPath(self.RelativePath), unquote(Src))
|
||||
if Path not in self.Pictures:
|
||||
try:
|
||||
self.Pictures[Path] = self.TheApp.Source.ReadBytes(self.Root, Path)
|
||||
except SourceError:
|
||||
self.Pictures[Path] = None
|
||||
if self.Pictures[Path] is None:
|
||||
return None
|
||||
Picture = ft.Image(src=self.Pictures[Path], fit=ft.BoxFit.CONTAIN, tooltip=Alt or None)
|
||||
return ft.Container(
|
||||
ft.GestureDetector(content=Picture, on_tap=lambda e: self.OpenTarget(Src), mouse_cursor=ft.MouseCursor.CLICK),
|
||||
alignment=ft.Alignment.CENTER_LEFT,
|
||||
padding=ft.Padding.symmetric(vertical=4),
|
||||
)
|
||||
|
||||
def _ToggleAndSave(self, Line: int) -> None:
|
||||
"""A checkbox ticked in View: flip its line and save, like the web view."""
|
||||
|
||||
Reference in new issue
Block a user