// Copyright (C) 2026 Danilo M. // // 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 && d.name === pl.open) pl.tracks = d.tracks || []; }); } Component.onCompleted: { pl.rev = Sonix.player.playlists_revision; pl.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 = []; pl.load(); } } Button { text: "Play all" onClicked: Sonix.command({ do: "play_all", list: "playlist", value: pl.open }, null, pl.report) } } Text { textFormat: Text.PlainText 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 } }