diff --git a/frontends/qt6/config/dropdown-defaults.json b/frontends/qt6/config/dropdown-defaults.json new file mode 100644 index 000000000..af4a7c0c2 --- /dev/null +++ b/frontends/qt6/config/dropdown-defaults.json @@ -0,0 +1,9 @@ +[ + { "name": "user_roles", "description": "Assignable user roles for access control", "allowCustom": false, "required": true, "options": [{ "label": "Administrator", "value": "admin" }, { "label": "Moderator", "value": "moderator" }, { "label": "Editor", "value": "editor" }, { "label": "Viewer", "value": "viewer" }, { "label": "Guest", "value": "guest" }] }, + { "name": "content_status", "description": "Publication lifecycle status for content items", "allowCustom": false, "required": true, "options": [{ "label": "Draft", "value": "draft" }, { "label": "In Review", "value": "in_review" }, { "label": "Published", "value": "published" }, { "label": "Archived", "value": "archived" }] }, + { "name": "priority_levels", "description": "Task and issue priority classifications", "allowCustom": false, "required": true, "options": [{ "label": "Critical", "value": "critical" }, { "label": "High", "value": "high" }, { "label": "Medium", "value": "medium" }, { "label": "Low", "value": "low" }, { "label": "None", "value": "none" }] }, + { "name": "categories", "description": "General-purpose content categorization tags", "allowCustom": true, "required": false, "options": [{ "label": "Technology", "value": "technology" }, { "label": "Design", "value": "design" }, { "label": "Business", "value": "business" }, { "label": "Science", "value": "science" }, { "label": "Education", "value": "education" }, { "label": "Entertainment", "value": "entertainment" }] }, + { "name": "languages", "description": "Supported interface and content languages", "allowCustom": false, "required": true, "options": [{ "label": "English", "value": "en" }, { "label": "Spanish", "value": "es" }, { "label": "French", "value": "fr" }, { "label": "German", "value": "de" }, { "label": "Japanese", "value": "ja" }, { "label": "Chinese", "value": "zh" }, { "label": "Portuguese", "value": "pt" }] }, + { "name": "themes", "description": "Available UI theme presets", "allowCustom": true, "required": false, "options": [{ "label": "Light", "value": "light" }, { "label": "Dark", "value": "dark" }, { "label": "System Default", "value": "system" }, { "label": "High Contrast", "value": "high_contrast" }] }, + { "name": "database_backends", "description": "Supported DBAL database adapter backends", "allowCustom": false, "required": true, "options": [{ "label": "SQLite", "value": "sqlite" }, { "label": "PostgreSQL", "value": "postgres" }, { "label": "MySQL", "value": "mysql" }, { "label": "MariaDB", "value": "mariadb" }, { "label": "MongoDB", "value": "mongodb" }, { "label": "Redis", "value": "redis" }, { "label": "CockroachDB", "value": "cockroachdb" }, { "label": "SurrealDB", "value": "surrealdb" }, { "label": "Supabase", "value": "supabase" }, { "label": "In-Memory", "value": "memory" }] } +] diff --git a/frontends/qt6/config/media-mock-data.json b/frontends/qt6/config/media-mock-data.json new file mode 100644 index 000000000..077f53a0e --- /dev/null +++ b/frontends/qt6/config/media-mock-data.json @@ -0,0 +1,55 @@ +{ + "jobs": [ + { "id": "job-001", "type": "video", "status": "completed", "progress": 100, "created": "2026-03-19 08:12:34" }, + { "id": "job-002", "type": "audio", "status": "processing", "progress": 67, "created": "2026-03-19 09:45:01" }, + { "id": "job-003", "type": "image", "status": "queued", "progress": 0, "created": "2026-03-19 10:02:18" }, + { "id": "job-004", "type": "document", "status": "failed", "progress": 23, "created": "2026-03-19 10:15:42" }, + { "id": "job-005", "type": "video", "status": "processing", "progress": 34, "created": "2026-03-19 10:30:55" } + ], + "radioChannels": [ + { + "name": "MetaBuilder FM", "status": "live", "listeners": 142, + "currentTrack": "Synthwave Dreams - NeonCoder", "bitrate": "320 kbps", + "playlist": ["Synthwave Dreams - NeonCoder", "Digital Horizon - ByteRunner", "Midnight Protocol - CipherAce", "Neon Streets - RetroVolt", "Electric Soul - WaveForm"] + }, + { + "name": "Chiptune Radio", "status": "live", "listeners": 87, + "currentTrack": "8-Bit Adventure - PixelMaster", "bitrate": "192 kbps", + "playlist": ["8-Bit Adventure - PixelMaster", "Game Over Theme - ChipTuner", "Level Up! - BitCrafter", "Boss Battle - NEStalgia"] + }, + { + "name": "Ambient Lounge", "status": "offline", "listeners": 0, + "currentTrack": "---", "bitrate": "256 kbps", + "playlist": ["Deep Focus - AmbientWave", "Ocean Drift - CalmCode", "Forest Rain - NatureByte"] + } + ], + "tvChannels": [ + { + "name": "MetaBuilder TV", "status": "broadcasting", "resolution": "1080p", + "viewers": 234, "uptime": "6h 14m", + "schedule": [ + { "time": "10:00", "program": "Morning Code Review", "duration": "60 min" }, + { "time": "11:00", "program": "Architecture Deep Dive", "duration": "90 min" }, + { "time": "12:30", "program": "Live Build Session", "duration": "120 min" }, + { "time": "14:30", "program": "Community Q&A", "duration": "60 min" }, + { "time": "15:30", "program": "Plugin Showcase", "duration": "45 min" } + ] + }, + { + "name": "Retro Gaming Channel", "status": "offline", "resolution": "720p", + "viewers": 0, "uptime": "0m", + "schedule": [ + { "time": "18:00", "program": "Speedrun Saturday", "duration": "120 min" }, + { "time": "20:00", "program": "Retro Reviews", "duration": "60 min" }, + { "time": "21:00", "program": "Chiptune Live", "duration": "90 min" } + ] + } + ], + "plugins": [ + { "name": "FFmpeg", "version": "8.0.1", "status": "active", "capabilities": ["H.264", "H.265", "VP9", "AV1", "AAC", "FLAC", "Opus"] }, + { "name": "ImageMagick", "version": "7.1.1", "status": "active", "capabilities": ["JPEG", "PNG", "WebP", "AVIF", "SVG", "TIFF", "Resize", "Crop"] }, + { "name": "Pandoc", "version": "3.6.1", "status": "active", "capabilities": ["Markdown", "PDF", "DOCX", "HTML", "LaTeX", "EPUB"] }, + { "name": "Radio", "version": "1.2.0", "status": "active", "capabilities": ["Icecast", "MP3 Stream", "OGG Stream", "Playlist", "Metadata"] }, + { "name": "LibRetro", "version": "1.19.1", "status": "inactive", "capabilities": ["NES", "SNES", "Genesis", "GBA", "N64", "PS1", "Recording"] } + ] +} diff --git a/qml/MetaBuilder/qmldir b/qml/MetaBuilder/qmldir index e7319d2f7..9ab979194 100644 --- a/qml/MetaBuilder/qmldir +++ b/qml/MetaBuilder/qmldir @@ -47,7 +47,6 @@ CModActionCard 1.0 CModActionCard.qml CModStatsRow 1.0 CModStatsRow.qml CWorkflowToolbar 1.0 CWorkflowToolbar.qml CNodePalette 1.0 CNodePalette.qml -CNodePaletteItem 1.0 CNodePaletteItem.qml CNodePropertiesPanel 1.0 CNodePropertiesPanel.qml CNodeParameterList 1.0 CNodeParameterList.qml CNodePortsDisplay 1.0 CNodePortsDisplay.qml @@ -60,7 +59,6 @@ DropdownPreview 1.0 DropdownPreview.qml UserStatsBar 1.0 UserStatsBar.qml UserSearchFilter 1.0 UserSearchFilter.qml UserTable 1.0 UserTable.qml -UserTableRow 1.0 UserTableRow.qml UserFormDialog 1.0 UserFormDialog.qml CssClassSidebar 1.0 CssClassSidebar.qml CssPropertyEditor 1.0 CssPropertyEditor.qml diff --git a/qml/qt6/AdminView.qml b/qml/qt6/AdminView.qml index 1c173fe4a..0a4b546c7 100644 --- a/qml/qt6/AdminView.qml +++ b/qml/qt6/AdminView.qml @@ -227,30 +227,18 @@ Rectangle { } // ── Delete confirmation dialog ─────────────────────────────── - CDialog { - id: deleteConfirmDialog; visible: deleteDialogOpen; title: "Delete " + selectedEntity - ColumnLayout { - width: 360; spacing: 16 - CAlert { Layout.fillWidth: true; severity: "warning"; text: "This action cannot be undone." } - CText { - Layout.fillWidth: true; variant: "body1" - text: { var rec = getPagedRecords()[editingIndex]; return rec ? "Are you sure you want to delete record " + rec.id + "?" : "Delete this record?"; } - } - FlexRow { - Layout.fillWidth: true; Layout.topMargin: 8; spacing: 8 - Item { Layout.fillWidth: true } - CButton { text: "Cancel"; variant: "ghost"; size: "sm"; onClicked: deleteDialogOpen = false } - CButton { - text: "Delete"; variant: "danger"; size: "sm" - onClicked: { - if (useLiveData) { - var rec = getPagedRecords()[editingIndex]; - if (rec) dbal.remove(selectedEntity, rec.id, function(result, error) { if (!error) loadEntityData(); else deleteRecord(editingIndex); }); - } else { deleteRecord(editingIndex); } - deleteDialogOpen = false; - } - } - } + CDeleteRecordDialog { + visible: deleteDialogOpen + entity: selectedEntity + recordId: { var rec = getPagedRecords()[editingIndex]; return rec ? rec.id : ""; } + useLiveData: root.useLiveData + onConfirmed: { + if (useLiveData) { + var rec = getPagedRecords()[editingIndex]; + if (rec) dbal.remove(selectedEntity, rec.id, function(result, error) { if (!error) loadEntityData(); else deleteRecord(editingIndex); }); + } else { deleteRecord(editingIndex); } + deleteDialogOpen = false; } + onCancelled: deleteDialogOpen = false } } diff --git a/qml/qt6/DropdownConfigManager.qml b/qml/qt6/DropdownConfigManager.qml index a2d14c746..71c8de8ee 100644 --- a/qml/qt6/DropdownConfigManager.qml +++ b/qml/qt6/DropdownConfigManager.qml @@ -16,18 +16,20 @@ Rectangle { property int selectedIndex: -1 property bool addDialogOpen: false property bool deleteDialogOpen: false - property string newDropdownName: "" - property string newDropdownDescription: "" - property var dropdowns: [ - { name: "user_roles", description: "Assignable user roles for access control", allowCustom: false, required: true, options: [{ label: "Administrator", value: "admin" }, { label: "Moderator", value: "moderator" }, { label: "Editor", value: "editor" }, { label: "Viewer", value: "viewer" }, { label: "Guest", value: "guest" }] }, - { name: "content_status", description: "Publication lifecycle status for content items", allowCustom: false, required: true, options: [{ label: "Draft", value: "draft" }, { label: "In Review", value: "in_review" }, { label: "Published", value: "published" }, { label: "Archived", value: "archived" }] }, - { name: "priority_levels", description: "Task and issue priority classifications", allowCustom: false, required: true, options: [{ label: "Critical", value: "critical" }, { label: "High", value: "high" }, { label: "Medium", value: "medium" }, { label: "Low", value: "low" }, { label: "None", value: "none" }] }, - { name: "categories", description: "General-purpose content categorization tags", allowCustom: true, required: false, options: [{ label: "Technology", value: "technology" }, { label: "Design", value: "design" }, { label: "Business", value: "business" }, { label: "Science", value: "science" }, { label: "Education", value: "education" }, { label: "Entertainment", value: "entertainment" }] }, - { name: "languages", description: "Supported interface and content languages", allowCustom: false, required: true, options: [{ label: "English", value: "en" }, { label: "Spanish", value: "es" }, { label: "French", value: "fr" }, { label: "German", value: "de" }, { label: "Japanese", value: "ja" }, { label: "Chinese", value: "zh" }, { label: "Portuguese", value: "pt" }] }, - { name: "themes", description: "Available UI theme presets", allowCustom: true, required: false, options: [{ label: "Light", value: "light" }, { label: "Dark", value: "dark" }, { label: "System Default", value: "system" }, { label: "High Contrast", value: "high_contrast" }] }, - { name: "database_backends", description: "Supported DBAL database adapter backends", allowCustom: false, required: true, options: [{ label: "SQLite", value: "sqlite" }, { label: "PostgreSQL", value: "postgres" }, { label: "MySQL", value: "mysql" }, { label: "MariaDB", value: "mariadb" }, { label: "MongoDB", value: "mongodb" }, { label: "Redis", value: "redis" }, { label: "CockroachDB", value: "cockroachdb" }, { label: "SurrealDB", value: "surrealdb" }, { label: "Supabase", value: "supabase" }, { label: "In-Memory", value: "memory" }] } - ] + property var dropdowns: [] + + Component.onCompleted: { + var xhr = new XMLHttpRequest() + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { + dropdowns = JSON.parse(xhr.responseText) + } + } + xhr.open("GET", "config/dropdown-defaults.json") + xhr.send() + loadDropdowns() + } function selectedDropdown() { if (selectedIndex < 0 || selectedIndex >= dropdowns.length) return null @@ -67,12 +69,12 @@ Rectangle { var dd = JSON.parse(JSON.stringify(dropdowns[selectedIndex])); dd.options[optIndex][field] = value; updateDropdown(selectedIndex, dd) } - function addDropdown() { - if (newDropdownName.trim() === "") return - var newDd = { name: newDropdownName.trim().toLowerCase().replace(/ /g, "_"), description: newDropdownDescription.trim() || "No description", allowCustom: false, required: false, options: [{ label: "Option 1", value: "option_1" }] } + function addDropdown(name, description) { + if (name.trim() === "") return + var newDd = { name: name.trim().toLowerCase().replace(/ /g, "_"), description: description.trim() || "No description", allowCustom: false, required: false, options: [{ label: "Option 1", value: "option_1" }] } if (useLiveData) dbal.execute("core/dropdown-configs/create", { data: newDd }, function(r, e) { if (!e) loadDropdowns() }) var copy = dropdowns.slice(); copy.push(newDd); dropdowns = copy - selectedIndex = dropdowns.length - 1; newDropdownName = ""; newDropdownDescription = ""; addDialogOpen = false + selectedIndex = dropdowns.length - 1; addDialogOpen = false } function deleteSelectedDropdown() { @@ -97,7 +99,6 @@ Rectangle { }) } onUseLiveDataChanged: { if (useLiveData) loadDropdowns() } - Component.onCompleted: { loadDropdowns() } function saveDropdown(index) { if (!useLiveData || index < 0 || index >= dropdowns.length) return @@ -208,20 +209,10 @@ Rectangle { } // Add Dropdown Dialog - CDialog { - visible: addDialogOpen; title: "Add New Dropdown" - ColumnLayout { - spacing: 16; width: 400 - CText { variant: "body2"; text: "Create a new dropdown configuration. The name will be normalized to snake_case." } - CTextField { label: "Dropdown Name"; placeholderText: "e.g. ticket_types"; text: newDropdownName; Layout.fillWidth: true; onTextChanged: newDropdownName = text } - CTextField { label: "Description"; placeholderText: "What will this dropdown be used for?"; text: newDropdownDescription; Layout.fillWidth: true; onTextChanged: newDropdownDescription = text } - CAlert { severity: "info"; text: "A default option will be added. You can configure options after creation."; Layout.fillWidth: true } - FlexRow { - Layout.fillWidth: true; spacing: 8; Item { Layout.fillWidth: true } - CButton { text: "Cancel"; variant: "ghost"; onClicked: { addDialogOpen = false; newDropdownName = ""; newDropdownDescription = "" } } - CButton { text: "Create"; variant: "primary"; enabled: newDropdownName.trim() !== ""; onClicked: addDropdown() } - } - } + CAddDropdownDialog { + visible: addDialogOpen + onCreateRequested: function(name, description) { addDropdown(name, description) } + onCancelled: addDialogOpen = false } // Delete Confirmation Dialog diff --git a/qml/qt6/PackageManager.qml b/qml/qt6/PackageManager.qml index cb7c0fc94..7ae97ad3a 100644 --- a/qml/qt6/PackageManager.qml +++ b/qml/qt6/PackageManager.qml @@ -41,101 +41,9 @@ Rectangle { Layout.fillHeight: true spacing: 16 - // Package details sidebar - CCard { - Layout.preferredWidth: 320 - Layout.fillHeight: true - - ColumnLayout { - anchors.fill: parent - anchors.margins: 16 - spacing: 12 - - CText { variant: "subtitle1"; text: "Package Details" } - - // Show details for selected package - Rectangle { - Layout.fillWidth: true - Layout.fillHeight: true - color: "transparent" - visible: selectedPackageId !== "" - - ColumnLayout { - anchors.fill: parent - spacing: 8 - - CText { - variant: "h5" - text: { - var pkg = PackageLoader.getPackage(selectedPackageId) - return pkg ? pkg.name : "" - } - } - - CText { - variant: "body2" - wrapMode: Text.Wrap - Layout.fillWidth: true - text: { - var pkg = PackageLoader.getPackage(selectedPackageId) - return pkg ? pkg.description : "" - } - } - - CDivider { Layout.fillWidth: true } - - CText { variant: "caption"; text: "Version" } - CText { - variant: "body2" - text: { - var pkg = PackageLoader.getPackage(selectedPackageId) - return pkg ? pkg.version : "" - } - } - - CText { variant: "caption"; text: "Category" } - CBadge { - text: { - var pkg = PackageLoader.getPackage(selectedPackageId) - return pkg && pkg.category ? pkg.category : "—" - } - } - - CText { variant: "caption"; text: "Dependencies" } - CText { - variant: "body2" - wrapMode: Text.Wrap - Layout.fillWidth: true - text: { - var deps = PackageLoader.resolveDependencies(selectedPackageId) - return deps.length > 0 ? deps.join(", ") : "None" - } - } - - Item { Layout.fillHeight: true } - } - } - - // Placeholder when nothing selected - CText { - visible: selectedPackageId === "" - variant: "body2" - text: "Select a package to view details" - Layout.fillWidth: true - wrapMode: Text.Wrap - } - - Item { Layout.fillHeight: true } - - CDivider { Layout.fillWidth: true } - - CButton { - text: "Rescan packages" - variant: "ghost" - Layout.fillWidth: true - onClicked: PackageLoader.scan() - } - } + CPackageDetailSidebar { + selectedPackageId: root.selectedPackageId + onRescanRequested: PackageLoader.scan() } // Package list @@ -169,73 +77,12 @@ Rectangle { model: filteredPackages() spacing: 8 clip: true - delegate: CCard { + delegate: CPackageListItem { width: parent ? parent.width : 400 - variant: "outlined" - - FlexRow { - anchors.fill: parent - anchors.margins: 16 - spacing: 16 - - // Package icon badge - Rectangle { - width: 40 - height: 40 - radius: 8 - color: modelData.installed ? Theme.primary : Theme.border - Layout.alignment: Qt.AlignVCenter - - CText { - anchors.centerIn: parent - text: modelData.icon ? modelData.icon : modelData.name.charAt(0) - variant: "subtitle1" - color: modelData.installed ? "#ffffff" : Theme.textPrimary - } - } - - ColumnLayout { - Layout.fillWidth: true - spacing: 4 - CText { variant: "subtitle1"; text: modelData.name } - CText { variant: "body2"; text: modelData.description; wrapMode: Text.Wrap } - FlexRow { - spacing: 8 - CBadge { text: "v" + modelData.version } - CBadge { - text: modelData.category ? modelData.category : "—" - visible: modelData.category !== undefined - } - CBadge { - text: "Level " + (modelData.level ? modelData.level : 2) - } - } - } - - ColumnLayout { - spacing: 6 - CButton { - text: modelData.installed ? "Installed" : "Install" - variant: modelData.installed ? "default" : "primary" - enabled: !modelData.installed - size: "sm" - onClicked: PackageLoader.install(modelData.packageId) - } - CButton { - text: "Uninstall" - variant: "danger" - size: "sm" - enabled: modelData.installed - onClicked: PackageLoader.uninstall(modelData.packageId) - } - CButton { - text: "Details" - variant: "ghost" - size: "sm" - onClicked: selectedPackageId = modelData.packageId - } - } - } + packageData: modelData + onInstallRequested: PackageLoader.install(modelData.packageId) + onUninstallRequested: PackageLoader.uninstall(modelData.packageId) + onDetailsRequested: selectedPackageId = modelData.packageId } } }