DemoBench: UI improvements, part uno

This commit is contained in:
Mike Hearn 2017-04-13 17:31:13 +02:00
parent 87dd99d968
commit a90b2ba839
11 changed files with 194 additions and 70 deletions

View File

@ -48,17 +48,20 @@ data class PhysicalLocation(val coordinate: WorldCoordinate, val description: St
* A simple lookup table of city names to their coordinates. Lookups are case insensitive.
*/
object CityDatabase {
private val cityMap = HashMap<String, PhysicalLocation>()
private val caseInsensitiveLookups = HashMap<String, PhysicalLocation>()
val cityMap = HashMap<String, PhysicalLocation>()
init {
javaClass.getResourceAsStream("cities.txt").bufferedReader().useLines { lines ->
for (line in lines) {
if (line.startsWith("#")) continue
val (name, lng, lat) = line.split('\t')
cityMap[name.toLowerCase()] = PhysicalLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), name)
val location = PhysicalLocation(WorldCoordinate(lat.toDouble(), lng.toDouble()), name)
caseInsensitiveLookups[name.toLowerCase()] = location
cityMap[name] = location
}
}
}
operator fun get(name: String) = cityMap[name.toLowerCase()]
operator fun get(name: String) = caseInsensitiveLookups[name.toLowerCase()]
}

View File

