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,
|
initialOctave: Int = 4,
|
||||||
val keyboardWidth: Int = 210,
|
val keyboardWidth: Int = 210,
|
||||||
val keyboardHeight: Int = keyboardWidth / 2,
|
val keyboardHeight: Int = keyboardWidth / 2,
|
||||||
|
val rounding: Int = 4,
|
||||||
val onNoteDown: (Int) -> Unit = {},
|
val onNoteDown: (Int) -> Unit = {},
|
||||||
val onNoteUp: (Int) -> Unit = {}
|
val onNoteUp: (Int) -> Unit = {}
|
||||||
) : Komponent() {
|
) : Komponent() {
|
||||||
@@ -222,7 +223,7 @@ class KeyboardComponent(
|
|||||||
0,
|
0,
|
||||||
whiteKeyWidth,
|
whiteKeyWidth,
|
||||||
keyboardHeight,
|
keyboardHeight,
|
||||||
0,
|
rounding,
|
||||||
if (isPressed) WhiteKeyPressedCls.name else WhiteKeyCls.name
|
if (isPressed) WhiteKeyPressedCls.name else WhiteKeyCls.name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -238,7 +239,7 @@ class KeyboardComponent(
|
|||||||
0,
|
0,
|
||||||
blackKeyWidth,
|
blackKeyWidth,
|
||||||
blackKeyHeight,
|
blackKeyHeight,
|
||||||
0,
|
rounding,
|
||||||
if (isPressed) BlackKeyPressedCls.name else BlackKeyCls.name
|
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,
|
y: Int,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
rx: Int,
|
rounding: Int,
|
||||||
cls: String
|
cls: String,
|
||||||
) {
|
) {
|
||||||
this.unsafe {
|
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()
|
""".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.PatchDao
|
||||||
import nl.astraeus.vst.base.db.PatchEntity
|
import nl.astraeus.vst.base.db.PatchEntity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ class WebsocketHandler(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// Ensure the data directory exists
|
// Ensure the data directory exists
|
||||||
private val dataDir = File(Settings.dataDir).apply {
|
private val filesDir = File(Settings.dataDir, "files").apply {
|
||||||
if (!exists()) {
|
if (!exists()) {
|
||||||
mkdirs()
|
mkdirs()
|
||||||
}
|
}
|
||||||
@@ -31,7 +32,7 @@ class WebsocketHandler(
|
|||||||
fun fileExists(hash: String): Boolean {
|
fun fileExists(hash: String): Boolean {
|
||||||
check(hash.length == 64) { "Hash must be 64 characters long" }
|
check(hash.length == 64) { "Hash must be 64 characters long" }
|
||||||
|
|
||||||
var currentDir = dataDir
|
var currentDir = filesDir
|
||||||
var remaining = hash
|
var remaining = hash
|
||||||
while(remaining.length > 8) {
|
while(remaining.length > 8) {
|
||||||
val subDir = remaining.substring(0, 8)
|
val subDir = remaining.substring(0, 8)
|
||||||
@@ -49,7 +50,7 @@ class WebsocketHandler(
|
|||||||
fun getFileFromHash(hash: String): File {
|
fun getFileFromHash(hash: String): File {
|
||||||
check(hash.length == 64) { "Hash must be 64 characters long" }
|
check(hash.length == 64) { "Hash must be 64 characters long" }
|
||||||
|
|
||||||
var currentDir = dataDir
|
var currentDir = filesDir
|
||||||
var remaining = hash
|
var remaining = hash
|
||||||
while(remaining.length > 8) {
|
while(remaining.length > 8) {
|
||||||
val subDir = remaining.substring(0, 8)
|
val subDir = remaining.substring(0, 8)
|
||||||
@@ -98,7 +99,11 @@ class WebsocketHandler(
|
|||||||
PatchDao.insert(PatchEntity(0, patchId, value))
|
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)
|
val patchEntity = PatchDao.findById(patchId)
|
||||||
|
|
||||||
if (patchEntity != null) {
|
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)
|
val file = getFileFromHash(hash)
|
||||||
if (file.exists() && file.isFile) {
|
if (file.exists() && file.isFile) {
|
||||||
val bytes = file.readBytes()
|
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,
|
// Process binary message: create hash from binary, save file in data/files/ directory,
|
||||||
// sub directories are 5 characters of the hash per directory
|
// sub directories are 5 characters of the hash per directory
|
||||||
if (channel != null && message != null) {
|
if (channel != null && message != null) {
|
||||||
try {
|
try {
|
||||||
// Get the binary data
|
// Get the binary data
|
||||||
val pooled = message.data
|
val pooled = message.data
|
||||||
val buffer = pooled.resource[0] // Get the first ByteBuffer
|
val resources = pooled.resource
|
||||||
|
|
||||||
// Convert ByteBuffer to ByteArray
|
// Collect all bytes from all buffers
|
||||||
val bytes = ByteArray(buffer.remaining())
|
var totalSize = 0
|
||||||
buffer.get(bytes)
|
|
||||||
|
|
||||||
// Free the pooled resource
|
// First pass: collect all bytes and calculate total size
|
||||||
pooled.free()
|
for (buffer in resources) {
|
||||||
|
totalSize += buffer.remaining()
|
||||||
|
}
|
||||||
|
|
||||||
// Create hash from binary data
|
// Combine all bytes into a single array
|
||||||
val hash = createHashFromBytes(bytes)
|
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
|
// Save file in data/files/ directory with subdirectories
|
||||||
val file = getFileFromHash(hash)
|
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
|
// Send the hash back to the client
|
||||||
WebSockets.sendText("BINARY_SAVED\n$hash", channel, null)
|
WebSockets.sendText("BINARY_SAVED\n$hash", channel, null)
|
||||||
|
|
||||||
|
// Free the pooled resource after processing
|
||||||
|
pooled.free()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
WebSockets.sendText("ERROR\n${e.message}", channel, null)
|
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.komp.UnsafeMode
|
||||||
import nl.astraeus.vst.ui.css.CssSettings
|
import nl.astraeus.vst.ui.css.CssSettings
|
||||||
import nl.astraeus.vst.ui.view.MainView
|
import nl.astraeus.vst.ui.view.MainView
|
||||||
|
import nl.astraeus.vst.ui.ws.WebsocketClient
|
||||||
|
|
||||||
val mainView: MainView by lazy {
|
val mainView: MainView by lazy {
|
||||||
MainView()
|
MainView()
|
||||||
@@ -16,4 +17,8 @@ fun main() {
|
|||||||
|
|
||||||
Komponent.unsafeMode = UnsafeMode.UNSAFE_SVG_ONLY
|
Komponent.unsafeMode = UnsafeMode.UNSAFE_SVG_ONLY
|
||||||
Komponent.create(document.body!!, mainView)
|
Komponent.create(document.body!!, mainView)
|
||||||
|
|
||||||
|
WebsocketClient.connect {
|
||||||
|
println("Connected")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
package nl.astraeus.vst.ui.view
|
package nl.astraeus.vst.ui.view
|
||||||
|
|
||||||
|
import kotlinx.html.InputType
|
||||||
import kotlinx.html.div
|
import kotlinx.html.div
|
||||||
import kotlinx.html.h1
|
import kotlinx.html.h1
|
||||||
import kotlinx.html.hr
|
import kotlinx.html.hr
|
||||||
|
import kotlinx.html.input
|
||||||
|
import kotlinx.html.js.onChangeFunction
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import kotlinx.html.option
|
import kotlinx.html.option
|
||||||
|
import kotlinx.html.org.w3c.dom.events.Event
|
||||||
import kotlinx.html.select
|
import kotlinx.html.select
|
||||||
import kotlinx.html.span
|
import kotlinx.html.span
|
||||||
import kotlinx.html.style
|
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.Css.noTextSelect
|
||||||
import nl.astraeus.vst.ui.css.CssName
|
import nl.astraeus.vst.ui.css.CssName
|
||||||
import nl.astraeus.vst.ui.css.hover
|
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() {
|
class MainView : Komponent() {
|
||||||
private var messages: MutableList<String> = ArrayList()
|
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 {
|
div {
|
||||||
@@ -134,209 +153,226 @@ class MainView : Komponent() {
|
|||||||
}
|
}
|
||||||
hr {}
|
hr {}
|
||||||
div {
|
div {
|
||||||
include(KeyboardComponent(
|
include(
|
||||||
keyboardWidth = keyboardWidth,
|
KeyboardComponent(
|
||||||
keyboardHeight = keyboardWidth / 2,
|
keyboardWidth = keyboardWidth,
|
||||||
onNoteDown = { println("Note down: $it") },
|
keyboardHeight = keyboardWidth / 2,
|
||||||
onNoteUp = { println("Note up: $it") },
|
onNoteDown = { println("Note down: $it") },
|
||||||
))
|
onNoteUp = { println("Note up: $it") },
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
div {
|
div {
|
||||||
span(ButtonBarCss.name) {
|
span(ButtonBarCss.name) {
|
||||||
+"SAVE"
|
+"SAVE"
|
||||||
onClickFunction = {
|
onClickFunction = {
|
||||||
val patch = VstChipWorklet.save().copy(
|
val patch = VstChipWorklet.save().copy(
|
||||||
midiId = Midi.currentInput?.id ?: "",
|
midiId = Midi.currentInput?.id ?: "",
|
||||||
midiName = Midi.currentInput?.name ?: ""
|
midiName = Midi.currentInput?.name ?: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
WebsocketClient.send("SAVE\n${JSON.stringify(patch)}")
|
WebsocketClient.send("SAVE\n${JSON.stringify(patch)}")
|
||||||
}
|
|
||||||
}
|
|
||||||
span(ButtonBarCss.name) {
|
|
||||||
+"STOP"
|
|
||||||
onClickFunction = {
|
|
||||||
VstChipWorklet.postDirectlyToWorklet(
|
|
||||||
TimedMidiMessage(getCurrentTime(), (0xb0 + midiChannel).toByte(), 123, 0)
|
|
||||||
.data.buffer.data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
span(ButtonBarCss.name) {
|
||||||
|
+"STOP"
|
||||||
|
onClickFunction = {
|
||||||
|
VstChipWorklet.postDirectlyToWorklet(
|
||||||
|
TimedMidiMessage(getCurrentTime(), (0xb0 + midiChannel).toByte(), 123, 0)
|
||||||
|
.data.buffer.data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
div(ControlsCss.name) {
|
div(ControlsCss.name) {
|
||||||
include(
|
include(
|
||||||
ExpKnobComponent(
|
ExpKnobComponent(
|
||||||
value = 0.001,
|
value = 0.001,
|
||||||
label = "Volume",
|
label = "Volume",
|
||||||
minValue = 0.001,
|
minValue = 0.001,
|
||||||
maxValue = 1.0,
|
maxValue = 1.0,
|
||||||
step = 10.0 / 127.0,
|
step = 10.0 / 127.0,
|
||||||
width = 100,
|
width = 100,
|
||||||
height = 120,
|
height = 120,
|
||||||
) { value ->
|
) { value ->
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
include(
|
include(
|
||||||
KnobComponent(
|
KnobComponent(
|
||||||
value = 0.5,
|
value = 0.5,
|
||||||
label = "Duty cycle",
|
label = "Duty cycle",
|
||||||
minValue = 0.0,
|
minValue = 0.0,
|
||||||
maxValue = 1.0,
|
maxValue = 1.0,
|
||||||
step = 2.0 / 127.0,
|
step = 2.0 / 127.0,
|
||||||
width = 100,
|
width = 100,
|
||||||
height = 120,
|
height = 120,
|
||||||
) { value ->
|
) { value ->
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
include(
|
include(
|
||||||
KnobComponent(
|
KnobComponent(
|
||||||
value = 0.5,
|
value = 0.5,
|
||||||
label = "Duty cycle",
|
label = "Duty cycle",
|
||||||
minValue = 0.0,
|
minValue = 0.0,
|
||||||
maxValue = 1.0,
|
maxValue = 1.0,
|
||||||
step = 2.0 / 127.0,
|
step = 2.0 / 127.0,
|
||||||
width = 500,
|
width = 500,
|
||||||
height = 600,
|
height = 600,
|
||||||
) { value ->
|
) { 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() {
|
private fun Style.commonButton() {
|
||||||
object MainDivCss : CssName()
|
display(Display.inlineBlock)
|
||||||
object ActiveCss : CssName()
|
padding(1.rem)
|
||||||
object ButtonCss : CssName()
|
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||||
object ButtonBarCss : CssName()
|
borderColor(Css.currentStyle.buttonBorderColor)
|
||||||
object SelectedCss : CssName()
|
borderWidth(Css.currentStyle.buttonBorderWidth)
|
||||||
object NoteBarCss : CssName()
|
color(Css.currentStyle.mainFontColor)
|
||||||
object StartSplashCss : CssName()
|
|
||||||
object StartBoxCss : CssName()
|
|
||||||
object StartButtonCss : CssName()
|
|
||||||
object ControlsCss : CssName()
|
|
||||||
|
|
||||||
init {
|
hover {
|
||||||
defineCss {
|
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover())
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
and(SelectedCss.cls()) {
|
||||||
private fun Style.commonButton() {
|
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover().hover().hover())
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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