Split the bar panel into one QML file per stage
Panel.qml keeps the state, the CLI calls and the stage switch (889 lines, was 2360); each stage is its own file taking the panel as a required property. The omarchy installer copies every QML/JS file and clears stale ones. tests/static.sh filters qmllint on [syntax], not the word error. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import QtQuick
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
|
||||
// TierStage.qml - One tier: pick from the cached catalogue, or type any id.
|
||||
//
|
||||
// One stage of the claude-mode panel. Panel.qml hands itself in as `panel`
|
||||
// and owns all the state and actions; this file only draws them.
|
||||
Column {
|
||||
id: stage
|
||||
required property var panel
|
||||
|
||||
width: parent.width
|
||||
visible: stage.panel.stage === "presetTier"
|
||||
spacing: Style.space(7)
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: stage.panel.editTier + " · '" + stage.panel.editPreset + "'"
|
||||
color: Color.popups.text
|
||||
elide: Text.ElideRight
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(13)
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "now: " + (stage.panel.editCurrent(stage.panel.editTier) || "not set")
|
||||
color: Color.muted
|
||||
elide: Text.ElideLeft
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(10)
|
||||
}
|
||||
|
||||
// One field that both filters the catalogue and takes any id typed by
|
||||
// hand, with the matches listed inline beneath it - rather than the
|
||||
// shell's SearchableDropdown, whose list is a second popup layered on
|
||||
// this card. Inline, the card's height is the only thing to manage,
|
||||
// and the list can never be clipped by the card's edge.
|
||||
TextField {
|
||||
id: modelField
|
||||
width: parent.width
|
||||
text: stage.panel.editModel
|
||||
placeholderText: stage.panel.modelOptions.length > 0
|
||||
? "Search " + stage.panel.modelOptions.length + " models, or type any id"
|
||||
: "Model id"
|
||||
foreground: Color.popups.text
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(11)
|
||||
onTextChanged: {
|
||||
stage.panel.editModel = text
|
||||
modelList.currentIndex = -1
|
||||
}
|
||||
// Enter takes the highlighted match if the arrows chose one, and
|
||||
// saves what is in the field otherwise.
|
||||
onAccepted: {
|
||||
if (modelList.currentIndex >= 0 && modelList.currentIndex < stage.panel.modelMatches.length)
|
||||
stage.panel.pickModel(String(stage.panel.modelMatches[modelList.currentIndex].value))
|
||||
else
|
||||
stage.panel.saveTier()
|
||||
}
|
||||
Keys.onDownPressed: function (event) {
|
||||
if (modelList.count > 0) {
|
||||
modelList.currentIndex = Math.min(modelList.currentIndex + 1, modelList.count - 1)
|
||||
modelList.positionViewAtIndex(modelList.currentIndex, ListView.Contain)
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
Keys.onUpPressed: function (event) {
|
||||
if (modelList.currentIndex >= 0) {
|
||||
modelList.currentIndex = modelList.currentIndex - 1
|
||||
if (modelList.currentIndex >= 0) modelList.positionViewAtIndex(modelList.currentIndex, ListView.Contain)
|
||||
}
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Scrolls inside a box of at most six rows: the card clamps to its
|
||||
// content rather than scrolling, so an unbounded list would push the
|
||||
// buttons off the bottom edge.
|
||||
Rectangle {
|
||||
id: modelListFrame
|
||||
readonly property int rowHeight: Style.space(30)
|
||||
width: parent.width
|
||||
visible: stage.panel.modelOptions.length > 0
|
||||
height: rowHeight * Math.max(1, Math.min(modelList.count, 6)) + 2
|
||||
radius: Style.space(4)
|
||||
color: "transparent"
|
||||
border.width: 1
|
||||
border.color: Qt.rgba(Color.popups.text.r, Color.popups.text.g, Color.popups.text.b, 0.14)
|
||||
|
||||
ListView {
|
||||
id: modelList
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
currentIndex: -1
|
||||
model: stage.panel.stage === "presetTier" ? stage.panel.modelMatches : []
|
||||
|
||||
delegate: Item {
|
||||
id: matchRow
|
||||
required property var modelData
|
||||
required property int index
|
||||
readonly property bool chosen: String(modelData.value) === stage.panel.editModel.trim()
|
||||
width: modelList.width
|
||||
height: modelListFrame.rowHeight
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: matchRow.index === modelList.currentIndex || matchArea.containsMouse
|
||||
? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.16)
|
||||
: (matchRow.chosen ? Qt.rgba(Color.accent.r, Color.accent.g, Color.accent.b, 0.10) : "transparent")
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Style.space(8)
|
||||
anchors.rightMargin: Style.space(8)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: String(matchRow.modelData.label)
|
||||
color: matchRow.chosen ? Color.accent : Color.popups.text
|
||||
elide: Text.ElideMiddle
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(11)
|
||||
font.bold: matchRow.chosen
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: text !== ""
|
||||
text: String(matchRow.modelData.description || "")
|
||||
color: Color.muted
|
||||
elide: Text.ElideRight
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(9)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: matchArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: stage.panel.pickModel(String(matchRow.modelData.value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: modelList.count === 0
|
||||
text: "No match. Save uses the id as typed."
|
||||
color: Color.muted
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(10)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: stage.panel.modelOptions.length === 0
|
||||
text: stage.panel.catalogueNode && stage.panel.catalogueNode.ok === false
|
||||
? "The last fetch failed and no list is cached. Type an id, or try fetching again."
|
||||
: "No model list cached for " + stage.panel.pTitle(stage.panel.editProvider) + " yet. Type an id, or fetch the list."
|
||||
color: Color.muted
|
||||
wrapMode: Text.WordWrap
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(10)
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: stage.panel.catalogueNode !== null && stage.panel.modelOptions.length > 0
|
||||
text: stage.panel.catalogueNode
|
||||
? stage.panel.modelOptions.length + " models cached, fetched " + stage.panel.ageLabel(stage.panel.catalogueNode.fetchedAt)
|
||||
+ (stage.panel.catalogueNode.ok === false ? " · the last refresh failed" : "")
|
||||
: ""
|
||||
color: Color.muted
|
||||
opacity: 0.85
|
||||
elide: Text.ElideRight
|
||||
font.family: stage.panel.uiFont
|
||||
font.pixelSize: Style.space(9)
|
||||
}
|
||||
|
||||
Flow {
|
||||
width: parent.width
|
||||
spacing: Style.space(7)
|
||||
|
||||
PillButton { fontFamily: stage.panel.uiFont; label: "Save"; primary: true; onTriggered: stage.panel.saveTier() }
|
||||
PillButton { fontFamily: stage.panel.uiFont;
|
||||
label: stage.panel.modelOptions.length > 0 ? "Refresh list" : "Fetch models"
|
||||
onTriggered: stage.panel.fetchModels()
|
||||
}
|
||||
PillButton { fontFamily: stage.panel.uiFont;
|
||||
label: "Back"
|
||||
onTriggered: { stage.panel.lastError = ""; stage.panel.stage = "preset" }
|
||||
}
|
||||
}
|
||||
|
||||
// The panel cannot reach this field by id, so when a tier is opened or a
|
||||
// model picked it says so, and the field is reset or filled here. Typing
|
||||
// breaks the field's binding, so without the reset a value left from another
|
||||
// tier would survive into this one.
|
||||
Connections {
|
||||
target: stage.panel
|
||||
function onTierEditorOpened() {
|
||||
modelField.text = ""
|
||||
modelList.currentIndex = -1
|
||||
Qt.callLater(function () { modelField.forceActiveFocus() })
|
||||
}
|
||||
function onModelPicked(modelId) {
|
||||
modelField.text = modelId
|
||||
modelField.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user