aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/modules/sonix
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-07 12:03:15 +0100
committerDanilo M. <danix@danix.xyz>2026-11-07 12:03:15 +0100
commit4ed1eef421fa3af5aa25d231cc3ad36e8cdf4419 (patch)
treec06f7e0dffff958fe4dafa75f12c0e92aee5d497 /desktop/modules/sonix
parentf8ace2469099f2bfc8ef0b10e22d119af1903c84 (diff)
downloadquickshell-4ed1eef421fa3af5aa25d231cc3ad36e8cdf4419.tar.gz
quickshell-4ed1eef421fa3af5aa25d231cc3ad36e8cdf4419.zip
sonix: the Playlists tab
Diffstat (limited to 'desktop/modules/sonix')
-rw-r--r--desktop/modules/sonix/SonixPage.qml2
-rw-r--r--desktop/modules/sonix/SonixPlaylists.qml151
2 files changed, 152 insertions, 1 deletions
diff --git a/desktop/modules/sonix/SonixPage.qml b/desktop/modules/sonix/SonixPage.qml
index 1ee89a6..4e1c12c 100644
--- a/desktop/modules/sonix/SonixPage.qml
+++ b/desktop/modules/sonix/SonixPage.qml
@@ -51,6 +51,6 @@ Column {
}
Component { id: deviceTab; SonixDevice { onGo: name => page.go(name) } }
- Component { id: playlistsTab; Item {} }
+ Component { id: playlistsTab; SonixPlaylists { mod: page.mod } }
Component { id: foldersTab; Item {} }
}
diff --git a/desktop/modules/sonix/SonixPlaylists.qml b/desktop/modules/sonix/SonixPlaylists.qml
new file mode 100644
index 0000000..7a3cb1e
--- /dev/null
+++ b/desktop/modules/sonix/SonixPlaylists.qml
@@ -0,0 +1,151 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+
+import QtQuick
+import "../.."
+import "../appearance"
+import "../sound"
+import "Sonix.js" as S
+
+// Playlists: list, play, open, remove tracks, and new names that become
+// playlists when Folders adds their first track. No rename, delete or
+// reorder: SonixLink has none (README.md).
+Column {
+ id: pl
+ required property var mod
+ property var lists: []
+ property string open: ""
+ property var tracks: []
+ property string error: ""
+ property var rev: undefined
+ spacing: 8
+
+ function glyph(cp) { return String.fromCodePoint(cp); }
+ function report(ok, d, err) { pl.error = ok ? "" : err; }
+
+ function load() {
+ Sonix.get("/api/playlists", (ok, d, err) => {
+ pl.report(ok, d, err);
+ if (!ok) return;
+ pl.lists = d.playlists || [];
+ const names = pl.lists.map(l => l.name);
+ pl.mod.draftPlaylists = pl.mod.draftPlaylists.filter(n => names.indexOf(n) < 0);
+ });
+ if (pl.open !== "") pl.loadTracks();
+ }
+ function loadTracks() {
+ Sonix.get("/api/playlist?" + S.query({ name: pl.open }), (ok, d, err) => {
+ pl.report(ok, d, err);
+ if (ok) pl.tracks = d.tracks || [];
+ });
+ }
+
+ Component.onCompleted: load()
+ // Edits made on the player show up: the state carries a revision.
+ Connections {
+ target: Sonix
+ function onPlayerChanged() {
+ const r = Sonix.player.playlists_revision;
+ if (r !== undefined && r !== pl.rev) { pl.rev = r; pl.load(); }
+ }
+ }
+
+ // --- the list ---------------------------------------------------------
+
+ Row {
+ visible: pl.open === ""
+ spacing: 8
+ Field { id: newName; width: 220; placeholder: "New playlist name" }
+ Button {
+ text: "New"
+ onClicked: {
+ const n = newName.value.trim();
+ if (n === "" || pl.lists.some(l => l.name === n) || pl.mod.draftPlaylists.indexOf(n) >= 0) return;
+ pl.mod.draftPlaylists = pl.mod.draftPlaylists.concat([n]);
+ newName.value = "";
+ }
+ }
+ }
+
+ Repeater {
+ model: pl.open === "" ? pl.lists : []
+ delegate: SonixRow {
+ required property var modelData
+ width: pl.width
+ text: modelData.name
+ sub: modelData.count + " tracks"
+ onClicked: { pl.open = modelData.name; pl.tracks = []; pl.loadTracks(); }
+ TransportButton {
+ glyph: pl.glyph(0xF040A) // md-play
+ onClicked: Sonix.command({ do: "play_all", list: "playlist", value: modelData.name }, null, pl.report)
+ }
+ }
+ }
+
+ Repeater {
+ model: pl.open === "" ? pl.mod.draftPlaylists : []
+ delegate: SonixRow {
+ required property var modelData
+ width: pl.width
+ text: modelData
+ sub: "empty: add its first track from Folders"
+ }
+ }
+
+ // --- one playlist -----------------------------------------------------
+
+ Row {
+ visible: pl.open !== ""
+ spacing: 8
+ Button { text: "Playlists"; icon: "\uF053"; onClicked: { pl.open = ""; pl.tracks = []; } }
+ Button {
+ text: "Play all"
+ onClicked: Sonix.command({ do: "play_all", list: "playlist", value: pl.open }, null, pl.report)
+ }
+ }
+
+ Text {
+ visible: pl.open !== ""
+ text: pl.open
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize; bold: true }
+ color: Theme.text
+ }
+
+ Repeater {
+ model: pl.open !== "" ? pl.tracks : []
+ delegate: SonixRow {
+ required property var modelData
+ width: pl.width
+ text: modelData.title || modelData.path.split("/").pop()
+ sub: modelData.artist || ""
+ TransportButton {
+ glyph: pl.glyph(0xF040A) // md-play
+ onClicked: Sonix.command({ do: "play_path", path: modelData.path, list: "playlist", value: pl.open },
+ null, pl.report)
+ }
+ TransportButton {
+ glyph: pl.glyph(0xF0156) // md-close
+ onClicked: Sonix.command({ do: "playlist_remove", value: pl.open }, modelData.path,
+ (ok, d, err) => { pl.report(ok, d, err); if (ok) pl.loadTracks(); })
+ }
+ }
+ }
+
+ Text {
+ visible: pl.error !== ""
+ width: pl.width
+ wrapMode: Text.Wrap
+ text: pl.error
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ color: Theme.red
+ }
+}