282 lines
13 KiB
QML
282 lines
13 KiB
QML
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
|
|
Item {
|
|
id: tree
|
|
property bool visiblePane: true
|
|
property int renamingIndex: -1
|
|
property string renamingText: ""
|
|
property string killTargetSessionId: ""
|
|
property string killTargetAgentSessionId: ""
|
|
property string killTargetAgentId: ""
|
|
signal newSessionRequested()
|
|
signal settingsRequested()
|
|
signal newAgentRequested(string sessionId)
|
|
signal renameFailed(string itemType, string attemptedName)
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
id: sidebarPane
|
|
visible: tree.visiblePane
|
|
spacing: 0
|
|
|
|
ToolBar {
|
|
Layout.fillWidth: true
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
Label { text: "Sessions"; Layout.fillWidth: true; font.bold: true; leftPadding: 8 }
|
|
ToolButton {
|
|
text: "\u2795"
|
|
onClicked: tree.newSessionRequested()
|
|
ToolTip.text: "New Session"
|
|
ToolTip.visible: hovered
|
|
}
|
|
ToolButton {
|
|
text: "\u21BB"
|
|
onClicked: ollie.refreshSessions()
|
|
}
|
|
ToolButton {
|
|
text: "\u2699"
|
|
onClicked: tree.settingsRequested()
|
|
ToolTip.text: "Settings"
|
|
ToolTip.visible: hovered
|
|
}
|
|
}
|
|
}
|
|
|
|
ListView {
|
|
id: sessionList
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
model: sessionModel
|
|
clip: true
|
|
focus: false
|
|
activeFocusOnTab: false
|
|
|
|
// Show placeholder when empty or disconnected
|
|
Label {
|
|
anchors.centerIn: parent
|
|
text: ollie.daemonConnected ? "(no sessions)" : "(not connected)"
|
|
color: theme.midColor
|
|
font.italic: true
|
|
font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.9 : 12
|
|
visible: sessionList.count === 0 || !ollie.daemonConnected
|
|
activeFocusOnTab: false
|
|
}
|
|
|
|
delegate: ItemDelegate {
|
|
width: sessionList.width
|
|
|
|
// Guard against undefined during initial render
|
|
height: {
|
|
if (!type) return 0
|
|
if (type === "header") return 24
|
|
if (type === "agent" && !parentExpanded)
|
|
return 0
|
|
return 28
|
|
}
|
|
visible: height > 0
|
|
property int modelIndex: index
|
|
property bool isHeader: type === "header"
|
|
|
|
// Headers are not interactive
|
|
enabled: !isHeader
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
hoverEnabled: false
|
|
enabled: !parent.isHeader
|
|
onClicked: function(mouse) {
|
|
if (mouse.button === Qt.RightButton) {
|
|
if (type === "session") {
|
|
var item = sessionModel.get(parent.modelIndex)
|
|
if (item)
|
|
tree.killTargetSessionId = item.id
|
|
sessionCtxMenu.popup()
|
|
} else if (type === "agent") {
|
|
var agentItem = sessionModel.get(parent.modelIndex)
|
|
if (agentItem) {
|
|
tree.killTargetAgentSessionId = agentItem.parentId
|
|
tree.killTargetAgentId = agentItem.agentId
|
|
}
|
|
agentCtxMenu.popup()
|
|
}
|
|
return
|
|
}
|
|
if (renamingIndex >= 0) return
|
|
if (type === "session") {
|
|
// Find first agent in this session and select it
|
|
var foundAgent = null
|
|
for (var i = 0; i < sessionModel.count; ++i) {
|
|
var item = sessionModel.get(i)
|
|
if (item.type === "agent" && item.parentId === id) {
|
|
foundAgent = item
|
|
break
|
|
}
|
|
}
|
|
if (foundAgent) {
|
|
// Select the first agent
|
|
ollie.switchAgent(id, foundAgent.agentId)
|
|
} else {
|
|
// No agents - just select session (clears agent)
|
|
ollie.setActiveSessionId(id)
|
|
}
|
|
sessionModel.setExpanded(parent.modelIndex, !expanded)
|
|
} else if (type === "agent") {
|
|
ollie.switchAgent(parentId, agentId)
|
|
}
|
|
}
|
|
onDoubleClicked: {
|
|
if (type === "session" || type === "agent") {
|
|
startRename(parent.modelIndex)
|
|
}
|
|
}
|
|
|
|
Menu {
|
|
id: sessionCtxMenu
|
|
MenuItem {
|
|
text: "New Agent..."
|
|
onClicked: {
|
|
tree.newAgentRequested(tree.killTargetSessionId)
|
|
}
|
|
}
|
|
MenuItem {
|
|
text: ollie.isSessionPaused(tree.killTargetSessionId) ? "Resume" : "Pause"
|
|
onClicked: {
|
|
if (tree.killTargetSessionId) {
|
|
if (ollie.isSessionPaused(tree.killTargetSessionId))
|
|
ollie.resumeSession(tree.killTargetSessionId)
|
|
else
|
|
ollie.pauseSession(tree.killTargetSessionId)
|
|
}
|
|
}
|
|
}
|
|
MenuItem {
|
|
text: "Kill Session"
|
|
onClicked: {
|
|
if (tree.killTargetSessionId)
|
|
ollie.killSession(tree.killTargetSessionId)
|
|
ollie.refreshSessions()
|
|
}
|
|
}
|
|
}
|
|
|
|
Menu {
|
|
id: agentCtxMenu
|
|
MenuItem {
|
|
text: "Kill"
|
|
onClicked: {
|
|
if (tree.killTargetAgentSessionId && tree.killTargetAgentId)
|
|
ollie.killAgent(tree.killTargetAgentSessionId, tree.killTargetAgentId)
|
|
ollie.refreshSessions()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
contentItem: RowLayout {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
// Indent + expand/collapse indicator
|
|
Item {
|
|
Layout.preferredWidth: type === "header" ? 8 : (level || 0) * 16 + 16
|
|
Layout.preferredHeight: type === "header" ? 24 : 28
|
|
|
|
Label {
|
|
anchors.centerIn: parent
|
|
text: type === "session" ? (expanded ? "▼" : "▶") : ""
|
|
font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.7 : 10
|
|
color: theme.textColor
|
|
opacity: 0.6
|
|
visible: type !== "header"
|
|
}
|
|
}
|
|
|
|
// Connection status indicator (session rows only)
|
|
Rectangle {
|
|
Layout.preferredWidth: 8
|
|
Layout.preferredHeight: 8
|
|
Layout.alignment: Qt.AlignVCenter
|
|
Layout.rightMargin: 6
|
|
radius: 4
|
|
color: connectionColor || "gray"
|
|
visible: type === "session"
|
|
}
|
|
|
|
// Name area
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: type === "header" ? 24 : 28
|
|
|
|
TextField {
|
|
anchors.fill: parent
|
|
text: (modelIndex === renamingIndex) ? renamingText : ""
|
|
visible: modelIndex === renamingIndex
|
|
verticalAlignment: Text.AlignVCenter
|
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
|
|
color: theme.textColor
|
|
background: Rectangle {
|
|
color: theme.baseColor
|
|
border.color: theme.highlightColor
|
|
radius: 2
|
|
}
|
|
onTextChanged: {
|
|
if (modelIndex === renamingIndex) {
|
|
renamingText = text
|
|
}
|
|
}
|
|
Keys.onReturnPressed: commitRename()
|
|
Keys.onEscapePressed: cancelRename()
|
|
onAccepted: commitRename()
|
|
onVisibleChanged: {
|
|
if (visible) {
|
|
forceActiveFocus()
|
|
selectAll()
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
anchors.fill: parent
|
|
text: displayName || ""
|
|
color: type === "header" ? theme.midColor : theme.textColor
|
|
font.pointSize: theme.fontSize > 0 ? theme.fontSize * (type === "header" ? 0.85 : 1) : Qt.application.font.pointSize
|
|
font.bold: type === "header"
|
|
font.capitalization: type === "header" ? Font.AllUppercase : Font.MixedCase
|
|
verticalAlignment: Text.AlignVCenter
|
|
visible: modelIndex !== renamingIndex
|
|
}
|
|
}
|
|
}
|
|
|
|
highlighted: (type === "agent" && agentId === ollie.activeAgentId && parentId === ollie.activeSessionId)
|
|
}
|
|
}
|
|
}
|
|
function startRename(index) {
|
|
var item = sessionModel.get(index)
|
|
if (item.type === "session" || item.type === "agent") {
|
|
tree.renamingIndex = index
|
|
tree.renamingText = item.type === "session" ? (item.baseName || item.displayName) : item.displayName
|
|
}
|
|
}
|
|
|
|
function commitRename() {
|
|
if (tree.renamingIndex < 0) return
|
|
var item = sessionModel.get(tree.renamingIndex)
|
|
var name = tree.renamingText.trim()
|
|
if (name !== "") {
|
|
var ok = item.type === "session" ? ollie.renameSession(item.id, name) : ollie.renameAgent(item.parentId, item.agentId, name)
|
|
if (!ok) tree.renameFailed(item.type, name)
|
|
}
|
|
tree.renamingIndex = -1
|
|
tree.renamingText = ""
|
|
}
|
|
|
|
function cancelRename() { tree.renamingIndex = -1; tree.renamingText = "" }
|
|
|
|
}
|