Setup project structure
This commit is contained in:
parent
166bcc3fc2
commit
35a6544e65
44 changed files with 2439 additions and 1 deletions
9
src/jsMain/kotlin/de/kif/frontend/main.kt
Normal file
9
src/jsMain/kotlin/de/kif/frontend/main.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
package de.kif.frontend
|
||||
|
||||
import de.westermann.kwebview.components.h1
|
||||
import de.westermann.kwebview.components.init
|
||||
|
||||
fun main() = init {
|
||||
clear()
|
||||
h1("Test")
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Delegate to easily access html attributes.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class AttributeDelegate(
|
||||
private val paramName: String? = null
|
||||
) {
|
||||
|
||||
private fun getParamName(property: KProperty<*>): String = paramName ?: property.name.toLowerCase()
|
||||
|
||||
operator fun getValue(container: View, property: KProperty<*>) = container.html.getAttribute(getParamName(property))
|
||||
|
||||
operator fun setValue(container: View, property: KProperty<*>, value: String?) {
|
||||
if (value == null) {
|
||||
container.html.removeAttribute(getParamName(property))
|
||||
} else {
|
||||
container.html.setAttribute(getParamName(property), value.toString())
|
||||
}
|
||||
}
|
||||
}
|
49
src/jsMain/kotlin/de/westermann/kwebview/ClassDelegate.kt
Normal file
49
src/jsMain/kotlin/de/westermann/kwebview/ClassDelegate.kt
Normal file
|
@ -0,0 +1,49 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.basic.property
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
* Delegate to easily set css classes as boolean attributes.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class ClassDelegate(
|
||||
className: String? = null
|
||||
) {
|
||||
|
||||
private lateinit var container: View
|
||||
private lateinit var paramName: String
|
||||
private lateinit var classProperty: Property<Boolean>
|
||||
|
||||
operator fun getValue(container: View, property: KProperty<*>): Property<Boolean> {
|
||||
if (!this::container.isInitialized) {
|
||||
this.container = container
|
||||
}
|
||||
|
||||
if (!this::paramName.isInitialized) {
|
||||
var name = property.name.toDashCase()
|
||||
if (name.endsWith("-property")) {
|
||||
name = name.replace("-property", "")
|
||||
}
|
||||
paramName = name
|
||||
}
|
||||
|
||||
if (!this::classProperty.isInitialized) {
|
||||
classProperty = property(container.html.classList.contains(paramName))
|
||||
|
||||
classProperty.onChange {
|
||||
container.html.classList.toggle(paramName, classProperty.value)
|
||||
}
|
||||
}
|
||||
|
||||
return classProperty
|
||||
}
|
||||
|
||||
init {
|
||||
if (className != null) {
|
||||
this.paramName = className
|
||||
}
|
||||
}
|
||||
}
|
119
src/jsMain/kotlin/de/westermann/kwebview/ClassList.kt
Normal file
119
src/jsMain/kotlin/de/westermann/kwebview/ClassList.kt
Normal file
|
@ -0,0 +1,119 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.ListenerReference
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import org.w3c.dom.DOMTokenList
|
||||
|
||||
/**
|
||||
* Represents the css classes of an html element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class ClassList(
|
||||
private val list: DOMTokenList
|
||||
) : Iterable<String> {
|
||||
|
||||
private val bound: MutableMap<String, Bound> = mutableMapOf()
|
||||
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
fun add(clazz: String) {
|
||||
if (clazz in bound) {
|
||||
val p = bound[clazz] ?: return
|
||||
if (p.property is Property<Boolean>) {
|
||||
p.property.value = true
|
||||
} else {
|
||||
throw IllegalStateException("The given class is bound and cannot be modified manually!")
|
||||
}
|
||||
} else {
|
||||
list.add(clazz)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
operator fun plusAssign(clazz: String) = add(clazz)
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
fun remove(clazz: String) {
|
||||
if (clazz in bound) {
|
||||
val p = bound[clazz] ?: return
|
||||
if (p.property is Property<Boolean>) {
|
||||
p.property.value = false
|
||||
} else {
|
||||
throw IllegalStateException("The given class is bound and cannot be modified manually!")
|
||||
}
|
||||
} else {
|
||||
list.remove(clazz)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove css class.
|
||||
*/
|
||||
operator fun minusAssign(clazz: String) = remove(clazz)
|
||||
|
||||
/**
|
||||
* Check if css class exits.
|
||||
*/
|
||||
operator fun get(clazz: String): Boolean = list.contains(clazz)
|
||||
|
||||
/**
|
||||
* Check if css class exits.
|
||||
*/
|
||||
operator fun contains(clazz: String): Boolean = list.contains(clazz)
|
||||
|
||||
/**
|
||||
* Set css class present.
|
||||
*/
|
||||
operator fun set(clazz: String, present: Boolean) =
|
||||
if (present) {
|
||||
add(clazz)
|
||||
} else {
|
||||
remove(clazz)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle css class.
|
||||
*/
|
||||
fun toggle(clazz: String, force: Boolean? = null) = set(clazz, force ?: !contains(clazz))
|
||||
|
||||
fun bind(clazz: String, property: ReadOnlyProperty<Boolean>) {
|
||||
if (clazz in bound) {
|
||||
throw IllegalArgumentException("Class is already bound!")
|
||||
}
|
||||
|
||||
set(clazz, property.value)
|
||||
bound[clazz] = Bound(property,
|
||||
property.onChange.reference {
|
||||
list.toggle(clazz, property.value)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun unbind(clazz: String) {
|
||||
if (clazz !in bound) {
|
||||
throw IllegalArgumentException("Class is not bound!")
|
||||
}
|
||||
|
||||
bound[clazz]?.reference?.remove()
|
||||
bound -= clazz
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<String> {
|
||||
return toString().split(" +".toRegex()).iterator()
|
||||
}
|
||||
|
||||
override fun toString(): String = list.value
|
||||
|
||||
private data class Bound(
|
||||
val property: ReadOnlyProperty<Boolean>,
|
||||
val reference: ListenerReference<Unit>?
|
||||
)
|
||||
}
|
128
src/jsMain/kotlin/de/westermann/kwebview/DataSet.kt
Normal file
128
src/jsMain/kotlin/de/westermann/kwebview/DataSet.kt
Normal file
|
@ -0,0 +1,128 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.ListenerReference
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import org.w3c.dom.DOMStringMap
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.dom.set
|
||||
|
||||
/**
|
||||
* Represents the css classes of an html element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class DataSet(
|
||||
private val map: DOMStringMap
|
||||
) {
|
||||
|
||||
private val bound: MutableMap<String, Bound> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Add css class.
|
||||
*/
|
||||
operator fun plusAssign(entry: Pair<String, String>) {
|
||||
if (entry.first in bound) {
|
||||
bound[entry.first]?.set(entry.second)
|
||||
} else {
|
||||
map[entry.first] = entry.second
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove css class.
|
||||
*/
|
||||
operator fun minusAssign(key: String) {
|
||||
if (key in bound) {
|
||||
bound[key]?.set(null)
|
||||
} else {
|
||||
delete(map, key)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if css class exits.
|
||||
*/
|
||||
operator fun get(key: String): String? = map[key]
|
||||
|
||||
/**
|
||||
* Set css class present.
|
||||
*/
|
||||
operator fun set(key: String, value: String?) =
|
||||
if (value == null) {
|
||||
this -= key
|
||||
} else {
|
||||
this += key to value
|
||||
}
|
||||
|
||||
fun bind(key: String, property: ReadOnlyProperty<String>) {
|
||||
if (key in bound) {
|
||||
throw IllegalArgumentException("Class is already bound!")
|
||||
}
|
||||
|
||||
bound[key] = Bound(key, null, property)
|
||||
}
|
||||
|
||||
fun bind(key: String, property: ReadOnlyProperty<String?>) {
|
||||
if (key in bound) {
|
||||
throw IllegalArgumentException("Class is already bound!")
|
||||
}
|
||||
|
||||
bound[key] = Bound(key, property, null)
|
||||
}
|
||||
|
||||
fun unbind(key: String) {
|
||||
if (key !in bound) {
|
||||
throw IllegalArgumentException("Class is not bound!")
|
||||
}
|
||||
|
||||
bound[key]?.reference?.remove()
|
||||
bound -= key
|
||||
}
|
||||
|
||||
private inner class Bound(
|
||||
val key: String,
|
||||
val propertyNullable: ReadOnlyProperty<String?>?,
|
||||
val property: ReadOnlyProperty<String>?
|
||||
) {
|
||||
|
||||
var reference: ListenerReference<Unit>? = null
|
||||
|
||||
fun set(value: String?) {
|
||||
if (propertyNullable != null && propertyNullable is Property) {
|
||||
propertyNullable.value = value
|
||||
} else if (property != null && property is Property && value != null) {
|
||||
property.value = value
|
||||
} else {
|
||||
throw IllegalStateException("The given class is bound and cannot be modified manually!")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
if (propertyNullable != null) {
|
||||
reference = propertyNullable.onChange.reference {
|
||||
val value = propertyNullable.value
|
||||
if (value == null) {
|
||||
delete(map, key)
|
||||
} else {
|
||||
map[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
val value = propertyNullable.value
|
||||
if (value == null) {
|
||||
delete(map, key)
|
||||
} else {
|
||||
map[key] = value
|
||||
}
|
||||
} else if (property != null) {
|
||||
reference = property.onChange.reference {
|
||||
map[key] = property.value
|
||||
}
|
||||
|
||||
map[key] = property.value
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
62
src/jsMain/kotlin/de/westermann/kwebview/Dimension.kt
Normal file
62
src/jsMain/kotlin/de/westermann/kwebview/Dimension.kt
Normal file
|
@ -0,0 +1,62 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* @author lars
|
||||
*/
|
||||
data class Dimension(
|
||||
val left: Double,
|
||||
val top: Double,
|
||||
val width: Double = 0.0,
|
||||
val height: Double = 0.0
|
||||
) {
|
||||
|
||||
constructor(position: Point, size: Point = Point.ZERO) : this(position.x, position.y, size.x, size.y)
|
||||
|
||||
val position: Point
|
||||
get() = Point(left, top)
|
||||
|
||||
val size: Point
|
||||
get() = Point(width, height)
|
||||
|
||||
val right: Double
|
||||
get() = left + width
|
||||
|
||||
val bottom: Double
|
||||
get() = top + height
|
||||
|
||||
val edges: Set<Point>
|
||||
get() = setOf(
|
||||
Point(left, top),
|
||||
Point(right, top),
|
||||
Point(left, bottom),
|
||||
Point(right, bottom)
|
||||
)
|
||||
|
||||
val normalized: Dimension
|
||||
get() {
|
||||
val l = min(left, right)
|
||||
val t = min(top, bottom)
|
||||
return Dimension(l, t, abs(width), abs(width))
|
||||
}
|
||||
|
||||
operator fun contains(other: Dimension): Boolean = !(other.left > right ||
|
||||
other.right < left ||
|
||||
other.top > bottom ||
|
||||
other.bottom < top)
|
||||
|
||||
|
||||
operator fun contains(other: Point): Boolean {
|
||||
val n = normalized
|
||||
return (n.left <= other.x && (n.left + width) >= other.x)
|
||||
&& (n.top <= other.y && (n.top + height) >= other.y)
|
||||
}
|
||||
|
||||
operator fun plus(point: Point) = copy(left + point.x, top + point.y)
|
||||
|
||||
companion object {
|
||||
val ZERO = Dimension(0.0, 0.0)
|
||||
}
|
||||
}
|
4
src/jsMain/kotlin/de/westermann/kwebview/KWebViewDsl.kt
Normal file
4
src/jsMain/kotlin/de/westermann/kwebview/KWebViewDsl.kt
Normal file
|
@ -0,0 +1,4 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
@DslMarker
|
||||
annotation class KWebViewDsl
|
50
src/jsMain/kotlin/de/westermann/kwebview/Point.kt
Normal file
50
src/jsMain/kotlin/de/westermann/kwebview/Point.kt
Normal file
|
@ -0,0 +1,50 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* @author lars
|
||||
*/
|
||||
data class Point(
|
||||
val x: Double,
|
||||
val y: Double
|
||||
) {
|
||||
constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
|
||||
|
||||
operator fun plus(number: Int) = Point(x + number, y + number)
|
||||
operator fun plus(number: Double) = Point(x + number, y + number)
|
||||
operator fun plus(point: Point) = Point(x + point.x, y + point.y)
|
||||
|
||||
operator fun minus(number: Int) = Point(x - number, y - number)
|
||||
operator fun minus(number: Double) = Point(x - number, y - number)
|
||||
operator fun minus(point: Point) = Point(x - point.x, y - point.y)
|
||||
|
||||
operator fun times(number: Int) = Point(x * number, y * number)
|
||||
operator fun times(number: Double) = Point(x * number, y * number)
|
||||
operator fun times(point: Point) = Point(x * point.x, y * point.y)
|
||||
|
||||
operator fun div(number: Int) = Point(x / number, y / number)
|
||||
operator fun div(number: Double) = Point(x / number, y / number)
|
||||
operator fun div(point: Point) = Point(x / point.x, y / point.y)
|
||||
|
||||
operator fun unaryMinus(): Point = Point(-x, -y)
|
||||
|
||||
fun min(): Double = min(x, y)
|
||||
fun max(): Double = max(x, y)
|
||||
|
||||
val isZero: Boolean
|
||||
get() = x == 0.0 && y == 0.0
|
||||
|
||||
companion object {
|
||||
val ZERO = Point(0.0, 0.0)
|
||||
}
|
||||
|
||||
val asPx: String
|
||||
get() = "${x}px, ${y}px"
|
||||
|
||||
fun distance(): Double = sqrt(x * x + y * y)
|
||||
|
||||
infix fun distance(other: Point) = (this - other).distance()
|
||||
}
|
124
src/jsMain/kotlin/de/westermann/kwebview/View.kt
Normal file
124
src/jsMain/kotlin/de/westermann/kwebview/View.kt
Normal file
|
@ -0,0 +1,124 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.EventHandler
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.css.CSSStyleDeclaration
|
||||
import org.w3c.dom.events.FocusEvent
|
||||
import org.w3c.dom.events.KeyboardEvent
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import org.w3c.dom.events.WheelEvent
|
||||
|
||||
abstract class View(view: HTMLElement = createHtmlView()) {
|
||||
|
||||
open val html: HTMLElement = view.also { view ->
|
||||
this::class.simpleName?.let { name ->
|
||||
view.classList.add(name.toDashCase())
|
||||
}
|
||||
}
|
||||
|
||||
val classList = ClassList(view.classList)
|
||||
val dataset = DataSet(view.dataset)
|
||||
|
||||
var id by AttributeDelegate()
|
||||
|
||||
val clientLeft: Int
|
||||
get() = html.clientLeft
|
||||
val clientTop: Int
|
||||
get() = html.clientTop
|
||||
val clientWidth: Int
|
||||
get() = html.clientWidth
|
||||
val clientHeight: Int
|
||||
get() = html.clientHeight
|
||||
|
||||
val offsetLeft: Int
|
||||
get() = html.offsetLeft
|
||||
val offsetTop: Int
|
||||
get() = html.offsetTop
|
||||
val offsetWidth: Int
|
||||
get() = html.offsetWidth
|
||||
val offsetHeight: Int
|
||||
get() = html.offsetHeight
|
||||
|
||||
val offsetLeftTotal: Int
|
||||
get() {
|
||||
var element: HTMLElement? = html
|
||||
var offset = 0
|
||||
while (element != null) {
|
||||
offset += element.offsetLeft
|
||||
element = element.offsetParent as? HTMLElement
|
||||
}
|
||||
return offset
|
||||
}
|
||||
val offsetTopTotal: Int
|
||||
get() {
|
||||
var element: HTMLElement? = html
|
||||
var offset = 0
|
||||
while (element != null) {
|
||||
offset += element.offsetTop
|
||||
element = element.offsetParent as? HTMLElement
|
||||
}
|
||||
return offset
|
||||
}
|
||||
|
||||
val dimension: Dimension
|
||||
get() = html.getBoundingClientRect().toDimension()
|
||||
|
||||
var title by AttributeDelegate()
|
||||
|
||||
val style = view.style
|
||||
fun style(block: CSSStyleDeclaration.() -> Unit) {
|
||||
block(style)
|
||||
}
|
||||
|
||||
fun focus() {
|
||||
html.focus()
|
||||
}
|
||||
|
||||
fun blur() {
|
||||
html.blur()
|
||||
}
|
||||
|
||||
fun click() {
|
||||
html.click()
|
||||
}
|
||||
|
||||
val onClick = EventHandler<MouseEvent>()
|
||||
val onDblClick = EventHandler<MouseEvent>()
|
||||
val onContext = EventHandler<MouseEvent>()
|
||||
|
||||
val onMouseDown = EventHandler<MouseEvent>()
|
||||
val onMouseMove = EventHandler<MouseEvent>()
|
||||
val onMouseUp = EventHandler<MouseEvent>()
|
||||
val onMouseEnter = EventHandler<MouseEvent>()
|
||||
val onMouseLeave = EventHandler<MouseEvent>()
|
||||
|
||||
val onWheel = EventHandler<WheelEvent>()
|
||||
|
||||
val onKeyDown = EventHandler<KeyboardEvent>()
|
||||
val onKeyPress = EventHandler<KeyboardEvent>()
|
||||
val onKeyUp = EventHandler<KeyboardEvent>()
|
||||
|
||||
val onFocus = EventHandler<FocusEvent>()
|
||||
val onBlur = EventHandler<FocusEvent>()
|
||||
|
||||
init {
|
||||
onClick.bind(view, "click")
|
||||
onDblClick.bind(view, "dblclick")
|
||||
onContext.bind(view, "contextmenu")
|
||||
|
||||
onMouseDown.bind(view, "mousedown")
|
||||
onMouseMove.bind(view, "mousemove")
|
||||
onMouseUp.bind(view, "mouseup")
|
||||
onMouseEnter.bind(view, "mouseenter")
|
||||
onMouseLeave.bind(view, "mouseleave")
|
||||
|
||||
onWheel.bind(view, "wheel")
|
||||
|
||||
onKeyDown.bind(view, "keydown")
|
||||
onKeyPress.bind(view, "keypress")
|
||||
onKeyUp.bind(view, "keyup")
|
||||
|
||||
onFocus.bind(view, "focus")
|
||||
onBlur.bind(view, "blur")
|
||||
}
|
||||
}
|
69
src/jsMain/kotlin/de/westermann/kwebview/ViewCollection.kt
Normal file
69
src/jsMain/kotlin/de/westermann/kwebview/ViewCollection.kt
Normal file
|
@ -0,0 +1,69 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import org.w3c.dom.HTMLElement
|
||||
import kotlin.dom.clear
|
||||
|
||||
/**
|
||||
* @author lars
|
||||
*/
|
||||
abstract class ViewCollection<V : View>(view: HTMLElement = createHtmlView()) : View(view), Iterable<V> {
|
||||
|
||||
private val children: MutableList<V> = mutableListOf()
|
||||
|
||||
fun append(view: V) {
|
||||
children += view
|
||||
html.appendChild(view.html)
|
||||
}
|
||||
|
||||
operator fun plusAssign(view: V) = append(view)
|
||||
|
||||
fun prepand(view: V) {
|
||||
children.add(0, view)
|
||||
html.insertBefore(view.html, html.firstChild)
|
||||
}
|
||||
|
||||
fun remove(view: V) {
|
||||
if (children.contains(view)) {
|
||||
children -= view
|
||||
html.removeChild(view.html)
|
||||
}
|
||||
}
|
||||
|
||||
fun toForeground(view: V) {
|
||||
if (view in children && children.indexOf(view) < children.size - 1) {
|
||||
remove(view)
|
||||
append(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun toBackground(view: V) {
|
||||
if (view in children && children.indexOf(view) > 0) {
|
||||
remove(view)
|
||||
prepand(view)
|
||||
}
|
||||
}
|
||||
|
||||
fun first(): V = children.first()
|
||||
fun last(): V = children.last()
|
||||
|
||||
operator fun minusAssign(view: V) = remove(view)
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = children.isEmpty()
|
||||
|
||||
fun clear() {
|
||||
children.clear()
|
||||
html.clear()
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<V> = children.iterator()
|
||||
|
||||
val size: Int
|
||||
get() = children.size
|
||||
|
||||
operator fun contains(view: V) = children.contains(view)
|
||||
|
||||
operator fun V.unaryPlus() {
|
||||
append(this)
|
||||
}
|
||||
}
|
57
src/jsMain/kotlin/de/westermann/kwebview/ViewForLabel.kt
Normal file
57
src/jsMain/kotlin/de/westermann/kwebview/ViewForLabel.kt
Normal file
|
@ -0,0 +1,57 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kwebview.components.Label
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
abstract class ViewForLabel : View(createHtmlView<HTMLInputElement>()) {
|
||||
override val html = super.html as HTMLInputElement
|
||||
|
||||
private var label: Label? = null
|
||||
|
||||
fun setLabel(label: Label) {
|
||||
if (this.label != null) {
|
||||
throw IllegalStateException("Label already set!")
|
||||
}
|
||||
|
||||
this.label = label
|
||||
|
||||
val id = id
|
||||
if (id?.isNotBlank() == true) {
|
||||
label.html.htmlFor = id
|
||||
} else {
|
||||
val newId = this::class.simpleName?.toDashCase() + "-" + generateId()
|
||||
this.id = newId
|
||||
label.html.htmlFor = newId
|
||||
}
|
||||
}
|
||||
|
||||
private var requiredInternal by AttributeDelegate("required")
|
||||
var required: Boolean
|
||||
get() = requiredInternal != null
|
||||
set(value) {
|
||||
requiredInternal = if (value) "required" else null
|
||||
}
|
||||
private var readonlyInternal by AttributeDelegate("readonly")
|
||||
var readonly: Boolean
|
||||
get() = readonlyInternal != null
|
||||
set(value) {
|
||||
readonlyInternal = if (value) "readonly" else null
|
||||
}
|
||||
|
||||
var tabindex by AttributeDelegate()
|
||||
fun preventTabStop() {
|
||||
tabindex = "-1"
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun generateId(length: Int = 16): String {
|
||||
var str = ""
|
||||
while (str.length <= length) {
|
||||
str += abs(Random.nextLong()).toString(36)
|
||||
}
|
||||
return str.take(length)
|
||||
}
|
||||
}
|
||||
}
|
42
src/jsMain/kotlin/de/westermann/kwebview/components/Body.kt
Normal file
42
src/jsMain/kotlin/de/westermann/kwebview/components/Body.kt
Normal file
|
@ -0,0 +1,42 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.i18n
|
||||
import org.w3c.dom.DocumentReadyState
|
||||
import org.w3c.dom.HTMLBodyElement
|
||||
import org.w3c.dom.LOADING
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
|
||||
object Body : ViewCollection<View>(document.body
|
||||
?: throw NullPointerException("Access to body before body was loaded")) {
|
||||
override val html = super.html as HTMLBodyElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun init(language: String? = null, block: Body.() -> Unit) {
|
||||
var done = if (language == null) 1 else 2
|
||||
if (document.readyState == DocumentReadyState.LOADING) {
|
||||
window.onload = {
|
||||
done -= 1
|
||||
if (done <= 0) {
|
||||
block(Body)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done -= 1
|
||||
if (done <= 0) {
|
||||
block(Body)
|
||||
}
|
||||
}
|
||||
if (language != null) {
|
||||
i18n.load(language) {
|
||||
done -= 1
|
||||
if (done <= 0) {
|
||||
block(Body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLDivElement
|
||||
|
||||
class BoxView() : ViewCollection<View>(createHtmlView<HTMLDivElement>()) {
|
||||
override val html = super.html as HTMLDivElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in BoxView>.boxView(vararg classes: String, init: BoxView.() -> Unit = {}): BoxView {
|
||||
val view = BoxView()
|
||||
for (c in classes) {
|
||||
view.classList += c
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLButtonElement
|
||||
|
||||
/**
|
||||
* Represents a html span element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class Button() : ViewCollection<View>(createHtmlView<HTMLButtonElement>()) {
|
||||
|
||||
constructor(text: String) : this() {
|
||||
this.text = text
|
||||
}
|
||||
|
||||
override val html = super.html as HTMLButtonElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Button>.button(text: String = "", init: Button.() -> Unit = {}) =
|
||||
Button(text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Button>.button(text: ReadOnlyProperty<String>, init: Button.() -> Unit = {}) =
|
||||
Button(text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Button>.button(init: Button.() -> Unit = {}) =
|
||||
Button().also(this::append).also(init)
|
|
@ -0,0 +1,68 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.ValidationProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
|
||||
class Checkbox(
|
||||
initValue: Boolean = false
|
||||
) : ViewForLabel() {
|
||||
|
||||
fun bind(property: ReadOnlyProperty<Boolean>) {
|
||||
checkedProperty.bind(property)
|
||||
readonly = true
|
||||
}
|
||||
|
||||
fun bind(property: Property<Boolean>) {
|
||||
checkedProperty.bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
checkedProperty.unbind()
|
||||
}
|
||||
|
||||
var checked: Boolean
|
||||
get() = html.checked
|
||||
set(value) {
|
||||
html.checked = value
|
||||
checkedProperty.invalidate()
|
||||
}
|
||||
|
||||
val checkedProperty: Property<Boolean> = property(this::checked)
|
||||
|
||||
init {
|
||||
checked = initValue
|
||||
html.type = "checkbox"
|
||||
|
||||
var lastValue = checked
|
||||
val changeListener = object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
val value = checked
|
||||
if (value != checkedProperty.value || value != lastValue) {
|
||||
lastValue = value
|
||||
checkedProperty.value = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.addEventListener("change", changeListener)
|
||||
html.addEventListener("keyup", changeListener)
|
||||
html.addEventListener("keypress", changeListener)
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Checkbox>.checkbox(value: Boolean = false, init: Checkbox.() -> Unit = {}) =
|
||||
Checkbox(value).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Checkbox>.checkbox(value: ReadOnlyProperty<Boolean>, init: Checkbox.() -> Unit = {}) =
|
||||
Checkbox(value.value).also(this::append).also { it.bind(value) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Checkbox>.checkbox(value: Property<Boolean>, init: Checkbox.() -> Unit = {}) =
|
||||
Checkbox(value.value).also(this::append).also { it.bind(value) }.also(init)
|
|
@ -0,0 +1,105 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
|
||||
class FilterList<T, V : View>(
|
||||
val property: ReadOnlyProperty<T>,
|
||||
val filter: Filter<T, V>
|
||||
) : ViewCollection<V>(createHtmlView()) {
|
||||
|
||||
private val content: MutableMap<T, V> = mutableMapOf()
|
||||
|
||||
fun update() {
|
||||
val list = filter.filter(property.value)
|
||||
var missing = list
|
||||
|
||||
for ((element, view) in content.entries) {
|
||||
if (element in list) {
|
||||
missing -= element
|
||||
} else {
|
||||
if (contains(view)) {
|
||||
remove(view)
|
||||
}
|
||||
if (!filter.useCache) {
|
||||
content -= element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (element in missing) {
|
||||
val view = filter.render(element)
|
||||
append(view)
|
||||
if (property is Property<T>) {
|
||||
view.onClick {
|
||||
property.value = element
|
||||
}
|
||||
}
|
||||
content[element] = view
|
||||
}
|
||||
|
||||
clear()
|
||||
|
||||
for (element in list) {
|
||||
append(content[element]!!)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
update()
|
||||
|
||||
property.onChange {
|
||||
update()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Filter<T, V : View> {
|
||||
fun filter(partial: T): List<T>
|
||||
fun render(element: T): V
|
||||
|
||||
val useCache: Boolean
|
||||
}
|
||||
|
||||
class StringFilter(
|
||||
private val dataSet: List<String>
|
||||
) : Filter<String, TextView> {
|
||||
override fun filter(partial: String): List<String> {
|
||||
val lower = partial.trim().toLowerCase()
|
||||
return dataSet.filter {
|
||||
it.toLowerCase().contains(lower)
|
||||
}.sortedBy { it.length + it.toLowerCase().indexOf(partial) * 2 }
|
||||
}
|
||||
|
||||
override fun render(element: String) = TextView(element)
|
||||
|
||||
override val useCache = true
|
||||
}
|
||||
|
||||
class StaticStringFilter(
|
||||
private val dataSet: List<String>
|
||||
) : Filter<String, TextView> {
|
||||
override fun filter(partial: String) = dataSet
|
||||
|
||||
override fun render(element: String) = TextView(element)
|
||||
|
||||
override val useCache = true
|
||||
}
|
||||
|
||||
|
||||
class DynamicStringFilter(
|
||||
private val filter: (partial: String) -> List<String>
|
||||
) : Filter<String, TextView> {
|
||||
override fun filter(partial: String) = filter.invoke(partial)
|
||||
|
||||
override fun render(element: String) = TextView(element)
|
||||
|
||||
override val useCache = false
|
||||
}
|
||||
|
||||
|
||||
fun <T, V : View> ViewCollection<in FilterList<T, V>>.filterList(property: ReadOnlyProperty<T>, filter: Filter<T, V>, init: FilterList<T, V>.() -> Unit = {}) =
|
||||
FilterList(property, filter).also(this::append).also(init)
|
|
@ -0,0 +1,96 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLHeadingElement
|
||||
|
||||
class Heading(
|
||||
val type: Type,
|
||||
value: String = ""
|
||||
) : View(createHtmlView<HTMLHeadingElement>(type.tagName)) {
|
||||
|
||||
override val html = super.html as HTMLHeadingElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
|
||||
init {
|
||||
text = value
|
||||
}
|
||||
|
||||
enum class Type(val tagName: String) {
|
||||
H1("h1"),
|
||||
H2("h2"),
|
||||
H3("h3"),
|
||||
H4("h4"),
|
||||
H5("h5"),
|
||||
H6("h6")
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h1(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H1, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h1(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H1, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h2(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H2, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h2(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H2, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h3(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H3, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h3(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H3, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h4(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H4, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h4(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H4, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h5(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H5, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h5(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H5, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h6(text: String = "", init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H6, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Heading>.h6(text: ReadOnlyProperty<String>, init: Heading.() -> Unit = {}) =
|
||||
Heading(Heading.Type.H6, text.value).also(this::append).also { it.bind(text) }.also(init)
|
|
@ -0,0 +1,46 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLImageElement
|
||||
|
||||
class ImageView(
|
||||
src: String
|
||||
) : View(createHtmlView<HTMLImageElement>("img")) {
|
||||
|
||||
override val html = super.html as HTMLImageElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
sourceProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
sourceProperty.unbind()
|
||||
}
|
||||
|
||||
var source: String
|
||||
get() = html.src
|
||||
set(value) {
|
||||
html.src = value
|
||||
sourceProperty.invalidate()
|
||||
}
|
||||
|
||||
val sourceProperty: Property<String> = property(this::source)
|
||||
|
||||
|
||||
var alt by AttributeDelegate("alt")
|
||||
|
||||
init {
|
||||
source = src
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in ImageView>.imageView(src: String = "", init: ImageView.() -> Unit = {}) =
|
||||
ImageView(src).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in ImageView>.imageView(src: ReadOnlyProperty<String>, init: ImageView.() -> Unit = {}) =
|
||||
ImageView(src.value).also(this::append).also { it.bind(src) }.also(init)
|
154
src/jsMain/kotlin/de/westermann/kwebview/components/InputView.kt
Normal file
154
src/jsMain/kotlin/de/westermann/kwebview/components/InputView.kt
Normal file
|
@ -0,0 +1,154 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.ValidationProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kobserve.not
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
import org.w3c.dom.events.KeyboardEvent
|
||||
|
||||
class InputView(
|
||||
type: InputType,
|
||||
initValue: String = ""
|
||||
) : ViewForLabel() {
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
valueProperty.bind(property)
|
||||
readonly = true
|
||||
}
|
||||
|
||||
fun bind(property: Property<String>) {
|
||||
valueProperty.bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun bind(property: ValidationProperty<String>) {
|
||||
valueProperty.bindBidirectional(property)
|
||||
invalidProperty.bind(!property.validProperty)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
valueProperty.unbind()
|
||||
if (invalidProperty.isBound) {
|
||||
invalidProperty.unbind()
|
||||
}
|
||||
}
|
||||
|
||||
var value: String
|
||||
get() = html.value
|
||||
set(value) {
|
||||
html.value = value
|
||||
valueProperty.invalidate()
|
||||
}
|
||||
|
||||
val valueProperty: Property<String> = property(this::value)
|
||||
|
||||
var placeholder: String
|
||||
get() = html.placeholder
|
||||
set(value) {
|
||||
html.placeholder = value
|
||||
placeholderProperty.invalidate()
|
||||
}
|
||||
|
||||
val placeholderProperty: Property<String> = property(this::placeholder)
|
||||
|
||||
val invalidProperty by ClassDelegate("invalid")
|
||||
var invalid by invalidProperty
|
||||
|
||||
private var typeInternal by AttributeDelegate("type")
|
||||
var type: InputType?
|
||||
get() = typeInternal?.let(InputType.Companion::find)
|
||||
set(value) {
|
||||
typeInternal = value?.html
|
||||
}
|
||||
private var minInternal by AttributeDelegate("min")
|
||||
var min: Double?
|
||||
get() = minInternal?.toDoubleOrNull()
|
||||
set(value) {
|
||||
minInternal = value?.toString()
|
||||
}
|
||||
private var maxInternal by AttributeDelegate("max")
|
||||
var max: Double?
|
||||
get() = maxInternal?.toDoubleOrNull()
|
||||
set(value) {
|
||||
maxInternal = value?.toString()
|
||||
}
|
||||
private var stepInternal by AttributeDelegate("step")
|
||||
var step: Double?
|
||||
get() = stepInternal?.toDoubleOrNull()
|
||||
set(value) {
|
||||
stepInternal = value?.toString()
|
||||
}
|
||||
|
||||
init {
|
||||
value = initValue
|
||||
this.type = type
|
||||
|
||||
var lastValue = value
|
||||
val changeListener = object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
val value = value
|
||||
if (value != valueProperty.value || value != lastValue) {
|
||||
lastValue = value
|
||||
valueProperty.value = value
|
||||
}
|
||||
|
||||
(event as? KeyboardEvent)?.let { e ->
|
||||
when (e.keyCode) {
|
||||
13, 27 -> blur()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.addEventListener("change", changeListener)
|
||||
html.addEventListener("keyup", changeListener)
|
||||
html.addEventListener("keypress", changeListener)
|
||||
}
|
||||
}
|
||||
|
||||
enum class InputType(val html: String) {
|
||||
TEXT("text"),
|
||||
NUMBER("number"),
|
||||
PASSWORD("password");
|
||||
|
||||
companion object {
|
||||
fun find(html: String): InputType? = values().find { it.html == html }
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: String = "", init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: ReadOnlyProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: Property<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(text: ValidationProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(InputType.TEXT, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: String = "", init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: ReadOnlyProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: Property<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in InputView>.inputView(type: InputType = InputType.TEXT, text: ValidationProperty<String>, init: InputView.() -> Unit = {}) =
|
||||
InputView(type, text.value).also(this::append).also { it.bind(text) }.also(init)
|
51
src/jsMain/kotlin/de/westermann/kwebview/components/Label.kt
Normal file
51
src/jsMain/kotlin/de/westermann/kwebview/components/Label.kt
Normal file
|
@ -0,0 +1,51 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLLabelElement
|
||||
|
||||
/**
|
||||
* Represents a html label element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class Label(
|
||||
inputElement: ViewForLabel,
|
||||
value: String = ""
|
||||
) : View(createHtmlView<HTMLLabelElement>()) {
|
||||
|
||||
override val html = super.html as HTMLLabelElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
|
||||
init {
|
||||
text = value
|
||||
|
||||
inputElement.setLabel(this)
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Label>.label(inputElement: ViewForLabel, text: String = "", init: Label.() -> Unit = {}) =
|
||||
Label(inputElement, text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Label>.label(inputElement: ViewForLabel, text: ReadOnlyProperty<String>, init: Label.() -> Unit = {}) =
|
||||
Label(inputElement, text.value).also(this::append).also { it.bind(text) }.also(init)
|
44
src/jsMain/kotlin/de/westermann/kwebview/components/Link.kt
Normal file
44
src/jsMain/kotlin/de/westermann/kwebview/components/Link.kt
Normal file
|
@ -0,0 +1,44 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLAnchorElement
|
||||
|
||||
/**
|
||||
* Represents a html span element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class Link(target: String) : ViewCollection<View>(createHtmlView<HTMLAnchorElement>("a")) {
|
||||
|
||||
override val html = super.html as HTMLAnchorElement
|
||||
|
||||
var text: String?
|
||||
get() = html.textContent
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
}
|
||||
|
||||
var target: String
|
||||
get() = html.href
|
||||
set(value) {
|
||||
html.href = value
|
||||
}
|
||||
|
||||
init {
|
||||
this.target = target
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Link>.link(target: String, text: String? = null, init: Link.() -> Unit = {}): Link {
|
||||
val view = Link(target)
|
||||
if (text != null) {
|
||||
view.text = text
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLOptionElement
|
||||
|
||||
class OptionView<T>(val value: T) : View(createHtmlView<HTMLOptionElement>()) {
|
||||
|
||||
override val html = super.html as HTMLOptionElement
|
||||
|
||||
var htmlValue: String
|
||||
get() = html.value
|
||||
set(value) {
|
||||
html.value = value
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.text
|
||||
set(value) {
|
||||
html.text = value
|
||||
}
|
||||
|
||||
val index: Int
|
||||
get() = html.index
|
||||
|
||||
var selected: Boolean
|
||||
get() = html.selected
|
||||
set(value) {
|
||||
html.selected = value
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.AttributeDelegate
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
|
||||
class SelectView<T : Any>(
|
||||
dataSet: List<T>,
|
||||
private val initValue: T,
|
||||
val transform: (T) -> String = { it.toString() }
|
||||
) : ViewCollection<OptionView<T>>(createHtmlView<HTMLSelectElement>()) {
|
||||
|
||||
override val html = super.html as HTMLSelectElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<T>) {
|
||||
valueProperty.bind(property)
|
||||
readonly = true
|
||||
}
|
||||
|
||||
fun bind(property: Property<T>) {
|
||||
valueProperty.bindBidirectional(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
valueProperty.unbind()
|
||||
}
|
||||
|
||||
var dataSet: List<T> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
clear()
|
||||
|
||||
value.forEachIndexed { index, v ->
|
||||
+OptionView(v).also { option ->
|
||||
option.text = transform(v)
|
||||
option.htmlValue = index.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var index: Int
|
||||
get() = html.selectedIndex
|
||||
set(value) {
|
||||
val invalidate = html.selectedIndex != value
|
||||
html.selectedIndex = value
|
||||
if (invalidate) {
|
||||
valueProperty.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
var value: T
|
||||
get() = dataSet.getOrNull(index) ?: initValue
|
||||
set(value) {
|
||||
index = dataSet.indexOf(value)
|
||||
}
|
||||
val valueProperty = property(this::value)
|
||||
|
||||
private var readonlyInternal by AttributeDelegate("readonly")
|
||||
var readonly: Boolean
|
||||
get() = readonlyInternal != null
|
||||
set(value) {
|
||||
readonlyInternal = if (value) "readonly" else null
|
||||
}
|
||||
|
||||
var tabindex by AttributeDelegate()
|
||||
fun preventTabStop() {
|
||||
tabindex = "-1"
|
||||
}
|
||||
|
||||
init {
|
||||
this.dataSet = dataSet
|
||||
this.value = initValue
|
||||
|
||||
html.addEventListener("change", object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
valueProperty.invalidate()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun <T : Any> ViewCollection<in SelectView<T>>.selectView(dataSet: List<T>, initValue: T, transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(dataSet, initValue, transform).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun <T : Any> ViewCollection<in SelectView<T>>.selectView(dataSet: List<T>, property: ReadOnlyProperty<T>, transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(dataSet, property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun <T : Any> ViewCollection<in SelectView<T>>.selectView(dataSet: List<T>, property: Property<T>, transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(dataSet, property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
||||
|
||||
|
||||
@KWebViewDsl
|
||||
inline fun <reified T : Enum<T>> ViewCollection<in SelectView<T>>.selectView(initValue: T, noinline transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(enumValues<T>().toList(), initValue, transform).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
inline fun <reified T : Enum<T>> ViewCollection<in SelectView<T>>.selectView(property: ReadOnlyProperty<T>, noinline transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(enumValues<T>().toList(), property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
inline fun <reified T : Enum<T>> ViewCollection<in SelectView<T>>.selectView(property: Property<T>, noinline transform: (T) -> String = { it.toString() }, init: SelectView<T>.() -> Unit = {}) =
|
||||
SelectView(enumValues<T>().toList(), property.value, transform).apply { bind(property) }.also(this::append).also(init)
|
22
src/jsMain/kotlin/de/westermann/kwebview/components/Table.kt
Normal file
22
src/jsMain/kotlin/de/westermann/kwebview/components/Table.kt
Normal file
|
@ -0,0 +1,22 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableElement
|
||||
|
||||
class Table() : ViewCollection<View>(createHtmlView<HTMLTableElement>()) {
|
||||
override val html = super.html as HTMLTableElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in Table>.table(vararg classes: String, init: Table.() -> Unit = {}): Table {
|
||||
val view = Table()
|
||||
for (c in classes) {
|
||||
view.classList += c
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableCaptionElement
|
||||
|
||||
class TableCaption() : ViewCollection<View>(createHtmlView<HTMLTableCaptionElement>("caption")) {
|
||||
override val html = super.html as HTMLTableCaptionElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableCaption>.caption(init: TableCaption.() -> Unit = {}): TableCaption {
|
||||
val view = TableCaption()
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.*
|
||||
import org.w3c.dom.HTMLTableCellElement
|
||||
|
||||
class TableCell(val isHead: Boolean) :
|
||||
ViewCollection<View>(createHtmlView<HTMLTableCellElement>(if (isHead) "th" else "td")) {
|
||||
override val html = super.html as HTMLTableCellElement
|
||||
|
||||
private var colSpanInternal by AttributeDelegate("colspan")
|
||||
var colSpan: Int?
|
||||
get() = colSpanInternal?.toIntOrNull()
|
||||
set(value) {
|
||||
colSpanInternal = value?.toString()
|
||||
}
|
||||
|
||||
private var rowSpanInternal by AttributeDelegate("rowspan")
|
||||
var rowSpan: Int?
|
||||
get() = rowSpanInternal?.toIntOrNull()
|
||||
set(value) {
|
||||
rowSpanInternal = value?.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableCell>.cell(colSpan: Int? = null, init: TableCell.() -> Unit = {}): TableCell {
|
||||
val view = TableCell(false)
|
||||
view.colSpan = colSpan
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableCell>.head(colSpan: Int? = null, init: TableCell.() -> Unit = {}): TableCell {
|
||||
val view = TableCell(true)
|
||||
view.colSpan = colSpan
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableRowElement
|
||||
|
||||
class TableRow() : ViewCollection<TableCell>(createHtmlView<HTMLTableRowElement>("tr")) {
|
||||
override val html = super.html as HTMLTableRowElement
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableRow>.row(vararg classes: String, init: TableRow.() -> Unit = {}): TableRow {
|
||||
val view = TableRow()
|
||||
for (c in classes) {
|
||||
view.classList += c
|
||||
}
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLTableSectionElement
|
||||
|
||||
class TableSection(val type: Type) : ViewCollection<TableRow>(createHtmlView<HTMLTableSectionElement>(type.tagName)) {
|
||||
override val html = super.html as HTMLTableSectionElement
|
||||
|
||||
enum class Type(val tagName: String) {
|
||||
THEAD("thead"),
|
||||
TBODY("tbody"),
|
||||
TFOOT("tfoot")
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableSection>.thead(init: TableSection.() -> Unit = {}): TableSection {
|
||||
val view = TableSection(TableSection.Type.THEAD)
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableSection>.tbody(init: TableSection.() -> Unit = {}): TableSection {
|
||||
val view = TableSection(TableSection.Type.TBODY)
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TableSection>.tfoot(init: TableSection.() -> Unit = {}): TableSection {
|
||||
val view = TableSection(TableSection.Type.TFOOT)
|
||||
append(view)
|
||||
init(view)
|
||||
return view
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package de.westermann.kwebview.components
|
||||
|
||||
import de.westermann.kobserve.Property
|
||||
import de.westermann.kobserve.ReadOnlyProperty
|
||||
import de.westermann.kobserve.basic.property
|
||||
import de.westermann.kwebview.KWebViewDsl
|
||||
import de.westermann.kwebview.View
|
||||
import de.westermann.kwebview.ViewCollection
|
||||
import de.westermann.kwebview.createHtmlView
|
||||
import org.w3c.dom.HTMLSpanElement
|
||||
|
||||
/**
|
||||
* Represents a html span element.
|
||||
*
|
||||
* @author lars
|
||||
*/
|
||||
class TextView(
|
||||
value: String = ""
|
||||
) : View(createHtmlView<HTMLSpanElement>()) {
|
||||
|
||||
override val html = super.html as HTMLSpanElement
|
||||
|
||||
fun bind(property: ReadOnlyProperty<String>) {
|
||||
textProperty.bind(property)
|
||||
}
|
||||
|
||||
fun unbind() {
|
||||
textProperty.unbind()
|
||||
}
|
||||
|
||||
var text: String
|
||||
get() = html.textContent ?: ""
|
||||
set(value) {
|
||||
html.textContent = value
|
||||
textProperty.invalidate()
|
||||
}
|
||||
|
||||
val textProperty: Property<String> = property(this::text)
|
||||
|
||||
init {
|
||||
text = value
|
||||
}
|
||||
}
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TextView>.textView(text: String = "", init: TextView.() -> Unit = {}) =
|
||||
TextView(text).also(this::append).also(init)
|
||||
|
||||
@KWebViewDsl
|
||||
fun ViewCollection<in TextView>.textView(text: ReadOnlyProperty<String>, init: TextView.() -> Unit = {}) =
|
||||
TextView(text.value).also(this::append).also { it.bind(text) }.also(init)
|
79
src/jsMain/kotlin/de/westermann/kwebview/extensions.kt
Normal file
79
src/jsMain/kotlin/de/westermann/kwebview/extensions.kt
Normal file
|
@ -0,0 +1,79 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import de.westermann.kobserve.EventHandler
|
||||
import org.w3c.dom.DOMRect
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import kotlin.browser.document
|
||||
import kotlin.browser.window
|
||||
|
||||
inline fun <reified V : HTMLElement> createHtmlView(tag: String? = null): V {
|
||||
var tagName: String
|
||||
if (tag != null) {
|
||||
tagName = tag
|
||||
} else {
|
||||
tagName = V::class.js.name.toLowerCase().replace("html([a-z]*)element".toRegex(), "$1")
|
||||
if (tagName.isBlank()) {
|
||||
tagName = "div"
|
||||
}
|
||||
}
|
||||
return document.createElement(tagName) as V
|
||||
}
|
||||
|
||||
fun String.toDashCase() = replace("([a-z])([A-Z])".toRegex(), "$1-$2").toLowerCase()
|
||||
|
||||
inline fun <reified T> EventHandler<T>.bind(element: HTMLElement, event: String) {
|
||||
val listener = object : EventListener {
|
||||
override fun handleEvent(event: Event) {
|
||||
this@bind.emit(event as T)
|
||||
}
|
||||
}
|
||||
var isAttached = false
|
||||
|
||||
val updateState = {
|
||||
if (isEmpty() && isAttached) {
|
||||
element.removeEventListener(event, listener)
|
||||
isAttached = false
|
||||
} else if (!isEmpty() && !isAttached) {
|
||||
element.addEventListener(event, listener)
|
||||
isAttached = true
|
||||
}
|
||||
}
|
||||
|
||||
onAttach = updateState
|
||||
onDetach = updateState
|
||||
updateState()
|
||||
}
|
||||
|
||||
fun MouseEvent.toPoint(): Point = Point(clientX, clientY)
|
||||
fun DOMRect.toDimension(): Dimension = Dimension(x, y, width, height)
|
||||
|
||||
fun Number.format(digits: Int): String = this.asDynamic().toFixed(digits)
|
||||
|
||||
external fun delete(p: dynamic): Boolean = definedExternally
|
||||
|
||||
fun delete(thing: dynamic, key: String) {
|
||||
delete(thing[key])
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply current dom changes and recalculate all sizes. Executes the given block afterwards.
|
||||
*
|
||||
* @param timeout Optionally set a timeout for this call. Defaults to 1.
|
||||
* @param block Callback
|
||||
*/
|
||||
fun async(timeout: Int = 1, block: () -> Unit) {
|
||||
if (timeout < 1) throw IllegalArgumentException("Timeout must be greater than 0!")
|
||||
window.setTimeout(block, timeout)
|
||||
}
|
||||
|
||||
fun interval(timeout: Int, block: () -> Unit): Int {
|
||||
if (timeout < 1) throw IllegalArgumentException("Timeout must be greater than 0!")
|
||||
return window.setInterval(block, timeout)
|
||||
}
|
||||
|
||||
fun clearInterval(id: Int) {
|
||||
window.clearInterval(id)
|
||||
}
|
147
src/jsMain/kotlin/de/westermann/kwebview/i18n.kt
Normal file
147
src/jsMain/kotlin/de/westermann/kwebview/i18n.kt
Normal file
|
@ -0,0 +1,147 @@
|
|||
package de.westermann.kwebview
|
||||
|
||||
import kotlin.browser.window
|
||||
|
||||
@Suppress("ClassName")
|
||||
object i18n {
|
||||
private val data: MutableMap<String, Locale> = mutableMapOf()
|
||||
|
||||
private var fallbackLocale: Locale? = null
|
||||
private var locale: Locale? = null
|
||||
|
||||
fun register(id: String, name: String, path: String, fallback: Boolean = false) {
|
||||
val locale = Locale(id, name, path, fallback)
|
||||
|
||||
if (fallback) {
|
||||
if (fallbackLocale != null) {
|
||||
throw IllegalStateException("Fallback locale is already set!")
|
||||
}
|
||||
|
||||
fallbackLocale = locale
|
||||
}
|
||||
|
||||
data[id] = locale
|
||||
|
||||
window.fetch(path).then {
|
||||
it.json()
|
||||
}.then {
|
||||
locale.json = it
|
||||
locale.isLoaded = true
|
||||
}.catch {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
|
||||
val isReady: Boolean
|
||||
get() = data.values.all { it.isLoaded }
|
||||
|
||||
fun load(id: String, block: () -> Unit) {
|
||||
fun ready() {
|
||||
if (isReady) {
|
||||
locale = data[id]
|
||||
block()
|
||||
} else {
|
||||
async(50) { ready() }
|
||||
}
|
||||
}
|
||||
ready()
|
||||
}
|
||||
|
||||
private fun findKey(locale: Locale, key: String): dynamic {
|
||||
val keys = key.split(".")
|
||||
|
||||
var result = locale.json
|
||||
for (k in keys) {
|
||||
if (result.hasOwnProperty(k) as Boolean) {
|
||||
result = result[k]
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun findKey(key: String): dynamic {
|
||||
var result: dynamic = undefined
|
||||
|
||||
if (locale != null) {
|
||||
result = findKey(locale!!, key)
|
||||
}
|
||||
|
||||
if (result == undefined) {
|
||||
if (fallbackLocale != null) {
|
||||
result = findKey(fallbackLocale!!, key)
|
||||
}
|
||||
}
|
||||
|
||||
if (result == undefined) {
|
||||
throw InternationalizationError("Cannot find key '$key'!")
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private fun replace(str: String, arguments: List<Pair<String?, Any?>>): String {
|
||||
val unnamed = arguments.filter { it.first == null }.map { it.second }
|
||||
val named = arguments.mapNotNull { it.first?.to(it.second) }
|
||||
|
||||
var s = str
|
||||
|
||||
for ((key, replacement) in named) {
|
||||
s = s.replace("{$key}", replacement?.toString() ?: "null")
|
||||
}
|
||||
|
||||
for (replacement in unnamed) {
|
||||
if (s.contains("{}")) {
|
||||
s = s.replaceFirst("{}", replacement?.toString() ?: "null")
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
fun t(key: String, arguments: List<Pair<String?, Any?>>): String {
|
||||
return replace(findKey(key).toString(), arguments)
|
||||
}
|
||||
|
||||
fun t(count: Number, key: String, arguments: List<Pair<String?, Any?>>): String {
|
||||
val json = findKey(key)
|
||||
if (count == 0 && json.hasOwnProperty("zero") as Boolean) {
|
||||
return replace(json.zero.toString(), arguments)
|
||||
} else if (count == 1 && json.hasOwnProperty("one") as Boolean) {
|
||||
return replace(json.one.toString(), arguments)
|
||||
}
|
||||
|
||||
return if (json.hasOwnProperty("many") as Boolean)
|
||||
replace(json.many.toString(), arguments)
|
||||
else {
|
||||
replace(json.toString(), arguments)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class Locale(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val path: String,
|
||||
val fallback: Boolean
|
||||
) {
|
||||
var isLoaded = false
|
||||
var json = js("{}")
|
||||
}
|
||||
}
|
||||
|
||||
class InternationalizationError(message: String? = null) : Error(message)
|
||||
|
||||
fun t(key: String) = i18n.t(key, emptyList())
|
||||
|
||||
fun t(key: String, vararg arguments: Any?) = i18n.t(key, arguments.map { null to it })
|
||||
|
||||
fun t(key: String, vararg arguments: Pair<String?, Any?>) = i18n.t(key, arguments.asList())
|
||||
|
||||
fun t(count: Number, key: String) = i18n.t(count, key, emptyList())
|
||||
|
||||
fun t(count: Number, key: String, vararg arguments: Any?) = i18n.t(count, key, arguments.map { null to it })
|
||||
|
||||
fun t(count: Number, key: String, vararg arguments: Pair<String?, Any?>) = i18n.t(count, key, arguments.asList())
|
2
src/jsMain/resources/require.min.js
vendored
Normal file
2
src/jsMain/resources/require.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
5
src/jsMain/resources/style/style.scss
Normal file
5
src/jsMain/resources/style/style.scss
Normal file
|
@ -0,0 +1,5 @@
|
|||
$primary-background-color: yellow;
|
||||
|
||||
body {
|
||||
background: $primary-background-color;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue