原 AboutDialog inherits Slint Dialog(PopupWindow 子类)并 inline 实例化,没经过运行时 show(),close() 是空操作 → 用户点 Close 没反应、UI 卡死。\n- dialogs.slint:AboutDialog / ConfirmDialog 改为 inherits Rectangle,手写 backdrop(点击关闭)+ content card(Close 按钮调 close-dialog 回调)\n- app.slint:新增 show-about bool 属性;Sidebar About 点击同时触发 about-clicked + show-about=true;最外层 if root.show-about: AboutDialog 渲染\n- 删掉 selection==About 的右栏 marker(现在 About 是模态,不再需要)
451 lines
19 KiB
Plaintext
451 lines
19 KiB
Plaintext
// Main window + sidebar (AGENTS.md §6, §7).
|
|
//
|
|
// Phase 7 C3 additions:
|
|
// - Sidebar priority filter (All / Low / Normal / High / Max)
|
|
// flips `priority-filter` on MainWindow; MessageList hides
|
|
// non-matching cards via `visible` (see message-list.slint).
|
|
// - Right-hand detail panel that opens when a message is clicked:
|
|
// app + priority dot, title, absolute timestamp, full body,
|
|
// and an "Open Link" button (re-uses the URL opener wired up
|
|
// in Phase 7 C2).
|
|
|
|
import { AppearanceMode } from "./theme.slint";
|
|
import { NavItem, SectionLabel } from "./components.slint";
|
|
import { MessageItem, Priority } from "./message-card.slint";
|
|
import { MessageList } from "./message-list.slint";
|
|
import { EmptyState } from "./empty-state.slint";
|
|
import { SettingsPage } from "./settings-page.slint";
|
|
import { AboutDialog } from "./dialogs.slint";
|
|
|
|
export enum SidebarSelection {
|
|
All,
|
|
Settings,
|
|
About,
|
|
}
|
|
|
|
export component MainWindow inherits Window {
|
|
title: "Gotify";
|
|
preferred-width: 960px;
|
|
preferred-height: 680px;
|
|
min-width: 760px;
|
|
min-height: 520px;
|
|
|
|
in-out property <AppearanceMode> theme: AppearanceMode.Dark;
|
|
out property <bool> is-dark: theme != AppearanceMode.Light;
|
|
|
|
out property <color> bg: is-dark ? #1f2024 : #f5f6f8;
|
|
out property <color> surface: is-dark ? #28292d : #ffffff;
|
|
out property <color> surface-elev: is-dark ? #2f3035 : #fafbfc;
|
|
out property <color> border: is-dark ? #3a3b40 : #e4e6ea;
|
|
out property <color> text-primary: is-dark ? #e6e6e6 : #1a1a1a;
|
|
out property <color> text-second: is-dark ? #a0a0a0 : #5c5c5c;
|
|
out property <color> text-muted: is-dark ? #6a6a6a : #9a9a9a;
|
|
out property <color> accent: is-dark ? #4ea1ff : #0a66c2;
|
|
out property <color> success: #4caf50;
|
|
out property <color> warning: #ff9800;
|
|
out property <color> danger: #f44336;
|
|
out property <color> priority-low: #6a6a6a;
|
|
out property <color> priority-normal: is-dark ? #4ea1ff : #0a66c2;
|
|
out property <color> priority-high: #ff9800;
|
|
out property <color> priority-max: #f44336;
|
|
|
|
background: bg;
|
|
|
|
in-out property <SidebarSelection> selection: SidebarSelection.All;
|
|
|
|
// ── Phase 7 C3 fix: AboutDialog overlay. The previous version
|
|
// inherited Slint's `Dialog` (a PopupWindow-derived type) and
|
|
// instantiated it inline; without a runtime `.show()` call,
|
|
// close() was a no-op and the Close button became inert. We
|
|
// now drive the dialog with a plain boolean (toggled from Rust)
|
|
// and render it as a manual Rectangle overlay in `if
|
|
// root.show-about: AboutDialog { … }`. See dialogs.slint. ──
|
|
in-out property <bool> show-about: false;
|
|
callback close-about();
|
|
|
|
// ── Phase 2 C2: mock messages + selection ────────────────────
|
|
in-out property <[MessageItem]> messages;
|
|
in-out property <int> selected-message-index: -1;
|
|
callback test-notification-clicked();
|
|
callback message-card-clicked(int);
|
|
|
|
// ── Phase 7 C3: priority filter (0=All, 1=Low, 2=Normal,
|
|
// 3=High, 4=Max). The integer values intentionally align with
|
|
// `PriorityFilter` (0=All is the special "no filter" sentinel;
|
|
// 1..4 map to the four priority buckets). See message-list.slint
|
|
// for the matching `visible` predicate. ──
|
|
in-out property <int> priority-filter: 0;
|
|
|
|
// ── Phase 2 C3: Settings / About / window state ──────────────
|
|
in-out property <int> appearance-index: 2;
|
|
callback about-clicked();
|
|
// Phase 5 C2: fires whenever the user picks a new theme in Settings.
|
|
// Rust persists it into `state.json::last_theme`.
|
|
callback appearance-changed-persist(int);
|
|
|
|
// ── Phase 6 C2: live AppConfig toggles (Settings page) ─────────
|
|
// Forwarded from SettingsPage so the Rust runtime can:
|
|
// 1. Persist into `config.json`
|
|
// 2. For `start_with_windows`, also write HKCU Run.
|
|
// 3. For `close_to_tray`, immediately affect the close-requested
|
|
// handler below without a restart.
|
|
in-out property <bool> desktop-notify: true;
|
|
in-out property <bool> sound: true;
|
|
in-out property <bool> notify-high: true;
|
|
in-out property <bool> notify-max: true;
|
|
in-out property <bool> start-with-windows: false;
|
|
in-out property <bool> start-minimized: false;
|
|
in-out property <bool> close-to-tray: true;
|
|
callback setting-toggled(string, bool);
|
|
|
|
// ── Phase 3 C2: live connection status ───────────────────────
|
|
in-out property <bool> is-connected: false;
|
|
in-out property <string> status-text: "Disconnected";
|
|
callback test-connection-requested();
|
|
|
|
HorizontalLayout {
|
|
// §7 Sidebar width: 220-240px.
|
|
Rectangle {
|
|
width: 232px;
|
|
background: surface;
|
|
|
|
VerticalLayout {
|
|
padding: 20px;
|
|
spacing: 6px;
|
|
|
|
Text {
|
|
text: "Gotify";
|
|
color: text-primary;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
}
|
|
Text {
|
|
text: "Windows Client · Phase 7";
|
|
color: text-muted;
|
|
font-size: 10px;
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
// Live status row (Phase 3 C2): dot + text.
|
|
HorizontalLayout {
|
|
spacing: 6px;
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
background: is-connected ? success : text-muted;
|
|
}
|
|
Text {
|
|
text: is-connected ? "Connected" : "Disconnected";
|
|
color: is-connected ? success : text-muted;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
SectionLabel { label: "MESSAGES"; text-color: text-muted; }
|
|
NavItem {
|
|
label: "All";
|
|
// Phase 7 C3: highlight the All nav item whenever
|
|
// the priority filter is off (i.e. the user is
|
|
// looking at the unfiltered list).
|
|
selected: root.selection == SidebarSelection.All
|
|
&& root.priority-filter == 0;
|
|
clicked => {
|
|
root.selection = SidebarSelection.All;
|
|
root.priority-filter = 0;
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
SectionLabel { label: "SERVERS"; text-color: text-muted; }
|
|
Text {
|
|
text: "(no servers configured)";
|
|
color: text-muted;
|
|
font-size: 11px;
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
SectionLabel { label: "FILTERS"; text-color: text-muted; }
|
|
// Phase 7 C3: each row maps to one priority bucket.
|
|
// The dot's color is the priority color so the row
|
|
// doubles as a legend.
|
|
NavItem {
|
|
label: "Max";
|
|
selected: root.selection == SidebarSelection.All
|
|
&& root.priority-filter == 4;
|
|
has-accent: true;
|
|
accent-color: priority-max;
|
|
clicked => {
|
|
root.selection = SidebarSelection.All;
|
|
root.priority-filter = 4;
|
|
}
|
|
}
|
|
NavItem {
|
|
label: "High";
|
|
selected: root.selection == SidebarSelection.All
|
|
&& root.priority-filter == 3;
|
|
has-accent: true;
|
|
accent-color: priority-high;
|
|
clicked => {
|
|
root.selection = SidebarSelection.All;
|
|
root.priority-filter = 3;
|
|
}
|
|
}
|
|
NavItem {
|
|
label: "Normal";
|
|
selected: root.selection == SidebarSelection.All
|
|
&& root.priority-filter == 2;
|
|
has-accent: true;
|
|
accent-color: priority-normal;
|
|
clicked => {
|
|
root.selection = SidebarSelection.All;
|
|
root.priority-filter = 2;
|
|
}
|
|
}
|
|
NavItem {
|
|
label: "Low";
|
|
selected: root.selection == SidebarSelection.All
|
|
&& root.priority-filter == 1;
|
|
has-accent: true;
|
|
accent-color: priority-low;
|
|
clicked => {
|
|
root.selection = SidebarSelection.All;
|
|
root.priority-filter = 1;
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
SectionLabel { label: "APP"; text-color: text-muted; }
|
|
NavItem {
|
|
label: "Settings";
|
|
selected: root.selection == SidebarSelection.Settings;
|
|
clicked => { root.selection = SidebarSelection.Settings; }
|
|
}
|
|
NavItem {
|
|
label: "About";
|
|
selected: root.selection == SidebarSelection.About;
|
|
clicked => {
|
|
root.selection = SidebarSelection.About;
|
|
root.about-clicked();
|
|
// Phase 7 C3 fix: surface the (now
|
|
// Rectangle-overlay) About dialog. The
|
|
// Rust `about-clicked` callback still runs
|
|
// for telemetry; the actual show happens
|
|
// here so the dialog opens the moment the
|
|
// user clicks the nav row.
|
|
root.show-about = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Right pane — driven by `selection`.
|
|
Rectangle {
|
|
background: bg;
|
|
|
|
// Phase 7 C3: split the right pane horizontally so the
|
|
// detail panel can sit alongside the message list when
|
|
// the user picks a card. The detail panel is hidden when
|
|
// no card is selected or when the user is on Settings /
|
|
// About (which have their own full-pane content).
|
|
HorizontalLayout {
|
|
padding: 0;
|
|
spacing: 0;
|
|
|
|
// Messages column (always visible when on "All").
|
|
Rectangle {
|
|
background: bg;
|
|
horizontal-stretch: 1;
|
|
visible: root.selection == SidebarSelection.All;
|
|
|
|
VerticalLayout {
|
|
// Title bar with status text under it.
|
|
padding: 24px;
|
|
spacing: 4px;
|
|
|
|
Text {
|
|
text: messages.length == 0 ? "Messages" : "Messages · " + messages.length;
|
|
color: text-primary;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
}
|
|
Text {
|
|
text: status-text;
|
|
color: text-muted;
|
|
font-size: 11px;
|
|
}
|
|
|
|
Rectangle { height: 12px; }
|
|
|
|
if messages.length > 0: MessageList {
|
|
messages: messages;
|
|
selected-index: root.selected-message-index;
|
|
priority-filter: root.priority-filter;
|
|
vertical-stretch: 1;
|
|
message-clicked(idx) => {
|
|
root.selected-message-index = idx;
|
|
root.message-card-clicked(idx);
|
|
}
|
|
}
|
|
|
|
if messages.length == 0: EmptyState {
|
|
vertical-stretch: 1;
|
|
test-notification => {
|
|
root.test-notification-clicked();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Phase 7 C3: detail panel. Visible when a card is
|
|
// selected AND the user is on the All view. Always
|
|
// reads from `messages[selected-message-index]` so
|
|
// it stays in sync with whatever the card list
|
|
// currently shows.
|
|
if root.selection == SidebarSelection.All
|
|
&& root.selected-message-index >= 0
|
|
&& root.selected-message-index < root.messages.length: Rectangle {
|
|
width: 360px;
|
|
background: surface-elev;
|
|
border-color: border;
|
|
border-width: 1px;
|
|
|
|
VerticalLayout {
|
|
padding: 20px;
|
|
spacing: 8px;
|
|
|
|
HorizontalLayout {
|
|
spacing: 8px;
|
|
alignment: center;
|
|
Rectangle {
|
|
width: 10px;
|
|
height: 10px;
|
|
border-radius: 5px;
|
|
background: root.messages[root.selected-message-index].priority == Priority.Low ? priority-low :
|
|
root.messages[root.selected-message-index].priority == Priority.Normal ? priority-normal :
|
|
root.messages[root.selected-message-index].priority == Priority.High ? priority-high :
|
|
priority-max;
|
|
}
|
|
Text {
|
|
text: root.messages[root.selected-message-index].app_name;
|
|
color: text-second;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
horizontal-stretch: 1;
|
|
}
|
|
Text {
|
|
text: root.messages[root.selected-message-index].timestamp_unix == 0
|
|
? ""
|
|
: "id #" + root.messages[root.selected-message-index].id;
|
|
color: text-muted;
|
|
font-size: 11px;
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: root.messages[root.selected-message-index].title != ""
|
|
? root.messages[root.selected-message-index].title
|
|
: root.messages[root.selected-message-index].body;
|
|
color: text-primary;
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
wrap: word-wrap;
|
|
}
|
|
|
|
Text {
|
|
text: root.messages[root.selected-message-index].relative_time;
|
|
color: text-muted;
|
|
font-size: 11px;
|
|
}
|
|
|
|
Rectangle {
|
|
height: 1px;
|
|
background: border;
|
|
}
|
|
|
|
Text {
|
|
text: root.messages[root.selected-message-index].body;
|
|
color: text-primary;
|
|
font-size: 13px;
|
|
wrap: word-wrap;
|
|
vertical-stretch: 1;
|
|
}
|
|
|
|
// Open-link button: only when the message
|
|
// carries a click URL. Reuses the same
|
|
// Rust callback as the card click.
|
|
if root.messages[root.selected-message-index].has_click_url: Rectangle {
|
|
height: 32px;
|
|
background: accent;
|
|
border-radius: 6px;
|
|
TouchArea {
|
|
clicked => {
|
|
root.message-card-clicked(root.selected-message-index);
|
|
}
|
|
}
|
|
Text {
|
|
x: 0;
|
|
y: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
text: "Open link ↗";
|
|
color: white;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
horizontal-alignment: center;
|
|
vertical-alignment: center;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if root.selection == SidebarSelection.Settings: SettingsPage {
|
|
appearance-index: root.appearance-index;
|
|
desktop-notify: root.desktop-notify;
|
|
sound: root.sound;
|
|
notify-high: root.notify-high;
|
|
notify-max: root.notify-max;
|
|
start-with-windows: root.start-with-windows;
|
|
start-minimized: root.start-minimized;
|
|
close-to-tray: root.close-to-tray;
|
|
appearance-changed(idx) => {
|
|
root.appearance-index = idx;
|
|
root.theme = idx == 0 ? AppearanceMode.System :
|
|
idx == 1 ? AppearanceMode.Light :
|
|
AppearanceMode.Dark;
|
|
root.appearance-changed-persist(idx);
|
|
}
|
|
test-connection-requested => {
|
|
root.test-connection-requested();
|
|
}
|
|
toggle-persist(label, value) => {
|
|
root.setting-toggled(label, value);
|
|
}
|
|
}
|
|
|
|
// Phase 7 C3 fix: the About page is no longer a separate
|
|
// right-pane marker. Clicking the sidebar About row opens
|
|
// a modal dialog overlay (see `if root.show-about:
|
|
// AboutDialog { … }` further down), so there's nothing
|
|
// to show in the right pane for `selection == About`.
|
|
}
|
|
}
|
|
|
|
// Phase 7 C3 fix: conditional About overlay driven by
|
|
// `show-about`. See the comment on the property above for why
|
|
// we no longer use Slint's `Dialog` type here.
|
|
if root.show-about: AboutDialog {
|
|
close-dialog => {
|
|
root.show-about = false;
|
|
}
|
|
}
|
|
} |