Adding column/row insert/delete

main
Oliver Kennedy 2023-01-14 13:53:50 -05:00
parent 2478523622
commit fc8b106200
Signed by: okennedy
GPG Key ID: 3E5F9B3ABD3FDB60
19 changed files with 3025 additions and 75 deletions

View File

@ -91,6 +91,19 @@ object cells extends Module {
.map { PathRef(_) } .map { PathRef(_) }
} }
def css =
T.sources {
os.walk(millSourcePath / "css")
.filter { _.ext == "css" }
.map { PathRef(_) }
}
def fonts =
T.sources {
os.walk(millSourcePath / "fonts")
.map { PathRef(_) }
}
} }
def resources = def resources =
@ -110,13 +123,15 @@ object cells extends Module {
) )
os.write( os.write(
target / "app" / "cells.css", target / "app" / "css" / "cells.css",
ui.compiledSass(), ui.compiledSass(),
createFolders = true createFolders = true
) )
val assets = val assets =
ui.html().map { x => (x.path -> os.rel / x.path.last) } ui.html().map { x => (x.path -> os.rel / x.path.last) }++
ui.css().map { x => (x.path -> os.rel / "css" / x.path.last) }++
ui.fonts().map { x => (x.path -> os.rel / "fonts" / x.path.last) }
for((asset, assetTarget) <- assets){ for((asset, assetTarget) <- assets){
os.copy.over( os.copy.over(

View File

@ -7,6 +7,8 @@ import net.okennedy.cells.WebsocketConnection
object CellsServer extends cask.MainRoutes object CellsServer extends cask.MainRoutes
{ {
override def port: Int = 4444
var layout = new state.Canvas() var layout = new state.Canvas()
layout.addTable(rows = 5, cols = 3) layout.addTable(rows = 5, cols = 3)
@ -31,10 +33,6 @@ object CellsServer extends cask.MainRoutes
def app(request: cask.Request) = def app(request: cask.Request) =
serveResource("app/cells.js", "Content-Type" -> "application/javascript") serveResource("app/cells.js", "Content-Type" -> "application/javascript")
@cask.get("/cells.css")
def css(request: cask.Request) =
serveResource("app/cells.css", "Content-Type" -> "text/css")
@cask.get("/out.js.map") @cask.get("/out.js.map")
def appMap(request: cask.Request) = def appMap(request: cask.Request) =
serveResource("app/out.js.map", "Content-Type" -> "application/javascript") serveResource("app/out.js.map", "Content-Type" -> "application/javascript")
@ -49,8 +47,15 @@ object CellsServer extends cask.MainRoutes
} }
} }
@cask.staticResources("/app") @cask.staticResources("/css",
def staticResourceRoutes() = "app" headers = Seq("Content-Type" -> "text/css")
)
def cssResourceRoutes() = "app/css"
@cask.staticResources("/fonts",
// headers = Seq("Content-Type" -> "text/css")
)
def fontResourceRoutes() = "app/fonts"
initialize() initialize()
} }

View File

