Skip to content

Commit 40f7e48

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 40f7e48

File tree

10 files changed

+1099
-6
lines changed

10 files changed

+1099
-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,241 @@
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+
balloon.addListener(onClosed(view))
64+
65+
balloon.show(RelativePoint(event), Balloon.Position.above)
66+
67+
focusTextAre(view)
68+
}
69+
70+
private fun focusTextAre(view: TextAreaView) {
71+
val focusManager = IdeFocusManager.getInstance(editor.project)
72+
focusManager.doWhenFocusSettlesDown(onFocused(focusManager, view.textArea))
73+
}
74+
75+
private fun createBalloon(panel: JPanel): Balloon {
76+
return JBPopupFactory.getInstance()
77+
.createBalloonBuilder(panel)
78+
.setCloseButtonEnabled(true)
79+
.setBlockClicksThroughBalloon(true)
80+
.setAnimationCycle(0)
81+
.setHideOnKeyOutside(true)
82+
.setHideOnClickOutside(true)
83+
.setFillColor(panel.background)
84+
.setHideOnAction(false) // allow user to Ctrl+A & Ctrl+C
85+
.createBalloon()
86+
}
87+
88+
private fun onClosed(view: TextAreaView): JBPopupListener {
89+
return object : JBPopupListener {
90+
override fun beforeShown(event: LightweightWindowEvent) {
91+
// do nothing
92+
}
93+
94+
override fun onClosed(event: LightweightWindowEvent) {
95+
view.dispose()
96+
}
97+
}
98+
}
99+
100+
private fun onFocused(focusManager: IdeFocusManager, field: JTextComponent): ExpirableRunnable {
101+
return object : ExpirableRunnable {
102+
103+
override fun run() {
104+
focusManager.requestFocus(field, true)
105+
field.selectAll()
106+
}
107+
108+
override fun isExpired(): Boolean {
109+
return false
110+
}
111+
}
112+
}
113+
114+
private fun isMultiline(): Boolean {
115+
return value.contains('\n')
116+
}
117+
118+
private fun setValue(balloon: Balloon, textComponent: JTextComponent): Boolean {
119+
return if (isValid) {
120+
balloon.hide()
121+
setValue.invoke(textComponent.text)
122+
true
123+
} else {
124+
false
125+
}
126+
}
127+
128+
private inner class TextAreaView(
129+
private val value: String,
130+
private val balloon: Balloon,
131+
private val disposable: Disposable
132+
) {
133+
private val MIN_ROWS = 4
134+
private val MAX_COLUMNS = 64
135+
136+
lateinit var textArea: JTextArea
137+
private lateinit var applyButton: JButton
138+
private lateinit var keyListener: KeyListener
139+
140+
fun addTo(panel: JPanel) {
141+
val label = JBLabel("Value:")
142+
label.border = JBUI.Borders.empty(0, 3, 4, 0)
143+
panel.add(label, BorderLayout.NORTH)
144+
val textArea = JBTextArea(
145+
value,
146+
max(MIN_ROWS, value.length.floorDiv(MAX_COLUMNS) + 1), // textarea has text lines + 1
147+
MAX_COLUMNS - 1
148+
)
149+
textArea.lineWrap = !isMultiline() // have text area line wrap if content is not manually wrapped
150+
textArea.wrapStyleWord = true
151+
val scrolled = ScrollPaneFactory.createScrollPane(textArea, true)
152+
panel.add(scrolled, BorderLayout.CENTER)
153+
this.keyListener = onKeyPressed(textArea, balloon)
154+
textArea.addKeyListener(keyListener)
155+
this.textArea = textArea
156+
157+
val buttonPanel = JPanel(BorderLayout())
158+
buttonPanel.border = JBUI.Borders.empty(2, 0, 0, 0)
159+
panel.add(buttonPanel, BorderLayout.SOUTH)
160+
buttonPanel.add(
161+
createExplanationLabel("Shift & Return to insert a new line, Return to apply"),
162+
BorderLayout.CENTER
163+
)
164+
applyButton = JButton("Apply")
165+
applyButton.addMouseListener(onApply(textArea, balloon))
166+
buttonPanel.add(applyButton, BorderLayout.EAST)
167+
168+
addValidation(textArea, disposable)
169+
}
170+
171+
private fun addValidation(textComponent: JTextComponent, disposable: Disposable) {
172+
ComponentValidator(disposable)
173+
.withValidator(ValueValidator(textComponent))
174+
.installOn(textComponent)
175+
.andRegisterOnDocumentListener(textComponent)
176+
.revalidate()
177+
}
178+
179+
private fun onApply(textComponent: JTextComponent, balloon: Balloon): MouseListener {
180+
return object : MouseAdapter() {
181+
override fun mouseClicked(e: MouseEvent?) {
182+
setValue(balloon, textComponent)
183+
}
184+
}
185+
}
186+
187+
fun dispose() {
188+
textArea.removeKeyListener(keyListener)
189+
}
190+
191+
private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
192+
return object : KeyAdapter() {
193+
override fun keyPressed(e: KeyEvent) {
194+
when {
195+
KeyEvent.VK_ESCAPE == e.keyCode ->
196+
balloon.hide()
197+
198+
KeyEvent.VK_ENTER == e.keyCode
199+
&& (e.isShiftDown || e.isControlDown) -> {
200+
insertNewLineAtCaret(textComponent)
201+
}
202+
203+
KeyEvent.VK_ENTER == e.keyCode
204+
&& (!e.isShiftDown && !e.isControlDown) ->
205+
if (setValue(balloon, textComponent)) {
206+
e.consume()
207+
}
208+
}
209+
}
210+
}
211+
}
212+
213+
private inner class ValueValidator(private val textComponent: JTextComponent) : Supplier<ValidationInfo?> {
214+
215+
override fun get(): ValidationInfo? {
216+
if (!textComponent.isEnabled
217+
|| !textComponent.isVisible
218+
) {
219+
return null
220+
}
221+
return validate(textComponent.text)
222+
}
223+
224+
private fun validate(newValue: String): ValidationInfo? {
225+
val validation = when {
226+
StringUtil.isEmptyOrSpaces(newValue) ->
227+
ValidationInfo("Provide a value", textComponent).asWarning()
228+
229+
value == newValue ->
230+
ValidationInfo("Provide new value", textComponent).asWarning()
231+
232+
else ->
233+
null
234+
}
235+
this@StringInputBalloon.isValid = (validation == null)
236+
applyButton.setEnabled(validation == null)
237+
return validation
238+
}
239+
}
240+
}
241+
}

0 commit comments

Comments
 (0)