Work in progress on the formula editor
parent
fc8b106200
commit
6df1b5900c
|
@ -14,7 +14,7 @@ class WebsocketConnection(val id: Int, channel: WsChannelActor)
|
||||||
event match {
|
event match {
|
||||||
case Ws.Text(data) =>
|
case Ws.Text(data) =>
|
||||||
|
|
||||||
println(s"ZZZ: $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)
|
||||||
|
|
|
@ -61,7 +61,7 @@ class Table(val id: Identifier)
|
||||||
case SeqReplace(pos, _) => op.map { _.copy(id = columns(pos).id) }
|
case SeqReplace(pos, _) => op.map { _.copy(id = columns(pos).id) }
|
||||||
case SeqDelete(_) => op
|
case SeqDelete(_) => op
|
||||||
}
|
}
|
||||||
println(s"Update: $op")
|
// println(s"Update: $op")
|
||||||
opWithValidId.bufferApply(columns)
|
opWithValidId.bufferApply(columns)
|
||||||
WebsocketConnection.broadcast(
|
WebsocketConnection.broadcast(
|
||||||
UpdateTableColumns(id, opWithValidId)
|
UpdateTableColumns(id, opWithValidId)
|
||||||
|
@ -101,13 +101,13 @@ class Table(val id: Identifier)
|
||||||
{
|
{
|
||||||
this.x = x
|
this.x = x
|
||||||
this.y = y
|
this.y = y
|
||||||
println(s"Position now $x, $y")
|
// println(s"Position now $x, $y")
|
||||||
WebsocketConnection.broadcast(SetTablePosition(id, x, y))
|
WebsocketConnection.broadcast(SetTablePosition(id, x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
def update(op: TableRequest): Unit =
|
def update(op: TableRequest): Unit =
|
||||||
{
|
{
|
||||||
println(s"Table update: $op")
|
// println(s"Table update: $op")
|
||||||
op match {
|
op match {
|
||||||
case RequestSetTablePosition(_, x, y) => setPosition(x, y)
|
case RequestSetTablePosition(_, x, y) => setPosition(x, y)
|
||||||
case RequestUpdateColumns(_, cop) => updateColumns(cop)
|
case RequestUpdateColumns(_, cop) => updateColumns(cop)
|
||||||
|
|
|
@ -167,4 +167,78 @@ body
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
border: dashed lightgrey 2px;
|
border: dashed lightgrey 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.formulaEditor
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
.pointer
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: -4px;
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.editorBody
|
||||||
|
{
|
||||||
|
$editorWidth: 200px;
|
||||||
|
$buttonSize: 20px;
|
||||||
|
$spacing: 8px;
|
||||||
|
$editorPadding: 2px;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
border: solid 1px #aaa;
|
||||||
|
border-radius: 2px;
|
||||||
|
top: 5px;
|
||||||
|
left: -16px;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #ddd;
|
||||||
|
width: $editorWidth;
|
||||||
|
height: $buttonSize + 4px;
|
||||||
|
|
||||||
|
.inputArea
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
left: $spacing;
|
||||||
|
top: $spacing;
|
||||||
|
width: $editorWidth - 2 * $buttonSize - 3 * $spacing;
|
||||||
|
height: $buttonSize;
|
||||||
|
background-color: white;
|
||||||
|
padding: 2px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
cursor: text;
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
{
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button
|
||||||
|
{
|
||||||
|
position: absolute;
|
||||||
|
top: $editorPadding + $spacing;
|
||||||
|
width: $buttonSize;
|
||||||
|
height: $buttonSize;
|
||||||
|
border-radius: $buttonSize / 2;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.cancel
|
||||||
|
{
|
||||||
|
right: $buttonSize + 2 * $spacing;
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.accept
|
||||||
|
{
|
||||||
|
right: $spacing;
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -28,6 +28,7 @@ import scala.collection.Searching.InsertionPoint
|
||||||
import net.okennedy.cells.SeqReplace
|
import net.okennedy.cells.SeqReplace
|
||||||
import net.okennedy.cells.SeqInsert
|
import net.okennedy.cells.SeqInsert
|
||||||
import net.okennedy.cells.SeqDelete
|
import net.okennedy.cells.SeqDelete
|
||||||
|
import net.okennedy.cells.widgets.FormulaEditor
|
||||||
|
|
||||||
class Table(val id: Identifier, connection: Connection)
|
class Table(val id: Identifier, connection: Connection)
|
||||||
{
|
{
|
||||||
|
@ -46,6 +47,7 @@ class Table(val id: Identifier, connection: Connection)
|
||||||
this.columns.set(ser.columns)
|
this.columns.set(ser.columns)
|
||||||
this.rows.set(ser.rows)
|
this.rows.set(ser.rows)
|
||||||
)
|
)
|
||||||
|
startEditing(columns.now()(1).id, rows.now()(1).id)
|
||||||
}
|
}
|
||||||
|
|
||||||
def process(op: TableOp): Unit =
|
def process(op: TableOp): Unit =
|
||||||
|
@ -114,6 +116,28 @@ 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 positionAndHeightOfRow(rowKey: Identifier): (Int, Int) =
|
||||||
|
{
|
||||||
|
rows.now().foldLeft[Either[Int, (Int, Int)]]( Left(0) ) {
|
||||||
|
case (Left(x), row) if row.id == rowKey =>
|
||||||
|
Right(x -> row.height)
|
||||||
|
case (Left(x), row) =>
|
||||||
|
Left(x + row.height)
|
||||||
|
case (x@Right(pos, height), _) => x
|
||||||
|
}.right.get
|
||||||
|
}
|
||||||
|
|
||||||
|
def positionAndWidthOfColumn(colKey: Identifier): (Int, Int) =
|
||||||
|
{
|
||||||
|
columns.now().foldLeft[Either[Int, (Int, Int)]]( Left(0) ) {
|
||||||
|
case (Left(x), col) if col.id == colKey =>
|
||||||
|
Right(x -> col.width)
|
||||||
|
case (Left(x), col) =>
|
||||||
|
Left(x + col.width)
|
||||||
|
case (x@Right(pos, width), _) => x
|
||||||
|
}.right.get
|
||||||
|
}
|
||||||
|
|
||||||
def snapIndex(snaps: Seq[Int], excessStep: Int, value: Int): Int =
|
def snapIndex(snaps: Seq[Int], excessStep: Int, value: Int): Int =
|
||||||
{
|
{
|
||||||
// println(s"Snap $value")
|
// println(s"Snap $value")
|
||||||
|
@ -151,6 +175,20 @@ class Table(val id: Identifier, connection: Connection)
|
||||||
else { snaps.lastOption.getOrElse(0) + excessStep * (idx - (snaps.length -1)) }
|
else { snaps.lastOption.getOrElse(0) + excessStep * (idx - (snaps.length -1)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def startEditing(colKey: Identifier, rowKey: Identifier): Unit =
|
||||||
|
{
|
||||||
|
val (cellX, cellWidth) =
|
||||||
|
positionAndWidthOfColumn(colKey)
|
||||||
|
val (cellY, cellHeight) =
|
||||||
|
positionAndHeightOfRow(rowKey)
|
||||||
|
|
||||||
|
println(s"Editing $colKey x $rowKey")
|
||||||
|
FormulaEditor(
|
||||||
|
cellX+x.now()+Constants.GUTTER_WIDTH+(cellWidth/2),
|
||||||
|
cellY+y.now()+Constants.GUTTER_HEIGHT+cellHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
def root = div(
|
def root = div(
|
||||||
className("dataTable"),
|
className("dataTable"),
|
||||||
|
|
||||||
|
@ -262,7 +300,17 @@ class Table(val id: Identifier, connection: Connection)
|
||||||
div(className("cellBody empty"), "")
|
div(className("cellBody empty"), "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
// onMouseDown is handled before onClick, so
|
||||||
|
// avoid the MouseDown event propagating to the
|
||||||
|
// parent container and triggering a drag
|
||||||
|
onMouseDown --> {
|
||||||
|
evt => evt.stopPropagation()
|
||||||
|
},
|
||||||
|
onClick --> {
|
||||||
|
evt => startEditing(colKey, rowKey)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
@ -288,13 +336,7 @@ class Table(val id: Identifier, connection: Connection)
|
||||||
// We can't peek into colStream without violating ownership, so just
|
// We can't peek into colStream without violating ownership, so just
|
||||||
// recompute the position
|
// recompute the position
|
||||||
val (pos, height) =
|
val (pos, height) =
|
||||||
rows.now().foldLeft[Either[Int, (Int, Int)]]( Left(0) ) {
|
positionAndHeightOfRow(rowKey)
|
||||||
case (Left(x), row) if row.id == rowKey =>
|
|
||||||
Right(x -> row.height)
|
|
||||||
case (Left(x), row) =>
|
|
||||||
Left(x + row.height)
|
|
||||||
case (x@Right(pos, width), _) => x
|
|
||||||
}.right.get
|
|
||||||
|
|
||||||
DragFrame.height(
|
DragFrame.height(
|
||||||
x.now(),
|
x.now(),
|
||||||
|
@ -339,13 +381,7 @@ class Table(val id: Identifier, connection: Connection)
|
||||||
// We can't peek into rowStream without violating ownership, so just
|
// We can't peek into rowStream without violating ownership, so just
|
||||||
// recompute the position
|
// recompute the position
|
||||||
val (pos, width) =
|
val (pos, width) =
|
||||||
columns.now().foldLeft[Either[Int, (Int, Int)]]( Left(0) ) {
|
positionAndWidthOfColumn(colKey)
|
||||||
case (Left(x), col) if col.id == colKey =>
|
|
||||||
Right(x -> col.width)
|
|
||||||
case (Left(x), col) =>
|
|
||||||
Left(x + col.width)
|
|
||||||
case (x@Right(pos, width), _) => x
|
|
||||||
}.right.get
|
|
||||||
|
|
||||||
DragFrame.width(
|
DragFrame.width(
|
||||||
x.now()+pos+Constants.GUTTER_WIDTH,
|
x.now()+pos+Constants.GUTTER_WIDTH,
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
package net.okennedy.cells.widgets
|
||||||
|
|
||||||
|
import org.scalajs.dom
|
||||||
|
import com.raquo.laminar.api.L._
|
||||||
|
import com.raquo.laminar.nodes.ReactiveHtmlElement
|
||||||
|
|
||||||
|
class FormulaEditor(x: Int, y: Int)
|
||||||
|
extends Widget
|
||||||
|
{
|
||||||
|
val formula = Var[String](initial = "Hihihihi")
|
||||||
|
|
||||||
|
val inputArea =
|
||||||
|
input(
|
||||||
|
className("inputArea"),
|
||||||
|
`type`("text"),
|
||||||
|
controlled(
|
||||||
|
value <-- formula,
|
||||||
|
onInput.mapToValue --> formula.writer
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def cancel(): Unit =
|
||||||
|
{
|
||||||
|
println("Cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
def accept(): Unit =
|
||||||
|
{
|
||||||
|
println(s"Accepted with ${formula.now()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
val root =
|
||||||
|
div(
|
||||||
|
className("formulaEditor"),
|
||||||
|
styleAttr(s"left: ${x}px; top: ${y}px"),
|
||||||
|
div(
|
||||||
|
className("pointer"),
|
||||||
|
Icon("caret-up")
|
||||||
|
),
|
||||||
|
div(
|
||||||
|
className("editorBody"),
|
||||||
|
inputArea,
|
||||||
|
div( className("cancel button"), Icon("times"),
|
||||||
|
onClick --> { evt => cancel() }
|
||||||
|
),
|
||||||
|
div( className("accept button"), Icon("check"),
|
||||||
|
onClick --> { evt => accept() }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object FormulaEditor
|
||||||
|
{
|
||||||
|
def apply(x: Int, y: Int) =
|
||||||
|
Widgets.register(new FormulaEditor(x, y))
|
||||||
|
}
|
Loading…
Reference in New Issue