Skip to content

Commit 145ea90

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

File tree

10 files changed

+1062
-6
lines changed

10 files changed

+1062
-6
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,282 @@
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.editor.Editor
14+
import com.intellij.openapi.ui.ComponentValidator
15+
import com.intellij.openapi.ui.ValidationInfo
16+
import com.intellij.openapi.ui.popup.Balloon
17+
import com.intellij.openapi.ui.popup.JBPopupFactory
18+
import com.intellij.openapi.ui.popup.JBPopupListener
19+
import com.intellij.openapi.ui.popup.LightweightWindowEvent
20+
import com.intellij.openapi.util.Disposer
21+
import com.intellij.openapi.util.ExpirableRunnable
22+
import com.intellij.openapi.util.text.StringUtil
23+
import com.intellij.openapi.wm.IdeFocusManager
24+
import com.intellij.ui.ScrollPaneFactory
25+
import com.intellij.ui.awt.RelativePoint
26+
import com.intellij.ui.components.JBLabel
27+
import com.intellij.ui.components.JBTextArea
28+
import com.intellij.ui.components.JBTextField
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.Dimension
34+
import java.awt.event.KeyAdapter
35+
import java.awt.event.KeyEvent
36+
import java.awt.event.KeyListener
37+
import java.awt.event.MouseAdapter
38+
import java.awt.event.MouseEvent
39+
import java.awt.event.MouseListener
40+
import java.util.function.Supplier
41+
import javax.swing.JButton
42+
import javax.swing.JPanel
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+
companion object {
53+
private const val MAX_WIDTH = 220.0
54+
private const val MAX_CHARACTERS = 64
55+
}
56+
57+
private var isValid = false
58+
59+
fun show(event: MouseEvent) {
60+
val (field, balloon) = create()
61+
balloon.show(RelativePoint(event), Balloon.Position.above)
62+
val focusManager = IdeFocusManager.getInstance(editor.project)
63+
focusManager.doWhenFocusSettlesDown(onFocused(focusManager, field))
64+
}
65+
66+
private fun create(): Pair<JTextComponent, Balloon> {
67+
val panel = JPanel(BorderLayout())
68+
val balloon = createBalloon(panel)
69+
val textView = if (isMultiline()) {
70+
TextAreaPanel(value, balloon)
71+
} else {
72+
TextFieldPanel(value, balloon)
73+
}
74+
textView.addTo(panel)
75+
val disposable = Disposer.newDisposable()
76+
Disposer.register(balloon, disposable)
77+
ComponentValidator(disposable)
78+
.withValidator(ValueValidator(textView.textComponent))
79+
.installOn(textView.textComponent)
80+
.andRegisterOnDocumentListener(textView.textComponent)
81+
.revalidate()
82+
balloon.addListener(onClosed(textView))
83+
return Pair(textView.textComponent, balloon)
84+
}
85+
86+
private fun createBalloon(panel: JPanel): Balloon {
87+
return JBPopupFactory.getInstance()
88+
.createBalloonBuilder(panel)
89+
.setCloseButtonEnabled(true)
90+
.setBlockClicksThroughBalloon(true)
91+
.setAnimationCycle(0)
92+
.setHideOnKeyOutside(true)
93+
.setHideOnClickOutside(true)
94+
.setFillColor(panel.background)
95+
.setHideOnAction(false) // allow user to Ctrl+A & Ctrl+C
96+
.createBalloon()
97+
}
98+
99+
private fun onClosed(view: CentralPanelView): JBPopupListener {
100+
return object : JBPopupListener {
101+
override fun beforeShown(event: LightweightWindowEvent) {
102+
// do nothing
103+
}
104+
105+
override fun onClosed(event: LightweightWindowEvent) {
106+
view.dispose()
107+
}
108+
}
109+
}
110+
111+
private fun onFocused(focusManager: IdeFocusManager, field: JTextComponent): ExpirableRunnable {
112+
return object : ExpirableRunnable {
113+
114+
override fun run() {
115+
focusManager.requestFocus(field, true)
116+
field.selectAll()
117+
}
118+
119+
override fun isExpired(): Boolean {
120+
return false
121+
}
122+
}
123+
}
124+
125+
private fun isMultiline(): Boolean {
126+
return value.contains('\n')
127+
}
128+
129+
private fun setValue(balloon: Balloon, textComponent: JTextComponent): Boolean {
130+
return if (isValid) {
131+
balloon.hide()
132+
setValue.invoke(textComponent.text)
133+
true
134+
} else {
135+
false
136+
}
137+
}
138+
139+
private inner class ValueValidator(private val textComponent: JTextComponent) : Supplier<ValidationInfo?> {
140+
141+
override fun get(): ValidationInfo? {
142+
if (!textComponent.isEnabled
143+
|| !textComponent.isVisible
144+
) {
145+
return null
146+
}
147+
return validate(textComponent.text)
148+
}
149+
150+
private fun validate(newValue: String): ValidationInfo? {
151+
val validation = when {
152+
StringUtil.isEmptyOrSpaces(newValue) ->
153+
ValidationInfo("Provide a value", textComponent).asWarning()
154+
155+
value == newValue ->
156+
ValidationInfo("Provide new value", textComponent).asWarning()
157+
158+
else ->
159+
null
160+
}
161+
this@StringInputBalloon.isValid = (validation == null)
162+
return validation
163+
}
164+
}
165+
166+
interface CentralPanelView {
167+
168+
var textComponent: JTextComponent
169+
170+
fun addTo(panel: JPanel)
171+
fun dispose()
172+
}
173+
174+
inner class TextFieldPanel(private val value: String, private val balloon: Balloon) : CentralPanelView {
175+
176+
override lateinit var textComponent: JTextComponent
177+
private lateinit var keyListener: KeyListener
178+
179+
override fun addTo(panel: JPanel) {
180+
val label = JBLabel("Value:")
181+
label.border = JBUI.Borders.empty(0, 3, 0, 1)
182+
panel.add(label, BorderLayout.WEST)
183+
val textField = JBTextField(value)
184+
textField.preferredSize = Dimension(
185+
max(MAX_WIDTH, textField.preferredSize.width.toDouble()).toInt(),
186+
textField.preferredSize.height
187+
)
188+
panel.add(textField, BorderLayout.CENTER)
189+
keyListener = onKeyPressed(textField, balloon)
190+
textField.addKeyListener(keyListener)
191+
this.textComponent = textField
192+
}
193+
194+
override fun dispose() {
195+
textComponent.removeKeyListener(keyListener)
196+
}
197+
198+
private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
199+
return object : KeyAdapter() {
200+
override fun keyPressed(e: KeyEvent) {
201+
when (e.keyCode) {
202+
KeyEvent.VK_ESCAPE ->
203+
balloon.hide()
204+
205+
KeyEvent.VK_ENTER ->
206+
setValue(balloon, textComponent)
207+
}
208+
}
209+
}
210+
}
211+
}
212+
213+
inner class TextAreaPanel(private val value: String, private val balloon: Balloon) : CentralPanelView {
214+
215+
override lateinit var textComponent: JTextComponent
216+
private lateinit var keyListener: KeyListener
217+
218+
override fun addTo(panel: JPanel) {
219+
val label = JBLabel("Value:")
220+
label.border = JBUI.Borders.empty(0, 3, 4, 0)
221+
panel.add(label, BorderLayout.NORTH)
222+
val textArea = JBTextArea(
223+
value,
224+
value.length.floorDiv(MAX_CHARACTERS) + 1 + numOfNewLines(value), // textarea has text lines + 1
225+
MAX_CHARACTERS - 1
226+
)
227+
val scrolled = ScrollPaneFactory.createScrollPane(textArea, true)
228+
panel.add(scrolled, BorderLayout.CENTER)
229+
this.keyListener = onKeyPressed(textArea, balloon)
230+
textArea.addKeyListener(keyListener)
231+
this.textComponent = textArea
232+
233+
val buttonPanel = JPanel(BorderLayout())
234+
buttonPanel.border = JBUI.Borders.empty(2, 0, 0, 0)
235+
panel.add(buttonPanel, BorderLayout.SOUTH)
236+
buttonPanel.add(
237+
createExplanationLabel("Shift & Return to insert a new line, Return to apply"),
238+
BorderLayout.CENTER)
239+
val button = JButton("Apply")
240+
button.addMouseListener(onApply(textArea, balloon))
241+
buttonPanel.add(button, BorderLayout.EAST)
242+
}
243+
244+
private fun onApply(textComponent: JTextComponent, balloon: Balloon): MouseListener {
245+
return object: MouseAdapter() {
246+
override fun mouseClicked(e: MouseEvent?) {
247+
setValue(balloon, textComponent)
248+
}
249+
}
250+
}
251+
252+
override fun dispose() {
253+
textComponent.removeKeyListener(keyListener)
254+
}
255+
256+
private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
257+
return object : KeyAdapter() {
258+
override fun keyPressed(e: KeyEvent) {
259+
when {
260+
KeyEvent.VK_ESCAPE == e.keyCode ->
261+
balloon.hide()
262+
263+
KeyEvent.VK_ENTER == e.keyCode
264+
&& (e.isShiftDown || e.isControlDown) -> {
265+
insertNewLineAtCaret(textComponent)
266+
}
267+
268+
KeyEvent.VK_ENTER == e.keyCode
269+
&& (!e.isShiftDown && !e.isControlDown) ->
270+
if (setValue(balloon, textComponent)) {
271+
e.consume()
272+
}
273+
}
274+
}
275+
}
276+
}
277+
278+
private fun numOfNewLines(string: String): Int {
279+
return string.count { char -> char == '\n' }
280+
}
281+
}
282+
}

0 commit comments

Comments
 (0)