@ -5,6 +5,8 @@ import net.corda.demobench.views.DemoBenchView
import tornadofx.*
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets.UTF_8
import javax.swing.SwingUtilities
import javax.swing.UIManager
/**
* README!

View File

@ -25,6 +25,7 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
private var baseDir: Path = baseDirFor(ManagementFactory.getRuntimeMXBean().startTime)
private val cordaPath: Path = jvm.applicationDir.resolve("corda").resolve("corda.jar")
//private val command = arrayOf("/usr/local/bin/fish")
private val command = jvm.commandFor(cordaPath).toTypedArray()
private val nodes = LinkedHashMap<String, NodeConfig>()

View File

@ -15,7 +15,6 @@ import tornadofx.*
import java.util.*
class DemoBenchView : View("Corda DemoBench") {
override val root by fxml<Parent>()
private val profileController by inject<ProfileController>()
@ -26,6 +25,7 @@ class DemoBenchView : View("Corda DemoBench") {
private val menuSaveAs by fxid<MenuItem>()
init {
importStylesheet("/net/corda/demobench/r3-style.css")
importStylesheet("/net/corda/demobench/style.css")
configureShutdown()

View File

@ -1,27 +1,34 @@
package net.corda.demobench.views
import javafx.application.Platform
import javafx.scene.control.ComboBox
import javafx.scene.control.SelectionMode.MULTIPLE
import javafx.scene.control.SelectionMode.values
import javafx.scene.input.KeyCode
import javafx.scene.layout.Pane
import javafx.scene.layout.Priority
import javafx.stage.FileChooser
import javafx.util.converter.NumberStringConverter
import net.corda.core.node.CityDatabase
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.demobench.model.*
import net.corda.demobench.ui.CloseableTab
import org.controlsfx.control.textfield.TextFields
import tornadofx.*
import java.nio.file.Path
import java.text.DecimalFormat
import java.util.*
class NodeTabView : Fragment() {
private val cityList = CityDatabase.cityMap.keys.toList().sorted()
override val root = stackpane {}
private val main by inject<DemoBenchView>()
private val showConfig by param(true)
private companion object : Component() {
const val textWidth = 200.0
const val textWidth = 465.0
const val numberWidth = 100.0
const val maxNameLength = 15
@ -46,32 +53,28 @@ class NodeTabView : Fragment() {
private val nodeTerminalView = find<NodeTerminalView>()
private val nodeConfigView = stackpane {
isVisible = showConfig
styleClass += "config-view"
form {
fieldset("Configuration") {
isFillWidth = false
field("Node Name", op = { nodeNameField() })
field("Nearest City", op = { nearestCityField() })
field("Legal name") { nodeNameField() }
field("Nearest city") { nearestCityField() }
}
/*fieldset("Ports") {
field("P2P Port", op = { p2pPortField() })
field("RPC port", op = { rpcPortField() })
field("Web Port", op = { webPortField() })
field("Database Port", op = { databasePortField() })
}
}*/
hbox {
styleClass.addAll("node-panel")
fieldset("Services") {
styleClass.addAll("services-panel")
listview(availableServices.observable()) {
selectionModel.selectionMode = MULTIPLE
model.item.extraServices.set(selectionModel.selectedItems)
}
}
fieldset("CorDapps") {
hboxConstraints { hGrow = Priority.ALWAYS }
styleClass.addAll("cordapps-panel")
listview(cordapps) {
@ -82,23 +85,41 @@ class NodeTabView : Fragment() {
key.consume()
}
}
button("Add CorDapp") {
setOnAction {
val app = (chooser.showOpenDialog(null) ?: return@setOnAction).toPath()
if (!cordapps.contains(app)) {
cordapps.add(app)
}
}
}
fieldset("Services") {
styleClass.addAll("services-panel")
listview(availableServices.observable()) {
selectionModel.selectionMode = MULTIPLE
model.item.extraServices.set(selectionModel.selectedItems)
}
}
}
button("Create Node") {
setOnAction {
if (model.validate()) {
launch()
main.enableAddNodes()
main.enableSaveProfile()
hbox {
button("Add CorDapp") {
setOnAction {
val app = (chooser.showOpenDialog(null) ?: return@setOnAction).toPath()
if (!cordapps.contains(app)) {
cordapps.add(app)
}
}
}
// Spacer pane.
pane {
hboxConstraints { hGrow = Priority.ALWAYS }
}
button("Start node") {
styleClass += "start-button"
setOnAction {
if (model.validate()) {
launch()
main.enableAddNodes()
main.enableSaveProfile()
}
}
}
}
@ -117,6 +138,7 @@ class NodeTabView : Fragment() {
root.add(nodeTerminalView)
model.legalName.value = if (nodeController.hasNetworkMap()) "" else DUMMY_NOTARY.name
model.nearestCity.value = if (nodeController.hasNetworkMap()) "" else "London"
model.p2pPort.value = nodeController.nextPort
model.rpcPort.value = nodeController.nextPort
model.webPort.value = nodeController.nextPort
@ -147,13 +169,29 @@ class NodeTabView : Fragment() {
}
}
private fun Pane.nearestCityField() = textfield(model.nearestCity) {
// private fun Pane.nearestCityField() = textfield(model.nearestCity) {
// minWidth = textWidth
// TextFields.bindAutoCompletion(this, CityDatabase.cityMap.keys)
// validator(trigger = ValidationTrigger.OnBlur) {
// if (it == null || it.trim().isEmpty()) {
// error("Nearest city is required")
// } else if (CityDatabase[it] == null) {
// error("$it is not in our database, sorry")
// } else {
// null
// }
// }
// }
private fun Pane.nearestCityField() = combobox(model.nearestCity, cityList) {
minWidth = textWidth
validator {
if (it == null) {
error("Nearest city is required")
} else if (it.trim().isEmpty()) {
isEditable = true
TextFields.bindAutoCompletion(editor, cityList)
validator(trigger = ValidationTrigger.OnBlur) {
if (it == null || it.trim().isEmpty()) {
error("Nearest city is required")
} else if (CityDatabase[it] == null) {
error("$it is not in our database, sorry")
} else {
null
}

View File

@ -31,7 +31,6 @@ class NodeTerminalView : Fragment() {
private val webServerController by inject<WebServerController>()
private val nodeName by fxid<Label>()
private val p2pPort by fxid<PropertyLabel>()
private val states by fxid<PropertyLabel>()
private val transactions by fxid<PropertyLabel>()
private val balance by fxid<PropertyLabel>()
@ -49,7 +48,6 @@ class NodeTerminalView : Fragment() {
fun open(config: NodeConfig, onExit: () -> Unit) {
nodeName.text = config.legalName
p2pPort.value = config.p2pPort.toString()
launchWebButton.text = "Launch\nWeb Server\n(Port ${config.webPort})"
val swingTerminal = SwingNode()
@ -177,8 +175,8 @@ class NodeTerminalView : Fragment() {
}
class TerminalSettingsProvider : DefaultSettingsProvider() {
override fun getDefaultStyle() = TextStyle(TerminalColor.WHITE, TerminalColor.BLACK)
override fun getDefaultStyle() = TextStyle(TerminalColor.WHITE, TerminalColor.rgb(50, 50, 50))
override fun emulateX11CopyPaste() = true
}
}

View File

@ -0,0 +1,55 @@
/** Styles that follow the corporate branding guidelines, and which give a web/bootstrap look'n'feel */
.root-pane {
-fx-font-family: sans-serif;
-fx-font-size: 14pt;
-db-grey: #ddd;
}
.button {
-fx-padding: 12px;
-fx-border-radius: 4px;
}
.info-button {
-fx-base: #5bc0de;
-fx-text-fill: white;
-fx-border-color: #298dff;
-fx-background-color: linear-gradient(to bottom, #70b3ff 0%, #3392ff 100%);
}
.info-button:hover {
-fx-background-color: #3392ff;
}
.tab-pane:top > .tab-header-area > .tab-header-background {
-fx-background-color: white;
-fx-border-color: -db-grey;
-fx-border-width: 0 0 1 0;
}
.tab-pane:top > .tab-header-area {
-fx-padding: 0.816667em 0 0 8em;
}
.tab .focus-indicator {
-fx-border-width: 0, 0, 0;
}
.tab, .tab:selected:hover {
-fx-background-color: white;
-fx-background-radius: 0;
-fx-border-radius: 4px 4px 0 0;
-fx-padding: 10px 15px;
}
.tab:selected {
-fx-border-width: 1;
-fx-border-color: -db-grey -db-grey white -db-grey;
}
.tab:hover {
-fx-background-color: -db-grey;
-fx-background-radius: 4px 4px 0 0;
-fx-cursor: hand;
}

View File

@ -4,27 +4,38 @@
*/
.header {
-fx-background-color: #505050;
-fx-padding: 15px;
-fx-background-color: white;
-fx-padding: 1.5em;
-fx-spacing: 20px;
}
.property-label .label {
-fx-font-size: 14pt;
-fx-text-fill: white;
}
.add-node-button {
-fx-base: red;
-fx-text-fill: black;
}
.big-button {
-fx-base: #009759;
-fx-background-radius: 5px;
-fx-opacity: 80%;
-fx-font-size: 12pt;
}
.start-button {
-fx-base: #26b426;
-fx-text-fill: white;
}
.terminal-gradient-pane {
-fx-border-color: black;
-fx-border-width: 1 0 0 0;
-fx-background-color: linear-gradient(to bottom, black, #323232);
}
.terminal-vbox {
-fx-background-color: #323232;
}
.node-panel {
-fx-spacing: 20px;
-fx-spacing: 1em;
-fx-background-color: white;
}
.services-panel {
@ -39,5 +50,10 @@
.list-cell {
-fx-control-inner-background: white;
-fx-control-inner-background-alt: gainsboro;
-fx-control-inner-background-alt: #f7f7f7;
}
.config-view {
-fx-background-color: white;
-fx-padding: 1em;
}

View File

@ -1,22 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx/8.0.102" xmlns:fx="http://javafx.com/fxml/1">
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<VBox xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
<MenuBar useSystemMenuBar="true">
<Menu text="File">
<MenuItem fx:id="menuOpen" text="Open"/>
<MenuItem fx:id="menuSaveAs" disable="true" text="Save As"/>
<MenuItem fx:id="menuOpen" text="Open" />
<MenuItem fx:id="menuSaveAs" disable="true" text="Save As" />
</Menu>
</MenuBar>
<StackPane VBox.vgrow="ALWAYS">
<TabPane fx:id="nodeTabPane" minHeight="444.0" minWidth="800.0" prefHeight="613.0" prefWidth="1231.0"
tabClosingPolicy="UNAVAILABLE" tabMinHeight="30.0"/>
<Button fx:id="addNodeButton" mnemonicParsing="false" styleClass="add-node-button" text="Add Node"
StackPane.alignment="TOP_RIGHT">
<StackPane styleClass="root-pane" VBox.vgrow="ALWAYS">
<TabPane fx:id="nodeTabPane" minHeight="444.0" minWidth="800.0" prefHeight="613.0" prefWidth="1231.0" tabClosingPolicy="UNAVAILABLE" tabMinHeight="30.0" />
<ImageView fitHeight="40.0" preserveRatio="true" StackPane.alignment="TOP_LEFT">
<Image url="@cordalogo-full-trans.png"/>
<StackPane.margin>
<Insets right="5.0" top="5.0"/>
<Insets left="20" top="15.0"/>
</StackPane.margin>
</ImageView>
<Button fx:id="addNodeButton" mnemonicParsing="false" styleClass="info-button" text="Add Node" StackPane.alignment="TOP_RIGHT">
<StackPane.margin>
<Insets right="25.0" top="10.0" />
</StackPane.margin>
</Button>
</StackPane>

View File

@ -3,19 +3,17 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import net.corda.demobench.ui.*?>
<VBox visible="false" prefHeight="953.0" prefWidth="1363.0" xmlns="http://javafx.com/javafx/8.0.102"
xmlns:fx="http://javafx.com/fxml/1">
<HBox prefHeight="95.0" prefWidth="800.0" spacing="15.0" styleClass="header">
<VBox prefHeight="66.0" prefWidth="296.0" spacing="20.0">
<Label fx:id="nodeName" style="-fx-font-size: 40; -fx-text-fill: red;"/>
<PropertyLabel fx:id="p2pPort" name="P2P port: "/>
<VBox visible="false" prefHeight="953.0" prefWidth="1363.0" xmlns="http://javafx.com/javafx/8.0.102" xmlns:fx="http://javafx.com/fxml/1" styleClass="terminal-vbox">
<HBox prefHeight="95.0" prefWidth="800.0" styleClass="header">
<VBox prefHeight="66.0" HBox.hgrow="ALWAYS">
<Label fx:id="nodeName" style="-fx-font-size: 40; -fx-text-fill: red;" minWidth="NaN"/>
</VBox>
<VBox prefHeight="93.0" prefWidth="267.0">
<PropertyLabel fx:id="states" name="States in vault: "/>
<PropertyLabel fx:id="transactions" name="Known transactions: "/>
<PropertyLabel fx:id="balance" name="Balance: "/>
</VBox>
<Pane prefHeight="200.0" prefWidth="200.0" HBox.hgrow="ALWAYS"/>
<!--<Pane prefHeight="200.0" prefWidth="200.0"/>-->
<Button fx:id="viewDatabaseButton" disable="true" mnemonicParsing="false" prefHeight="92.0" prefWidth="115.0"
styleClass="big-button" text="View&#10;Database" textAlignment="CENTER"/>
<Button fx:id="launchWebButton" disable="true" mnemonicParsing="false" prefHeight="92.0" prefWidth="115.0"
@ -23,4 +21,5 @@
<Button fx:id="launchExplorerButton" disable="true" mnemonicParsing="false" prefHeight="92.0" prefWidth="115.0"
styleClass="big-button" text="Launch&#10;Explorer" textAlignment="CENTER"/>
</HBox>
<Pane minHeight="8" mouseTransparent="true" styleClass="terminal-gradient-pane"/>
</VBox>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB