import QtQuick import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs import Flora Dialog { id: root required property bool firstRun readonly property int stepFolder: 0 readonly property int stepSetup: 1 property int step: stepFolder property string vaultPath: "" property string setupStatus: "" signal finished() title: firstRun ? qsTr("Первое знакомство: создайте хранилище") : qsTr("Новое хранилище") modal: true width: 560 height: 480 anchors.centerIn: parent closePolicy: Dialog.NoAutoClose background: Rectangle { color: Theme.surface; border.color: Theme.borderStrong; radius: 10 } // индикатор шагов header: Rectangle { height: 48 color: Theme.bgAlt radius: 10 RowLayout { anchors.fill: parent anchors.margins: 12 spacing: 8 Repeater { model: 2 Rectangle { Layout.fillWidth: true height: 4 radius: 2 color: index <= root.step ? Theme.accent : Theme.border } } } Rectangle { anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; height: 1; color: Theme.divider } } StackLayout { id: stack anchors.fill: parent currentIndex: root.step ColumnLayout { spacing: 12 Label { text: qsTr("Выберите локальную папку — это будет ваше хранилище.\nМожно указать новую папку (будет создана) или уже существующую с файлами.") wrapMode: Text.Wrap Layout.fillWidth: true color: Theme.textSecondary } RowLayout { Layout.fillWidth: true TextField { id: pathField Layout.fillWidth: true placeholderText: qsTr("Путь к папке…") placeholderTextColor: Theme.textFaint text: root.vaultPath color: Theme.text background: Rectangle { color: Theme.bgAlt; border.color: pathField.activeFocus ? Theme.accent : Theme.border; radius: 6 } onTextChanged: root.vaultPath = text } Button { text: qsTr("Обзор…"); onClicked: folderDialog.open() } } FolderDialog { id: folderDialog title: qsTr("Выберите папку хранилища") onAccepted: { // Используем toLocalFile где возможно для корректной обработки unicode и пробелов let p = "" try { const url = selectedFolder if (url && url.toString) { // QUrl -> local file // В QML FolderDialog selectedFolder уже QUrl, пробуем toString и декодируем const s = url.toString() if (s.startsWith("file://")) { // Пытаемся через decodeURIComponent и обрезку file:// p = decodeURIComponent(s) if (p.startsWith("file://")) p = p.substring(7) // Windows: /C:/path -> C:/path if (p.length >= 3 && p[0] === "/" && p[2] === ":" && /[A-Za-z]/.test(p[1])) p = p.substring(1) // Убираем якорь/параметры если есть const hash = p.indexOf('#') if (hash >= 0) p = p.substring(0, hash) const q = p.indexOf('?') if (q >= 0) p = p.substring(0, q) } else { p = decodeURIComponent(s) } } } catch(e) { console.warn("FolderDialog parse error", e) } if (p.length === 0 && selectedFolder) { try { p = selectedFolder.toString() } catch(e) {} } root.vaultPath = p } } Rectangle { Layout.fillWidth: true visible: step1Error.text.length > 0 color: Theme.errorBg border.color: Theme.errorText radius: 6 height: step1Error.implicitHeight + 12 Label { id: step1Error anchors.fill: parent anchors.margins: 6 color: Theme.errorText wrapMode: Text.Wrap text: "" } } Item { Layout.fillHeight: true } Label { text: qsTr("Шаг 1 из 2 — выбор папки"); color: Theme.textFaint; font.pixelSize: 11 } RowLayout { Layout.fillWidth: true Item { Layout.fillWidth: true } Button { text: qsTr("Отмена"); onClicked: root.reject() } Button { text: qsTr("Далее") highlighted: true enabled: root.vaultPath.length > 0 && !busyOverlay.running onClicked: { step1Error.text = "" busyOverlay.running = true const err = vaultManager.createVault(root.vaultPath) busyOverlay.running = false if (err.length > 0) { step1Error.text = err; return } root.step = stepSetup } } } } ColumnLayout { spacing: 12 Label { text: qsTr("Хранилище готово. Внутри автоматически создан git-репозиторий.\nПри желании подключите синхронизацию:") wrapMode: Text.Wrap Layout.fillWidth: true color: Theme.textSecondary } GridLayout { columns: 2 Layout.fillWidth: true Layout.fillHeight: true columnSpacing: 8 rowSpacing: 8 Button { text: "GitHub"; Layout.fillWidth: true; onClicked: openProviderGuide(0, "GitHub") } Button { text: "GitLab"; Layout.fillWidth: true; onClicked: openProviderGuide(1, "GitLab") } Button { text: "Gitea"; Layout.fillWidth: true; onClicked: openProviderGuide(2, "Gitea") } Button { text: "Nextcloud (WebDAV)"; Layout.fillWidth: true; onClicked: openProviderGuide(3, "Nextcloud (WebDAV)") } Button { text: "Syncthing"; Layout.fillWidth: true; Layout.columnSpan: 2; onClicked: openProviderGuide(4, "Syncthing") } function openProviderGuide(idx, name) { providerGuideForButtons.providerIndex = idx providerGuideForButtons.providerName = name providerGuideForButtons.connected = false providerGuideForButtons.open() } } RowLayout { Layout.fillWidth: true Button { text: qsTr("Назад"); onClicked: root.step = stepFolder } Item { Layout.fillWidth: true } Button { text: qsTr("Готово") highlighted: true onClicked: { vaultManager.selectVault(root.vaultPath); root.accept(); root.finished() } } } } } BusyIndicator { id: busyOverlay; anchors.centerIn: parent; running: false; visible: running } ProviderSetupGuide { id: providerGuideForButtons } onRejected: { if (firstRun) visible = false } }