[gh] Preselection of default account in PR window fixed

#IJPL-78576 Fixed

GitOrigin-RevId: 5cf9a3ba2f814453c76697ab85a7c6811bab19d7
This commit is contained in:
Bartosz Janusz
2025-07-21 08:44:48 +00:00
committed by intellij-monorepo-bot
parent 5b612a7085
commit 6e1e1dbf46
9 changed files with 95 additions and 51 deletions
@@ -271,8 +271,8 @@ f:com.intellij.collaboration.api.json.JsonHttpApiHelperKt
- a:isValid():Z
*f:com.intellij.collaboration.ui.AccountSelectorComponentFactory
- <init>(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
@@ -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<A : Account>(
private val accountsState: StateFlow<Collection<A>>,
private val selectionState: MutableStateFlow<A?>
private val selectionState: MutableStateFlow<A?>,
) {
fun create(scope: CoroutineScope,
avatarIconsProvider: IconsProvider<A>,
avatarSize: Int,
popupAvatarSize: Int,
emptyStateTooltip: @Nls String,
actions: StateFlow<List<Action>> = MutableStateFlow(emptyList())): JComponent {
fun create(
scope: CoroutineScope,
defaultAccountHolder: DefaultAccountHolder<A>?,
avatarIconsProvider: IconsProvider<A>,
avatarSize: Int,
popupAvatarSize: Int,
emptyStateTooltip: @Nls String,
actions: StateFlow<List<Action>> = MutableStateFlow(emptyList()),
): JComponent {
val comboModel = ComboBoxWithActionsModel<A>().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<A : Account>(
return label
}
private class Controller<A : Account>(private val accountsModel: ComboBoxWithActionsModel<A>,
private val label: JLabel,
private val avatarIconsProvider: IconsProvider<A>,
private val avatarSize: Int,
private val popupAvatarSize: Int,
private val emptyStateTooltip: @Nls String)
private class Controller<A : Account>(
private val accountsModel: ComboBoxWithActionsModel<A>,
private val label: JLabel,
private val avatarIconsProvider: IconsProvider<A>,
private val avatarSize: Int,
private val popupAvatarSize: Int,
private val emptyStateTooltip: @Nls String,
)
: ComboBoxPopup.Context<ComboBoxWithActionsModel.Item<A>> {
private var popup: ComboBoxPopup<*>? = null
@@ -125,11 +135,13 @@ class AccountSelectorComponentFactory<A : Account>(
private val delegateRenderer = AccountMenuItemRenderer()
override fun getListCellRendererComponent(list: JList<out ComboBoxWithActionsModel.Item<A>>?,
value: ComboBoxWithActionsModel.Item<A>,
index: Int,
selected: Boolean,
focused: Boolean): Component {
override fun getListCellRendererComponent(
list: JList<out ComboBoxWithActionsModel.Item<A>>?,
value: ComboBoxWithActionsModel.Item<A>,
index: Int,
selected: Boolean,
focused: Boolean,
): Component {
val item = when (value) {
is ComboBoxWithActionsModel.Item.Wrapper<A> ->
value.wrappee.let { account ->
@@ -15,7 +15,7 @@ import kotlin.properties.Delegates
internal fun <T : Any> MutableCollectionComboBoxModel<T>.setItems(value: List<T>) {
val delta = CollectionDelta(items, value)
delta.removedItems.forEach { removeElement(it) }
delta.newItems.forEach { addElement(it) }
add(delta.newItems.toList())
}
internal class ComboBoxWithActionsModel<T>
@@ -16,26 +16,32 @@ import javax.swing.JList
@ApiStatus.Internal
class SimpleComboboxWithActionsFactory<T : Any>(
private val mappingsState: StateFlow<Collection<T>>,
private val selectionState: MutableStateFlow<T?>
private val selectionState: MutableStateFlow<T?>,
) {
fun create(scope: CoroutineScope,
presenter: (T) -> Presentation,
actions: StateFlow<List<Action>> = MutableStateFlow(emptyList()),
sortComparator: Comparator<T> = Comparator.comparing { presenter(it).name }): ComboBox<*> {
fun create(
scope: CoroutineScope,
presenter: (T) -> Presentation,
actions: StateFlow<List<Action>> = MutableStateFlow(emptyList()),
sortComparator: Comparator<T> = Comparator.comparing { presenter(it).name },
): ComboBox<*> {
val comboModel = ComboBoxWithActionsModel<T>().apply {
bindIn(scope, mappingsState, selectionState, actions, sortComparator)
selectFirst()
if (selectedItem == null) {
selectFirst()
}
}
return ComboBox(comboModel).apply {
renderer = object : ColoredListCellRenderer<ComboBoxWithActionsModel.Item<T>>() {
override fun customizeCellRenderer(list: JList<out ComboBoxWithActionsModel.Item<T>>,
value: ComboBoxWithActionsModel.Item<T>?,
index: Int,
selected: Boolean,
hasFocus: Boolean) {
override fun customizeCellRenderer(
list: JList<out ComboBoxWithActionsModel.Item<T>>,
value: ComboBoxWithActionsModel.Item<T>?,
index: Int,
selected: Boolean,
hasFocus: Boolean,
) {
if (value is ComboBoxWithActionsModel.Item.Wrapper) {
val presentations = presenter(value.wrappee)
append(presentations.name)
@@ -88,6 +88,9 @@ internal fun <T> ComboBoxWithActionsModel<T>.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 <T> ComboBoxWithActionsModel<T>.bindIn(
}
}
}
if (selectedItem == null) {
selectedItem = selectionState.value?.let { ComboBoxWithActionsModel.Item.Wrapper(it) }
}
}
internal fun <T> ComboBoxWithActionsModel<T>.bindIn(
@@ -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<M : HostedGitRepositoryMapping, A : ServerAccount>(
private val vm: RepositoryAndAccountSelectorViewModel<M, A>
) {
fun create(
scope: CoroutineScope,
repoNamer: (M) -> @Nls String,
@@ -53,8 +53,18 @@ class RepositoryAndAccountSelectorComponentFactory<M : HostedGitRepositoryMappin
submitActionText: @Nls String,
loginButtons: List<JButton>,
errorPresenter: ErrorStatusPresenter<RepositoryAndAccountSelectorViewModel.Error>
): JComponent {
): JComponent = create(scope,null, repoNamer, detailsProvider,accountsPopupActionsSupplier,submitActionText,loginButtons,errorPresenter)
fun create(
scope: CoroutineScope,
defaultAccountHolder: DefaultAccountHolder<A>?,
repoNamer: (M) -> @Nls String,
detailsProvider: LoadingAccountsDetailsProvider<A, *>,
accountsPopupActionsSupplier: (M) -> List<Action>,
submitActionText: @Nls String,
loginButtons: List<JButton>,
errorPresenter: ErrorStatusPresenter<RepositoryAndAccountSelectorViewModel.Error>
): JComponent {
val repoCombo = SimpleComboboxWithActionsFactory(vm.repositoriesState, vm.repoSelectionState).create(scope, { mapping ->
SimpleComboboxWithActionsFactory.Presentation(
repoNamer(mapping),
@@ -67,6 +77,7 @@ class RepositoryAndAccountSelectorComponentFactory<M : HostedGitRepositoryMappin
val accountCombo = AccountSelectorComponentFactory(vm.accountsState, vm.accountSelectionState).create(
scope,
defaultAccountHolder,
detailsProvider,
Avatar.Sizes.BASE,
Avatar.Sizes.ACCOUNT,
@@ -4,6 +4,7 @@ package org.jetbrains.plugins.github.pullrequest.ui.selector
import com.intellij.collaboration.ui.CollaborationToolsUIUtil.isDefault
import com.intellij.collaboration.ui.util.bindDisabledIn
import com.intellij.collaboration.ui.util.bindVisibilityIn
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.ui.components.ActionLink
import git4idea.remote.hosting.ui.RepositoryAndAccountSelectorComponentFactory
@@ -14,6 +15,7 @@ import org.jetbrains.plugins.github.authentication.AuthorizationType
import org.jetbrains.plugins.github.authentication.GHAccountsUtil
import org.jetbrains.plugins.github.authentication.GHLoginSource
import org.jetbrains.plugins.github.authentication.accounts.GHAccountManager
import org.jetbrains.plugins.github.authentication.accounts.GithubProjectDefaultAccountHolder
import org.jetbrains.plugins.github.authentication.ui.GHAccountsDetailsProvider
import org.jetbrains.plugins.github.i18n.GithubBundle
import org.jetbrains.plugins.github.ui.util.GHUIUtil
@@ -28,26 +30,30 @@ class GHRepositoryAndAccountSelectorComponentFactory(
private val project: Project,
private val vm: GHRepositoryAndAccountSelectorViewModel,
private val accountManager: GHAccountManager,
private val loginSource: GHLoginSource
private val loginSource: GHLoginSource,
) {
fun create(scope: CoroutineScope): JComponent {
val defaultAccountHolder = project.service<GithubProjectDefaultAccountHolder>()
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<JButton> {
@@ -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<GitLabProjectDefaultAccountHolder>()
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)
@@ -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)
}
}
}