diff --git a/DarkCombo.qml b/DarkCombo.qml index ccf467c..153d9c5 100644 --- a/DarkCombo.qml +++ b/DarkCombo.qml @@ -1,12 +1,9 @@ import QtQuick import QtQuick.Controls 2.15 -// Комбобокс в общем тёмном стиле окна. ComboBox { id: darkCombo - // Та же стрелка, что у комбобоксов профиля и версии на главном окне, - // вместо двойного шеврона Basic-стиля. indicator: Image { width: 10; height: 10 x: darkCombo.width - width - 12 diff --git a/JavaPickerDialog.qml b/JavaPickerDialog.qml index 516be97..48d6cb6 100644 --- a/JavaPickerDialog.qml +++ b/JavaPickerDialog.qml @@ -4,22 +4,16 @@ import QtQuick import QtQuick.Controls 2.15 import QtQuick.Layouts 2.15 -// Выбор сборки Java отдельным окном: слева типы, справа сами версии с поиском. -// Устроено так же, как выбор версии Minecraft, но выбранное здесь ещё и -// качается — в отличие от версий игры, где загрузку начинает сама сборка. Dialog { id: picker required property var backend - // Сборка, с которой окно открылось: по «Отмене» выбор к ней и возвращается. property string selectedId: "" property string category: "java" property string filterText: "" property bool installedOnly: false - // Java, ниже которой выбранной версии игры не запуститься. 0 — версия игры - // не выбрана, предупреждать не о чем. property int requiredMajor: 0 signal runtimeChosen(string runtimeId) @@ -43,8 +37,6 @@ Dialog { { key: "jre", title: Loc.t.common.java.kind.jre, hint: Loc.t.java.filter.temurinJre } ] - // Каталог приходит из C++ уже отсортированным (новые сверху, скачанные - // первыми), поэтому здесь только отбор — порядок не трогаем. readonly property var visibleEntries: { const query = picker.filterText.trim().toLowerCase() const source = picker.backend.javaCatalog @@ -107,7 +99,6 @@ Dialog { runtimeList.positionViewAtIndex(index, ListView.Center) } - // «Выбрать» и скачивает: сборка, которой нет на диске, запуску не поможет. function acceptSelection() { const entry = picker.selectedEntry if (!entry) @@ -138,7 +129,6 @@ Dialog { contentItem: RowLayout { spacing: 0 - // ── Типы сборок ───────────────────────────────────────────────── Rectangle { Layout.preferredWidth: 170 Layout.fillHeight: true @@ -195,8 +185,6 @@ Dialog { Item { width: 1; height: 8 } - // Требование выбранной сборки — чтобы не пришлось держать в - // голове, какая Java нужна какой версии игры. Text { width: parent.width - 4 visible: picker.requiredMajor > 0 @@ -215,7 +203,6 @@ Dialog { color: "#333333" } - // ── Версии ────────────────────────────────────────────────────── Item { Layout.fillWidth: true Layout.fillHeight: true @@ -317,8 +304,7 @@ Dialog { required property int index readonly property bool chosen: picker.selectedId === runtimeRow.modelData.id - // Сборка ниже требования игры выбирается, но помечается: она - // может пригодиться для другой сборки. + readonly property bool tooOld: picker.requiredMajor > 0 && runtimeRow.modelData.major < picker.requiredMajor @@ -353,8 +339,6 @@ Dialog { } Text { - // Колонка растянута якорями, поэтому её ширина - // задана, а не выведена из детей — цикла тут нет. width: parent.width elide: Text.ElideRight color: runtimeRow.chosen ? "#1e1e1e" @@ -388,8 +372,6 @@ Dialog { anchors.verticalCenter: parent.verticalCenter } - // Сборки весят по двести мегабайт — удалять их нужно - // прямо здесь, иначе папка лаунчера растёт молча. Image { visible: runtimeRow.modelData.installed source: "images/Trash.svg" @@ -430,7 +412,6 @@ Dialog { } } - // Пустая категория, ничего не нашлось или каталог ещё не пришёл. Text { anchors.centerIn: runtimeList width: runtimeList.width - 20 diff --git a/LabelledField.qml b/LabelledField.qml index cbf27e1..4470fa1 100644 --- a/LabelledField.qml +++ b/LabelledField.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Controls 2.15 -// Подпись + поле ввода одной колонкой — используется в настройках. Column { id: labelled @@ -10,7 +9,6 @@ Column { property string label: "" property string placeholder: "" - // Правка закончена: поле потеряло фокус или пользователь нажал Enter. signal editingFinished() spacing: 3 diff --git a/LoaderRow.qml b/LoaderRow.qml index 92721e5..0fd1588 100644 --- a/LoaderRow.qml +++ b/LoaderRow.qml @@ -1,13 +1,6 @@ import QtQuick import QtQuick.Controls 2.15 -// Один модлоадер в карточке сборки: чекбокс и, когда он отмечен, список версий -// именно под выбранную версию Minecraft. -// -// Совместимость проверять не нужно: backend.loaderVersions() отдаёт список уже -// под конкретную версию игры, поэтому несовместимой строки в нём не бывает. -// Пустой список — это «лоадер не поддерживает эту версию игры», и он тоже -// показывается словами, а не пустым выпадающим списком. Column { id: loaderRow @@ -15,21 +8,13 @@ Column { required property string loaderKey required property string title - // Версия Minecraft, выбранная в карточке. Её смена сбрасывает выбор. property string gameVersion: "" - property alias checked: box.checked property string selectedVersion: "" property var entries: [] - - // Пользователь щёлкнул чекбокс — карточка снимет отметки с остальных - // лоадеров: одновременно в .minecraft может жить только один. signal userChecked() - // Отметка или версия изменились — карточке пора сохранить сборку. signal changed() - // Ставится, пока карточка заполняет строку данными сборки: в это время - // обработчики не должны считать изменения пользовательскими. property bool applying: false spacing: 4 @@ -42,16 +27,9 @@ Column { return -1 } - // Единственное место, где меняются entries и selectedVersion. Через него - // проходят все три пути — кэш, ответ сети и заполнение из сборки, — потому - // что выбранная версия обязана существовать в списке под текущую версию - // игры. Раньше проверял только путь из сети, и версия лоадера от прошлой - // версии игры доживала до сохранения сборки. function applyEntries(list) { loaderRow.entries = list - // Список отсортирован новыми вперёд, поэтому максимально доступная - // версия для этой версии игры — всегда первая строка. if (loaderRow.versionIndex(loaderRow.selectedVersion) < 0) { const fallback = list.length > 0 ? list[0].version : "" const changedValue = loaderRow.selectedVersion !== fallback @@ -68,26 +46,20 @@ Column { loaderRow.applyEntries([]) return } - // Сначала из кэша — список появляется мгновенно, — потом обновление сети. loaderRow.applyEntries(loaderRow.backend.loaderVersions(loaderRow.loaderKey, loaderRow.gameVersion)) loaderRow.backend.refreshLoaderVersions(loaderRow.loaderKey, loaderRow.gameVersion) } - // Заполнение из сохранённой сборки: без сигналов changed(). function applyBuild(loader, loaderVersion) { loaderRow.applying = true box.checked = loader === loaderRow.loaderKey - // Версию из сборки берём как пожелание: если под выбранную версию игры - // её нет, applyEntries() заменит её на максимально доступную. loaderRow.selectedVersion = box.checked ? loaderVersion : "" loaderRow.reload() loaderRow.applying = false } onGameVersionChanged: { - // Сборка лоадера привязана к версии игры: под новой версией прежний - // выбор недействителен, и список перезапрашивается заново. loaderRow.selectedVersion = "" loaderRow.reload() } diff --git a/MicrosoftLoginDialog.qml b/MicrosoftLoginDialog.qml index 667f47d..b96cf00 100644 --- a/MicrosoftLoginDialog.qml +++ b/MicrosoftLoginDialog.qml @@ -4,19 +4,12 @@ import QtQuick import QtQuick.Controls 2.15 import QtWebEngine -// Вход в аккаунт Microsoft — то же самое, что делает официальный лаунчер: -// страница входа показывается внутри приложения, а лаунчер ждёт, когда браузер -// уйдёт на redirect_uri с кодом авторизации в адресе. Dialog { id: msLogin required property var backend - // Окно ничего не знает про тосты главного окна: о неудаче сообщает сигналом. signal failed(string message) - - // Код уже отдан backend — второй раз по тому же адресу не реагируем: - // WebEngineView успевает сообщить об urlChanged несколько раз. property bool codeTaken: false anchors.centerIn: parent @@ -32,8 +25,6 @@ Dialog { open() } - // Разбор адреса живёт в C++: правила совпадения должны совпадать с теми, - // по которым сервис строит сам redirect_uri. function handleUrl(url) { if (codeTaken) return @@ -76,9 +67,6 @@ Dialog { } contentItem: Item { - // Профиль без storageName — значит без диска: куки живут только пока - // работает лаунчер и в общий браузер не попадают. За выбор аккаунта в - // пределах сессии отвечает prompt=select_account в адресе входа. WebEngineProfilePrototype { id: msProfile } diff --git a/ProgressPanel.qml b/ProgressPanel.qml index 338e2d3..5346721 100644 --- a/ProgressPanel.qml +++ b/ProgressPanel.qml @@ -1,16 +1,12 @@ import QtQuick import QtQuick.Controls 2.15 -// Плашка хода долгой операции в левом нижнем углу: не задевает кнопку запуска, -// сообщение по центру и кнопки папки с настройками. Rectangle { id: panel property string title: "" property string status: "" - // -1 — итог ещё неизвестен, показываем «…» вместо процентов. property double fraction: -1 - // Необязательная вторая строка: мегабайты у загрузки, путь у архивации. property string detail: "" property bool cancellable: true diff --git a/SeasonalBuildsDialog.qml b/SeasonalBuildsDialog.qml index a22346b..4d70ede 100644 --- a/SeasonalBuildsDialog.qml +++ b/SeasonalBuildsDialog.qml @@ -3,23 +3,11 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls 2.15 -// Готовые сезонные сборки с сервера лаунчера: таблица со всем, что нужно -// решить, ставить её или нет, и одна кнопка, которая делает всё остальное — -// заводит сборку, ставит версию игры, модлоадер, Java и раскладывает файлы. -// -// Таблица собрана из Row фиксированных колонок, а не из TableView: в проекте -// нет ни одной модели QAbstractItemModel, а строки приходят готовыми -// QVariantMap — заводить ради семи колонок отдельную модель незачем. Dialog { id: seasons required property var backend - - // Выбранная строка. Хранится по id, а не по индексу: список обновляется - // под руками, и индекс после обновления указывал бы на другую сборку. property string selectedId: "" - - // Ширины колонок в одном месте: их повторяют и шапка, и делегат. readonly property var columns: [ { key: "name", title: Loc.t.common.header.name, width: 250, align: Text.AlignLeft }, { key: "minecraftVersion", title: Loc.t.common.header.version, width: 90, align: Text.AlignLeft }, @@ -34,8 +22,6 @@ Dialog { padding: 0 width: 960 height: 560 - // Пока идёт установка, окно закрывается только кнопкой: случайный щелчок - // мимо не должен спрятать единственную видимую отмену. closePolicy: seasons.backend.seasonalInstalling ? Popup.NoAutoClose : (Popup.CloseOnEscape | Popup.CloseOnPressOutside) @@ -47,8 +33,6 @@ Dialog { border.width: 1 } - // Строки приходят из C++ уже отсортированными и сведёнными с локальными - // записями — здесь только показ. readonly property var entries: seasons.backend.seasonalCatalog readonly property var selectedEntry: { @@ -79,7 +63,6 @@ Dialog { return String(entry[key]) } - // Свежий кэш отвечает без сети, поэтому дёргаем при каждом открытии. onAboutToShow: seasons.backend.refreshSeasonalCatalog(false) header: Item { @@ -101,7 +84,6 @@ Dialog { contentItem: Item { - // ── Шапка таблицы ─────────────────────────────────────────────── Row { id: tableHeader anchors.top: parent.top @@ -139,9 +121,6 @@ Dialog { color: "#333333" } - // ── Сообщение вместо таблицы ──────────────────────────────────── - // Пустой список и ошибка выглядят одинаково пустыми, поэтому причина - // показывается прямо на месте строк. Text { anchors.centerIn: parent width: parent.width - 80 @@ -157,7 +136,6 @@ Dialog { : seasons.backend.seasonalCatalogError) } - // ── Строки ────────────────────────────────────────────────────── ListView { id: buildList anchors.top: headerLine.bottom @@ -243,8 +221,6 @@ Dialog { color: "#333333" } - // Описание и размер выбранной сборки: они длинные и в таблицу не - // помещаются, а решение принимается по ним. Column { anchors.left: parent.left anchors.leftMargin: 18 @@ -314,8 +290,6 @@ Dialog { id: installButton width: 150 height: 36 - // Установка занимает и панель загрузки, и .minecraft целиком: - // пока лаунчер занят чем угодно, вторую начинать нельзя. enabled: seasons.selectedEntry !== null && !seasons.backend.busy contentItem: Text { text: seasons.selectedEntry && seasons.selectedEntry.updateAvailable diff --git a/VersionPickerDialog.qml b/VersionPickerDialog.qml index 7834ac8..b440090 100644 --- a/VersionPickerDialog.qml +++ b/VersionPickerDialog.qml @@ -4,25 +4,16 @@ import QtQuick import QtQuick.Controls 2.15 import QtQuick.Layouts 2.15 -// Выбор версии Minecraft отдельным окном: слева категории, справа сами версии -// с поиском сверху. -// -// Раньше это был выпадающий список на всю тысячу версий: найти в нём бету 1.7 -// можно было только поиском по точному номеру. Категории делят список на -// обозримые части, а поиск работает внутри выбранной. Dialog { id: picker required property var backend - // Версия, с которой окно открылось: по «Отмене» выбор к ней и возвращается. property string selectedId: "" property string category: "release" property string filterText: "" property bool installedOnly: false - // Выбранная версия подтверждена кнопкой или двойным щелчком. Имя не - // accepted(): такой сигнал у Dialog уже есть, и переопределить его нельзя. signal versionChosen(string versionId) modal: true @@ -46,8 +37,6 @@ Dialog { { key: "other", title: Loc.t.version.filter.other } ] - // Каталог приходит из C++ уже отсортированным (новые сверху), поэтому здесь - // только отбор — порядок не трогаем. readonly property var visibleEntries: { const query = picker.filterText.trim().toLowerCase() const source = picker.backend.versionCatalog @@ -69,7 +58,6 @@ Dialog { picker.selectedId = versionId picker.filterText = "" searchField.text = "" - // Открываемся на категории текущей версии, а не всегда на релизах. picker.category = picker.categoryOf(versionId) picker.backend.refreshVersionCatalog() picker.open() @@ -117,9 +105,6 @@ Dialog { return false } - // Спрашиваем до удаления: версию придётся качать заново, а профили - // модлоадеров поверх неё — переустанавливать. Id запоминаем здесь: - // к моменту ответа строка под курсором может быть уже другой. function askRemove(versionId) { const info = picker.backend.versionRemovalInfo(versionId) if (!info || info.installed !== true) @@ -133,8 +118,6 @@ Dialog { function performRemove(versionId) { picker.backend.removeVersion(versionId) - // Профиль модлоадера был в каталоге только потому, что установлен: - // после удаления строка исчезает совсем, и выбор надо снять. if (picker.selectedId === versionId && !picker.catalogHas(versionId)) picker.selectedId = "" picker.revealSelected() @@ -305,7 +288,6 @@ Dialog { contentItem: RowLayout { spacing: 0 - // ── Категории ─────────────────────────────────────────────────── Rectangle { Layout.preferredWidth: 150 Layout.fillHeight: true @@ -358,7 +340,6 @@ Dialog { color: "#333333" } - // ── Версии ────────────────────────────────────────────────────── Item { Layout.fillWidth: true Layout.fillHeight: true @@ -493,8 +474,6 @@ Dialog { anchors.verticalCenter: parent.verticalCenter } - // Версия весит от десятков мегабайт, а галочка рядом — - // единственное место, где видно, что именно скачано. Image { visible: versionRow.modelData.installed source: "images/Trash.svg" @@ -531,7 +510,6 @@ Dialog { } } - // Пустая категория, ничего не нашлось или манифест ещё не пришёл. Text { anchors.centerIn: versionList width: versionList.width - 20 diff --git a/i18n/translations.json b/i18n/translations.json index c78f951..b14149e 100644 --- a/i18n/translations.json +++ b/i18n/translations.json @@ -40,7 +40,7 @@ "en": "Open mods folder" }, "app.tooltip.seasonal": { - "ru": "Сезонные сборки от команды Мельника", + "ru": "Сезонные сборки от команды Galeon'а", "en": "Seasonal builds from Galeon team" }, "app.tooltip.settings": { @@ -156,8 +156,8 @@ "en": "The Microsoft account owns the game but has no game profile yet. Sign in to the official launcher once and pick a nickname." }, "auth.msa.error.noWebEngine": { - "ru": "Эта сборка лаунчера собрана без Qt WebEngine — показать окно входа Microsoft нечем", - "en": "This launcher build was made without Qt WebEngine — there is nothing to show the Microsoft sign-in window with" + "ru": "Эта сборка лаунчера собрана без Qt WebEngine", + "en": "This launcher build was made without Qt WebEngine" }, "auth.msa.error.noXboxProfile": { "ru": "К аккаунту Microsoft не привязан профиль Xbox. Создайте его на xbox.com и повторите вход.", @@ -196,8 +196,8 @@ "en": "The Microsoft session has expired — sign in to the account again (%1)" }, "auth.msa.error.windowUnavailable": { - "ru": "Окно входа Microsoft недоступно: сборка без Qt WebEngine", - "en": "The Microsoft sign-in window is unavailable: this build has no Qt WebEngine" + "ru": "Окно входа Microsoft недоступно", + "en": "The Microsoft sign-in window is unavailable" }, "auth.msa.error.xboxNoToken": { "ru": "Xbox Live не вернул токен", @@ -288,8 +288,8 @@ "en": "Pick a version" }, "build.error.closeGameFilesMoving": { - "ru": "Сначала закройте игру — её файлы сейчас переносятся", - "en": "Close the game first — its files are being moved right now" + "ru": "Файлы игры в процессе переноса, сначала закройте игру", + "en": "Game files are being moved, close the game first" }, "build.error.filesIncomplete": { "ru": "Файлы сборки установлены не полностью: %1\nПовторите установку сборки.", @@ -328,8 +328,8 @@ "en": "The build was not switched: %1" }, "build.error.switchFailedDirty": { - "ru": "Сборка не переключена: %1\nСодержимое .minecraft осталось разобранным — повторите переключение.", - "en": "The build was not switched: %1\nThe contents of .minecraft are left half-applied — switch again." + "ru": "Сборка не переключена: %1\n Повторите переключение.", + "en": "The build was not switched: %1\n Switch again." }, "build.error.versionNotInstalled": { "ru": "Версия «%1» из сборки «%2» не установлена в %3", @@ -340,8 +340,8 @@ "en": "Build \"%1\" will be removed and cannot be recovered." }, "build.hint.removingLast": { - "ru": "Это последняя сборка. Содержимое .minecraft останется на месте.", - "en": "This is the last build. The contents of .minecraft will stay where they are." + "ru": "Последняя сборка, файлы из корня игры не удаляются.", + "en": "Last build. The contents of .minecraft will stay." }, "build.label.active": { "ru": "активна", @@ -360,8 +360,8 @@ "en": "Server address" }, "build.placeholder.name": { - "ru": "как её видно в списке", - "en": "as it appears in the list" + "ru": "Введите наименование", + "en": "Write a name" }, "build.placeholder.serverAddress": { "ru": "host:port (необязательно)", @@ -384,8 +384,8 @@ "en": "Build \"%1\": %2" }, "build.status.ready": { - "ru": "Всё на месте, сборку можно запускать", - "en": "Everything is in place, the build can be launched" + "ru": "Готова к запуску", + "en": "Ready to launch" }, "build.title.numbered": { "ru": "Сборка %1", @@ -396,8 +396,8 @@ "en": "Remove the build?" }, "build.warning.archiveGoesToo": { - "ru": "Вместе с ней удалится её архив: моды, конфиги и миры этой сборки восстановить будет нельзя.", - "en": "Its archive goes with it: the mods, configs and worlds of this build cannot be recovered." + "ru": "Внимание, при удалении так же очищаются пользовательские сохранённые архивы.", + "en": "Attention, after removing from the list all saved files will be deleted." }, "build.warning.archiveNotRemoved": { "ru": "Сборка «%1» удалена, но её архив в %2 стереть не удалось",