From 0f9f7fb495f2f4ff7537bc1cf4da8fd90a875738 Mon Sep 17 00:00:00 2001 From: updraft0 <121029559+updraft0@users.noreply.github.com> Date: Sat, 15 Feb 2025 19:45:35 +0000 Subject: [PATCH] feat(ui,signatures): add paste shortcut to paste signatures directly when a system is selected --- .../scala/controltower/backend/client.scala | 2 +- .../controltower/page/map/view/MapView.scala | 51 ++++++++++++++++--- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/ui/src/main/scala/controltower/backend/client.scala b/ui/src/main/scala/controltower/backend/client.scala index b847feb..7ab50be 100644 --- a/ui/src/main/scala/controltower/backend/client.scala +++ b/ui/src/main/scala/controltower/backend/client.scala @@ -13,7 +13,7 @@ import sttp.tapir.client.sttp.{SttpClientInterpreter, WebSocketToPipe} import scala.concurrent.Future -class ControlTowerBackend( +final class ControlTowerBackend( // no default hardcoding of backend urls - rely on frontend proxy to route appropriately val backendUrlOpt: Option[Uri] = None, val wsUrlOpt: Option[Uri] = None diff --git a/ui/src/main/scala/controltower/page/map/view/MapView.scala b/ui/src/main/scala/controltower/page/map/view/MapView.scala index 7fb8b78..0d854d9 100644 --- a/ui/src/main/scala/controltower/page/map/view/MapView.scala +++ b/ui/src/main/scala/controltower/page/map/view/MapView.scala @@ -10,6 +10,7 @@ import controltower.db.ReferenceDataStore import controltower.page.map.* import controltower.ui.* import io.laminext.websocket.* +import org.scalajs.dom import org.scalajs.dom.KeyboardEvent import org.updraft0.controltower.constant.SystemId import org.updraft0.controltower.protocol.* @@ -178,7 +179,7 @@ private final class MapView( contextMenuView.view, // A -> add system modalKeyBinding( - "KeyA", + "a", controller.mapRole.map(RoleController.canAddSystem).combineWith(ws.isConnected).map(_ && _), _.map(ev => (ev, ())), (_, onClose) => @@ -208,7 +209,7 @@ private final class MapView( ), // P -> paste system signatures modalKeyBinding( - "KeyP", + "p", controller.mapRole.map(RoleController.canEditSignatures).combineWith(ws.isConnected).map(_ && _), _.filterWith(controller.selectedSystem, _.isDefined) .withCurrentValueOf(controller.selectedSystem) @@ -231,9 +232,24 @@ private final class MapView( ) } ), + // paste system signatures without confirmation // TODO why is event handler only fired on document level + clipboardEventBinding[(SystemId, Instant)]( + documentEvents(_.onPaste), + controller.mapRole.map(RoleController.canEditSignatures).combineWith(ws.isConnected).map(_ && _), + _.filterWith(controller.selectedSystemId, _.isDefined) + .withCurrentValueOf(controller.selectedSystemId, time) + .map((ev, opt, now) => (ev, (opt.get, now))), + { case (clipboard, (systemId, now), onClose) => + parseLines(clipboard) + .flatMap(_.map(parseLineToSignature(_, now)).sequence) + .foreach: sigs => + controller.actionsBus.onNext(MapAction.UpdateSignatures(systemId, false, sigs.toArray)) + onClose.onNext(()) + } + ), // R -> rename system modalKeyBinding( - "KeyR", + "r", controller.mapRole.map(RoleController.canRenameSystem).combineWith(ws.isConnected).map(_ && _), _.filterWith(controller.selectedSystem, _.isDefined) .withCurrentValueOf(controller.selectedSystem) @@ -332,14 +348,22 @@ private final class MapView( ) private def modalKeyBinding[B]( - code: String, + key: String, shouldEnable: Signal[Boolean], compose: EventStream[KeyboardEvent] => EventStream[(KeyboardEvent, B)], - action: (B, Observer[Unit]) => Unit + action: (B, Observer[Unit]) => Unit, + ctrlOn: Boolean = false, + shiftOn: Boolean = false, + metaOn: Boolean = false ) = documentEvents( _.onKeyDown - .filter(ev => !ev.repeat && ev.code == code && !ev.ctrlKey && !ev.shiftKey && !ev.metaKey) + .filter(ev => + !ev.repeat && ev.key == key && + ((ctrlOn && ev.ctrlKey) || (!ctrlOn && !ev.ctrlKey)) && + ((shiftOn && ev.shiftKey) || (!shiftOn && !ev.shiftKey)) && + ((metaOn && ev.metaKey) || (!metaOn && !ev.metaKey)) + ) ).compose(es => compose(es).filterWith(Modal.Shown.signal.map(!_)).filterWith(shouldEnable).filterWith(notInKeyHandler.signal) ) --> { (ev, b) => @@ -348,6 +372,21 @@ private final class MapView( action(b, Observer(_ => inKeyHandler.set(false))) } + private def clipboardEventBinding[B]( + event: EventStream[dom.ClipboardEvent], + shouldEnable: Signal[Boolean], + compose: EventStream[dom.ClipboardEvent] => EventStream[(dom.ClipboardEvent, B)], + action: (String, B, Observer[Unit]) => Unit + ) = + event.compose(es => + compose(es).filterWith(Modal.Shown.signal.map(!_)).filterWith(shouldEnable).filterWith(notInKeyHandler.signal) + ) --> { (ev, b) => + ev.preventDefault() + inKeyHandler.set(true) + val s = ev.clipboardData.getData("text") + action(s, b, Observer(_ => inKeyHandler.set(false))) + } + object MapView: import org.updraft0.controltower.protocol.jsoncodec.given import sttp.client3.UriContext