Files
Flora/qml/MainWindow.qml
T
grendervill 417d0273b7 Инкрементальный индекс, пресеты автосейва, диалог Настройки
- VaultIndex: updateFile/removeFile/syncIncremental (mtime+size), полный рескан только на старте/смене/внешних изменениях; relink висячих [[ссылок]] при создании файла; FileStore.externalChanged для внешних правок
- QML: сейвы и правки идут точечно, дерево — через syncIncremental
- Настройки: пресеты safe/balanced/eco/manual (диск 2/5/10с/выкл, коммит после паузы 5/15/60с/вручную), диалог с +/-, шкалами и подсказкой; коммит только в простое, в manual — по Ctrl+S
- Удалены highlightedCode, словный merge, fileDiffText/Html, 2-arg fileHistory; renderInner без побочек
- Тесты: инкремент индекса, пресеты, autosaveEnabled, ~~~-блоки
2026-09-06 23:02:05 +03:00

326 lines
11 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Flora
ApplicationWindow {
id: window
width: 1280
height: 800
minimumWidth: 900
minimumHeight: 600
visible: true
title: "Flora"
color: Theme.bg
palette.window: Theme.bg
palette.windowText: Theme.text
palette.base: Theme.surface
palette.text: Theme.text
palette.highlight: Theme.accent
palette.highlightedText: "white"
property string windowVault: ""
readonly property string activeVault: {
if (fileStore.ready) return fileStore.vault
if (windowVault.length > 0) return windowVault
return vaultManager.currentVault
}
WelcomeWizard {
id: welcomeWizard
firstRun: vaultManager.vaults.length === 0
onFinished: {
if (fileStore.ready) {
windowController.openWindow(welcomeWizard.vaultPath)
} else {
fileStore.openVault(welcomeWizard.vaultPath)
root_status("Хранилище открыто")
}
}
}
function openWizard() {
welcomeWizard.firstRun = false
welcomeWizard.open()
}
function root_status(msg, ok) {
// статус можно показать в UI, пока просто игнорируем
}
function applyAutosavePreset() {
if (settingsStore.autosaveEnabled) {
documentManager.setAutosaveIntervalMs(settingsStore.autosaveDiskMs)
documentManager.setAutosaveEnabled(true)
} else {
documentManager.setAutosaveEnabled(false)
}
}
Component.onCompleted: {
if (windowVault.length > 0) {
fileStore.openVault(windowVault)
} else if (vaultManager.currentVault.length > 0) {
fileStore.openVault(vaultManager.currentVault)
}
if (vaultManager.vaults.length === 0) {
welcomeWizard.open()
}
applyAutosavePreset()
}
Connections {
target: settingsStore
function onAutosavePresetChanged() { applyAutosavePreset() }
}
menuBar: MenuBar {
// Переопределяем цвета под тему (Basic стиль не красит сам)
background: Rectangle { color: Theme.bgAlt; border.color: Theme.divider }
Menu {
title: qsTr("&Файл")
Action { text: qsTr("Добавить хранилище…"); onTriggered: window.openWizard() }
Menu {
title: qsTr("Экспорт")
Action { text: qsTr("В HTML…"); enabled: false }
Action { text: qsTr("В PDF…"); enabled: false }
}
MenuSeparator { }
Action { text: qsTr("Настройки…"); onTriggered: settingsDialog.open() }
Action { text: qsTr("Выход"); onTriggered: window.close() }
}
Menu {
title: qsTr("&Правка")
Action { text: qsTr("Отменить"); shortcut: "Ctrl+Z"; enabled: false }
Action { text: qsTr("Повторить"); shortcut: "Ctrl+Shift+Z"; enabled: false }
MenuSeparator { }
Action {
text: qsTr("История файла…")
shortcut: "Ctrl+H"
enabled: documentManager.activeIndex >= 0 && fileStore.ready
onTriggered: {
const p = documentManager.pathAt(documentManager.activeIndex)
if (p.length>0) {
historyDialog.vaultPath = fileStore.vault
historyDialog.filePath = p
historyDialog.open()
}
}
}
Action {
text: qsTr("Вернуться к предыдущей версии…")
enabled: documentManager.activeIndex >= 0 && fileStore.ready
onTriggered: {
const p = documentManager.pathAt(documentManager.activeIndex)
if (p.length>0) {
historyDialog.vaultPath = fileStore.vault
historyDialog.filePath = p
historyDialog.open()
}
}
}
}
Menu {
title: qsTr("&Вид")
ActionGroup { id: themeGroup }
Action {
text: qsTr("Светлая тема")
checkable: true
checked: settingsStore.theme !== "dark"
ActionGroup.group: themeGroup
onTriggered: settingsStore.theme = "light"
}
Action {
text: qsTr("Тёмная тема")
checkable: true
checked: settingsStore.theme === "dark"
ActionGroup.group: themeGroup
onTriggered: settingsStore.theme = "dark"
}
MenuSeparator { }
Action {
text: qsTr("Сбросить ширины панелей")
onTriggered: {
settingsStore.leftPanelWidth = 220
settingsStore.rightPanelWidth = 220
leftPanel.width = 220
rightPanel.width = 220
}
}
}
Menu {
title: qsTr("&Справка")
Action {
text: qsTr("О программе")
onTriggered: aboutDialog.open()
}
}
}
Dialog {
id: aboutDialog
title: qsTr("О программе")
standardButtons: Dialog.Ok
modal: true
anchors.centerIn: parent
background: Rectangle { color: Theme.surface; border.color: Theme.border; radius: 8 }
Label {
color: Theme.text
text: "Flora\nМинималистичный markdown-заметочник (GPL-3.0)\n"
+ "Версия " + Qt.application.version
}
}
HistoryDialog {
id: historyDialog
onRestored: {
// после восстановления обновляем граф и статус
if (fileStore.ready) vaultIndex.rebuild(fileStore.vault)
}
}
SettingsDialog {
id: settingsDialog
}
Popup {
id: vaultSwitcher
x: 12
y: window.height - height - 12
width: 340
modal: false
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
background: Rectangle { color: Theme.surface; border.color: Theme.borderStrong; radius: 8 }
ColumnLayout {
width: parent.width
spacing: 8
Label {
text: qsTr("Хранилища")
font.bold: true
color: Theme.text
}
ListView {
id: vaultList
Layout.preferredHeight: Math.min(200, contentHeight)
Layout.fillWidth: true
model: vaultManager.vaults
clip: true
ScrollBar.vertical: ScrollBar {}
delegate: ItemDelegate {
width: ListView.view.width
text: modelData
highlighted: modelData === window.activeVault
palette.highlight: Theme.accent
onClicked: {
if (modelData === window.activeVault) {
vaultSwitcher.close()
} else {
vaultManager.selectVault(modelData)
windowController.openWindow(modelData)
vaultSwitcher.close()
}
}
}
}
Label {
visible: vaultList.count === 0
text: qsTr("Хранилищ пока нет")
color: Theme.textMuted
}
Label {
text: qsTr("Текущее: %1").arg(window.activeVault.length > 0
? window.activeVault : "—")
color: Theme.textSecondary
elide: Text.ElideMiddle
Layout.fillWidth: true
font.pixelSize: 11
}
Button {
Layout.fillWidth: true
text: qsTr("Добавить новое хранилище…")
onClicked: { vaultSwitcher.close(); window.openWizard() }
}
}
}
SplitView {
id: mainSplit
anchors.fill: parent
orientation: Qt.Horizontal
// Увеличенная зона захвата — работает по всей высоте
handle: Rectangle {
implicitWidth: 12
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: 1; height: parent.height
color: Theme.divider
}
Rectangle {
anchors.fill: parent
color: "transparent"
MouseArea {
anchors.fill: parent
cursorShape: Qt.SplitHCursor
hoverEnabled: true
onPressed: mouse.accepted = false
}
}
}
FileTreePanel {
id: leftPanel
SplitView.preferredWidth: settingsStore.leftPanelWidth
SplitView.minimumWidth: 180
SplitView.maximumWidth: 500
onWidthChanged: {
if (width >= 180 && width <= 500)
settingsStore.leftPanelWidth = width
}
onOpenVaultRequested: vaultSwitcher.open()
onOpenFileRequested: (path) => {
if (documentManager.isTextForEditor(path)) {
documentManager.openDocument(path)
} else {
leftPanel.showStatus(qsTr("Предпросмотр HTML/PDF/изображений появится в Фазе 6-10"), false)
}
}
onHistoryRequested: (path) => {
historyDialog.vaultPath = fileStore.vault
historyDialog.filePath = path
historyDialog.open()
}
}
EditorPanel {
id: centerPanel
SplitView.fillWidth: true
SplitView.minimumWidth: 300
height: window.height - (menuBar ? menuBar.height : 0)
onHistoryRequested: (path) => {
historyDialog.vaultPath = fileStore.vault
historyDialog.filePath = path
historyDialog.open()
}
}
RightPanel {
id: rightPanel
SplitView.preferredWidth: settingsStore.rightPanelWidth
SplitView.minimumWidth: 200
SplitView.maximumWidth: 500
onWidthChanged: {
if (width >= 200 && width <= 500)
settingsStore.rightPanelWidth = width
}
}
}
}