diff --git a/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt b/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt
index a0f6c50..e359f82 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardComponent.kt
@@ -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
)
}
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardInputComponent.kt b/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardInputComponent.kt
deleted file mode 100644
index f6d44d6..0000000
--- a/src/jsMain/kotlin/nl/astraeus/vst/ui/components/KeyboardInputComponent.kt
+++ /dev/null
@@ -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()) {
-
- }
- }
- }
-
- }
-}
\ No newline at end of file
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/ui/util/SVGFunctions.kt b/src/jsMain/kotlin/nl/astraeus/vst/ui/util/SVGFunctions.kt
index 6e6a4c8..a9ab4f4 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/ui/util/SVGFunctions.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/ui/util/SVGFunctions.kt
@@ -45,12 +45,12 @@ fun SVG.rect(
y: Int,
width: Int,
height: Int,
- rx: Int,
- cls: String
+ rounding: Int,
+ cls: String,
) {
this.unsafe {
+ """
-
+
""".trimIndent()
}
}
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt b/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt
index 98af233..e3b078c 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/base/web/WebsocketHandler.kt
@@ -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)
}
diff --git a/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/Test.kt b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/Test.kt
index 857c5e7..fb9778c 100644
--- a/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/Test.kt
+++ b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/Test.kt
@@ -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")
+ }
}
diff --git a/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt
index f91dde1..dea78b9 100644
--- a/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt
+++ b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/view/MainView.kt
@@ -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 = 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())
}
}
+ }
}
\ No newline at end of file
diff --git a/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/ws/WebsocketClient.kt b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/ws/WebsocketClient.kt
new file mode 100644
index 0000000..8dadb70
--- /dev/null
+++ b/test-app/src/jsMain/kotlin/nl/astraeus/vst/ui/ws/WebsocketClient.kt
@@ -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)
+ }
+}