mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
feat(styles): create global styles entry point and organize global styles feat(styles): implement base HTML element styles and utility classes for flexbox feat(styles): establish layout, position, spacing, and text utility classes feat(styles): introduce mixins for animations, cards, dialogs, flexbox, grid, and responsive design test(quick_guide): add component and metadata validation tests for quick_guide package test(ui_level6): implement metadata validation tests for ui_level6 package
348 lines
12 KiB
QML
348 lines
12 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import "../contexts"
|
|
import "../fakemui"
|
|
|
|
/**
|
|
* TaskList.qml - Task list component
|
|
* Uses Python AppController (app) directly instead of HTTP/XHR
|
|
*/
|
|
Item {
|
|
id: root
|
|
|
|
signal taskSelected(int index)
|
|
|
|
// State
|
|
property bool loading: true
|
|
property string error: ""
|
|
property string filter: "current"
|
|
property int limit: 20
|
|
|
|
Component.onCompleted: {
|
|
// Connect to Python controller signals
|
|
app.tasksLoaded.connect(onTasksLoaded)
|
|
app.errorOccurred.connect(onError)
|
|
// Load tasks via Python API
|
|
app.loadTasks()
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
app.tasksLoaded.disconnect(onTasksLoaded)
|
|
app.errorOccurred.disconnect(onError)
|
|
}
|
|
|
|
function onTasksLoaded() {
|
|
loading = false
|
|
error = ""
|
|
}
|
|
|
|
function onError(msg) {
|
|
loading = false
|
|
error = msg
|
|
}
|
|
|
|
function refresh() {
|
|
loading = true
|
|
error = ""
|
|
app.loadTasks()
|
|
}
|
|
|
|
function archiveTask(index) {
|
|
app.archiveTask(index)
|
|
}
|
|
|
|
function getStatusColor(status, hasPr) {
|
|
if (hasPr) return Theme.success
|
|
if (status === "completed") return Theme.success
|
|
if (status === "running") return Theme.warning
|
|
return Theme.textMuted
|
|
}
|
|
|
|
function getStatusLabel(status, hasPr, prUrl) {
|
|
if (hasPr && prUrl) {
|
|
// Extract PR number from URL if possible
|
|
const match = prUrl.match(/\/pull\/(\d+)/)
|
|
if (match) return "PR #" + match[1]
|
|
return "Has PR"
|
|
}
|
|
return status || "pending"
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 16
|
|
spacing: 16
|
|
|
|
// Toolbar
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 12
|
|
|
|
// Filter dropdown
|
|
ComboBox {
|
|
id: filterCombo
|
|
model: [
|
|
{ value: "current", text: LanguageContext.t("current") },
|
|
{ value: "archived", text: LanguageContext.t("archived") },
|
|
{ value: "all", text: LanguageContext.t("all") }
|
|
]
|
|
textRole: "text"
|
|
valueRole: "value"
|
|
currentIndex: 0
|
|
onActivated: {
|
|
filter = currentValue
|
|
refresh()
|
|
}
|
|
|
|
background: Rectangle {
|
|
color: Theme.surface
|
|
border.color: Theme.border
|
|
border.width: 1
|
|
radius: 4
|
|
implicitWidth: 120
|
|
implicitHeight: 36
|
|
}
|
|
}
|
|
|
|
// Limit dropdown
|
|
ComboBox {
|
|
id: limitCombo
|
|
model: [10, 20, 50, 100]
|
|
currentIndex: 1
|
|
onActivated: {
|
|
limit = currentValue
|
|
refresh()
|
|
}
|
|
|
|
background: Rectangle {
|
|
color: Theme.surface
|
|
border.color: Theme.border
|
|
border.width: 1
|
|
radius: 4
|
|
implicitWidth: 80
|
|
implicitHeight: 36
|
|
}
|
|
}
|
|
|
|
// Refresh button
|
|
CIconButton {
|
|
icon: "🔄"
|
|
onClicked: refresh()
|
|
}
|
|
|
|
Item { Layout.fillWidth: true }
|
|
|
|
// Task count - use model count
|
|
Text {
|
|
text: app.taskModel.rowCount() + " " + LanguageContext.t("tasksCount")
|
|
color: Theme.textSecondary
|
|
font.pixelSize: 14
|
|
}
|
|
}
|
|
|
|
// Error alert
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
visible: error !== ""
|
|
height: 48
|
|
color: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
|
|
radius: 4
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: error
|
|
color: Theme.error
|
|
font.pixelSize: 14
|
|
}
|
|
}
|
|
|
|
// Loading
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
visible: loading
|
|
|
|
BusyIndicator {
|
|
anchors.centerIn: parent
|
|
running: loading
|
|
}
|
|
}
|
|
|
|
// Task Grid - uses app.taskModel from Python controller
|
|
ScrollView {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
visible: !loading
|
|
clip: true
|
|
|
|
GridLayout {
|
|
id: taskGrid
|
|
width: parent.width
|
|
// Auto-fill grid similar to CSS: repeat(auto-fill, minmax(320px, 1fr))
|
|
columns: Math.max(1, Math.floor(width / 320))
|
|
columnSpacing: 16
|
|
rowSpacing: 16
|
|
|
|
Repeater {
|
|
model: app.taskModel
|
|
|
|
delegate: CCard {
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 220
|
|
|
|
required property int index
|
|
required property string taskId
|
|
required property string title
|
|
required property string status
|
|
required property string repo
|
|
required property string branch
|
|
required property string created
|
|
required property string alias
|
|
required property string prUrl
|
|
required property bool hasPr
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
spacing: 0
|
|
|
|
// Card content area
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.margins: 16
|
|
spacing: 8
|
|
|
|
// Header row with status chips
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
|
|
CChip {
|
|
text: getStatusLabel(status, hasPr, prUrl)
|
|
color: getStatusColor(status, hasPr)
|
|
}
|
|
|
|
CChip {
|
|
text: "#" + alias
|
|
variant: "outlined"
|
|
}
|
|
}
|
|
|
|
// Title
|
|
Text {
|
|
Layout.fillWidth: true
|
|
text: title || LanguageContext.t("untitledTask")
|
|
font.pixelSize: 16
|
|
font.bold: true
|
|
color: Theme.text
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 2
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
// Description placeholder (would use prompt from full task data)
|
|
Text {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
text: "" // Description not available from TaskModel roles
|
|
font.pixelSize: 13
|
|
color: Theme.textSecondary
|
|
wrapMode: Text.WordWrap
|
|
maximumLineCount: 2
|
|
elide: Text.ElideRight
|
|
visible: false // Hidden until we have description data
|
|
}
|
|
|
|
// Spacer to push content up
|
|
Item { Layout.fillHeight: true }
|
|
|
|
// Repo
|
|
Text {
|
|
Layout.fillWidth: true
|
|
text: repo || LanguageContext.t("noRepo")
|
|
font.pixelSize: 13
|
|
color: Theme.textSecondary
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
// Branch / ID (nerd mode)
|
|
Text {
|
|
text: NerdModeContext.nerdMode ? taskId : (branch || "main")
|
|
font.pixelSize: 12
|
|
font.family: "monospace"
|
|
color: Theme.textMuted
|
|
elide: Text.ElideMiddle
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
// Actions row with top border (matches React card__actions)
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
height: 1
|
|
color: Theme.border
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.margins: 8
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
spacing: 8
|
|
|
|
CButton {
|
|
text: LanguageContext.t("view")
|
|
variant: "outlined"
|
|
size: "small"
|
|
onClicked: taskSelected(index)
|
|
}
|
|
|
|
Item { Layout.fillWidth: true }
|
|
|
|
CIconButton {
|
|
icon: "📝"
|
|
size: "small"
|
|
tooltip: LanguageContext.t("getPatch")
|
|
onClicked: {
|
|
app.extractPatch(index)
|
|
}
|
|
}
|
|
|
|
CIconButton {
|
|
icon: "📦"
|
|
size: "small"
|
|
tooltip: LanguageContext.t("archive")
|
|
onClicked: archiveTask(index)
|
|
}
|
|
|
|
CIconButton {
|
|
icon: "🔗"
|
|
size: "small"
|
|
tooltip: "Open PR"
|
|
visible: hasPr && prUrl !== ""
|
|
onClicked: app.openUrl(prUrl)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Empty state
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
visible: !loading && app.taskModel.rowCount() === 0
|
|
|
|
CEmptyState {
|
|
anchors.centerIn: parent
|
|
icon: "📋"
|
|
title: LanguageContext.t("noTasks")
|
|
}
|
|
}
|
|
}
|
|
}
|