Skip to content

Commit 2efc0cd

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 2efc0cd

File tree

10 files changed

+1056
-6
lines changed

10 files changed

+1056
-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,276 @@
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) {
130+
if (isValid) {
131+
balloon.hide()
132+
setValue.invoke(textComponent.text)
133+
}
134+
}
135+
136+
private inner class ValueValidator(private val textComponent: JTextComponent) : Supplier<ValidationInfo?> {
137+
138+
override fun get(): ValidationInfo? {
139+
if (!textComponent.isEnabled
140+
|| !textComponent.isVisible
141+
) {
142+
return null
143+
}
144+
return validate(textComponent.text)
145+
}
146+
147+
private fun validate(newValue: String): ValidationInfo? {
148+
val validation = when {
149+
StringUtil.isEmptyOrSpaces(newValue) ->
150+
ValidationInfo("Provide a value", textComponent).asWarning()
151+
152+
value == newValue ->
153+
ValidationInfo("Provide new value", textComponent).asWarning()
154+
155+
else ->
156+
null
157+
}
158+
this@StringInputBalloon.isValid = (validation == null)
159+
return validation
160+
}
161+
}
162+
163+
interface CentralPanelView {
164+
165+
var textComponent: JTextComponent
166+
167+
fun addTo(panel: JPanel)
168+
fun dispose()
169+
}
170+
171+
inner class TextFieldPanel(private val value: String, private val balloon: Balloon) : CentralPanelView {
172+
173+
override lateinit var textComponent: JTextComponent
174+
private lateinit var keyListener: KeyListener
175+
176+
override fun addTo(panel: JPanel) {
177+
val label = JBLabel("Value:")
178+
label.border = JBUI.Borders.empty(0, 3, 0, 1)
179+
panel.add(label, BorderLayout.WEST)
180+
val textField = JBTextField(value)
181+
textField.preferredSize = Dimension(
182+
max(MAX_WIDTH, textField.preferredSize.width.toDouble()).toInt(),
183+
textField.preferredSize.height
184+
)
185+
panel.add(textField, BorderLayout.CENTER)
186+
keyListener = onKeyPressed(textField, balloon)
187+
textField.addKeyListener(keyListener)
188+
this.textComponent = textField
189+
}
190+
191+
override fun dispose() {
192+
textComponent.removeKeyListener(keyListener)
193+
}
194+
195+
private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
196+
return object : KeyAdapter() {
197+
override fun keyPressed(e: KeyEvent) {
198+
when (e.keyCode) {
199+
KeyEvent.VK_ESCAPE ->
200+
balloon.hide()
201+
202+
KeyEvent.VK_ENTER ->
203+
setValue(balloon, textComponent)
204+
}
205+
}
206+
}
207+
}
208+
}
209+
210+
inner class TextAreaPanel(private val value: String, private val balloon: Balloon) : CentralPanelView {
211+
212+
override lateinit var textComponent: JTextComponent
213+
private lateinit var keyListener: KeyListener
214+
215+
override fun addTo(panel: JPanel) {
216+
val label = JBLabel("Value:")
217+
label.border = JBUI.Borders.empty(0, 3, 4, 0)
218+
panel.add(label, BorderLayout.NORTH)
219+
val textArea = JBTextArea(
220+
value,
221+
value.length.floorDiv(MAX_CHARACTERS) + 1 + numOfNewLines(value), // textarea has text lines + 1
222+
MAX_CHARACTERS - 1
223+
)
224+
val scrolled = ScrollPaneFactory.createScrollPane(textArea, true)
225+
panel.add(scrolled, BorderLayout.CENTER)
226+
this.keyListener = onKeyPressed(textArea, balloon)
227+
textArea.addKeyListener(keyListener)
228+
this.textComponent = textArea
229+
230+
val buttonPanel = JPanel(BorderLayout())
231+
buttonPanel.border = JBUI.Borders.empty(2, 0, 0, 0)
232+
panel.add(buttonPanel, BorderLayout.SOUTH)
233+
buttonPanel.add(
234+
createExplanationLabel("Shift & Return to insert a new line, Return to apply"),
235+
BorderLayout.CENTER)
236+
val button = JButton("Apply")
237+
button.addMouseListener(onApply(textArea, balloon))
238+
buttonPanel.add(button, BorderLayout.EAST)
239+
}
240+
241+
private fun onApply(textComponent: JTextComponent, balloon: Balloon): MouseListener {
242+
return object: MouseAdapter() {
243+
override fun mouseClicked(e: MouseEvent?) {
244+
setValue(balloon, textComponent)
245+
}
246+
}
247+
}
248+
249+
override fun dispose() {
250+
textComponent.removeKeyListener(keyListener)
251+
}
252+
253+
private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
254+
return object : KeyAdapter() {
255+
override fun keyReleased(e: KeyEvent) {
256+
when {
257+
KeyEvent.VK_ESCAPE == e.keyCode ->
258+
balloon.hide()
259+
260+
KeyEvent.VK_ENTER == e.keyCode
261+
&& (e.isShiftDown || e.isControlDown) ->
262+
insertNewLineAtCaret(textComponent)
263+
264+
KeyEvent.VK_ENTER == e.keyCode
265+
&& (!e.isShiftDown && !e.isControlDown) ->
266+
setValue(balloon, textComponent)
267+
}
268+
}
269+
}
270+
}
271+
272+
private fun numOfNewLines(string: String): Int {
273+
return string.count { char -> char == '\n' }
274+
}
275+
}
276+
}

0 commit comments

Comments
 (0)