Work in progress on the formula editor

main
Oliver Kennedy 2023-01-15 16:25:09 -05:00
parent fc8b106200
commit 6df1b5900c
Signed by: okennedy
GPG Key ID: 3E5F9B3ABD3FDB60
5 changed files with 186 additions and 19 deletions

View File

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

View File

@ -61,7 +61,7 @@ class Table(val id: Identifier)
case SeqReplace(pos, _) => op.map { _.copy(id = columns(pos).id) }
case SeqDelete(_) => op
}
println(s"Update: $op")
// println(s"Update: $op")
opWithValidId.bufferApply(columns)
WebsocketConnection.broadcast(
UpdateTableColumns(id, opWithValidId)
@ -101,13 +101,13 @@ class Table(val id: Identifier)
{
this.x = x
this.y = y
println(s"Position now $x, $y")
// println(s"Position now $x, $y")
WebsocketConnection.broadcast(SetTablePosition(id, x, y))
}
def update(op: TableRequest): Unit =
{
println(s"Table update: $op")
// println(s"Table update: $op")
op match {
case RequestSetTablePosition(_, x, y) => setPosition(x, y)
case RequestUpdateColumns(_, cop) => updateColumns(cop)

View File

@ -167,4 +167,78 @@ body
margin-top: 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;
}
}
}
}
}

View File

@ -28,6 +28,7 @@ import scala.collection.Searching.InsertionPoint
import net.okennedy.cells.SeqReplace
import net.okennedy.cells.SeqInsert
import net.okennedy.cells.SeqDelete
import net.okennedy.cells.widgets.FormulaEditor
class Table(val id: Identifier, connection: Connection)
{
@ -46,6 +47,7 @@ class Table(val id: Identifier, connection: Connection)
this.columns.set(ser.columns)
this.rows.set(ser.rows)
)
startEditing(columns.now()(1).id, rows.now()(1).id)
}
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) }
.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 =
{
// 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)) }
}
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(
className("dataTable"),
@ -262,7 +300,17 @@ class Table(val id: Identifier, connection: Connection)
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
// recompute the position
val (pos, height) =
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, width), _) => x
}.right.get
positionAndHeightOfRow(rowKey)
DragFrame.height(
x.now(),
@ -339,13 +381,7 @@ class Table(val id: Identifier, connection: Connection)
// We can't peek into rowStream without violating ownership, so just
// recompute the position
val (pos, width) =
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
positionAndWidthOfColumn(colKey)
DragFrame.width(
x.now()+pos+Constants.GUTTER_WIDTH,

View File

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