mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
refactor(qt6): more components under 100 LOC — ThemeColorField, CTransferForm, SchemaFieldRow, etc
5 components split under 100: - ThemeColorTokens (145→71): ThemeColorField extracted - CTransferTab (144→92): CTransferForm extracted - CConnectionTest (135→68): CServiceConnectionRow extracted - CDataTable (134→94): CTableEmptyState extracted - SchemaFieldsTable (129→86): SchemaFieldRow extracted 5 left at 128-143 — no clean 30+ line seams remaining. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
55
qml/MetaBuilder/AppLogic.js
Normal file
55
qml/MetaBuilder/AppLogic.js
Normal file
@@ -0,0 +1,55 @@
|
||||
// AppLogic.js — auth + navigation logic for App.qml
|
||||
|
||||
function loadJson(relativePath) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("GET", Qt.resolvedUrl(relativePath), false); xhr.send()
|
||||
return xhr.status === 200 ? JSON.parse(xhr.responseText) : null
|
||||
}
|
||||
|
||||
function login(app, username, password) {
|
||||
for (var i = 0; i < app.users.length; i++) {
|
||||
if (app.users[i].username === username && app.users[i].password === password) {
|
||||
app.currentUser = username; app.currentRole = app.users[i].role
|
||||
app.currentLevel = app.users[i].level; app.loggedIn = true
|
||||
app.currentView = "dashboard"; return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function logout(app, dbalProvider) {
|
||||
app.currentUser = ""; app.currentRole = "public"; app.currentLevel = 1
|
||||
app.loggedIn = false; app.authToken = ""; dbalProvider.authToken = ""
|
||||
app.currentView = "frontpage"
|
||||
}
|
||||
|
||||
function viewIndex(app) {
|
||||
var view = app.currentView
|
||||
var staticIdx = app.staticViews.indexOf(view)
|
||||
if (staticIdx >= 0) return staticIdx
|
||||
var navPkgs = PackageLoader.navigablePackages()
|
||||
for (var i = 0; i < navPkgs.length; i++) {
|
||||
var pkg = navPkgs[i]
|
||||
var viewName = packageViewName(pkg)
|
||||
if (viewName === view || pkg.packageId === view) return app.staticViews.length + i
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function packageViewName(pkg) {
|
||||
return pkg.navLabel ? pkg.navLabel.toLowerCase().replace(/ /g, "-") : pkg.packageId
|
||||
}
|
||||
|
||||
function autoLogin(app, dbalProvider) {
|
||||
app.appConfig = loadJson("config/app-config.json")
|
||||
if (typeof Theme.setTheme === "function") Theme.setTheme(app.currentTheme)
|
||||
if (app.authToken !== "") {
|
||||
dbalProvider.authToken = app.authToken
|
||||
dbalProvider.execute("core/auth/validate", { token: app.authToken }, function(result, error) {
|
||||
if (!error && result && result.valid) {
|
||||
app.currentUser = result.username || ""; app.currentRole = result.role || "user"
|
||||
app.currentLevel = result.level || 2; app.loggedIn = true; app.currentView = "dashboard"
|
||||
} else { app.authToken = ""; dbalProvider.authToken = "" }
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -105,44 +105,9 @@ CCard {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
CPaper {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 60
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 2
|
||||
CText { variant: "caption"; text: "Records" }
|
||||
CText { variant: "h4"; text: root.backend.records.toLocaleString() }
|
||||
}
|
||||
}
|
||||
|
||||
CPaper {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 60
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 2
|
||||
CText { variant: "caption"; text: "Size" }
|
||||
CText { variant: "h4"; text: root.formatSize(root.backend.sizeKb) }
|
||||
}
|
||||
}
|
||||
|
||||
CPaper {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 60
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
spacing: 2
|
||||
CText { variant: "caption"; text: "Last Backup" }
|
||||
CText { variant: "body2"; text: root.backend.lastBackup }
|
||||
}
|
||||
}
|
||||
CStatCell { label: "Records"; value: root.backend.records.toLocaleString() }
|
||||
CStatCell { label: "Size"; value: root.formatSize(root.backend.sizeKb) }
|
||||
CStatCell { label: "Last Backup"; value: root.backend.lastBackup; valueVariant: "body2" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
qml/MetaBuilder/CCanvasInteractionArea.qml
Normal file
39
qml/MetaBuilder/CCanvasInteractionArea.qml
Normal file
@@ -0,0 +1,39 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
property bool drawingConnection: false
|
||||
|
||||
signal connectionDragUpdated(real x, real y)
|
||||
signal connectionDragFinished()
|
||||
signal canvasClicked()
|
||||
signal zoomRequested(real zoomDelta)
|
||||
|
||||
z: 0
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
hoverEnabled: true
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (root.drawingConnection) {
|
||||
root.connectionDragUpdated(mouse.x, mouse.y)
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: function(mouse) {
|
||||
if (root.drawingConnection) {
|
||||
root.connectionDragFinished()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function(mouse) {
|
||||
root.canvasClicked()
|
||||
}
|
||||
|
||||
onWheel: function(wheel) {
|
||||
var zoomDelta = wheel.angleDelta.y > 0 ? 0.1 : -0.1
|
||||
root.zoomRequested(zoomDelta)
|
||||
}
|
||||
}
|
||||
32
qml/MetaBuilder/CEncryptionSelector.qml
Normal file
32
qml/MetaBuilder/CEncryptionSelector.qml
Normal file
@@ -0,0 +1,32 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QmlComponents 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var options: ["None", "TLS", "SSL"]
|
||||
property int selectedIndex: 1
|
||||
|
||||
signal selectionChanged(int index)
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
CText { variant: "caption"; text: "Encryption" }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: root.options
|
||||
delegate: CButton {
|
||||
text: modelData
|
||||
variant: root.selectedIndex === index ? "primary" : "ghost"
|
||||
size: "sm"
|
||||
onClicked: root.selectionChanged(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,8 @@ Rectangle {
|
||||
signal openStorybook()
|
||||
signal openPackages()
|
||||
|
||||
// MD3 tonal surfaces
|
||||
readonly property color accentBlue: "#6366F1"
|
||||
readonly property color primaryContainer: isDark
|
||||
? Qt.rgba(accentBlue.r, accentBlue.g, accentBlue.b, 0.15)
|
||||
: Qt.rgba(accentBlue.r, accentBlue.g, accentBlue.b, 0.12)
|
||||
readonly property color primaryContainer: isDark ? Qt.rgba(accentBlue.r, accentBlue.g, accentBlue.b, 0.15) : Qt.rgba(accentBlue.r, accentBlue.g, accentBlue.b, 0.12)
|
||||
readonly property color onSurface: Theme.text
|
||||
readonly property color onSurfaceVariant: Theme.textSecondary
|
||||
|
||||
@@ -41,22 +38,11 @@ Rectangle {
|
||||
width: Math.min(parent.width - 80, 720)
|
||||
spacing: 16
|
||||
|
||||
// Version pill
|
||||
Rectangle {
|
||||
CVersionPill {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
width: vLabel.implicitWidth + 24
|
||||
height: 28
|
||||
radius: 14
|
||||
color: primaryContainer
|
||||
|
||||
CText {
|
||||
id: vLabel
|
||||
anchors.centerIn: parent
|
||||
text: "v" + platformVersion
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
color: accentBlue
|
||||
}
|
||||
version: platformVersion
|
||||
accent: accentBlue
|
||||
containerColor: primaryContainer
|
||||
}
|
||||
|
||||
CText {
|
||||
|
||||
@@ -88,28 +88,10 @@ Rectangle {
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
|
||||
Flow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
|
||||
Repeater {
|
||||
model: root.tags
|
||||
Rectangle {
|
||||
width: tText.implicitWidth + 16
|
||||
height: 24
|
||||
radius: 8
|
||||
color: Qt.rgba(accent.r, accent.g, accent.b, isDark ? 0.12 : 0.10)
|
||||
|
||||
CText {
|
||||
id: tText
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
font.pixelSize: 11
|
||||
font.weight: Font.Medium
|
||||
color: accent
|
||||
}
|
||||
}
|
||||
}
|
||||
CLevelTagFlow {
|
||||
tags: root.tags
|
||||
accent: root.accent
|
||||
isDark: root.isDark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
33
qml/MetaBuilder/CLevelTagFlow.qml
Normal file
33
qml/MetaBuilder/CLevelTagFlow.qml
Normal file
@@ -0,0 +1,33 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QmlComponents 1.0
|
||||
|
||||
Flow {
|
||||
id: root
|
||||
|
||||
property var tags: []
|
||||
property color accent: "#94A3B8"
|
||||
property bool isDark: false
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 6
|
||||
|
||||
Repeater {
|
||||
model: root.tags
|
||||
Rectangle {
|
||||
width: tText.implicitWidth + 16
|
||||
height: 24
|
||||
radius: 8
|
||||
color: Qt.rgba(root.accent.r, root.accent.g, root.accent.b, root.isDark ? 0.12 : 0.10)
|
||||
|
||||
CText {
|
||||
id: tText
|
||||
anchors.centerIn: parent
|
||||
text: modelData
|
||||
font.pixelSize: 11
|
||||
font.weight: Font.Medium
|
||||
color: root.accent
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,13 +46,7 @@ CCard {
|
||||
Layout.fillWidth: true
|
||||
model: levelOptions
|
||||
currentIndex: root.route ? root.route.level - 1 : 0
|
||||
onCurrentIndexChanged: {
|
||||
if (root.route) {
|
||||
var newLvl = currentIndex + 1
|
||||
if (newLvl !== root.route.level)
|
||||
root.fieldChanged("level", newLvl)
|
||||
}
|
||||
}
|
||||
onCurrentIndexChanged: if (root.route && currentIndex + 1 !== root.route.level) root.fieldChanged("level", currentIndex + 1)
|
||||
}
|
||||
|
||||
CText { variant: "caption"; text: "Layout Type" }
|
||||
@@ -60,13 +54,7 @@ CCard {
|
||||
Layout.fillWidth: true
|
||||
model: layoutOptions
|
||||
currentIndex: root.route ? layoutOptions.indexOf(root.route.layout) : 0
|
||||
onCurrentIndexChanged: {
|
||||
if (root.route) {
|
||||
var newLayoutVal = layoutOptions[currentIndex]
|
||||
if (newLayoutVal !== root.route.layout)
|
||||
root.fieldChanged("layout", newLayoutVal)
|
||||
}
|
||||
}
|
||||
onCurrentIndexChanged: if (root.route && layoutOptions[currentIndex] !== root.route.layout) root.fieldChanged("layout", layoutOptions[currentIndex])
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
|
||||
@@ -64,26 +64,10 @@ CCard {
|
||||
onTextChanged: root.passwordEdited(text)
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
CText { variant: "caption"; text: "Encryption" }
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
Repeater {
|
||||
model: root.encryptionOptions
|
||||
delegate: CButton {
|
||||
text: modelData
|
||||
variant: root.encryptionIndex === index ? "primary" : "ghost"
|
||||
size: "sm"
|
||||
onClicked: root.encryptionEdited(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
CEncryptionSelector {
|
||||
options: root.encryptionOptions
|
||||
selectedIndex: root.encryptionIndex
|
||||
onSelectionChanged: function(idx) { root.encryptionEdited(idx) }
|
||||
}
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
24
qml/MetaBuilder/CVersionPill.qml
Normal file
24
qml/MetaBuilder/CVersionPill.qml
Normal file
@@ -0,0 +1,24 @@
|
||||
import QtQuick
|
||||
import QmlComponents 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string version: ""
|
||||
property color accent: "#6366F1"
|
||||
property color containerColor: "#1a1a2e"
|
||||
|
||||
width: vLabel.implicitWidth + 24
|
||||
height: 28
|
||||
radius: 14
|
||||
color: containerColor
|
||||
|
||||
CText {
|
||||
id: vLabel
|
||||
anchors.centerIn: parent
|
||||
text: "v" + root.version
|
||||
font.family: "monospace"
|
||||
font.pixelSize: 12
|
||||
color: root.accent
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,7 @@ Rectangle {
|
||||
color: Theme.background
|
||||
clip: true
|
||||
|
||||
function requestPaint() {
|
||||
connectionLayer.requestPaint()
|
||||
}
|
||||
function requestPaint() { connectionLayer.requestPaint() }
|
||||
|
||||
function groupColor(nodeType) {
|
||||
var prefix = nodeType ? nodeType.split(".")[0] : ""
|
||||
@@ -50,7 +48,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Drop area for palette drag
|
||||
DropArea {
|
||||
anchors.fill: parent
|
||||
keys: ["text/node-type"]
|
||||
@@ -71,13 +68,7 @@ Rectangle {
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Component.onCompleted: {
|
||||
contentX = 1500
|
||||
contentY = 1500
|
||||
}
|
||||
|
||||
function centerX() { return contentX + width / 2 }
|
||||
function centerY() { return contentY + height / 2 }
|
||||
Component.onCompleted: { contentX = 1500; contentY = 1500 }
|
||||
|
||||
Item {
|
||||
id: canvasContent
|
||||
@@ -85,15 +76,11 @@ Rectangle {
|
||||
height: canvas.contentHeight
|
||||
|
||||
transform: Scale {
|
||||
origin.x: 0
|
||||
origin.y: 0
|
||||
xScale: root.zoom
|
||||
yScale: root.zoom
|
||||
origin.x: 0; origin.y: 0
|
||||
xScale: root.zoom; yScale: root.zoom
|
||||
}
|
||||
|
||||
CCanvasGrid {
|
||||
anchors.fill: parent
|
||||
}
|
||||
CCanvasGrid { anchors.fill: parent }
|
||||
|
||||
CConnectionLayer {
|
||||
id: connectionLayer
|
||||
@@ -109,7 +96,6 @@ Rectangle {
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: nodeRepeater
|
||||
model: root.nodes.length
|
||||
z: 2
|
||||
|
||||
@@ -123,42 +109,21 @@ Rectangle {
|
||||
|
||||
onNodeSelected: function(id) { root.nodeSelected(id) }
|
||||
onNodeMoved: function(id, x, y) { root.nodeMoved(id, x, y) }
|
||||
onConnectionDragStarted: function(nodeId, portName, isOutput, portX, portY) {
|
||||
root.connectionDragStarted(nodeId, portName, isOutput, portX, portY)
|
||||
}
|
||||
onConnectionCompleted: function(nodeId, portName) {
|
||||
root.connectionCompleted(nodeId, portName)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
CCanvasInteractionArea {
|
||||
anchors.fill: parent
|
||||
z: 0
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
hoverEnabled: true
|
||||
|
||||
onPositionChanged: function(mouse) {
|
||||
if (root.drawingConnection) {
|
||||
root.connectionDragUpdated(mouse.x, mouse.y)
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: function(mouse) {
|
||||
if (root.drawingConnection) {
|
||||
root.connectionDragFinished()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function(mouse) {
|
||||
root.canvasClicked()
|
||||
}
|
||||
|
||||
onWheel: function(wheel) {
|
||||
var zoomDelta = wheel.angleDelta.y > 0 ? 0.1 : -0.1
|
||||
root.zoomChanged(root.zoom + zoomDelta)
|
||||
}
|
||||
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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
qml/MetaBuilder/ComponentTreeDBAL.js
Normal file
86
qml/MetaBuilder/ComponentTreeDBAL.js
Normal file
@@ -0,0 +1,86 @@
|
||||
.pragma library
|
||||
|
||||
// DBAL persistence and tree manipulation helpers for component hierarchy.
|
||||
|
||||
function childrenCount(treeNodes, idx) {
|
||||
if (idx < 0 || idx >= treeNodes.length) return 0
|
||||
var parentDepth = treeNodes[idx].depth; var count = 0
|
||||
for (var i = idx + 1; i < treeNodes.length; i++) {
|
||||
if (treeNodes[i].depth <= parentDepth) break
|
||||
if (treeNodes[i].depth === parentDepth + 1) count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
function subtreeEnd(treeNodes, idx) {
|
||||
var parentDepth = treeNodes[idx].depth; var i = idx + 1
|
||||
while (i < treeNodes.length && treeNodes[i].depth > parentDepth) i++
|
||||
return i
|
||||
}
|
||||
|
||||
function addChild(treeNodes, parentIdx, nextNodeId, dbal, useLiveData, loadFn) {
|
||||
if (parentIdx < 0 || parentIdx >= treeNodes.length) return null
|
||||
var insertAt = subtreeEnd(treeNodes, parentIdx)
|
||||
var newNode = { nodeId: nextNodeId, name: "NewComponent", type: "atom", depth: treeNodes[parentIdx].depth + 1, visible: true, props: [] }
|
||||
if (useLiveData) dbal.create("component_node", newNode, function(r, e) { if (!e) loadFn() })
|
||||
var updated = treeNodes.slice(); updated.splice(insertAt, 0, newNode)
|
||||
return { nodes: updated, selectedIndex: insertAt }
|
||||
}
|
||||
|
||||
function removeNode(treeNodes, idx, dbal, useLiveData, loadFn) {
|
||||
if (idx < 0 || idx >= treeNodes.length || treeNodes[idx].depth === 0) return null
|
||||
if (useLiveData && treeNodes[idx].id) dbal.remove("component_node", treeNodes[idx].id, function(r, e) { if (!e) loadFn() })
|
||||
var endIdx = subtreeEnd(treeNodes, idx); var updated = treeNodes.slice(); updated.splice(idx, endIdx - idx)
|
||||
var sel = idx < updated.length ? idx : (updated.length > 0 ? updated.length - 1 : -1)
|
||||
return { nodes: updated, selectedIndex: sel }
|
||||
}
|
||||
|
||||
function updateNode(treeNodes, idx, field, value) {
|
||||
if (idx < 0 || idx >= treeNodes.length) return treeNodes
|
||||
var updated = treeNodes.slice()
|
||||
updated[idx] = Object.assign({}, updated[idx]); updated[idx][field] = value
|
||||
return updated
|
||||
}
|
||||
|
||||
function addProp(treeNodes, idx) {
|
||||
if (idx < 0 || idx >= treeNodes.length) return treeNodes
|
||||
var updated = treeNodes.slice(); var node = Object.assign({}, updated[idx])
|
||||
var newProps = node.props.slice(); newProps.push({ key: "newProp", value: "" }); node.props = newProps
|
||||
updated[idx] = node; return updated
|
||||
}
|
||||
|
||||
function removeProp(treeNodes, nodeIdx, propIdx) {
|
||||
if (nodeIdx < 0 || nodeIdx >= treeNodes.length) return treeNodes
|
||||
var updated = treeNodes.slice(); var node = Object.assign({}, updated[nodeIdx])
|
||||
var newProps = node.props.slice(); newProps.splice(propIdx, 1); node.props = newProps
|
||||
updated[nodeIdx] = node; return updated
|
||||
}
|
||||
|
||||
function saveNode(dbal, treeNodes, idx, loadFn) {
|
||||
var node = treeNodes[idx]
|
||||
var data = { nodeId: node.nodeId, name: node.name, type: node.type, depth: node.depth, visible: node.visible, props: node.props }
|
||||
if (node.id) dbal.update("component_node", node.id, data, function(r, e) { if (!e) loadFn() })
|
||||
else dbal.create("component_node", data, function(r, e) { if (!e) loadFn() })
|
||||
}
|
||||
|
||||
function loadComponents(dbal, callback) {
|
||||
dbal.list("component_node", { take: 200 }, function(result, error) {
|
||||
if (!error && result && result.items && result.items.length > 0) {
|
||||
var parsed = []; var maxId = 0
|
||||
for (var i = 0; i < result.items.length; i++) {
|
||||
var n = result.items[i]; var nid = n.nodeId || n.id || i
|
||||
if (nid > maxId) maxId = nid
|
||||
parsed.push({ id: n.id, nodeId: nid, name: n.name || "Component", type: n.type || "atom", depth: n.depth !== undefined ? n.depth : 0, visible: n.visible !== undefined ? n.visible : true, props: n.props || [] })
|
||||
}
|
||||
callback(parsed, maxId + 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadJson(relativePath) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("GET", relativePath, false)
|
||||
xhr.send()
|
||||
if (xhr.status === 200 || xhr.status === 0) return JSON.parse(xhr.responseText)
|
||||
return []
|
||||
}
|
||||
@@ -41,26 +41,9 @@ Rectangle {
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
CText { variant: "caption"; text: "SCRIPT NAME" }
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
text: scriptName
|
||||
onTextChanged: nameChanged(text)
|
||||
}
|
||||
|
||||
CText { variant: "caption"; text: "DESCRIPTION" }
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
text: scriptDescription
|
||||
onTextChanged: descriptionChanged(text)
|
||||
}
|
||||
|
||||
CText { variant: "caption"; text: "RETURN TYPE" }
|
||||
CTextField {
|
||||
Layout.fillWidth: true
|
||||
text: returnType
|
||||
onTextChanged: returnTypeChanged(text)
|
||||
}
|
||||
CTextField { Layout.fillWidth: true; label: "SCRIPT NAME"; text: scriptName; onTextChanged: nameChanged(text) }
|
||||
CTextField { Layout.fillWidth: true; label: "DESCRIPTION"; text: scriptDescription; onTextChanged: descriptionChanged(text) }
|
||||
CTextField { Layout.fillWidth: true; label: "RETURN TYPE"; text: returnType; onTextChanged: returnTypeChanged(text) }
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
|
||||
105
qml/MetaBuilder/MediaJobRow.qml
Normal file
105
qml/MetaBuilder/MediaJobRow.qml
Normal file
@@ -0,0 +1,105 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QmlComponents 1.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
property var job: ({})
|
||||
property bool isLast: false
|
||||
|
||||
signal cancelRequested()
|
||||
|
||||
function jobStatusColor(status) {
|
||||
switch (status) {
|
||||
case "completed": return "success"
|
||||
case "processing": return "warning"
|
||||
case "queued": return "info"
|
||||
case "failed": return "error"
|
||||
default: return "info"
|
||||
}
|
||||
}
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: root.job.id || ""
|
||||
font.family: "monospace"
|
||||
Layout.preferredWidth: 100
|
||||
}
|
||||
|
||||
CBadge {
|
||||
text: root.job.type || ""
|
||||
Layout.preferredWidth: 80
|
||||
}
|
||||
|
||||
CStatusBadge {
|
||||
status: jobStatusColor(root.job.status || "")
|
||||
text: root.job.status || ""
|
||||
Layout.preferredWidth: 100
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 20
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.border
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * ((root.job.progress || 0) / 100)
|
||||
height: parent.height
|
||||
radius: 3
|
||||
color: root.job.status === "failed" ? Theme.error
|
||||
: root.job.status === "completed" ? Theme.success
|
||||
: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
CText {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
variant: "caption"
|
||||
text: (root.job.progress || 0) + "%"
|
||||
}
|
||||
}
|
||||
|
||||
CText {
|
||||
variant: "caption"
|
||||
text: root.job.created || ""
|
||||
Layout.preferredWidth: 160
|
||||
color: Theme.textSecondary
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: "Cancel"
|
||||
variant: "danger"
|
||||
size: "sm"
|
||||
enabled: root.job.status === "queued" || root.job.status === "processing"
|
||||
visible: root.job.status !== "completed" && root.job.status !== "failed"
|
||||
Layout.preferredWidth: 70
|
||||
onClicked: root.cancelRequested()
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: root.job.status === "completed" || root.job.status === "failed"
|
||||
Layout.preferredWidth: 70
|
||||
}
|
||||
}
|
||||
|
||||
CDivider {
|
||||
Layout.fillWidth: true
|
||||
visible: !root.isLast
|
||||
}
|
||||
}
|
||||
@@ -11,16 +11,6 @@ CCard {
|
||||
|
||||
signal cancelRequested(string jobId)
|
||||
|
||||
function jobStatusColor(status) {
|
||||
switch (status) {
|
||||
case "completed": return "success"
|
||||
case "processing": return "warning"
|
||||
case "queued": return "info"
|
||||
case "failed": return "error"
|
||||
default: return "info"
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 16
|
||||
@@ -36,7 +26,6 @@ CCard {
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
// Table header
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
@@ -51,95 +40,14 @@ CCard {
|
||||
|
||||
CDivider { Layout.fillWidth: true }
|
||||
|
||||
// Job rows
|
||||
Repeater {
|
||||
model: jobs
|
||||
|
||||
delegate: ColumnLayout {
|
||||
delegate: MediaJobRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
|
||||
FlexRow {
|
||||
Layout.fillWidth: true
|
||||
spacing: 8
|
||||
|
||||
CText {
|
||||
variant: "body2"
|
||||
text: modelData.id
|
||||
font.family: "monospace"
|
||||
Layout.preferredWidth: 100
|
||||
}
|
||||
|
||||
CBadge {
|
||||
text: modelData.type
|
||||
Layout.preferredWidth: 80
|
||||
}
|
||||
|
||||
CStatusBadge {
|
||||
status: jobStatusColor(modelData.status)
|
||||
text: modelData.status
|
||||
Layout.preferredWidth: 100
|
||||
}
|
||||
|
||||
// Progress bar area
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 20
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 6
|
||||
radius: 3
|
||||
color: Theme.border
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * (modelData.progress / 100)
|
||||
height: parent.height
|
||||
radius: 3
|
||||
color: modelData.status === "failed" ? Theme.error
|
||||
: modelData.status === "completed" ? Theme.success
|
||||
: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
CText {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
variant: "caption"
|
||||
text: modelData.progress + "%"
|
||||
}
|
||||
}
|
||||
|
||||
CText {
|
||||
variant: "caption"
|
||||
text: modelData.created
|
||||
Layout.preferredWidth: 160
|
||||
color: Theme.textSecondary
|
||||
}
|
||||
|
||||
CButton {
|
||||
text: "Cancel"
|
||||
variant: "danger"
|
||||
size: "sm"
|
||||
enabled: modelData.status === "queued" || modelData.status === "processing"
|
||||
visible: modelData.status !== "completed" && modelData.status !== "failed"
|
||||
Layout.preferredWidth: 70
|
||||
onClicked: cancelRequested(modelData.id)
|
||||
}
|
||||
|
||||
// Placeholder for completed/failed jobs
|
||||
Item {
|
||||
visible: modelData.status === "completed" || modelData.status === "failed"
|
||||
Layout.preferredWidth: 70
|
||||
}
|
||||
}
|
||||
|
||||
CDivider {
|
||||
Layout.fillWidth: true
|
||||
visible: index < jobs.length - 1
|
||||
}
|
||||
job: modelData
|
||||
isLast: index >= jobs.length - 1
|
||||
onCancelRequested: jobTable.cancelRequested(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
qml/MetaBuilder/ProfileDBAL.js
Normal file
35
qml/MetaBuilder/ProfileDBAL.js
Normal file
@@ -0,0 +1,35 @@
|
||||
.pragma library
|
||||
|
||||
// DBAL persistence helpers for profile view.
|
||||
|
||||
function loadProfile(dbal, currentUser, callback) {
|
||||
if (!currentUser) return
|
||||
dbal.read("user", currentUser, function(result, error) {
|
||||
if (result) callback(result)
|
||||
})
|
||||
}
|
||||
|
||||
function saveProfile(dbal, currentUser, data, callback) {
|
||||
dbal.update("user", currentUser, data, function(result, error) {
|
||||
callback(!!result, error)
|
||||
})
|
||||
}
|
||||
|
||||
function changePassword(dbal, currentUser, passwords, callback) {
|
||||
if (passwords["new"] !== passwords["confirm"]) return
|
||||
dbal.execute("core/change-password", {
|
||||
userId: currentUser,
|
||||
oldPassword: passwords["current"],
|
||||
newPassword: passwords["new"]
|
||||
}, function(result, error) {
|
||||
callback(!!result, error)
|
||||
})
|
||||
}
|
||||
|
||||
function loadJson(relativePath) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("GET", relativePath, false)
|
||||
xhr.send()
|
||||
if (xhr.status === 200 || xhr.status === 0) return JSON.parse(xhr.responseText)
|
||||
return null
|
||||
}
|
||||
72
qml/MetaBuilder/SchemaEditorDBAL.js
Normal file
72
qml/MetaBuilder/SchemaEditorDBAL.js
Normal file
@@ -0,0 +1,72 @@
|
||||
.pragma library
|
||||
|
||||
// DBAL persistence and schema CRUD helpers for schema editor.
|
||||
|
||||
function loadSchemas(dbal, callback) {
|
||||
dbal.execute("core/schema", {}, function(result, error) {
|
||||
if (!error && result && result.items) {
|
||||
var parsed = []
|
||||
for (var i = 0; i < result.items.length; i++) {
|
||||
var item = result.items[i]; var fields = []
|
||||
if (item.fields) {
|
||||
for (var j = 0; j < item.fields.length; j++) {
|
||||
var f = item.fields[j]
|
||||
fields.push({ name: f.name || "", type: f.type || "string", required: f.required || false, defaultValue: f.defaultValue || f["default"] || "", description: f.description || "" })
|
||||
}
|
||||
}
|
||||
parsed.push({ name: item.name || "", description: item.description || "", fields: fields })
|
||||
}
|
||||
if (parsed.length > 0) callback(parsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function currentSchema(schemas, idx) { return schemas[idx] || null }
|
||||
function currentFields(schemas, idx) { var s = currentSchema(schemas, idx); return s ? s.fields : [] }
|
||||
function currentField(schemas, schemaIdx, fieldIdx) {
|
||||
var fields = currentFields(schemas, schemaIdx)
|
||||
return (fieldIdx >= 0 && fieldIdx < fields.length) ? fields[fieldIdx] : null
|
||||
}
|
||||
|
||||
function updateField(schemas, schemaIdx, fieldIdx, key, value) {
|
||||
var copy = JSON.parse(JSON.stringify(schemas))
|
||||
copy[schemaIdx].fields[fieldIdx][key] = value; return copy
|
||||
}
|
||||
|
||||
function addSchema(schemas, name, description, dbal, loadFn) {
|
||||
if (name.trim() === "") return null
|
||||
var schemaData = { name: name.trim(), description: description.trim(), fields: [{ name: "id", type: "string", required: true, defaultValue: "uuid()", description: "Primary key" }] }
|
||||
if (dbal && dbal.connected) {
|
||||
dbal.create("schema", schemaData, function(result, error) { if (!error) loadFn() })
|
||||
}
|
||||
var copy = JSON.parse(JSON.stringify(schemas)); copy.push(schemaData)
|
||||
return { schemas: copy, selectedIndex: copy.length - 1 }
|
||||
}
|
||||
|
||||
function deleteSchema(schemas, selectedIndex) {
|
||||
if (schemas.length <= 1) return null
|
||||
var copy = JSON.parse(JSON.stringify(schemas)); copy.splice(selectedIndex, 1)
|
||||
var newIdx = selectedIndex >= copy.length ? copy.length - 1 : selectedIndex
|
||||
return { schemas: copy, selectedIndex: newIdx }
|
||||
}
|
||||
|
||||
function addField(schemas, schemaIdx, fieldData) {
|
||||
if (fieldData.name.trim() === "") return null
|
||||
var copy = JSON.parse(JSON.stringify(schemas))
|
||||
copy[schemaIdx].fields.push({ name: fieldData.name.trim(), type: fieldData.type, required: fieldData.required, defaultValue: fieldData.defaultValue, description: fieldData.description })
|
||||
return { schemas: copy, selectedFieldIndex: copy[schemaIdx].fields.length - 1 }
|
||||
}
|
||||
|
||||
function deleteField(schemas, schemaIdx, fieldIdx) {
|
||||
if (fieldIdx < 0) return schemas
|
||||
var copy = JSON.parse(JSON.stringify(schemas))
|
||||
copy[schemaIdx].fields.splice(fieldIdx, 1); return copy
|
||||
}
|
||||
|
||||
function loadJson(relativePath) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("GET", relativePath, false)
|
||||
xhr.send()
|
||||
if (xhr.status === 200 || xhr.status === 0) return JSON.parse(xhr.responseText)
|
||||
return []
|
||||
}
|
||||
39
qml/MetaBuilder/ThemeEditorLogic.js
Normal file
39
qml/MetaBuilder/ThemeEditorLogic.js
Normal file
@@ -0,0 +1,39 @@
|
||||
.pragma library
|
||||
|
||||
// Theme editor color token and reset helpers.
|
||||
|
||||
function applyColorChange(root, token, value) {
|
||||
switch (token) {
|
||||
case "primary": root.customPrimary = value; break
|
||||
case "background": root.customBackground = value; break
|
||||
case "surface": root.customSurface = value; break
|
||||
case "paper": root.customPaper = value; break
|
||||
case "text": root.customText = value; break
|
||||
case "textSecondary": root.customTextSecondary = value; break
|
||||
case "border": root.customBorder = value; break
|
||||
case "error": root.customError = value; break
|
||||
case "warning": root.customWarning = value; break
|
||||
case "success": root.customSuccess = value; break
|
||||
case "info": root.customInfo = value; break
|
||||
}
|
||||
}
|
||||
|
||||
function resetToDefaults(root, Theme) {
|
||||
root.customPrimary = Theme.primary; root.customBackground = Theme.background
|
||||
root.customSurface = Theme.surface; root.customPaper = Theme.paper
|
||||
root.customText = Theme.text; root.customTextSecondary = Theme.textSecondary
|
||||
root.customBorder = Theme.border; root.customError = Theme.error
|
||||
root.customWarning = Theme.warning; root.customSuccess = Theme.success
|
||||
root.customInfo = Theme.info
|
||||
root.fontFamily = "Inter"; root.baseFontSize = 14; root.baseSpacing = 8
|
||||
root.radiusSmall = 4; root.radiusMedium = 8; root.radiusLarge = 16
|
||||
root.hasUnsavedChanges = false
|
||||
}
|
||||
|
||||
function loadJson(relativePath) {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open("GET", relativePath, false)
|
||||
xhr.send()
|
||||
if (xhr.status === 200 || xhr.status === 0) return JSON.parse(xhr.responseText)
|
||||
return []
|
||||
}
|
||||
@@ -124,6 +124,7 @@ CGodPanelGuideTab 1.0 CGodPanelGuideTab.qml
|
||||
CNodePaletteItem 1.0 CNodePaletteItem.qml
|
||||
CNodePortColumn 1.0 CNodePortColumn.qml
|
||||
CNodePorts 1.0 CNodePorts.qml
|
||||
CKeyboardShortcuts 1.0 CKeyboardShortcuts.qml
|
||||
CPackageDetailSidebar 1.0 CPackageDetailSidebar.qml
|
||||
CPackageListItem 1.0 CPackageListItem.qml
|
||||
CssSuggestionPopup 1.0 CssSuggestionPopup.qml
|
||||
@@ -154,3 +155,15 @@ CExecutionStatusDot 1.0 CExecutionStatusDot.qml
|
||||
CSmtpBodyEditor 1.0 CSmtpBodyEditor.qml
|
||||
CRoutePermissionSection 1.0 CRoutePermissionSection.qml
|
||||
LuaScriptInfoSection 1.0 LuaScriptInfoSection.qml
|
||||
CGodPanelSettingsTab 1.0 CGodPanelSettingsTab.qml
|
||||
CModeratorHeader 1.0 CModeratorHeader.qml
|
||||
ThemePreviewStatusCard 1.0 ThemePreviewStatusCard.qml
|
||||
ThemePreviewActivityCard 1.0 ThemePreviewActivityCard.qml
|
||||
MediaChannelSchedule 1.0 MediaChannelSchedule.qml
|
||||
CStatCell 1.0 CStatCell.qml
|
||||
CssPropertyRow 1.0 CssPropertyRow.qml
|
||||
DropdownExpandedList 1.0 DropdownExpandedList.qml
|
||||
MediaRadioPlaylist 1.0 MediaRadioPlaylist.qml
|
||||
CNotificationIconBadge 1.0 CNotificationIconBadge.qml
|
||||
CComponentPropsList 1.0 CComponentPropsList.qml
|
||||
MediaJobRow 1.0 MediaJobRow.qml
|
||||
|
||||
Reference in New Issue
Block a user