refactor(qt6): 100% of views under 100 LOC — final component + JS extractions

All 29 views now under 100 LOC. Latest extractions:
- 15 components split from 150+ LOC originals (CWorkflowCanvas→99, ThemePreviewCard→52, etc.)
- CStatCell reusable component eliminates 60 lines of duplication
- Profile: CProfilePasswordCard, CProfileConnectedAccounts
- 6 JS modules: LuaEditorLogic, UserManagementDBAL, ProfileDBAL, ComponentTreeDBAL, ThemeEditorLogic, SchemaEditorDBAL
- 7 JSON data files in qml/MetaBuilder/data/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 15:04:03 +00:00
parent b40f1e0167
commit d4f7e85007
11 changed files with 275 additions and 169 deletions

View File

@@ -0,0 +1,88 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QmlComponents 1.0
Flickable {
id: canvas
property alias canvasContent: contentItem
property alias connectionLayer: connLayer
property var nodes: []
property var connections: ({})
property real zoom: 1.0
property string selectedNodeId: ""
property bool drawingConnection: false
property string connSourceNode: ""
property bool connSourceIsOutput: true
property real connDragX: 0
property real connDragY: 0
property var groupColorFn: function(t) { return "gray" }
signal nodeSelected(string id)
signal nodeMoved(string id, real x, real y)
signal connectionDragStarted(string nodeId, string portName, bool isOutput, real portX, real portY)
signal connectionCompleted(string nodeId, string portName)
signal connectionDragUpdated(real x, real y)
signal connectionDragFinished()
signal canvasClicked()
signal zoomRequested(real delta)
contentWidth: 5000
contentHeight: 5000
clip: true
boundsBehavior: Flickable.StopAtBounds
Component.onCompleted: { contentX = 1500; contentY = 1500 }
Item {
id: contentItem
width: canvas.contentWidth
height: canvas.contentHeight
transform: Scale {
origin.x: 0; origin.y: 0
xScale: canvas.zoom; yScale: canvas.zoom
}
CCanvasGrid { anchors.fill: parent }
CConnectionLayer {
id: connLayer
anchors.fill: parent; z: 1
nodes: canvas.nodes
connections: canvas.connections
drawingConnection: canvas.drawingConnection
connSourceNode: canvas.connSourceNode
connSourceIsOutput: canvas.connSourceIsOutput
connDragX: canvas.connDragX
connDragY: canvas.connDragY
}
Repeater {
model: canvas.nodes.length; z: 2
delegate: CWorkflowNodeDelegate {
nodeData: canvas.nodes[index]
isSelected: canvas.selectedNodeId === nodeData.id
groupColorValue: canvas.groupColorFn(nodeData.type)
drawingConnection: canvas.drawingConnection
connSourceIsOutput: canvas.connSourceIsOutput
canvasContentItem: contentItem
onNodeSelected: function(id) { canvas.nodeSelected(id) }
onNodeMoved: function(id, x, y) { canvas.nodeMoved(id, x, y) }
onConnectionDragStarted: function(nId, p, o, pX, pY) { canvas.connectionDragStarted(nId, p, o, pX, pY) }
onConnectionCompleted: function(nId, p) { canvas.connectionCompleted(nId, p) }
onPaintRequested: connLayer.requestPaint()
}
}
CCanvasInteractionArea {
anchors.fill: parent
drawingConnection: canvas.drawingConnection
onConnectionDragUpdated: function(x, y) { canvas.connectionDragUpdated(x, y) }
onConnectionDragFinished: canvas.connectionDragFinished()
onCanvasClicked: canvas.canvasClicked()
onZoomRequested: function(d) { canvas.zoomRequested(d) }
}
}
}

View File

