Skip to content

Commit

Permalink
feat: show/allow setting decoded base64 values in Secrets, ConfigMaps (
Browse files Browse the repository at this point in the history
…#663)

Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Feb 22, 2024
1 parent 0a05c6a commit 27deb39
Show file tree
Hide file tree
Showing 10 changed files with 1,062 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/IJ.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
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]
IJ: [IC-2022.1, IC-2022.2, IC-2022.3, IC-2023.1, IC-2023.2, IC-2023.3]

steps:
- uses: actions/checkout@v2
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.kubernetes

import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import javax.swing.text.JTextComponent

fun createExplanationLabel(text: String): JBLabel {
return JBLabel(text).apply {
componentStyle = UIUtil.ComponentStyle.SMALL
foreground = JBUI.CurrentTheme.Link.Foreground.DISABLED
}
}

fun insertNewLineAtCaret(textComponent: JTextComponent) {
val caretPosition = textComponent.caretPosition
val newText = StringBuilder(textComponent.text).insert(caretPosition, '\n').toString()
textComponent.text = newText
textComponent.caretPosition = caretPosition + 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
/*******************************************************************************
* Copyright (c) 2024 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.kubernetes.balloon

import com.intellij.openapi.editor.Editor
import com.intellij.openapi.ui.ComponentValidator
import com.intellij.openapi.ui.ValidationInfo
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.JBPopupListener
import com.intellij.openapi.ui.popup.LightweightWindowEvent
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.ExpirableRunnable
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.ui.ScrollPaneFactory
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBTextArea
import com.intellij.ui.components.JBTextField
import com.intellij.util.ui.JBUI
import com.redhat.devtools.intellij.kubernetes.createExplanationLabel
import com.redhat.devtools.intellij.kubernetes.insertNewLineAtCaret
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.event.KeyAdapter
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.util.function.Supplier
import javax.swing.JButton
import javax.swing.JPanel
import javax.swing.text.JTextComponent
import kotlin.math.max
import kotlin.math.min

class StringInputBalloon(
private val value: String,
private val setValue: (String) -> Unit,
private val editor: Editor
) {

companion object {
private const val MAX_CHARACTERS = 64
}

private var isValid = false

fun show(event: MouseEvent) {
val (field, balloon) = create()
balloon.show(RelativePoint(event), Balloon.Position.above)
val focusManager = IdeFocusManager.getInstance(editor.project)
focusManager.doWhenFocusSettlesDown(onFocused(focusManager, field))
}

private fun create(): Pair<JTextComponent, Balloon> {
val panel = JPanel(BorderLayout())
val balloon = createBalloon(panel)
val textView = if (isMultiline()
|| value.length > MAX_CHARACTERS) {
TextAreaPanel(value, balloon)
} else {
TextFieldPanel(value, balloon)
}
textView.addTo(panel)
val disposable = Disposer.newDisposable()
Disposer.register(balloon, disposable)
ComponentValidator(disposable)
.withValidator(ValueValidator(textView.textComponent))
.installOn(textView.textComponent)
.andRegisterOnDocumentListener(textView.textComponent)
.revalidate()
balloon.addListener(onClosed(textView))
return Pair(textView.textComponent, balloon)
}

private fun createBalloon(panel: JPanel): Balloon {
return JBPopupFactory.getInstance()
.createBalloonBuilder(panel)
.setCloseButtonEnabled(true)
.setBlockClicksThroughBalloon(true)
.setAnimationCycle(0)
.setHideOnKeyOutside(true)
.setHideOnClickOutside(true)
.setFillColor(panel.background)
.setHideOnAction(false) // allow user to Ctrl+A & Ctrl+C
.createBalloon()
}

private fun onClosed(view: CentralPanelView): JBPopupListener {
return object : JBPopupListener {
override fun beforeShown(event: LightweightWindowEvent) {
// do nothing
}

override fun onClosed(event: LightweightWindowEvent) {
view.dispose()
}
}
}

private fun onFocused(focusManager: IdeFocusManager, field: JTextComponent): ExpirableRunnable {
return object : ExpirableRunnable {

override fun run() {
focusManager.requestFocus(field, true)
field.selectAll()
}

override fun isExpired(): Boolean {
return false
}
}
}

private fun isMultiline(): Boolean {
return value.contains('\n')
}

private fun setValue(balloon: Balloon, textComponent: JTextComponent): Boolean {
return if (isValid) {
balloon.hide()
setValue.invoke(textComponent.text)
true
} else {
false
}
}

private inner class ValueValidator(private val textComponent: JTextComponent) : Supplier<ValidationInfo?> {

override fun get(): ValidationInfo? {
if (!textComponent.isEnabled
|| !textComponent.isVisible
) {
return null
}
return validate(textComponent.text)
}

private fun validate(newValue: String): ValidationInfo? {
val validation = when {
StringUtil.isEmptyOrSpaces(newValue) ->
ValidationInfo("Provide a value", textComponent).asWarning()

value == newValue ->
ValidationInfo("Provide new value", textComponent).asWarning()

else ->
null
}
this@StringInputBalloon.isValid = (validation == null)
return validation
}
}

interface CentralPanelView {

var textComponent: JTextComponent

fun addTo(panel: JPanel)
fun dispose()
}

inner class TextFieldPanel(private val value: String, private val balloon: Balloon) : CentralPanelView {

private val TEXTFIELD_COLUMNS = 30

override lateinit var textComponent: JTextComponent
private lateinit var keyListener: KeyListener

override fun addTo(panel: JPanel) {
val label = JBLabel("Value:")
label.border = JBUI.Borders.empty(0, 3, 0, 1)
panel.add(label, BorderLayout.WEST)
val textField = JBTextField(value, TEXTFIELD_COLUMNS)
panel.add(textField, BorderLayout.CENTER)
keyListener = onKeyPressed(textField, balloon)
textField.addKeyListener(keyListener)
this.textComponent = textField
}

override fun dispose() {
textComponent.removeKeyListener(keyListener)
}

private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
return object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
when (e.keyCode) {
KeyEvent.VK_ESCAPE ->
balloon.hide()

KeyEvent.VK_ENTER ->
setValue(balloon, textComponent)
}
}
}
}
}

inner class TextAreaPanel(private val value: String, private val balloon: Balloon) : CentralPanelView {

override lateinit var textComponent: JTextComponent
private lateinit var keyListener: KeyListener

override fun addTo(panel: JPanel) {
val label = JBLabel("Value:")
label.border = JBUI.Borders.empty(0, 3, 4, 0)
panel.add(label, BorderLayout.NORTH)
val textArea = JBTextArea(
value,
value.length.floorDiv(MAX_CHARACTERS) + 1 + numOfNewLines(value), // textarea has text lines + 1
MAX_CHARACTERS - 1
)
textArea.lineWrap = true
val scrolled = ScrollPaneFactory.createScrollPane(textArea, true)
panel.add(scrolled, BorderLayout.CENTER)
this.keyListener = onKeyPressed(textArea, balloon)
textArea.addKeyListener(keyListener)
this.textComponent = textArea

val buttonPanel = JPanel(BorderLayout())
buttonPanel.border = JBUI.Borders.empty(2, 0, 0, 0)
panel.add(buttonPanel, BorderLayout.SOUTH)
buttonPanel.add(
createExplanationLabel("Shift & Return to insert a new line, Return to apply"),
BorderLayout.CENTER)
val button = JButton("Apply")
button.addMouseListener(onApply(textArea, balloon))
buttonPanel.add(button, BorderLayout.EAST)
}

private fun onApply(textComponent: JTextComponent, balloon: Balloon): MouseListener {
return object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent?) {
setValue(balloon, textComponent)
}
}
}

override fun dispose() {
textComponent.removeKeyListener(keyListener)
}

private fun onKeyPressed(textComponent: JTextComponent, balloon: Balloon): KeyListener {
return object : KeyAdapter() {
override fun keyPressed(e: KeyEvent) {
when {
KeyEvent.VK_ESCAPE == e.keyCode ->
balloon.hide()

KeyEvent.VK_ENTER == e.keyCode
&& (e.isShiftDown || e.isControlDown) -> {
insertNewLineAtCaret(textComponent)
}

KeyEvent.VK_ENTER == e.keyCode
&& (!e.isShiftDown && !e.isControlDown) ->
if (setValue(balloon, textComponent)) {
e.consume()
}
}
}
}
}

private fun numOfNewLines(string: String): Int {
return string.count { char -> char == '\n' }
}
}
}
Loading

0 comments on commit 27deb39

Please sign in to comment.