From f4af8086edcd99c05b2813892ce69ebcca7e342b Mon Sep 17 00:00:00 2001 From: Vyacheslav Moklev Date: Thu, 18 Sep 2025 11:47:45 +0300 Subject: [PATCH] [RDCT] PY-84272: Show UI feature buttons only for available backend features GitOrigin-RevId: b833d1aa4e9e24d9f477b90b534a58e3153a14cf --- .../src/BackendWelcomeScreenFeatureApi.kt | 4 +++ .../src/WelcomeScreenFeatureBackend.kt | 4 +++ .../WelcomeRightTabContentProvider.kt | 30 +++++++++++++++++-- .../rightTab/WelcomeScreenFeatureApi.kt | 2 ++ .../rightTab/WelcomeScreenRightTab.kt | 23 +++++++++++--- 5 files changed, 57 insertions(+), 6 deletions(-) diff --git a/platform/non-modal-welcome-screen/backend/src/BackendWelcomeScreenFeatureApi.kt b/platform/non-modal-welcome-screen/backend/src/BackendWelcomeScreenFeatureApi.kt index efb7180ce45d..565ce411ffd7 100644 --- a/platform/non-modal-welcome-screen/backend/src/BackendWelcomeScreenFeatureApi.kt +++ b/platform/non-modal-welcome-screen/backend/src/BackendWelcomeScreenFeatureApi.kt @@ -12,6 +12,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext internal class BackendWelcomeScreenFeatureApi : WelcomeScreenFeatureApi { + override suspend fun getAvailableFeatureIds(): List { + return WelcomeScreenFeatureBackend.getFeatureIds() + } + override suspend fun onClick(projectId: ProjectId, featureKey: String) { val project = projectId.findProject() val feature = WelcomeScreenFeatureBackend.getForFeatureKey(featureKey) ?: run { diff --git a/platform/non-modal-welcome-screen/backend/src/WelcomeScreenFeatureBackend.kt b/platform/non-modal-welcome-screen/backend/src/WelcomeScreenFeatureBackend.kt index a028489bdcce..df049f7374d3 100644 --- a/platform/non-modal-welcome-screen/backend/src/WelcomeScreenFeatureBackend.kt +++ b/platform/non-modal-welcome-screen/backend/src/WelcomeScreenFeatureBackend.kt @@ -23,6 +23,10 @@ abstract class WelcomeScreenFeatureBackend { private val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.intellij.platform.ide.welcomeScreenFeatureBackend") + fun getFeatureIds(): List { + return EP_NAME.extensionList.map { it.featureKey } + } + fun getForFeatureKey(featureKey: String): WelcomeScreenFeatureBackend? { return EP_NAME.lazySequence().firstOrNull { it.featureKey == featureKey } } diff --git a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeRightTabContentProvider.kt b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeRightTabContentProvider.kt index f578907264c1..1d45202a7cac 100644 --- a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeRightTabContentProvider.kt +++ b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeRightTabContentProvider.kt @@ -7,6 +7,9 @@ import androidx.compose.ui.graphics.vector.ImageVector import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project +import com.intellij.platform.project.projectId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import org.jetbrains.jewel.ui.icon.IconKey @@ -15,6 +18,8 @@ import javax.swing.Icon @ApiStatus.Internal interface WelcomeRightTabContentProvider { + val coroutineScope: CoroutineScope + // Use Valkyrie IDEA plugin to generate the ImageVector val backgroundImageVectorLight: ImageVector val backgroundImageVectorDark: ImageVector @@ -28,13 +33,34 @@ interface WelcomeRightTabContentProvider { @Composable fun getFeatureButtonModels(project: Project): List - data class FeatureButtonModel( + /** + * Base feature button model. Use for frontend-only features. + * For backend-based features, use [FeatureButtonModelWithBackend] and + * register a `WelcomeScreenFeatureBackend` implementation. + */ + open class FeatureButtonModel( val text: String, val icon: IconKey, val tint: Color = Color.Unspecified, - val onClick: () -> Unit, + val onClick: (Project, CoroutineScope) -> Unit, ) + /** + * Feature button backed by a `WelcomeScreenFeatureBackend` implementation. + */ + class FeatureButtonModelWithBackend( + val featureKey: String, + text: String, + icon: IconKey, + tint: Color = Color.Unspecified, + val beforeOnClick: suspend (Project) -> Unit = {} + ) : FeatureButtonModel(text, icon, tint, { project, coroutineScope -> + coroutineScope.launch { + beforeOnClick(project) + WelcomeScreenFeatureApi.getInstance().onClick(project.projectId(), featureKey) + } + }) + companion object { private val EP_NAME: ExtensionPointName = ExtensionPointName("com.intellij.platform.ide.welcomeScreenContentProvider") diff --git a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenFeatureApi.kt b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenFeatureApi.kt index 2f2bb07ba89e..f9739912c2c5 100644 --- a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenFeatureApi.kt +++ b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenFeatureApi.kt @@ -11,6 +11,8 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal @Rpc interface WelcomeScreenFeatureApi : RemoteApi { + suspend fun getAvailableFeatureIds(): List + suspend fun onClick(projectId: ProjectId, featureKey: String) companion object { diff --git a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenRightTab.kt b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenRightTab.kt index 7b1e69640b10..d878897e8384 100644 --- a/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenRightTab.kt +++ b/platform/non-modal-welcome-screen/src/com/intellij/platform/ide/nonModalWelcomeScreen/rightTab/WelcomeScreenRightTab.kt @@ -32,11 +32,14 @@ import com.intellij.platform.ide.nonModalWelcomeScreen.GoFileDragAndDropHandler import com.intellij.platform.ide.nonModalWelcomeScreen.NON_MODAL_WELCOME_SCREEN_SETTING_ID import com.intellij.platform.ide.nonModalWelcomeScreen.NonModalWelcomeScreenBundle import com.intellij.platform.ide.nonModalWelcomeScreen.WelcomeScreenTabUsageCollector +import com.intellij.platform.ide.nonModalWelcomeScreen.rightTab.WelcomeRightTabContentProvider.FeatureButtonModelWithBackend import com.intellij.platform.ide.nonModalWelcomeScreen.rightTab.WelcomeScreenRightTabComboBoxModel.KeymapModel import com.intellij.platform.ide.nonModalWelcomeScreen.rightTab.WelcomeScreenRightTabComboBoxModel.ThemeModel import com.intellij.platform.ide.nonModalWelcomeScreen.rightTab.components.WelcomeScreenCustomButton import com.intellij.platform.ide.nonModalWelcomeScreen.rightTab.components.WelcomeScreenCustomListComboBox +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus import org.jetbrains.jewel.bridge.JewelComposePanel @@ -138,12 +141,24 @@ class WelcomeScreenRightTab( @Composable fun FeatureGrid(modifier: Modifier = Modifier) { + val coroutineScope = contentProvider.coroutineScope + var backendFeatureIds by remember { mutableStateOf(emptySet()) } + LaunchedEffect(true) { + coroutineScope.launch { + backendFeatureIds = WelcomeScreenFeatureApi.getInstance().getAvailableFeatureIds().toSet() + } + } + Column(modifier = modifier.wrapContentSize(Alignment.Center), verticalArrangement = Arrangement.spacedBy(16.dp)) { - val featureModels = contentProvider.getFeatureButtonModels(project) + // Show only available backend features (and all non-backend features) + val featureModels = contentProvider.getFeatureButtonModels(project).filter { + it !is FeatureButtonModelWithBackend || it.featureKey in backendFeatureIds + } + for (row in featureModels.chunked(3)) { Row(modifier = Modifier.wrapContentSize(Alignment.Center), horizontalArrangement = Arrangement.spacedBy(16.dp)) { for (model in row) { - FeatureButton(model) + FeatureButton(model, coroutineScope) } } } @@ -151,10 +166,10 @@ class WelcomeScreenRightTab( } @Composable - private fun FeatureButton(model: WelcomeRightTabContentProvider.FeatureButtonModel) { + private fun FeatureButton(model: WelcomeRightTabContentProvider.FeatureButtonModel, scope: CoroutineScope) { WelcomeScreenCustomButton( onClick = { - model.onClick() + model.onClick(project, scope) }, style = CustomButtonStyle(), modifier = Modifier.size(112.dp, 87.dp),