@ -13,6 +13,8 @@ class WebsocketConnection(val id: Int, channel: WsChannelActor)
{ {
event match { event match {
case Ws.Text(data) => case Ws.Text(data) =>
println(s"ZZZ: $data")
Json.parse(data).as[WebsocketRequest] match { Json.parse(data).as[WebsocketRequest] match {
case WebsocketHello(_) => case WebsocketHello(_) =>
for(table <- CellsServer.layout.tables.values) for(table <- CellsServer.layout.tables.values)

View File

@ -5,10 +5,8 @@ import net.okennedy.cells.Identifier
import net.okennedy.cells.serialized import net.okennedy.cells.serialized
import net.okennedy.cells.TableRequest import net.okennedy.cells.TableRequest
import net.okennedy.cells.RequestSetTablePosition import net.okennedy.cells.RequestSetTablePosition
import net.okennedy.cells.RequestAppendColumn import net.okennedy.cells.RequestUpdateColumns
import net.okennedy.cells.RequestAppendRow import net.okennedy.cells.RequestUpdateRows
import net.okennedy.cells.RequestUpdateColumn
import net.okennedy.cells.RequestUpdateRow
import net.okennedy.cells.SetTablePosition import net.okennedy.cells.SetTablePosition
import net.okennedy.cells.WebsocketConnection import net.okennedy.cells.WebsocketConnection
import net.okennedy.cells.serialized.ColSpec import net.okennedy.cells.serialized.ColSpec
@ -55,48 +53,48 @@ class Table(val id: Identifier)
return id return id
} }
def updateColumns(op: SeqOp[ColSpec]): Unit =
{
val opWithValidId =
op match {
case SeqInsert(_, _) => op.map { _.copy(id = genSafeColId()) }
case SeqReplace(pos, _) => op.map { _.copy(id = columns(pos).id) }
case SeqDelete(_) => op
}
println(s"Update: $op")
opWithValidId.bufferApply(columns)
WebsocketConnection.broadcast(
UpdateTableColumns(id, opWithValidId)
)
}
def addColumn(width: Int = 100): Unit = def addColumn(width: Int = 100): Unit =
{ {
val col = ColSpec( updateColumns(SeqInsert(columns.length,
id = genSafeColId(), ColSpec(id = null, width = width)
width = width ))
) }
columns.append(col)
def updateRows(op: SeqOp[RowSpec]): Unit =
{
val opWithValidId =
op match {
case SeqInsert(_, _) => op.map { _.copy(id = genSafeRowId()) }
case SeqReplace(pos, _) => op.map { _.copy(id = rows(pos).id) }
case SeqDelete(_) => op
}
opWithValidId.bufferApply(rows)
WebsocketConnection.broadcast( WebsocketConnection.broadcast(
UpdateTableColumns(id, SeqInsert(columns.size-1, col)) UpdateTableRows(id, opWithValidId)
) )
} }
def addRow(height: Int = 30): Unit = def addRow(height: Int = 30): Unit =
{ {
val row = RowSpec( updateRows(SeqInsert(rows.length,
id = genSafeColId(), RowSpec(id = null, height = height)
height = height ))
)
rows.append(row)
WebsocketConnection.broadcast(
UpdateTableRows(id, SeqInsert(rows.size-1, row))
)
}
def updateColumn(spec: ColSpec): Unit =
{
val idx = columns.indexWhere { _.id == spec.id }
if(idx < 0){ return }
columns.update(idx, spec)
WebsocketConnection.broadcast(
UpdateTableColumns(id, SeqReplace(idx, spec))
)
}
def updateRow(spec: RowSpec): Unit =
{
val idx = rows.indexWhere { _.id == spec.id }
if(idx < 0){ return }
rows.update(idx, spec)
WebsocketConnection.broadcast(
UpdateTableRows(id, SeqReplace(idx, spec))
)
} }
def setPosition(x: Int, y: Int): Unit = def setPosition(x: Int, y: Int): Unit =
@ -109,12 +107,11 @@ class Table(val id: Identifier)
def update(op: TableRequest): Unit = def update(op: TableRequest): Unit =
{ {
println(s"Table update: $op")
op match { op match {
case RequestSetTablePosition(_, x, y) => setPosition(x, y) case RequestSetTablePosition(_, x, y) => setPosition(x, y)
case RequestAppendColumn(_) => addColumn() case RequestUpdateColumns(_, cop) => updateColumns(cop)
case RequestAppendRow(_) => addRow() case RequestUpdateRows(_, rop) => updateRows(rop)
case RequestUpdateColumn(_, spec) => updateColumn(spec)
case RequestUpdateRow(_, spec) => updateRow(spec)
} }
} }

View File

@ -1,5 +1,6 @@
package net.okennedy.cells package net.okennedy.cells
import scala.collection.mutable
import play.api.libs.json.Json import play.api.libs.json.Json
import play.api.libs.json.Format import play.api.libs.json.Format
import play.api.libs.json.JsResult import play.api.libs.json.JsResult
@ -12,6 +13,7 @@ sealed trait SeqOp[T]
{ {
def apply(seq: Seq[T]): Seq[T] def apply(seq: Seq[T]): Seq[T]
def map[B](op: T => B): SeqOp[B] def map[B](op: T => B): SeqOp[B]
def bufferApply(seq: mutable.Buffer[T]): Unit
} }
@ -23,6 +25,8 @@ case class SeqInsert[T](position: Int, value: T) extends SeqOp[T]
(before :+ value) ++ after (before :+ value) ++ after
} }
def map[B](op: T => B): SeqOp[B] = SeqInsert[B](position, op(value)) def map[B](op: T => B): SeqOp[B] = SeqInsert[B](position, op(value))
def bufferApply(seq: mutable.Buffer[T]): Unit =
seq.insert(position, value)
} }
case class SeqDelete[T](position: Int) extends SeqOp[T] case class SeqDelete[T](position: Int) extends SeqOp[T]
{ {
@ -32,6 +36,8 @@ case class SeqDelete[T](position: Int) extends SeqOp[T]
before ++ after.drop(1) before ++ after.drop(1)
} }
def map[B](op: T => B): SeqOp[B] = SeqDelete(position) def map[B](op: T => B): SeqOp[B] = SeqDelete(position)
def bufferApply(seq: mutable.Buffer[T]): Unit =
seq.remove(position)
} }
case class SeqReplace[T](position: Int, value: T) extends SeqOp[T] case class SeqReplace[T](position: Int, value: T) extends SeqOp[T]
{ {
@ -41,6 +47,8 @@ case class SeqReplace[T](position: Int, value: T) extends SeqOp[T]
(before :+ value) ++ after.drop(1) (before :+ value) ++ after.drop(1)
} }
def map[B](op: T => B): SeqOp[B] = SeqReplace[B](position, op(value)) def map[B](op: T => B): SeqOp[B] = SeqReplace[B](position, op(value))
def bufferApply(seq: mutable.Buffer[T]): Unit =
seq(position) = value
} }
object SeqOp object SeqOp

