Phase-2 Slice 12c (user directive: use the app's circular controls, no invented buttons; fix the glyph-stuck-left bug). - Hero §3.6: deleted the local ActionCircle; the 4 hero controls are now the shared CircleAction (Play primary = filled white disc + black glyph; the loading spinner is now available = more 1:1). Added `enabled` to CircleAction (additive). - All MyQBZ modal footers (Create / Edit / Mix / AddTo / Discography) now use the shared QbzPrimaryButton (+ a new `destructive` variant for Delete) and a new shared SecondaryButton (promoted from AppearanceSettings' inline copy). QbzPrimaryButton auto-sizes as a footer chip while staying full-width in login (verified: login stretches it in a VerticalLayout; footers take preferred width). - Kept legitimately-local icon+label controls (grid "+ New" CTA, AddToMixtape picker chips, toolbar sort/filter/view-mode pills) but verified every glyph is centered. - Fixed 3 glyph-stuck-left bugs (DiscographyBuilder checkboxes used non-snapped centering -> the Math.round(.../1px)*1px crisp idiom). Backend crates untouched. cargo check -p qbz-slint green. cc
138 lines
5.9 KiB
Plaintext
138 lines
5.9 KiB
Plaintext
// My QBZ — Rename / Description / Delete-confirm modals (spec 12 §11).
|
|
//
|
|
// Driven from the hero overflow (⋯) menu. A single global (MyQbzEditState)
|
|
// backs all three; `mode` ("rename" | "description" | "delete") selects which
|
|
// body renders. Chrome mirrors EditPlaylistModal / CreateMyQbzModal: a 420px
|
|
// panel, h2 title (18px bold), the m-input field family, and an m-footer with
|
|
// secondary Cancel + a primary/destructive submit. Backdrop click + Cancel
|
|
// close without committing; Enter submits the rename. Submit is disabled while
|
|
// busy or (rename) the trimmed name is blank.
|
|
//
|
|
// Per spec §23 the Cancel / Save button labels are HARDCODED in the Tauri
|
|
// source; we keep them @tr here (DQ2 — translate hardcoded UI strings on port).
|
|
|
|
import { Theme } from "../foundation/semantic-colors.slint";
|
|
import { Typography } from "../foundation/typography.slint";
|
|
import { Radius } from "../foundation/radius.slint";
|
|
import { MyQbzEditState, MyQbzEditActions } from "../state.slint";
|
|
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
|
import { QbzPrimaryButton } from "../primitives/QbzPrimaryButton.slint";
|
|
import { SecondaryButton } from "../primitives/SecondaryButton.slint";
|
|
import { LineEdit, TextEdit } from "std-widgets.slint";
|
|
|
|
export component MyQbzEditModal inherits Rectangle {
|
|
visible: MyQbzEditState.open;
|
|
|
|
property <bool> is-rename: MyQbzEditState.mode == "rename";
|
|
property <bool> is-description: MyQbzEditState.mode == "description";
|
|
property <bool> is-delete: MyQbzEditState.mode == "delete";
|
|
// Rename submit needs a non-blank trimmed name; description/delete always
|
|
// allowed (description "" clears to null). Trim check approximated by
|
|
// comparing against a stripped copy is not available in Slint, so we gate
|
|
// on a non-empty raw string for rename (the Rust side trims again).
|
|
property <bool> can-submit:
|
|
MyQbzEditState.busy ? false
|
|
: (root.is-rename ? (MyQbzEditState.draft-name != "") : true);
|
|
|
|
if MyQbzEditState.open: Rectangle {
|
|
background: #000000bf;
|
|
TouchArea {
|
|
mouse-cursor: default;
|
|
clicked => {
|
|
MyQbzEditActions.close();
|
|
}
|
|
}
|
|
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;
|
|
// Swallow clicks on the panel itself (so they don't hit the
|
|
// backdrop's close TouchArea below).
|
|
TouchArea { }
|
|
|
|
panel := VerticalLayout {
|
|
padding: 24px;
|
|
spacing: 16px;
|
|
|
|
// --- Title (h2) ------------------------------------------
|
|
Text {
|
|
text: root.is-rename
|
|
? @tr("Rename")
|
|
: (root.is-description ? @tr("Edit description") : @tr("Delete"));
|
|
color: Theme.text-primary;
|
|
font-size: 18px;
|
|
font-weight: Typography.bold;
|
|
}
|
|
|
|
// --- Body: rename (name input) ---------------------------
|
|
if root.is-rename: rename-input := LineEdit {
|
|
text <=> MyQbzEditState.draft-name;
|
|
accepted => {
|
|
if (root.can-submit) {
|
|
MyQbzEditActions.submit-rename();
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- Body: description (textarea) ------------------------
|
|
if root.is-description: desc-input := TextEdit {
|
|
height: 96px;
|
|
wrap: word-wrap;
|
|
text <=> MyQbzEditState.draft-description;
|
|
}
|
|
|
|
// --- Body: delete-confirm (prompt) -----------------------
|
|
if root.is-delete: Text {
|
|
// Delete "{name}"? This cannot be undone. — interpolated
|
|
// with the collection name (spec §11.3 deleteConfirm, double quotes).
|
|
text: @tr("Delete \"{}\"? This cannot be undone.", MyQbzEditState.name);
|
|
color: Theme.text-secondary;
|
|
font-size: 14px;
|
|
wrap: word-wrap;
|
|
}
|
|
|
|
// --- Footer (m-footer) — shared labeled primitives -------
|
|
HorizontalLayout {
|
|
alignment: end;
|
|
spacing: 8px;
|
|
|
|
// Cancel (secondary).
|
|
SecondaryButton {
|
|
label: @tr("Cancel");
|
|
height: 38px;
|
|
enabled: !MyQbzEditState.busy;
|
|
clicked => {
|
|
MyQbzEditActions.close();
|
|
}
|
|
}
|
|
|
|
// Submit — primary (rename/description) or destructive
|
|
// (delete) variant of the shared QbzPrimaryButton.
|
|
QbzPrimaryButton {
|
|
label: root.is-delete ? @tr("Delete") : @tr("Save");
|
|
height: 38px;
|
|
destructive: root.is-delete;
|
|
enabled: root.can-submit;
|
|
clicked => {
|
|
if (root.is-rename) {
|
|
MyQbzEditActions.submit-rename();
|
|
} else if (root.is-description) {
|
|
MyQbzEditActions.submit-description();
|
|
} else {
|
|
MyQbzEditActions.confirm-delete();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|