Skip to content
This repository was archived by the owner on Sep 26, 2020. It is now read-only.

Commit a68212c

Browse files
AustinShalitOctogonapus
and
Octogonapus
authored
Add plugin manager to preferences (#153)
* Add sections to preferences page * Add plugins view * Make dialog bigger * Format * Fix tests * Remove upload because we are not using it * Respond to comments * Remove dialog var & fix line length * Formatting Co-authored-by: Octogonapus <rgbenasutti@wpi.edu>
1 parent e8352fa commit a68212c

File tree

4 files changed

+267
-24
lines changed

4 files changed

+267
-24
lines changed

plugin/src/main/kotlin/edu/wpi/axon/plugin/Plugin.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,29 @@ sealed class Plugin {
1313
/**
1414
* The name of this plugin.
1515
*/
16-
abstract val name: String
16+
abstract var name: String
1717

1818
/**
1919
* The contents of the plugin to be included in the generated code.
2020
*/
21-
abstract val contents: String
21+
abstract var contents: String
2222

2323
/**
2424
* An Axon-supported plugin that users get by default.
2525
*/
2626
@Serializable
2727
data class Official(
28-
override val name: String,
29-
override val contents: String
28+
override var name: String,
29+
override var contents: String
3030
) : Plugin()
3131

3232
/**
3333
* A plugin that the user added themselves.
3434
*/
3535
@Serializable
3636
data class Unofficial(
37-
override val name: String,
38-
override val contents: String
37+
override var name: String,
38+
override var contents: String
3939
) : Plugin()
4040

4141
fun serialize(): String = Json(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package edu.wpi.axon.ui.view.preferences
2+
3+
import com.github.mvysny.karibudsl.v10.KComposite
4+
import com.github.mvysny.karibudsl.v10.beanValidationBinder
5+
import com.github.mvysny.karibudsl.v10.bind
6+
import com.github.mvysny.karibudsl.v10.button
7+
import com.github.mvysny.karibudsl.v10.dialog
8+
import com.github.mvysny.karibudsl.v10.horizontalAlignSelf
9+
import com.github.mvysny.karibudsl.v10.horizontalLayout
10+
import com.github.mvysny.karibudsl.v10.onLeftClick
11+
import com.github.mvysny.karibudsl.v10.setPrimary
12+
import com.github.mvysny.karibudsl.v10.textArea
13+
import com.github.mvysny.karibudsl.v10.textField
14+
import com.github.mvysny.karibudsl.v10.verticalLayout
15+
import com.vaadin.flow.component.icon.Icon
16+
import com.vaadin.flow.component.icon.VaadinIcon
17+
import com.vaadin.flow.component.orderedlayout.FlexComponent
18+
import edu.wpi.axon.plugin.Plugin
19+
import edu.wpi.axon.plugin.PluginManager
20+
import edu.wpi.axon.ui.view.HasNotifications
21+
22+
class PluginEditorDialog(
23+
pluginManager: PluginManager,
24+
bean: Plugin? = null,
25+
onSave: (Plugin) -> Unit = {}
26+
) : KComposite(), HasNotifications {
27+
private val binder = beanValidationBinder<Plugin>()
28+
29+
init {
30+
ui {
31+
dialog {
32+
width = "50vw"
33+
verticalLayout {
34+
textField("Name") {
35+
setWidthFull()
36+
addValueChangeListener { binder.validate() }
37+
bind(binder).asRequired()
38+
.withValidator(
39+
{ name ->
40+
!pluginManager.listPlugins().map { it.name }.contains(
41+
name
42+
) || name == bean?.name
43+
},
44+
"A plugin with that name already exists!"
45+
).bind(Plugin::name)
46+
}
47+
textArea("Content") {
48+
setWidthFull()
49+
height = "40vh"
50+
addValueChangeListener { binder.validate() }
51+
bind(binder).asRequired()
52+
.bind(Plugin::contents)
53+
}
54+
horizontalLayout {
55+
horizontalAlignSelf = FlexComponent.Alignment.END
56+
button("Cancel") {
57+
onLeftClick {
58+
this@dialog.close()
59+
}
60+
}
61+
button("Save", Icon(VaadinIcon.CHECK_CIRCLE)) {
62+
setPrimary()
63+
isEnabled = false
64+
binder.addStatusChangeListener { isEnabled = !it.hasValidationErrors() }
65+
onLeftClick {
66+
val plugin = Plugin.Unofficial("", "")
67+
if (binder.validate().isOk && binder.writeBeanIfValid(plugin)) {
68+
if (bean == null) {
69+
pluginManager.addUnofficialPlugin(plugin)
70+
} else {
71+
pluginManager.modifyUnofficialPlugin(bean.name, plugin)
72+
}
73+
onSave.invoke(plugin)
74+
this@dialog.close()
75+
}
76+
}
77+
}
78+
}
79+
}
80+
open()
81+
}
82+
}
83+
84+
binder.readBean(bean)
85+
}
86+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package edu.wpi.axon.ui.view.preferences
2+
3+
import com.github.mvysny.karibudsl.v10.KComposite
4+
import com.github.mvysny.karibudsl.v10.VaadinDsl
5+
import com.github.mvysny.karibudsl.v10.button
6+
import com.github.mvysny.karibudsl.v10.grid
7+
import com.github.mvysny.karibudsl.v10.h3
8+
import com.github.mvysny.karibudsl.v10.horizontalLayout
9+
import com.github.mvysny.karibudsl.v10.init
10+
import com.github.mvysny.karibudsl.v10.onLeftClick
11+
import com.github.mvysny.karibudsl.v10.verticalLayout
12+
import com.vaadin.flow.component.Component
13+
import com.vaadin.flow.component.HasComponents
14+
import com.vaadin.flow.component.HasSize
15+
import com.vaadin.flow.component.button.Button
16+
import com.vaadin.flow.component.button.ButtonVariant
17+
import com.vaadin.flow.component.grid.ColumnTextAlign
18+
import com.vaadin.flow.component.grid.GridVariant
19+
import com.vaadin.flow.component.icon.Icon
20+
import com.vaadin.flow.component.icon.VaadinIcon
21+
import com.vaadin.flow.component.textfield.TextArea
22+
import com.vaadin.flow.data.provider.CallbackDataProvider
23+
import com.vaadin.flow.data.provider.CallbackDataProvider.CountCallback
24+
import com.vaadin.flow.data.provider.CallbackDataProvider.FetchCallback
25+
import com.vaadin.flow.data.renderer.ComponentRenderer
26+
import com.vaadin.flow.data.renderer.TextRenderer
27+
import edu.wpi.axon.plugin.Plugin
28+
import edu.wpi.axon.plugin.PluginManager
29+
import edu.wpi.axon.ui.view.HasNotifications
30+
import java.util.function.Predicate
31+
32+
class PluginManagerComponent(
33+
title: String,
34+
pluginManager: PluginManager
35+
) : KComposite(), HasNotifications, HasSize {
36+
37+
class PluginManagerDataProvider(pluginManager: PluginManager) :
38+
CallbackDataProvider<Plugin, Predicate<Plugin>>(
39+
FetchCallback<Plugin, Predicate<Plugin>> {
40+
pluginManager.listPlugins().stream()
41+
.filter(it.filter.orElse(Predicate { true }))
42+
.sorted { o1, o2 -> o1.name.compareTo(o2.name) }
43+
.skip(it.offset.toLong())
44+
.limit(it.limit.toLong())
45+
},
46+
CountCallback<Plugin, Predicate<Plugin>> {
47+
pluginManager.listPlugins().stream()
48+
.filter(it.filter.orElse(Predicate { true }))
49+
.count()
50+
.toInt()
51+
}
52+
)
53+
54+
private val dataProvider = PluginManagerDataProvider(pluginManager)
55+
56+
init {
57+
ui {
58+
verticalLayout {
59+
horizontalLayout {
60+
setWidthFull()
61+
h3(title) {
62+
style["margin-right"] = "auto"
63+
}
64+
button("Add Plugin", Icon(VaadinIcon.PLUS_CIRCLE)) {
65+
onLeftClick {
66+
PluginEditorDialog(pluginManager) {
67+
dataProvider.refreshAll()
68+
}
69+
}
70+
}
71+
}
72+
grid(dataProvider) {
73+
addThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_NO_BORDER)
74+
isHeightByRows = true
75+
addColumn(TextRenderer { it.name })
76+
addComponentColumn { plugin ->
77+
Button(Icon(VaadinIcon.PENCIL)).apply {
78+
if (plugin is Plugin.Unofficial) {
79+
onLeftClick {
80+
PluginEditorDialog(pluginManager, plugin) {
81+
dataProvider.refreshAll()
82+
}
83+
}
84+
} else {
85+
isEnabled = false
86+
}
87+
}
88+
}.apply {
89+
textAlign = ColumnTextAlign.END
90+
}
91+
addComponentColumn { plugin ->
92+
Button(Icon(VaadinIcon.TRASH)).apply {
93+
addThemeVariants(ButtonVariant.LUMO_ERROR)
94+
if (plugin is Plugin.Unofficial) {
95+
onLeftClick {
96+
pluginManager.removeUnofficialPlugin(plugin.name)
97+
dataProvider.refreshAll()
98+
}
99+
} else {
100+
isEnabled = false
101+
}
102+
}
103+
}.apply {
104+
textAlign = ColumnTextAlign.END
105+
}
106+
setItemDetailsRenderer(ComponentRenderer<Component, Plugin> { plugin ->
107+
TextArea().apply {
108+
setSizeFull()
109+
value = plugin.contents
110+
isReadOnly = true
111+
}
112+
})
113+
}
114+
}
115+
}
116+
}
117+
}
118+
119+
@VaadinDsl
120+
fun (@VaadinDsl HasComponents).pluginManagerComponent(
121+
title: String,
122+
pluginManager: PluginManager,
123+
block: (@VaadinDsl PluginManagerComponent).() -> Unit = {}
124+
) = init(PluginManagerComponent(title, pluginManager), block)

ui-vaadin/src/main/kotlin/edu/wpi/axon/ui/view/preferences/PreferencesView.kt

+51-18
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,78 @@
11
package edu.wpi.axon.ui.view.preferences
22

33
import com.github.mvysny.karibudsl.v10.KComposite
4+
import com.github.mvysny.karibudsl.v10.VaadinDsl
45
import com.github.mvysny.karibudsl.v10.beanValidationBinder
56
import com.github.mvysny.karibudsl.v10.bind
67
import com.github.mvysny.karibudsl.v10.button
78
import com.github.mvysny.karibudsl.v10.comboBox
9+
import com.github.mvysny.karibudsl.v10.h4
10+
import com.github.mvysny.karibudsl.v10.hr
11+
import com.github.mvysny.karibudsl.v10.init
812
import com.github.mvysny.karibudsl.v10.numberField
913
import com.github.mvysny.karibudsl.v10.onLeftClick
1014
import com.github.mvysny.karibudsl.v10.toLong
1115
import com.github.mvysny.karibudsl.v10.verticalLayout
16+
import com.vaadin.flow.component.HasComponents
17+
import com.vaadin.flow.component.orderedlayout.VerticalLayout
1218
import com.vaadin.flow.data.binder.ValidationResult
1319
import com.vaadin.flow.router.PageTitle
1420
import com.vaadin.flow.router.Route
1521
import edu.wpi.axon.aws.preferences.Preferences
1622
import edu.wpi.axon.aws.preferences.PreferencesManager
23+
import edu.wpi.axon.plugin.PluginManager
1724
import edu.wpi.axon.ui.MainLayout
1825
import edu.wpi.axon.ui.view.HasNotifications
26+
import edu.wpi.axon.util.datasetPluginManagerName
27+
import edu.wpi.axon.util.testPluginManagerName
1928
import mu.KotlinLogging
2029
import org.koin.core.KoinComponent
2130
import org.koin.core.inject
31+
import org.koin.core.qualifier.named
2232
import software.amazon.awssdk.services.ec2.model.InstanceType
2333

2434
@Route(layout = MainLayout::class)
2535
@PageTitle("Preferences")
2636
class PreferencesView : KComposite(), HasNotifications, KoinComponent {
37+
2738
private val binder = beanValidationBinder<Preferences>()
39+
private val datasetPluginManager by inject<PluginManager>(named(datasetPluginManagerName))
2840
private val preferencesManager by inject<PreferencesManager>()
41+
private val testPluginManager by inject<PluginManager>(named(testPluginManagerName))
2942

3043
init {
3144
ui {
3245
verticalLayout {
33-
comboBox<InstanceType>("Training Instance Type") {
34-
setItems(InstanceType.knownValues().stream().sorted())
35-
isPreventInvalidInput = true
36-
isRequired = true
37-
bind(binder).asRequired().bind(Preferences::defaultEC2NodeType)
46+
setSizeFull()
47+
verticalLayoutSection("AWS") {
48+
comboBox<InstanceType>("Training Instance Type") {
49+
setItems(InstanceType.knownValues().stream().sorted())
50+
isPreventInvalidInput = true
51+
isRequired = true
52+
bind(binder).asRequired().bind(Preferences::defaultEC2NodeType)
53+
}
54+
numberField("Status Polling Delay (ms)") {
55+
width = "12em"
56+
isPreventInvalidInput = true
57+
bind(binder)
58+
.asRequired()
59+
.toLong()
60+
.withValidator { value, _ ->
61+
if (value != null && value > 0) {
62+
ValidationResult.ok()
63+
} else {
64+
ValidationResult.error("must be greater than zero.")
65+
}
66+
}.bind(Preferences::statusPollingDelay)
67+
}
3868
}
3969

40-
numberField("Status Polling Delay (ms)") {
41-
width = "12em"
42-
isPreventInvalidInput = true
43-
bind(binder)
44-
.asRequired()
45-
.toLong()
46-
.withValidator { value, _ ->
47-
if (value != null && value > 0) {
48-
ValidationResult.ok()
49-
} else {
50-
ValidationResult.error("must be greater than zero.")
51-
}
52-
}.bind(Preferences::statusPollingDelay)
70+
verticalLayoutSection {
71+
pluginManagerComponent("Dataset Plugins", datasetPluginManager)
72+
}
73+
74+
verticalLayoutSection {
75+
pluginManagerComponent("Test Plugins", testPluginManager)
5376
}
5477

5578
button("Save") {
@@ -70,6 +93,16 @@ class PreferencesView : KComposite(), HasNotifications, KoinComponent {
7093
binder.readBean(preferencesManager.get())
7194
}
7295

96+
@VaadinDsl
97+
private fun (@VaadinDsl HasComponents).verticalLayoutSection(
98+
title: String? = null,
99+
block: (@VaadinDsl VerticalLayout).() -> Unit = {}
100+
) = init(VerticalLayout()) {
101+
if (title != null) h4(title)
102+
block()
103+
hr()
104+
}
105+
73106
companion object {
74107
private val LOGGER = KotlinLogging.logger { }
75108
}

0 commit comments

Comments
 (0)