mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 05:52:35 +08:00
fix(jetbrains): make migration wizard modal
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Hide the JetBrains migration wizard behind a true modal overlay and restore its width after tool-window resizing.
|
||||
+15
-7
@@ -127,6 +127,7 @@ class SessionUi(
|
||||
private lateinit var prompt: PromptPanel
|
||||
private lateinit var load: LoadingPanel
|
||||
private lateinit var migrationOverlay: MigrationOverlayPanel
|
||||
private var modalFocus: (() -> JComponent)? = null
|
||||
private var style = SessionEditorStyle.current()
|
||||
|
||||
init {
|
||||
@@ -159,13 +160,15 @@ class SessionUi(
|
||||
internal fun currentStyle() = style
|
||||
|
||||
val defaultFocusedComponent: JComponent get() {
|
||||
val state = migration.state.value
|
||||
if (state !is MigrationUiState.Hidden && root.blocker.isVisible) {
|
||||
return migrationOverlay.preferredFocusComponent()
|
||||
}
|
||||
modalFocus?.invoke()?.let { return it }
|
||||
return prompt.defaultFocusedComponent
|
||||
}
|
||||
|
||||
internal fun setModalContent(content: JComponent?, focus: (() -> JComponent)? = null) {
|
||||
modalFocus = if (content == null) null else focus
|
||||
root.setModalContent(content)
|
||||
}
|
||||
|
||||
private fun buildUi() {
|
||||
root = SessionRootPanel()
|
||||
|
||||
@@ -176,7 +179,12 @@ class SessionUi(
|
||||
onStart = { sel -> migration.start(sel) }
|
||||
onForce = { ids -> migration.force(ids) }
|
||||
}
|
||||
root.setBlocker(migrationOverlay)
|
||||
migrationOverlay.border = JBUI.Borders.empty(
|
||||
JBUI.scale(SessionUiStyle.View.Prompt.PANEL_VERTICAL_PADDING),
|
||||
JBUI.scale(SessionUiStyle.View.Prompt.PANEL_HORIZONTAL_PADDING),
|
||||
JBUI.scale(SessionUiStyle.View.Prompt.PANEL_VERTICAL_PADDING),
|
||||
JBUI.scale(SessionUiStyle.View.Prompt.PANEL_HORIZONTAL_PADDING),
|
||||
)
|
||||
|
||||
account = SessionAccountOverlay(
|
||||
select = { org -> controller.selectOrganization(org) },
|
||||
@@ -336,12 +344,12 @@ class SessionUi(
|
||||
when (state) {
|
||||
is MigrationUiState.Hidden -> {
|
||||
if (root.blocker.isVisible) LOG.info("Migration wizard: overlay hidden session=${id ?: cacheKey ?: "new"}")
|
||||
root.setBlocked(false)
|
||||
setModalContent(null)
|
||||
}
|
||||
is MigrationUiState.Needed -> {
|
||||
if (!root.blocker.isVisible) LOG.info("Migration wizard: overlay shown session=${id ?: cacheKey ?: "new"} phase=${state.phase}")
|
||||
migrationOverlay.update(state)
|
||||
root.setBlocked(true)
|
||||
setModalContent(migrationOverlay) { migrationOverlay.preferredFocusComponent() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-4
@@ -1,7 +1,12 @@
|
||||
package ai.kilocode.client.session.ui
|
||||
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.layout.HAlign
|
||||
import ai.kilocode.client.ui.layout.VAlign
|
||||
import ai.kilocode.client.ui.layout.align
|
||||
import com.intellij.util.ui.JBDimension
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Dimension
|
||||
import java.awt.Rectangle
|
||||
import javax.swing.JComponent
|
||||
@@ -31,11 +36,15 @@ class SessionRootPanel : JLayeredPane() {
|
||||
overlay.addOverlay(child, bounds)
|
||||
}
|
||||
|
||||
fun setBlocker(child: JComponent) {
|
||||
fun setModalContent(child: JComponent?) {
|
||||
blocker.removeAll()
|
||||
blocker.add(child)
|
||||
if (child != null) blocker.add(child.align(HAlign.CENTER, VAlign.CENTER), BorderLayout.CENTER)
|
||||
blocker.isVisible = child != null
|
||||
if (child != null) blocker.requestFocusInWindow()
|
||||
blocker.revalidate()
|
||||
blocker.repaint()
|
||||
revalidate()
|
||||
repaint()
|
||||
}
|
||||
|
||||
fun setBlocked(value: Boolean) {
|
||||
@@ -105,14 +114,24 @@ class SessionRootPanel : JLayeredPane() {
|
||||
*/
|
||||
class Blocker : JPanel() {
|
||||
init {
|
||||
layout = java.awt.BorderLayout()
|
||||
isOpaque = false
|
||||
layout = BorderLayout()
|
||||
isFocusable = true
|
||||
}
|
||||
|
||||
override fun updateUI() {
|
||||
super.updateUI()
|
||||
background = UiStyle.Colors.bg()
|
||||
isOpaque = true
|
||||
}
|
||||
|
||||
override fun contains(x: Int, y: Int): Boolean {
|
||||
if (!isVisible) return false
|
||||
return super.contains(x, y)
|
||||
}
|
||||
|
||||
override fun doLayout() {
|
||||
super.doLayout()
|
||||
components.forEach { it.doLayout() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+31
-3
@@ -2,8 +2,12 @@ package ai.kilocode.client.migration
|
||||
|
||||
import ai.kilocode.client.session.SessionUiTestBase
|
||||
import ai.kilocode.client.session.ui.SessionRootPanel
|
||||
import ai.kilocode.client.session.ui.prompt.PromptPanel
|
||||
import ai.kilocode.client.migration.ui.MigrationOverlayPanel
|
||||
import ai.kilocode.client.migration.ui.MigrationWizardPanel
|
||||
import ai.kilocode.rpc.dto.LegacyMigrationDetectionDto
|
||||
import ai.kilocode.rpc.dto.MigrationProviderInfoDto
|
||||
import java.awt.Rectangle
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
class SessionUiMigrationTest : SessionUiTestBase() {
|
||||
@@ -29,7 +33,19 @@ class SessionUiMigrationTest : SessionUiTestBase() {
|
||||
val root = find<SessionRootPanel>(ui)
|
||||
fakeMigration._state.value = MigrationUiState.Needed(detection = sampleDetection())
|
||||
settle()
|
||||
layout()
|
||||
assertTrue("blocker should be visible", root.blocker.isVisible)
|
||||
assertTrue("blocker should be opaque", root.blocker.isOpaque)
|
||||
assertEquals(Rectangle(0, 0, root.width, root.height), root.blocker.bounds)
|
||||
assertEquals(1, root.blocker.componentCount)
|
||||
}
|
||||
|
||||
fun `test migration opens on selection screen with keep file checked`() {
|
||||
fakeMigration._state.value = MigrationUiState.Needed(detection = sampleDetection())
|
||||
settle()
|
||||
|
||||
val wizard = find<MigrationWizardPanel>(ui)
|
||||
assertTrue(wizard.keepLegacySettingsFileSelectedForTest())
|
||||
}
|
||||
|
||||
fun `test hidden state after visible hides blocker`() {
|
||||
@@ -41,6 +57,7 @@ class SessionUiMigrationTest : SessionUiTestBase() {
|
||||
fakeMigration._state.value = MigrationUiState.Hidden
|
||||
settle()
|
||||
assertFalse(root.blocker.isVisible)
|
||||
assertEquals(0, root.blocker.componentCount)
|
||||
}
|
||||
|
||||
fun `test two session UIs sharing one controller both react to state change`() {
|
||||
@@ -64,9 +81,20 @@ class SessionUiMigrationTest : SessionUiTestBase() {
|
||||
settle()
|
||||
val root = find<SessionRootPanel>(ui)
|
||||
assertTrue("blocker should be visible for defaultFocused test", root.blocker.isVisible)
|
||||
// defaultFocusedComponent should not throw and should not be the prompt editor
|
||||
val focused = ui.defaultFocusedComponent
|
||||
assertNotNull(focused)
|
||||
val overlay = find<MigrationOverlayPanel>(ui)
|
||||
assertSame(overlay.preferredFocusComponent(), ui.defaultFocusedComponent)
|
||||
assertNotSame(find<PromptPanel>(ui).defaultFocusedComponent, ui.defaultFocusedComponent)
|
||||
}
|
||||
|
||||
fun `test migration modal covers prompt with opaque background`() {
|
||||
fakeMigration._state.value = MigrationUiState.Needed(detection = sampleDetection())
|
||||
settle()
|
||||
layout()
|
||||
val root = find<SessionRootPanel>(ui)
|
||||
|
||||
assertTrue(root.blocker.isVisible)
|
||||
assertTrue(root.blocker.isOpaque)
|
||||
assertEquals(Rectangle(0, 0, root.width, root.height), root.blocker.bounds)
|
||||
}
|
||||
|
||||
private fun sampleDetection() = LegacyMigrationDetectionDto(
|
||||
|
||||
+31
@@ -1,5 +1,6 @@
|
||||
package ai.kilocode.client.session.ui
|
||||
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import java.awt.Dimension
|
||||
@@ -26,6 +27,13 @@ class SessionRootPanelTest : BasePlatformTestCase() {
|
||||
assertFalse(root.blocker.isVisible)
|
||||
}
|
||||
|
||||
fun `test blocker is opaque and uses panel background`() {
|
||||
val root = SessionRootPanel()
|
||||
|
||||
assertTrue(root.blocker.isOpaque)
|
||||
assertEquals(UiStyle.Colors.bg(), root.blocker.background)
|
||||
}
|
||||
|
||||
fun `test root layout fills all immediate children`() {
|
||||
val root = SessionRootPanel().apply {
|
||||
setSize(320, 180)
|
||||
@@ -76,6 +84,29 @@ class SessionRootPanelTest : BasePlatformTestCase() {
|
||||
assertFalse(root.blocker.isVisible)
|
||||
}
|
||||
|
||||
fun `test modal content is centered inside blocker`() {
|
||||
val root = SessionRootPanel().apply { setSize(200, 100) }
|
||||
val child = Probe()
|
||||
|
||||
root.setModalContent(child)
|
||||
root.doLayout()
|
||||
|
||||
assertTrue(root.blocker.isVisible)
|
||||
assertEquals(1, root.blocker.componentCount)
|
||||
assertEquals(Rectangle(60, 38, 80, 24), child.bounds)
|
||||
}
|
||||
|
||||
fun `test clearing modal content hides and removes blocker children`() {
|
||||
val root = SessionRootPanel().apply { setSize(200, 100) }
|
||||
root.setModalContent(Probe())
|
||||
root.doLayout()
|
||||
|
||||
root.setModalContent(null)
|
||||
|
||||
assertFalse(root.blocker.isVisible)
|
||||
assertEquals(0, root.blocker.componentCount)
|
||||
}
|
||||
|
||||
fun `test blocker contains returns false when hidden`() {
|
||||
val root = SessionRootPanel().apply { setSize(200, 100) }
|
||||
root.doLayout()
|
||||
|
||||
Reference in New Issue
Block a user