diff options
author | Robert Jaros <rjaros@finn.pl> | 2019-10-03 19:03:21 +0200 |
---|---|---|
committer | Robert Jaros <rjaros@finn.pl> | 2019-10-03 19:03:21 +0200 |
commit | 6b53324c97bfc80ed14dfca6a5dbc879950715b9 (patch) | |
tree | 55c7bb7d06e37470795a93e1f542e51ef3f1ace6 /src/main/kotlin/pl/treksoft/kvision/panel | |
parent | 22a8d5c35db97d65a90b21d97e6835380191845d (diff) | |
download | kvision-6b53324c97bfc80ed14dfca6a5dbc879950715b9.tar.gz kvision-6b53324c97bfc80ed14dfca6a5dbc879950715b9.tar.bz2 kvision-6b53324c97bfc80ed14dfca6a5dbc879950715b9.zip |
Upgrade to Bootstrap 4.
Upgrade to Font Awesome 5.
Restructure modules.
Diffstat (limited to 'src/main/kotlin/pl/treksoft/kvision/panel')
4 files changed, 19 insertions, 466 deletions
diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt deleted file mode 100644 index 7a5b07d6..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.panel - -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.WidgetWrapper -import pl.treksoft.kvision.html.Align -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -/** - * Bootstrap grid sizes. - */ -enum class GridSize(internal val size: String) { - XS("xs"), - SM("sm"), - MD("md"), - LG("lg") -} - -internal const val MAX_COLUMNS = 12 - -internal data class WidgetParam(val widget: Component, val size: Int, val offset: Int) - -/** - * The container with support for Bootstrap responsive grid layout. - * - * @constructor - * @param gridSize grid size - * @param rows number of rows - * @param cols number of columns - * @param align text align of grid cells - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class ResponsiveGridPanel( - private val gridSize: GridSize = GridSize.MD, - private var rows: Int = 0, private var cols: Int = 0, align: Align? = null, - classes: Set<String> = setOf(), init: (ResponsiveGridPanel.() -> Unit)? = null -) : SimplePanel(classes + "container-fluid") { - - /** - * Text align of grid cells. - */ - var align by refreshOnUpdate(align) { refreshRowContainers() } - - internal val map = mutableMapOf<Int, MutableMap<Int, WidgetParam>>() - private var auto: Boolean = true - - init { - @Suppress("LeakingThis") - init?.invoke(this) - } - - /** - * Adds child component to the grid. - * @param child child component - * @param col column number - * @param row row number - * @param size cell size (colspan) - * @param offset cell offset - * @return this container - */ - open fun add(child: Component, col: Int, row: Int, size: Int = 0, offset: Int = 0): ResponsiveGridPanel { - val cRow = maxOf(row, 0) - val cCol = maxOf(col, 0) - if (row > rows - 1) rows = cRow + 1 - if (col > cols - 1) cols = cCol + 1 - map.getOrPut(cRow) { mutableMapOf() }[cCol] = WidgetParam(child, size, offset) - if (size > 0 || offset > 0) auto = false - refreshRowContainers() - return this - } - - override fun add(child: Component): ResponsiveGridPanel { - return this.add(child, this.cols, 0) - } - - override fun addAll(children: List<Component>): ResponsiveGridPanel { - children.forEach { this.add(it) } - return this - } - - @Suppress("NestedBlockDepth") - override fun remove(child: Component): ResponsiveGridPanel { - map.values.forEach { row -> - row.filterValues { it.widget == child } - .forEach { (i, _) -> row.remove(i) } - } - refreshRowContainers() - return this - } - - /** - * Removes child component at given location (column, row). - * @param col column number - * @param row row number - * @return this container - */ - open fun removeAt(col: Int, row: Int): ResponsiveGridPanel { - map[row]?.remove(col) - refreshRowContainers() - return this - } - - @Suppress("ComplexMethod", "NestedBlockDepth") - private fun refreshRowContainers() { - singleRender { - clearRowContainers() - val num = MAX_COLUMNS / cols - for (i in 0 until rows) { - val rowContainer = SimplePanel(setOf("row")) - val row = map[i] - if (row != null) { - (0 until cols).map { row[it] }.forEach { wp -> - if (auto) { - val widget = wp?.widget?.let { - WidgetWrapper(it, setOf("col-" + gridSize.size + "-" + num)) - } ?: Tag(TAG.DIV, classes = setOf("col-" + gridSize.size + "-" + num)) - align?.let { - widget.addCssClass(it.className) - } - rowContainer.add(widget) - } else { - if (wp != null) { - val s = if (wp.size > 0) wp.size else num - val widget = WidgetWrapper(wp.widget, setOf("col-" + gridSize.size + "-" + s)) - if (wp.offset > 0) { - widget.addCssClass("col-" + gridSize.size + "-offset-" + wp.offset) - } - align?.let { - widget.addCssClass(it.className) - } - rowContainer.add(widget) - } - } - } - } - addInternal(rowContainer) - } - } - } - - private fun clearRowContainers() { - children.forEach { it.dispose() } - removeAll() - } - - companion object { - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.responsiveGridPanel( - gridSize: GridSize = GridSize.MD, - rows: Int = 0, cols: Int = 0, align: Align? = null, - classes: Set<String> = setOf(), init: (ResponsiveGridPanel.() -> Unit)? = null - ): ResponsiveGridPanel { - val responsiveGridPanel = ResponsiveGridPanel(gridSize, rows, cols, align, classes, init) - this.add(responsiveGridPanel) - return responsiveGridPanel - } - } -} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt index 2d9dcc46..e0c70ac4 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/Root.kt @@ -27,8 +27,7 @@ import org.w3c.dom.HTMLElement import pl.treksoft.kvision.KVManager import pl.treksoft.kvision.core.StringBoolPair import pl.treksoft.kvision.core.Style -import pl.treksoft.kvision.dropdown.ContextMenu -import pl.treksoft.kvision.modal.Modal +import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.utils.snClasses import pl.treksoft.kvision.utils.snOpt @@ -52,7 +51,7 @@ class Root( private val fixed: Boolean = false, init: (Root.() -> Unit)? = null ) : SimplePanel() { - private val contextMenus: MutableList<ContextMenu> = mutableListOf() + private val contextMenus: MutableList<Widget> = mutableListOf() private var rootVnode: VNode = renderVNode() internal var renderDisabled = false @@ -71,7 +70,7 @@ class Root( } roots.add(this) if (isFirstRoot) { - Modal.modals.forEach { it.parent = this } + modals.forEach { it.parent = this } } @Suppress("LeakingThis") init?.invoke(this) @@ -87,7 +86,7 @@ class Root( } } - internal fun addContextMenu(contextMenu: ContextMenu) { + fun addContextMenu(contextMenu: Widget) { contextMenus.add(contextMenu) contextMenu.parent = this this.setInternalEventListener<Root> { @@ -114,7 +113,7 @@ class Root( private fun modalsVNodes(): Array<VNode> { return if (isFirstRoot) { - Modal.modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray() + modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray() } else { arrayOf() } @@ -150,12 +149,13 @@ class Root( roots.remove(this) if (isFirstRoot) { Style.styles.clear() - Modal.modals.clear() + modals.clear() } } companion object { internal var counter = 0 + private val modals: MutableList<Widget> = mutableListOf() /** * @suppress internal function @@ -167,18 +167,26 @@ class Root( internal val roots: MutableList<Root> = mutableListOf() - internal fun getFirstRoot(): Root? { + fun getFirstRoot(): Root? { return if (roots.isNotEmpty()) roots[0] else null } - internal fun getLastRoot(): Root? { + fun getLastRoot(): Root? { return if (roots.isNotEmpty()) roots[roots.size - 1] else null } + + fun addModal(modal: Widget) { + modals.add(modal) + } + + fun removeModal(modal: Widget) { + modals.remove(modal) + } } } diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt index 2f702fad..91eeda80 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/SimplePanel.kt @@ -35,7 +35,7 @@ import pl.treksoft.kvision.core.Widget */ open class SimplePanel(classes: Set<String> = setOf(), init: (SimplePanel.() -> Unit)? = null) : Widget(classes), Container { - internal val children: MutableList<Component> = mutableListOf() + protected val children: MutableList<Component> = mutableListOf() init { @Suppress("LeakingThis") @@ -94,7 +94,7 @@ open class SimplePanel(classes: Set<String> = setOf(), init: (SimplePanel.() -> } override fun getChildren(): List<Component> { - return ArrayList(children) + return children } override fun dispose() { diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt deleted file mode 100644 index f620f2b0..00000000 --- a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2017-present Robert Jaros - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package pl.treksoft.kvision.panel - -import pl.treksoft.kvision.core.Component -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.ResString -import pl.treksoft.kvision.core.WidgetWrapper -import pl.treksoft.kvision.html.Icon -import pl.treksoft.kvision.html.Link.Companion.link -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag -import pl.treksoft.kvision.routing.routing -import pl.treksoft.kvision.utils.obj -import pl.treksoft.kvision.html.Icon.Companion.icon as cicon - -/** - * Tab position. - */ -enum class TabPosition { - TOP, - LEFT, - RIGHT -} - -/** - * Left or right tab size. - */ -enum class SideTabSize { - SIZE_1, - SIZE_2, - SIZE_3, - SIZE_4, - SIZE_5, - SIZE_6 -} - -/** - * The container rendering it's children as tabs. - * - * It supports activating children by a JavaScript route. - * - * @constructor - * @param tabPosition tab position - * @param sideTabSize side tab size - * @param scrollableTabs determines if tabs are scrollable (default: false) - * @param classes a set of CSS class names - * @param init an initializer extension function - */ -open class TabPanel( - private val tabPosition: TabPosition = TabPosition.TOP, - private val sideTabSize: SideTabSize = SideTabSize.SIZE_3, - scrollableTabs: Boolean = false, - classes: Set<String> = setOf(), - init: (TabPanel.() -> Unit)? = null -) : SimplePanel(classes) { - - /** - * The index of active (visible) tab. - */ - var activeIndex - get() = content.activeIndex - set(value) { - if (content.activeIndex != value) { - content.activeIndex = value - nav.children.forEach { - it.removeCssClass("active") - } - if (content.activeIndex in nav.children.indices) { - nav.children[content.activeIndex].addCssClass("active") - } - } - } - private val navClasses = when (tabPosition) { - TabPosition.TOP -> if (scrollableTabs) setOf("nav", "nav-tabs", "tabs-top") else setOf("nav", "nav-tabs") - TabPosition.LEFT -> setOf("nav", "nav-tabs", "tabs-left") - TabPosition.RIGHT -> setOf("nav", "nav-tabs", "tabs-right") - } - private var nav = Tag(TAG.UL, classes = navClasses) - private var content = StackPanel(false) - - internal val childrenMap = mutableMapOf<Int, Component>() - - init { - when (tabPosition) { - TabPosition.TOP -> { - this.addInternal(nav) - this.addInternal(content) - } - TabPosition.LEFT -> { - this.addCssClass("clearfix") - val sizes = calculateSideClasses() - this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "col-nopadding"))) - this.addInternal(WidgetWrapper(content, setOf(sizes.second, "col-nopadding"))) - } - TabPosition.RIGHT -> { - this.addCssClass("clearfix") - val sizes = calculateSideClasses() - this.addInternal(WidgetWrapper(content, setOf(sizes.second, "col-nopadding"))) - this.addInternal(WidgetWrapper(nav, setOf(sizes.first, "col-nopadding"))) - } - } - @Suppress("LeakingThis") - init?.invoke(this) - } - - private fun calculateSideClasses(): Pair<String, String> { - return when (sideTabSize) { - SideTabSize.SIZE_1 -> Pair("col-xs-1", "col-xs-11") - SideTabSize.SIZE_2 -> Pair("col-xs-2", "col-xs-10") - SideTabSize.SIZE_3 -> Pair("col-xs-3", "col-xs-9") - SideTabSize.SIZE_4 -> Pair("col-xs-4", "col-xs-8") - SideTabSize.SIZE_5 -> Pair("col-xs-5", "col-xs-7") - SideTabSize.SIZE_6 -> Pair("col-xs-6", "col-xs-6") - } - } - - /** - * Adds new tab and optionally bounds it's activation to a given route. - * @param title title of the tab - * @param panel child component - * @param icon icon of the tab - * @param image image of the tab - * @param closable determines if this tab is closable - * @param route JavaScript route to activate given child - * @return current container - */ - open fun addTab( - title: String, panel: Component, icon: String? = null, - image: ResString? = null, closable: Boolean = false, route: String? = null - ): TabPanel { - val currentIndex = counter++ - childrenMap[currentIndex] = panel - val tag = Tag(TAG.LI) { - role = "presentation" - link(title, "#", icon, image) { - if (closable) { - cicon("remove") { - addCssClass("kv-tab-close") - setEventListener<Icon> { - click = { e -> - val actIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) - e.asDynamic().data = actIndex - @Suppress("UnsafeCastFromDynamic") - if (this@TabPanel.dispatchEvent( - "tabClosing", - obj { detail = e; cancelable = true }) != false - ) { - this@TabPanel.removeTab(actIndex) - this@TabPanel.dispatchEvent("tabClosed", obj { detail = e }) - } - e.stopPropagation() - } - } - } - } - } - setEventListener { - click = { e -> - activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) - e.preventDefault() - if (route != null) { - routing.navigate(route) - } - } - } - } - nav.add(tag) - if (nav.children.size == 1) { - tag.addCssClass("active") - activeIndex = 0 - } - content.add(panel) - if (route != null) { - routing.on(route, { _ -> activeIndex = this@TabPanel.content.children.indexOf(childrenMap[currentIndex]) }) - .resolve() - } - return this - } - - /** - * Removes tab at given index. - */ - open fun removeTab(index: Int): TabPanel { - nav.remove(nav.children[index]) - childrenMap.filter { it.value == content.children[index] }.keys.firstOrNull()?.let { - childrenMap.remove(it) - } - content.remove(content.children[index]) - activeIndex = content.activeIndex - return this - } - - override fun add(child: Component): TabPanel { - return addTab("", child) - } - - override fun addAll(children: List<Component>): TabPanel { - children.forEach { add(it) } - return this - } - - override fun remove(child: Component): TabPanel { - val index = content.children.indexOf(child) - return removeTab(index) - } - - /** - * Returns child component by tab index. - * @param index tab index - */ - open fun getChildComponent(index: Int): Component? { - return content.children[index] - } - - /** - * Returns tab header component by tab index. - * @param index tab index - */ - open fun getNavComponent(index: Int): Tag? { - return nav.children[index] as? Tag - } - - override fun removeAll(): TabPanel { - content.removeAll() - nav.removeAll() - childrenMap.clear() - refresh() - return this - } - - companion object { - internal var counter = 0 - /** - * DSL builder extension function. - * - * It takes the same parameters as the constructor of the built component. - */ - fun Container.tabPanel( - tabPosition: TabPosition = TabPosition.TOP, - sideTabSize: SideTabSize = SideTabSize.SIZE_3, - scrollableTabs: Boolean = false, - classes: Set<String> = setOf(), - init: (TabPanel.() -> Unit)? = null - ): TabPanel { - val tabPanel = TabPanel(tabPosition, sideTabSize, scrollableTabs, classes, init) - this.add(tabPanel) - return tabPanel - } - } -} |