feat: wire family

code display and status restore in StatusTab + HomePage
This commit is contained in:
jack
2026-06-30 13:22:44 -07:00
parent 5105ab1ab6
commit d7962f2850
2 changed files with 506 additions and 362 deletions
+2
View File
@@ -323,6 +323,7 @@ Rectangle {
label: "READ MEMORY"
iconSource: "../icons/read.svg"
Layout.fillWidth: true
enabled: root.waferDetected
onClicked: deviceController.readMemoryAsync()
}
@@ -331,6 +332,7 @@ Rectangle {
iconSource: "../icons/erase.svg"
Layout.fillWidth: true
destructive: true
enabled: root.waferDetected
onClicked: deviceController.eraseMemory(
deviceController.selectedPort)
}
+394 -252
View File
@@ -4,37 +4,106 @@ import QtQuick.Controls
import QtQuick.Dialogs
import ISC
// ===== Status Tab =====
// Shows connection status, wafer info, and activity log.
// Initially visible — displays connection status and activity log from start.
// ===== Status Tab — Bento Grid Dashboard =====
// ROW 1: Connection Status (65%) | Cycles Completed (35%)
// ROW 2: Wafer & Sensor + Directory (65%) | Total Runtime (35%)
// ROW 3: Activity Log (100%, fills remaining)
ColumnLayout {
id: root
anchors.fill: parent
anchors.margins: Theme.panelPadding
spacing: Theme.rightPaneGap
spacing: 8
property alias waferFamilyCode: waferInfoFamily.text
property alias waferSerialNumber: waferSerial.text
property alias waferSensorCount: waferSensors.text
property alias waferRuntime: waferRuntime.text
property alias waferCycles: waferCycles.text
property bool waferDetected: false
// ── Reusable Inline Components ──
component StatCard : Rectangle {
property alias value: valueText.text
property alias label: labelText.text
property bool active: root.waferDetected
property int valSize: active ? Theme.font3xl : Theme.font2xl
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusSm
ColumnLayout {
anchors.centerIn: parent
spacing: 4
Text {
id: valueText
color: active ? Theme.headingColor : Theme.sideMutedText
font.pixelSize: valSize
font.weight: Font.Bold
font.family: Theme.uiFontFamily
Layout.alignment: Qt.AlignHCenter
}
Text {
id: labelText
color: Theme.sideMutedText
font.pixelSize: Theme.fontXs
font.weight: Font.Medium
font.letterSpacing: 1.2
font.family: Theme.uiFontFamily
Layout.alignment: Qt.AlignHCenter
}
}
}
component GridCell : ColumnLayout {
property alias label: labelText.text
property alias value: valueText.text
property bool isRight: false
property bool active: root.waferDetected
spacing: 1
Layout.alignment: isRight ? Qt.AlignRight : Qt.AlignLeft
Text {
id: labelText
color: Theme.sideMutedText
font.pixelSize: Theme.fontXs
font.family: Theme.uiFontFamily
Layout.alignment: isRight ? Qt.AlignRight : Qt.AlignLeft
}
Text {
id: valueText
color: active ? Theme.headingColor : Theme.sideMutedText
font.pixelSize: Theme.fontMd
font.weight: Font.Bold
font.family: Theme.uiFontFamily
Layout.alignment: isRight ? Qt.AlignRight : Qt.AlignLeft
}
}
// ── Aliases for external wiring ──
property alias waferFamilyCode: waferInfoFamilyCell.value
property alias waferSerialNumber: waferSerialCell.value
property alias waferSensorCount: waferSensorsCell.value
property alias waferRuntime: runtimeCard.value
property alias waferCycles: waferCyclesCell.value
property bool waferDetected: {
var info = deviceController.lastWaferInfo;
return info && info.length > 0 && info[0] !== "";
}
// Data summary after parse
property int dataRows: 0
property int dataCols: 0
property string csvPath: ""
property bool dataParsed: false
readonly property bool isConnected: deviceController.connectionStatus === "Connected"
readonly property bool isBusy: deviceController.connectionStatus.endsWith("...")
readonly property string portName: deviceController.selectedPort || "—"
function cleanFolderUrl(url) {
return decodeURIComponent(String(url).replace(/^file:\/\//, ""));
}
function currentFamilyCode() {
var info = deviceController.lastWaferInfo;
return info && info.length > 0 ? (info[0] || "") : "";
}
function parseAndSavePendingRead() {
deviceController.parseAndSaveData(root.currentFamilyCode(), deviceController.selectedPort || "");
}
@@ -48,268 +117,349 @@ ColumnLayout {
}
}
ColumnLayout {
// ═══════════════════════════════════════════════════════════════════════
// ROW 1: Connection Status (100%)
// ═══════════════════════════════════════════════════════════════════════
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: Theme.rightPaneGap
Layout.preferredHeight: 90
Layout.maximumHeight: 90
spacing: 8
// --- Connection Status ---
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 92
Layout.fillHeight: true
color: Theme.cardBackground
border.color: {
if (deviceController.connectionStatus === "Connected")
return Theme.statusSuccessColor;
if (deviceController.connectionStatus === "Disconnected")
return Theme.statusErrorColor;
return Theme.cardBorder;
border.color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.statusErrorColor)
border.width: 1
radius: Theme.radiusSm
ColumnLayout {
id: connStatusCol
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.panelPadding
anchors.rightMargin: Theme.panelPadding
spacing: 8
Text {
text: "CONNECTION STATUS"
color: Theme.sideMutedText
font.pixelSize: Theme.fontXs
font.weight: Font.Medium
font.letterSpacing: 1.2
font.family: Theme.uiFontFamily
}
RowLayout {
Layout.fillWidth: true
spacing: 0
ColumnLayout {
spacing: 1
Text {
text: "HOST PC"
color: Theme.sideMutedText
font.pixelSize: Theme.fontXs
font.family: Theme.uiFontFamily
}
Text {
text: "localhost"
color: Theme.headingColor
font.pixelSize: Theme.fontMd
font.weight: Font.Bold
font.family: Theme.uiFontFamily
}
}
Item {
Layout.fillWidth: true
implicitHeight: 22
Row {
anchors.centerIn: parent
spacing: 5
Repeater {
model: 4
Rectangle {
width: 4; height: 4; radius: 2
anchors.verticalCenter: parent.verticalCenter
color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.sideMutedText)
opacity: (root.isConnected || root.isBusy) ? 1.0 : 0.3
SequentialAnimation on opacity {
running: root.isConnected || root.isBusy
loops: Animation.Infinite
NumberAnimation { to: 0.3; duration: 1400 }
NumberAnimation { to: 1.0; duration: 1400 }
}
}
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: statusPillText.implicitWidth + 16
height: 20
radius: 10
color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.statusErrorColor)
Text {
id: statusPillText
anchors.centerIn: parent
text: root.isConnected ? "ACTIVE" : (root.isBusy ? deviceController.connectionStatus.replace("...", "").toUpperCase() : "INACTIVE")
color: "#111111"
font.pixelSize: Theme.fontXs
font.weight: Font.Bold
font.family: Theme.uiFontFamily
font.letterSpacing: 0.6
}
}
Repeater {
model: 4
Rectangle {
width: 4; height: 4; radius: 2
anchors.verticalCenter: parent.verticalCenter
color: root.isConnected ? Theme.statusSuccessColor : (root.isBusy ? Theme.statusWarningColor : Theme.sideMutedText)
opacity: (root.isConnected || root.isBusy) ? 1.0 : 0.3
SequentialAnimation on opacity {
running: root.isConnected || root.isBusy
loops: Animation.Infinite
NumberAnimation { to: 0.3; duration: 1400 }
NumberAnimation { to: 1.0; duration: 1400 }
}
}
}
}
}
ColumnLayout {
spacing: 1
Text {
text: "COM PORT"
color: Theme.sideMutedText
font.pixelSize: Theme.fontXs
font.family: Theme.uiFontFamily
Layout.alignment: Qt.AlignRight
}
Text {
text: root.portName
color: root.isConnected ? Theme.statusSuccessColor : Theme.headingColor
font.pixelSize: Theme.fontMd
font.weight: Font.Bold
font.family: Theme.uiFontFamily
Layout.alignment: Qt.AlignRight
}
}
}
}
}
}
// ═══════════════════════════════════════════════════════════════════════
// ROW 2: Wafer & Sensor + Directory (65%) | Total Runtime (35%)
// ═══════════════════════════════════════════════════════════════════════
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 90
Layout.maximumHeight: 90
spacing: 8
RowLayout {
Layout.preferredWidth: 65
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 8
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusSm
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.panelPadding
spacing: 4
spacing: 6
Text {
text: deviceController.connectionStatus
color: {
if (deviceController.connectionStatus === "Connected")
return Theme.statusSuccessColor;
if (deviceController.connectionStatus === "Disconnected")
return Theme.statusErrorColor;
return Theme.bodyColor;
}
font.bold: true
font.pixelSize: Theme.fontLg
ColumnLayout {
Layout.fillWidth: true
}
spacing: 2
Text {
text: "Port: " + (deviceController.selectedPort || "None selected")
color: Theme.bodyColor
text: "DIRECTORY"
color: Theme.sideMutedText
font.pixelSize: Theme.fontMd
font.weight: Font.Medium
font.letterSpacing: 1.2
font.family: Theme.uiFontFamily
}
Text {
text: deviceController.saveDataDir || "Not configured"
color: Theme.bodyColor
font.pixelSize: Theme.fontSm
font.family: Theme.uiFontFamily
font.italic: true
opacity: 0.7
elide: Text.ElideMiddle
Layout.fillWidth: true
}
}
RowLayout {
Layout.fillWidth: true
spacing: 8
Layout.alignment: Qt.AlignVCenter
Text {
text: "Save dir: " + (deviceController.saveDataDir || "None selected")
color: Theme.bodyColor
font.pixelSize: Theme.fontSm
opacity: 0.7
elide: Text.ElideMiddle
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
Button {
id: browseBtn
text: "BROWSE..."
font.pixelSize: Theme.font2xs
text: "Browse"
font.pixelSize: Theme.fontSm
font.weight: Font.Medium
implicitHeight: 20
implicitWidth: 65
font.family: Theme.uiFontFamily
implicitHeight: 24
implicitWidth: 60
hoverEnabled: true
background: Rectangle {
color: parent.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
color: browseBtn.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
text: browseBtn.text
color: Theme.bodyColor
font: parent.font
font: browseBtn.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
onClicked: saveDirDialog.open()
}
Item { Layout.fillWidth: true }
Row {
spacing: 4
visible: root.isConnected
Rectangle {
width: 6; height: 6; radius: 3
color: Theme.statusSuccessColor
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
visible: root.csvPath !== ""
width: sessionSavedLabel.implicitWidth + 12
height: 18
radius: 4
color: Theme.fieldBackground
border.color: Theme.sideBorder
border.width: 1
Text {
id: sessionSavedLabel
anchors.centerIn: parent
text: "SESSION SAVED"
color: Theme.sideMutedText
font.pixelSize: Theme.fontXs
font.weight: Font.Bold
font.family: Theme.uiFontFamily
font.letterSpacing: 0.6
}
}
}
}
}
// --- Wafer Info ---
GroupBox {
title: "Wafer Information"
visible: root.waferDetected
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusSm
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.panelPadding
spacing: 8
RowLayout {
Item {
Layout.fillWidth: true
spacing: Theme.rightPaneGap
Layout.fillHeight: true
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "Family Code"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
}
Text {
id: waferInfoFamily
text: "—"
color: Theme.bodyColor
font.pixelSize: Theme.fontLg
font.bold: true
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "Serial Number"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
}
Text {
id: waferSerial
text: "—"
color: Theme.bodyColor
font.pixelSize: Theme.fontLg
font.bold: true
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "Sensor Count"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
}
Text {
id: waferSensors
text: "—"
color: Theme.bodyColor
font.pixelSize: Theme.fontLg
font.bold: true
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.rightPaneGap
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "Runtime"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
}
Text {
id: waferRuntime
text: "—"
color: Theme.bodyColor
font.pixelSize: Theme.fontLg
font.bold: true
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "Cycles"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
}
Text {
id: waferCycles
text: "—"
color: Theme.bodyColor
font.pixelSize: Theme.fontLg
font.bold: true
}
}
}
}
}
// --- Data Summary (shown after parse) ---
GroupBox {
title: "Data Summary"
visible: root.dataParsed
Layout.fillWidth: true
RowLayout {
GridLayout {
anchors.fill: parent
anchors.margins: Theme.panelPadding
spacing: Theme.rightPaneGap
columns: 2
rowSpacing: 6
columnSpacing: 16
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "Rows"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
}
Text {
text: String(root.dataRows)
color: Theme.bodyColor
font.pixelSize: Theme.fontLg
font.bold: true
GridCell {
id: waferInfoFamilyCell
label: "Family Code"
value: {
var info = deviceController.lastWaferInfo;
return (info && info.length > 0 && info[0]) ? info[0] : "—";
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "Sensors"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
GridCell {
id: waferCyclesCell
label: "Cycles Completed"
value: {
var info = deviceController.lastWaferInfo;
return (info && info.length > 5 && info[5] !== undefined) ? String(info[5]) : "—";
}
Text {
text: String(root.dataCols)
color: Theme.bodyColor
font.pixelSize: Theme.fontLg
font.bold: true
isRight: true
active: root.waferDetected
}
GridCell {
id: waferSerialCell
label: "Serial"
value: {
var info = deviceController.lastWaferInfo;
return (info && info.length > 1 && info[1]) ? info[1] : "—";
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: "CSV"
color: Theme.subheadingColor
font.pixelSize: Theme.fontSm
GridCell {
id: waferSensorsCell
label: "Sensors"
value: {
var info = deviceController.lastWaferInfo;
return (info && info.length > 2 && info[2]) ? String(info[2]) : "—";
}
isRight: true
}
}
Text {
text: root.csvPath
color: Theme.bodyColor
font.pixelSize: Theme.fontSm
elide: Text.ElideMiddle
}
}
}
}
// --- Activity Log ---
StatCard {
id: runtimeCard
Layout.preferredWidth: 35
value: {
var info = deviceController.lastWaferInfo;
return (info && info.length > 4 && info[4] !== undefined) ? info[4] + "s" : "—";
}
label: "TOTAL RUNTIME"
}
}
// ═══════════════════════════════════════════════════════════════════════
// ROW 3: Activity Log — ALWAYS visible (fills remaining height)
// ═══════════════════════════════════════════════════════════════════════
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: Theme.cardBackground
border.color: Theme.cardBorder
border.width: 1
radius: Theme.radiusMd
clip: true
@@ -317,40 +467,51 @@ ColumnLayout {
anchors.fill: parent
spacing: 0
// Header
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 40
Layout.preferredHeight: 36
color: Theme.subtleSectionBackground
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.panelPadding
anchors.rightMargin: Theme.panelPadding
spacing: 8
Text {
text: "📄"
font.pixelSize: Theme.fontSm
visible: root.csvPath !== ""
Layout.alignment: Qt.AlignVCenter
}
Label {
text: "ACTIVITY LOG"
text: root.csvPath !== "" ? root.csvPath.split("/").pop() : "ACTIVITY LOG"
color: Theme.bodyColor
font.pixelSize: Theme.fontXs
font.letterSpacing: 1.8
font.pixelSize: root.csvPath !== "" ? Theme.fontMd : Theme.fontSm
font.letterSpacing: root.csvPath !== "" ? 0 : 1.8
font.weight: Font.Medium
font.family: Theme.uiFontFamily
elide: Text.ElideMiddle
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
Button {
text: "REFRESH SESSION"
font.pixelSize: Theme.fontXs
id: refreshSessionBtn
text: "Refresh"
font.pixelSize: Theme.fontSm
font.family: Theme.uiFontFamily
implicitHeight: 24
hoverEnabled: true
background: Rectangle {
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
color: refreshSessionBtn.hovered ? Theme.buttonNeutralHover : "transparent"
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
text: refreshSessionBtn.text
color: Theme.bodyColor
font: parent.font
font: refreshSessionBtn.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
@@ -358,18 +519,20 @@ ColumnLayout {
}
Button {
text: "CLEAR LOG"
font.pixelSize: Theme.fontXs
id: clearLogBtn
text: "Clear"
font.pixelSize: Theme.fontSm
font.family: Theme.uiFontFamily
implicitHeight: 24
hoverEnabled: true
background: Rectangle {
color: parent.hovered ? Theme.buttonNeutralHover : "transparent"
color: clearLogBtn.hovered ? Theme.buttonNeutralHover : "transparent"
radius: Theme.radiusSm
}
contentItem: Text {
text: parent.text
text: clearLogBtn.text
color: Theme.bodyColor
font: parent.font
font: clearLogBtn.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
@@ -409,56 +572,36 @@ ColumnLayout {
Connections {
target: deviceController
function onActivityLogUpdated(newLog) {
activityLog.text = newLog;
}
activityLog.text = newLog ? newLog : qsTr("Welcome — use the side rail to detect a wafer, or import data to review.");
}
}
}
// --- Signal Handlers ---
// ═══════════════════════════════════════════════════════════════════════
// SIGNAL HANDLERS
// ═══════════════════════════════════════════════════════════════════════
Connections {
target: deviceController
function onLogMessage(message) {
if (message == "CHOOSE_SAVE_DIR") {
saveDirDialog.open();
}
}
// Any operation start (detect/read/erase/debug) latches the panel on.
function onPortsUpdated() {
// Status tab always visible now
}
function onPortsUpdated() {}
function onDetectResult(result) {
if (result && result.familyCode) {
root.waferDetected = true;
waferInfoFamily.text = result.familyCode;
waferSerial.text = result.serialNumber;
waferSensors.text = String(result.sensorCount);
waferRuntime.text = result.runtime + "s";
waferCycles.text = String(result.cycleCount);
if (!result) {
root.dataParsed = false;
root.dataRows = 0;
root.dataCols = 0;
root.csvPath = "";
}
// On failure, keep the last displayed wafer info and do not change waferDetected.
}
function onStatusRestored() {
// Show restored wafer info if available
var info = deviceController.lastWaferInfo;
if (info && info.length > 0) {
root.waferDetected = true;
waferInfoFamily.text = info[0] || "—";
waferSerial.text = info[1] || "—";
waferSensors.text = String(info[2] || 0);
waferRuntime.text = (info[3] || 0) + "s";
waferCycles.text = String(info[4] || 0);
}
// Show data summary if data was previously parsed
if (deviceController.dataRowCount > 0) {
root.dataParsed = true;
root.dataRows = deviceController.dataRowCount;
root.dataCols = deviceController.dataColCount;
root.csvPath = ""; // CSV path not persisted as full path, just show it was parsed
root.csvPath = "";
}
}
}
@@ -470,7 +613,6 @@ ColumnLayout {
root.dataRows = 0;
root.dataCols = 0;
root.csvPath = "";
if (result && result.success === true)
saveDirDialog.open();
}