wip: database checker

This commit is contained in:
2025-09-12 00:02:28 +08:00
parent 5db2207ee0
commit 1453686de6
24 changed files with 1060 additions and 430 deletions

24
ui/qmls/App.qml Normal file
View File

@ -0,0 +1,24 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "./DatabaseChecker" as DatabaseChecker
ApplicationWindow {
visible: true
width: 800
height: 600
StackLayout {
id: stackLayout
anchors.fill: parent
currentIndex: 0
DatabaseChecker.Index {
onReady: parent.currentIndex = 1
}
AppMain {}
}
}

68
ui/qmls/AppMain.qml Normal file
View File

@ -0,0 +1,68 @@
import QtQuick
import QtQuick.Layouts
RowLayout {
id: layout
spacing: 5
ListModel {
id: navListModel
ListElement {
_id: 'home'
label: 'Overview'
qmlSource: 'Overview.qml'
}
ListElement {
_id: 'database'
label: 'Database'
qmlSource: '404.qml'
}
}
ListView {
id: navListView
Layout.preferredWidth: 200
Layout.fillHeight: true
model: navListModel
focus: true
delegate: Item {
required property int index
required property string label
width: parent.width
height: 30
MouseArea {
anchors.fill: parent
onClicked: () => {
navListView.currentIndex = index;
}
}
Text {
anchors.margins: 5
anchors.fill: parent
text: parent.label
}
}
highlight: Rectangle {
width: parent.width
height: 30
color: "#FFFF88"
y: ListView.view.currentItem.y
}
}
Loader {
Layout.preferredWidth: 500
Layout.fillWidth: true
Layout.fillHeight: true
source: navListView.currentIndex > -1 ? navListModel.get(navListView.currentIndex).qmlSource : '404.qml'
}
}

View File

@ -8,6 +8,6 @@ Label {
anchors.topMargin: 7
anchors.bottomMargin: 10
font.pointSize: 12
font.pointSize: 14
font.bold: true
}

View File

@ -0,0 +1,56 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../Components"
ColumnLayout {
id: root
signal confirm
required property url directoryUrl
required property string filename
GridLayout {
columns: 2
Label {
text: "Directory"
}
DirectorySelector {
Layout.fillWidth: true
directoryUrl: root.directoryUrl
onDirectoryUrlChanged: {
root.directoryUrl = this.directoryUrl;
}
}
Label {
text: "Filename"
}
TextField {
Layout.fillWidth: true
text: root.filename
placeholderText: 'Please enter…'
onEditingFinished: {
root.filename = this.text;
}
onAccepted: {
confirmButton.click();
}
}
}
Button {
id: confirmButton
Layout.alignment: Qt.AlignRight
text: 'Confirm'
onClicked: root.confirm()
}
}

View File

@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Dialog {
id: root
required property string databaseUrl
padding: 10
title: qsTr('Confirm Database Connection')
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
Overlay.modal: Rectangle {
color: Qt.alpha('gray', 0.2)
}
ColumnLayout {
Label {
text: 'The connection below will be saved to settings. Confirm?'
}
Pane {
Label {
id: databaseUrlLabel
text: root.databaseUrl
}
}
}
}

View File

@ -0,0 +1,34 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Dialog {
id: root
required property string errorTitle
required property string errorMessage
padding: 10
title: qsTr('Error')
standardButtons: Dialog.Ok
modal: true
Overlay.modal: Rectangle {
color: Qt.alpha('darkgray', 0.5)
}
ColumnLayout {
spacing: 5
Label {
font.pointSize: 12
font.bold: true
text: root.errorTitle
}
Label {
text: root.errorMessage
}
}
}

View File

@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Layouts
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.topMargin: 5
Layout.bottomMargin: 5
color: "lightgray"
opacity: 0.5
}

View File

