diff --git a/locales/en/messages.json b/locales/en/messages.json index 3ae750aa..6699fdc9 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -6857,6 +6857,10 @@ "message": "Keywords:", "description": "Hint text in the presets detailed dialog" }, + "presetsSourceRepository": { + "message": "Source:", + "description": "Text prefixing user defined name of the preset repository containing a preset" + }, "presetsVersions": { "message": "Firmware:", "description": "Hint text in the presets detailed dialog" @@ -7005,6 +7009,10 @@ "message": "Make Active", "description": "Presets tab, sources dialog, button to make selected source active" }, + "presetsSourcesDialogMakeSourceDisable": { + "message": "Make disabled", + "description": "Presets tab, sources dialog, button to make selected source disabled" + }, "presetsSourcesDialogDeleteSource": { "message": "Delete", "description": "Presets tab, sources dialog, button to delete selected source" @@ -7013,6 +7021,10 @@ "message": "WARNING! A third party preset source is selected.", "description": "Warning message that shows up when a third party preset source is selected" }, + "presetsFailedToLoadRepositories": { + "message": "WARNING! Failed to load following repositories: {{repos}}", + "description": "Warning message that shows up when we fail to load some repositories" + }, "presetsWarningBackup": { "message": "Please make sure you backup your current configuration ('$t(presetsBackupSave.message)' button or via CLI if the button is disabled) before picking and applying presets. Otherwise there is no way to return to previous configuration after applying presets.", "description": "Warning message that shows up at the top of the presets tab" diff --git a/src/tabs/presets/DetailedDialog/PresetsDetailedDialog.js b/src/tabs/presets/DetailedDialog/PresetsDetailedDialog.js index 0795b4a9..c6570960 100644 --- a/src/tabs/presets/DetailedDialog/PresetsDetailedDialog.js +++ b/src/tabs/presets/DetailedDialog/PresetsDetailedDialog.js @@ -26,9 +26,10 @@ export default class PresetsDetailedDialog { }); } - open(preset, presetsRepo) { + open(preset, presetsRepo, showPresetRepoName) { this._presetsRepo = presetsRepo; this._preset = preset; + this._showPresetRepoName = showPresetRepoName; this._setLoadingState(true); this._domDialog[0].showModal(); this._optionsShowedAtLeastOnce = false; @@ -78,8 +79,8 @@ export default class PresetsDetailedDialog { } this._titlePanel.empty(); - const titlePanel = new PresetTitlePanel(this._titlePanel, this._preset, false, - () => this._setLoadingState(false), this._favoritePresets); + const titlePanel = new PresetTitlePanel(this._titlePanel, this._preset, this._presetsRepo, false, + this._showPresetRepoName, () => this._setLoadingState(false), this._favoritePresets); titlePanel.load(); this._loadOptionsSelect(); this._updateFinalCliText(); @@ -263,7 +264,7 @@ export default class PresetsDetailedDialog { _pickPreset() { const cliStrings = this._getFinalCliText(); - const pickedPreset = new PickedPreset(this._preset, cliStrings); + const pickedPreset = new PickedPreset(this._preset, cliStrings, this._presetsRepo); this._pickedPresetList.push(pickedPreset); this._onPresetPickedCallback?.(); this._isPresetPickedOnClose = true; diff --git a/src/tabs/presets/FavoritePresets.js b/src/tabs/presets/FavoritePresets.js index b4ce87a0..4a3f62a7 100644 --- a/src/tabs/presets/FavoritePresets.js +++ b/src/tabs/presets/FavoritePresets.js @@ -75,19 +75,19 @@ class FavoritePresetsClass { this._favoritePresetsData = new FavoritePresetsData(); } - add(preset) { - const favoritePreset = this._favoritePresetsData.add(preset.fullPath); + add(preset, repo) { + const favoritePreset = this._favoritePresetsData.add(repo.getPresetOnlineLink(preset)); preset.lastPickDate = favoritePreset.lastPickDate; } - delete(preset) { - this._favoritePresetsData.delete(preset.fullPath); + delete(preset, repo) { + this._favoritePresetsData.delete(repo.getPresetOnlineLink(preset)); preset.lastPickDate = undefined; } - addLastPickDate(presets) { + addLastPickDate(presets, repo) { for (let preset of presets) { - let favoritePreset = this._favoritePresetsData.findPreset(preset.fullPath); + let favoritePreset = this._favoritePresetsData.findPreset(repo.getPresetOnlineLink(preset)); if (favoritePreset) { preset.lastPickDate = favoritePreset.lastPickDate; diff --git a/src/tabs/presets/PickedPreset.js b/src/tabs/presets/PickedPreset.js index 96b197e0..19adde35 100644 --- a/src/tabs/presets/PickedPreset.js +++ b/src/tabs/presets/PickedPreset.js @@ -1,8 +1,9 @@ export default class PickedPreset { - constructor(preset, presetCli) + constructor(preset, presetCli, presetRepo) { this.preset = preset; this.presetCli = presetCli; + this.presetRepo = presetRepo; } } diff --git a/src/tabs/presets/PresetsRepoIndexed/PresetsGithubRepo.js b/src/tabs/presets/PresetsRepoIndexed/PresetsGithubRepo.js index e3405784..4ad36bc3 100644 --- a/src/tabs/presets/PresetsRepoIndexed/PresetsGithubRepo.js +++ b/src/tabs/presets/PresetsRepoIndexed/PresetsGithubRepo.js @@ -1,7 +1,7 @@ import PresetsRepoIndexed from "./PresetsRepoIndexed"; export default class PresetsGithubRepo extends PresetsRepoIndexed { - constructor(urlRepo, branch) { + constructor(urlRepo, branch, official, name) { let correctUrlRepo = urlRepo.trim(); if (!correctUrlRepo.endsWith("/")) { @@ -21,6 +21,6 @@ export default class PresetsGithubRepo extends PresetsRepoIndexed { const urlRaw = `https://raw.githubusercontent.com${correctUrlRepo.slice("https://github.com".length)}${correctBranch}/`; const urlViewOnline = `${correctUrlRepo}blob/${correctBranch}/`; - super(urlRaw, urlViewOnline); + super(urlRaw, urlViewOnline, official, name); } } diff --git a/src/tabs/presets/PresetsRepoIndexed/PresetsRepoIndexed.js b/src/tabs/presets/PresetsRepoIndexed/PresetsRepoIndexed.js index 4455d0d8..1b0f0207 100644 --- a/src/tabs/presets/PresetsRepoIndexed/PresetsRepoIndexed.js +++ b/src/tabs/presets/PresetsRepoIndexed/PresetsRepoIndexed.js @@ -1,16 +1,26 @@ import PresetParser from "./PresetParser"; export default class PresetsRepoIndexed { - constructor(urlRaw, urlViewOnline) { + constructor(urlRaw, urlViewOnline, official, name) { this._urlRaw = urlRaw; this._urlViewOnline = urlViewOnline; this._index = null; + this._name = name; + this._official = official; } get index() { return this._index; } + get official() { + return this._official; + } + + get name() { + return this._name; + } + loadIndex() { return fetch(`${this._urlRaw}index.json`, {cache: "no-cache"}) .then(res => res.json()) diff --git a/src/tabs/presets/PresetsRepoIndexed/PresetsWebsiteRepo.js b/src/tabs/presets/PresetsRepoIndexed/PresetsWebsiteRepo.js index 28388cd6..c9d8a089 100644 --- a/src/tabs/presets/PresetsRepoIndexed/PresetsWebsiteRepo.js +++ b/src/tabs/presets/PresetsRepoIndexed/PresetsWebsiteRepo.js @@ -1,7 +1,7 @@ import PresetsRepoIndexed from "./PresetsRepoIndexed"; export default class PresetsWebsiteRepo extends PresetsRepoIndexed { - constructor(url) { + constructor(url, official, name) { let correctUrl = url.trim(); if (!correctUrl.endsWith("/")) { @@ -11,6 +11,6 @@ export default class PresetsWebsiteRepo extends PresetsRepoIndexed { const urlRaw = correctUrl; const urlViewOnline = correctUrl; - super(urlRaw, urlViewOnline); + super(urlRaw, urlViewOnline, official, name); } } diff --git a/src/tabs/presets/SourcesDialog/SourcePanel.html b/src/tabs/presets/SourcesDialog/SourcePanel.html index 72b275c8..4193097a 100644 --- a/src/tabs/presets/SourcesDialog/SourcePanel.html +++ b/src/tabs/presets/SourcesDialog/SourcePanel.html @@ -21,6 +21,7 @@
+ diff --git a/src/tabs/presets/SourcesDialog/SourcePanel.js b/src/tabs/presets/SourcesDialog/SourcePanel.js index a930b8a5..273a62d9 100644 --- a/src/tabs/presets/SourcesDialog/SourcePanel.js +++ b/src/tabs/presets/SourcesDialog/SourcePanel.js @@ -47,6 +47,12 @@ export default class SourcePanel { this._onActivateCallback = onActivateCallback; } + setOnDeactivateCallback(onDeactivateCallback) { + // callback with this (SourcePanel) argument + // so that consumer knew which panel was clicked on + this._onDeactivateCallback = onDeactivateCallback; + } + setOnSaveCallback(onSaveCallback) { // callback with this (SourcePanel) argument // so that consumer knew which panel was clicked on @@ -65,6 +71,7 @@ export default class SourcePanel { this._active = isActive; this._domDivSelectedIndicator.toggle(this._active); this._domButtonActivate.toggle(!isActive); + this._domButtonDeactivate.toggle(isActive); } _setUiOfficial() { @@ -120,6 +127,7 @@ export default class SourcePanel { this._domButtonReset.on("click", () => this._onResetButtonClick()); this._domButtonDelete.on("click", () => this._onDeleteButtonClick()); this._domButtonActivate.on("click", () => this._onActivateButtonClick()); + this._domButtonDeactivate.on("click", () => this._onDeactivateButtonClick()); this._domEditName.on("input", () => this._onInputChange()); this._domEditUrl.on("input", () => this._onInputChange()); @@ -168,6 +176,12 @@ export default class SourcePanel { this._onActivateCallback?.(this); } + _onDeactivateButtonClick() { + this._onSaveButtonClick(); + this.setActive(false); + this._onDeactivateCallback?.(this); + } + _setIsSaved(isSaved) { if (isSaved) { this._domButtonSave.addClass(GUI.buttonDisabledClass); @@ -190,6 +204,7 @@ export default class SourcePanel { this._domButtonSave = this._domWrapperDiv.find(".presets_source_panel_save"); this._domButtonReset = this._domWrapperDiv.find(".presets_source_panel_reset"); this._domButtonActivate = this._domWrapperDiv.find(".presets_source_panel_activate"); + this._domButtonDeactivate = this._domWrapperDiv.find(".presets_source_panel_deactivate"); this._domButtonDelete = this._domWrapperDiv.find(".presets_source_panel_delete"); this._domDivGithubBranch = this._domWrapperDiv.find(".presets_source_panel_editing_github_branch"); this._domDivNoEditingName = this._domWrapperDiv.find(".presets_source_panel_no_editing_name"); diff --git a/src/tabs/presets/SourcesDialog/SourcesDialog.js b/src/tabs/presets/SourcesDialog/SourcesDialog.js index bf9679d5..671b4846 100644 --- a/src/tabs/presets/SourcesDialog/SourcesDialog.js +++ b/src/tabs/presets/SourcesDialog/SourcesDialog.js @@ -9,7 +9,7 @@ export default class PresetsSourcesDialog { this._sourceSelectedPromiseResolve = null; this._sourcesPanels = []; this._sources = []; - this._activeSourceIndex = 0; + this._activeSourceIndexes = [0]; } load() { @@ -28,20 +28,20 @@ export default class PresetsSourcesDialog { return new Promise(resolve => this._sourceSelectedPromiseResolve = resolve); } - getActivePresetSource() { - return this._sources[this._activeSourceIndex]; + getActivePresetSources() { + return this._activeSourceIndexes.map(index => this._sources[index]); } - get isOfficialActive() { - return this._activeSourceIndex === 0; + get isThirdPartyActive() { + return this.getActivePresetSources().filter(source => !source.official).length > 0; } _initializeSources() { this._sources = this._readSourcesFromStorage(); - this._activeSourceIndex = this._readActiveSourceIndexFromStorage(this._sources.length); + this._activeSourceIndexes = this._readActiveSourceIndexFromStorage(this._sources.length); for (let i = 0; i < this._sources.length; i++) { - const isActive = this._activeSourceIndex === i; + const isActive = this._activeSourceIndexes.includes(i); this._addNewSourcePanel(this._sources[i], isActive, false); } } @@ -67,15 +67,8 @@ export default class PresetsSourcesDialog { } _readActiveSourceIndexFromStorage(sourcesCount) { - const obj = getConfig('PresetSourcesActiveIndex'); - const index = Number(obj.PresetSourcesActiveIndex); - let result = 0; - - if (index && Number.isInteger(index) && index < sourcesCount) { - result = index; - } - - return result; + const obj = getConfig('PresetSourcesActiveIndexes'); + return obj.PresetSourcesActiveIndexes || [0]; } _createOfficialSource() { @@ -117,6 +110,7 @@ export default class PresetsSourcesDialog { sourcePanel.setOnSelectedCallback(selectedPanel => this._onSourcePanelSelected(selectedPanel)); sourcePanel.setOnDeleteCallback(selectedPanel => this._onSourcePanelDeleted(selectedPanel)); sourcePanel.setOnActivateCallback(selectedPanel => this._onSourcePanelActivated(selectedPanel)); + sourcePanel.setOnDeactivateCallback(selectedPanel => this._onSourcePanelDeactivated(selectedPanel)); sourcePanel.setOnSaveCallback(() => this._onSourcePanelSaved()); sourcePanel.setActive(isActive); if (isSelected) { @@ -140,18 +134,18 @@ export default class PresetsSourcesDialog { _readPanels() { this._sources = []; - this._activeSourceIndex = 0; - for(let i = 0; i < this._sourcesPanels.length; i++) { + this._activeSourceIndexes = []; + for (let i = 0; i < this._sourcesPanels.length; i++) { this._sources.push(this._sourcesPanels[i].presetSource); if (this._sourcesPanels[i].active) { - this._activeSourceIndex = i; + this._activeSourceIndexes.push(i); } } } _saveSources() { setConfig({'PresetSources': this._sources}); - setConfig({'PresetSourcesActiveIndex': this._activeSourceIndex}); + setConfig({'PresetSourcesActiveIndexes': this._activeSourceIndexes}); } _updateSourcesFromPanels() { @@ -183,15 +177,22 @@ export default class PresetsSourcesDialog { _onSourcePanelActivated(selectedPanel) { for (const panel of this._sourcesPanels) { - if (panel !== selectedPanel) { - panel.setActive(false); - } else { + if (panel === selectedPanel) { panel.setActive(true); } } this._updateSourcesFromPanels(); } + _onSourcePanelDeactivated(selectedPanel) { + for (const panel of this._sourcesPanels) { + if (panel === selectedPanel) { + panel.setActive(false); + } + } + this._updateSourcesFromPanels(); + } + _readDom() { this._domButtonAddNew = $("#presets_sources_dialog_add_new"); this._domButtonClose = $("#presets_sources_dialog_close"); diff --git a/src/tabs/presets/TitlePanel/PresetTitlePanel.css b/src/tabs/presets/TitlePanel/PresetTitlePanel.css index e9481d12..65a320e8 100644 --- a/src/tabs/presets/TitlePanel/PresetTitlePanel.css +++ b/src/tabs/presets/TitlePanel/PresetTitlePanel.css @@ -20,7 +20,7 @@ overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - width: calc(100% - 30px); + width: calc(100% - 60px); } .preset_title_panel_star { @@ -37,6 +37,20 @@ top: -5px; } +.preset_title_panel_betaflight_official { + background-image: url(../../../images/icons/cf_icon_welcome_orange.svg); + width: 25px; + height: 25px; + background-size: cover; + border-radius: 5px; + padding: 5px; + background-origin: content-box; + background-repeat: no-repeat; + position: absolute; + right: 26px; + top: -5px; +} + .preset_title_panel_category { color: var(--mutedText); font-weight: bold; @@ -75,6 +89,12 @@ text-overflow: ellipsis; } +.preset_title_panel_repository_text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .presets_title_panel_table { table-layout: fixed; width: 100%; diff --git a/src/tabs/presets/TitlePanel/PresetTitlePanel.js b/src/tabs/presets/TitlePanel/PresetTitlePanel.js index 373fdcb7..a74efd72 100644 --- a/src/tabs/presets/TitlePanel/PresetTitlePanel.js +++ b/src/tabs/presets/TitlePanel/PresetTitlePanel.js @@ -2,13 +2,15 @@ import { i18n } from "../../../js/localization"; export default class PresetTitlePanel { - constructor(parentDiv, preset, clickable, onLoadedCallback, favoritePresets) + constructor(parentDiv, preset, presetRepo, clickable, showPresetRepoName, onLoadedCallback, favoritePresets) { PresetTitlePanel.s_panelCounter ++; this._parentDiv = parentDiv; this._onLoadedCallback = onLoadedCallback; this._domId = `preset_title_panel_${PresetTitlePanel.s_panelCounter}`; this._preset = preset; + this._presetRepo = presetRepo; + this._showPresetRepoName = showPresetRepoName; this._clickable = clickable; this._favoritePresets = favoritePresets; @@ -68,7 +70,7 @@ export default class PresetTitlePanel } _showPresetsDetailedDialog(presetsDetailedDialog, presetsRepo) { - presetsDetailedDialog.open(this._preset, presetsRepo).then(isPresetPicked => { + presetsDetailedDialog.open(this._preset, presetsRepo, this._showPresetRepoName).then(isPresetPicked => { if (isPresetPicked) { const color = this._domWrapperDiv.css( "background-color" ); this._domWrapperDiv.css('background-color', 'green'); @@ -103,11 +105,16 @@ export default class PresetTitlePanel this._domTitle.prop("title", this._preset.title); this._domAuthor.text(this._preset.author); this._domVersions.text(this._preset.firmware_version?.join("; ")); + this._domSourceRepository.text(this._presetRepo.name); + this._domSourceRepositoryRow.toggle(this._showPresetRepoName); + this._domKeywords.text(this._preset.keywords?.join("; ")); this._domKeywords.prop("title", this._preset.keywords?.join("; ")); this._domStatusOfficial.toggle(this._preset.status === "OFFICIAL"); this._domStatusCommunity.toggle(this._preset.status === "COMMUNITY"); this._domStatusExperimental.toggle(this._preset.status === "EXPERIMENTAL"); + this._domOfficialSourceIcon.toggle(this._presetRepo.official); + this.setPicked(this._preset.isPicked); this._setupStar(); @@ -128,10 +135,13 @@ export default class PresetTitlePanel this._domCategory = this._domWrapperDiv.find('.preset_title_panel_category'); this._domAuthor = this._domWrapperDiv.find('.preset_title_panel_author_text'); this._domKeywords = this._domWrapperDiv.find('.preset_title_panel_keywords_text'); + this._domSourceRepository = this._domWrapperDiv.find('.preset_title_panel_repository_text'); + this._domSourceRepositoryRow = this._domWrapperDiv.find('.preset_title_panel_repository_row'); this._domVersions = this._domWrapperDiv.find('.preset_title_panel_versions_text'); this._domStatusOfficial = this._domWrapperDiv.find('.preset_title_panel_status_official'); this._domStatusCommunity = this._domWrapperDiv.find('.preset_title_panel_status_community'); this._domStatusExperimental = this._domWrapperDiv.find('.preset_title_panel_status_experimental'); + this._domOfficialSourceIcon = this._domWrapperDiv.find('.preset_title_panel_betaflight_official'); } _setupStar() { @@ -145,9 +155,9 @@ export default class PresetTitlePanel _processStarClick() { if (this._preset.lastPickDate) { - this._favoritePresets.delete(this._preset); + this._favoritePresets.delete(this._preset, this._presetRepo); } else { - this._favoritePresets.add(this._preset); + this._favoritePresets.add(this._preset, this._presetRepo); } this._favoritePresets.saveToStorage(); diff --git a/src/tabs/presets/TitlePanel/PresetTitlePanelBody.html b/src/tabs/presets/TitlePanel/PresetTitlePanelBody.html index 7dd031ba..132959c3 100644 --- a/src/tabs/presets/TitlePanel/PresetTitlePanelBody.html +++ b/src/tabs/presets/TitlePanel/PresetTitlePanelBody.html @@ -1,4 +1,5 @@
+
@@ -40,6 +41,14 @@ + + + + + + + +
diff --git a/src/tabs/presets/presets.html b/src/tabs/presets/presets.html index 97c2ff9b..5b5873cf 100644 --- a/src/tabs/presets/presets.html +++ b/src/tabs/presets/presets.html @@ -17,6 +17,7 @@
+
Don't show again diff --git a/src/tabs/presets/presets.js b/src/tabs/presets/presets.js index f731bbfd..79a9d8a6 100644 --- a/src/tabs/presets/presets.js +++ b/src/tabs/presets/presets.js @@ -17,7 +17,7 @@ import PresetsSourcesDialog from './SourcesDialog/SourcesDialog'; import PresetSource from './SourcesDialog/PresetSource'; const presets = { - presetsRepo: null, + presetsRepo: [], cliEngine: null, pickedPresetList: [], majorVersion: 1, @@ -65,6 +65,7 @@ presets.readDom = function() { this._domButtonPresetSources = $(".presets_sources_show"); this._domWarningNotOfficialSource = $(".presets_warning_not_official_source"); + this._domWarningFailedToLoadRepositories = $(".presets_failed_to_load_repositories"); this._domWarningBackup = $(".presets_warning_backup"); this._domButtonHideBackupWarning = $(".presets_warning_backup_button_hide"); @@ -116,8 +117,10 @@ presets.onSaveClick = function() { }; presets.markPickedPresetsAsFavorites = function() { - for(const pickedPreset of this.pickedPresetList) { - favoritePresets.add(pickedPreset.preset); + for (const pickedPreset of this.pickedPresetList) { + if (pickedPreset.presetRepo !== undefined){ + favoritePresets.add(pickedPreset.preset, pickedPreset.presetRepo); + } } favoritePresets.saveToStorage(); @@ -134,7 +137,7 @@ presets.setupMenuButtons = function() { this._domButtonCancel.on("click", () => { - for(const pickedPreset of this.pickedPresetList) { + for (const pickedPreset of this.pickedPresetList) { pickedPreset.preset.isPicked = false; } @@ -268,7 +271,7 @@ presets.onLoadConfigClick = function() { .then(text => { if (text) { const cliStrings = text.split("\n"); - const pickedPreset = new PickedPreset({title: "user configuration"}, cliStrings); + const pickedPreset = new PickedPreset({title: "user configuration"}, cliStrings, undefined); this.pickedPresetList.push(pickedPreset); this.onSaveClick(); } @@ -316,23 +319,32 @@ presets.reload = function() { }; presets.tryLoadPresets = function() { - const presetSource = this.presetsSourcesDialog.getActivePresetSource(); + const presetSources = this.presetsSourcesDialog.getActivePresetSources(); - if (PresetSource.isUrlGithubRepo(presetSource.url)) { - this.presetsRepo = new PresetsGithubRepo(presetSource.url, presetSource.gitHubBranch); - } else { - this.presetsRepo = new PresetsWebsiteRepo(presetSource.url); - } + this.presetsRepo = presetSources.map(source => { + if (PresetSource.isUrlGithubRepo(source.url)) { + return new PresetsGithubRepo(source.url, source.gitHubBranch, source.official, source.name); + } else { + return new PresetsWebsiteRepo(source.url, source.official, source.name); + } + }); this._divMainContent.toggle(false); this._divGlobalLoadingError.toggle(false); this._divGlobalLoading.toggle(true); - this._domWarningNotOfficialSource.toggle(!this.presetsSourcesDialog.isOfficialActive); + this._domWarningNotOfficialSource.toggle(this.presetsSourcesDialog.isThirdPartyActive); - this.presetsRepo.loadIndex() - .then(() => this.checkPresetSourceVersion()) + const failedToLoad = []; + + Promise.all(this.presetsRepo.map(p => p.loadIndex().catch((reason => failedToLoad.push(p))))) .then(() => { - favoritePresets.addLastPickDate(this.presetsRepo.index.presets); + this._domWarningFailedToLoadRepositories.toggle(failedToLoad.length > 0); + this._domWarningFailedToLoadRepositories.html(i18n.getMessage("presetsFailedToLoadRepositories", {"repos": failedToLoad.map(repo => repo.name).join("; ")})); + this.presetsRepo = this.presetsRepo.filter(repo => !failedToLoad.includes(repo)); + return this.checkPresetSourceVersion(); + }) + .then(() => { + this.presetsRepo.forEach(p => favoritePresets.addLastPickDate(p.index.presets, p)); this.prepareFilterFields(); this._divGlobalLoading.toggle(false); this._divMainContent.toggle(true); @@ -365,11 +377,12 @@ presets.checkPresetSourceVersion = function() { const self = this; return new Promise((resolve, reject) => { - if (self.majorVersion === self.presetsRepo.index.majorVersion) { + const differentMajorVersionsRepos = self.presetsRepo.filter(pr => self.majorVersion !== pr.index.majorVersion); + if (differentMajorVersionsRepos.length === 0) { resolve(); } else { const versionRequired = `${self.majorVersion}.X`; - const versionSource = `${self.presetsRepo.index.majorVersion}.${self.presetsRepo.index.minorVersion}`; + const versionSource = `${differentMajorVersionsRepos[0].index.majorVersion}.${differentMajorVersionsRepos[0].index.minorVersion}`; const dialogSettings = { title: i18n.getMessage("presetsWarningDialogTitle"), @@ -377,7 +390,7 @@ presets.checkPresetSourceVersion = function() { buttonYesText: i18n.getMessage("yes"), buttonNoText: i18n.getMessage("no"), buttonYesCallback: () => resolve(), - buttonNoCallback: () => reject("Prset source version mismatch"), + buttonNoCallback: () => reject("Preset source version mismatch"), }; GUI.showYesNoDialog(dialogSettings); @@ -385,13 +398,21 @@ presets.checkPresetSourceVersion = function() { }); }; +function getUniqueValues(objects, extractor) { + let values = objects.map(extractor); + let uniqueValues = [...values.reduce((a, b) => new Set([...a, ...b]), new Set())]; + return uniqueValues.sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'})); +} + presets.prepareFilterFields = function() { this._freezeSearch = true; - this.prepareFilterSelectField(this._selectCategory, this.presetsRepo.index.uniqueValues.category, 3); - this.prepareFilterSelectField(this._selectKeyword, this.presetsRepo.index.uniqueValues.keywords, 3); - this.prepareFilterSelectField(this._selectAuthor, this.presetsRepo.index.uniqueValues.author, 1); - this.prepareFilterSelectField(this._selectFirmwareVersion, this.presetsRepo.index.uniqueValues.firmware_version, 2); - this.prepareFilterSelectField(this._selectStatus, this.presetsRepo.index.settings.PresetStatusEnum, 2); + + this.prepareFilterSelectField(this._selectCategory, getUniqueValues(this.presetsRepo, x => x.index.uniqueValues.category), 3); + this.prepareFilterSelectField(this._selectKeyword, getUniqueValues(this.presetsRepo, x => x.index.uniqueValues.keywords), 3); + this.prepareFilterSelectField(this._selectAuthor, getUniqueValues(this.presetsRepo, x => x.index.uniqueValues.author), 1); + this.prepareFilterSelectField(this._selectFirmwareVersion, getUniqueValues(this.presetsRepo, x => x.index.uniqueValues.firmware_version), 2); + this.prepareFilterSelectField(this._selectStatus, getUniqueValues(this.presetsRepo, x => x.index.settings.PresetStatusEnum), 2); + this.multipleSelectComponentScrollFix().then(() => { this.preselectFilterFields(); this._inputTextFilter.on('input', () => this.updateSearchResults()); @@ -404,9 +425,11 @@ presets.preselectFilterFields = function() { const currentVersion = FC.CONFIG.flightControllerVersion; const selectedVersions = []; - for(const bfVersion of this.presetsRepo.index.uniqueValues.firmware_version) { - if (currentVersion.startsWith(bfVersion)) { - selectedVersions.push(bfVersion); + for (const repo of this.presetsRepo) { + for (const bfVersion of repo.index.uniqueValues.firmware_version) { + if (currentVersion.startsWith(bfVersion)) { + selectedVersions.push(bfVersion); + } } } @@ -474,25 +497,29 @@ presets.displayPresets = function(fitPresets) { this._domListNoFound.toggle(fitPresets.length === 0); fitPresets.forEach(preset => { - const presetPanel = new PresetTitlePanel(this._divPresetList, preset, true, undefined, favoritePresets); + const presetPanel = new PresetTitlePanel(this._divPresetList, preset[0], preset[1], true, this.presetsSourcesDialog.isThirdPartyActive, favoritePresets); presetPanel.load(); this._presetPanels.push(presetPanel); - presetPanel.subscribeClick(this.presetsDetailedDialog, this.presetsRepo); + presetPanel.subscribeClick(this.presetsDetailedDialog, preset[1]); }); this._domListTooManyFound.appendTo(this._divPresetList); }; + presets.getFitPresets = function(searchParams) { const result = []; - - for(const preset of this.presetsRepo.index.presets) { - if(this.isPresetFitSearch(preset, searchParams)) { - result.push(preset); + const seenHashes = new Set(); + for (const repo of this.presetsRepo){ + for (const preset of repo.index.presets) { + if (this.isPresetFitSearch(preset, searchParams) && !seenHashes.has(preset.hash)) { + result.push([preset, repo]); + seenHashes.add(preset.hash); + } } } - result.sort((a, b) => this.presetSearchPriorityComparer(a,b)); + result.sort((a, b) => this.presetSearchPriorityComparer(a[0], b[0])); return result; }; @@ -653,7 +680,7 @@ presets.cleanup = function(callback) { presets.resetInitialValues = function() { CONFIGURATOR.cliEngineActive = false; CONFIGURATOR.cliEngineValid = false; - TABS.presets.presetsRepo = null; + TABS.presets.presetsRepo = []; TABS.presets.pickedPresetList.length = 0; this._domProgressDialog.close(); };