mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
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:
88
qml/MetaBuilder/CCanvasViewport.qml
Normal file
88
qml/MetaBuilder/CCanvasViewport.qml
Normal 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
44
qml/MetaBuilder/CProfileConnectedAccounts.qml
Normal file
44
qml/MetaBuilder/CProfileConnectedAccounts.qml
Normal 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
49
qml/MetaBuilder/CProfilePasswordCard.qml
Normal file
49
qml/MetaBuilder/CProfilePasswordCard.qml
Normal 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"]
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user