Files
qbz/crates/qbz-ui/ui/settings/SettingsExportModal.slint
T
vicrodh 897ba7128c feat(qbz-ui): settings export modal + Developer row (04 §4.2)
Add the desktop "Export settings…" surface for the qbzd daemon epic (T18):
a Developer-settings row that opens a modal with the single default-off
"Include Qobuz login and integration tokens" gate, its warning line, and a
Cancel/Export footer. Confirm() is a Rust callback (wired next). New
SettingsExportState/SettingsExportActions globals, exported from app.slint
so the Rust layer binds them, and the modal mounted in AppShell. New strings
translated across all 8 locales (en es de fr pt ru ja nl).

cc
2026-07-15 07:50:27 -06:00

193 lines
8.8 KiB
Plaintext

// "Export settings" modal (Settings > Developer > Export settings…, 04
// §4.2). Saves a portable settings bundle via the shared
// `qbz_app::settings::bundle` engine — the modal adds UI only.
//
// One checkbox, default OFF, is the single `--include-auth` gate (one
// checkbox, not two, to stay 1:1 with the CLI). Its warning line sits
// under it, indented to the label's edge. Confirm() is wired in Rust
// (crate::settings::export_settings): it reads `include-auth`, closes the
// modal, opens a native save dialog seeded with
// `qbz-settings-YYYYMMDD.qbzb`, writes the bundle 0600, and toasts the
// import command.
//
// Chrome follows the alignment standard: Radius.md surface-card panel,
// heading + close-X, checkbox row (18px box + 10px + label), footer
// alignment:end with Cancel before the accent primary. No pills
// (ADR-008); z via declaration order in AppShell (ADR-009/010).
import { Theme } from "../foundation/semantic-colors.slint";
import { Typography } from "../foundation/typography.slint";
import { Radius } from "../foundation/radius.slint";
import { SettingsExportState, SettingsExportActions } from "../state.slint";
import { QbzIcon } from "../primitives/QbzIcon.slint";
export component SettingsExportModal inherits Rectangle {
visible: SettingsExportState.open;
if SettingsExportState.open: Rectangle {
background: #000000bf;
TouchArea {
mouse-cursor: default;
clicked => {
SettingsExportState.open = false;
}
}
Rectangle {
width: Math.min(root.width - 80px, 420px);
height: panel.preferred-height;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
border-radius: Radius.md;
background: Theme.surface-card;
border-width: 1px;
border-color: Theme.border-subtle;
drop-shadow-blur: 32px;
drop-shadow-color: #00000080;
TouchArea { }
panel := VerticalLayout {
padding: 20px;
spacing: 16px;
// Header: title + close X.
HorizontalLayout {
Text {
text: @tr("Export settings");
color: Theme.text-primary;
font-size: Typography.heading;
font-weight: Typography.semibold;
horizontal-stretch: 1;
vertical-alignment: center;
}
close-x := TouchArea {
width: 28px;
height: 28px;
mouse-cursor: pointer;
clicked => {
SettingsExportState.open = false;
}
QbzIcon {
source: @image-url("../assets/icons/x.svg");
width: 17px;
height: 17px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: close-x.has-hover ? Theme.text-primary : Theme.text-muted;
}
}
}
// What the export produces.
Text {
text: @tr("Save your settings as a bundle to import on the qbzd daemon or another machine.");
color: Theme.text-muted;
font-size: Typography.legal;
wrap: word-wrap;
}
// Include-auth checkbox (default OFF) — the single
// --include-auth gate, with its warning line indented to the
// label's left edge.
VerticalLayout {
spacing: 6px;
HorizontalLayout {
spacing: 10px;
auth-toggle := TouchArea {
width: 20px;
height: 20px;
mouse-cursor: pointer;
clicked => {
SettingsExportState.include-auth = !SettingsExportState.include-auth;
}
Rectangle {
width: 18px;
height: 18px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
border-radius: 4px;
background: SettingsExportState.include-auth ? Theme.accent : transparent;
border-width: SettingsExportState.include-auth ? 0px : 1.5px;
border-color: Theme.text-muted;
QbzIcon {
source: @image-url("../assets/icons/check.svg");
width: 12px;
height: 12px;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
tint: #ffffff;
visible: SettingsExportState.include-auth;
}
}
}
Text {
text: @tr("Include Qobuz login and integration tokens");
color: Theme.text-secondary;
font-size: Typography.body;
vertical-alignment: center;
horizontal-stretch: 1;
wrap: word-wrap;
}
}
// Warning line — indented to the label's left edge (20px
// box + 10px spacing = 30px).
HorizontalLayout {
padding-left: 30px;
Text {
text: @tr("Anyone with the file can use your accounts. It is saved readable only by you; delete it after importing.");
color: Theme.text-muted;
font-size: Typography.legal;
wrap: word-wrap;
}
}
}
// Footer: Cancel before the accent Export primary.
HorizontalLayout {
alignment: end;
spacing: 10px;
// Cancel.
Rectangle {
width: cancel-label.preferred-width + 28px;
height: 36px;
border-radius: Radius.sm;
background: cancel-ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
cancel-label := Text {
text: @tr("Cancel");
color: Theme.text-primary;
font-size: Typography.body;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
}
cancel-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
SettingsExportState.open = false;
}
}
}
// Export (opens the native save dialog via Rust).
Rectangle {
width: export-label.preferred-width + 36px;
height: 36px;
border-radius: Radius.sm;
background: export-ta.has-hover ? #ffffffd6 : Theme.accent;
export-label := Text {
text: @tr("Export");
color: #ffffff;
font-size: Typography.body;
font-weight: Typography.semibold;
x: Math.round((parent.width - self.width) / 2 / 1px) * 1px;
y: Math.round((parent.height - self.height) / 2 / 1px) * 1px;
}
export-ta := TouchArea {
mouse-cursor: pointer;
clicked => {
SettingsExportActions.confirm();
}
}
}
}
}
}
}
}