From 6e1e1dbf46d389b2bb24636093d39cfbcbce8116 Mon Sep 17 00:00:00 2001 From: Bartosz Janusz Date: Thu, 17 Jul 2025 17:12:02 +0200 Subject: [PATCH] [gh] Preselection of default account in PR window fixed #IJPL-78576 Fixed GitOrigin-RevId: 5cf9a3ba2f814453c76697ab85a7c6811bab19d7 --- .../api-dump-experimental.txt | 4 +- .../ui/AccountSelectorComponentFactory.kt | 52 ++++++++++++------- .../ui/ComboBoxWithActionsModel.kt | 2 +- .../ui/SimpleComboboxWithActionsFactory.kt | 30 ++++++----- .../collaboration/ui/util/swingBindings.kt | 6 +++ ...itoryAndAccountSelectorComponentFactory.kt | 15 +++++- ...itoryAndAccountSelectorComponentFactory.kt | 28 ++++++---- ...abMergeRequestSelectorsComponentFactory.kt | 7 ++- .../GitLabReviewTabComponentFactory.kt | 2 +- 9 files changed, 95 insertions(+), 51 deletions(-) diff --git a/platform/collaboration-tools/api-dump-experimental.txt b/platform/collaboration-tools/api-dump-experimental.txt index 7634ea71ab11..d4ce10ae4c70 100644 --- a/platform/collaboration-tools/api-dump-experimental.txt +++ b/platform/collaboration-tools/api-dump-experimental.txt @@ -271,8 +271,8 @@ f:com.intellij.collaboration.api.json.JsonHttpApiHelperKt - a:isValid():Z *f:com.intellij.collaboration.ui.AccountSelectorComponentFactory - (kotlinx.coroutines.flow.StateFlow,kotlinx.coroutines.flow.MutableStateFlow):V -- f:create(kotlinx.coroutines.CoroutineScope,com.intellij.collaboration.ui.icon.IconsProvider,I,I,java.lang.String,kotlinx.coroutines.flow.StateFlow):javax.swing.JComponent -- bs:create$default(com.intellij.collaboration.ui.AccountSelectorComponentFactory,kotlinx.coroutines.CoroutineScope,com.intellij.collaboration.ui.icon.IconsProvider,I,I,java.lang.String,kotlinx.coroutines.flow.StateFlow,I,java.lang.Object):javax.swing.JComponent +- f:create(kotlinx.coroutines.CoroutineScope,com.intellij.collaboration.auth.DefaultAccountHolder,com.intellij.collaboration.ui.icon.IconsProvider,I,I,java.lang.String,kotlinx.coroutines.flow.StateFlow):javax.swing.JComponent +- bs:create$default(com.intellij.collaboration.ui.AccountSelectorComponentFactory,kotlinx.coroutines.CoroutineScope,com.intellij.collaboration.auth.DefaultAccountHolder,com.intellij.collaboration.ui.icon.IconsProvider,I,I,java.lang.String,kotlinx.coroutines.flow.StateFlow,I,java.lang.Object):javax.swing.JComponent *f:com.intellij.collaboration.ui.CollaborationToolsUIUtil - sf:INSTANCE:com.intellij.collaboration.ui.CollaborationToolsUIUtil - f:asObservableIn(kotlinx.coroutines.flow.StateFlow,kotlinx.coroutines.CoroutineScope):com.intellij.openapi.observable.properties.AbstractObservableProperty diff --git a/platform/collaboration-tools/src/com/intellij/collaboration/ui/AccountSelectorComponentFactory.kt b/platform/collaboration-tools/src/com/intellij/collaboration/ui/AccountSelectorComponentFactory.kt index b8f9296eff06..d2d78e83264e 100644 --- a/platform/collaboration-tools/src/com/intellij/collaboration/ui/AccountSelectorComponentFactory.kt +++ b/platform/collaboration-tools/src/com/intellij/collaboration/ui/AccountSelectorComponentFactory.kt @@ -2,6 +2,7 @@ package com.intellij.collaboration.ui import com.intellij.collaboration.auth.Account +import com.intellij.collaboration.auth.DefaultAccountHolder import com.intellij.collaboration.auth.ServerAccount import com.intellij.collaboration.ui.icon.IconsProvider import com.intellij.collaboration.ui.util.bindIn @@ -27,20 +28,27 @@ import javax.swing.event.ListDataListener class AccountSelectorComponentFactory( private val accountsState: StateFlow>, - private val selectionState: MutableStateFlow + private val selectionState: MutableStateFlow, ) { - fun create(scope: CoroutineScope, - avatarIconsProvider: IconsProvider, - avatarSize: Int, - popupAvatarSize: Int, - emptyStateTooltip: @Nls String, - actions: StateFlow> = MutableStateFlow(emptyList())): JComponent { - + fun create( + scope: CoroutineScope, + defaultAccountHolder: DefaultAccountHolder?, + avatarIconsProvider: IconsProvider, + avatarSize: Int, + popupAvatarSize: Int, + emptyStateTooltip: @Nls String, + actions: StateFlow> = MutableStateFlow(emptyList()), + ): JComponent { val comboModel = ComboBoxWithActionsModel().apply { bindIn(scope, accountsState, selectionState, actions, Comparator.comparing { it.name }) - if (size > 0) { + val defaultAccount = defaultAccountHolder?.account + if (selectedItem == null && defaultAccount != null) { + selectedItem = ComboBoxWithActionsModel.Item.Wrapper(defaultAccount) + } + + if (size > 0 && selectedItem == null) { for (i in 0 until size) { val item = getElementAt(i) if (item is ComboBoxWithActionsModel.Item.Wrapper) { @@ -61,12 +69,14 @@ class AccountSelectorComponentFactory( return label } - private class Controller(private val accountsModel: ComboBoxWithActionsModel, - private val label: JLabel, - private val avatarIconsProvider: IconsProvider, - private val avatarSize: Int, - private val popupAvatarSize: Int, - private val emptyStateTooltip: @Nls String) + private class Controller( + private val accountsModel: ComboBoxWithActionsModel, + private val label: JLabel, + private val avatarIconsProvider: IconsProvider, + private val avatarSize: Int, + private val popupAvatarSize: Int, + private val emptyStateTooltip: @Nls String, + ) : ComboBoxPopup.Context> { private var popup: ComboBoxPopup<*>? = null @@ -125,11 +135,13 @@ class AccountSelectorComponentFactory( private val delegateRenderer = AccountMenuItemRenderer() - override fun getListCellRendererComponent(list: JList>?, - value: ComboBoxWithActionsModel.Item, - index: Int, - selected: Boolean, - focused: Boolean): Component { + override fun getListCellRendererComponent( + list: JList>?, + value: ComboBoxWithActionsModel.Item, + index: Int, + selected: Boolean, + focused: Boolean, + ): Component { val item = when (value) { is ComboBoxWithActionsModel.Item.Wrapper -> value.wrappee.let { account -> diff --git a/platform/collaboration-tools/src/com/intellij/collaboration/ui/ComboBoxWithActionsModel.kt b/platform/collaboration-tools/src/com/intellij/collaboration/ui/ComboBoxWithActionsModel.kt index 0b020dc0bf37..44f3d21fbddf 100644 --- a/platform/collaboration-tools/src/com/intellij/collaboration/ui/ComboBoxWithActionsModel.kt +++ b/platform/collaboration-tools/src/com/intellij/collaboration/ui/ComboBoxWithActionsModel.kt @@ -15,7 +15,7 @@ import kotlin.properties.Delegates internal fun MutableCollectionComboBoxModel.setItems(value: List) { val delta = CollectionDelta(items, value) delta.removedItems.forEach { removeElement(it) } - delta.newItems.forEach { addElement(it) } + add(delta.newItems.toList()) } internal class ComboBoxWithActionsModel diff --git a/platform/collaboration-tools/src/com/intellij/collaboration/ui/SimpleComboboxWithActionsFactory.kt b/platform/collaboration-tools/src/com/intellij/collaboration/ui/SimpleComboboxWithActionsFactory.kt index 230fd58f91e4..768afae16e7a 100644 --- a/platform/collaboration-tools/src/com/intellij/collaboration/ui/SimpleComboboxWithActionsFactory.kt +++ b/platform/collaboration-tools/src/com/intellij/collaboration/ui/SimpleComboboxWithActionsFactory.kt @@ -16,26 +16,32 @@ import javax.swing.JList @ApiStatus.Internal class SimpleComboboxWithActionsFactory( private val mappingsState: StateFlow>, - private val selectionState: MutableStateFlow + private val selectionState: MutableStateFlow, ) { - - fun create(scope: CoroutineScope, - presenter: (T) -> Presentation, - actions: StateFlow> = MutableStateFlow(emptyList()), - sortComparator: Comparator = Comparator.comparing { presenter(it).name }): ComboBox<*> { + fun create( + scope: CoroutineScope, + presenter: (T) -> Presentation, + actions: StateFlow> = MutableStateFlow(emptyList()), + sortComparator: Comparator = Comparator.comparing { presenter(it).name }, + ): ComboBox<*> { val comboModel = ComboBoxWithActionsModel().apply { bindIn(scope, mappingsState, selectionState, actions, sortComparator) - selectFirst() + + if (selectedItem == null) { + selectFirst() + } } return ComboBox(comboModel).apply { renderer = object : ColoredListCellRenderer>() { - override fun customizeCellRenderer(list: JList>, - value: ComboBoxWithActionsModel.Item?, - index: Int, - selected: Boolean, - hasFocus: Boolean) { + override fun customizeCellRenderer( + list: JList>, + value: ComboBoxWithActionsModel.Item?, + index: Int, + selected: Boolean, + hasFocus: Boolean, + ) { if (value is ComboBoxWithActionsModel.Item.Wrapper) { val presentations = presenter(value.wrappee) append(presentations.name) diff --git a/platform/collaboration-tools/src/com/intellij/collaboration/ui/util/swingBindings.kt b/platform/collaboration-tools/src/com/intellij/collaboration/ui/util/swingBindings.kt index 17073a73921b..caad7635745f 100644 --- a/platform/collaboration-tools/src/com/intellij/collaboration/ui/util/swingBindings.kt +++ b/platform/collaboration-tools/src/com/intellij/collaboration/ui/util/swingBindings.kt @@ -88,6 +88,9 @@ internal fun ComboBoxWithActionsModel.bindIn( scope.launchNow { items.collect { this@bindIn.items = it.sortedWith(sortComparator) + if (!it.contains(selectedItem?.wrappee)) { + selectedItem = it.firstOrNull()?.let { ComboBoxWithActionsModel.Item.Wrapper(it) } + } } } addSelectionChangeListenerIn(scope) { @@ -100,6 +103,9 @@ internal fun ComboBoxWithActionsModel.bindIn( } } } + if (selectedItem == null) { + selectedItem = selectionState.value?.let { ComboBoxWithActionsModel.Item.Wrapper(it) } + } } internal fun ComboBoxWithActionsModel.bindIn( diff --git a/plugins/git4idea/src/git4idea/remote/hosting/ui/RepositoryAndAccountSelectorComponentFactory.kt b/plugins/git4idea/src/git4idea/remote/hosting/ui/RepositoryAndAccountSelectorComponentFactory.kt index 73d6eb97dcdf..a554098886a4 100644 --- a/plugins/git4idea/src/git4idea/remote/hosting/ui/RepositoryAndAccountSelectorComponentFactory.kt +++ b/plugins/git4idea/src/git4idea/remote/hosting/ui/RepositoryAndAccountSelectorComponentFactory.kt @@ -2,6 +2,7 @@ package git4idea.remote.hosting.ui import com.intellij.collaboration.async.mapState +import com.intellij.collaboration.auth.DefaultAccountHolder import com.intellij.collaboration.auth.ServerAccount import com.intellij.collaboration.auth.ui.AccountsPanelFactory import com.intellij.collaboration.auth.ui.LoadingAccountsDetailsProvider @@ -44,7 +45,6 @@ import javax.swing.* class RepositoryAndAccountSelectorComponentFactory( private val vm: RepositoryAndAccountSelectorViewModel ) { - fun create( scope: CoroutineScope, repoNamer: (M) -> @Nls String, @@ -53,8 +53,18 @@ class RepositoryAndAccountSelectorComponentFactory, errorPresenter: ErrorStatusPresenter - ): JComponent { + ): JComponent = create(scope,null, repoNamer, detailsProvider,accountsPopupActionsSupplier,submitActionText,loginButtons,errorPresenter) + fun create( + scope: CoroutineScope, + defaultAccountHolder: DefaultAccountHolder?, + repoNamer: (M) -> @Nls String, + detailsProvider: LoadingAccountsDetailsProvider, + accountsPopupActionsSupplier: (M) -> List, + submitActionText: @Nls String, + loginButtons: List, + errorPresenter: ErrorStatusPresenter + ): JComponent { val repoCombo = SimpleComboboxWithActionsFactory(vm.repositoriesState, vm.repoSelectionState).create(scope, { mapping -> SimpleComboboxWithActionsFactory.Presentation( repoNamer(mapping), @@ -67,6 +77,7 @@ class RepositoryAndAccountSelectorComponentFactory() val accountDetailsProvider = GHAccountsDetailsProvider(scope, accountManager) val errorPresenter = GHSelectorErrorStatusPresenter(project, loginSource) { vm.submitSelection() } return RepositoryAndAccountSelectorComponentFactory(vm) - .create(scope = scope, - repoNamer = { mapping -> - val allRepositories = vm.repositoriesState.value.map { it.repository } - GHUIUtil.getRepositoryDisplayName(allRepositories, mapping.repository, true) - }, - detailsProvider = accountDetailsProvider, - accountsPopupActionsSupplier = { createPopupLoginActions(it) }, - submitActionText = GithubBundle.message("pull.request.view.list"), - loginButtons = createLoginButtons(scope), - errorPresenter = errorPresenter) + .create( + scope = scope, + defaultAccountHolder = defaultAccountHolder, + repoNamer = { mapping -> + val allRepositories = vm.repositoriesState.value.map { it.repository } + GHUIUtil.getRepositoryDisplayName(allRepositories, mapping.repository, true) + }, + detailsProvider = accountDetailsProvider, + accountsPopupActionsSupplier = { createPopupLoginActions(it) }, + submitActionText = GithubBundle.message("pull.request.view.list"), + loginButtons = createLoginButtons(scope), + errorPresenter = errorPresenter + ) } private fun createLoginButtons(scope: CoroutineScope): List { diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/selector/GitLabMergeRequestSelectorsComponentFactory.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/selector/GitLabMergeRequestSelectorsComponentFactory.kt index 8f65679a3ff3..6d2e80b77a5f 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/selector/GitLabMergeRequestSelectorsComponentFactory.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/selector/GitLabMergeRequestSelectorsComponentFactory.kt @@ -8,6 +8,7 @@ import com.intellij.collaboration.ui.util.bindVisibilityIn import com.intellij.collaboration.util.URIUtil import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project import com.intellij.openapi.util.NlsSafe import com.intellij.util.asSafely import git4idea.remote.hosting.ui.RepositoryAndAccountSelectorComponentFactory @@ -20,6 +21,7 @@ import org.jetbrains.plugins.gitlab.api.GitLabProjectCoordinates import org.jetbrains.plugins.gitlab.authentication.GitLabLoginSource import org.jetbrains.plugins.gitlab.authentication.GitLabLoginUtil import org.jetbrains.plugins.gitlab.authentication.LoginResult +import org.jetbrains.plugins.gitlab.authentication.accounts.GitLabProjectDefaultAccountHolder import org.jetbrains.plugins.gitlab.authentication.ui.GitLabAccountsDetailsProvider import org.jetbrains.plugins.gitlab.mergerequest.ui.toolwindow.GitLabSelectorErrorStatusPresenter import org.jetbrains.plugins.gitlab.mergerequest.ui.toolwindow.model.GitLabRepositoryAndAccountSelectorViewModel @@ -33,8 +35,8 @@ import javax.swing.JComponent @ApiStatus.Internal object GitLabMergeRequestSelectorsComponentFactory { - fun createSelectorsComponent(cs: CoroutineScope, selectorVm: GitLabRepositoryAndAccountSelectorViewModel, loginSource: GitLabLoginSource): JComponent { - + fun createSelectorsComponent(cs: CoroutineScope, project: Project, selectorVm: GitLabRepositoryAndAccountSelectorViewModel, loginSource: GitLabLoginSource): JComponent { + val defaultAccountHolder = project.service() val accountsDetailsProvider = GitLabAccountsDetailsProvider(cs, selectorVm.accountManager) { account -> // TODO: separate loader selectorVm.accountManager.findCredentials(account)?.let { token -> @@ -44,6 +46,7 @@ object GitLabMergeRequestSelectorsComponentFactory { val selectors = RepositoryAndAccountSelectorComponentFactory(selectorVm).create( scope = cs, + defaultAccountHolder = defaultAccountHolder, repoNamer = { mapping -> val allProjects = selectorVm.repositoriesState.value.map { it.repository } getProjectDisplayName(allProjects, mapping.repository) diff --git a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/toolwindow/GitLabReviewTabComponentFactory.kt b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/toolwindow/GitLabReviewTabComponentFactory.kt index efc1bc5ba7a1..bb07c0ed4587 100644 --- a/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/toolwindow/GitLabReviewTabComponentFactory.kt +++ b/plugins/gitlab/gitlab-core/src/org/jetbrains/plugins/gitlab/mergerequest/ui/toolwindow/GitLabReviewTabComponentFactory.kt @@ -65,7 +65,7 @@ internal class GitLabReviewTabComponentFactory( launchOnShow("SelectorsComponent") { bindChildIn(this, vm.selectorVm, BorderLayout.NORTH) { selectorVm -> if (selectorVm == null) return@bindChildIn null - GitLabMergeRequestSelectorsComponentFactory.createSelectorsComponent(this, selectorVm, GitLabLoginSource.MR_TW) + GitLabMergeRequestSelectorsComponentFactory.createSelectorsComponent(this, project, selectorVm, GitLabLoginSource.MR_TW) } } }