mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[python] PY-79486: (WIP): Front-end improvements:
Remove hardcodes, add filter GitOrigin-RevId: 09e9b7dad0ac6c9ccfa5554915ee337697b8e1cb
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0215915f09
commit
13b58c18a3
@@ -39,6 +39,7 @@ jvm_library(
|
||||
"//platform/projectModel-impl",
|
||||
"//platform/workspace/jps",
|
||||
"//platform/workspace/storage",
|
||||
"@lib//:kotlinx-collections-immutable",
|
||||
]
|
||||
)
|
||||
### auto-generated section `build intellij.python.sdkConfigurator.backend` end
|
||||
@@ -51,5 +51,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.workspace.jps" />
|
||||
<orderEntry type="module" module-name="intellij.platform.workspace.storage" />
|
||||
<orderEntry type="library" name="kotlinx-collections-immutable" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
<idea-plugin>
|
||||
<idea-plugin visibility="private">
|
||||
<dependencies>
|
||||
<module name="intellij.python.pyproject"/>
|
||||
<module name="intellij.python.sdk"/>
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.jetbrains.python.sdk.configuration.CreateSdkInfoWithTool
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.sdk.setAssociationToPath
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
@@ -68,7 +69,7 @@ internal class ModulesSdkConfigurator private constructor(
|
||||
is CreateSdkInfo.ExistingEnv -> r.pythonInfo.languageLevel.toPythonVersion()
|
||||
is CreateSdkInfo.WillCreateEnv -> null
|
||||
}
|
||||
ModuleDTO(moduleName, createInfo.toolId.id, version, children[moduleName]!!.toList().sorted())
|
||||
ModuleDTO(moduleName, createInfo.toolId.id, version, children[moduleName]!!.toList().sorted().toPersistentList())
|
||||
}
|
||||
is ModuleCreateInfo.SameAs -> null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.intellij.python.sdkConfigurator.common.impl
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias ModuleName = @NlsSafe String
|
||||
@@ -15,7 +16,7 @@ data class ModuleDTO(
|
||||
val name: ModuleName,
|
||||
val createdByTool: ToolIdDTO,
|
||||
val existingPyVersion: @NlsSafe String?,
|
||||
val childModules: List<ModuleName>,
|
||||
val childModules: ImmutableList<ModuleName>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
<idea-plugin>
|
||||
<idea-plugin visibility="private">
|
||||
<dependencies>
|
||||
<module name="intellij.platform.frontend"/>
|
||||
<module name="intellij.python.sdkConfigurator.common"/>
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
python.sdk.configurator.frontend.choose.modules.title=Configure Modules Environment
|
||||
python.sdk.configurator.frontend.choose.modules.text=Pick the ones you want to use to run code and set up environment for them
|
||||
python.sdk.configurator.frontend.choose.modules.project.structure=Project Structure
|
||||
python.sdk.configurator.frontend.choose.modules.project.structure=Project Structure:
|
||||
python.sdk.configurator.frontend.choose.modules.environment=Environment
|
||||
python.sdk.configurator.frontend.choose.modules.workspace.member=Same as {0}
|
||||
python.sdk.configurator.frontend.choose.modules.workspace.existing=Use existing version {0}
|
||||
python.sdk.configurator.frontend.choose.modules.new=Create new environment
|
||||
|
||||
@@ -3,79 +3,65 @@ package com.intellij.python.sdkConfigurator.frontend.components
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateSet
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ToolIdDTO
|
||||
import com.intellij.python.sdkConfigurator.frontend.ModulesViewModel
|
||||
import com.intellij.python.sdkConfigurator.frontend.PySdkConfiguratorFrontendBundle
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.jewel.foundation.Stroke
|
||||
import org.jetbrains.jewel.foundation.modifier.border
|
||||
import org.jetbrains.jewel.foundation.theme.JewelTheme
|
||||
import org.jetbrains.jewel.ui.Orientation
|
||||
import org.jetbrains.jewel.ui.component.*
|
||||
import org.jetbrains.jewel.ui.component.styling.LocalCheckboxStyle
|
||||
import org.jetbrains.jewel.ui.icon.IconKey
|
||||
import org.jetbrains.jewel.ui.theme.dividerStyle
|
||||
|
||||
|
||||
/**
|
||||
* List of modules from [viewModel]
|
||||
*/
|
||||
@OptIn(FlowPreview::class)
|
||||
@Composable
|
||||
internal fun ModuleList(
|
||||
moduleItems: PersistentList<ModuleDTO>,
|
||||
icons: ImmutableMap<ToolIdDTO, IconKey>,
|
||||
checked: SnapshotStateSet<String>,
|
||||
onCheck: (ModuleName) -> Unit,
|
||||
topLabel: String,
|
||||
projectStructureLabel: String,
|
||||
environmentLabel: String,
|
||||
viewModel: ModulesViewModel,
|
||||
// UI labels
|
||||
topLabel: @Nls String,
|
||||
projectStructureLabel: @Nls String,
|
||||
environmentLabel: @Nls String,
|
||||
) {
|
||||
LaunchedEffect(viewModel) {
|
||||
viewModel.processFilterUpdates()
|
||||
}
|
||||
val space = 5.dp
|
||||
VerticallyScrollableContainer {
|
||||
val colPadding = 2.dp
|
||||
val horizontalArrangement = Arrangement.spacedBy(colPadding)
|
||||
Column(Modifier.width(IntrinsicSize.Max)
|
||||
.padding(end = scrollbarContentSafePadding())
|
||||
.border(Stroke.Alignment.Inside, JewelTheme.dividerStyle.metrics.thickness, JewelTheme.dividerStyle.color)
|
||||
.padding(5.dp)
|
||||
val checkboxArrangement = Arrangement.spacedBy(space)
|
||||
Column(Modifier.width(IntrinsicSize.Max), verticalArrangement = checkboxArrangement) {
|
||||
Text(text = topLabel, Modifier.padding(end = space, bottom = space))
|
||||
|
||||
) {
|
||||
Text(text = topLabel, maxLines = 1, softWrap = false, modifier = Modifier
|
||||
.padding(bottom = 10.dp)
|
||||
)
|
||||
Divider(Orientation.Horizontal, thickness = 5.dp)
|
||||
Row {
|
||||
val modifier = Modifier.weight(1f)
|
||||
Text(projectStructureLabel, modifier = modifier)
|
||||
Divider(Orientation.Vertical)
|
||||
Text(environmentLabel, modifier = modifier)
|
||||
}
|
||||
Divider(Orientation.Horizontal)
|
||||
val hide = Modifier.alpha(0f)
|
||||
for (module in moduleItems) {
|
||||
var subModuleOpened by remember { mutableStateOf(false) }
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = horizontalArrangement) {
|
||||
OpenArrow(subModuleOpened, { subModuleOpened = !subModuleOpened },
|
||||
modifier = if (module.childModules.isEmpty()) hide else Modifier)
|
||||
Module(module.name in checked, onCheck, colPadding, module, icons)
|
||||
} // Module children
|
||||
|
||||
for (childModule in module.childModules) {
|
||||
Row(Modifier.alpha(if (subModuleOpened) 1f else 0f)) {
|
||||
// Invisible elements are for padding only: will replace to the real padding
|
||||
OpenArrow(false, {}, hide)
|
||||
Checkbox(false, onCheckedChange = {}, modifier = hide)
|
||||
CheckboxRow(
|
||||
childModule,
|
||||
childModule in checked,
|
||||
onCheckedChange = {},
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
enabled = false,
|
||||
)
|
||||
Column(Modifier.padding(space).border(Stroke.Alignment.Outside, 1.dp, JewelTheme.globalColors.borders.normal).padding(space).fillMaxWidth(), verticalArrangement = checkboxArrangement) {
|
||||
Row(Modifier.height(IntrinsicSize.Min).padding(start = space, bottom = space, end = space), horizontalArrangement = checkboxArrangement) {
|
||||
ModuleRow(
|
||||
left = { modifier ->
|
||||
Row(modifier) {
|
||||
Text(projectStructureLabel)
|
||||
Spacer(Modifier.width(1.dp))
|
||||
TextField(viewModel.moduleFilter, undecorated = true, modifier = Modifier.weight(1f))
|
||||
}
|
||||
},
|
||||
right = { modifier ->
|
||||
Text(environmentLabel, modifier = modifier)
|
||||
}
|
||||
)
|
||||
}
|
||||
for (module in viewModel.filteredModules) {
|
||||
Row(verticalAlignment = Alignment.Top, horizontalArrangement = checkboxArrangement, modifier = Modifier.padding(horizontal = space)) {
|
||||
Module(module.name in viewModel.checkedModules, viewModel::clicked, module, viewModel.icons, checkboxArrangement)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,36 +69,78 @@ internal fun ModuleList(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders [module]
|
||||
*/
|
||||
@Composable
|
||||
private fun RowScope.Module(
|
||||
private fun Module(
|
||||
checked: Boolean,
|
||||
onCheck: (ModuleName) -> Unit,
|
||||
columnPadding: Dp,
|
||||
moduleInfo: ModuleDTO,
|
||||
module: ModuleDTO,
|
||||
icons: ImmutableMap<ToolIdDTO, IconKey>,
|
||||
checkBoxArrangement: Arrangement.HorizontalOrVertical,
|
||||
) {
|
||||
val colModifier = Modifier.weight(1f)
|
||||
var subModuleOpened by remember { mutableStateOf(false) }
|
||||
val newText = remember { PySdkConfiguratorFrontendBundle.message("python.sdk.configurator.frontend.choose.modules.new") }
|
||||
val moduleName = moduleInfo.name
|
||||
CheckboxRow(
|
||||
moduleName,
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
onCheck(moduleName)
|
||||
},
|
||||
modifier = colModifier
|
||||
)
|
||||
val moduleName = module.name
|
||||
val checkBoxWidth = Modifier.padding(start = LocalCheckboxStyle.current.metrics.checkboxSize.width)
|
||||
|
||||
Row(colModifier.padding(start = columnPadding)) {
|
||||
val text = moduleInfo.existingPyVersion?.let { PySdkConfiguratorFrontendBundle.message("python.sdk.configurator.frontend.choose.modules.workspace.existing", it) }
|
||||
?: newText
|
||||
val icon = icons[moduleInfo.createdByTool]
|
||||
if (icon != null) {
|
||||
Icon(icon, text)
|
||||
}
|
||||
Text(text, Modifier.clickable(onClick = { onCheck(moduleName) }), maxLines = 1, softWrap = false)
|
||||
Row(verticalAlignment = Alignment.Top, horizontalArrangement = checkBoxArrangement) {
|
||||
ModuleRow(
|
||||
left = { modifier ->
|
||||
Row(modifier) {
|
||||
// TODO: Get real size instead of "invisible" checkbox
|
||||
OpenArrow(subModuleOpened, { subModuleOpened = !subModuleOpened },
|
||||
modifier = if (module.childModules.isEmpty()) {
|
||||
Modifier.alpha(0f)
|
||||
}
|
||||
else Modifier)
|
||||
Column(verticalArrangement = checkBoxArrangement) {
|
||||
CheckboxRow(
|
||||
moduleName,
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
onCheck(moduleName)
|
||||
},
|
||||
)
|
||||
if (subModuleOpened) {
|
||||
for (childModule in module.childModules) {
|
||||
Row {
|
||||
CheckboxRow(
|
||||
childModule,
|
||||
checked,
|
||||
onCheckedChange = {},
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
enabled = false,
|
||||
modifier = checkBoxWidth
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
right = { modifier ->
|
||||
Row(modifier) {
|
||||
val text = module.existingPyVersion?.let { PySdkConfiguratorFrontendBundle.message("python.sdk.configurator.frontend.choose.modules.workspace.existing", it) }
|
||||
?: newText
|
||||
val icon = icons[module.createdByTool]
|
||||
if (icon != null) {
|
||||
Icon(icon, text)
|
||||
}
|
||||
Text(text, Modifier.clickable(onClick = { onCheck(moduleName) }), maxLines = 1, softWrap = false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.ModuleRow(left: @Composable (modifier: Modifier) -> Unit, right: @Composable (modifier: Modifier) -> Unit) {
|
||||
val modifier = Modifier.weight(1f)
|
||||
left(modifier)
|
||||
Divider(Orientation.Vertical, color = JewelTheme.globalColors.borders.normal, thickness = 1.dp, modifier = Modifier.fillMaxHeight())
|
||||
right(modifier)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.jewel.ui.theme.comboBoxStyle
|
||||
internal fun OpenArrow(opened: Boolean, onOpenChangeState: (Boolean) -> Unit, modifier: Modifier = Modifier) {
|
||||
var opened by remember { mutableStateOf(opened) }
|
||||
val rotate = if (opened) 360f else 270f
|
||||
|
||||
Icon(key = JewelTheme.comboBoxStyle.icons.chevronDown,
|
||||
"",
|
||||
Modifier.rotate(rotate).clickable(onClick = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.intellij.python.sdkConfigurator.frontend
|
||||
|
||||
import androidx.compose.runtime.mutableStateSetOf
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateSet
|
||||
import com.intellij.python.common.tools.ToolId
|
||||
import com.intellij.python.common.tools.getIcon
|
||||
@@ -9,27 +10,38 @@ import com.intellij.python.sdkConfigurator.common.impl.ModuleName
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ModulesDTO
|
||||
import com.intellij.python.sdkConfigurator.common.impl.ToolIdDTO
|
||||
import kotlinx.collections.immutable.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.jewel.bridge.icon.fromPlatformIcon
|
||||
import org.jetbrains.jewel.ui.icon.IconKey
|
||||
import org.jetbrains.jewel.ui.icon.IntelliJIconKey
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* UI should display [checkBoxItems] (either enabled or disabled). On each click call [clicked].
|
||||
* Result can be taken from [checked]
|
||||
* UI should display [filteredModules] (either enabled or disabled). On each click call [clicked].
|
||||
* Show filter from [moduleFilter]
|
||||
* Result can be taken from [checkedModules].
|
||||
* While composable is displayed. call [processFilterUpdates]
|
||||
*/
|
||||
internal class ModulesViewModel(modulesDTO: ModulesDTO) {
|
||||
val icons: PersistentMap<ToolIdDTO, IconKey> = persistentMapOf(*modulesDTO.modules
|
||||
val icons: ImmutableMap<ToolIdDTO, IconKey> = persistentMapOf(*modulesDTO.modules
|
||||
.mapNotNull { module ->
|
||||
val toolId = module.createdByTool
|
||||
val icon = getIcon(ToolId(toolId))?.let { IntelliJIconKey.fromPlatformIcon(it.first, it.second) } ?: return@mapNotNull null
|
||||
Pair(toolId, icon)
|
||||
}.toTypedArray())
|
||||
|
||||
val checkBoxItems: PersistentList<ModuleDTO> = modulesDTO.modules.toPersistentList()
|
||||
val checked: SnapshotStateSet<ModuleName> = mutableStateSetOf()
|
||||
private val modules: List<ModuleDTO> = modulesDTO.modules.sortedBy { it.name }
|
||||
|
||||
var filteredModules: List<ModuleDTO> by mutableStateOf(modules)
|
||||
val checkedModules: SnapshotStateSet<ModuleName> = mutableStateSetOf()
|
||||
val moduleFilter = TextFieldState()
|
||||
|
||||
// parent -> children
|
||||
private val children: ImmutableMap<ModuleName, ImmutableSet<ModuleName>> = checkBoxItems.associate {
|
||||
private val children: ImmutableMap<ModuleName, ImmutableSet<ModuleName>> = filteredModules.associate {
|
||||
Pair(it.name, it.childModules.toPersistentSet())
|
||||
}.toPersistentMap()
|
||||
|
||||
@@ -41,13 +53,25 @@ internal class ModulesViewModel(modulesDTO: ModulesDTO) {
|
||||
fun clicked(module: ModuleName) {
|
||||
val what = parents[module] ?: module // Get parent if child module
|
||||
val toChange = setOf(what) + children.getOrDefault(what, emptySet()) // Get children if parent
|
||||
val checkBoxSet = what !in checked
|
||||
val checkBoxSet = what !in checkedModules
|
||||
if (checkBoxSet) {
|
||||
checked.addAll(toChange)
|
||||
checkedModules.addAll(toChange)
|
||||
}
|
||||
else {
|
||||
checked.removeAll(toChange)
|
||||
checkedModules.removeAll(toChange)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
suspend fun processFilterUpdates() {
|
||||
snapshotFlow { moduleFilter.text }
|
||||
.debounce(500.milliseconds)
|
||||
.collectLatest { filter ->
|
||||
withContext(Dispatchers.Default) {
|
||||
val filter = filter.trim()
|
||||
filteredModules = if (filter.isEmpty()) modules else modules.filter { filter in it.name }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ internal suspend fun askUser(project: Project, modules: ModulesDTO, onResult: (S
|
||||
withContext(Dispatchers.EDT) {
|
||||
val myDialog = MyDialog(project, viewModel)
|
||||
if (myDialog.showAndGet()) {
|
||||
onResult(viewModel.checked)
|
||||
onResult(viewModel.checkedModules)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,10 +38,7 @@ private class MyDialog(project: Project, private val viewModel: ModulesViewModel
|
||||
override fun createCenterPanel(): JComponent {
|
||||
enableNewSwingCompositing()
|
||||
return compose(focusOnClickInside = true, content = {
|
||||
ModuleList(viewModel.checkBoxItems,
|
||||
viewModel.icons,
|
||||
viewModel.checked,
|
||||
viewModel::clicked,
|
||||
ModuleList(viewModel,
|
||||
topLabel = message("python.sdk.configurator.frontend.choose.modules.text"),
|
||||
projectStructureLabel = message("python.sdk.configurator.frontend.choose.modules.project.structure"),
|
||||
environmentLabel = message("python.sdk.configurator.frontend.choose.modules.environment"))
|
||||
|
||||
Reference in New Issue
Block a user