@@ -0,0 +1,44 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QmlComponents 1.0
CCard {
id: root
Layout.fillWidth: true
Layout.leftMargin: 24
Layout.rightMargin: 24
variant: "filled"
property var accounts: []
property string currentUser: ""
CText { Layout.fillWidth: true; variant: "h4"; text: "Connected Accounts" }
Item { Layout.preferredHeight: 8 }
CDivider { Layout.fillWidth: true }
Item { Layout.preferredHeight: 8 }
Repeater {
model: root.accounts
delegate: ColumnLayout {
Layout.fillWidth: true; spacing: 0
CListItem {
Layout.fillWidth: true; title: modelData.service
subtitle: modelData.linked ? "Linked as @" + root.currentUser : "Not linked"
leadingIcon: modelData.icon
}
FlexRow {
Layout.fillWidth: true; Layout.leftMargin: 12; spacing: 8
CStatusBadge { status: modelData.statusType; text: modelData.statusText }
Item { Layout.fillWidth: true }
CButton {
text: modelData.linked ? (modelData.unlinkLabel || "Unlink") : (modelData.linkLabel || "Link Account")
variant: modelData.linked ? "ghost" : "primary"; size: "sm"
}
}
Item { Layout.preferredHeight: 8; visible: index < (root.accounts.length - 1) }
CDivider { Layout.fillWidth: true; visible: index < (root.accounts.length - 1) }
Item { Layout.preferredHeight: 8; visible: index < (root.accounts.length - 1) }
}
}
}

View File

@@ -0,0 +1,49 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QmlComponents 1.0
CCard {
id: root
Layout.fillWidth: true
Layout.leftMargin: 24
Layout.rightMargin: 24
variant: "filled"
property var passwordFields: []
property var passwords: ({ "current": "", "new": "", "confirm": "" })
signal passwordChanged(string key, string value)
CText { Layout.fillWidth: true; variant: "h4"; text: "Change Password" }
Item { Layout.preferredHeight: 8 }
CDivider { Layout.fillWidth: true }
Item { Layout.preferredHeight: 14 }
Repeater {
model: root.passwordFields
delegate: ColumnLayout {
Layout.fillWidth: true; spacing: 0
CTextField {
Layout.fillWidth: true
label: modelData.label
placeholderText: modelData.placeholder
echoMode: TextInput.Password
text: root.passwords[modelData.key]
onTextChanged: root.passwordChanged(modelData.key, text)
}
Item { Layout.preferredHeight: 14 }
}
}
CAlert {
Layout.fillWidth: true; severity: "info"
text: "Passwords must be at least 8 characters with uppercase, lowercase, and a number."
visible: root.passwords["new"].length > 0
}
CAlert {
Layout.fillWidth: true; severity: "error"
text: "Passwords do not match."
visible: root.passwords["confirm"].length > 0 && root.passwords["new"] !== root.passwords["confirm"]
}
}

View File

