Files
php-qml/examples/todo/qml/Main.qml
magdev fcf7dc26cf
Some checks failed
CI / Quality (push) Successful in 5m3s
Release / Linux AppImage (push) Failing after 4m50s
qml: silence skeleton + todo Main.qml qmllint warnings
Two warnings, two distinct kinds of fix:

1. `Item { width: 12 }` (skeleton:77) — explicit width on a
   layout-managed Item is undefined behaviour per qmllint. Replaced
   with `Item { Layout.preferredWidth: 12 }`, matching the pattern
   already used at line 85 (`Item { Layout.fillWidth: true }`). Real
   fix, not a suppression.

2. `target: SingleInstance` (skeleton:48, todo/Main.qml:220) — false
   positive. SingleInstance is intentionally a context property set
   by main() before the QML engine boots (see SingleInstance.h
   doc comment), so qmllint can't see it via static analysis.
   Disabled the `unqualified` warning at the call site with an inline
   `// qmllint disable unqualified` directive plus a one-line
   explanation comment above. (Note: the disable directive parses
   every word after `disable` as a category name, so the prose has
   to live on the previous line — found the hard way after qmllint
   complained about the "unknown category" of every English word in
   the explanation.)

Verified `make quality` from framework/skeleton green (qmllint clean
across both targets — `php_qml_bridge_qmllint` and `skeleton_qmllint`
both build with zero warnings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 11:05:17 +02:00

227 lines
7.9 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import PhpQml.Bridge
import Todo // local module — picks up TodoList.qml + TodoWindow.qml
ApplicationWindow {
id: window
width: 720
height: 560
visible: true
title: "php-qml — Todo example"
// Single shared model means a second window sees the same data.
// (Each ReactiveListModel instance has its own MercureClient
// subscription, but the underlying server state is the same.)
property alias rest: rest
property alias todoModel: todoModel
RestClient {
id: rest
baseUrl: BackendConnection.url
token: BackendConnection.token
}
ReactiveListModel {
id: todoModel
baseUrl: BackendConnection.url
token: BackendConnection.token
source: "/api/todos"
topic: "app://model/todo"
onCommandFailed: function(key, status, problem) {
log.append("× failed " + status + ": " + JSON.stringify(problem))
}
onCommandTimedOut: function(key) {
log.append("× timed out " + key)
}
}
Component {
id: secondWindowCmp
TodoWindow {}
}
AppShell {
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 12
// ── Header / actions ────────────────────────────────────
RowLayout {
Layout.fillWidth: true
spacing: 8
Label {
text: "Todos"
font.pixelSize: 18
font.bold: true
}
Item { Layout.fillWidth: true }
Button {
text: "Mark all done"
onClicked: {
rest.post("/api/mark-all-done").then(function(r) {
log.append("→ mark-all-done")
}).catch(function(e) {
log.append("× mark-all-done " + e.status)
})
}
}
Button {
text: "Open second window"
onClicked: {
const w = secondWindowCmp.createObject()
if (w) w.show()
}
}
}
// ── Add input ───────────────────────────────────────────
RowLayout {
Layout.fillWidth: true
spacing: 8
TextField {
id: newTodoField
Layout.fillWidth: true
placeholderText: "What needs doing?"
onAccepted: addBtn.clicked()
}
Button {
id: addBtn
text: "Add"
enabled: newTodoField.text.trim().length > 0
&& BackendConnection.connectionState === BackendConnection.Online
onClicked: {
const title = newTodoField.text.trim()
if (!title) return
rest.post("/api/todos", {title: title, done: false})
.then(function(r) { log.append("→ added '" + title + "'") })
.catch(function(e) { log.append("× add failed " + e.status) })
newTodoField.clear()
}
}
}
// ── Todo list ───────────────────────────────────────────
Frame {
Layout.fillWidth: true
Layout.fillHeight: true
padding: 0
ListView {
id: todoView
anchors.fill: parent
clip: true
model: todoModel
spacing: 1
delegate: ItemDelegate {
required property string id
required property string title
required property bool done
required property bool pending
width: ListView.view.width
opacity: pending ? 0.5 : 1.0
contentItem: RowLayout {
spacing: 8
CheckBox {
checked: done
enabled: !pending
onToggled: {
todoModel.invoke(
"PATCH",
"/api/todos/" + id,
{ done: checked },
{ op: "upsert", id: id, data: { id: id, title: title, done: checked } }
)
}
}
Label {
Layout.fillWidth: true
text: title
font.strikeout: done
elide: Text.ElideRight
}
Button {
text: "×"
flat: true
enabled: !pending
onClicked: {
todoModel.invoke(
"DELETE",
"/api/todos/" + id,
null,
{ op: "delete", id: id }
)
}
}
}
}
Label {
anchors.centerIn: parent
visible: todoView.count === 0 && todoModel.ready
text: "No todos yet — add one above."
opacity: 0.6
}
}
}
// ── Status / log ────────────────────────────────────────
Label {
text: BackendConnection.url
color: "#888"
font.pixelSize: 11
elide: Text.ElideRight
Layout.fillWidth: true
}
Frame {
Layout.fillWidth: true
Layout.preferredHeight: 100
padding: 0
ScrollView {
anchors.fill: parent
TextArea {
id: log
readOnly: true
wrapMode: TextArea.Wrap
font.family: "monospace"
font.pixelSize: 11
}
}
}
DevConsole {
id: devConsole
visible: false
Layout.fillWidth: true
Layout.preferredHeight: 220
}
}
}
// Ctrl+` toggles the FrankenPHP child output console (Phase 5 §13).
Shortcut {
sequences: ["Ctrl+`", "Ctrl+~"]
onActivated: devConsole.visible = !devConsole.visible
}
Connections {
// SingleInstance is a context property set in main(); qmllint can't see it.
target: SingleInstance // qmllint disable unqualified
function onLaunchArgsReceived(args) {
window.requestActivate()
}
}
}