The Dock's pinned applications were a sixteen-entry literal in Settings.qml, so changing what sits in the Dock meant editing QML. They are now an ordered list in the shared store, with move up, move down, unpin, and a filtered picker for adding installed applications. Keeping them in the shared store rather than a file of their own means they are covered by Restore defaults like everything else. This needed a "json" schema type for values the schema stores and resets but does not validate field by field. It exists so structured settings can live in the one file rather than growing a fourth preference store; the owning service validates the contents. Snapshots make the settings app safe to experiment with. The whole configuration is one file, so a backup is a copy and a restore is an overwrite, and restoring snapshots what it replaces so it is itself undoable. A snapshot is validated as JSON before it can be restored over a working configuration, and a name that is not a plain snapshot filename from the backup directory is refused. Snapshot names carry milliseconds. At one-second resolution a save followed promptly by a restore produced the same filename twice, and the restore's own safety snapshot overwrote the file it was about to read -- found by the contract, which restores immediately after saving. Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
113 lines
3.3 KiB
QML
113 lines
3.3 KiB
QML
// The Dock's pinned applications: reorder, remove, and add.
|
|
//
|
|
// The list was a sixteen-entry literal in Settings.qml, so changing what sits
|
|
// in the Dock meant editing a QML file and reloading the shell. It is a plain
|
|
// ordered list of desktop entry ids, stored in the shared settings file, which
|
|
// means it is covered by Restore defaults like everything else.
|
|
//
|
|
// Move up / move down rather than drag-and-drop. Dragging inside a Flickable
|
|
// that is itself inside a scrolling page is a genuinely hard interaction to get
|
|
// right, and it fails in a way the user reads as the app being broken; two
|
|
// buttons are unambiguous and keyboard-reachable.
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import qs.config
|
|
import qs.services
|
|
import qs.widgets
|
|
|
|
Column {
|
|
id: root
|
|
|
|
spacing: 0
|
|
|
|
readonly property var pinned: {
|
|
const stored = DesktopPreferences.get("dockPinned");
|
|
return Array.isArray(stored) ? stored : [];
|
|
}
|
|
|
|
// DesktopEntries populates asynchronously, so this must be read as a
|
|
// binding rather than looked up inside one -- byId() called during
|
|
// evaluation registers no dependency and answers from an empty list.
|
|
readonly property var entriesById: {
|
|
const index = {};
|
|
for (const entry of DesktopEntries.applications.values)
|
|
index[entry.id] = entry;
|
|
return index;
|
|
}
|
|
|
|
function nameFor(id: string): string {
|
|
const entry = root.entriesById[id];
|
|
return entry ? entry.name : id;
|
|
}
|
|
|
|
function commit(next: var): void {
|
|
DesktopPreferences.set("dockPinned", next);
|
|
}
|
|
|
|
function move(from: int, to: int): void {
|
|
if (to < 0 || to >= root.pinned.length)
|
|
return;
|
|
const next = root.pinned.slice();
|
|
const moved = next.splice(from, 1)[0];
|
|
next.splice(to, 0, moved);
|
|
root.commit(next);
|
|
}
|
|
|
|
function remove(index: int): void {
|
|
const next = root.pinned.slice();
|
|
next.splice(index, 1);
|
|
root.commit(next);
|
|
}
|
|
|
|
function add(id: string): void {
|
|
if (root.pinned.indexOf(id) >= 0)
|
|
return;
|
|
root.commit(root.pinned.concat([id]));
|
|
}
|
|
|
|
Repeater {
|
|
model: root.pinned
|
|
|
|
SettingRow {
|
|
id: pin
|
|
|
|
required property var modelData
|
|
required property int index
|
|
|
|
label: root.nameFor(pin.modelData)
|
|
detail: pin.modelData
|
|
divider: pin.index < root.pinned.length - 1
|
|
controlWidth: 132
|
|
|
|
Row {
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 4
|
|
|
|
SettingsButton {
|
|
text: "↑"
|
|
enabled: pin.index > 0
|
|
onClicked: root.move(pin.index, pin.index - 1)
|
|
}
|
|
SettingsButton {
|
|
text: "↓"
|
|
enabled: pin.index < root.pinned.length - 1
|
|
onClicked: root.move(pin.index, pin.index + 1)
|
|
}
|
|
SettingsButton {
|
|
text: "Unpin"
|
|
onClicked: root.remove(pin.index)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
SettingRow {
|
|
visible: root.pinned.length === 0
|
|
label: "Nothing is pinned"
|
|
detail: "The Dock will only show running applications"
|
|
divider: false
|
|
}
|
|
}
|