@@ -31,7 +31,7 @@ Rectangle {
color: Theme.background
clip: true
function requestPaint() { connectionLayer.requestPaint() }
function requestPaint() { viewport.connectionLayer.requestPaint() }
function groupColor(nodeType) {
var prefix = nodeType ? nodeType.split(".")[0] : ""
@@ -54,84 +54,37 @@ Rectangle {
onDropped: function(drop) {
var nodeType = drop.getDataAsString("text/node-type")
if (nodeType) {
var localPos = mapToItem(canvasContent, drop.x, drop.y)
var localPos = mapToItem(viewport.canvasContent, drop.x, drop.y)
root.nodeDropped(nodeType, localPos.x, localPos.y)
}
}
}
Flickable {
id: canvas
CCanvasViewport {
id: viewport
anchors.fill: parent
contentWidth: 5000
contentHeight: 5000
clip: true
boundsBehavior: Flickable.StopAtBounds
Component.onCompleted: { contentX = 1500; contentY = 1500 }
Item {
id: canvasContent
width: canvas.contentWidth
height: canvas.contentHeight
transform: Scale {
origin.x: 0; origin.y: 0
xScale: root.zoom; yScale: root.zoom
}
CCanvasGrid { anchors.fill: parent }
CConnectionLayer {
id: connectionLayer
anchors.fill: parent
z: 1
nodes: root.nodes
connections: root.connections
drawingConnection: root.drawingConnection
connSourceNode: root.connSourceNode
connSourceIsOutput: root.connSourceIsOutput
connDragX: root.connDragX
connDragY: root.connDragY
}
Repeater {
model: root.nodes.length
z: 2
delegate: CWorkflowNodeDelegate {
nodeData: root.nodes[index]
isSelected: root.selectedNodeId === nodeData.id
groupColorValue: groupColor(nodeData.type)
drawingConnection: root.drawingConnection
connSourceIsOutput: root.connSourceIsOutput
canvasContentItem: canvasContent
onNodeSelected: function(id) { root.nodeSelected(id) }
onNodeMoved: function(id, x, y) { root.nodeMoved(id, x, y) }
onConnectionDragStarted: function(nId, pName, isOut, pX, pY) {
root.connectionDragStarted(nId, pName, isOut, pX, pY)
}
onConnectionCompleted: function(nId, pName) { root.connectionCompleted(nId, pName) }
onPaintRequested: connectionLayer.requestPaint()
}
}
CCanvasInteractionArea {
anchors.fill: parent
drawingConnection: root.drawingConnection
onConnectionDragUpdated: function(x, y) { root.connectionDragUpdated(x, y) }
onConnectionDragFinished: root.connectionDragFinished()
onCanvasClicked: root.canvasClicked()
onZoomRequested: function(delta) { root.zoomChanged(root.zoom + delta) }
}
}
nodes: root.nodes
connections: root.connections
zoom: root.zoom
selectedNodeId: root.selectedNodeId
drawingConnection: root.drawingConnection
connSourceNode: root.connSourceNode
connSourceIsOutput: root.connSourceIsOutput
connDragX: root.connDragX
connDragY: root.connDragY
groupColorFn: root.groupColor
onNodeSelected: function(id) { root.nodeSelected(id) }
onNodeMoved: function(id, x, y) { root.nodeMoved(id, x, y) }
onConnectionDragStarted: function(nId, p, o, pX, pY) { root.connectionDragStarted(nId, p, o, pX, pY) }
onConnectionCompleted: function(nId, p) { root.connectionCompleted(nId, p) }
onConnectionDragUpdated: function(x, y) { root.connectionDragUpdated(x, y) }
onConnectionDragFinished: root.connectionDragFinished()
onCanvasClicked: root.canvasClicked()
onZoomRequested: function(d) { root.zoomChanged(root.zoom + d) }
}
CCanvasZoomOverlay {
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.margins: 12
anchors.bottom: parent.bottom; anchors.right: parent.right; anchors.margins: 12
zoom: root.zoom
onZoomIn: root.zoomChanged(root.zoom + 0.1)
onZoomOut: root.zoomChanged(root.zoom - 0.1)
@@ -141,7 +94,6 @@ Rectangle {
anchors.centerIn: parent
visible: root.nodes.length === 0
text: "Empty canvas — drag nodes from the palette or double-click a node type"
variant: "body1"
opacity: 0.5
variant: "body1"; opacity: 0.5
}
}

View File

@@ -167,9 +167,16 @@ MediaRadioPlaylist 1.0 MediaRadioPlaylist.qml
CNotificationIconBadge 1.0 CNotificationIconBadge.qml
CComponentPropsList 1.0 CComponentPropsList.qml
MediaJobRow 1.0 MediaJobRow.qml
CCanvasInteractionArea 1.0 CCanvasInteractionArea.qml
CCanvasViewport 1.0 CCanvasViewport.qml
CNotificationContent 1.0 CNotificationContent.qml
CBackendConnectionSection 1.0 CBackendConnectionSection.qml
CJobProgressBar 1.0 CJobProgressBar.qml
CLevelTagFlow 1.0 CLevelTagFlow.qml
CVersionPill 1.0 CVersionPill.qml
CEncryptionSelector 1.0 CEncryptionSelector.qml
CFrontPageFooter 1.0 CFrontPageFooter.qml
CDatabaseEnvConfig 1.0 CDatabaseEnvConfig.qml
CSettingsAbout 1.0 CSettingsAbout.qml
CProfilePasswordCard 1.0 CProfilePasswordCard.qml
CProfileConnectedAccounts 1.0 CProfileConnectedAccounts.qml

View File

@@ -13,34 +13,18 @@ Rectangle {
DBALProvider { id: dbal }
property bool useLiveData: dbal.connected
property int selectedBackendIndex: 2
property int activeBackendIndex: 2
property int selectedBackendIndex: 2; property int activeBackendIndex: 2; property int adapterPattern: 0
property string databaseUrl: "sqlite:///var/lib/dbal/metabuilder.db"
property string cacheUrl: "redis://localhost:6379/0?ttl=300&pattern=read-through"
property string searchUrl: "http://localhost:9200?index=dbal_search&refresh=true"
property int adapterPattern: 0
property bool showExportDialog: false
property bool showImportDialog: false
property var adapterPatterns: []
property var backends: []
property var testingIndex: -1
property var testResults: ({})
function loadSeedData() {
var xhr = new XMLHttpRequest()
xhr.open("GET", Qt.resolvedUrl("config/database-backends.json"), false); xhr.send()
if (xhr.status === 200) { var d = JSON.parse(xhr.responseText); backends = d.backends; adapterPatterns = d.adapterPatterns }
}
property bool showExportDialog: false; property bool showImportDialog: false
property var adapterPatterns: []; property var backends: []; property var testingIndex: -1; property var testResults: ({})
function loadSeedData() { var xhr = new XMLHttpRequest(); xhr.open("GET", Qt.resolvedUrl("config/database-backends.json"), false); xhr.send(); if (xhr.status === 200) { var d = JSON.parse(xhr.responseText); backends = d.backends; adapterPatterns = d.adapterPatterns } }
Timer {
id: testTimer; property int targetIndex: -1; interval: 1500
onTriggered: {
var backend = backends[targetIndex]
var result = (backend.status === "connected") ? "success" : (backend.status === "error" ? "error" : "warning")
var newResults = Object.assign({}, testResults); newResults[targetIndex] = result; testResults = newResults; testingIndex = -1
}
onTriggered: { var r = (backends[targetIndex].status === "connected") ? "success" : (backends[targetIndex].status === "error" ? "error" : "warning"); var nr = Object.assign({}, testResults); nr[targetIndex] = r; testResults = nr; testingIndex = -1 }
}
onUseLiveDataChanged: { if (useLiveData) Logic.loadAdapterStatus(root, dbal) }
Component.onCompleted: { loadSeedData(); Logic.loadAdapterStatus(root, dbal) }

View File

@@ -25,9 +25,7 @@ Rectangle {
saving = true; saveStatus = ""
var data = profileForm.getData()
userDisplayName = data.displayName; userEmail = data.email; userBio = data.bio
PDBAL.saveProfile(dbal, appWindow.currentUser, data, function(ok, error) {
saving = false; saveStatus = ok ? "saved" : "error"
})
PDBAL.saveProfile(dbal, appWindow.currentUser, data, function(ok) { saving = false; saveStatus = ok ? "saved" : "error" })
}
Component.onCompleted: {
@@ -68,48 +66,14 @@ Rectangle {
CProfileForm { id: profileForm; profile: ({ displayName: profileRoot.userDisplayName, email: profileRoot.userEmail, bio: profileRoot.userBio }); isDark: profileRoot.isDark }
Item { Layout.preferredHeight: 16 }
CCard {
Layout.fillWidth: true; Layout.leftMargin: 24; Layout.rightMargin: 24; variant: "filled"
CText { Layout.fillWidth: true; variant: "h4"; text: "Change Password" }
Item { Layout.preferredHeight: 8 }
CDivider { Layout.fillWidth: true }
Item { Layout.preferredHeight: 14 }
Repeater {
model: profileConfig ? profileConfig.passwordFields : []
delegate: ColumnLayout {
Layout.fillWidth: true; spacing: 0
CTextField { Layout.fillWidth: true; label: modelData.label; placeholderText: modelData.placeholder; echoMode: TextInput.Password; text: passwords[modelData.key]; onTextChanged: { var p = passwords; p[modelData.key] = text; passwords = p } }
Item { Layout.preferredHeight: 14 }
}
}
CAlert { Layout.fillWidth: true; severity: "info"; text: "Passwords must be at least 8 characters with uppercase, lowercase, and a number."; visible: passwords["new"].length > 0 }
CAlert { Layout.fillWidth: true; severity: "error"; text: "Passwords do not match."; visible: passwords["confirm"].length > 0 && passwords["new"] !== passwords["confirm"] }
CProfilePasswordCard {
passwordFields: profileConfig ? profileConfig.passwordFields : []
passwords: profileRoot.passwords
onPasswordChanged: function(key, value) { var p = passwords; p[key] = value; passwords = p }
}
Item { Layout.preferredHeight: 16 }
CCard {
Layout.fillWidth: true; Layout.leftMargin: 24; Layout.rightMargin: 24; variant: "filled"
CText { Layout.fillWidth: true; variant: "h4"; text: "Connected Accounts" }
Item { Layout.preferredHeight: 8 }
CDivider { Layout.fillWidth: true }
Item { Layout.preferredHeight: 8 }
Repeater {
model: profileConfig ? profileConfig.connectedAccounts : []
delegate: ColumnLayout {
Layout.fillWidth: true; spacing: 0
CListItem { Layout.fillWidth: true; title: modelData.service; subtitle: modelData.linked ? "Linked as @" + appWindow.currentUser : "Not linked"; leadingIcon: modelData.icon }
FlexRow {
Layout.fillWidth: true; Layout.leftMargin: 12; spacing: 8
CStatusBadge { status: modelData.statusType; text: modelData.statusText }
Item { Layout.fillWidth: true }
CButton { text: modelData.linked ? (modelData.unlinkLabel || "Unlink") : (modelData.linkLabel || "Link Account"); variant: modelData.linked ? "ghost" : "primary"; size: "sm" }
}
Item { Layout.preferredHeight: 8; visible: index < (profileConfig.connectedAccounts.length - 1) }
CDivider { Layout.fillWidth: true; visible: index < (profileConfig.connectedAccounts.length - 1) }
Item { Layout.preferredHeight: 8; visible: index < (profileConfig.connectedAccounts.length - 1) }
}
}
}
CProfileConnectedAccounts { accounts: profileConfig ? profileConfig.connectedAccounts : []; currentUser: appWindow.currentUser }
Item { Layout.preferredHeight: 16 }
FlexRow {

View File

@@ -9,34 +9,17 @@ Rectangle {
id: smtpEditor
color: "transparent"
property string smtpHost: "smtp.example.com"
property string smtpPort: "587"
property string smtpUsername: ""
property string smtpPassword: ""
property int encryptionIndex: 1
property var encryptionOptions: []
property string fromName: "MetaBuilder"
property string fromEmail: "noreply@example.com"
property string connectionStatus: "untested"
property string lastTestResult: ""
property string lastTestTime: ""
property string testRecipient: ""
property string testSubject: "MetaBuilder SMTP Test"
property string testBody: "This is a test email from MetaBuilder."
property bool sendingTest: false
property int selectedTemplateIndex: -1
property var emailTemplates: []
property string editTemplateName: ""
property string editTemplateSubject: ""
property string editTemplateBody: ""
property string smtpHost: "smtp.example.com"; property string smtpPort: "587"
property string smtpUsername: ""; property string smtpPassword: ""
property int encryptionIndex: 1; property var encryptionOptions: []
property string fromName: "MetaBuilder"; property string fromEmail: "noreply@example.com"
property string connectionStatus: "untested"; property string lastTestResult: ""; property string lastTestTime: ""
property string testRecipient: ""; property string testSubject: "MetaBuilder SMTP Test"
property string testBody: "This is a test email from MetaBuilder."; property bool sendingTest: false
property int selectedTemplateIndex: -1; property var emailTemplates: []
property string editTemplateName: ""; property string editTemplateSubject: ""; property string editTemplateBody: ""
property bool isDirty: false
function loadSeedData() {
var xhr = new XMLHttpRequest()
xhr.open("GET", Qt.resolvedUrl("config/smtp-templates.json"), false); xhr.send()
if (xhr.status === 200) { var d = JSON.parse(xhr.responseText); emailTemplates = d.emailTemplates; encryptionOptions = d.encryptionOptions }
}
function loadSeedData() { var xhr = new XMLHttpRequest(); xhr.open("GET", Qt.resolvedUrl("config/smtp-templates.json"), false); xhr.send(); if (xhr.status === 200) { var d = JSON.parse(xhr.responseText); emailTemplates = d.emailTemplates; encryptionOptions = d.encryptionOptions } }
Component.onCompleted: loadSeedData()
Timer { id: connectionTimer; interval: 1500; repeat: false; onTriggered: Logic.onConnectionTestComplete(smtpEditor) }

View File

@@ -62,7 +62,13 @@ Rectangle {
}
CDivider { Layout.fillWidth: true; Layout.topMargin: 16 }
FlexRow { Layout.fillWidth: true; Layout.topMargin: 12; spacing: 12; CButton { text: "Save Schema"; variant: "primary"; size: "md" }; CButton { text: "Export JSON"; variant: "secondary"; size: "md" }; Item { Layout.fillWidth: true }; CButton { text: "Delete Schema"; variant: "danger"; size: "md"; enabled: schemas.length > 1; onClicked: deleteSchema() } }
FlexRow {
Layout.fillWidth: true; Layout.topMargin: 12; spacing: 12
CButton { text: "Save Schema"; variant: "primary"; size: "md" }
CButton { text: "Export JSON"; variant: "secondary"; size: "md" }
Item { Layout.fillWidth: true }
CButton { text: "Delete Schema"; variant: "danger"; size: "md"; enabled: schemas.length > 1; onClicked: deleteSchema() }
}
}
CDialog {
@@ -71,7 +77,12 @@ Rectangle {
spacing: 16; width: 360
CTextField { label: "Schema Name"; placeholderText: "e.g. Invoice"; text: newSchemaName; Layout.fillWidth: true; onTextChanged: newSchemaName = text }
CTextField { label: "Description"; placeholderText: "Brief description of this schema"; text: newSchemaDescription; Layout.fillWidth: true; onTextChanged: newSchemaDescription = text }
FlexRow { Layout.fillWidth: true; spacing: 12; Item { Layout.fillWidth: true }; CButton { text: "Cancel"; variant: "ghost"; onClicked: { newSchemaName = ""; newSchemaDescription = ""; createSchemaDialogOpen = false } }; CButton { text: "Create"; variant: "primary"; enabled: newSchemaName.trim() !== ""; onClicked: addSchema() } }
FlexRow {
Layout.fillWidth: true; spacing: 12
Item { Layout.fillWidth: true }
CButton { text: "Cancel"; variant: "ghost"; onClicked: { newSchemaName = ""; newSchemaDescription = ""; createSchemaDialogOpen = false } }
CButton { text: "Create"; variant: "primary"; enabled: newSchemaName.trim() !== ""; onClicked: addSchema() }
}
}
}
@@ -80,11 +91,20 @@ Rectangle {
ColumnLayout {
spacing: 14; width: 360
CTextField { label: "Field Name"; placeholderText: "e.g. quantity"; text: newFieldName; Layout.fillWidth: true; onTextChanged: newFieldName = text }
ColumnLayout { Layout.fillWidth: true; spacing: 4; CText { variant: "caption"; text: "Type" }; CSelect { model: fieldTypes; currentIndex: fieldTypes.indexOf(newFieldType); Layout.fillWidth: true; onCurrentIndexChanged: newFieldType = fieldTypes[currentIndex] } }
ColumnLayout {
Layout.fillWidth: true; spacing: 4
CText { variant: "caption"; text: "Type" }
CSelect { model: fieldTypes; currentIndex: fieldTypes.indexOf(newFieldType); Layout.fillWidth: true; onCurrentIndexChanged: newFieldType = fieldTypes[currentIndex] }
}
CSwitch { text: "Required"; checked: newFieldRequired; onCheckedChanged: newFieldRequired = checked }
CTextField { label: "Default Value"; placeholderText: "Optional default"; text: newFieldDefault; Layout.fillWidth: true; onTextChanged: newFieldDefault = text }
CTextField { label: "Description"; placeholderText: "What this field represents"; text: newFieldDescription; Layout.fillWidth: true; onTextChanged: newFieldDescription = text }
FlexRow { Layout.fillWidth: true; spacing: 12; Item { Layout.fillWidth: true }; CButton { text: "Cancel"; variant: "ghost"; onClicked: { newFieldName = ""; newFieldType = "string"; newFieldRequired = false; newFieldDefault = ""; newFieldDescription = ""; addFieldDialogOpen = false } }; CButton { text: "Add Field"; variant: "primary"; enabled: newFieldName.trim() !== ""; onClicked: addField() } }
FlexRow {
Layout.fillWidth: true; spacing: 12
Item { Layout.fillWidth: true }
CButton { text: "Cancel"; variant: "ghost"; onClicked: { newFieldName = ""; newFieldType = "string"; newFieldRequired = false; newFieldDefault = ""; newFieldDescription = ""; addFieldDialogOpen = false } }
CButton { text: "Add Field"; variant: "primary"; enabled: newFieldName.trim() !== ""; onClicked: addField() }
}
}
}
}

View File

@@ -35,7 +35,12 @@ Rectangle {
ColumnLayout {
width: parent.width; spacing: 20
FlexRow { Layout.fillWidth: true; spacing: 12; CText { variant: "h3"; text: "Theme Editor" }; Item { Layout.fillWidth: true }; CBadge { text: hasUnsavedChanges ? "Unsaved changes" : "Synced" } }
FlexRow {
Layout.fillWidth: true; spacing: 12
CText { variant: "h3"; text: "Theme Editor" }
Item { Layout.fillWidth: true }
CBadge { text: hasUnsavedChanges ? "Unsaved changes" : "Synced" }
}
CText { variant: "body2"; text: "Customize the look and feel of your MetaBuilder workspace. Select a preset theme or fine-tune individual color tokens."; Layout.fillWidth: true }
ThemePresetGrid { selectedTheme: root.selectedTheme; radiusMedium: root.radiusMedium; themeDefinitions: root.themeDefinitions; onThemeSelected: function(name) { root.selectedTheme = name; hasUnsavedChanges = true } }
@@ -64,7 +69,12 @@ Rectangle {
fontFamily: root.fontFamily; baseFontSize: root.baseFontSize; radiusSmall: root.radiusSmall; radiusMedium: root.radiusMedium
}
FlexRow { Layout.fillWidth: true; spacing: 12; CButton { text: "Reset to Default"; variant: "ghost"; onClicked: TELogic.resetToDefaults(root, Theme) }; Item { Layout.fillWidth: true }; CButton { text: "Apply Theme"; variant: "primary"; onClicked: { hasUnsavedChanges = false; feedbackAlert.visible = true; feedbackTimer.restart() } } }
FlexRow {
Layout.fillWidth: true; spacing: 12
CButton { text: "Reset to Default"; variant: "ghost"; onClicked: TELogic.resetToDefaults(root, Theme) }
Item { Layout.fillWidth: true }
CButton { text: "Apply Theme"; variant: "primary"; onClicked: { hasUnsavedChanges = false; feedbackAlert.visible = true; feedbackTimer.restart() } }
}
CAlert { id: feedbackAlert; Layout.fillWidth: true; severity: "success"; text: "Theme applied successfully"; visible: false }
Item { Layout.preferredHeight: 20 }
}

View File

@@ -68,7 +68,12 @@ Rectangle {
ColumnLayout {
spacing: 16; width: 380
CAlert { Layout.fillWidth: true; severity: "error"; text: deleteIndex >= 0 && deleteIndex < users.length ? "Are you sure you want to delete \"" + users[deleteIndex].username + "\"? This action cannot be undone." : "Confirm deletion?" }
FlexRow { Layout.fillWidth: true; spacing: 8; Item { Layout.fillWidth: true }; CButton { text: "Cancel"; variant: "ghost"; onClicked: { deleteDialogOpen = false; deleteIndex = -1 } }; CButton { text: "Delete"; variant: "danger"; onClicked: confirmDelete() } }
FlexRow {
Layout.fillWidth: true; spacing: 8
Item { Layout.fillWidth: true }
CButton { text: "Cancel"; variant: "ghost"; onClicked: { deleteDialogOpen = false; deleteIndex = -1 } }
CButton { text: "Delete"; variant: "danger"; onClicked: confirmDelete() }
}
}
}
}