A use-site `changed has-focus` on a std-widgets LineEdit/TextEdit never fires — has-focus is an alias into the inner TextInput (slint#7747) — so typing a letter that doubles as a shortcut ran the action instead of inserting the character. Sweep every text-input surface (search, import, create/edit playlist & folder, tag editor, colour picker, settings fields, QConnect dev modal, kiosk shell) and drive UiFocusState.text-input-focused from a `guard-focused` probe property that the change tracker does observe. cc
403 lines
18 KiB
Plaintext
403 lines
18 KiB
Plaintext
// Folder-settings modal for a registered Local Library folder (Tauri's
|
|
// FolderSettingsModal). Alias, enabled, network override + fs-type,
|
|
// accessibility status, change-path, last-scanned, scan-this-folder.
|
|
//
|
|
// Driven by LibFolderEditState / LibraryManageActions. Mounted at the
|
|
// AppShell root (after the other global modals) so it overlays the whole
|
|
// window and is not clipped by the Settings ScrollView (ADR-009: on top by
|
|
// mount order, like FolderEditModal/EditPlaylistModal).
|
|
|
|
import { Theme } from "../foundation/semantic-colors.slint";
|
|
import { Typography } from "../foundation/typography.slint";
|
|
import { Radius } from "../foundation/radius.slint";
|
|
import { LibFolderEditState, LibraryManageActions , UiFocusState } from "../state.slint";
|
|
import { QbzIcon } from "../primitives/QbzIcon.slint";
|
|
import { QbzToggle } from "../primitives/QbzToggle.slint";
|
|
import { QbzSelect } from "../primitives/QbzSelect.slint";
|
|
import { LoadingSpinner } from "../primitives/LoadingSpinner.slint";
|
|
import { LineEdit } from "std-widgets.slint";
|
|
|
|
// One labeled section block.
|
|
component Field inherits VerticalLayout {
|
|
in property <string> label;
|
|
spacing: 6px;
|
|
Text {
|
|
text: root.label;
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
font-weight: Typography.semibold;
|
|
}
|
|
@children
|
|
}
|
|
|
|
export component LibFolderEditModal inherits Rectangle {
|
|
visible: LibFolderEditState.open;
|
|
|
|
// Map the fs-type QbzSelect index to the stored label. Index 0 = "auto"
|
|
// (let detection decide); the rest map to the modal's option list.
|
|
function fs-type-string(i: int) -> string {
|
|
return i == 1 ? "cifs"
|
|
: i == 2 ? "nfs"
|
|
: i == 3 ? "sshfs"
|
|
: i == 4 ? "rclone"
|
|
: i == 5 ? "webdav"
|
|
: i == 6 ? "glusterfs"
|
|
: i == 7 ? "ceph"
|
|
: i == 8 ? "other"
|
|
: "auto";
|
|
}
|
|
|
|
if LibFolderEditState.open: Rectangle {
|
|
background: #000000bf;
|
|
TouchArea {
|
|
mouse-cursor: default;
|
|
clicked => { LibFolderEditState.open = false; }
|
|
}
|
|
Rectangle {
|
|
width: Math.min(root.width - 80px, 520px);
|
|
height: Math.min(root.height - 80px, 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 so they don't reach the scrim.
|
|
TouchArea { }
|
|
|
|
panel := VerticalLayout {
|
|
padding: 22px;
|
|
spacing: 16px;
|
|
|
|
// Title + close.
|
|
HorizontalLayout {
|
|
Text {
|
|
text: @tr("Folder 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 => { LibFolderEditState.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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Folder location + change-path + accessibility.
|
|
Field {
|
|
label: @tr("Folder location");
|
|
HorizontalLayout {
|
|
spacing: 10px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
QbzIcon {
|
|
source: LibFolderEditState.is-network
|
|
? @image-url("../assets/icons/network.svg")
|
|
: @image-url("../assets/icons/hard-drive.svg");
|
|
width: 18px;
|
|
height: 18px;
|
|
tint: LibFolderEditState.is-network ? Theme.accent : Theme.text-secondary;
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 1;
|
|
Text {
|
|
text: LibFolderEditState.path;
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
overflow: elide;
|
|
}
|
|
}
|
|
// Change folder location.
|
|
Rectangle {
|
|
width: change-row.preferred-width + 22px;
|
|
height: 32px;
|
|
border-radius: Radius.sm;
|
|
border-width: 1px;
|
|
border-color: Theme.border-subtle;
|
|
background: change-ta.has-hover ? Theme.surface-hover : Theme.surface-elevated;
|
|
change-row := HorizontalLayout {
|
|
padding-left: 11px;
|
|
padding-right: 11px;
|
|
spacing: 6px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/folder-open.svg");
|
|
width: 14px;
|
|
height: 14px;
|
|
tint: Theme.text-secondary;
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
Text {
|
|
text: @tr("Change");
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.legal;
|
|
font-weight: Typography.medium;
|
|
}
|
|
}
|
|
}
|
|
change-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
LibraryManageActions.change-folder-path(LibFolderEditState.folder-id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Accessibility status.
|
|
HorizontalLayout {
|
|
spacing: 8px;
|
|
if LibFolderEditState.checking-accessible: LoadingSpinner { size: 14px; }
|
|
if !LibFolderEditState.checking-accessible: QbzIcon {
|
|
source: LibFolderEditState.accessible
|
|
? @image-url("../assets/icons/check.svg")
|
|
: @image-url("../assets/icons/x.svg");
|
|
width: 14px;
|
|
height: 14px;
|
|
tint: LibFolderEditState.accessible ? #3fae6a : #e0564f;
|
|
}
|
|
Text {
|
|
text: LibFolderEditState.checking-accessible
|
|
? @tr("Checking...")
|
|
: (LibFolderEditState.accessible
|
|
? @tr("Folder is accessible")
|
|
: @tr("Folder is not accessible"));
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
vertical-alignment: center;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Alias.
|
|
Field {
|
|
label: @tr("Display name");
|
|
LineEdit {
|
|
placeholder-text: @tr("Optional friendly name");
|
|
// Hotkey-guard probe: a use-site `changed has-focus` on a std-widgets
|
|
// input never fires (has-focus is an alias into the inner TextInput),
|
|
// so observe it through a local property instead (#619).
|
|
property <bool> guard-focused: self.has-focus;
|
|
changed guard-focused => { UiFocusState.text-input-focused = self.guard-focused; }
|
|
text <=> LibFolderEditState.alias;
|
|
}
|
|
Text {
|
|
text: @tr("Shown instead of the full path in the folder list.");
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
}
|
|
}
|
|
|
|
// Enabled.
|
|
HorizontalLayout {
|
|
spacing: 10px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 1;
|
|
Text {
|
|
text: @tr("Enabled");
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
}
|
|
Text {
|
|
text: @tr("Disabled folders are skipped during scans.");
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
QbzToggle {
|
|
checked: LibFolderEditState.enabled;
|
|
toggled(v) => { LibFolderEditState.enabled = v; }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Network override.
|
|
HorizontalLayout {
|
|
spacing: 10px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
horizontal-stretch: 1;
|
|
Text {
|
|
text: @tr("Network folder");
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
}
|
|
Text {
|
|
text: @tr("Mark this folder as a network share (excluded when offline).");
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
QbzToggle {
|
|
checked: LibFolderEditState.is-network;
|
|
toggled(v) => {
|
|
LibFolderEditState.is-network = v;
|
|
LibFolderEditState.user-override-network = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FS type (network only).
|
|
if LibFolderEditState.is-network: Field {
|
|
label: @tr("Network filesystem type");
|
|
QbzSelect {
|
|
menu-width: 200px;
|
|
options: [
|
|
@tr("Auto-detect"),
|
|
"CIFS / SMB",
|
|
"NFS",
|
|
"SSHFS",
|
|
"rclone",
|
|
"WebDAV",
|
|
"GlusterFS",
|
|
"Ceph",
|
|
@tr("Other"),
|
|
];
|
|
current-index: LibFolderEditState.fs-type-index;
|
|
selected(i) => { LibFolderEditState.fs-type-index = i; }
|
|
}
|
|
}
|
|
|
|
// Last scanned.
|
|
HorizontalLayout {
|
|
Text {
|
|
text: @tr("Last scanned");
|
|
color: Theme.text-muted;
|
|
font-size: Typography.legal;
|
|
horizontal-stretch: 1;
|
|
vertical-alignment: center;
|
|
}
|
|
Text {
|
|
text: LibFolderEditState.last-scan-label;
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.legal;
|
|
vertical-alignment: center;
|
|
}
|
|
}
|
|
|
|
Rectangle { height: 1px; background: Theme.border-subtle; }
|
|
|
|
// Footer.
|
|
HorizontalLayout {
|
|
spacing: 10px;
|
|
// Scan this folder.
|
|
Rectangle {
|
|
width: scan-row.preferred-width + 26px;
|
|
height: 36px;
|
|
border-radius: Radius.sm;
|
|
property <bool> can-scan: LibFolderEditState.enabled && LibFolderEditState.accessible;
|
|
opacity: self.can-scan ? 1.0 : 0.5;
|
|
border-width: 1px;
|
|
border-color: Theme.border-subtle;
|
|
background: scan-ta.has-hover && self.can-scan ? Theme.surface-hover : Theme.surface-elevated;
|
|
scan-row := HorizontalLayout {
|
|
padding-left: 13px;
|
|
padding-right: 13px;
|
|
spacing: 7px;
|
|
VerticalLayout {
|
|
alignment: center;
|
|
QbzIcon {
|
|
source: @image-url("../assets/icons/refresh-cw.svg");
|
|
width: 14px;
|
|
height: 14px;
|
|
tint: Theme.text-secondary;
|
|
}
|
|
}
|
|
VerticalLayout {
|
|
alignment: center;
|
|
Text {
|
|
text: @tr("Scan this folder");
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
font-weight: Typography.medium;
|
|
}
|
|
}
|
|
}
|
|
scan-ta := TouchArea {
|
|
mouse-cursor: parent.can-scan ? pointer : default;
|
|
clicked => {
|
|
if (parent.can-scan) {
|
|
LibraryManageActions.scan-folder(LibFolderEditState.folder-id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Rectangle { horizontal-stretch: 1; }
|
|
// Cancel.
|
|
Rectangle {
|
|
width: cancel-label.preferred-width + 28px;
|
|
height: 36px;
|
|
border-radius: Radius.sm;
|
|
background: cancel-ta.has-hover ? Theme.surface-hover : transparent;
|
|
border-width: 1px;
|
|
border-color: Theme.border-subtle;
|
|
cancel-label := Text {
|
|
text: @tr("Cancel");
|
|
color: Theme.text-secondary;
|
|
font-size: Typography.body;
|
|
font-weight: Typography.medium;
|
|
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 => { LibFolderEditState.open = false; }
|
|
}
|
|
}
|
|
// Save.
|
|
Rectangle {
|
|
width: save-label.preferred-width + 36px;
|
|
height: 36px;
|
|
border-radius: Radius.sm;
|
|
background: save-ta.has-hover ? Theme.accent-hover : Theme.accent;
|
|
save-label := Text {
|
|
text: @tr("Save");
|
|
color: Theme.accent-text;
|
|
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;
|
|
}
|
|
save-ta := TouchArea {
|
|
mouse-cursor: pointer;
|
|
clicked => {
|
|
LibraryManageActions.save-folder-settings(
|
|
LibFolderEditState.folder-id,
|
|
LibFolderEditState.alias,
|
|
LibFolderEditState.enabled,
|
|
LibFolderEditState.is-network,
|
|
root.fs-type-string(LibFolderEditState.fs-type-index),
|
|
LibFolderEditState.user-override-network);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|