mirror of
https://git.srhtnin.garden/sarah/SaraView.git
synced 2026-10-13 18:28:14 +02:00
Properties: colours by type, readable keys, lines between
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ca37409e37
commit
81ebcd66a1
3 files changed
+101
-14
No files matched your search
+55
-9
@@ -23,6 +23,37 @@ OpenOrClose = re.compile(r"^---\s*$")
|
||||
PropertyLine = re.compile(r"^([^:\s][^:]*?)\s*:\s*(.*)$")
|
||||
TaskLine = re.compile(r"^(\s*(?:[-*+]|\d+[.)])\s+)\[( |x|X)\]")
|
||||
BooleanValue = re.compile(r"^(true|false)$", re.IGNORECASE)
|
||||
Quoted = re.compile(r"""^(["']).*\1$""")
|
||||
IntegerValue = re.compile(r"^[+-]?\d+$")
|
||||
FloatValue = re.compile(r"^[+-]?(\d+\.\d*([eE][+-]?\d+)?|\.\d+([eE][+-]?\d+)?|\d+[eE][+-]?\d+)$")
|
||||
DateValue = re.compile(r"^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?)?$")
|
||||
|
||||
|
||||
def ValueKind(Value: str) -> str:
|
||||
"""What a value is, for its colour: quotes always make it text ("42" is
|
||||
text, 42 a number), like YAML. Same rules as Properties.js's ValueKind."""
|
||||
if Value == "":
|
||||
return "Empty"
|
||||
if Quoted.match(Value):
|
||||
return "String"
|
||||
if BooleanValue.match(Value):
|
||||
return "Boolean"
|
||||
if IntegerValue.match(Value):
|
||||
return "Integer"
|
||||
if FloatValue.match(Value):
|
||||
return "Float"
|
||||
if DateValue.match(Value):
|
||||
return "Date"
|
||||
return "String"
|
||||
|
||||
|
||||
def ShownValue(Value: str) -> str:
|
||||
"""A value as shown: without the quotes that only said "this is text"."""
|
||||
return Value[1:-1] if Quoted.match(Value) else Value
|
||||
|
||||
|
||||
def KindColour(Kind: str) -> str:
|
||||
return {"Integer": Palette.Peach, "Float": Palette.Yellow, "Date": Palette.Blue}.get(Kind, Palette.Green)
|
||||
|
||||
# Ticks a box: called with the line (0-based, in the whole note) to flip.
|
||||
OnToggle = Optional[Callable[[int], None]]
|
||||
@@ -93,26 +124,41 @@ def _Checkbox(Checked: bool, Line: int, Toggle: OnToggle) -> ft.Checkbox:
|
||||
)
|
||||
|
||||
|
||||
def _Value(Value: str, FontSize: int, MakeMarkdown: Callable[[str], ft.Control]) -> ft.Control:
|
||||
"""A value in its kind's colour; one with Markdown in it (a link, bold)
|
||||
is rendered as Markdown instead."""
|
||||
Shown = ShownValue(Value)
|
||||
if re.search(r"[\[*_`~]|https?://", Shown):
|
||||
return MakeMarkdown(Shown)
|
||||
return ft.Text(Shown, color=KindColour(ValueKind(Value)), size=FontSize, selectable=True)
|
||||
|
||||
|
||||
def PropertiesView(Found: Parsed, FontSize: int, MakeMarkdown: Callable[[str], ft.Control],
|
||||
Toggle: OnToggle) -> ft.Control:
|
||||
"""The panel. MakeMarkdown renders a value (links, bold)."""
|
||||
"""The panel. MakeMarkdown renders a value with Markdown in it (links, bold)."""
|
||||
Rows: list[ft.Control] = []
|
||||
for Item in Found.Properties:
|
||||
if BooleanValue.match(Item.Value):
|
||||
Shown: ft.Control = _Checkbox(Item.Value.lower() == "true", Item.Line, Toggle)
|
||||
elif Item.List:
|
||||
Shown = ft.Row([ft.Container(ft.Text(Entry, color=Palette.Mauve, size=FontSize - 1), bgcolor=Palette.Surface0,
|
||||
border_radius=4, padding=ft.Padding.symmetric(horizontal=6, vertical=1))
|
||||
Shown = ft.Row([ft.Container(ft.Text(ShownValue(Entry), color=KindColour(ValueKind(Entry)), size=FontSize - 1),
|
||||
bgcolor=Palette.Surface0, border_radius=4,
|
||||
padding=ft.Padding.symmetric(horizontal=6, vertical=1))
|
||||
for Entry in Item.List], wrap=True, spacing=4, run_spacing=4)
|
||||
elif Item.Value:
|
||||
Shown = MakeMarkdown(Item.Value)
|
||||
Shown = _Value(Item.Value, FontSize, MakeMarkdown)
|
||||
else:
|
||||
Shown = ft.Text("Empty", color=Palette.Overlay0, italic=True, size=FontSize - 1)
|
||||
Rows.append(ft.Row([
|
||||
ft.Container(ft.Text(Item.Key, color=Palette.Overlay0, size=FontSize - 1, max_lines=1,
|
||||
overflow=ft.TextOverflow.ELLIPSIS), expand=3),
|
||||
ft.Container(Shown, expand=7, alignment=ft.Alignment.CENTER_LEFT),
|
||||
], spacing=12, vertical_alignment=ft.CrossAxisAlignment.CENTER))
|
||||
Rows.append(ft.Container(
|
||||
ft.Row([
|
||||
ft.Container(ft.Text(Item.Key, color=Palette.Text, size=FontSize - 1, max_lines=1,
|
||||
overflow=ft.TextOverflow.ELLIPSIS), expand=3),
|
||||
ft.Container(Shown, expand=7, alignment=ft.Alignment.CENTER_LEFT),
|
||||
], spacing=12, vertical_alignment=ft.CrossAxisAlignment.CENTER),
|
||||
# A line between properties, like the web view's.
|
||||
border=ft.Border.only(top=ft.BorderSide(1, Palette.Surface1)) if Rows else None,
|
||||
padding=ft.Padding.symmetric(vertical=4),
|
||||
))
|
||||
return ft.Container(
|
||||
ft.Column([ft.Text("Properties", color=Palette.Overlay0, size=FontSize - 1), *Rows], spacing=2, tight=True),
|
||||
bgcolor=Palette.Mantle,
|
||||
|
||||
@@ -46,6 +46,23 @@ export function IsBoolean(Value) {
|
||||
return /^(true|false)$/i.test(Value);
|
||||
}
|
||||
|
||||
// What a value is, for its colour: quotes always make it text ("42" is text,
|
||||
// 42 a number), like YAML. Same rules as the client's Properties.ValueKind.
|
||||
export function ValueKind(Value) {
|
||||
if (Value === "") return "Empty";
|
||||
if (/^(["']).*\1$/.test(Value)) return "String";
|
||||
if (IsBoolean(Value)) return "Boolean";
|
||||
if (/^[+-]?\d+$/.test(Value)) return "Integer";
|
||||
if (/^[+-]?(\d+\.\d*([eE][+-]?\d+)?|\.\d+([eE][+-]?\d+)?|\d+[eE][+-]?\d+)$/.test(Value)) return "Float";
|
||||
if (/^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?)?$/.test(Value)) return "Date";
|
||||
return "String";
|
||||
}
|
||||
|
||||
// A value as shown: without the quotes that only said "this is text".
|
||||
export function ShownValue(Value) {
|
||||
return /^(["']).*\1$/.test(Value) ? Value.slice(1, -1) : Value;
|
||||
}
|
||||
|
||||
// The note without its properties block (what Markdown renders).
|
||||
export function WithoutProperties(Text) {
|
||||
const Parsed = ParseProperties(Text);
|
||||
@@ -99,9 +116,13 @@ export function PropertiesHtml(Parsed, RenderInline, Escape, CanEdit) {
|
||||
const Disabled = CanEdit ? "" : " disabled";
|
||||
Shown = `<input type="checkbox" class="PropertyCheckbox" data-line="${Line}"${Checked}${Disabled}>`;
|
||||
} else if (List) {
|
||||
Shown = List.map((Item) => `<span class="PropertyChip">${RenderInline(Item)}</span>`).join("");
|
||||
Shown = List.map(
|
||||
(Item) => `<span class="PropertyChip PropertyKind${ValueKind(Item)}">${RenderInline(ShownValue(Item))}</span>`,
|
||||
).join("");
|
||||
} else if (Value) {
|
||||
Shown = `<span class="PropertyKind${ValueKind(Value)}">${RenderInline(ShownValue(Value))}</span>`;
|
||||
} else {
|
||||
Shown = Value ? RenderInline(Value) : `<span class="PropertyEmpty">Empty</span>`;
|
||||
Shown = `<span class="PropertyEmpty">Empty</span>`;
|
||||
}
|
||||
return `<div class="PropertyRow"><span class="PropertyKey">${Escape(Key)}</span><span class="PropertyValue">${Shown}</span></div>`;
|
||||
});
|
||||
|
||||
@@ -527,7 +527,11 @@ img,
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 2px 0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.PropertyRow + .PropertyRow {
|
||||
border-top: 1px solid var(--Border);
|
||||
}
|
||||
|
||||
.PropertyKey {
|
||||
@@ -536,7 +540,7 @@ img,
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--TextMuted);
|
||||
color: var(--Text);
|
||||
}
|
||||
|
||||
.PropertyValue {
|
||||
@@ -552,7 +556,23 @@ img,
|
||||
padding: 0 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--SurfaceAlt);
|
||||
color: var(--Purple);
|
||||
}
|
||||
|
||||
/* Values by kind (Properties.js ValueKind): text, whole and decimal numbers, dates. */
|
||||
.PropertyKindString {
|
||||
color: var(--Green);
|
||||
}
|
||||
|
||||
.PropertyKindInteger {
|
||||
color: var(--Orange);
|
||||
}
|
||||
|
||||
.PropertyKindFloat {
|
||||
color: var(--Yellow);
|
||||
}
|
||||
|
||||
.PropertyKindDate {
|
||||
color: var(--Blue);
|
||||
}
|
||||
|
||||
.PropertyEmpty {
|
||||
|
||||
Reference in new issue
Block a user