Remove KeyboardInputComponent and add file upload and WebSocket support
Deleted unused `KeyboardInputComponent`. Added file upload functionality to `MainView` with WebSocket event handling. Introduced `WebsocketClient` class for managing server connection and file transmission. Enhanced `KeyboardComponent` with configurable key rounding. Updated `WebsocketHandler` for improved binary message handling and storage.
This commit is contained in:
@@ -38,6 +38,7 @@ class KeyboardComponent(
|
||||
initialOctave: Int = 4,
|
||||
val keyboardWidth: Int = 210,
|
||||
val keyboardHeight: Int = keyboardWidth / 2,
|
||||
val rounding: Int = 4,
|
||||
val onNoteDown: (Int) -> Unit = {},
|
||||
val onNoteUp: (Int) -> Unit = {}
|
||||
) : Komponent() {
|
||||
@@ -222,7 +223,7 @@ class KeyboardComponent(
|
||||
0,
|
||||
whiteKeyWidth,
|
||||
keyboardHeight,
|
||||
0,
|
||||
rounding,
|
||||
if (isPressed) WhiteKeyPressedCls.name else WhiteKeyCls.name
|
||||
)
|
||||
}
|
||||
@@ -238,7 +239,7 @@ class KeyboardComponent(
|
||||
0,
|
||||
blackKeyWidth,
|
||||
blackKeyHeight,
|
||||
0,
|
||||
rounding,
|
||||
if (isPressed) BlackKeyPressedCls.name else BlackKeyCls.name
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package nl.astraeus.vst.ui.components
|
||||
|
||||
import kotlinx.html.div
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.vst.ui.css.Css.defineCss
|
||||
import nl.astraeus.vst.ui.css.CssId
|
||||
import nl.astraeus.vst.ui.css.CssName
|
||||
|
||||
class KeyboardInputComponent : Komponent() {
|
||||
override fun HtmlBuilder.render() {
|
||||
div {
|
||||
+"Keyboard component"
|
||||
}
|
||||
}
|
||||
|
||||
companion object : CssId("keyboard-input") {
|
||||
object KeyboardInputCss : CssName()
|
||||
|
||||
init {
|
||||
defineCss {
|
||||
select(KeyboardInputCss.cls()) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,12 @@ fun SVG.rect(
|
||||
y: Int,
|
||||
width: Int,
|
||||
height: Int,
|
||||
rx: Int,
|
||||
cls: String
|
||||
rounding: Int,
|
||||
cls: String,
|
||||
) {
|
||||
this.unsafe {
|
||||
+ """
|
||||
<rect class="$cls" x="$x" y="$y" width="$width" height="$height" rx="$rx" />
|
||||
<rect class="$cls" x="$x" y="$y" width="$width" height="$height" rx="$rounding" rx="$rounding" />
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import nl.astraeus.vst.base.db.Database
|
||||
import nl.astraeus.vst.base.db.PatchDao
|
||||
import nl.astraeus.vst.base.db.PatchEntity
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.MessageDigest
|
||||
|
||||
@@ -22,7 +23,7 @@ class WebsocketHandler(
|
||||
|
||||
companion object {
|
||||
// Ensure the data directory exists
|
||||
private val dataDir = File(Settings.dataDir).apply {
|
||||
private val filesDir = File(Settings.dataDir, "files").apply {
|
||||
if (!exists()) {
|
||||
mkdirs()
|
||||
}
|
||||
@@ -31,7 +32,7 @@ class WebsocketHandler(
|
||||
fun fileExists(hash: String): Boolean {
|
||||
check(hash.length == 64) { "Hash must be 64 characters long" }
|
||||
|
||||
var currentDir = dataDir
|
||||
var currentDir = filesDir
|
||||
var remaining = hash
|
||||
while(remaining.length > 8) {
|
||||
val subDir = remaining.substring(0, 8)
|
||||
@@ -49,7 +50,7 @@ class WebsocketHandler(
|
||||
fun getFileFromHash(hash: String): File {
|
||||
check(hash.length == 64) { "Hash must be 64 characters long" }
|
||||
|
||||
var currentDir = dataDir
|
||||
var currentDir = filesDir
|
||||
var remaining = hash
|
||||
while(remaining.length > 8) {
|
||||
val subDir = remaining.substring(0, 8)
|
||||
@@ -98,7 +99,11 @@ class WebsocketHandler(
|
||||
PatchDao.insert(PatchEntity(0, patchId, value))
|
||||
}
|
||||
}
|
||||
WebSockets.sendText("SAVED\n$patchId", channel, null)
|
||||
WebSockets.sendText(
|
||||
"SAVED\n$patchId",
|
||||
channel,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +114,11 @@ class WebsocketHandler(
|
||||
val patchEntity = PatchDao.findById(patchId)
|
||||
|
||||
if (patchEntity != null) {
|
||||
WebSockets.sendText("LOAD\n${patchEntity.patch}", channel, null)
|
||||
WebSockets.sendText(
|
||||
"LOAD\n${patchEntity.patch}",
|
||||
channel,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,7 +131,11 @@ class WebsocketHandler(
|
||||
val file = getFileFromHash(hash)
|
||||
if (file.exists() && file.isFile) {
|
||||
val bytes = file.readBytes()
|
||||
WebSockets.sendBinary(ByteBuffer.wrap(bytes), channel, null)
|
||||
WebSockets.sendBinary(
|
||||
ByteBuffer.wrap(bytes),
|
||||
channel,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,31 +144,51 @@ class WebsocketHandler(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFullBinaryMessage(channel: WebSocketChannel?, message: BufferedBinaryMessage?) {
|
||||
override fun onFullBinaryMessage(
|
||||
channel: WebSocketChannel?,
|
||||
message: BufferedBinaryMessage?
|
||||
) {
|
||||
// Process binary message: create hash from binary, save file in data/files/ directory,
|
||||
// sub directories are 5 characters of the hash per directory
|
||||
if (channel != null && message != null) {
|
||||
try {
|
||||
// Get the binary data
|
||||
val pooled = message.data
|
||||
val buffer = pooled.resource[0] // Get the first ByteBuffer
|
||||
val resources = pooled.resource
|
||||
|
||||
// Convert ByteBuffer to ByteArray
|
||||
val bytes = ByteArray(buffer.remaining())
|
||||
buffer.get(bytes)
|
||||
// Collect all bytes from all buffers
|
||||
var totalSize = 0
|
||||
|
||||
// Free the pooled resource
|
||||
pooled.free()
|
||||
// First pass: collect all bytes and calculate total size
|
||||
for (buffer in resources) {
|
||||
totalSize += buffer.remaining()
|
||||
}
|
||||
|
||||
// Create hash from binary data
|
||||
val hash = createHashFromBytes(bytes)
|
||||
// Combine all bytes into a single array
|
||||
val combinedBytes = ByteArray(totalSize)
|
||||
var position = 0
|
||||
for (buffer in resources) {
|
||||
val remaining = buffer.remaining()
|
||||
buffer.get(combinedBytes, position, remaining)
|
||||
position += remaining
|
||||
}
|
||||
|
||||
// Create hash from combined binary data
|
||||
val hash = createHashFromBytes(combinedBytes)
|
||||
|
||||
// Save file in data/files/ directory with subdirectories
|
||||
val file = getFileFromHash(hash)
|
||||
file.writeBytes(bytes)
|
||||
|
||||
// Use FileOutputStream to write all bytes at once
|
||||
FileOutputStream(file).use { outputStream ->
|
||||
outputStream.write(combinedBytes)
|
||||
}
|
||||
|
||||
// Send the hash back to the client
|
||||
WebSockets.sendText("BINARY_SAVED\n$hash", channel, null)
|
||||
|
||||
// Free the pooled resource after processing
|
||||
pooled.free()
|
||||
} catch (e: Exception) {
|
||||
WebSockets.sendText("ERROR\n${e.message}", channel, null)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.komp.UnsafeMode
|
||||
import nl.astraeus.vst.ui.css.CssSettings
|
||||
import nl.astraeus.vst.ui.view.MainView
|
||||
import nl.astraeus.vst.ui.ws.WebsocketClient
|
||||
|
||||
val mainView: MainView by lazy {
|
||||
MainView()
|
||||
@@ -16,4 +17,8 @@ fun main() {
|
||||
|
||||
Komponent.unsafeMode = UnsafeMode.UNSAFE_SVG_ONLY
|
||||
Komponent.create(document.body!!, mainView)
|
||||
|
||||
WebsocketClient.connect {
|
||||
println("Connected")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
package nl.astraeus.vst.ui.view
|
||||
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.h1
|
||||
import kotlinx.html.hr
|
||||
import kotlinx.html.input
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import kotlinx.html.option
|
||||
import kotlinx.html.org.w3c.dom.events.Event
|
||||
import kotlinx.html.select
|
||||
import kotlinx.html.span
|
||||
import kotlinx.html.style
|
||||
@@ -37,6 +41,11 @@ import nl.astraeus.vst.ui.css.Css.defineCss
|
||||
import nl.astraeus.vst.ui.css.Css.noTextSelect
|
||||
import nl.astraeus.vst.ui.css.CssName
|
||||
import nl.astraeus.vst.ui.css.hover
|
||||
import nl.astraeus.vst.ui.ws.WebsocketClient
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.files.File
|
||||
import org.w3c.files.FileList
|
||||
import org.w3c.files.get
|
||||
|
||||
class MainView : Komponent() {
|
||||
private var messages: MutableList<String> = ArrayList()
|
||||
@@ -113,6 +122,16 @@ class MainView : Komponent() {
|
||||
}
|
||||
*/
|
||||
}
|
||||
span {
|
||||
+"Upload file: "
|
||||
input {
|
||||
type = InputType.file
|
||||
onChangeFunction = {
|
||||
fileInputSelectHandler(it)
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
@@ -134,209 +153,226 @@ class MainView : Komponent() {
|
||||
}
|
||||
hr {}
|
||||
div {
|
||||
include(KeyboardComponent(
|
||||
keyboardWidth = keyboardWidth,
|
||||
keyboardHeight = keyboardWidth / 2,
|
||||
onNoteDown = { println("Note down: $it") },
|
||||
onNoteUp = { println("Note up: $it") },
|
||||
))
|
||||
include(
|
||||
KeyboardComponent(
|
||||
keyboardWidth = keyboardWidth,
|
||||
keyboardHeight = keyboardWidth / 2,
|
||||
onNoteDown = { println("Note down: $it") },
|
||||
onNoteUp = { println("Note up: $it") },
|
||||
)
|
||||
)
|
||||
}
|
||||
/*
|
||||
div {
|
||||
span(ButtonBarCss.name) {
|
||||
+"SAVE"
|
||||
onClickFunction = {
|
||||
val patch = VstChipWorklet.save().copy(
|
||||
midiId = Midi.currentInput?.id ?: "",
|
||||
midiName = Midi.currentInput?.name ?: ""
|
||||
)
|
||||
/*
|
||||
div {
|
||||
span(ButtonBarCss.name) {
|
||||
+"SAVE"
|
||||
onClickFunction = {
|
||||
val patch = VstChipWorklet.save().copy(
|
||||
midiId = Midi.currentInput?.id ?: "",
|
||||
midiName = Midi.currentInput?.name ?: ""
|
||||
)
|
||||
|
||||
WebsocketClient.send("SAVE\n${JSON.stringify(patch)}")
|
||||
}
|
||||
}
|
||||
span(ButtonBarCss.name) {
|
||||
+"STOP"
|
||||
onClickFunction = {
|
||||
VstChipWorklet.postDirectlyToWorklet(
|
||||
TimedMidiMessage(getCurrentTime(), (0xb0 + midiChannel).toByte(), 123, 0)
|
||||
.data.buffer.data
|
||||
)
|
||||
}
|
||||
WebsocketClient.send("SAVE\n${JSON.stringify(patch)}")
|
||||
}
|
||||
}
|
||||
span(ButtonBarCss.name) {
|
||||
+"STOP"
|
||||
onClickFunction = {
|
||||
VstChipWorklet.postDirectlyToWorklet(
|
||||
TimedMidiMessage(getCurrentTime(), (0xb0 + midiChannel).toByte(), 123, 0)
|
||||
.data.buffer.data
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
div(ControlsCss.name) {
|
||||
include(
|
||||
ExpKnobComponent(
|
||||
value = 0.001,
|
||||
label = "Volume",
|
||||
minValue = 0.001,
|
||||
maxValue = 1.0,
|
||||
step = 10.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
div(ControlsCss.name) {
|
||||
include(
|
||||
ExpKnobComponent(
|
||||
value = 0.001,
|
||||
label = "Volume",
|
||||
minValue = 0.001,
|
||||
maxValue = 1.0,
|
||||
step = 10.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = 0.5,
|
||||
label = "Duty cycle",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = 0.5,
|
||||
label = "Duty cycle",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = 0.5,
|
||||
label = "Duty cycle",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 500,
|
||||
height = 600,
|
||||
) { value ->
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = 0.5,
|
||||
label = "Duty cycle",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 500,
|
||||
height = 600,
|
||||
) { value ->
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fileInputSelectHandler(event: Event) {
|
||||
val target = event.target as? HTMLInputElement
|
||||
val list: FileList? = target?.files
|
||||
|
||||
if (list == null || list.length != 1) {
|
||||
return
|
||||
}
|
||||
|
||||
val file: File? = list[0]
|
||||
|
||||
file?.let { f: File ->
|
||||
WebsocketClient.send(f)
|
||||
}
|
||||
}
|
||||
|
||||
companion object MainViewCss : CssName() {
|
||||
object MainDivCss : CssName()
|
||||
object ActiveCss : CssName()
|
||||
object ButtonCss : CssName()
|
||||
object ButtonBarCss : CssName()
|
||||
object SelectedCss : CssName()
|
||||
object NoteBarCss : CssName()
|
||||
object StartSplashCss : CssName()
|
||||
object StartBoxCss : CssName()
|
||||
object StartButtonCss : CssName()
|
||||
object ControlsCss : CssName()
|
||||
|
||||
init {
|
||||
defineCss {
|
||||
select("*") {
|
||||
select("*:before") {
|
||||
select("*:after") {
|
||||
boxSizing(BoxSizing.borderBox)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
select("html", "body") {
|
||||
margin(0.px)
|
||||
padding(0.px)
|
||||
height(100.prc)
|
||||
}
|
||||
select("html", "body") {
|
||||
backgroundColor(Css.currentStyle.mainBackgroundColor)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
|
||||
fontFamily("JetbrainsMono, monospace")
|
||||
fontSize(14.px)
|
||||
fontWeight(FontWeight.bold)
|
||||
|
||||
//transition()
|
||||
noTextSelect()
|
||||
}
|
||||
select("input", "textarea") {
|
||||
backgroundColor(Css.currentStyle.inputBackgroundColor)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
border("none")
|
||||
}
|
||||
select(cls(ButtonCss)) {
|
||||
margin(1.rem)
|
||||
commonButton()
|
||||
}
|
||||
select(cls(ButtonBarCss)) {
|
||||
margin(1.rem, 0.px)
|
||||
commonButton()
|
||||
}
|
||||
select(cls(ActiveCss)) {
|
||||
//backgroundColor(Css.currentStyle.selectedBackgroundColor)
|
||||
}
|
||||
select(cls(NoteBarCss)) {
|
||||
minHeight(4.rem)
|
||||
}
|
||||
select(cls(MainDivCss)) {
|
||||
margin(1.rem)
|
||||
}
|
||||
select("select") {
|
||||
plain("appearance", "none")
|
||||
border("0")
|
||||
outline("0")
|
||||
width(20.rem)
|
||||
padding(0.5.rem, 2.rem, 0.5.rem, 0.5.rem)
|
||||
backgroundImage("url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Caret_down_font_awesome_whitevariation.svg')")
|
||||
background("right 0.8em center/1.4em")
|
||||
backgroundColor(Css.currentStyle.inputBackgroundColor)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
borderRadius(0.25.em)
|
||||
}
|
||||
select(cls(StartSplashCss)) {
|
||||
position(Position.fixed)
|
||||
left(0.px)
|
||||
top(0.px)
|
||||
width(100.vw)
|
||||
height(100.vh)
|
||||
zIndex(100)
|
||||
backgroundColor(hsla(32, 0, 5, 0.65))
|
||||
|
||||
select(cls(StartBoxCss)) {
|
||||
position(Position.relative)
|
||||
left(25.vw)
|
||||
top(25.vh)
|
||||
width(50.vw)
|
||||
height(50.vh)
|
||||
backgroundColor(hsla(239, 50, 10, 1.0))
|
||||
borderColor(Css.currentStyle.mainFontColor)
|
||||
borderWidth(2.px)
|
||||
|
||||
select(cls(StartButtonCss)) {
|
||||
position(Position.absolute)
|
||||
left(50.prc)
|
||||
top(50.prc)
|
||||
transform(Transform("translate(-50%, -50%)"))
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||
cursor("pointer")
|
||||
}
|
||||
}
|
||||
}
|
||||
select(ControlsCss.cls()) {
|
||||
display(Display.flex)
|
||||
flexDirection(FlexDirection.row)
|
||||
justifyContent(JustifyContent.flexStart)
|
||||
alignItems(AlignItems.center)
|
||||
margin(1.rem)
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.mainBackgroundColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object MainViewCss : CssName() {
|
||||
object MainDivCss : CssName()
|
||||
object ActiveCss : CssName()
|
||||
object ButtonCss : CssName()
|
||||
object ButtonBarCss : CssName()
|
||||
object SelectedCss : CssName()
|
||||
object NoteBarCss : CssName()
|
||||
object StartSplashCss : CssName()
|
||||
object StartBoxCss : CssName()
|
||||
object StartButtonCss : CssName()
|
||||
object ControlsCss : CssName()
|
||||
private fun Style.commonButton() {
|
||||
display(Display.inlineBlock)
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||
borderColor(Css.currentStyle.buttonBorderColor)
|
||||
borderWidth(Css.currentStyle.buttonBorderWidth)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
|
||||
init {
|
||||
defineCss {
|
||||
select("*") {
|
||||
select("*:before") {
|
||||
select("*:after") {
|
||||
boxSizing(BoxSizing.borderBox)
|
||||
}
|
||||
}
|
||||
}
|
||||
select("html", "body") {
|
||||
margin(0.px)
|
||||
padding(0.px)
|
||||
height(100.prc)
|
||||
}
|
||||
select("html", "body") {
|
||||
backgroundColor(Css.currentStyle.mainBackgroundColor)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
|
||||
fontFamily("JetbrainsMono, monospace")
|
||||
fontSize(14.px)
|
||||
fontWeight(FontWeight.bold)
|
||||
|
||||
//transition()
|
||||
noTextSelect()
|
||||
}
|
||||
select("input", "textarea") {
|
||||
backgroundColor(Css.currentStyle.inputBackgroundColor)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
border("none")
|
||||
}
|
||||
select(cls(ButtonCss)) {
|
||||
margin(1.rem)
|
||||
commonButton()
|
||||
}
|
||||
select(cls(ButtonBarCss)) {
|
||||
margin(1.rem, 0.px)
|
||||
commonButton()
|
||||
}
|
||||
select(cls(ActiveCss)) {
|
||||
//backgroundColor(Css.currentStyle.selectedBackgroundColor)
|
||||
}
|
||||
select(cls(NoteBarCss)) {
|
||||
minHeight(4.rem)
|
||||
}
|
||||
select(cls(MainDivCss)) {
|
||||
margin(1.rem)
|
||||
}
|
||||
select("select") {
|
||||
plain("appearance", "none")
|
||||
border("0")
|
||||
outline("0")
|
||||
width(20.rem)
|
||||
padding(0.5.rem, 2.rem, 0.5.rem, 0.5.rem)
|
||||
backgroundImage("url('https://upload.wikimedia.org/wikipedia/commons/9/9d/Caret_down_font_awesome_whitevariation.svg')")
|
||||
background("right 0.8em center/1.4em")
|
||||
backgroundColor(Css.currentStyle.inputBackgroundColor)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
borderRadius(0.25.em)
|
||||
}
|
||||
select(cls(StartSplashCss)) {
|
||||
position(Position.fixed)
|
||||
left(0.px)
|
||||
top(0.px)
|
||||
width(100.vw)
|
||||
height(100.vh)
|
||||
zIndex(100)
|
||||
backgroundColor(hsla(32, 0, 5, 0.65))
|
||||
|
||||
select(cls(StartBoxCss)) {
|
||||
position(Position.relative)
|
||||
left(25.vw)
|
||||
top(25.vh)
|
||||
width(50.vw)
|
||||
height(50.vh)
|
||||
backgroundColor(hsla(239, 50, 10, 1.0))
|
||||
borderColor(Css.currentStyle.mainFontColor)
|
||||
borderWidth(2.px)
|
||||
|
||||
select(cls(StartButtonCss)) {
|
||||
position(Position.absolute)
|
||||
left(50.prc)
|
||||
top(50.prc)
|
||||
transform(Transform("translate(-50%, -50%)"))
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||
cursor("pointer")
|
||||
}
|
||||
}
|
||||
}
|
||||
select(ControlsCss.cls()) {
|
||||
display(Display.flex)
|
||||
flexDirection(FlexDirection.row)
|
||||
justifyContent(JustifyContent.flexStart)
|
||||
alignItems(AlignItems.center)
|
||||
margin(1.rem)
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.mainBackgroundColor)
|
||||
}
|
||||
}
|
||||
hover {
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover())
|
||||
}
|
||||
|
||||
private fun Style.commonButton() {
|
||||
display(Display.inlineBlock)
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||
borderColor(Css.currentStyle.buttonBorderColor)
|
||||
borderWidth(Css.currentStyle.buttonBorderWidth)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
|
||||
hover {
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover())
|
||||
}
|
||||
and(SelectedCss.cls()) {
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover().hover().hover())
|
||||
}
|
||||
and(SelectedCss.cls()) {
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover().hover().hover())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
@file:OptIn(ExperimentalJsExport::class)
|
||||
|
||||
package nl.astraeus.vst.ui.ws
|
||||
|
||||
import kotlinx.browser.window
|
||||
import org.khronos.webgl.ArrayBuffer
|
||||
import org.w3c.dom.MessageEvent
|
||||
import org.w3c.dom.WebSocket
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.files.Blob
|
||||
|
||||
private const val WEBSOCKET_PART_SIZE = 65000
|
||||
|
||||
object WebsocketClient {
|
||||
var websocket: WebSocket? = null
|
||||
var interval: Int = 0
|
||||
|
||||
fun connect(onConnect: () -> Unit) {
|
||||
close()
|
||||
|
||||
websocket =
|
||||
if (window.location.hostname.contains("localhost") || window.location.hostname.contains("192.168")) {
|
||||
WebSocket("ws://${window.location.hostname}:${window.location.port}/ws")
|
||||
} else {
|
||||
WebSocket("wss://${window.location.hostname}/ws")
|
||||
}
|
||||
|
||||
websocket?.also { ws ->
|
||||
ws.onopen = {
|
||||
onOpen(ws, it)
|
||||
onConnect()
|
||||
}
|
||||
ws.onmessage = { onMessage(ws, it) }
|
||||
ws.onclose = { onClose(ws, it) }
|
||||
ws.onerror = { onError(ws, it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
websocket?.close(-1, "Application closed socket.")
|
||||
}
|
||||
|
||||
fun onOpen(
|
||||
ws: WebSocket,
|
||||
event: Event
|
||||
) {
|
||||
interval = window.setInterval({
|
||||
val actualWs = websocket
|
||||
|
||||
if (actualWs == null) {
|
||||
window.clearInterval(interval)
|
||||
|
||||
console.log("Connection to the server was lost!\\nPlease try again later.")
|
||||
reconnect()
|
||||
}
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
fun reconnect() {
|
||||
val actualWs = websocket
|
||||
|
||||
if (actualWs != null) {
|
||||
if (actualWs.readyState == WebSocket.OPEN) {
|
||||
console.log("Connection to the server was lost!\\nPlease try again later.")
|
||||
} else {
|
||||
window.setTimeout({
|
||||
reconnect()
|
||||
}, 1000)
|
||||
}
|
||||
} else {
|
||||
connect {}
|
||||
|
||||
window.setTimeout({
|
||||
reconnect()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessage(
|
||||
ws: WebSocket,
|
||||
event: Event
|
||||
) {
|
||||
if (event is MessageEvent) {
|
||||
val data = event.data
|
||||
|
||||
if (data is String) {
|
||||
console.log("Received message: $data")
|
||||
} else if (data is ArrayBuffer) {
|
||||
console.log("Received binary message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onClose(
|
||||
ws: WebSocket,
|
||||
event: Event
|
||||
): dynamic {
|
||||
websocket = null
|
||||
|
||||
return "dynamic"
|
||||
}
|
||||
|
||||
fun onError(
|
||||
ws: WebSocket,
|
||||
event: Event
|
||||
): dynamic {
|
||||
console.log("Error websocket!", ws, event)
|
||||
|
||||
websocket = null
|
||||
|
||||
return "dynamic"
|
||||
}
|
||||
|
||||
fun send(message: String) {
|
||||
websocket?.send(message)
|
||||
}
|
||||
|
||||
fun send(file: Blob) {
|
||||
websocket?.send(file)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user