View File

@ -16,19 +16,15 @@ sealed trait TableRequest extends CanvasRequest
} }
case class RequestSetTablePosition(table: Identifier, x: Int, y: Int) extends TableRequest case class RequestSetTablePosition(table: Identifier, x: Int, y: Int) extends TableRequest
case class RequestAppendColumn(table: Identifier) extends TableRequest case class RequestUpdateColumns(table: Identifier, op: SeqOp[serialized.ColSpec]) extends TableRequest
case class RequestUpdateColumn(table: Identifier, spec: serialized.ColSpec) extends TableRequest case class RequestUpdateRows(table: Identifier, op: SeqOp[serialized.RowSpec]) extends TableRequest
case class RequestAppendRow(table: Identifier) extends TableRequest
case class RequestUpdateRow(table: Identifier, spec: serialized.RowSpec) extends TableRequest
object WebsocketRequest object WebsocketRequest
{ {
implicit val WebsocketHelloFormat: Format[WebsocketHello] = Json.format implicit val WebsocketHelloFormat: Format[WebsocketHello] = Json.format
implicit val RequestAddTableFormat: Format[RequestAddTable] = Json.format implicit val RequestAddTableFormat: Format[RequestAddTable] = Json.format
implicit val RequestSetTablePositionFormat: Format[RequestSetTablePosition] = Json.format implicit val RequestSetTablePositionFormat: Format[RequestSetTablePosition] = Json.format
implicit val RequestAppendColumnFormat: Format[RequestAppendColumn] = Json.format implicit val RequestUpdateColumnsFormat: Format[RequestUpdateColumns] = Json.format
implicit val RequestUpdateColumnFormat: Format[RequestUpdateColumn] = Json.format implicit val RequestUpdateRowsFormat: Format[RequestUpdateRows] = Json.format
implicit val RequestAppendRowFormat: Format[RequestAppendRow] = Json.format
implicit val RequestUpdateRowFormat: Format[RequestUpdateRow] = Json.format
implicit val WebsocketRequestFormat: Format[WebsocketRequest] = Json.format implicit val WebsocketRequestFormat: Format[WebsocketRequest] = Json.format
} }

View File

@ -117,6 +117,44 @@ body
margin-top: -5px; margin-top: -5px;
cursor: row-resize; cursor: row-resize;
} }
.addRemoveColumnWidget
{
$addRemoveColumnWidgetOffset: 16px;
position: absolute;
right: -1px-$addRemoveColumnWidgetOffset;
top: 0px;
width: $addRemoveColumnWidgetOffset;
height: 20px;
background-color: #eee;
text-align: center;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
cursor: ew-resize;
visibility: hidden;
}
.addRemoveRowWidget
{
$addRemoveRowWidgetOffset: 16px;
position: absolute;
left: 0px;
bottom: -1px-$addRemoveRowWidgetOffset;
height: $addRemoveRowWidgetOffset;
width: 20px;
background-color: #eee;
text-align: center;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
cursor: ns-resize;
visibility: hidden;
}
&:hover
{
.addRemoveColumnWidget { visibility: visible; }
.addRemoveRowWidget { visibility: visible; }
}
} }
.widgets .widgets

