mirror of
https://git.srhtnin.garden/sarah/SaraTag.git
synced 2026-10-13 18:28:16 +02:00
477 lines
23 KiB
Python
477 lines
23 KiB
Python
"""Lexicons: edit tag definitions. Left, every Lexicon (Lexicons/ and the
|
|
ones inside Record directories); middle, its options and tags; right, the
|
|
selected tag's properties. Every change is saved straight away.
|
|
|
|
Inherited tags show their Source's values; changing one writes an override
|
|
with only that property. "Revert" drops the override again.
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any, Callable, Optional
|
|
|
|
import flet as ft
|
|
|
|
from Books import ParseNumber
|
|
from Core import ChipButton, DisplayPath, Heading, Note, Palette
|
|
from Library import DataError, FormatValue, LexiconsDir, TagType, TagTypes
|
|
from Lexicons import (
|
|
CommonProperties, KnownLexicons, Lexicon, NewSharedLexicon, PropertyHelp, TagView, TypeProperties,
|
|
)
|
|
from Records import ListRecords
|
|
from Widgets import App, Badge, EmptyState, Panel, SectionTitle, TextInput
|
|
|
|
OriginColors = {"Local": Palette.Mauve, "Inherited": Palette.Overlay0, "Overridden": Palette.Peach, "Excluded": Palette.Red}
|
|
OriginHelp = {
|
|
"Local": "Defined in this Lexicon.",
|
|
"Inherited": "Comes unchanged from the Source Lexicon.",
|
|
"Overridden": "Comes from the Source, with some properties changed here.",
|
|
"Excluded": "In the Source, but left out here (ExcludeTags).",
|
|
}
|
|
|
|
|
|
class LexiconsPage(ft.Container):
|
|
def __init__(self, TheApp: App):
|
|
super().__init__(expand=True)
|
|
self.App = TheApp
|
|
self.Paths: list[Path] = []
|
|
self.UsedBy: dict[Path, list[str]] = {}
|
|
self.Current: Optional[Lexicon] = None
|
|
self.SelectedTag: Optional[str] = None
|
|
|
|
self.Search = TextInput(TheApp, Hint="Find a Lexicon", Prefix=ft.Icons.SEARCH, OnChange=lambda e: self._BuildList())
|
|
self.List = ft.ListView(spacing=2, expand=True)
|
|
Left = ft.Container(
|
|
ft.Column([
|
|
SectionTitle("Lexicons", [ChipButton("New", lambda e: self.NewDialog(), ft.Icons.ADD)]),
|
|
self.Search, self.List,
|
|
], spacing=8, expand=True),
|
|
width=270, bgcolor=Palette.Mantle, padding=ft.Padding.only(left=12, right=8, top=8),
|
|
border=ft.Border.only(right=ft.BorderSide(1, Palette.Surface1)),
|
|
)
|
|
self.Middle = ft.Container(width=330, padding=12, border=ft.Border.only(right=ft.BorderSide(1, Palette.Surface1)))
|
|
self.Right = ft.Container(expand=True, padding=16)
|
|
self.content = ft.Row([Left, self.Middle, self.Right], spacing=0, expand=True,
|
|
vertical_alignment=ft.CrossAxisAlignment.STRETCH)
|
|
|
|
def Refresh(self, Open: str = "") -> None:
|
|
self.page.on_keyboard_event = None
|
|
try:
|
|
Records = ListRecords()[0]
|
|
except DataError as Error:
|
|
self.App.Notify(str(Error), "error")
|
|
Records = []
|
|
self.UsedBy = {}
|
|
for Record in Records:
|
|
self.UsedBy.setdefault(Record.LexiconPath.resolve(), []).append(Record.Name)
|
|
self.Paths = KnownLexicons([Record.Directory for Record in Records if Record.Directory.is_dir()])
|
|
Target = Path(Open).resolve() if Open else (self.Current.Path if self.Current else None)
|
|
if Target is not None and Target not in self.Paths and Target.is_file():
|
|
self.Paths.append(Target)
|
|
if Target is None and self.Paths:
|
|
Target = self.Paths[0]
|
|
self._Load(Target, Keep=not Open)
|
|
|
|
def Leave(self) -> None:
|
|
pass
|
|
|
|
def _Load(self, FilePath: Optional[Path], Keep: bool = False) -> None:
|
|
Previous = self.SelectedTag if Keep else None
|
|
self.Current = None
|
|
self.LoadError = ""
|
|
if FilePath is not None:
|
|
try:
|
|
self.Current = Lexicon(FilePath)
|
|
except DataError as Error:
|
|
self.LoadError = str(Error)
|
|
self.CurrentPath = FilePath
|
|
self.SelectedTag = Previous if self.Current and Previous and self.Current.View(Previous) else (
|
|
self.Current.Views[0].Name if self.Current and self.Current.Views else None)
|
|
self.CurrentPath = FilePath
|
|
self._BuildAll()
|
|
|
|
def _BuildAll(self) -> None:
|
|
self._BuildList(Update=False)
|
|
self._BuildMiddle()
|
|
self._BuildRight()
|
|
self.update()
|
|
|
|
def _Label(self, FilePath: Path) -> str:
|
|
try:
|
|
return str(FilePath.relative_to(LexiconsDir.resolve()))
|
|
except ValueError:
|
|
return DisplayPath(str(FilePath))
|
|
|
|
def _BuildList(self, Update: bool = True) -> None:
|
|
Query = (self.Search.value or "").casefold()
|
|
Shared, Inside = [], []
|
|
for FilePath in self.Paths:
|
|
if Query and Query not in str(FilePath).casefold():
|
|
continue
|
|
Selected = FilePath == getattr(self, "CurrentPath", None)
|
|
Users = self.UsedBy.get(FilePath, [])
|
|
Tile = ft.Container(
|
|
ft.Column([
|
|
ft.Text(FilePath.name, size=13, color=Palette.Mauve if Selected else Palette.Text, max_lines=1,
|
|
overflow=ft.TextOverflow.ELLIPSIS),
|
|
*([Note("Used by " + ", ".join(Users), Size=11)] if Users else []),
|
|
*([Note(DisplayPath(str(FilePath.parent)), Size=11)] if FilePath.parent != LexiconsDir.resolve() else []),
|
|
], spacing=0, tight=True),
|
|
padding=ft.Padding.symmetric(horizontal=8, vertical=6), border_radius=4, ink=True,
|
|
bgcolor=Palette.Surface0 if Selected else None, on_click=lambda e, P=FilePath: self._Load(P),
|
|
)
|
|
(Shared if FilePath.parent == LexiconsDir.resolve() else Inside).append(Tile)
|
|
Controls: list[ft.Control] = []
|
|
if Shared:
|
|
Controls += [Note("In Lexicons/", Size=11)] + Shared
|
|
if Inside:
|
|
Controls += [Note("Inside directories", Size=11)] + Inside
|
|
self.List.controls = Controls or [Note("No Lexicons found.")]
|
|
if Update:
|
|
self.List.update()
|
|
|
|
# Middle: options and tags
|
|
|
|
def _BuildMiddle(self) -> None:
|
|
Lex = self.Current
|
|
if Lex is None:
|
|
self.Middle.content = EmptyState(self.LoadError or "No Lexicon selected.", Icon=ft.Icons.MENU_BOOK_OUTLINED)
|
|
return
|
|
Others = [FilePath for FilePath in self.Paths if FilePath != Lex.Path.resolve()]
|
|
SourceText = str(Lex.Options.get("Source", "") or "")
|
|
SourceText = "" if SourceText == "None" else SourceText
|
|
Choices = {self._Relative(Lex.Path, FilePath): FilePath for FilePath in Others}
|
|
if SourceText and SourceText not in Choices:
|
|
Choices = {SourceText: None} | Choices
|
|
SourcePicker = ft.Dropdown(
|
|
label="Source", value=SourceText or None, editable=True, enable_filter=True, dense=True, menu_height=320,
|
|
options=[ft.DropdownOption(key=Key, text=Key) for Key in Choices], expand=True,
|
|
)
|
|
InheritSwitch = ft.Switch(label="Inherit tags from a Source", value=bool(Lex.Options.get("Inherit")))
|
|
|
|
def ApplyOptions(e=None) -> None:
|
|
Source = SourcePicker.value or ""
|
|
try:
|
|
Lex.SetOptions(bool(InheritSwitch.value), Source)
|
|
except DataError as Error:
|
|
self.App.Notify(str(Error), "error")
|
|
self._BuildAll()
|
|
return
|
|
self.App.Notify("Saved. Run Update to bring Books in line.", "success")
|
|
self._BuildAll()
|
|
|
|
InheritSwitch.on_change = ApplyOptions
|
|
SourcePicker.on_select = ApplyOptions
|
|
|
|
Header: list[ft.Control] = [
|
|
Heading(Lex.Path.name),
|
|
Note(DisplayPath(str(Lex.Path.parent)), Size=11),
|
|
ft.Row([InheritSwitch]),
|
|
ft.Row([SourcePicker, ft.IconButton(ft.Icons.OPEN_IN_NEW, tooltip="Open the Source",
|
|
on_click=lambda e: self._Load(Lex.SourcePath) if Lex.SourcePath else None,
|
|
disabled=Lex.SourcePath is None)]),
|
|
]
|
|
if Lex.SourceError:
|
|
Header.append(ft.Text(Lex.SourceError, color=Palette.Red, size=12))
|
|
Tags = []
|
|
for View in Lex.Views:
|
|
Selected = View.Name == self.SelectedTag
|
|
Tags.append(ft.Container(
|
|
ft.Row([
|
|
ft.Text(View.Name, size=13, expand=True, color=Palette.Overlay0 if View.Origin == "Excluded" else Palette.Text,
|
|
style=ft.TextStyle(decoration=ft.TextDecoration.LINE_THROUGH) if View.Origin == "Excluded" else None),
|
|
Note(TagType(View.Definition) or "?", Size=11),
|
|
ft.Container(Badge(View.Origin, OriginColors[View.Origin]), tooltip=OriginHelp[View.Origin]),
|
|
], spacing=6),
|
|
padding=ft.Padding.symmetric(horizontal=8, vertical=6), border_radius=4, ink=True,
|
|
bgcolor=Palette.Surface0 if Selected else None,
|
|
on_click=lambda e, Name=View.Name: self._SelectTag(Name),
|
|
))
|
|
self.Middle.content = ft.Column([
|
|
*Header,
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
SectionTitle("Tags", [ChipButton("Add", lambda e: self.AddTagDialog(), ft.Icons.ADD)]),
|
|
ft.Column(Tags, spacing=2, scroll=ft.ScrollMode.AUTO, expand=True) if Tags else Note("No tags yet."),
|
|
], spacing=8, expand=True)
|
|
|
|
@staticmethod
|
|
def _Relative(LexiconPath: Path, Target: Path) -> str:
|
|
Relative = os.path.relpath(Target, LexiconPath.resolve().parent)
|
|
return Relative if Relative.startswith(".") else "./" + Relative
|
|
|
|
def _SelectTag(self, Name: str) -> None:
|
|
self.SelectedTag = Name
|
|
self._BuildMiddle()
|
|
self._BuildRight()
|
|
self.update()
|
|
|
|
# Right: one tag's properties
|
|
|
|
def _BuildRight(self) -> None:
|
|
Lex = self.Current
|
|
View = Lex.View(self.SelectedTag) if Lex and self.SelectedTag else None
|
|
if View is None:
|
|
self.Right.content = EmptyState("Pick a tag." if Lex else "", Icon=ft.Icons.LABEL_OUTLINE if Lex else None)
|
|
return
|
|
Kind = TagType(View.Definition)
|
|
Local = Lex.Local.get(View.Name, {})
|
|
Actions: list[ft.Control] = []
|
|
if View.Origin == "Local":
|
|
Actions += [
|
|
ChipButton("Rename", lambda e: self.RenameDialog(View), ft.Icons.DRIVE_FILE_RENAME_OUTLINE),
|
|
ft.IconButton(ft.Icons.ARROW_UPWARD, tooltip="Move up", on_click=lambda e: self._Do(lambda: Lex.MoveTag(View.Name, -1))),
|
|
ft.IconButton(ft.Icons.ARROW_DOWNWARD, tooltip="Move down", on_click=lambda e: self._Do(lambda: Lex.MoveTag(View.Name, 1))),
|
|
ChipButton("Delete", lambda e: self.DeleteTag(View), ft.Icons.DELETE_OUTLINE, Accent=Palette.Red),
|
|
]
|
|
elif View.Origin == "Excluded":
|
|
Actions.append(ChipButton("Restore (stop excluding)", lambda e: self._Do(lambda: Lex.RestoreTag(View.Name), "Restored."),
|
|
ft.Icons.RESTORE))
|
|
else:
|
|
Actions.append(ChipButton("Exclude from this Lexicon", lambda e: self._Do(lambda: Lex.DeleteTag(View.Name), "Excluded."),
|
|
ft.Icons.REMOVE_CIRCLE_OUTLINE, Accent=Palette.Red))
|
|
if View.Origin == "Overridden":
|
|
Actions.append(ChipButton("Revert all overrides", lambda e: self.RevertAll(View), ft.Icons.UNDO))
|
|
|
|
TypePicker = ft.Dropdown(
|
|
label="Type", value=Kind or None, dense=True, width=180, disabled=View.Origin != "Local",
|
|
options=[ft.DropdownOption(key=Name, text=Name) for Name in TagTypes],
|
|
on_select=lambda e: self._ChangeType(View, e.control.value),
|
|
tooltip=None if View.Origin == "Local" else "Change the type in the Source Lexicon",
|
|
)
|
|
Rows: list[ft.Control] = []
|
|
if View.Origin != "Excluded":
|
|
for Property in CommonProperties + TypeProperties.get(Kind, []):
|
|
Rows.append(self._PropertyRow(Lex, View, Property, Local))
|
|
return_note = {
|
|
"Local": "Changes here apply to every Book using this Lexicon (and Lexicons inheriting from it).",
|
|
"Inherited": "Changing a property here overrides it for this Lexicon only.",
|
|
"Overridden": "Orange properties are overridden here; Revert brings back the Source's value.",
|
|
"Excluded": "Excluded tags aren't part of this Lexicon. Clean unused tags removes them from Books.",
|
|
}[View.Origin]
|
|
self.Right.content = ft.Column([
|
|
ft.Row([Heading(View.Name), Badge(View.Origin, OriginColors[View.Origin])], spacing=10),
|
|
Note(return_note),
|
|
ft.Row(Actions, wrap=True, spacing=8),
|
|
TypePicker if View.Origin != "Excluded" else ft.Container(),
|
|
ft.Divider(height=1, color=Palette.Surface1),
|
|
*Rows,
|
|
], spacing=12, scroll=ft.ScrollMode.AUTO, expand=True)
|
|
|
|
def _Do(self, Action: Callable[[], None], Success: str = "") -> None:
|
|
try:
|
|
Action()
|
|
except DataError as Error:
|
|
self.App.Notify(str(Error), "error")
|
|
else:
|
|
if Success:
|
|
self.App.Notify(Success, "success")
|
|
if self.Current and self.SelectedTag and not self.Current.View(self.SelectedTag):
|
|
self.SelectedTag = self.Current.Views[0].Name if self.Current.Views else None
|
|
self._BuildMiddle()
|
|
self._BuildRight()
|
|
self.update()
|
|
|
|
def _Set(self, View: TagView, Property: str, Value: Any) -> None:
|
|
self._Do(lambda: self.Current.SetProperty(View.Name, Property, Value))
|
|
|
|
def _PropertyRow(self, Lex: Lexicon, View: TagView, Property: str, Local: dict[str, Any]) -> ft.Control:
|
|
Definition = View.Definition
|
|
Kind = TagType(Definition)
|
|
Origin = View.PropertyOrigin(Property, Local)
|
|
Present = Property in Definition
|
|
Value = Definition.get(Property)
|
|
Label = ft.Text(Property, size=13, width=120,
|
|
color=Palette.Peach if Origin == "Overridden" else Palette.Text, tooltip=PropertyHelp.get(Property))
|
|
Trailing: list[ft.Control] = []
|
|
if Origin == "Overridden":
|
|
Trailing.append(ft.IconButton(ft.Icons.UNDO, icon_size=16, tooltip="Revert to the Source's value",
|
|
on_click=lambda e: self._Set(View, Property, None)))
|
|
elif Property in ("Default", "Minimum", "Maximum", "EmptySymbol") and Present and View.Origin == "Local":
|
|
Trailing.append(ft.IconButton(ft.Icons.CLEAR, icon_size=16, tooltip=f"Remove {Property}",
|
|
on_click=lambda e: self._Set(View, Property, None)))
|
|
|
|
if Property in ("Required", "ForceDefault"):
|
|
Editor: ft.Control = ft.Switch(value=bool(Value), on_change=lambda e: self._Set(View, Property, bool(e.control.value)))
|
|
elif Property == "Values":
|
|
Editor = self._ValuesEditor(View, list(Value or []))
|
|
elif Property == "Default" and Kind == "List":
|
|
Options = [ft.DropdownOption(key=str(Item), text=str(Item)) for Item in Definition.get("Values", [])]
|
|
ByKey = {str(Item): Item for Item in Definition.get("Values", [])}
|
|
Editor = ft.Dropdown(value=str(Value) if Present else None, options=Options, dense=True, expand=True,
|
|
hint_text="(none)", on_select=lambda e: self._Set(View, "Default", ByKey.get(e.control.value)))
|
|
elif Property == "Default" and Kind == "Boolean":
|
|
Editor = ft.Dropdown(value=("Yes" if Value else "No") if Present else None, dense=True, width=140, hint_text="(none)",
|
|
options=[ft.DropdownOption(key="Yes", text="Yes"), ft.DropdownOption(key="No", text="No")],
|
|
on_select=lambda e: self._Set(View, "Default", e.control.value == "Yes"))
|
|
else:
|
|
Numeric = Property in ("Minimum", "Maximum") or (Property == "Default" and Kind in ("Number", "Rating"))
|
|
Editor = self._TextEditor(View, Property, "" if not Present else FormatValue(Value) if not Numeric else str(Value),
|
|
Numeric, Whole=Kind == "Rating")
|
|
return ft.Row([Label, ft.Container(Editor, expand=True), *Trailing], vertical_alignment=ft.CrossAxisAlignment.CENTER)
|
|
|
|
def _TextEditor(self, View: TagView, Property: str, Shown: str, Numeric: bool, Whole: bool) -> ft.Control:
|
|
Field = TextInput(self.App, Value=Shown, Expand=True, Hint="(none)" if Numeric or Property == "Default" else None)
|
|
State = {"Saved": Shown}
|
|
|
|
def Commit(e=None) -> None:
|
|
Text = Field.value or ""
|
|
if Text == State["Saved"]:
|
|
return
|
|
State["Saved"] = Text
|
|
if Numeric:
|
|
if not Text.strip():
|
|
self._Set(View, Property, None)
|
|
return
|
|
try:
|
|
Value = ParseNumber(Text)
|
|
except ValueError:
|
|
self.App.Notify(f"{Property} must be a number.", "error")
|
|
return
|
|
if Whole and not isinstance(Value, int):
|
|
self.App.Notify(f"{Property} must be a whole number for a Rating.", "error")
|
|
return
|
|
self._Set(View, Property, Value)
|
|
else:
|
|
self._Set(View, Property, Text)
|
|
|
|
Field.on_submit = Commit
|
|
OldBlur = Field.on_blur
|
|
|
|
def Blur(e) -> None:
|
|
OldBlur(e)
|
|
Commit()
|
|
|
|
Field.on_blur = Blur
|
|
return Field
|
|
|
|
def _ValuesEditor(self, View: TagView, Values: list[Any]) -> ft.Control:
|
|
def Save(NewValues: list[Any]) -> None:
|
|
self._Set(View, "Values", NewValues)
|
|
|
|
def Move(Index: int, Offset: int) -> None:
|
|
Target = Index + Offset
|
|
if 0 <= Target < len(Values):
|
|
NewValues = list(Values)
|
|
NewValues.insert(Target, NewValues.pop(Index))
|
|
Save(NewValues)
|
|
|
|
def Rename(Index: int) -> None:
|
|
def Done(Text: str) -> None:
|
|
Text = Text.strip()
|
|
if not Text or Text == str(Values[Index]):
|
|
return
|
|
NewValues = list(Values)
|
|
NewValues[Index] = Text
|
|
Save(NewValues)
|
|
Default = self.Current.View(View.Name).Definition.get("Default") if self.Current.View(View.Name) else None
|
|
if Default == Values[Index]:
|
|
self._Set(View, "Default", Text)
|
|
|
|
self.App.Ask("Rename value", "Value", str(Values[Index]), Done,
|
|
Help="Books holding the old value keep it (and show as invalid) until you change them.")
|
|
|
|
def Remove(Index: int) -> None:
|
|
Save([Item for Number, Item in enumerate(Values) if Number != Index])
|
|
|
|
Rows: list[ft.Control] = []
|
|
for Index, Item in enumerate(Values):
|
|
Rows.append(ft.Row([
|
|
ft.Text(str(Item), size=13, expand=True),
|
|
ft.IconButton(ft.Icons.ARROW_UPWARD, icon_size=14, on_click=lambda e, I=Index: Move(I, -1), disabled=Index == 0),
|
|
ft.IconButton(ft.Icons.ARROW_DOWNWARD, icon_size=14, on_click=lambda e, I=Index: Move(I, 1),
|
|
disabled=Index == len(Values) - 1),
|
|
ft.IconButton(ft.Icons.EDIT_OUTLINED, icon_size=14, on_click=lambda e, I=Index: Rename(I)),
|
|
ft.IconButton(ft.Icons.DELETE_OUTLINE, icon_size=14, icon_color=Palette.Red, on_click=lambda e, I=Index: Remove(I)),
|
|
], spacing=0, height=32))
|
|
NewField = TextInput(self.App, Hint="Add a value…", Expand=True)
|
|
|
|
def Add(e=None) -> None:
|
|
Text = (NewField.value or "").strip()
|
|
if Text:
|
|
Save(Values + [Text])
|
|
|
|
NewField.on_submit = Add
|
|
Rows.append(ft.Row([NewField, ft.IconButton(ft.Icons.ADD, on_click=Add)]))
|
|
return Panel(ft.Column(Rows, spacing=0), padding=6)
|
|
|
|
# Dialogs and actions
|
|
|
|
def _ChangeType(self, View: TagView, NewKind: str) -> None:
|
|
if NewKind == TagType(View.Definition):
|
|
return
|
|
|
|
def Yes() -> None:
|
|
self._Do(lambda: self.Current.ChangeType(View.Name, NewKind), f"{View.Name} is now {NewKind}.")
|
|
|
|
self.App.Confirm("Change type", f"Make {View.Name} a {NewKind}? Its type-specific properties are replaced; the "
|
|
"Default is converted when possible. Existing Book values aren't changed (Set all to defaults "
|
|
"on the Update page resets them).", Yes, "Change", Accent=Palette.Yellow)
|
|
|
|
def AddTagDialog(self) -> None:
|
|
if self.Current is None:
|
|
return
|
|
NameField = TextInput(self.App, Label="Tag name", Autofocus=True)
|
|
KindPicker = ft.Dropdown(label="Type", value="Text", options=[ft.DropdownOption(key=Name, text=Name) for Name in TagTypes])
|
|
|
|
def Add(e=None) -> None:
|
|
Name = (NameField.value or "").strip()
|
|
try:
|
|
self.Current.AddTag(Name, KindPicker.value or "Text")
|
|
except DataError as Error:
|
|
self.App.Notify(str(Error), "error")
|
|
return
|
|
self.App.Close()
|
|
self.SelectedTag = Name
|
|
self.App.Notify(f"Added {Name}. Run Update to add it to Books.", "success")
|
|
self._BuildMiddle()
|
|
self._BuildRight()
|
|
self.update()
|
|
|
|
NameField.on_submit = Add
|
|
self.App.ShowDialog("Add a tag", ft.Column([NameField, KindPicker, Note(
|
|
"Text: anything. Number: any number, optional Minimum/Maximum. Rating: whole points shown as symbols. "
|
|
"List: one of fixed Values. Boolean: yes or no.")], tight=True, spacing=10), [
|
|
ChipButton("Cancel", lambda e: self.App.Close()), ChipButton("Add", Add, ft.Icons.ADD)], Width=460)
|
|
|
|
def RenameDialog(self, View: TagView) -> None:
|
|
def Done(NewName: str) -> None:
|
|
try:
|
|
self.Current.RenameTag(View.Name, NewName)
|
|
except DataError as Error:
|
|
self.App.Notify(str(Error), "error")
|
|
return
|
|
self.SelectedTag = NewName.strip()
|
|
self.App.Notify(f"Renamed to {NewName.strip()}. Books still hold the old key; Clean unused tags drops it, "
|
|
"and Update adds the new one.", "success")
|
|
self._BuildMiddle()
|
|
self._BuildRight()
|
|
self.update()
|
|
|
|
self.App.Ask("Rename tag", "New name", View.Name, Done, "Rename")
|
|
|
|
def DeleteTag(self, View: TagView) -> None:
|
|
self.App.Confirm("Delete tag", f"Delete {View.Name} from this Lexicon? Books keep their value until you run "
|
|
"Clean unused tags.", lambda: self._Do(lambda: self.Current.DeleteTag(View.Name), f"Deleted {View.Name}."),
|
|
"Delete")
|
|
|
|
def RevertAll(self, View: TagView) -> None:
|
|
def Revert() -> None:
|
|
self.Current.Local.pop(View.Name, None)
|
|
self.Current.Save()
|
|
|
|
self._Do(Revert, "Overrides removed.")
|
|
|
|
def NewDialog(self) -> None:
|
|
def Done(Name: str) -> None:
|
|
try:
|
|
FilePath = NewSharedLexicon(Name)
|
|
except (DataError, OSError) as Error:
|
|
self.App.Notify(str(Error), "error")
|
|
return
|
|
self.Paths.append(FilePath.resolve())
|
|
self._Load(FilePath.resolve())
|
|
|
|
self.App.Ask("New shared Lexicon", "File name (in Lexicons/)", "", Done, "Create",
|
|
"For tags several Records share: point their Lexicons' Source at it. A Record's own Lexicon is "
|
|
"made when you add the Record.")
|