aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/assistant/Card.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-16 16:41:51 +0200
committerDanilo M. <danix@danix.xyz>2026-10-16 16:41:51 +0200
commit607934e74ffabc1dcce72b29fc28e92a669e54ce (patch)
tree47ca9f67742c1892ff9197b5d4d31f6dd6534d36 /desktop/assistant/Card.qml
parent9a2a0c93cc30af979a26d957fef603e2378676be (diff)
downloadquickshell-607934e74ffabc1dcce72b29fc28e92a669e54ce.tar.gz
quickshell-607934e74ffabc1dcce72b29fc28e92a669e54ce.zip
feat(assistant): the Do row of file operations
Conversions, extraction, merge and compression on the staged files from the file state, one at a time, with Stop; Compress… opens its formats. To .md goes through the daemon's write, the rest through fileops. The card keeps the result line, Saved name with Open and Folder, or the error, so another operation can follow on the same files. Results land in Documents/Assistant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/assistant/Card.qml')
-rw-r--r--desktop/assistant/Card.qml85
1 files changed, 80 insertions, 5 deletions
diff --git a/desktop/assistant/Card.qml b/desktop/assistant/Card.qml
index f63ce59..02ee476 100644
--- a/desktop/assistant/Card.qml
+++ b/desktop/assistant/Card.qml
@@ -9,10 +9,12 @@
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
+import Quickshell
import QtQuick
import ".."
import "Files.js" as Files
import "Presets.js" as Presets
+import "Ops.js" as Ops
// The card in its three states (Assistant.qml, mode): entry is the input
// line alone; file adds the staged chips and what can be done with them;
@@ -33,6 +35,8 @@ Rectangle {
height: card.tall ? card.fullHeight
: footer.height + 28 + (chips.visible ? chips.height + 10 : 0)
+ (asks.visible ? asks.height + 10 : 0)
+ + (dos.visible ? dos.height + 10 : 0)
+ + (status.visible ? status.height + 10 : 0)
+ (actions.visible ? actions.height + 10 : 0)
Behavior on height { NumberAnimation { duration: 180; easing.type: Easing.OutCubic } }
clip: true
@@ -153,7 +157,8 @@ Rectangle {
id: chips
visible: card.canvas.staged.length > 0
anchors {
- bottom: asks.visible ? asks.top : actions.visible ? actions.top : footer.top
+ bottom: asks.visible ? asks.top : dos.visible ? dos.top : status.visible ? status.top
+ : actions.visible ? actions.top : footer.top
bottomMargin: 10
left: parent.left; right: parent.right; leftMargin: 14; rightMargin: 14
}
@@ -198,15 +203,14 @@ Rectangle {
// --- what to do with them (file) ---
// Ask: canned questions matching the staged kinds (Presets.js). A click
- // sends one; Shift+click puts it in the input to edit first. The Do row
- // (local operations) is its own spec and lands here too.
+ // sends one; Shift+click puts it in the input to edit first.
Flow {
id: asks
readonly property var offered: Presets.offered(Presets.list(Prefs.presets), card.canvas.staged)
- visible: card.mode === "file" && offered.length > 0
+ visible: card.mode === "file" && offered.length > 0 && !card.canvas.opRunning
anchors {
- bottom: actions.top; bottomMargin: 10
+ bottom: dos.visible ? dos.top : status.visible ? status.top : actions.top; bottomMargin: 10
left: parent.left; right: parent.right; leftMargin: 14; rightMargin: 14
}
spacing: 8
@@ -230,6 +234,77 @@ Rectangle {
}
}
+ // Do: local operations (Ops.js, run by fileops or the daemon's write).
+ // Compress… swaps the row for its formats. Hidden while one runs.
+ property bool compressing: false
+ Connections { target: card.canvas; function onStagedChanged() { card.compressing = false; } }
+
+ Flow {
+ id: dos
+ readonly property var offered: card.compressing
+ ? Ops.COMPRESS.concat([{ label: "Back", op: "back" }])
+ : Ops.offered(card.canvas.staged)
+ visible: card.mode === "file" && offered.length > 0 && !card.canvas.opRunning
+ anchors {
+ bottom: status.visible ? status.top : actions.top; bottomMargin: 10
+ left: parent.left; right: parent.right; leftMargin: 14; rightMargin: 14
+ }
+ spacing: 8
+
+ Repeater {
+ model: dos.offered
+ Button {
+ required property var modelData
+ text: modelData.label
+ // Local operations need no daemon; To .md does.
+ enabled: modelData.op !== "md" || card.canvas.connected
+ onClicked: {
+ if (modelData.op === "compress") card.compressing = true;
+ else if (modelData.op === "back") card.compressing = false;
+ else { card.compressing = false; card.canvas.runOp(modelData); }
+ }
+ }
+ }
+ }
+
+ // What the operation is doing, or what it made.
+ Row {
+ id: status
+ readonly property var c: card.canvas
+ visible: card.mode === "file" && (c.opRunning || c.opError !== "" || c.opOut.length > 0)
+ anchors { bottom: actions.top; bottomMargin: 10; left: parent.left; leftMargin: 14 }
+ spacing: 10
+
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: status.c.opRunning
+ ? (status.c.opDaemon && status.c.opChars
+ ? `${status.c.opLabel}… ${(status.c.opChars / 1000).toFixed(1)}k characters`
+ : `Working on ${status.c.opLabel}…`)
+ : status.c.opError !== "" ? status.c.opError
+ : status.c.opOut.length === 1 ? `Saved ${status.c.opOut[0].slice(status.c.opOut[0].lastIndexOf("/") + 1)}`
+ : `Saved ${status.c.opOut.length} files`
+ color: status.c.opError !== "" && !status.c.opRunning ? Theme.red : Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+ Button {
+ visible: status.c.opRunning
+ text: "Stop"
+ danger: true
+ onClicked: status.c.stopOp()
+ }
+ Button {
+ visible: !status.c.opRunning && status.c.opOut.length === 1
+ text: "Open"
+ onClicked: Quickshell.execDetached(["xdg-open", status.c.opOut[0]])
+ }
+ Button {
+ visible: !status.c.opRunning && status.c.opOut.length > 0
+ text: "Folder"
+ onClicked: Quickshell.execDetached(["xdg-open", status.c.outDir])
+ }
+ }
+
Row {
id: actions
visible: card.mode === "file"