4
cells/ui/css/font-awesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,7 +4,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cells</title> <title>Cells</title>
<link rel="stylesheet" type="text/css" href="cells.css"> <link rel="stylesheet" type="text/css" href="css/cells.css">
<link rel="stylesheet" type="text/css" href="css/font-awesome.min.css"/>
</head> </head>
<body> <body>
<script type="text/javascript" src="cells.js"></script> <script type="text/javascript" src="cells.js"></script>

View File

@ -14,7 +14,7 @@ object CellsUI
@JSExport("run") @JSExport("run")
def run(): Unit = def run(): Unit =
{ {
val conn = new network.Connection("ws://localhost:8080/ws") val conn = new network.Connection("ws://localhost:4444/ws")
dom.window.onload = { (evt: dom.Event) => dom.window.onload = { (evt: dom.Event) =>
val container: dom.Element = val container: dom.Element =

View File

@ -5,4 +5,10 @@ object Constants
val GUTTER_HEIGHT = 20 val GUTTER_HEIGHT = 20
val GUTTER_WIDTH = 30 val GUTTER_WIDTH = 30
val BORDER = 1 val BORDER = 1
val DEFAULT_CELL_WIDTH = 100
val DEFAULT_CELL_HEIGHT = 30
val NULL_UUID =
java.util.UUID(0, 0)
} }

View File

@ -20,8 +20,14 @@ import net.okennedy.cells.network.Connection
import net.okennedy.cells.RequestSetTablePosition import net.okennedy.cells.RequestSetTablePosition
import net.okennedy.cells.UpdateTableColumns import net.okennedy.cells.UpdateTableColumns
import net.okennedy.cells.UpdateTableRows import net.okennedy.cells.UpdateTableRows
import net.okennedy.cells.RequestUpdateRow import net.okennedy.cells.RequestUpdateRows
import net.okennedy.cells.RequestUpdateColumn import net.okennedy.cells.RequestUpdateColumns
import net.okennedy.cells.widgets.Icon
import scala.collection.Searching.Found
import scala.collection.Searching.InsertionPoint
import net.okennedy.cells.SeqReplace
import net.okennedy.cells.SeqInsert
import net.okennedy.cells.SeqDelete
class Table(val id: Identifier, connection: Connection) class Table(val id: Identifier, connection: Connection)
{ {
@ -50,8 +56,10 @@ class Table(val id: Identifier, connection: Connection)
x.set(newX); y.set(newY) x.set(newX); y.set(newY)
) )
case UpdateTableColumns(_, update) => case UpdateTableColumns(_, update) =>
println(s"ColUpdate??? : $update")
columns.set(update(columns.now())) columns.set(update(columns.now()))
case UpdateTableRows(_, update) => case UpdateTableRows(_, update) =>
println(s"RowUpdate??? : $update")
rows.set(update(rows.now())) rows.set(update(rows.now()))
} }
} }
@ -106,6 +114,43 @@ class Table(val id: Identifier, connection: Connection)
rows.signal.map { _.foldLeft(0)( (h, row) => h + row.height) } rows.signal.map { _.foldLeft(0)( (h, row) => h + row.height) }
.observe .observe
def snapIndex(snaps: Seq[Int], excessStep: Int, value: Int): Int =
{
// println(s"Snap $value")
if(snaps.size > 0 && value < snaps.last){
snaps.search(value) match {
case Found(foundIndex) =>
// println(s"Found: $foundIndex");
foundIndex
case InsertionPoint(insertionPoint) =>
if(insertionPoint == 0) {
// println(s"Insert @ start");
0
} else if(insertionPoint >= snaps.length) {
// println(s"Insert @ end");
snaps.length - 1
} else {
val lowDiff = value - snaps(insertionPoint - 1)
val highDiff = snaps(insertionPoint) - value
// println(s"Insert @ [${insertionPoint-1}, $insertionPoint] : $lowDiff <-> $highDiff");
if(lowDiff > highDiff){ insertionPoint }
else { insertionPoint-1 }
}
}
} else {
val excess = value - snaps.lastOption.getOrElse(0) + (excessStep / 2)
// println(s"Over with excess: $excess")
(snaps.length - 1) + (excess / excessStep).toInt
}
}
def snapTo(snaps: Seq[Int], excessStep: Int, value: Int): Int =
{
val idx = snapIndex(snaps, excessStep, value)
if(idx < snaps.length){ snaps(idx) }
else { snaps.lastOption.getOrElse(0) + excessStep * (idx - (snaps.length -1)) }
}
def root = div( def root = div(
className("dataTable"), className("dataTable"),
@ -228,8 +273,8 @@ class Table(val id: Identifier, connection: Connection)
// Display the resizing bars - Up<->Down // Display the resizing bars - Up<->Down
children <-- children <--
rowGutters.map { _.map { rowGutters.map { _.zipWithIndex.map {
case (rowKey, rowStream) => case ((rowKey, rowStream), rowIdx) =>
div( div(
className("rowResizeBar"), className("rowResizeBar"),
styleAttr <-- styleAttr <--
@ -260,10 +305,13 @@ class Table(val id: Identifier, connection: Connection)
) { newHeight => ) { newHeight =>
println(s"Drag $rowKey @ ($pos, ${pos+height}) -> $newHeight") println(s"Drag $rowKey @ ($pos, ${pos+height}) -> $newHeight")
connection.send( connection.send(
RequestUpdateRow( RequestUpdateRows(
id, id,
rows.now().find { _.id == rowKey }.get SeqReplace(
.copy( height = newHeight ) rowIdx,
rows.now().find { _.id == rowKey }.get
.copy( height = newHeight )
)
) )
) )
} }
@ -276,8 +324,8 @@ class Table(val id: Identifier, connection: Connection)
// Display the resizing bars - Left<->Right // Display the resizing bars - Left<->Right
children <-- children <--
columnGutters.map { _.map { columnGutters.map { _.zipWithIndex.map {
case (colKey, colStream) => case ((colKey, colStream), colIdx) =>
div( div(
className("columnResizeBar"), className("columnResizeBar"),
styleAttr <-- styleAttr <--
@ -308,10 +356,13 @@ class Table(val id: Identifier, connection: Connection)
) { newWidth => ) { newWidth =>
println(s"Drag $colKey @ ($pos, ${pos+width}) -> $newWidth") println(s"Drag $colKey @ ($pos, ${pos+width}) -> $newWidth")
connection.send( connection.send(
RequestUpdateColumn( RequestUpdateColumns(
id, id,
columns.now().find { _.id == colKey }.get SeqReplace(
.copy( width = newWidth ) colIdx,
columns.now().find { _.id == colKey }.get
.copy( width = newWidth )
)
) )
) )
} }
@ -319,6 +370,132 @@ class Table(val id: Identifier, connection: Connection)
evt.stopPropagation() evt.stopPropagation()
} }
) )
} } } },
// Display the add/remove column widget
div(
className("addRemoveColumnWidget"),
Icon("caret-right"),
onMouseDown --> {
(evt) =>
if(evt.button == 0){
val snaps =
columns.now()
.foldLeft( Seq[Int]() ){
(accum, col) =>
accum :+ (accum.lastOption.getOrElse(0) + col.width )
}
// println(s"Snaps: ${snaps.mkString(", ")}")
DragFrame.snapWidth(
x.now()+Constants.GUTTER_WIDTH,
y.now(),
width.now(),
height.now()+Constants.GUTTER_HEIGHT,
evt
)(snapTo(snaps, Constants.DEFAULT_CELL_WIDTH, _)) { snap =>
val oldColCount = columns.now().length
val newColCount = snapIndex(snaps, Constants.DEFAULT_CELL_WIDTH, snap) + 1
var i = 0
if(oldColCount < newColCount)
{
for(i <- oldColCount until newColCount)
{
println(s"Adding column $i")
connection.send(
RequestUpdateColumns(
id,
SeqInsert(
i,
serialized.ColSpec(
id = Constants.NULL_UUID,
width = Constants.DEFAULT_CELL_WIDTH
)
)
)
)
}
} else if(oldColCount > newColCount)
{
for(i <- (oldColCount-1) to newColCount by -1)
{
println(s"Removing column $i")
connection.send(
RequestUpdateColumns(
id,
SeqDelete(i)
)
)
}
}
}
evt.stopPropagation()
}
}
),
div(
className("addRemoveRowWidget"),
Icon("caret-down"),
onMouseDown --> {
(evt) =>
if(evt.button == 0){
val snaps =
rows.now()
.foldLeft( Seq[Int]() ){
(accum, row) =>
accum :+ (accum.lastOption.getOrElse(0) + row.height )
}
// println(s"Snaps: ${snaps.mkString(", ")}")
DragFrame.snapHeight(
x.now(),
y.now()+Constants.GUTTER_HEIGHT,
width.now()+Constants.GUTTER_WIDTH,
height.now(),
evt
)(snapTo(snaps, Constants.DEFAULT_CELL_HEIGHT, _)) { snap =>
val oldRowCount = rows.now().length
val newRowCount = snapIndex(snaps, Constants.DEFAULT_CELL_HEIGHT, snap) + 1
var i = 0
if(oldRowCount < newRowCount)
{
for(i <- oldRowCount until newRowCount)
{
println(s"Adding row $i")
connection.send(
RequestUpdateRows(
id,
SeqInsert(
i,
serialized.RowSpec(
id = Constants.NULL_UUID,
height = Constants.DEFAULT_CELL_HEIGHT
)
)
)
)
}
} else if(oldRowCount > newRowCount)
{
for(i <- (oldRowCount-1) to newRowCount by -1)
{
println(s"Removing row $i")
connection.send(
RequestUpdateRows(
id,
SeqDelete(i)
)
)
}
}
}
evt.stopPropagation()
}
}
)
) )
} }

