Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ trait BoxStyle {
val headerTitle: StyleA
val linkHeaderFooter: StyleA
val fullHeightMax: StyleA
val fullHeightMaxForm: StyleA
val tableWrapper: StyleA
val fullHeight: StyleA
val loading: StyleA
Expand Down
16 changes: 14 additions & 2 deletions client/src/main/scala/ch/wsl/box/client/styles/GlobalStyles.scala
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,16 @@ class GlobalStyles(settings:Settings,conf:StyleConf) extends StyleSheet.Inline()


override val fullHeightMax = style(
height :=! "calc(100vh - 150px)",
height :=! "calc(100vh - 201px)",
media.maxWidth(600 px)(
height :=! "calc(100vh - 110px)",
//paddingBottom(70 px)
),
overflow.auto
)

override val fullHeightMaxForm = style(
height :=! "calc(100vh - 229px)",
media.maxWidth(600 px)(
height :=! "calc(100vh - 110px)",
//paddingBottom(70 px)
Expand Down Expand Up @@ -1447,7 +1456,7 @@ class GlobalStyles(settings:Settings,conf:StyleConf) extends StyleSheet.Inline()


override val mapTable = style(
height :=! "calc(100vh - 105px)",
height :=! "calc(100vh - 201px)",
media.maxWidth(600 px)(
height :=! "calc(100vh - 50px)",
),
Expand Down Expand Up @@ -1513,6 +1522,9 @@ class GlobalStyles(settings:Settings,conf:StyleConf) extends StyleSheet.Inline()
unsafeChild("svg")(
marginTop(-4 px),
svgStroke(c"#fff")
),
&.focus(
borderColor(conf.colors.main).important
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ case class EntityFormView(model:ModelProperty[EntityFormModel], presenter:Entity
if(showHeader && _form.isDefined) {
formHeader(nested,showId,_form.get).render
},
div(BootstrapCol.md(12),if(showHeader) { ClientConf.style.fullHeightMax },
div(BootstrapCol.md(12),if(showHeader) { ClientConf.style.fullHeightMaxForm },

_form match {
case None => div()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@ case class EntityTableView(model:ModelProperty[EntityTableModel], presenter:Enti

sc.set(sc.get.flatMap{ f =>
if(f.title == oldPosition) Seq()
else if(f.title == newPosition) metadata.table.find(_.title == oldPosition) ++ Seq(f)
else if(f.title == newPosition) metadata.fields.find(_.title == oldPosition) ++ Seq(f)
else Seq(f)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ class ExportTableDialog extends Logging {

Seq(
button(`type` := "button", onclick :+= ((e:Event) => open(modal,onOpen())), ClientConf.style.boxButton, "Export"),

)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ch.wsl.box.client.views.components.widget
import ch.wsl.box.client.services.ClientConf
import ch.wsl.box.client.utils.ListenerManager
import io.circe._
import io.circe.syntax._
import io.udash._
Expand All @@ -12,12 +13,19 @@ import io.udash.bindings.modifiers.Binding
import io.udash.bootstrap.tooltip.UdashTooltip
import scalacss.ScalatagsCss._
import io.udash.css._
import org.scalajs.dom.Event
import org.scalajs.dom.{Event, KeyboardEvent}

case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget with IsCheckBoxWithData {

val noLabel = field.params.exists(_.js("nolabel") == true.asJson)

val lm = new ListenerManager()
import lm._

override def killWidget(): Unit = {
lm.clearAll()
}

def jsToBool(json:Json):Option[Boolean] = field.`type` match {
case JSONFieldTypes.BOOLEAN => json.asBoolean
case JSONFieldTypes.NUMBER => json.asNumber.flatMap(_.toInt).map(_ == 1)
Expand All @@ -34,12 +42,25 @@ case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget
case _ => Json.Null
}



def tristateCheckbox(booleanModel:Property[Option[Boolean]]) = {

def rotate() = {
booleanModel.set{
booleanModel.get match {
case Some(true) => Some(false)
case Some(false) => None
case None =>Some(true)
}}
}

// https://carsonf92.medium.com/introducing-the-three-state-checkbox-1b6f00b6ec89
val positive = raw("<svg id=\"i-checkmark\" viewBox=\"0 0 32 32\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentcolor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"20%\"><path d=\"M2 20 L12 28 30 4\" /></svg>")
val negative = raw("<svg id=\"i-close\" viewBox=\"0 0 32 32\" width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentcolor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"20%\"><path d=\"M2 30 L30 2 M30 30 L2 2\" /></svg>")

val checkbox = span(
tabindex := 0,
`class`.bind(booleanModel.transform {
case Some(true) => Seq(ClientConf.style.tristateCheckBox.htmlClass,ClientConf.style.tristatePositive.htmlClass).mkString(" ")
case Some(false) => Seq(ClientConf.style.tristateCheckBox.htmlClass,ClientConf.style.tristateNegative.htmlClass).mkString(" ")
Expand All @@ -51,15 +72,21 @@ case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget
case None => span().render
},
onclick :+= {(e:Event) =>
booleanModel.set{
booleanModel.get match {
case Some(true) => Some(false)
case Some(false) => None
case None =>Some(true)
}}

e.preventDefault()
}
)
).render

checkbox.listen("click", _ => rotate())
checkbox.listen("keydown",{
case ke: KeyboardEvent if ke.key == " " => {
rotate()
ke.stopPropagation()
ke.preventDefault()
}
case _ => true
})

checkbox
}

Expand All @@ -75,15 +102,15 @@ case class TristateWidget(field:JSONField, data: Property[Json]) extends Widget

div(
div(ClientConf.style.label50,if(!noLabel) { WidgetUtils.toLabel(field,WidgetUtils.LabelRight) } else frag()),
tooltip(tristateCheckbox(booleanModel).render)._1
tooltip(tristateCheckbox(booleanModel))._1
)
}

override def editOnTable(nested:Binding.NestedInterceptor): JsDom.all.Modifier = {
val booleanModel:Property[Option[Boolean]] = Property(None)
autoRelease(data.sync[Option[Boolean]](booleanModel)(js => jsToBool(js),bool => boolToJson(bool)))

tristateCheckbox(booleanModel).render
tristateCheckbox(booleanModel)
}

override def json(): _root_.io.udash.ReadableProperty[Json] = data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ch.wsl.box.client.views.components.widget.array.{ChoicesWidget, MultiWidg
import ch.wsl.box.client.views.components.widget.boolean.SelectBooleanWidget
import ch.wsl.box.client.views.components.widget.child.{EditableTable, LookupFormWidget, SimpleChildFactory, TableChildFactory, TrasparentChild}
import ch.wsl.box.client.views.components.widget.geo.{MapChild, MapPointWidget, OlMapListWidget, OlMapWidget}
import ch.wsl.box.client.views.components.widget.child.{EditableTable, LookupFormWidget, SimpleChildFactory, Spreadsheet, TableChildFactory, TrasparentChild}
import ch.wsl.box.client.views.components.widget.child.{EditableTable, LookupFormWidget, SimpleChildFactory, Spreadsheet, TableChildFactory, TrasparentChild,ExportWidget}
import ch.wsl.box.client.views.components.widget.geo.{MapPointWidget, OlMapListWidget, OlMapWidget}
import ch.wsl.box.client.views.components.widget.labels.{HtmlWidget, LinkedFormWidget, LookupLabelWidget, StaticTextWidget, TitleWidget}
import ch.wsl.box.client.views.components.widget.lookup.{MultipleLookupWidget, PopupSelectWidget, SelectWidgetFactory}
Expand Down Expand Up @@ -56,6 +56,7 @@ object WidgetRegistry extends Logging {
LookupFormWidget,
LinkedFormWidget,
Spreadsheet,
ExportWidget,

FileSimpleWidgetFactory,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ case class TableStyle(conf:StyleConf,columns:Int) extends StyleSheet.Inline {
object EditableTable extends ChildRendererFactory {



override def name: String = WidgetsNames.editableTable


Expand All @@ -133,6 +134,9 @@ object EditableTable extends ChildRendererFactory {

case class EditableTableRenderer(widgetParam:WidgetParams) extends ChildRenderer {


val tableId = s"table-${UUID.randomUUID()}"

val parentMetadata = widgetParam.metadata

import ch.wsl.box.client.Context._
Expand Down Expand Up @@ -412,26 +416,52 @@ object EditableTable extends ChildRendererFactory {
case _ => None
}

def setFocus(row:Int,col:Int):Boolean = Seq("select", "input").map { tagname =>
document
.querySelector(s" #$tableId tr[data-row='$row'] > td[data-column='$col']")
.getElementsByTagName(tagname).headOption match {
case Some(e: dom.HTMLElement) => {
e.focus()
true
}
case _ => false
}
}.exists(x => x)

def select(offsetRow:Int,offsetCol:Int) = {
for{
c <- column
r <- row
} yield {
Seq("select","input").foreach { tagname =>
document
.querySelector(s"tr[data-row='${r + offsetRow}'] > td[data-column='${c + offsetCol}']")
.getElementsByTagName(tagname).headOption.foreach { case e: dom.HTMLElement => e.focus() }
if(r + offsetRow >= entity.length) {
addItem(child,metadata.get)
val observer = Some(new MutationObserver({ (mutations, observer) => {
if(setFocus(r + offsetRow,c + offsetCol))
observer.disconnect()
} }))
observer.foreach(_.observe(document,MutationObserverInit(childList = true, subtree = true)))
} else {
setFocus(r + offsetRow,c + offsetCol)
}


}
e.stopImmediatePropagation()
e.preventDefault()
false
}

e match {
case ke:KeyboardEvent if ke.key == "Enter" || ke.key == "ArrowDown" => select(1,0)
case ke:KeyboardEvent if (ke.key == "Enter" && !column.exists(c => fields(metadata.get).lift(c).exists(_.lookup.isDefined))) || ke.key == "ArrowDown" => select(1,0)
case ke:KeyboardEvent if ke.key == "ArrowUp" => select(-1,0)
case ke:KeyboardEvent if ke.key == "Tab" => {
val lastRow = row.exists(r => r+1 == entity.length)
val lastCol = column.exists(c => c+1 == fields(metadata.get).length)
if(lastRow && lastCol) {
select(1,-column.getOrElse(0))
} else true

}
case _ => true
}
}
Expand All @@ -451,7 +481,7 @@ object EditableTable extends ChildRendererFactory {
val additionalColumns = if (write && !disableRemove) 1 else 0
val colWidth = (width := _colWidth(additionalColumns))

val tab = table(tableStyle.table,
val tab = table(id := tableId, tableStyle.table,
thead(
for (field <- f) yield {
val name = colHeader(field)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ch.wsl.box.client.views.components.widget.child

import ch.wsl.box.client.views.components.table.{ExportParams, ExportTableDialog}
import ch.wsl.box.client.views.components.widget.{ComponentWidgetFactory, Widget, WidgetParams}
import ch.wsl.box.model.shared.{JSONField, JSONQuery, JSONQueryFilter, WidgetsNames}
import ch.wsl.box.shared.utils.JSONUtils.EnhancedJson
import io.udash.bindings.modifiers.Binding
import scalatags.JsDom
import scalatags.JsDom.all._



object ExportWidget extends ComponentWidgetFactory {

override def name: String = WidgetsNames.`export`

override def create(params: WidgetParams): Widget = ExportWidgetImpl(params)

case class ExportWidgetImpl(params: WidgetParams) extends Widget {

val exportDialog = new ExportTableDialog

override def field: JSONField = params.field


def exportParams = for{
c <- field.child
m <- params.children.find(_.objId == c.objId)
} yield {
val childFilters = c.mapping.map(m => JSONQueryFilter.WHERE.eq(m.child,params.allData.get.get(m.parent)))
ExportParams(m,m.table,c.childQuery.getOrElse(JSONQuery.empty).filterWith(childFilters:_*))
}

override def killWidget(): Unit = {
super.killWidget()
exportDialog.clean()
}

override protected def show(nested:Binding.NestedInterceptor): JsDom.all.Modifier = {
exportParams match {
case Some(ep) => div(
exportDialog.render(nested,() => ep)
)
case None => div("Please check form definition, can't render the export")
}

}

override protected def edit(nested:Binding.NestedInterceptor): JsDom.all.Modifier = show(nested)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,32 +86,17 @@ object Spreadsheet extends ComponentWidgetFactory {
case Some(value:JSONFieldLookupRemote) => value
case None => throw new Exception("Not a remote lookup")
}
val allColumnId: Seq[String] = rawData.map{ row =>
val keys = fieldLookup.map.localKeysColumn.map { local =>
val value = row.js(local).fold(
"null",
bool => bool.toString,
num => num.toString,
str => s"'$str'",
arr => arr.toString,
obj => obj.toString
)
value
val allColumnId: Seq[Seq[Json]] = rawData.map{ row =>
fieldLookup.map.localKeysColumn.map { local =>
row.js(local)
}
keys.mkString("(",",",")")
}
logger.debug(s"Loading dropdown for $allColumnId")


val query =
s"""
| ${fieldLookup.map.foreign.keyColumns.mkString("(",",",")")} in ${allColumnId.mkString("(",",",")")}
|""".stripMargin
val query = JSONQuery.filterWith(JSONQueryFilter.WHERE.inSet(fieldLookup.map.foreign.keyColumns,allColumnId))

logger.debug(s"Generated where: $query")
val values = JSONQuery.where(query)

services.rest.lookup(EntityKind.FORM.kind,services.clientSession.lang(),childMetadata.get.name,field.name,values).map{ rows =>
services.rest.lookup(EntityKind.FORM.kind,services.clientSession.lang(),childMetadata.get.name,field.name,query).map{ rows =>
rows.map { row =>
row.id -> row.value.asJson
}.toMap
Expand Down
Loading
Loading