From 479a771d61ea29ff77286bb7ed3a41e37579aa34 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 7 Nov 2026 12:04:31 +0100 Subject: sonix: the Folders tab, with add to playlist Co-Authored-By: Claude Sonnet 5.5 --- desktop/modules/sonix/SonixFolders.qml | 194 +++++++++++++++++++++++++++++++++ desktop/modules/sonix/SonixPage.qml | 2 +- 2 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 desktop/modules/sonix/SonixFolders.qml (limited to 'desktop/modules/sonix') diff --git a/desktop/modules/sonix/SonixFolders.qml b/desktop/modules/sonix/SonixFolders.qml new file mode 100644 index 0000000..ad7ae88 --- /dev/null +++ b/desktop/modules/sonix/SonixFolders.qml @@ -0,0 +1,194 @@ +// 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 + +// The card's folders: browse, play a folder or a file, add either to a +// playlist (an existing one, a draft from Playlists, or a new name). +Column { + id: fo + required property var mod + property string root: "" + property string path: "" + property var entries: [] + property string error: "" + property string notice: "" + // What + was pressed on: { label, folder: bool, path }; null when closed. + property var adding: null + spacing: 8 + + function glyph(cp) { return String.fromCodePoint(cp); } + function report(ok, d, err) { fo.error = ok ? "" : err; } + + function go(p) { + Sonix.get("/api/browse?" + S.query({ path: p }), (ok, d, err) => { + fo.report(ok, d, err); + if (!ok) return; + fo.root = d.root; + fo.path = d.path; + fo.entries = d.entries || []; + fo.adding = null; + }); + } + Component.onCompleted: go("") + + // The files directly inside a folder, for playing or adding it whole. + function filesIn(folder, cb) { + Sonix.get("/api/browse?" + S.query({ path: folder }), (ok, d, err) => { + if (!ok) { fo.report(ok, d, err); return; } + cb((d.entries || []).filter(e => !e.dir).map(e => e.path)); + }); + } + + function playFolder(folder) { + fo.filesIn(folder, files => { + if (files.length === 0) { fo.error = "no tracks directly in this folder"; return; } + Sonix.command({ do: "play_path", path: files[0], list: "folder", value: folder }, null, fo.report); + }); + } + + function addTo(name) { + const a = fo.adding; + if (!a || name === "") return; + const send = paths => { + if (paths.length === 0) { fo.error = "no tracks directly in this folder"; return; } + Sonix.command({ do: "playlist_add", value: name }, paths.join("\n"), (ok, d, err) => { + fo.report(ok, d, err); + if (ok) fo.notice = `Added ${paths.length} to ${name}`; + }); + }; + fo.adding = null; + if (a.folder) fo.filesIn(a.path, send); else send([a.path]); + } + + property var playlists: [] + function loadPlaylists() { + Sonix.get("/api/playlists", (ok, d, err) => { if (ok) fo.playlists = (d.playlists || []).map(l => l.name); }); + } + + // --- breadcrumbs ------------------------------------------------------ + + Flow { + width: fo.width + spacing: 6 + Repeater { + model: S.crumbs(fo.path, fo.root) + delegate: Text { + required property var modelData + required property int index + text: (index > 0 ? "› " : "") + modelData.name + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + color: modelData.path === fo.path ? Theme.text : Theme.accent + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: fo.go(modelData.path) + } + } + } + } + + Button { + text: "Up" + icon: "" + enabled: fo.path !== fo.root + onClicked: fo.go(S.parentOf(fo.path, fo.root)) + } + + // --- the add-to chooser ----------------------------------------------- + + Rectangle { + visible: fo.adding !== null + width: fo.width + height: chooser.implicitHeight + 16 + radius: 8 + color: Qt.alpha(Theme.surface, 0.8) + border.width: 1 + border.color: Qt.alpha(Theme.accent, 0.5) + + Column { + id: chooser + x: 8; y: 8 + width: parent.width - 16 + spacing: 6 + + Text { + text: fo.adding ? `Add “${fo.adding.label}” to:` : "" + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + color: Theme.text + } + Flow { + width: parent.width + spacing: 6 + Repeater { + model: fo.playlists.concat(fo.mod.draftPlaylists.filter(n => fo.playlists.indexOf(n) < 0)) + delegate: Button { + required property var modelData + text: modelData + onClicked: fo.addTo(modelData) + } + } + } + Row { + spacing: 6 + Field { id: newName; width: 200; placeholder: "New playlist name" } + Button { text: "Add"; onClicked: fo.addTo(newName.value.trim()) } + Button { text: "Cancel"; onClicked: fo.adding = null } + } + } + } + + // --- entries ---------------------------------------------------------- + + Repeater { + model: fo.entries + delegate: SonixRow { + required property var modelData + width: fo.width + text: modelData.name + sub: modelData.dir ? "folder" : "" + onClicked: if (modelData.dir) fo.go(modelData.path) + TransportButton { + glyph: fo.glyph(0xF040A) // md-play + onClicked: modelData.dir + ? fo.playFolder(modelData.path) + : Sonix.command({ do: "play_path", path: modelData.path, list: "folder", value: fo.path }, null, fo.report) + } + TransportButton { + glyph: fo.glyph(0xF0415) // md-plus + onClicked: { + fo.notice = ""; + fo.loadPlaylists(); + fo.adding = { label: modelData.name, folder: modelData.dir, path: modelData.path }; + } + } + } + } + + Text { + visible: fo.notice !== "" && fo.error === "" + text: fo.notice + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.green + } + Text { + visible: fo.error !== "" + width: fo.width + wrapMode: Text.Wrap + text: fo.error + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + color: Theme.red + } +} diff --git a/desktop/modules/sonix/SonixPage.qml b/desktop/modules/sonix/SonixPage.qml index 4e1c12c..853e075 100644 --- a/desktop/modules/sonix/SonixPage.qml +++ b/desktop/modules/sonix/SonixPage.qml @@ -52,5 +52,5 @@ Column { Component { id: deviceTab; SonixDevice { onGo: name => page.go(name) } } Component { id: playlistsTab; SonixPlaylists { mod: page.mod } } - Component { id: foldersTab; Item {} } + Component { id: foldersTab; SonixFolders { mod: page.mod } } } -- cgit v1.2.3