Setup project structure

This commit is contained in:
Lars Westermann 2019-02-05 22:18:15 +01:00
parent 166bcc3fc2
commit 35a6544e65
Signed by: lars.westermann
GPG key ID: 9D417FA5BB9D5E1D
44 changed files with 2439 additions and 1 deletions

View 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")
}

View file

@ -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())
}
}
}

View 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
}
}
}

View 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>?
)
}

View 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
}
}
}
}

View 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)
}
}

View file

@ -0,0 +1,4 @@
package de.westermann.kwebview
@DslMarker
annotation class KWebViewDsl

View 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()
}

View 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")
}
}

View 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)
}
}

View 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)
}
}
}

View 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)
}
}
}
}

View 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.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
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View 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)

View 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)

View 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
}

View file

@ -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
}
}

View file

@ -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)

View 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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View 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.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)

View 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)
}

View 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

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,5 @@
$primary-background-color: yellow;
body {
background: $primary-background-color;
}