@ -0,0 +1,124 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import internal.ui.vm 1.0
import "../Components"
Page {
id: root
property bool showConfirmConnectionDialog: false
signal ready
function confirm(source: string): void {
if (!source)
throw new Error("source is required");
const shouldConfirm = (source === 'continueButton' && vm.canConfirmSilently) || source === 'dialog';
if (shouldConfirm) {
vm.confirmCurrentConnection();
root.ready();
} else {
root.showConfirmConnectionDialog = true;
}
}
DatabaseInitViewModel {
id: vm
onSelectFileUrlChanged: {
selectOrCreate.selectFileUrl = this.selectFileUrl;
}
onDirectoryUrlChanged: {
selectOrCreate.directoryUrl = this.directoryUrl;
}
onFilenameChanged: {
selectOrCreate.filename = this.filename;
}
onDatabaseUrlChanged: {
confirmConnectionDialog.databaseUrl = this.databaseUrl;
}
onDatabaseInfoChanged: {
databaseInfo.info = this.databaseInfo;
}
onCanContinueChanged: {
continueButton.enabled = this.canContinue;
}
}
Page {
padding: 10
anchors.fill: parent
Dialog_ConfirmConnection {
id: confirmConnectionDialog
anchors.centerIn: parent
visible: root.showConfirmConnectionDialog
onAccepted: root.confirm('dialog')
onClosed: root.showConfirmConnectionDialog = false
databaseUrl: vm.databaseUrl
}
ColumnLayout {
width: parent.width
spacing: 2
SectionTitle {
text: qsTr('Select or Create Database')
}
Section_SelectOrCreate {
id: selectOrCreate
selectFileUrl: vm.selectFileUrl
createDirectoryUrl: vm.directoryUrl
createFilename: vm.filename
onUiModeChanged: uiMode => vm.uiMode = uiMode
onSelectFileUrlChanged: {
vm.selectFileUrl = selectFileUrl;
}
onCreateDirectoryUrlChanged: {
vm.directoryUrl = createDirectoryUrl;
}
onCreateFilenameChanged: {
vm.filename = createFilename;
}
onConfirmCreate: {
vm.loadDatabaseInfo();
}
}
HorizontalDivider {}
SectionTitle {
text: 'Database Status'
}
Section_DatabaseInfo {
id: databaseInfo
info: vm.databaseInfo
}
}
footer: Pane {
padding: 5
RowLayout {
Button {
id: continueButton
Layout.alignment: Qt.AlignHCenter
text: qsTr('Continue')
enabled: vm.canContinue
onClicked: root.confirm('continueButton')
}
}
}
}
}

View File

@ -0,0 +1,138 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout {
id: root
property var info: {
'url': undefined,
'error': {
'title': undefined,
'message': undefined
},
'initialized': undefined,
'version': undefined
}
property bool showErrorDialog: false
function hasError(): bool {
return info?.error?.title !== undefined || info?.error?.message !== undefined;
}
function displayText(value): string {
return value ?? '-';
}
function displayBool(value): string {
if (value === undefined)
return '-';
// TODO: color success & error
return value ? `<font color="lightgreen">Yes</font>` : `<font color="lightpink">No</font>`;
}
component LabelLabel: Label {
Layout.alignment: Qt.AlignRight | Qt.AlignBaseline
font.pointSize: 10
}
SystemPalette {
id: palette
}
Dialog_Error {
parent: Overlay.overlay
anchors.centerIn: parent
visible: root.hasError() && root.showErrorDialog
errorTitle: root.displayText(root.info?.error?.title)
errorMessage: root.displayText(root.info?.error?.message)
onClosed: root.showErrorDialog = false
}
Pane {
clip: true
background: Rectangle {
color: Qt.darker(Qt.alpha(palette.window, 0.9), 0.2)
}
// Layout.preferredHeight: root.hasError() ? this.implicitHeight : 0
Layout.preferredHeight: 0
Behavior on Layout.preferredHeight {
PropertyAnimation {
duration: 300
easing.type: Easing.InOutCubic
}
}
ColumnLayout {
anchors.fill: parent
Label {
font.bold: true
text: root.displayText(root.info.error?.title)
}
Label {
text: root.displayText(root.info.error?.message)
}
}
}
GridLayout {
columns: 2
columnSpacing: 10
LabelLabel {
text: 'Connection'
}
Label {
text: root.info.url
}
LabelLabel {
text: 'Initialized'
}
Label {
text: root.displayBool(root.info?.initialized)
}
LabelLabel {
text: 'Version'
}
Label {
text: root.displayText(root.info?.version)
}
Column {
Layout.alignment: Qt.AlignRight | Qt.AlignBaseline
LabelLabel {
text: 'Error'
}
ToolButton {
Layout.preferredWidth: root.hasError() ? this.implicitWidth : 0
Behavior on Layout.preferredWidth {
PropertyAnimation {
duration: 300
easing.type: Easing.InOutCubic
}
}
text: '[?]'
onClicked: root.showErrorDialog = true
}
}
Label {
Layout.alignment: Qt.AlignBaseline
text: root.displayText(root.info?.error?.title)
}
}
}

View File

@ -0,0 +1,69 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../Components"
ColumnLayout {
id: root
property alias selectFileUrl: fileSelector.url
property alias createDirectoryUrl: fileCreator.directoryUrl
property alias createFilename: fileCreator.filename
signal uiModeChanged(string value)
signal confirmCreate
ListModel {
id: uiModeModel
ListElement {
value: 'select'
}
ListElement {
value: 'create'
}
}
TabBar {
id: tabBar
Layout.fillWidth: true
TabButton {
text: qsTr("Select Existing")
width: implicitWidth + 10
}
TabButton {
text: qsTr("Create New File")
width: implicitWidth + 10
}
onCurrentIndexChanged: {
const idx = this.currentIndex;
root.uiModeChanged(uiModeModel.get(idx).value);
}
}
StackLayout {
currentIndex: tabBar.currentIndex
Layout.fillWidth: true
Layout.preferredHeight: children[currentIndex].height
Behavior on Layout.preferredHeight {
PropertyAnimation {
duration: 300
easing.type: Easing.InOutCubic
}
}
clip: true
FileSelector {
id: fileSelector
}
DatabaseFileCreator {
id: fileCreator
onConfirm: root.confirmCreate()
}
}
}