View File

@ -17,12 +17,24 @@ object DragFrame
Widgets.register(frame) Widgets.register(frame)
} }
def snapWidth(x: Int, y: Int, width: Int, height: Int, evt: org.scalajs.dom.MouseEvent)(doSnap: Int => Int)(callback: (Int) => Unit) =
{
val frame = new Width(x, y, width, height, evt.pageX, evt.pageY, callback) { override def snap(x: Int) = doSnap(x+width)-width }
Widgets.register(frame)
}
def height(x: Int, y: Int, width: Int, height: Int, evt: org.scalajs.dom.MouseEvent)(callback: (Int) => Unit) = def height(x: Int, y: Int, width: Int, height: Int, evt: org.scalajs.dom.MouseEvent)(callback: (Int) => Unit) =
{ {
val frame = new Height(x, y, width, height, evt.pageX, evt.pageY, callback) val frame = new Height(x, y, width, height, evt.pageX, evt.pageY, callback)
Widgets.register(frame) Widgets.register(frame)
} }
def snapHeight(x: Int, y: Int, width: Int, height: Int, evt: org.scalajs.dom.MouseEvent)(doSnap: Int => Int)(callback: (Int) => Unit) =
{
val frame = new Height(x, y, width, height, evt.pageX, evt.pageY, callback) { override def snap(x: Int) = doSnap(x+height)-height }
Widgets.register(frame)
}
class Position(initX: Int, initY: Int, width: Int, height: Int, cursorX: Double, cursorY: Double, callback: (Int, Int) => Unit) class Position(initX: Int, initY: Int, width: Int, height: Int, cursorX: Double, cursorY: Double, callback: (Int, Int) => Unit)
extends Widget extends Widget
{ {
@ -55,6 +67,8 @@ object DragFrame
{ {
val curr = Var[Int](initial = width) val curr = Var[Int](initial = width)
def snap(v: Int): Int = v
val root = div( val root = div(
className("dragFrame"), className("dragFrame"),
styleAttr <-- styleAttr <--
@ -69,7 +83,7 @@ object DragFrame
}, },
documentEvents.onMouseMove --> { documentEvents.onMouseMove --> {
(evt) => (evt) =>
curr.set( (width) + (evt.pageX - cursorX).toInt ) curr.set( (width) + snap( (evt.pageX - cursorX).toInt ) )
} }
) )
} }
@ -79,6 +93,8 @@ object DragFrame
{ {
val curr = Var[Int](initial = height) val curr = Var[Int](initial = height)
def snap(v: Int): Int = v
val root = div( val root = div(
className("dragFrame"), className("dragFrame"),
styleAttr <-- styleAttr <--
@ -93,7 +109,7 @@ object DragFrame
}, },
documentEvents.onMouseMove --> { documentEvents.onMouseMove --> {
(evt) => (evt) =>
curr.set( (height) + (evt.pageY - cursorY).toInt ) curr.set( (height) + snap( (evt.pageY - cursorY).toInt ) )
} }
) )
} }

View File

@ -0,0 +1,14 @@
package net.okennedy.cells.widgets
import org.scalajs.dom
import com.raquo.laminar.api.L._
import com.raquo.laminar.nodes.ReactiveHtmlElement
object Icon
{
def apply(name: String): ReactiveHtmlElement[dom.html.Element]=
i(
className(s"fa fa-${name}"),
aria.hidden(true)
)
}