From a3d4eddacfc4f9e86b91264c4e6440af521995d7 Mon Sep 17 00:00:00 2001 From: Ivan Semenov Date: Mon, 1 Oct 2018 16:59:49 +0300 Subject: [PATCH] [github] Simplify pull requests list components structure --- .../GithubPullRequestsComponentFactory.kt | 119 ++++++++++-------- .../ui/GithubDataLoadingComponent.kt | 44 +++++++ .../ui/GithubPullRequestChangesComponent.kt | 76 +++++------ .../ui/GithubPullRequestDetailsComponent.kt | 59 +++------ .../ui/GithubPullRequestPreviewComponent.kt | 28 ++--- .../ui/GithubPullRequestsListComponent.kt | 2 +- 6 files changed, 165 insertions(+), 163 deletions(-) create mode 100644 plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubDataLoadingComponent.kt diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/GithubPullRequestsComponentFactory.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/GithubPullRequestsComponentFactory.kt index ef781c524542..f769c7ddcd7c 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/GithubPullRequestsComponentFactory.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/GithubPullRequestsComponentFactory.kt @@ -9,7 +9,6 @@ import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.intellij.ui.OnePixelSplitter -import com.intellij.ui.components.panels.Wrapper import git4idea.commands.Git import git4idea.repo.GitRemote import git4idea.repo.GitRepository @@ -19,9 +18,14 @@ import org.jetbrains.plugins.github.authentication.accounts.GithubAccount import org.jetbrains.plugins.github.pullrequest.action.GithubPullRequestKeys import org.jetbrains.plugins.github.pullrequest.avatars.CachingGithubAvatarIconsProvider import org.jetbrains.plugins.github.pullrequest.config.GithubPullRequestsUISettings +import org.jetbrains.plugins.github.pullrequest.config.GithubPullRequestsUISettings.SettingsChangedListener import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDataLoader import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsLoader -import org.jetbrains.plugins.github.pullrequest.ui.* +import org.jetbrains.plugins.github.pullrequest.ui.GithubPullRequestChangesComponent +import org.jetbrains.plugins.github.pullrequest.ui.GithubPullRequestDetailsComponent +import org.jetbrains.plugins.github.pullrequest.ui.GithubPullRequestPreviewComponent +import org.jetbrains.plugins.github.pullrequest.ui.GithubPullRequestsListComponent +import org.jetbrains.plugins.github.pullrequest.ui.GithubPullRequestsListSelectionModel.SelectionChangedListener import org.jetbrains.plugins.github.util.CachingGithubUserAvatarLoader import org.jetbrains.plugins.github.util.GithubImageResizer import javax.swing.JComponent @@ -40,69 +44,76 @@ class GithubPullRequestsComponentFactory(private val project: Project, repository: GitRepository, remote: GitRemote, repoDetails: GithubRepoDetailed, account: GithubAccount): JComponent? { + val avatarIconsProviderFactory = CachingGithubAvatarIconsProvider.Factory(avatarLoader, imageResizer, requestExecutor) + return GithubPullRequestsComponent(requestExecutor, avatarIconsProviderFactory, repository, remote, repoDetails, account) + } - val listLoader = GithubPullRequestsLoader(progressManager, requestExecutor, - account.server, repoDetails.fullPath) - val selectionModel = GithubPullRequestsListSelectionModel() - val list = GithubPullRequestsListComponent(project, actionManager, autoPopupController, - selectionModel, listLoader, - CachingGithubAvatarIconsProvider.Factory(avatarLoader, imageResizer, requestExecutor)) - requestExecutor.addListener(list) { list.refresh() } + private inner class GithubPullRequestsComponent(requestExecutor: GithubApiRequestExecutor, + avatarIconsProviderFactory: CachingGithubAvatarIconsProvider.Factory, + private val repository: GitRepository, private val remote: GitRemote, + private val repoDetails: GithubRepoDetailed, + private val account: GithubAccount) + : OnePixelSplitter("Github.PullRequests.Component", 0.6f), Disposable, DataProvider { - val dataLoader = GithubPullRequestsDataLoader(project, progressManager, git, requestExecutor, repository, remote) + private val dataLoader = GithubPullRequestsDataLoader(project, progressManager, git, requestExecutor, repository, remote) - val changes = GithubPullRequestChangesComponent(project, selectionModel, dataLoader, actionManager) - val details = GithubPullRequestDetailsComponent(project, selectionModel, dataLoader) + private val changes = GithubPullRequestChangesComponent(project, actionManager).apply { + diffAction.registerCustomShortcutSet(this@GithubPullRequestsComponent, this@GithubPullRequestsComponent) + } + private val details = GithubPullRequestDetailsComponent(project) + private val preview = GithubPullRequestPreviewComponent(changes, details).apply { + detailsVisible = uiSettings.showDetails + } - val preview = GithubPullRequestPreviewComponent(uiSettings, changes, details) - list.setToolbarHeightReferent(preview.toolbarComponent) + private val listLoader = GithubPullRequestsLoader(progressManager, requestExecutor, account.server, repoDetails.fullPath) + private val list = GithubPullRequestsListComponent(project, actionManager, autoPopupController, + listLoader, + avatarIconsProviderFactory).apply { + requestExecutor.addListener(this) { this.refresh() } + setToolbarHeightReferent(preview.toolbarComponent) + } - val splitter = OnePixelSplitter("Github.PullRequests.Component", 0.6f) - splitter.firstComponent = list - splitter.secondComponent = preview - // disposed by content manager when tab is closed - val wrapper = WrappingComponent(splitter, - repository, remote, repoDetails, account, - list,selectionModel, dataLoader) - Disposer.register(wrapper, Disposable { + init { + firstComponent = list + secondComponent = preview + isFocusCycleRoot = true + + uiSettings.addChangesListener(object : SettingsChangedListener { + override fun settingsChanged() { + preview.detailsVisible = uiSettings.showDetails + } + }, preview) + + list.selectionModel.addChangesListener(object : SelectionChangedListener { + override fun selectionChanged() { + val dataProvider = list.selectionModel.current?.let(dataLoader::getDataProvider) + preview.setPreviewDataProvider(dataProvider) + } + }, preview) + } + + override fun getData(dataId: String): Any? { + return when { + GithubPullRequestKeys.REPOSITORY.`is`(dataId) -> repository + GithubPullRequestKeys.REMOTE.`is`(dataId) -> remote + GithubPullRequestKeys.REPO_DETAILS.`is`(dataId) -> repoDetails + GithubPullRequestKeys.SERVER_PATH.`is`(dataId) -> account.server + GithubPullRequestKeys.PULL_REQUESTS_LIST_COMPONENT.`is`(dataId) -> list + GithubPullRequestKeys.SELECTED_PULL_REQUEST_DATA_PROVIDER.`is`(dataId) -> + list.selectionModel.current?.let(dataLoader::getDataProvider) + else -> null + } + } + + override fun dispose() { Disposer.dispose(list) Disposer.dispose(preview) + Disposer.dispose(changes) + Disposer.dispose(details) Disposer.dispose(listLoader) Disposer.dispose(dataLoader) - }) - changes.diffAction.registerCustomShortcutSet(wrapper, wrapper) - return wrapper - } - - companion object { - private class WrappingComponent(wrapped: JComponent, - private val repository: GitRepository, - private val remote: GitRemote, - private val repoDetails: GithubRepoDetailed, - private val account: GithubAccount, - private val list: GithubPullRequestsListComponent, - private val selectionModel: GithubPullRequestsListSelectionModel, - private val dataLoader: GithubPullRequestsDataLoader) - : Wrapper(wrapped), Disposable, DataProvider { - init { - isFocusCycleRoot = true - } - - override fun getData(dataId: String): Any? { - return when { - GithubPullRequestKeys.REPOSITORY.`is`(dataId) -> repository - GithubPullRequestKeys.REMOTE.`is`(dataId) -> remote - GithubPullRequestKeys.REPO_DETAILS.`is`(dataId) -> repoDetails - GithubPullRequestKeys.SERVER_PATH.`is`(dataId) -> account.server - GithubPullRequestKeys.PULL_REQUESTS_LIST_COMPONENT.`is`(dataId) -> list - GithubPullRequestKeys.SELECTED_PULL_REQUEST_DATA_PROVIDER.`is`(dataId) -> selectionModel.current?.let(dataLoader::getDataProvider) - else -> null - } - } - - override fun dispose() {} } } } \ No newline at end of file diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubDataLoadingComponent.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubDataLoadingComponent.kt new file mode 100644 index 000000000000..ecc08310206f --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubDataLoadingComponent.kt @@ -0,0 +1,44 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.github.pullrequest.ui + +import com.intellij.ui.components.panels.Wrapper +import org.jetbrains.annotations.CalledInAwt +import org.jetbrains.plugins.github.util.GithubAsyncUtil +import org.jetbrains.plugins.github.util.handleOnEdt +import java.util.concurrent.CompletableFuture + +abstract class GithubDataLoadingComponent : Wrapper() { + private var updateFuture: CompletableFuture? = null + + /** + * This works because [handleOnEdt] basically forms a EDT-synchronized section and result/exception is acquired from [dataRequest] on EDT + * + * In pseudocode: + * when (dataRequest.isDone) { runOnEdt { handler(getResult(), getException()) } } + */ + @CalledInAwt + fun loadAndShow(dataRequest: CompletableFuture?) { + updateFuture?.cancel(true) + reset() + + if (dataRequest == null) { + updateFuture = null + setBusy(false) + return + } + + setBusy(true) + updateFuture = dataRequest.handleOnEdt { result, error -> + when { + error != null && !GithubAsyncUtil.isCancellation(error) -> handleError(error) + result != null -> handleResult(result) + } + setBusy(false) + } + } + + protected abstract fun reset() + protected abstract fun handleResult(result: T) + protected abstract fun handleError(error: Throwable) + protected abstract fun setBusy(busy: Boolean) +} \ No newline at end of file diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestChangesComponent.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestChangesComponent.kt index 8a495ace0e38..877db077f603 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestChangesComponent.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestChangesComponent.kt @@ -15,73 +15,53 @@ import com.intellij.ui.IdeBorderFactory import com.intellij.ui.SideBorder import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.components.JBLoadingPanel -import com.intellij.ui.components.panels.Wrapper import com.intellij.util.ui.ComponentWithEmptyText -import org.jetbrains.plugins.github.api.data.GithubSearchedIssue -import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDataLoader -import org.jetbrains.plugins.github.util.GithubAsyncUtil -import org.jetbrains.plugins.github.util.handleOnEdt import java.awt.BorderLayout -import java.util.concurrent.CompletableFuture import javax.swing.JComponent import javax.swing.border.Border import kotlin.properties.Delegates -class GithubPullRequestChangesComponent(project: Project, - private val selectionModel: GithubPullRequestsListSelectionModel, - private val dataLoader: GithubPullRequestsDataLoader, - actionManager: ActionManager) - : Wrapper(), Disposable, GithubPullRequestsListSelectionModel.SelectionChangedListener { +class GithubPullRequestChangesComponent(project: Project, actionManager: ActionManager) + : GithubDataLoadingComponent>(), Disposable { private val changesBrowser = PullRequestChangesBrowserWithError(project, actionManager) + private val loadingPanel = JBLoadingPanel(BorderLayout(), this, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS) val toolbarComponent: JComponent = changesBrowser.toolbar.component val diffAction = changesBrowser.diffAction - private val changesLoadingPanel = JBLoadingPanel(BorderLayout(), this, - ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS) - - private var updateFuture: CompletableFuture? = null init { - selectionModel.addChangesListener(this, this) - - changesLoadingPanel.add(changesBrowser, BorderLayout.CENTER) - setContent(changesLoadingPanel) + loadingPanel.add(changesBrowser, BorderLayout.CENTER) changesBrowser.emptyText.text = DEFAULT_EMPTY_TEXT + setContent(loadingPanel) } - override fun selectionChanged() { - reset() - updateFuture = updateChanges(selectionModel.current) - } - - private fun updateChanges(item: GithubSearchedIssue?) = - item?.let { selection -> - changesBrowser.emptyText.clear() - changesLoadingPanel.startLoading() - - dataLoader.getDataProvider(selection).changesRequest - .handleOnEdt { changes, error -> - when { - error != null && !GithubAsyncUtil.isCancellation(error) -> { - changesBrowser.emptyText - .appendText("Cannot load changes", SimpleTextAttributes.ERROR_ATTRIBUTES) - .appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null) - } - changes != null -> { - changesBrowser.emptyText.text = "Pull request does not contain any changes" - changesBrowser.changes = changes - } - } - changesLoadingPanel.stopLoading() - } - } - - private fun reset() { - updateFuture?.cancel(true) + override fun reset() { changesBrowser.emptyText.text = DEFAULT_EMPTY_TEXT changesBrowser.changes = emptyList() } + override fun handleResult(result: List) { + changesBrowser.emptyText.text = "Pull request does not contain any changes" + changesBrowser.changes = result + } + + override fun handleError(error: Throwable) { + changesBrowser.emptyText + .clear() + .appendText("Cannot load changes", SimpleTextAttributes.ERROR_ATTRIBUTES) + .appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null) + } + + override fun setBusy(busy: Boolean) { + if (busy) { + changesBrowser.emptyText.clear() + loadingPanel.startLoading() + } + else { + loadingPanel.stopLoading() + } + } + override fun dispose() {} companion object { diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestDetailsComponent.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestDetailsComponent.kt index 79ccd56a33c6..89b5b2024dfc 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestDetailsComponent.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestDetailsComponent.kt @@ -6,62 +6,35 @@ import com.intellij.openapi.progress.util.ProgressWindow import com.intellij.openapi.project.Project import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.components.JBLoadingPanel -import com.intellij.ui.components.panels.Wrapper -import org.jetbrains.plugins.github.api.data.GithubSearchedIssue -import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDataLoader -import org.jetbrains.plugins.github.util.GithubAsyncUtil -import org.jetbrains.plugins.github.util.handleOnEdt +import org.jetbrains.plugins.github.api.data.GithubPullRequestDetailedWithHtml import java.awt.BorderLayout -import java.util.concurrent.CompletableFuture - -class GithubPullRequestDetailsComponent(project: Project, - private val selectionModel: GithubPullRequestsListSelectionModel, - private val dataLoader: GithubPullRequestsDataLoader) - : Wrapper(), Disposable, GithubPullRequestsListSelectionModel.SelectionChangedListener { +class GithubPullRequestDetailsComponent(project: Project) : GithubDataLoadingComponent(), Disposable { private val detailsPanel = GithubPullRequestDetailsPanel(project) - private val loadingPanel = JBLoadingPanel(BorderLayout(), this, ProgressWindow.DEFAULT_PROGRESS_DIALOG_POSTPONE_TIME_MILLIS) - private var updateFuture: CompletableFuture? = null - init { - selectionModel.addChangesListener(this, this) - loadingPanel.add(detailsPanel) setContent(loadingPanel) } - override fun selectionChanged() { - reset() - updateFuture = updateDetails(selectionModel.current) - } - - private fun updateDetails(item: GithubSearchedIssue?) = - item?.let { selection -> - loadingPanel.startLoading() - - dataLoader.getDataProvider(selection).detailsRequest - .handleOnEdt { details, error -> - when { - error != null && !GithubAsyncUtil.isCancellation(error) -> { - detailsPanel.emptyText - .appendText("Cannot load details", SimpleTextAttributes.ERROR_ATTRIBUTES) - .appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null) - } - details != null -> { - detailsPanel.details = details - } - } - loadingPanel.stopLoading() - } - } - - private fun reset() { - updateFuture?.cancel(true) + override fun reset() { detailsPanel.emptyText.clear() detailsPanel.details = null } + override fun handleResult(result: GithubPullRequestDetailedWithHtml) { + detailsPanel.details = result + } + + override fun handleError(error: Throwable) { + detailsPanel.emptyText + .clear() + .appendText("Cannot load details", SimpleTextAttributes.ERROR_ATTRIBUTES) + .appendSecondaryText(error.message ?: "Unknown error", SimpleTextAttributes.ERROR_ATTRIBUTES, null) + } + + override fun setBusy(busy: Boolean) = if (busy) loadingPanel.startLoading() else loadingPanel.stopLoading() + override fun dispose() {} } diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestPreviewComponent.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestPreviewComponent.kt index 08cf4a899da9..c444180bb8e5 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestPreviewComponent.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestPreviewComponent.kt @@ -2,33 +2,27 @@ package org.jetbrains.plugins.github.pullrequest.ui import com.intellij.openapi.Disposable -import com.intellij.openapi.util.Disposer import com.intellij.ui.OnePixelSplitter -import org.jetbrains.plugins.github.pullrequest.config.GithubPullRequestsUISettings +import org.jetbrains.plugins.github.pullrequest.data.GithubPullRequestsDataLoader -class GithubPullRequestPreviewComponent(private val uiSettings: GithubPullRequestsUISettings, - changes: GithubPullRequestChangesComponent, +class GithubPullRequestPreviewComponent(private val changes: GithubPullRequestChangesComponent, private val details: GithubPullRequestDetailsComponent) - : OnePixelSplitter(true, "Github.PullRequest.Preview.Component", 0.6f), - Disposable, GithubPullRequestsUISettings.SettingsChangedListener { + : OnePixelSplitter(true, "Github.PullRequest.Preview.Component", 0.6f), Disposable { val toolbarComponent = changes.toolbarComponent + var detailsVisible: Boolean + get() = secondComponent != null + set(value) { + secondComponent = if (value) details else null + } init { - Disposer.register(this, changes) - Disposer.register(this, details) - firstComponent = changes - uiSettings.addChangesListener(this, this) - updateDetails() } - override fun settingsChanged() { - updateDetails() - } - - private fun updateDetails() { - secondComponent = if (uiSettings.showDetails) details else null + fun setPreviewDataProvider(provider: GithubPullRequestsDataLoader.DataProvider?) { + changes.loadAndShow(provider?.changesRequest) + details.loadAndShow(provider?.detailsRequest) } override fun dispose() {} diff --git a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestsListComponent.kt b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestsListComponent.kt index 08ceb4183e02..d5ecd666bfaa 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestsListComponent.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/pullrequest/ui/GithubPullRequestsListComponent.kt @@ -34,11 +34,11 @@ import javax.swing.event.ListSelectionEvent class GithubPullRequestsListComponent internal constructor(project: Project, actionManager: ActionManager, autoPopupController: AutoPopupController, - private val selectionModel: GithubPullRequestsListSelectionModel, private val loader: GithubPullRequestsLoader, avatarIconsProviderFactory: CachingGithubAvatarIconsProvider.Factory) : BorderLayoutPanel(), Disposable, DataProvider { + val selectionModel = GithubPullRequestsListSelectionModel() private val listModel = CollectionListModel() private val list = GithubPullRequestsList(avatarIconsProviderFactory, listModel) private val scrollPane = ScrollPaneFactory.createScrollPane(list,