From 8d5589839d6af3fd36ee71aaa425312e7e0a6ea7 Mon Sep 17 00:00:00 2001 From: Andrea Vinci Date: Tue, 8 Dec 2020 07:57:17 +0000 Subject: [PATCH] Add readonly mode (#163) * Add readonly button * Add global state * Hide buttons on readonly mode * Make readonly button more generic * Fix style for toggle buttons * Add readonly mode to the list of features * Fix lint --- Readme.md | 1 + .../kotlin/insulator/helper/GlobalState.kt | 15 +++++++++ .../kotlin/insulator/ui/component/Button.kt | 31 +++++++++++++++++-- .../kotlin/insulator/ui/style/ButtonStyle.kt | 11 +++++-- .../kotlin/insulator/views/main/MainView.kt | 5 ++- .../main/consumergroup/ConsumerGroupView.kt | 3 +- .../main/schemaregistry/ListSchemaView.kt | 3 +- .../views/main/schemaregistry/SchemaView.kt | 3 +- .../views/main/topic/ListTopicView.kt | 3 +- .../insulator/views/main/topic/TopicView.kt | 25 +++------------ docs/_wiki/Home.md | 1 + docs/_wiki/Producer/Producer.md | 4 +-- 12 files changed, 72 insertions(+), 33 deletions(-) create mode 100644 app/src/main/kotlin/insulator/helper/GlobalState.kt diff --git a/Readme.md b/Readme.md index 2fd38bfb..f487ac2c 100644 --- a/Readme.md +++ b/Readme.md @@ -95,6 +95,7 @@ Download the binary from the latest release for your OS. Learn more [here](https * Windows, macOS and Linux ready. * **Dark/Light theme** * **Auto-update** +* **ReadOnly mode** ## Development diff --git a/app/src/main/kotlin/insulator/helper/GlobalState.kt b/app/src/main/kotlin/insulator/helper/GlobalState.kt new file mode 100644 index 00000000..063b866d --- /dev/null +++ b/app/src/main/kotlin/insulator/helper/GlobalState.kt @@ -0,0 +1,15 @@ +package insulator.helper + +import javafx.beans.property.SimpleBooleanProperty +import javafx.scene.Node +import tornadofx.visibleWhen + +object GlobalState { + val isReadOnlyProperty = SimpleBooleanProperty(true) +} + +fun T.hideOnReadonly(): T { + this.visibleWhen { GlobalState.isReadOnlyProperty.not() } + this.managedProperty().bind(GlobalState.isReadOnlyProperty.not()) + return this +} diff --git a/app/src/main/kotlin/insulator/ui/component/Button.kt b/app/src/main/kotlin/insulator/ui/component/Button.kt index 86e5eb3a..bc096ea1 100644 --- a/app/src/main/kotlin/insulator/ui/component/Button.kt +++ b/app/src/main/kotlin/insulator/ui/component/Button.kt @@ -3,21 +3,29 @@ package insulator.ui.component import insulator.helper.dispatch import insulator.ui.style.ButtonStyle import insulator.ui.style.theme +import javafx.beans.binding.Bindings +import javafx.beans.property.BooleanProperty import javafx.beans.property.SimpleBooleanProperty import javafx.beans.value.ObservableValue import javafx.event.EventTarget import javafx.geometry.Pos import javafx.scene.control.Alert +import javafx.scene.control.Button import javafx.scene.control.ButtonType +import javafx.scene.paint.Color import tornadofx.SVGIcon import tornadofx.action import tornadofx.addClass import tornadofx.button +import tornadofx.onChange +import tornadofx.toggleClass // from https://material.io/resources/icons const val ICON_MENU_SVG = "M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" const val ICON_SETTINGS_SVG = "M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z" const val ICON_THEME_SVG = "M20 15.31L23.31 12 20 8.69V4h-4.69L12 .69 8.69 4H4v4.69L.69 12 4 15.31V20h4.69L12 23.31 15.31 20H20v-4.69zM12 18V6c3.31 0 6 2.69 6 6s-2.69 6-6 6z" +const val ICON_LOCK_SVG = "M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z" +const val ICON_UNLOCK_SVG = "M12 17c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm6-9h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6h1.9c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm0 12H6V10h12v10z" fun EventTarget.refreshButton(name: String, refreshOp: suspend () -> Unit) = button("Refresh") { @@ -26,11 +34,30 @@ fun EventTarget.refreshButton(name: String, refreshOp: suspend () -> Unit) = addClass(ButtonStyle.blueButton) } +fun EventTarget.readOnlyButton(isReadOnlyProperty: BooleanProperty): Button { + val getIcon = { if (isReadOnlyProperty.value) ICON_LOCK_SVG else ICON_UNLOCK_SVG } + val color = { if (!isReadOnlyProperty.value) Color.RED else theme.mainColor } + val text = { if (isReadOnlyProperty.value) "ReadOnly mode" else "Read/Write mode" } + val button = button { + textProperty().bind(Bindings.createStringBinding(text, isReadOnlyProperty)) + action { isReadOnlyProperty.set(isReadOnlyProperty.not().value) } + graphicProperty().bind( + Bindings.createObjectBinding({ SVGIcon(getIcon(), 22.0, color()) }, isReadOnlyProperty) + ) + addClass(ButtonStyle.toggleButton) + } + isReadOnlyProperty.onChange { + button.toggleClass(ButtonStyle.alertButton, !isReadOnlyProperty.value) + } + return button +} + fun EventTarget.themeButton(op: () -> Unit) = button { + text = "Change theme" action(op) - graphic = SVGIcon(ICON_THEME_SVG, 20.0, theme.mainColor) - addClass(ButtonStyle.burgerButton) + graphic = SVGIcon(ICON_THEME_SVG, 22.0, theme.mainColor) + addClass(ButtonStyle.toggleButton) } fun EventTarget.settingsButton(op: () -> Unit) = button { diff --git a/app/src/main/kotlin/insulator/ui/style/ButtonStyle.kt b/app/src/main/kotlin/insulator/ui/style/ButtonStyle.kt index 64279ab3..2ec305d6 100644 --- a/app/src/main/kotlin/insulator/ui/style/ButtonStyle.kt +++ b/app/src/main/kotlin/insulator/ui/style/ButtonStyle.kt @@ -6,12 +6,13 @@ import tornadofx.box import tornadofx.cssclass import tornadofx.em import tornadofx.multi +import tornadofx.px class ButtonStyle : Stylesheet() { companion object { val alertButton by cssclass("alert-button") val blueButton by cssclass("blue-button") - val burgerButton by cssclass("burger-button") + val toggleButton by cssclass("toggle-button") val settingsButton by cssclass("settings-button") } @@ -38,12 +39,16 @@ class ButtonStyle : Stylesheet() { backgroundRadius = multi(box(5.em)) backgroundColor = multi(theme.mainColorDark) } - and(burgerButton) { - textFill = theme.mainColor + and(toggleButton) { backgroundRadius = multi(box(5.em)) + textFill = theme.black backgroundColor = multi(theme.mainColorDark) } } + and(toggleButton) { + textFill = theme.mainColor + minWidth = 200.px + } and(alertButton) { textFill = theme.alertColor } and(blueButton) { textFill = theme.blueColor } } diff --git a/app/src/main/kotlin/insulator/views/main/MainView.kt b/app/src/main/kotlin/insulator/views/main/MainView.kt index 233ada2b..3e2a07cf 100644 --- a/app/src/main/kotlin/insulator/views/main/MainView.kt +++ b/app/src/main/kotlin/insulator/views/main/MainView.kt @@ -1,12 +1,14 @@ package insulator.views.main import insulator.di.ClusterScope +import insulator.helper.GlobalState import insulator.helper.dispatch import insulator.kafka.model.Cluster import insulator.ui.ThemeHelper import insulator.ui.common.InsulatorView import insulator.ui.component.h1 import insulator.ui.component.h2 +import insulator.ui.component.readOnlyButton import insulator.ui.component.themeButton import insulator.ui.style.ButtonStyle.Companion.alertButton import insulator.ui.style.MainViewStyle @@ -101,7 +103,8 @@ class MainView @Inject constructor( menuItem("Schema Registry", ICON_REGISTRY, "sidebar-item-schema-registry") { viewModel.setContentList(ListSchemaView::class, currentWindow) } menuItem("Consumer Groups", ICON_CONSUMERS, "sidebar-item-consumer-group") { viewModel.setContentList(ListConsumerGroupView::class, currentWindow) } } - bottom = hbox(alignment = Pos.CENTER) { + bottom = vbox(alignment = Pos.CENTER, spacing = 10) { + readOnlyButton(GlobalState.isReadOnlyProperty) themeButton { themeHelper.dispatch { changeTheme() } } } addClass(MainViewStyle.sidebar) diff --git a/app/src/main/kotlin/insulator/views/main/consumergroup/ConsumerGroupView.kt b/app/src/main/kotlin/insulator/views/main/consumergroup/ConsumerGroupView.kt index e214c4ee..3090999f 100644 --- a/app/src/main/kotlin/insulator/views/main/consumergroup/ConsumerGroupView.kt +++ b/app/src/main/kotlin/insulator/views/main/consumergroup/ConsumerGroupView.kt @@ -2,6 +2,7 @@ package insulator.views.main.consumergroup import insulator.di.ConsumerGroupScope import insulator.helper.dispatch +import insulator.helper.hideOnReadonly import insulator.ui.common.InsulatorTabView import insulator.ui.component.appBar import insulator.ui.component.confirmationButton @@ -59,5 +60,5 @@ class ConsumerGroupView @Inject constructor(override val viewModel: ConsumerGrou confirmationButton("Delete", "The consumer group \"${viewModel.nameProperty.value}\" will be removed.") { viewModel.dispatch { delete() } closeTab() - } + }.hideOnReadonly() } diff --git a/app/src/main/kotlin/insulator/views/main/schemaregistry/ListSchemaView.kt b/app/src/main/kotlin/insulator/views/main/schemaregistry/ListSchemaView.kt index a116722f..b539cf6b 100644 --- a/app/src/main/kotlin/insulator/views/main/schemaregistry/ListSchemaView.kt +++ b/app/src/main/kotlin/insulator/views/main/schemaregistry/ListSchemaView.kt @@ -2,6 +2,7 @@ package insulator.views.main.schemaregistry import insulator.di.ClusterScope import insulator.helper.dispatch +import insulator.helper.hideOnReadonly import insulator.ui.common.InsulatorView import insulator.ui.component.appBar import insulator.ui.component.refreshButton @@ -63,5 +64,5 @@ class ListSchemaView @Inject constructor( button("Create schema") { action { viewModel.createNewSchema(currentWindow) } id = "button-create-schema" - } + }.hideOnReadonly() } diff --git a/app/src/main/kotlin/insulator/views/main/schemaregistry/SchemaView.kt b/app/src/main/kotlin/insulator/views/main/schemaregistry/SchemaView.kt index 289bfc34..05b4b020 100644 --- a/app/src/main/kotlin/insulator/views/main/schemaregistry/SchemaView.kt +++ b/app/src/main/kotlin/insulator/views/main/schemaregistry/SchemaView.kt @@ -1,6 +1,7 @@ package insulator.views.main.schemaregistry import insulator.di.SubjectScope +import insulator.helper.hideOnReadonly import insulator.jsonhelper.JsonFormatter import insulator.kafka.model.Schema import insulator.ui.common.InsulatorTabView @@ -49,5 +50,5 @@ class SchemaView @Inject constructor( confirmationButton("Delete", "The schema \"${viewModel.nameProperty.value}\" will be removed.") { viewModel.delete() closeTab() - } + }.hideOnReadonly() } diff --git a/app/src/main/kotlin/insulator/views/main/topic/ListTopicView.kt b/app/src/main/kotlin/insulator/views/main/topic/ListTopicView.kt index ac77f75e..9a5ddac8 100644 --- a/app/src/main/kotlin/insulator/views/main/topic/ListTopicView.kt +++ b/app/src/main/kotlin/insulator/views/main/topic/ListTopicView.kt @@ -2,6 +2,7 @@ package insulator.views.main.topic import insulator.di.ClusterScope import insulator.helper.dispatch +import insulator.helper.hideOnReadonly import insulator.ui.common.InsulatorView import insulator.ui.component.action import insulator.ui.component.appBar @@ -54,7 +55,7 @@ class ListTopicView @Inject constructor(override val viewModel: ListTopicViewMod button("Create topic") { id = "button-create-topic" action { viewModel.createNewTopic(currentWindow) } - } + }.hideOnReadonly() override fun onError(throwable: Throwable) { replaceWith() diff --git a/app/src/main/kotlin/insulator/views/main/topic/TopicView.kt b/app/src/main/kotlin/insulator/views/main/topic/TopicView.kt index 2780b228..004478d5 100644 --- a/app/src/main/kotlin/insulator/views/main/topic/TopicView.kt +++ b/app/src/main/kotlin/insulator/views/main/topic/TopicView.kt @@ -2,6 +2,7 @@ package insulator.views.main.topic import insulator.di.TopicScope import insulator.helper.dispatch +import insulator.helper.hideOnReadonly import insulator.helper.toObservable import insulator.kafka.consumer.ConsumeFrom import insulator.kafka.consumer.DeserializationFormat @@ -22,25 +23,7 @@ import javafx.scene.control.Control import javafx.scene.control.SelectionMode import javafx.scene.control.TableCell import javafx.scene.layout.Priority -import tornadofx.action -import tornadofx.addClass -import tornadofx.bindSelected -import tornadofx.borderpane -import tornadofx.button -import tornadofx.column -import tornadofx.combobox -import tornadofx.contextmenu -import tornadofx.enableWhen -import tornadofx.hbox -import tornadofx.hgrow -import tornadofx.item -import tornadofx.minus -import tornadofx.onDoubleClick -import tornadofx.stringBinding -import tornadofx.tableview -import tornadofx.text -import tornadofx.vbox -import tornadofx.vgrow +import tornadofx.* // ktlint-disable no-wildcard-imports import javax.inject.Inject @TopicScope @@ -78,7 +61,7 @@ class TopicView @Inject constructor( id = "button-produce" action { viewModel.showProducerView(currentWindow) } addClass(ButtonStyle.blueButton) - } + }.hideOnReadonly() } private fun EventTarget.consumeStopButton() { @@ -114,7 +97,7 @@ class TopicView @Inject constructor( confirmationButton("Delete", "The topic \"${viewModel.nameProperty.value}\" will be removed.") { viewModel.dispatch { delete() } closeTab() - }.enableWhen(viewModel.consumerViewModel.isConsumingProperty.not()) + }.enableWhen(viewModel.consumerViewModel.isConsumingProperty.not()).hideOnReadonly() private fun EventTarget.showInfoButton() = button("Info") { id = "button-info" diff --git a/docs/_wiki/Home.md b/docs/_wiki/Home.md index 10985b59..f9d2c414 100644 --- a/docs/_wiki/Home.md +++ b/docs/_wiki/Home.md @@ -72,3 +72,4 @@ nav_order: 1 * Windows, macOS and Linux ready. * **Dark/Light theme** * **Auto-update** +* **ReadOnly mode** diff --git a/docs/_wiki/Producer/Producer.md b/docs/_wiki/Producer/Producer.md index 26a90f1e..5167998a 100644 --- a/docs/_wiki/Producer/Producer.md +++ b/docs/_wiki/Producer/Producer.md @@ -6,8 +6,8 @@ nav_order: 4 --- # Producer -Insulator supports producing new record to the cluster topics. -Two types of producer are currently supported: Avro and String. +Insulator supports producing new records to the cluster topics. +Two types of producers are currently supported: Avro and String. The string producer allows the user to create a new record with string key and value.