logic seems fine?
This commit is contained in:
parent
47de8220d5
commit
7db4d246d9
17 changed files with 254 additions and 511 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
# Ignore Gradle build output directory
|
# Ignore Gradle build output directory
|
||||||
build
|
build
|
||||||
|
bin
|
||||||
|
|
||||||
# Ignore VSCode settings
|
# Ignore VSCode settings
|
||||||
.vscode
|
.vscode
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
/*
|
|
||||||
* This Kotlin source file was generated by the Gradle 'init' task.
|
|
||||||
*/
|
|
||||||
package xyz.brysonsteck.serverfordummies
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
class App : Application() {
|
|
||||||
|
|
||||||
override fun start(stage: Stage) {
|
|
||||||
var scene = Scene(loadFXML("primary"), 1500.0, 900.0)
|
|
||||||
stage.title = "Server For Dummies"
|
|
||||||
stage.scene = scene
|
|
||||||
stage.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadFXML(fxml: String) : Parent {
|
|
||||||
val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml"))
|
|
||||||
return fxmlLoader.load()
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun run() {
|
|
||||||
launch()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package xyz.brysonsteck.serverfordummies
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
App().run()
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
package xyz.brysonsteck.serverfordummies
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
import java.awt.Checkbox
|
|
||||||
import java.util.Properties
|
|
||||||
|
|
||||||
import javafx.beans.value.ChangeListener
|
|
||||||
import javafx.beans.value.ObservableValue
|
|
||||||
import javafx.beans.property.BooleanProperty
|
|
||||||
import javafx.collections.FXCollections
|
|
||||||
import javafx.fxml.FXML
|
|
||||||
import javafx.geometry.Insets
|
|
||||||
import javafx.scene.control.Button
|
|
||||||
import javafx.scene.control.ChoiceBox
|
|
||||||
import javafx.scene.control.Label
|
|
||||||
import javafx.scene.control.TextField
|
|
||||||
import javafx.scene.control.Spinner
|
|
||||||
import javafx.scene.control.TitledPane
|
|
||||||
import javafx.scene.control.ButtonBar
|
|
||||||
import javafx.scene.control.CheckBox
|
|
||||||
import javafx.scene.layout.Border
|
|
||||||
import javafx.scene.layout.BorderStroke
|
|
||||||
import javafx.scene.layout.GridPane
|
|
||||||
import javafx.scene.layout.Pane
|
|
||||||
import javafx.scene.layout.HBox
|
|
||||||
import javafx.scene.text.TextAlignment
|
|
||||||
import javafx.scene.Scene
|
|
||||||
import javafx.scene.input.MouseEvent
|
|
||||||
import javafx.stage.FileChooser
|
|
||||||
import javafx.stage.FileChooser.ExtensionFilter
|
|
||||||
import javafx.stage.DirectoryChooser
|
|
||||||
import javafx.event.EventHandler
|
|
||||||
|
|
||||||
class PrimaryController {
|
|
||||||
@FXML
|
|
||||||
lateinit private var currentDirectoryLabel: Label
|
|
||||||
@FXML
|
|
||||||
lateinit private var worldNameField: TextField
|
|
||||||
@FXML
|
|
||||||
lateinit private var seedField: TextField
|
|
||||||
@FXML
|
|
||||||
lateinit private var portSpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var difficultyBox: ChoiceBox<String>
|
|
||||||
@FXML
|
|
||||||
lateinit private var gamemodeBox: ChoiceBox<String>
|
|
||||||
@FXML
|
|
||||||
lateinit private var worldTypeBox: ChoiceBox<String>
|
|
||||||
@FXML
|
|
||||||
lateinit private var worldSettingsPane: HBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var parentPane: Pane
|
|
||||||
@FXML
|
|
||||||
lateinit private var buttonBar: ButtonBar
|
|
||||||
@FXML
|
|
||||||
lateinit private var flightCheckbox: CheckBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var netherCheckbox: CheckBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var structuresCheckbox: CheckBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var pvpCheckbox: CheckBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var whitelistCheckbox: CheckBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var cmdBlocksCheckbox: CheckBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var playerCountCheckbox: CheckBox
|
|
||||||
@FXML
|
|
||||||
lateinit private var maxPlayersSpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var maxSizeSpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var memorySpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var spawnSpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var simulationSpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var renderSpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var maxTickSpinner: Spinner<kotlin.Int>
|
|
||||||
@FXML
|
|
||||||
lateinit private var progressBar: ProgressBar
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private fun onDirectoryButtonClick() {
|
|
||||||
val dirChooser = DirectoryChooser()
|
|
||||||
dirChooser.title = "Open a server directory"
|
|
||||||
dirChooser.initialDirectory = File(System.getProperty("user.home"))
|
|
||||||
val result = dirChooser.showDialog(null)
|
|
||||||
if (result != null) {
|
|
||||||
currentDirectoryLabel.text = result.absolutePath
|
|
||||||
parentPane.isDisable = false
|
|
||||||
worldSettingsPane.isDisable = false
|
|
||||||
buttonBar.isDisable = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private fun onWorldNameChange() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private fun onSeedChange() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private fun onPortChange() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private fun onCheckboxClick() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private fun onSpinnerChange() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
import java.net.http.HttpClient
|
|
||||||
import kotlinx.coroutines.flow
|
|
||||||
|
|
||||||
sealed class DownloadStatus {
|
|
||||||
|
|
||||||
object Success : DownloadStatus()
|
|
||||||
|
|
||||||
data class Error(val message: String) : DownloadStatus()
|
|
||||||
|
|
||||||
data class Progress(val progress: Int): DownloadStatus()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// function from https://gist.githubusercontent.com/SG-K/63e379efcc3d1cd3ce4fb56ee0e29c42/raw/cd9a4a016401b7c54ec01303415b5871ffa26066/downloadFile.kt
|
|
||||||
suspend fun HttpClient.downloadFile(file: File, url: String): Flow<DownloadStatus> {
|
|
||||||
return flow {
|
|
||||||
val response = call {
|
|
||||||
url(url)
|
|
||||||
method = HttpMethod.Get
|
|
||||||
}.response
|
|
||||||
val byteArray = ByteArray(response.contentLength()!!.toInt())
|
|
||||||
var offset = 0
|
|
||||||
do {
|
|
||||||
val currentRead = response.content.readAvailable(byteArray, offset, byteArray.size)
|
|
||||||
offset += currentRead
|
|
||||||
val progress = (offset * 100f / byteArray.size).roundToInt()
|
|
||||||
emit(DownloadStatus.Progress(progress))
|
|
||||||
} while (currentRead > 0)
|
|
||||||
response.close()
|
|
||||||
if (response.status.isSuccess()) {
|
|
||||||
file.writeBytes(byteArray)
|
|
||||||
emit(DownloadStatus.Success)
|
|
||||||
} else {
|
|
||||||
emit(DownloadStatus.Error("File not downloaded"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,253 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.control.Button?>
|
|
||||||
<?import javafx.scene.control.ButtonBar?>
|
|
||||||
<?import javafx.scene.control.CheckBox?>
|
|
||||||
<?import javafx.scene.control.ChoiceBox?>
|
|
||||||
<?import javafx.scene.control.ContextMenu?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
|
||||||
<?import javafx.scene.control.MenuItem?>
|
|
||||||
<?import javafx.scene.control.ProgressBar?>
|
|
||||||
<?import javafx.scene.control.Separator?>
|
|
||||||
<?import javafx.scene.control.Spinner?>
|
|
||||||
<?import javafx.scene.control.TextField?>
|
|
||||||
<?import javafx.scene.control.TitledPane?>
|
|
||||||
<?import javafx.scene.control.Tooltip?>
|
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
|
||||||
<?import javafx.scene.layout.HBox?>
|
|
||||||
<?import javafx.scene.layout.Pane?>
|
|
||||||
<?import javafx.scene.text.Font?>
|
|
||||||
|
|
||||||
<Pane fx:id="primary" maxHeight="900.0" maxWidth="1500.0" minHeight="620.0" minWidth="963.0" prefHeight="708.0" prefWidth="963.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="xyz.brysonsteck.serverfordummies.PrimaryController">
|
|
||||||
<children>
|
|
||||||
<HBox prefHeight="39.0" prefWidth="963.0">
|
|
||||||
<children>
|
|
||||||
<Button id="openFile" fx:id="chooseDirectoryButton" lineSpacing="10.0" mnemonicParsing="false" onMouseClicked="#onDirectoryButtonClick" text="Choose Directory...">
|
|
||||||
<opaqueInsets>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</opaqueInsets>
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets />
|
|
||||||
</HBox.margin>
|
|
||||||
</Button>
|
|
||||||
<Separator orientation="VERTICAL" prefHeight="200.0">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="5.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Separator>
|
|
||||||
<Label text="Server Directory:">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="13.0" />
|
|
||||||
</font>
|
|
||||||
</Label>
|
|
||||||
<Label id="currentFilename" fx:id="currentDirectoryLabel" text="<NONE>">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets bottom="5.0" right="5.0" top="5.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Label>
|
|
||||||
</children>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="7.0" left="7.0" right="7.0" top="7.0" />
|
|
||||||
</padding>
|
|
||||||
</HBox>
|
|
||||||
<HBox fx:id="worldSettingsPane" disable="true" layoutY="39.0" prefHeight="41.0" prefWidth="963.0">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="7.0" left="7.0" right="7.0" top="7.0" />
|
|
||||||
</padding>
|
|
||||||
<children>
|
|
||||||
<Label text="World Name:">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="13.0" />
|
|
||||||
</font>
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="6.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Label>
|
|
||||||
<TextField fx:id="worldNameField" onInputMethodTextChanged="#onWorldNameChange">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets top="2.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</TextField>
|
|
||||||
<Separator orientation="VERTICAL" prefHeight="200.0">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="5.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Separator>
|
|
||||||
<Label text="Seed:">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="13.0" />
|
|
||||||
</font>
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="6.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Label>
|
|
||||||
<TextField fx:id="seedField" onInputMethodTextChanged="#onSeedChange" prefHeight="23.0" prefWidth="448.0">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets top="2.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</TextField>
|
|
||||||
<Separator orientation="VERTICAL" prefHeight="200.0">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="5.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Separator>
|
|
||||||
<Label text="Server Port:">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="13.0" />
|
|
||||||
</font>
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="6.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Label>
|
|
||||||
<Spinner fx:id="portSpinner" onInputMethodTextChanged="#onPortChange" prefHeight="23.0" prefWidth="112.0">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets top="2.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Spinner>
|
|
||||||
</children>
|
|
||||||
<opaqueInsets>
|
|
||||||
<Insets bottom="3.0" />
|
|
||||||
</opaqueInsets>
|
|
||||||
</HBox>
|
|
||||||
<Pane fx:id="parentPane" disable="true" layoutY="78.0" prefHeight="555.0" prefWidth="970.0">
|
|
||||||
<children>
|
|
||||||
<TitledPane fx:id="settingsPane" animated="false" collapsible="false" layoutX="10.0" layoutY="7.0" prefHeight="273.0" prefWidth="627.0" text="Server Settings">
|
|
||||||
<content>
|
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="311.0" prefWidth="625.0">
|
|
||||||
<children>
|
|
||||||
<CheckBox fx:id="flightCheckbox" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow Flight" />
|
|
||||||
<CheckBox fx:id="netherCheckbox" layoutX="14.0" layoutY="42.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow The Nether" />
|
|
||||||
<CheckBox fx:id="structuresCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="70.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" prefHeight="45.0" prefWidth="231.0" text="Generate Structures (such as villages and strongholds)" />
|
|
||||||
<CheckBox fx:id="pvpCheckbox" layoutX="14.0" layoutY="109.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Allow PvP" />
|
|
||||||
<CheckBox fx:id="whitelistCheckbox" alignment="TOP_LEFT" layoutX="14.0" layoutY="138.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" prefHeight="45.0" prefWidth="231.0" text="Enable Whitelist (Only users you specify can join)" />
|
|
||||||
<Spinner fx:id="maxPlayerSpinner" layoutX="130.0" layoutY="179.0" prefHeight="23.0" prefWidth="99.0" />
|
|
||||||
<Label layoutX="14.0" layoutY="183.0" text="Maximum Players:" />
|
|
||||||
<Spinner fx:id="maxSizeSpinner" layoutX="214.0" layoutY="212.0" prefHeight="23.0" prefWidth="155.0" />
|
|
||||||
<Label layoutX="14.0" layoutY="216.0" text="Maximum World Size (in blocks):" />
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
<TitledPane fx:id="advancedPane" animated="false" layoutX="10.0" layoutY="289.0" prefHeight="259.0" prefWidth="627.0" text="Advanced Server Settings">
|
|
||||||
<content>
|
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="269.0" prefWidth="625.0">
|
|
||||||
<children>
|
|
||||||
<CheckBox fx:id="cmdBlocksCheckbox" layoutX="14.0" layoutY="14.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Enable Command Blocks" />
|
|
||||||
<CheckBox fx:id="playerCountCheckbox" layoutX="14.0" layoutY="41.0" mnemonicParsing="false" onMouseClicked="#onCheckboxClick" text="Hide Online Player Count" />
|
|
||||||
<Spinner fx:id="memorySpinner" layoutX="154.0" layoutY="69.0" prefHeight="23.0" prefWidth="99.0" />
|
|
||||||
<Label ellipsisString="" layoutX="14.0" layoutY="73.0" text="Server Memory in MB:" textOverrun="CLIP">
|
|
||||||
<tooltip>
|
|
||||||
<Tooltip text="This is the amount of RAM that will get passed to Minecraft/the JVM. For simple servers, 1024 MB will be plenty. If you typically have more than 5 concurrent players, consider allocating more." />
|
|
||||||
</tooltip>
|
|
||||||
</Label>
|
|
||||||
<Spinner fx:id="spawnSpinner" layoutX="172.0" layoutY="100.0" prefHeight="23.0" prefWidth="99.0" />
|
|
||||||
<Label layoutX="14.0" layoutY="104.0" text="Spawn Protection Radius:">
|
|
||||||
<tooltip>
|
|
||||||
<Tooltip text="All blocks in a radius from 0,~,0 will be unbreakable. If you want to break blocks within spawn, change this value." />
|
|
||||||
</tooltip>
|
|
||||||
</Label>
|
|
||||||
<Spinner fx:id="simulationSpinner" layoutX="147.0" layoutY="132.0" prefHeight="23.0" prefWidth="99.0" />
|
|
||||||
<Label layoutX="14.0" layoutY="136.0" text="Simulation Distance:">
|
|
||||||
<tooltip>
|
|
||||||
<Tooltip text="The radius of chunks for each player where ticks will be updated. In other words, anything outside these circles, such as furnaces, mobs, etc, will not be updated or simulated." />
|
|
||||||
</tooltip>
|
|
||||||
</Label>
|
|
||||||
<Spinner fx:id="renderSpinner" layoutX="124.0" layoutY="165.0" prefHeight="23.0" prefWidth="99.0" />
|
|
||||||
<Label layoutX="14.0" layoutY="169.0" text="Render Distance:">
|
|
||||||
<tooltip>
|
|
||||||
<Tooltip text="The radius of chunks where the server will render the view distance. Any value higher on a client than what is set will be ignored. Higher values will be more demanding on the server." />
|
|
||||||
</tooltip>
|
|
||||||
</Label>
|
|
||||||
<Label layoutX="14.0" layoutY="203.0" text="Maximum Tick Time (in milliseconds):">
|
|
||||||
<tooltip>
|
|
||||||
<Tooltip text="If the server cannot update ticks (i.e. "lags") for longer than this amount of time, the server will shutdown. 60000 ms (60 seconds) is the default." />
|
|
||||||
</tooltip>
|
|
||||||
</Label>
|
|
||||||
<Spinner fx:id="maxTickSpinner" layoutX="246.0" layoutY="199.0" prefHeight="23.0" prefWidth="99.0" />
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
<TitledPane fx:id="difficultyPane" animated="false" collapsible="false" layoutX="649.0" layoutY="7.0" prefHeight="77.0" prefWidth="305.0" text="Difficulty">
|
|
||||||
<content>
|
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
|
||||||
<children>
|
|
||||||
<ChoiceBox fx:id="difficultyBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0">
|
|
||||||
<contextMenu>
|
|
||||||
<ContextMenu>
|
|
||||||
<items>
|
|
||||||
<MenuItem mnemonicParsing="false" />
|
|
||||||
</items>
|
|
||||||
</ContextMenu>
|
|
||||||
</contextMenu>
|
|
||||||
</ChoiceBox>
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
<TitledPane fx:id="gamemodePane" animated="false" collapsible="false" layoutX="649.0" layoutY="92.0" prefHeight="77.0" prefWidth="305.0" text="Gamemode">
|
|
||||||
<content>
|
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
|
||||||
<children>
|
|
||||||
<ChoiceBox fx:id="gamemodeBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" />
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
<TitledPane fx:id="worldTypePane" animated="false" collapsible="false" layoutX="649.0" layoutY="178.0" prefHeight="77.0" prefWidth="305.0" text="World Type">
|
|
||||||
<content>
|
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
|
||||||
<children>
|
|
||||||
<ChoiceBox fx:id="worldTypeBox" layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" />
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
<TitledPane fx:id="worldTypePane1" animated="false" collapsible="false" layoutX="649.0" layoutY="265.0" prefHeight="283.0" prefWidth="305.0" text="Whitelist">
|
|
||||||
<content>
|
|
||||||
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="94.0" prefWidth="303.0">
|
|
||||||
<children>
|
|
||||||
<ChoiceBox layoutX="14.0" layoutY="14.0" prefHeight="23.0" prefWidth="276.0" />
|
|
||||||
</children>
|
|
||||||
</AnchorPane>
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
</children>
|
|
||||||
</Pane>
|
|
||||||
<ButtonBar fx:id="buttonBar" disable="true" layoutY="635.0" prefHeight="40.0" prefWidth="963.0">
|
|
||||||
<buttons>
|
|
||||||
<Button mnemonicParsing="false" text="Build Server" />
|
|
||||||
<Button defaultButton="true" mnemonicParsing="false" text="Start Server" />
|
|
||||||
</buttons>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="8.0" left="8.0" right="8.0" top="8.0" />
|
|
||||||
</padding>
|
|
||||||
</ButtonBar>
|
|
||||||
<HBox layoutY="680.0" prefHeight="33.0" prefWidth="963.0" style="-fx-background-color: ddd;">
|
|
||||||
<children>
|
|
||||||
<Label text="Status:">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="13.0" />
|
|
||||||
</font>
|
|
||||||
</Label>
|
|
||||||
<Label fx:id="statusBar" text="Ready.">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="5.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</Label>
|
|
||||||
<ProgressBar fx:id="progressBar" prefWidth="400.0" progress="0.0" visible="false">
|
|
||||||
<HBox.margin>
|
|
||||||
<Insets left="460.0" />
|
|
||||||
</HBox.margin>
|
|
||||||
</ProgressBar>
|
|
||||||
</children>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="9.0" left="9.0" right="9.0" top="9.0" />
|
|
||||||
</padding>
|
|
||||||
</HBox>
|
|
||||||
</children>
|
|
||||||
</Pane>
|
|
|
@ -1,14 +0,0 @@
|
||||||
/*
|
|
||||||
* This Kotlin source file was generated by the Gradle 'init' task.
|
|
||||||
*/
|
|
||||||
package xyz.brysonsteck.serverfordummies
|
|
||||||
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertNotNull
|
|
||||||
|
|
||||||
class AppTest {
|
|
||||||
// @Test fun appHasAGreeting() {
|
|
||||||
// val classUnderTest = App()
|
|
||||||
// assertNotNull(classUnderTest.greeting, "app should have a greeting")
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
|
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.6.0'
|
id 'org.jetbrains.kotlin.jvm' version '1.8.0'
|
||||||
|
|
||||||
// Apply the application plugin to add support for building a CLI application in Java.
|
// Apply the application plugin to add support for building a CLI application in Java.
|
||||||
id 'application'
|
id 'application'
|
||||||
|
@ -32,12 +32,9 @@ dependencies {
|
||||||
// This dependency is used by the application.
|
// This dependency is used by the application.
|
||||||
implementation 'com.google.guava:guava:31.1-jre'
|
implementation 'com.google.guava:guava:31.1-jre'
|
||||||
|
|
||||||
// Ktor
|
|
||||||
implementation "io.ktor:ktor-server-core:2.3.0"
|
|
||||||
implementation "io.ktor:ktor-server-netty:2.3.0"
|
|
||||||
|
|
||||||
// Coroutines core
|
// Coroutines core
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0"
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
|
|
|
@ -18,7 +18,7 @@ class App : Application() {
|
||||||
stage.show()
|
stage.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadFXML(fxml: String) : Parent {
|
public fun loadFXML(fxml: String) : Parent {
|
||||||
val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml"))
|
val fxmlLoader = FXMLLoader(this.javaClass.getResource(fxml + ".fxml"))
|
||||||
return fxmlLoader.load()
|
return fxmlLoader.load()
|
||||||
}
|
}
|
||||||
|
|
119
app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt
Normal file
119
app/src/main/kotlin/xyz/brysonsteck/serverfordummies/Download.kt
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
class Download: Runnable {
|
||||||
|
public enum class Status {
|
||||||
|
DOWNLOADING, PAUSED, COMPLETE, CANCELLED, ERROR
|
||||||
|
}
|
||||||
|
public var size: Int
|
||||||
|
public var downloaded: Int
|
||||||
|
public var contentLength: Int
|
||||||
|
|
||||||
|
private final val MAX_BUFFER_SIZE: Number = 1024
|
||||||
|
|
||||||
|
private var url: URL
|
||||||
|
private var status: Status
|
||||||
|
|
||||||
|
constructor (url: URL) {
|
||||||
|
this.url = url
|
||||||
|
size = -1
|
||||||
|
downloaded = 0
|
||||||
|
status = Status.DOWNLOADING
|
||||||
|
contentLength = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun status(): Status {
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun start() {
|
||||||
|
val thread = Thread(this)
|
||||||
|
thread.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFilename(url: URL): String {
|
||||||
|
val filename = url.getFile()
|
||||||
|
return filename.substring(filename.lastIndexOf('/') + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
var stream: InputStream? = null
|
||||||
|
var file: RandomAccessFile? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Open connection to URL.
|
||||||
|
var connection = url.openConnection() as HttpURLConnection;
|
||||||
|
|
||||||
|
// Specify what portion of file to download.
|
||||||
|
connection.setRequestProperty("Range", "bytes=" + downloaded + "-");
|
||||||
|
|
||||||
|
// Connect to server.
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
// Make sure response code is in the 200 range.
|
||||||
|
if (connection.responseCode / 100 != 2) {
|
||||||
|
status = Status.ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid content length.
|
||||||
|
contentLength = connection.getContentLength();
|
||||||
|
if (contentLength < 1) {
|
||||||
|
status = Status.ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the size for this download if it
|
||||||
|
hasn't been already set. */
|
||||||
|
if (size == -1) {
|
||||||
|
size = contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open file and seek to the end of it.
|
||||||
|
file = RandomAccessFile(getFilename(url), "rw");
|
||||||
|
file.seek(downloaded.toLong());
|
||||||
|
|
||||||
|
stream = connection.getInputStream();
|
||||||
|
while (status == Status.DOWNLOADING) {
|
||||||
|
/* Size buffer according to how much of the
|
||||||
|
file is left to download. */
|
||||||
|
val buffer: ByteArray;
|
||||||
|
if (size - downloaded > MAX_BUFFER_SIZE as Int) {
|
||||||
|
buffer = ByteArray(MAX_BUFFER_SIZE)
|
||||||
|
} else {
|
||||||
|
buffer = ByteArray(size - downloaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read from server into buffer.
|
||||||
|
val read = stream.read(buffer);
|
||||||
|
if (read == -1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Write buffer to file.
|
||||||
|
file.write(buffer, 0, read);
|
||||||
|
downloaded += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change status to complete if this point was
|
||||||
|
reached because downloading has finished. */
|
||||||
|
if (status == Status.DOWNLOADING) {
|
||||||
|
status = Status.COMPLETE;
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
status = Status.ERROR
|
||||||
|
} finally {
|
||||||
|
// Close file.
|
||||||
|
if (file != null) {
|
||||||
|
try {
|
||||||
|
file.close();
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close connection to server.
|
||||||
|
if (stream != null) {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (e: Exception) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,17 @@
|
||||||
package xyz.brysonsteck.serverfordummies
|
package xyz.brysonsteck.serverfordummies
|
||||||
|
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.javafx.JavaFx
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.awt.Checkbox
|
import java.awt.Checkbox
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
import javafx.beans.value.ChangeListener
|
import javafx.beans.value.ChangeListener
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
|
import javafx.concurrent.Task
|
||||||
import javafx.beans.property.BooleanProperty
|
import javafx.beans.property.BooleanProperty
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import javafx.fxml.FXML
|
import javafx.fxml.FXML
|
||||||
|
@ -19,18 +24,24 @@ import javafx.scene.control.Spinner
|
||||||
import javafx.scene.control.TitledPane
|
import javafx.scene.control.TitledPane
|
||||||
import javafx.scene.control.ButtonBar
|
import javafx.scene.control.ButtonBar
|
||||||
import javafx.scene.control.CheckBox
|
import javafx.scene.control.CheckBox
|
||||||
|
import javafx.scene.control.ProgressBar
|
||||||
import javafx.scene.layout.Border
|
import javafx.scene.layout.Border
|
||||||
import javafx.scene.layout.BorderStroke
|
import javafx.scene.layout.BorderStroke
|
||||||
import javafx.scene.layout.GridPane
|
import javafx.scene.layout.GridPane
|
||||||
import javafx.scene.layout.Pane
|
import javafx.scene.layout.Pane
|
||||||
import javafx.scene.layout.HBox
|
import javafx.scene.layout.HBox
|
||||||
|
import javafx.scene.layout.VBox
|
||||||
import javafx.scene.text.TextAlignment
|
import javafx.scene.text.TextAlignment
|
||||||
|
import javafx.scene.text.Text
|
||||||
import javafx.scene.Scene
|
import javafx.scene.Scene
|
||||||
import javafx.scene.input.MouseEvent
|
import javafx.scene.input.MouseEvent
|
||||||
import javafx.stage.FileChooser
|
import javafx.stage.FileChooser
|
||||||
import javafx.stage.FileChooser.ExtensionFilter
|
import javafx.stage.FileChooser.ExtensionFilter
|
||||||
import javafx.stage.DirectoryChooser
|
import javafx.stage.DirectoryChooser
|
||||||
|
import javafx.stage.Modality
|
||||||
|
import javafx.stage.Stage
|
||||||
import javafx.event.EventHandler
|
import javafx.event.EventHandler
|
||||||
|
import Download
|
||||||
|
|
||||||
class PrimaryController {
|
class PrimaryController {
|
||||||
@FXML
|
@FXML
|
||||||
|
@ -82,6 +93,8 @@ class PrimaryController {
|
||||||
@FXML
|
@FXML
|
||||||
lateinit private var maxTickSpinner: Spinner<kotlin.Int>
|
lateinit private var maxTickSpinner: Spinner<kotlin.Int>
|
||||||
@FXML
|
@FXML
|
||||||
|
lateinit private var statusBar: Label
|
||||||
|
@FXML
|
||||||
lateinit private var progressBar: ProgressBar
|
lateinit private var progressBar: ProgressBar
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
|
@ -92,6 +105,7 @@ class PrimaryController {
|
||||||
val result = dirChooser.showDialog(null)
|
val result = dirChooser.showDialog(null)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
currentDirectoryLabel.text = result.absolutePath
|
currentDirectoryLabel.text = result.absolutePath
|
||||||
|
val real = loadServerDir(result.absolutePath)
|
||||||
parentPane.isDisable = false
|
parentPane.isDisable = false
|
||||||
worldSettingsPane.isDisable = false
|
worldSettingsPane.isDisable = false
|
||||||
buttonBar.isDisable = false
|
buttonBar.isDisable = false
|
||||||
|
@ -115,10 +129,91 @@ class PrimaryController {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private fun onCheckboxClick() {
|
private fun onCheckboxClick() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private fun onSpinnerChange() {
|
private fun onSpinnerChange() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private fun onBuild() {
|
||||||
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
|
withContext(Dispatchers.JavaFx){statusBar.text = "Downloading a file..."}
|
||||||
|
progressBar.isVisible = true
|
||||||
|
val download = Download(URL("https://brysonsteck.xyz/pub/a-really-big-file"))
|
||||||
|
download.start()
|
||||||
|
while (download.status() == Download.Status.DOWNLOADING) {
|
||||||
|
var prog = (download.downloaded.toDouble() / download.contentLength.toDouble())
|
||||||
|
// for whatever reason I need to print something to the screen in order for it to work
|
||||||
|
print("")
|
||||||
|
if (prog >= 0.01) {
|
||||||
|
withContext(Dispatchers.JavaFx) {progressBar.progress = prog}
|
||||||
|
}
|
||||||
|
Thread.sleep(300)
|
||||||
|
}
|
||||||
|
progressBar.isVisible = false
|
||||||
|
withContext(Dispatchers.JavaFx){statusBar.text = "Ready."}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private fun onStart() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDialog() {
|
||||||
|
val dialog = Stage();
|
||||||
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
val dialogScene = Scene(App().loadFXML("dialog"), 300.0, 200.0);
|
||||||
|
dialog.setScene(dialogScene);
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadServerDir(dir: String): Boolean {
|
||||||
|
createDialog()
|
||||||
|
var directory = dir
|
||||||
|
var hasServer = false
|
||||||
|
if (!File(directory).isDirectory) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directory[directory.length-1] != File.separatorChar)
|
||||||
|
directory += File.separatorChar
|
||||||
|
|
||||||
|
val hasDummy = File(directory + "ServerForDummies").isDirectory
|
||||||
|
|
||||||
|
for (i in 20 downTo 8) {
|
||||||
|
for (j in 15 downTo 0) {
|
||||||
|
var spigotFile: String = ""
|
||||||
|
if (j == 0)
|
||||||
|
spigotFile += "spigot-1.$i.jar"
|
||||||
|
else
|
||||||
|
spigotFile += "spigot-1.$i.$j.jar";
|
||||||
|
|
||||||
|
hasServer = File(directory + spigotFile).isFile || File(directory + "server.jar").isFile
|
||||||
|
if (hasServer)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasProperties = File(directory + File.separator + "server.properties").isFile
|
||||||
|
|
||||||
|
if (hasDummy && hasServer) {
|
||||||
|
// read jproperties
|
||||||
|
println("read jproperties")
|
||||||
|
} else if (hasDummy && !hasServer && hasProperties) {
|
||||||
|
// just needs to be built
|
||||||
|
println("build")
|
||||||
|
} else if (!hasDummy && hasServer) {
|
||||||
|
// server created externally
|
||||||
|
println("server made externally")
|
||||||
|
} else {
|
||||||
|
// assume clean directory
|
||||||
|
println("none")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
import java.net.http.HttpClient
|
|
||||||
import kotlinx.coroutines.flow
|
|
||||||
|
|
||||||
sealed class DownloadStatus {
|
|
||||||
|
|
||||||
object Success : DownloadStatus()
|
|
||||||
|
|
||||||
data class Error(val message: String) : DownloadStatus()
|
|
||||||
|
|
||||||
data class Progress(val progress: Int): DownloadStatus()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// function from https://gist.githubusercontent.com/SG-K/63e379efcc3d1cd3ce4fb56ee0e29c42/raw/cd9a4a016401b7c54ec01303415b5871ffa26066/downloadFile.kt
|
|
||||||
suspend fun HttpClient.downloadFile(file: File, url: String): Flow<DownloadStatus> {
|
|
||||||
return flow {
|
|
||||||
val response = call {
|
|
||||||
url(url)
|
|
||||||
method = HttpMethod.Get
|
|
||||||
}.response
|
|
||||||
val byteArray = ByteArray(response.contentLength()!!.toInt())
|
|
||||||
var offset = 0
|
|
||||||
do {
|
|
||||||
val currentRead = response.content.readAvailable(byteArray, offset, byteArray.size)
|
|
||||||
offset += currentRead
|
|
||||||
val progress = (offset * 100f / byteArray.size).roundToInt()
|
|
||||||
emit(DownloadStatus.Progress(progress))
|
|
||||||
} while (currentRead > 0)
|
|
||||||
response.close()
|
|
||||||
if (response.status.isSuccess()) {
|
|
||||||
file.writeBytes(byteArray)
|
|
||||||
emit(DownloadStatus.Success)
|
|
||||||
} else {
|
|
||||||
emit(DownloadStatus.Error("File not downloaded"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package xyz.brysonsteck.serverfordummies.server
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
public class Server {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.layout.Pane?>
|
||||||
|
|
||||||
|
|
||||||
|
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="200.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<children>
|
||||||
|
<Pane fx:id="iconPane" layoutX="14.0" layoutY="14.0" prefHeight="82.0" prefWidth="82.0" />
|
||||||
|
<Label fx:id="dialogLabel" layoutX="115.0" layoutY="40.0" text="Dialog Box" />
|
||||||
|
<ButtonBar layoutY="157.0" prefHeight="43.0" prefWidth="400.0">
|
||||||
|
<buttons>
|
||||||
|
<Button fx:id="otherButton" mnemonicParsing="false" text="Decline" />
|
||||||
|
<Button fx:id="defaultButton" defaultButton="true" mnemonicParsing="false" text="Accept" />
|
||||||
|
</buttons>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
</ButtonBar>
|
||||||
|
</children>
|
||||||
|
</Pane>
|
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 5 KiB |
|
@ -220,7 +220,7 @@
|
||||||
</Pane>
|
</Pane>
|
||||||
<ButtonBar fx:id="buttonBar" disable="true" layoutY="635.0" prefHeight="40.0" prefWidth="963.0">
|
<ButtonBar fx:id="buttonBar" disable="true" layoutY="635.0" prefHeight="40.0" prefWidth="963.0">
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button mnemonicParsing="false" text="Build Server" />
|
<Button mnemonicParsing="false" onMouseClicked="#onBuild" text="Build Server" />
|
||||||
<Button defaultButton="true" mnemonicParsing="false" text="Start Server" />
|
<Button defaultButton="true" mnemonicParsing="false" text="Start Server" />
|
||||||
</buttons>
|
</buttons>
|
||||||
<padding>
|
<padding>
|
||||||
|
@ -239,9 +239,9 @@
|
||||||
<Insets left="5.0" />
|
<Insets left="5.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
</Label>
|
</Label>
|
||||||
<ProgressBar fx:id="progressBar" prefWidth="400.0" progress="0.0" visible="false">
|
<ProgressBar fx:id="progressBar" prefWidth="400.0" visible="false">
|
||||||
<HBox.margin>
|
<HBox.margin>
|
||||||
<Insets left="460.0" />
|
<Insets left="10.0" />
|
||||||
</HBox.margin>
|
</HBox.margin>
|
||||||
</ProgressBar>
|
</ProgressBar>
|
||||||
</children>
|
</children>
|
||||||
|
|
Loading…
Add table
Reference in a new issue