Skip to content

Commit eff7074

Browse files
committed
feat: show/allow setting decoded base64 values in Secrets, ConfigMaps (#663)
Signed-off-by: Andre Dietisheim <[email protected]>
1 parent 0a05c6a commit eff7074

File tree

12 files changed

+1396
-7
lines changed

12 files changed

+1396
-7
lines changed

.github/workflows/IJ.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
runs-on: ubuntu-latest
1515
strategy:
1616
matrix:
17-
IJ: [IC-2021.1, IC-2021.2, IC-2021.3, IC-2022.1, IC-2022.2, IC-2022.3, IC-2023.1, IC-2023.2, IC-2023.3]
17+
IJ: [IC-2022.1, IC-2022.2, IC-2022.3, IC-2023.1, IC-2023.2, IC-2023.3]
1818

1919
steps:
2020
- uses: actions/checkout@v2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.kubernetes
12+
13+
import com.intellij.ui.components.JBLabel
14+
import com.intellij.util.ui.JBUI
15+
import com.intellij.util.ui.UIUtil
16+
import javax.swing.text.JTextComponent
17+
18+
fun createExplanationLabel(text: String): JBLabel {
19+
return JBLabel(text).apply {
20+
componentStyle = UIUtil.ComponentStyle.SMALL
21+
foreground = JBUI.CurrentTheme.Link.Foreground.DISABLED
22+
}
23+
}
24+
25+
fun insertNewLineAtCaret(textComponent: JTextComponent) {
26+
val caretPosition = textComponent.caretPosition
27+
val newText = StringBuilder(textComponent.text).insert(caretPosition, '\n').toString()
28+
textComponent.text = newText
29+
textComponent.caretPosition = caretPosition + 1
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.kubernetes.balloon
12+
13+
import com.intellij.openapi.Disposable
14+
import com.intellij.openapi.editor.Editor
15+
import com.intellij.openapi.ui.ComponentValidator
16+
import com.intellij.openapi.ui.ValidationInfo
17+
import com.intellij.openapi.ui.popup.Balloon
18+
import com.intellij.openapi.ui.popup.JBPopupFactory
19+
import com.intellij.openapi.ui.popup.JBPopupListener
20+
import com.intellij.openapi.ui.popup.LightweightWindowEvent
21+
import com.intellij.openapi.util.Disposer
22+
import com.intellij.openapi.util.ExpirableRunnable
23+
import com.intellij.openapi.util.text.StringUtil
24+
import com.intellij.openapi.wm.IdeFocusManager
25+
import com.intellij.ui.ScrollPaneFactory
26+
import com.intellij.ui.awt.RelativePoint
27+
import com.intellij.ui.components.JBLabel
28+
import com.intellij.ui.components.JBTextArea
29+
import com.intellij.util.ui.JBUI
30+
import com.redhat.devtools.intellij.kubernetes.createExplanationLabel
31+
import com.redhat.devtools.intellij.kubernetes.insertNewLineAtCaret
32+
import java.awt.BorderLayout
33+
import java.awt.event.KeyAdapter
34+
import java.awt.event.KeyEvent
35+
import java.awt.event.KeyListener
36+
import java.awt.event.MouseAdapter
37+
import java.awt.event.MouseEvent
38+
import java.awt.event.MouseListener
39+
import java.util.function.Supplier
40+
import javax.swing.JButton
41+
import javax.swing.JPanel
42+
import javax.swing.JTextArea
43+
import javax.swing.text.JTextComponent
44+
import kotlin.math.max
45+
46+
class StringInputBalloon(
47+
private val value: String,
48+
private val setValue: (String) -> Unit,
49+
private val editor: Editor
50+
) {
51+
52+
private var isValid = false
53+
54+
fun show(event: MouseEvent) {
55+
val disposable = Disposer.newDisposable()
56+
val panel = JPanel(BorderLayout())
57+
58+
val balloon = createBalloon(panel)
59+
Disposer.register(balloon, disposable)
60+
61+
val view = TextAreaView(value, balloon, disposable)
62+
view.addTo(panel)
63+
64+
balloon.addListener(view.onClosed())
65+
balloon.show(RelativePoint(event), Balloon.Position.above)
66+
67+
val focusManager = IdeFocusManager.getInstance(editor.project)
68+
focusManager.doWhenFocusSettlesDown(view.onFocusSettled(focusManager))
69+
}
70+
71+
private fun createBalloon(panel: JPanel): Balloon {
72+
return JBPopupFactory.getInstance()
73+
.createBalloonBuilder(panel)
74+
.setCloseButtonEnabled(true)
75+
.setBlockClicksThroughBalloon(true)
76+
.setAnimationCycle(0)
77+
.setHideOnKeyOutside(true)
78+
.setHideOnClickOutside(true)
79+
.setFillColor(panel.background)
80+
.setHideOnAction(false) // allow user to Ctrl+A & Ctrl+C
81+
.createBalloon()
82+
}
83+
84+
private fun isMultiline(): Boolean {
85+
return value.contains('\n')
86+
}
87+
88+
private fun setValue(balloon: Balloon, textComponent: JTextComponent): Boolean {
89+
return if (isValid) {
90+
balloon.hide()
91+
setValue.invoke(textComponent.text)
92+
true
93+
} else {
94+
false
95+
}
96+
}
97+
98+
private inner class TextAreaView(
99+
private val value: String,
100+
private val balloon: Balloon,
101+
private val disposable: Disposable
102+
) {
103+
private val MIN_ROWS = 4
104+
private val MAX_COLUMNS = 64
105+
106+
private lateinit var textArea: JTextArea
107+
private lateinit var applyButton: JButton
108+
private lateinit var keyListener: KeyListener
109+
110+
fun addTo(panel: JPanel) {
111+
val label = JBLabel("Value:")
112+
label.border = JBUI.Borders.empty(0, 3, 4, 0)
113+
panel.add(label, BorderLayout.NORTH)
114+
val textArea = JBTextArea(
115+
value,
116+
max(MIN_ROWS, value.length.floorDiv(MAX_COLUMNS) + 1), // textarea has text lines + 1
117+
MAX_COLUMNS - 1
118+
)
119+
textArea.lineWrap = !isMultiline() // have text area line wrap if content is not manually wrapped
120+
textArea.wrapStyleWord = true
121+
val scrolled = ScrollPaneFactory.createScrollPane(textArea, true)
122+
panel.add(scrolled, BorderLayout.CENTER)
123+
this.keyListener = onKeyPressed(textArea, balloon)
124+
textArea.addKeyListener(keyListener)
125+
this.textArea = textArea
126+
127+
val buttonPanel = JPanel(BorderLayout())
128+
buttonPanel.border = JBUI.Borders.empty(2, 0, 0, 0)
129+
panel.add(buttonPanel, BorderLayout.SOUTH)
130+
buttonPanel.add(
131+
createExplanationLabel("Shift & Return to insert a new line, Return to apply"),
132+
BorderLayout.CENTER
133+
)
134+
applyButton = JButton("Apply")
135+
applyButton.addMouseListener(onApply(textArea, balloon))
136+
buttonPanel.add(applyButton, BorderLayout.EAST)
137+
138+
addValidation(textArea, disposable)
139+
}
140+
141+
fun onFocusSettled(focusManager: IdeFocusManager): ExpirableRunnable {
142+
return object : ExpirableRunnable {
143+
144+
override fun run() {
145+
focusManager.requestFocus(textArea, true)
146+
textArea.selectAll()
147+
}
148+
149+
override fun isExpired(): Boolean {
150+
return false
151+
}
152+
}
153+
}
154+
155+
private fun addValidation(textComponent: JTextComponent, disposable: Disposable) {
156+
ComponentValidator(disposable)
157+
.withValidator(ValueValidator(textComponent))
158+
.installOn(textComponent)
159+
.andRegisterOnDocumentListener(textComponent)
160+
.revalidate()
161+
}
162+
163+
private fun onApply(textComponent: JTextComponent, balloon: Balloon): MouseListener {
164+
return object : MouseAdapter() {
165+
override fun mouseClicked(e: MouseEvent?) {
166+
setValue(balloon, textComponent)
167+
}
168+
}
169+
}
170+
171+
private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
172+
return object : KeyAdapter() {
173+
override fun keyPressed(e: KeyEvent) {
174+
when {
175+
KeyEvent.VK_ESCAPE == e.keyCode ->
176+
balloon.hide()
177+
178+
KeyEvent.VK_ENTER == e.keyCode
179+
&& (e.isShiftDown || e.isControlDown) -> {
180+
insertNewLineAtCaret(textComponent)
181+
}
182+
183+
KeyEvent.VK_ENTER == e.keyCode
184+
&& (!e.isShiftDown && !e.isControlDown) ->
185+
if (setValue(balloon, textComponent)) {
186+
e.consume()
187+
}
188+
}
189+
}
190+
}
191+
}
192+
193+
fun onClosed(): JBPopupListener {
194+
return object : JBPopupListener {
195+
override fun beforeShown(event: LightweightWindowEvent) {
196+
// do nothing
197+
}
198+
199+
override fun onClosed(event: LightweightWindowEvent) {
200+
dispose()
201+
}
202+
}
203+
}
204+
205+
private fun dispose() {
206+
textArea.removeKeyListener(keyListener)
207+
}
208+
209+
private inner class ValueValidator(private val textComponent: JTextComponent) : Supplier<ValidationInfo?> {
210+
211+
override fun get(): ValidationInfo? {
212+
if (!textComponent.isEnabled
213+
|| !textComponent.isVisible
214+
) {
215+
return null
216+
}
217+
return validate(textComponent.text)
218+
}
219+
220+
private fun validate(newValue: String): ValidationInfo? {
221+
val validation = when {
222+
StringUtil.isEmptyOrSpaces(newValue) ->
223+
ValidationInfo("Provide a value", textComponent).asWarning()
224+
225+
value == newValue ->
226+
ValidationInfo("Provide new value", textComponent).asWarning()
227+
228+
else ->
229+
null
230+
}
231+
this@StringInputBalloon.isValid = (validation == null)
232+
applyButton.setEnabled(validation == null)
233+
return validation
234+
}
235+
}
236+
}
237+
}

0 commit comments

Comments
 (0)