diff --git a/TODO.md b/TODO.md index d50f6fe..b93f375 100644 --- a/TODO.md +++ b/TODO.md @@ -33,4 +33,3 @@ Issues from JavaFX: https://bugs.openjdk.java.net/browse/JDK-8251240 workaround for that: `-Djdk.gtk.version=2` - diff --git a/build.sbt b/build.sbt index 1fb2985..71f07de 100644 --- a/build.sbt +++ b/build.sbt @@ -115,9 +115,9 @@ lazy val editor = (crossProject(JVMPlatform, JSPlatform) in file(".") / "editor" "io.circe" %%% "circe-generic", "io.circe" %%% "circe-parser" ).map(_ % circeVersion), - libraryDependencies += "org.typelevel" %%% "cats-effect" % "2.5.1", + libraryDependencies += "org.typelevel" %%% "cats-effect" % "3.2.9", libraryDependencies += "com.lihaoyi" %%% "scalatags" % "0.9.4", - libraryDependencies += "com.codecommit" %% "cats-effect-testing-scalatest" % "0.5.4" % Test + libraryDependencies += "org.typelevel" %% "cats-effect-testing-scalatest" % "1.3.0" % Test ).dependsOn(core, graphml, json, cats) lazy val editorJS = editor.js.settings( @@ -138,13 +138,13 @@ lazy val osName = System.getProperty("os.name") match { lazy val javaFXModules = Seq("base", "controls", "fxml", "graphics", "media", "swing", "web") lazy val editorJVM = editor.jvm.settings( - libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19", + libraryDependencies += "org.scalafx" %% "scalafx" % "15.0.1-R21", libraryDependencies += "org.fxmisc.richtext" % "richtextfx" % "0.10.5", libraryDependencies += "org.apache.logging.log4j" % "log4j-api" % "2.14.0", libraryDependencies += "org.apache.logging.log4j" % "log4j-core" % "2.14.0", - libraryDependencies += "org.apache.xmlgraphics" % "batik-rasterizer" % "1.13", + libraryDependencies += "org.apache.xmlgraphics" % "batik-rasterizer" % "1.14", libraryDependencies ++= javaFXModules.map( m => - "org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName + "org.openjfx" % s"javafx-$m" % "15.0.1" classifier osName ) ) diff --git a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorInstanceJs.scala b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorInstanceJs.scala index f738b68..8817238 100644 --- a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorInstanceJs.scala +++ b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorInstanceJs.scala @@ -1,12 +1,13 @@ package com.flowtick.graphs.editor import scala.scalajs.js.annotation.JSExport +import cats.effect.unsafe.implicits.global class EditorInstanceJs(val messageBus: EditorMessageBus) { @JSExport def execute(commandJson: String): Unit = io.circe.parser.decode[EditorCommand](commandJson) match { - case Right(command) => messageBus.publish(command).unsafeRunSync() + case Right(command) => messageBus.publish(command).unsafeToFuture() case Left(error) => println("unable to execute command:", error) } diff --git a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMainJs.scala b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMainJs.scala index a89317d..c9a7669 100644 --- a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMainJs.scala +++ b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMainJs.scala @@ -1,11 +1,14 @@ package com.flowtick.graphs.editor import cats.effect.IO +import cats.effect.unsafe.implicits.global + import org.scalajs.dom.Event import scala.scalajs.js import scala.scalajs.js.{JSON, undefined} import scala.scalajs.js.annotation.{JSExport, JSExportTopLevel} +import scala.concurrent.Future @JSExportTopLevel("graphs") object EditorMainJs extends EditorMain { @@ -16,7 +19,7 @@ object EditorMainJs extends EditorMain { optionsObj: js.UndefOr[js.Object], menuContainerId: js.UndefOr[String] = undefined, paletteContainerId: js.UndefOr[String] = undefined - ): EditorInstanceJs = (for { + ): Future[EditorInstanceJs] = (for { options <- optionsObj.toOption .map(obj => IO.fromEither(EditorConfiguration.decode(obj.toString))) .getOrElse(IO.pure(EditorConfiguration())) @@ -47,7 +50,7 @@ object EditorMainJs extends EditorMain { ), IO.pure ) - .unsafeRunSync() + .unsafeToFuture() def main(args: Array[String]): Unit = { println("graphs loaded...") diff --git a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMenuJs.scala b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMenuJs.scala index 389da5b..a22d8da 100644 --- a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMenuJs.scala +++ b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorMenuJs.scala @@ -12,6 +12,7 @@ import scalatags.JsDom.all._ import scala.scalajs.js class EditorMenuJs(menuContainerId: String)(val messageBus: EditorMessageBus) extends EditorMenu { + import cats.effect.unsafe.implicits.global override def order: Double = 0.6 @@ -118,7 +119,7 @@ class EditorMenuJs(menuContainerId: String)(val messageBus: EditorMessageBus) ex reader.onload = (_: Event) => messageBus .publish(Load(reader.result.toString, format)) - .unsafeRunSync() + .unsafeToFuture() reader.readAsText(file) } diff --git a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPageJs.scala b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPageJs.scala index f3227fb..062aea9 100644 --- a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPageJs.scala +++ b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPageJs.scala @@ -4,6 +4,7 @@ import cats.effect.IO import com.flowtick.graphs.editor.view._ import org.scalajs.dom import org.scalajs.dom.raw._ +import cats.effect.unsafe.implicits.global object EditorDomEventLike extends EventLike[Event, dom.Element] { override def target(event: Event): dom.Element = @@ -71,46 +72,56 @@ object EditorPageJs { renderer.graphSVG.root.addEventListener( "mousedown", (e: MouseEvent) => { - page.startDrag(e) match { - case Some(_) => // we already have a selection - case None => - page.click(e).foreach { clicked => - handleSelect(clicked)(e.ctrlKey).unsafeRunSync() - } - } + page + .startDrag(e) + .flatMap { + case Some(_) => IO.unit // we already have a selection + case None => + page.click(e).flatMap { + case Some(clicked) => handleSelect(clicked)(e.ctrlKey) + case None => IO.unit + } + } + .unsafeToFuture() } ) renderer.graphSVG.root.addEventListener("mousemove", page.drag) renderer.graphSVG.root.addEventListener( "mouseup", - (e: MouseEvent) => { - val drag = page.endDrag(e) - handleDrag(drag).unsafeRunSync() - - // handle up as a selection if we did not drag more the one pixel - drag match { - case Some(drag) if Math.abs(drag.deltaX) < 2 && Math.abs(drag.deltaY) < 2 => - page.click(e).foreach { element => - handleSelect(element)(false).unsafeRunSync() + (e: MouseEvent) => + { + for { + drag <- page.endDrag(e) + _ <- handleDrag(drag).attempt + result <- drag match { + case Some(drag) if Math.abs(drag.deltaX) < 2 && Math.abs(drag.deltaY) < 2 => + page.click(e).flatMap { + case Some(element) => handleSelect(element)(false) + case None => IO.unit + } + case _ => IO.unit } - case _ => - } - } + } yield result + }.unsafeToFuture() ) renderer.graphSVG.root.addEventListener( "mouseleave", - (e: MouseEvent) => { - page.stopPan(e) - handleDrag(page.endDrag(e)) - } + (e: MouseEvent) => + { + for { + _ <- page.stopPan(e) + drag <- page.endDrag(e) + result <- handleDrag(drag) + } yield result + }.unsafeToFuture() ) renderer.graphSVG.root.addEventListener( "dblclick", (e: MouseEvent) => { - handleDoubleClick(e).unsafeRunSync() + handleDoubleClick(e).unsafeToFuture() } ) diff --git a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJs.scala b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJs.scala index b5ee8a9..4a27a73 100644 --- a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJs.scala +++ b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJs.scala @@ -6,6 +6,7 @@ import com.flowtick.graphs.style.ImageSpec import org.scalajs.dom.html.{Button, Div, UList} import org.scalajs.dom.raw.{Event, HTMLElement} import scalatags.JsDom.all._ +import cats.effect.unsafe.implicits.global class EditorPaletteJs(paletteElementId: String)( val messageBus: EditorMessageBus @@ -119,7 +120,7 @@ class EditorPaletteJs(paletteElementId: String)( onclick := ((_: Event) => messageBus .publish(EditorToggle(EditorToggle.paletteKey, Some(false))) - .unsafeRunSync() + .unsafeToFuture() ) ).render diff --git a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesHtml.scala b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesHtml.scala index 7d2efa3..e4bf2f8 100644 --- a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesHtml.scala +++ b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesHtml.scala @@ -9,12 +9,13 @@ import org.scalajs.dom.raw.{Event, HTMLElement} import scalatags.{JsDom, generic} import scala.scalajs.js +import cats.implicits._ final case class PropertyFormGroupJs( property: PropertySpec, container: JsDom.TypedTag[Div], - init: () => Unit, - set: Json => Unit + init: IO[Unit], + set: Json => IO[Unit] ) extends PropertyFormGroup final case class PropertyForm(groups: List[PropertyFormGroupJs], html: Form) @@ -30,15 +31,12 @@ object EditorPropertiesHtml { display := "none" ).render - def createPropertyForm(properties: List[PropertySpec]): PropertyForm = { - val groups = properties.sortBy(_.order).map(propertyGroup) - - val propertyFormHtml = form( + def createPropertyForm(properties: List[PropertySpec]): IO[PropertyForm] = for { + groups <- properties.sortBy(_.order).map(propertyGroup).sequence + propertyFormHtml = form( id := "properties" ).apply(groups.map(_.container): _*).render - - PropertyForm(groups, propertyFormHtml) - } + } yield PropertyForm(groups, propertyFormHtml) def propertyContainers( property: PropertySpec, @@ -91,7 +89,7 @@ object EditorPropertiesHtml { ) } - def propertyGroup(property: PropertySpec): PropertyFormGroupJs = + def propertyGroup(property: PropertySpec): IO[PropertyFormGroupJs] = property.inputType match { case NumberInput => lazy val numberInput = input( @@ -103,20 +101,24 @@ object EditorPropertiesHtml { val (_, propertyContainer) = propertyContainers(property, numberInput) - PropertyFormGroupJs( - property, - propertyContainer, - init = () => { - numberInput.onchange = _ => - property.handler( - JsonValue(Json.fromDoubleOrString(numberInput.value.toDouble)) + IO( + PropertyFormGroupJs( + property, + propertyContainer, + init = IO { + numberInput.onchange = _ => + property.handler( + JsonValue(Json.fromDoubleOrString(numberInput.value.toDouble)) + ) + }, + set = json => + IO(numberInput.value = + NumberInput + .fromJson(property.key, json) + .map(_.toString) + .getOrElse("") ) - }, - set = json => - numberInput.value = NumberInput - .fromJson(property.key, json) - .map(_.toString) - .getOrElse("") + ) ) case BooleanInput => @@ -128,17 +130,19 @@ object EditorPropertiesHtml { val (_, propertyContainer) = propertyContainers(property, checkbox) - PropertyFormGroupJs( - property, - propertyContainer, - init = () => { - checkbox.onchange = _ => - property.handler( - JsonValue(if (checkbox.checked) Json.True else Json.False) - ) - }, - set = - json => checkbox.checked = BooleanInput.fromJson(property.key, json).getOrElse(false) + IO( + PropertyFormGroupJs( + property, + propertyContainer, + init = IO { + checkbox.onchange = _ => + property.handler( + JsonValue(if (checkbox.checked) Json.True else Json.False) + ) + }, + set = json => + IO(checkbox.checked = BooleanInput.fromJson(property.key, json).getOrElse(false)) + ) ) case IntegerInput => @@ -153,20 +157,24 @@ object EditorPropertiesHtml { val (_, propertyContainer) = propertyContainers(property, integerInput) - PropertyFormGroupJs( - property, - propertyContainer, - init = () => { - integerInput.onchange = _ => - property.handler( - JsonValue(Json.fromInt(integerInput.value.toInt)) + IO( + PropertyFormGroupJs( + property, + propertyContainer, + init = IO { + integerInput.onchange = _ => + property.handler( + JsonValue(Json.fromInt(integerInput.value.toInt)) + ) + }, + set = json => + IO(integerInput.value = + IntegerInput + .fromJson(property.key, json) + .map(_.toString) + .getOrElse("") ) - }, - set = json => - integerInput.value = IntegerInput - .fromJson(property.key, json) - .map(_.toString) - .getOrElse("") + ) ) case TextInput | LabelInputType | JsonInputType => @@ -183,7 +191,7 @@ object EditorPropertiesHtml { property.handler(JsonValue(Json.fromString(textAreaInput.value))) } - lazy val editor: Either[TextArea, AceEditor] = + lazy val createEditor: IO[Either[TextArea, AceEditor]] = (property.highlight match { case Some(mode) => IO { @@ -225,31 +233,36 @@ object EditorPropertiesHtml { textAreaInit Left(textAreaInput) } - }).unsafeRunSync() - - PropertyFormGroupJs( - property, - propertyContainer, - init = () => editor, - set = json => { - val newValue = TextInput.fromJson(property.key, json).getOrElse("") - editor match { - case Right(ace) => ace.session.setValue(newValue) - case Left(text) => - text.value = newValue - text.rows = newValue.split("\n").length - } - } + }) + + createEditor.map(editor => + PropertyFormGroupJs( + property, + propertyContainer, + init = IO.unit, + set = json => + IO { + val newValue = TextInput.fromJson(property.key, json).getOrElse("") + editor match { + case Right(ace) => ace.session.setValue(newValue) + case Left(text) => + text.value = newValue + text.rows = newValue.split("\n").length + } + } + ) ) case other => - PropertyFormGroupJs( - property, - div( - pre(s"unsupported property type $other", style := "display: none") - ), - () => (), - _ => () + IO( + PropertyFormGroupJs( + property, + div( + pre(s"unsupported property type $other", style := "display: none") + ), + IO.unit, + _ => IO.unit + ) ) } diff --git a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJs.scala b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJs.scala index 76eada7..dc057c4 100644 --- a/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJs.scala +++ b/editor/js/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJs.scala @@ -20,7 +20,7 @@ class EditorPropertiesJs(containerId: String)(val messageBus: EditorMessageBus) properties: List[PropertySpec], elementProperties: ElementProperties ): IO[List[PropertyFormGroup]] = for { - newForm <- IO(EditorPropertiesHtml.createPropertyForm(properties)) + newForm <- EditorPropertiesHtml.createPropertyForm(properties) _ <- IO { if (EditorPropertiesHtml.propertiesBody.children.length == 0) { diff --git a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphNodeFx.scala b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphNodeFx.scala index 9e04f18..2ea9564 100644 --- a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphNodeFx.scala +++ b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphNodeFx.scala @@ -13,6 +13,8 @@ import scalafx.scene.text.{Text, TextAlignment} import scalafx.scene.transform.Affine import scalafx.scene.{Group, Node} +import cats.effect.unsafe.implicits.global + class EditorGraphNodeFx( nodeId: String, geometry: Geometry, @@ -118,8 +120,7 @@ class EditorGraphNodeFx( onMousePressed = new EventHandler[MouseEvent] { override def handle(event: MouseEvent): Unit = { - handleSelect(ElementRef(nodeId, NodeType))(event.isControlDown) - .unsafeRunSync() + handleSelect(ElementRef(nodeId, NodeType))(event.isControlDown).unsafeRunSync() } } diff --git a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphPane.scala b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphPane.scala index 049f724..cb90b5c 100644 --- a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphPane.scala +++ b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorGraphPane.scala @@ -1,6 +1,7 @@ package com.flowtick.graphs.editor import cats.effect.IO +import cats.effect.unsafe.implicits.global import com.flowtick.graphs import com.flowtick.graphs._ import com.flowtick.graphs.editor.util.DrawUtil @@ -8,6 +9,7 @@ import com.flowtick.graphs.editor.view.GraphElement import com.flowtick.graphs.layout.{DefaultGeometry, PointSpec} import javafx.event.EventHandler import javafx.scene.input.{MouseEvent, ScrollEvent} +import scalafx.application.Platform import scalafx.scene.layout.{BorderPane, Pane, Priority} import scalafx.scene.paint.Color import scalafx.scene.shape.Line._ @@ -73,7 +75,7 @@ class EditorGraphPane(layout: BorderPane)( event.consume() if (event.getClickCount == 2) { - handleDoubleClick(()).unsafeRunSync() + handleDoubleClick(()).unsafeToFuture() } if (!event.isPrimaryButtonDown) { @@ -185,7 +187,7 @@ class EditorGraphPane(layout: BorderPane)( strokeWidth = 0.5 stroke = Color.Black } - children.add(cirlce) + Platform.runLater(children.add(cirlce)) }) visible = false @@ -208,8 +210,10 @@ class EditorGraphPane(layout: BorderPane)( } } - selectGroup.children.add(selectLine) - selectGroup.children.add(points) + Platform.runLater { + selectGroup.children.add(selectLine) + selectGroup.children.add(points) + } val label = new Text() { text = textValue @@ -228,13 +232,12 @@ class EditorGraphPane(layout: BorderPane)( selectLine.onMousePressed = new EventHandler[MouseEvent] { override def handle(t: MouseEvent): Unit = { - handleSelect(ElementRef(edge.id, EdgeType))(t.isControlDown) - .unsafeRunSync() + handleSelect(ElementRef(edge.id, EdgeType))(t.isControlDown).unsafeRunSync() selectGroup.visible = true } } - group.children.add(edgeGroup) + Platform.runLater(group.children.add(edgeGroup)) JFXElement( ElementRef(edge.id, EdgeType), @@ -262,8 +265,11 @@ class EditorGraphPane(layout: BorderPane)( node.value.label, shape )(transformation, handleSelect, handleDrag, handleDoubleClick) - group.children.add(graphNode) - group.children.add(graphNode.selectRect) + + Platform.runLater { + group.children.add(graphNode) + group.children.add(graphNode.selectRect) + } Some( JFXElement( @@ -288,8 +294,10 @@ class EditorGraphPane(layout: BorderPane)( } override def deleteElement(element: GraphElement[Node]): IO[Unit] = IO { - group.children.remove(element.group) - group.children.remove(element.selectElem) + Platform.runLater { + group.children.remove(element.group) + element.selectElem.foreach(group.children.remove(_)) + } } override def resetTransformation: IO[Unit] = IO { diff --git a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMainJvm.scala b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMainJvm.scala index d173826..2054305 100644 --- a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMainJvm.scala +++ b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMainJvm.scala @@ -1,16 +1,17 @@ package com.flowtick.graphs.editor -import java.io.File -import java.net.URL - import cats.effect.IO +import cats.effect.unsafe.IORuntime +import cats.effect.unsafe.implicits.global import scalafx.application.JFXApp import scalafx.scene.image.Image import scalafx.scene.layout.BorderPane import scalafx.scene.{Group, Scene} -object EditorMainJvm extends JFXApp with EditorMain { +import java.io.File +import java.net.URL +object EditorMainJvm extends JFXApp with EditorMain { lazy val pageRoot = new Group lazy val editorLayout = new BorderPane { @@ -96,5 +97,5 @@ object EditorMainJvm extends JFXApp with EditorMain { } } yield editor - initEditor.unsafeRunSync() + initEditor.attempt.unsafeRunSync().foreach(println) } diff --git a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMenuJavaFx.scala b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMenuJavaFx.scala index 93e6723..a4e381f 100644 --- a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMenuJavaFx.scala +++ b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorMenuJavaFx.scala @@ -1,11 +1,12 @@ package com.flowtick.graphs.editor -import java.io.{FileInputStream, FileOutputStream} - +import java.io.{File, FileInputStream, FileOutputStream} import cats.effect.IO +import cats.effect.unsafe.implicits.global import com.flowtick.graphs._ import javafx.event.EventHandler import javafx.scene.input.KeyEvent +import scalafx.application.Platform import scalafx.scene.control.{Menu, MenuBar, MenuItem} import scalafx.scene.input.{KeyCode, KeyCodeCombination, KeyCombination} import scalafx.scene.layout.BorderPane @@ -36,30 +37,35 @@ class EditorMenuJavaFx( bar } - def openFile: IO[Unit] = IO { - val fileChooser = new FileChooser() - fileChooser.setTitle("Open File") + def openFile: IO[Unit] = IO + .async_ { (cb: Either[Throwable, Option[File]] => Unit) => + val fileChooser = new FileChooser() + fileChooser.setTitle("Open File") - val graphmlFilter = - new FileChooser.ExtensionFilter("graphml files (*.graphml)", "*.graphml") - val jsonFilter = - new FileChooser.ExtensionFilter("JSON files (*.json)", "*.json") + val graphmlFilter = + new FileChooser.ExtensionFilter("graphml files (*.graphml)", "*.graphml") + val jsonFilter = + new FileChooser.ExtensionFilter("JSON files (*.json)", "*.json") - fileChooser.getExtensionFilters.add(graphmlFilter) - fileChooser.getExtensionFilters.add(jsonFilter) + fileChooser.getExtensionFilters.add(graphmlFilter) + fileChooser.getExtensionFilters.add(jsonFilter) - Option(fileChooser.showOpenDialog(stage)) - }.attempt.flatMap { - case Right(Some(file)) => { - val format = file.getAbsolutePath.split("\\.").lastOption match { - case Some("json") => JsonFormat - case _ => GraphMLFormat + Platform.runLater { + cb(Right(Option(fileChooser.showOpenDialog(stage)))) } - loadGraph(file.getAbsolutePath, format) } - case Right(None) => IO(println("no file selected")) - case Left(error) => IO.raiseError(error) - } + .attempt + .flatMap { + case Right(Some(file)) => { + val format = file.getAbsolutePath.split("\\.").lastOption match { + case Some("json") => JsonFormat + case _ => GraphMLFormat + } + loadGraph(file.getAbsolutePath, format) + } + case Right(None) => IO(println("no file selected")) + case Left(error) => IO.raiseError(error) + } case class ShortCut( keyCode: Option[KeyCode] = None, @@ -102,15 +108,19 @@ class EditorMenuJavaFx( } override def triggerFileOpen: Any => Unit = _ => { - openFile.unsafeRunSync() + openFile.unsafeToFuture() } override def handleExported(exported: ExportedGraph): IO[Unit] = for { - file <- IO { - new FileChooser() { - title = s"Save as ${exported.format.`extension`}" - }.showSaveDialog(stage) - }.map(Option(_)) + file <- IO + .async_ { (callback: Either[Throwable, File] => Unit) => + Platform.runLater { + callback(Right(new FileChooser() { + title = s"Save as ${exported.format.`extension`}" + }.showSaveDialog(stage))) + } + } + .map(Option(_)) _ <- file match { case Some(path) => IO { diff --git a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJavaFx.scala b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJavaFx.scala index c1948ff..b949e25 100644 --- a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJavaFx.scala +++ b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPaletteJavaFx.scala @@ -1,6 +1,7 @@ package com.flowtick.graphs.editor import cats.effect.IO + import com.flowtick.graphs.editor.feature.PaletteFeature import scalafx.geometry.Insets import scalafx.scene.control.{Accordion, TitledPane, Tooltip} @@ -42,7 +43,7 @@ class EditorPaletteJavaFx(val messageBus: EditorMessageBus, layout: BorderPane) ) ) - model.palette.stencilGroups.zipWithIndex.foreach { case (group, index) => + model.palette.stencilGroups.zipWithIndex.flatMap { case (group, index) => val accordion = new Accordion() pane.center = accordion @@ -60,7 +61,7 @@ class EditorPaletteJavaFx(val messageBus: EditorMessageBus, layout: BorderPane) } groupPane.content = groupContent - group.items.foreach { stencil => + group.items.map { stencil => val stencilImage = stencil.image .map(imageSpec => ImageLoaderFx @@ -68,37 +69,39 @@ class EditorPaletteJavaFx(val messageBus: EditorMessageBus, layout: BorderPane) ) .getOrElse(fallBackImage) - val stencilView = new ImageView { - image = stencilImage.unsafeRunSync() - fitWidth = 32 - fitHeight = 32 - } + val stencilView = stencilImage.map(theImage => + new ImageView { + image = theImage + fitWidth = 32 + fitHeight = 32 + } + ) val tooltip = new Tooltip { text = stencil.title } - Tooltip.install(stencilView, tooltip) + stencilView.map { view => + Tooltip.install(view, tooltip) - val stencilGroup = new VBox { - maxWidth = 50 - maxHeight = 50 + val stencilGroup = new VBox { + maxWidth = 50 + maxHeight = 50 - children.add(stencilView) + children.add(view) - onMouseClicked = e => { - selectPaletteItem(stencil) + onMouseClicked = e => { + selectPaletteItem(stencil) - if (e.getClickCount == 2) { - createFromStencil(stencil) + if (e.getClickCount == 2) { + createFromStencil(stencil) + } } } + groupContent.children.add(stencilGroup) } - - groupContent.children.add(stencilGroup) } } - } } yield () } diff --git a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJavaFx.scala b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJavaFx.scala index f37344e..67edf29 100644 --- a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJavaFx.scala +++ b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorPropertiesJavaFx.scala @@ -1,8 +1,10 @@ package com.flowtick.graphs.editor import cats.effect.IO +import cats.effect.unsafe.implicits.global import io.circe.Json import javafx.beans.value.{ChangeListener, ObservableValue} import javafx.scene.paint.Color +import scalafx.application.Platform import scalafx.geometry.{Insets, Pos} import scalafx.scene.control._ import scalafx.scene.layout._ @@ -10,8 +12,8 @@ import scalafx.scene.text.{Font, FontWeight} case class PropertyFormGroupFx( property: PropertySpec, - init: () => Unit, - set: Json => Unit + init: IO[Unit], + set: Json => IO[Unit] ) extends PropertyFormGroup class EditorPropertiesJavaFx( @@ -50,8 +52,7 @@ class EditorPropertiesJavaFx( elementProperties: ElementProperties ): IO[List[PropertyFormGroup]] = for { groups <- IO { - - pane.children.clear() + Platform.runLater(pane.children.clear()) val closeLabel = new Label("X") { minHeight = 30.0 @@ -61,7 +62,7 @@ class EditorPropertiesJavaFx( closeLabel.onMouseClicked = _ => { messageBus .publish(EditorToggle(EditorToggle.editKey, Some(false))) - .unsafeRunSync() + .unsafeToFuture() } val grid = new GridPane() @@ -74,7 +75,10 @@ class EditorPropertiesJavaFx( AnchorPane.setLeftAnchor(grid, 10.0) grid.add(closeLabel, 2, 0) - pane.children.add(grid) + + Platform.runLater { + pane.children.add(grid) + } properties.sortBy(_.order).zipWithIndex.map { case (property, index) => propertyGroup(property, index + 1)(grid) @@ -99,7 +103,7 @@ class EditorPropertiesJavaFx( PropertyFormGroupFx( property, - init = () => { + init = IO { grid.add(label(property), 0, index) grid.add(input, 1, index) @@ -115,13 +119,14 @@ class EditorPropertiesJavaFx( } }) }, - set = (newJson: Json) => { - val newText = NumberInput - .fromJson(property.key, newJson) - .map(_.toString) - .getOrElse("") - input.setText(newText) - } + set = (newJson: Json) => + IO { + val newText = NumberInput + .fromJson(property.key, newJson) + .map(_.toString) + .getOrElse("") + input.setText(newText) + } ) case BooleanInput => @@ -129,7 +134,7 @@ class EditorPropertiesJavaFx( PropertyFormGroupFx( property, - init = () => { + init = IO { grid.add(label(property), 0, index) grid.add(input, 1, index) @@ -143,9 +148,10 @@ class EditorPropertiesJavaFx( } }) }, - set = (newJson: Json) => { - input.selected = BooleanInput.fromJson(property.key, newJson).getOrElse(false) - } + set = (newJson: Json) => + IO { + input.selected = BooleanInput.fromJson(property.key, newJson).getOrElse(false) + } ) case IntegerInput => @@ -153,7 +159,7 @@ class EditorPropertiesJavaFx( PropertyFormGroupFx( property, - init = () => { + init = IO { grid.add(label(property), 0, index) grid.add(input, 1, index) @@ -167,13 +173,14 @@ class EditorPropertiesJavaFx( } }) }, - set = (newJson: Json) => { - val newText = IntegerInput - .fromJson(property.key, newJson) - .map(_.toString) - .getOrElse("") - input.setText(newText) - } + set = (newJson: Json) => + IO { + val newText = IntegerInput + .fromJson(property.key, newJson) + .map(_.toString) + .getOrElse("") + input.setText(newText) + } ) case ColorInputType => @@ -181,9 +188,11 @@ class EditorPropertiesJavaFx( PropertyFormGroupFx( property, - () => { - grid.add(label(property), 0, index) - grid.add(input, 1, index) + init = IO { + Platform.runLater { + grid.add(label(property), 0, index) + grid.add(input, 1, index) + } input.value.addListener(new ChangeListener[Color] { override def changed( @@ -195,9 +204,10 @@ class EditorPropertiesJavaFx( } }) }, - (newJson: Json) => { - input.value = newJson.asString.map(Color.web).getOrElse(Color.WHITE) - } + (newJson: Json) => + IO { + input.value = newJson.asString.map(Color.web).getOrElse(Color.WHITE) + } ) case _ => @@ -205,9 +215,11 @@ class EditorPropertiesJavaFx( PropertyFormGroupFx( property, - () => { - grid.add(label(property), 0, index) - grid.add(input, 1, index) + IO { + Platform.runLater { + grid.add(label(property), 0, index) + grid.add(input, 1, index) + } input.text.addListener(new ChangeListener[String] { override def changed( @@ -224,15 +236,16 @@ class EditorPropertiesJavaFx( } }) }, - (newJson: Json) => { - val newText = if (property.inputType == JsonInputType) { - newJson.spaces2 - } else TextInput.fromJson(property.key, newJson).getOrElse("") - - val rows = newText.split("\n").length - input.text.value = newText - input.setPrefRowCount(if (rows == 0) 1 else rows) - } + (newJson: Json) => + IO { + val newText = if (property.inputType == JsonInputType) { + newJson.spaces2 + } else TextInput.fromJson(property.key, newJson).getOrElse("") + + val rows = newText.split("\n").length + input.text.value = newText + input.setPrefRowCount(if (rows == 0) 1 else rows) + } ) } diff --git a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorViewJavaFx.scala b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorViewJavaFx.scala index 3604084..cfaaf45 100644 --- a/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorViewJavaFx.scala +++ b/editor/jvm/src/main/scala/com/flowtick/graphs/editor/EditorViewJavaFx.scala @@ -1,8 +1,8 @@ package com.flowtick.graphs.editor import cats.effect.IO -import cats.implicits.toFlatMapOps import javafx.scene.input.MouseEvent +import scalafx.application.Platform import scalafx.scene.Node import scalafx.scene.layout.BorderPane @@ -10,7 +10,7 @@ class EditorViewJavaFx(bus: EditorMessageBus, layout: BorderPane) extends EditorView[Node, MouseEvent] { override def createPage: IO[Page[Node, MouseEvent]] = IO( new EditorGraphPane(layout)(handleSelect, handleDrag, handleDoubleClick) - ).flatTap(pane => IO(layout.setCenter(pane))) + ).flatTap(pane => IO(Platform.runLater(layout.setCenter(pane)))) override def messageBus: EditorMessageBus = bus } diff --git a/editor/jvm/src/test/scala/com/flowtick/graphs/EditorBaseSpec.scala b/editor/jvm/src/test/scala/com/flowtick/graphs/EditorBaseSpec.scala index 0bc149e..fd1d207 100644 --- a/editor/jvm/src/test/scala/com/flowtick/graphs/EditorBaseSpec.scala +++ b/editor/jvm/src/test/scala/com/flowtick/graphs/EditorBaseSpec.scala @@ -1,16 +1,23 @@ package com.flowtick.graphs import cats.effect.IO -import cats.effect.concurrent.Ref +import cats.effect.kernel.Ref +import cats.effect.unsafe.implicits.global import com.flowtick.graphs.editor._ import com.flowtick.graphs.editor.view.SVGRendererJvm import org.apache.logging.log4j.{LogManager, Logger} +import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers import java.io.FileOutputStream -trait EditorBaseSpec extends AnyFlatSpec with Matchers with EditorMain { self => +trait EditorBaseSpec + extends AnyFlatSpec + with Matchers + with EditorMain + with ScalaFutures + with IntegrationPatience { self => val log: Logger = LogManager.getLogger(getClass) val lastExported: Ref[IO, Option[ExportedGraph]] = Ref.unsafe(None) @@ -22,7 +29,7 @@ trait EditorBaseSpec extends AnyFlatSpec with Matchers with EditorMain { self => List( testView(bus) ) - )(EditorConfiguration()).flatMap(editor => IO(f(editor))).unsafeRunSync() + )(EditorConfiguration()).flatMap(editor => IO(f(editor))).unsafeToFuture().futureValue protected def testView(bus: EditorMessageBus): EditorComponent = new EditorComponent { diff --git a/editor/jvm/src/test/scala/com/flowtick/graphs/ModelUpdateFeatureSpec.scala b/editor/jvm/src/test/scala/com/flowtick/graphs/ModelUpdateFeatureSpec.scala index 684e99c..b8c7b69 100644 --- a/editor/jvm/src/test/scala/com/flowtick/graphs/ModelUpdateFeatureSpec.scala +++ b/editor/jvm/src/test/scala/com/flowtick/graphs/ModelUpdateFeatureSpec.scala @@ -1,6 +1,7 @@ package com.flowtick.graphs import cats.effect.IO +import cats.effect.unsafe.implicits.global import com.flowtick.graphs.editor._ import com.flowtick.graphs.layout.{DefaultGeometry, EdgePath} @@ -27,7 +28,7 @@ class ModelUpdateFeatureSpec extends EditorBaseSpec { IO.fromOption(_)(new IllegalStateException("exported graph not set")) ) loaded <- editor.bus.publish(Load(exported.value, JsonFormat)) - } yield loaded).unsafeRunSync() + } yield loaded).unsafeToFuture().futureValue loaded.model.graph.nodes should have size (2) loaded.model.graph.edges should have size (1) @@ -56,7 +57,7 @@ class ModelUpdateFeatureSpec extends EditorBaseSpec { IO.fromOption(_)(new IllegalStateException("exported graph not set")) ) loaded <- editor.bus.publish(Load(exported.value, JsonFormat)) - } yield loaded).unsafeRunSync() + } yield loaded).unsafeToFuture().futureValue loaded.model.styleSheet .getNodeStyle(Some("1")) diff --git a/editor/jvm/src/test/scala/com/flowtick/graphs/RoutingFeatureSpec.scala b/editor/jvm/src/test/scala/com/flowtick/graphs/RoutingFeatureSpec.scala index 7c55db6..117b997 100644 --- a/editor/jvm/src/test/scala/com/flowtick/graphs/RoutingFeatureSpec.scala +++ b/editor/jvm/src/test/scala/com/flowtick/graphs/RoutingFeatureSpec.scala @@ -1,8 +1,10 @@ package com.flowtick.graphs +import cats.effect.unsafe.implicits.global import com.flowtick.graphs.editor._ +import org.scalatest.concurrent.ScalaFutures -class RoutingFeatureSpec extends EditorBaseSpec { +class RoutingFeatureSpec extends EditorBaseSpec with ScalaFutures { "Routing" should "update edges" in withEditor { editor => val (firstNodeId, secondNodeId) = ("1", "2") val edgeId = "e1" @@ -22,7 +24,7 @@ class RoutingFeatureSpec extends EditorBaseSpec { MoveTo(ElementRef(firstNodeId, NodeType), 110.0, 110.0) ) _ <- editor.bus.publish(Export(JsonFormat)) - } yield (added, moved)).unsafeRunSync() + } yield (added, moved)).unsafeToFuture().futureValue moved.model.graph.edgeIds should have size (1) diff --git a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMain.scala b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMain.scala index 8fc6c60..56e41a2 100644 --- a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMain.scala +++ b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMain.scala @@ -1,11 +1,11 @@ package com.flowtick.graphs.editor import cats.effect.IO -import cats.effect.concurrent.Ref import cats.implicits._ import com.flowtick.graphs.Graph import com.flowtick.graphs.editor.feature.{ModelUpdateFeature, RoutingFeature, UndoFeature} import com.flowtick.graphs.style._ +import cats.effect.kernel.Ref final case class EditorInstance( bus: EditorMessageBus, diff --git a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMenuSpec.scala b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMenuSpec.scala index 9053254..fb6a37f 100644 --- a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMenuSpec.scala +++ b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMenuSpec.scala @@ -3,6 +3,7 @@ package com.flowtick.graphs.editor import java.util.UUID import cats.effect.IO +import cats.effect.unsafe.implicits.global import cats.implicits._ import com.flowtick.graphs.graphml.{GraphML, GraphMLGraph} @@ -115,48 +116,48 @@ trait EditorMenu extends EditorComponent { def toggleConnect: Any => Unit = _ => { messageBus .publish(EditorToggle(EditorToggle.connectKey, Some(true))) - .unsafeRunSync() + .unsafeToFuture() } def triggerFileNew: Any => Unit = _ => { - messageBus.publish(Reset).unsafeRunSync() + messageBus.publish(Reset).unsafeToFuture() } def togglePalette: Any => Unit = _ => { messageBus .publish(EditorToggle(EditorToggle.paletteKey, None)) - .unsafeRunSync() + .unsafeToFuture() } def toggleEdit: Any => Unit = _ => { - messageBus.publish(EditorToggle(EditorToggle.editKey, None)).unsafeRunSync() + messageBus.publish(EditorToggle(EditorToggle.editKey, None)).unsafeToFuture() } def triggerUnselect: Any => Unit = _ => { - messageBus.publish(Select(Set.empty)).unsafeRunSync() + messageBus.publish(Select(Set.empty)).unsafeToFuture() } def triggerResetView: Any => Unit = _ => { - messageBus.publish(ResetTransformation).unsafeRunSync() + messageBus.publish(ResetTransformation).unsafeToFuture() } def triggerExportXML: Any => Unit = _ => { - messageBus.publish(Export(GraphMLFormat)).unsafeRunSync() + messageBus.publish(Export(GraphMLFormat)).unsafeToFuture() } def triggerExportJson: Any => Unit = _ => { - messageBus.publish(Export(JsonFormat)).unsafeRunSync() + messageBus.publish(Export(JsonFormat)).unsafeToFuture() } - def triggerDelete: Any => Unit = _ => messageBus.publish(DeleteSelection).unsafeRunSync() - def triggerUndo: Any => Unit = _ => messageBus.publish(Undo).unsafeRunSync() - def triggerSelectAll: Any => Unit = _ => messageBus.publish(SelectAll).unsafeRunSync() + def triggerDelete: Any => Unit = _ => messageBus.publish(DeleteSelection).unsafeToFuture() + def triggerUndo: Any => Unit = _ => messageBus.publish(Undo).unsafeToFuture() + def triggerSelectAll: Any => Unit = _ => messageBus.publish(SelectAll).unsafeToFuture() def triggerAdd: Any => Unit = _ => { val id = UUID.randomUUID().toString messageBus .publish(AddNode(id, None)) - .unsafeRunSync() + .unsafeToFuture() } } diff --git a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMessageBus.scala b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMessageBus.scala index 63cce6b..2e61fa7 100644 --- a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMessageBus.scala +++ b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorMessageBus.scala @@ -1,8 +1,8 @@ package com.flowtick.graphs.editor import cats.effect.IO -import cats.effect.concurrent.Ref import cats.implicits._ +import cats.effect.kernel.Ref trait EditorMessageBus { def subscribe(backend: EditorComponent): IO[EditorComponent] diff --git a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorProperties.scala b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorProperties.scala index 5c70aeb..0b3d0a1 100644 --- a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorProperties.scala +++ b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorProperties.scala @@ -1,12 +1,15 @@ package com.flowtick.graphs.editor import cats.effect.IO -import cats.effect.concurrent.Ref +import cats.effect.unsafe.implicits.global + import cats.implicits._ + import com.flowtick.graphs.json.schema.Schema import io.circe.Json import scala.util.Try +import cats.effect.kernel.Ref final case class ElementProperties( element: ElementRef, @@ -110,8 +113,8 @@ final case class PropertySpec( trait PropertyFormGroup { def property: PropertySpec - def init: () => Unit - def set: Json => Unit + def init: IO[Unit] + def set: Json => IO[Unit] } trait EditorProperties extends EditorComponent { @@ -255,7 +258,7 @@ trait EditorProperties extends EditorComponent { } else IO.unit case None => IO.unit } - } yield ()).unsafeRunSync() + } yield ()).unsafeToFuture() def setProperties( properties: List[PropertySpec], @@ -263,9 +266,7 @@ trait EditorProperties extends EditorComponent { ): IO[Unit] = for { newGroups <- setPropertiesGroups(properties, elementProperties) _ <- newGroups.map(setGroupValues(_, elementProperties)).sequence - _ <- IO(newGroups.foreach { group => - group.init() - }) + _ <- newGroups.map(_.init).sequence _ <- currentProperties.set(newGroups) } yield () diff --git a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorView.scala b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorView.scala index 26f1506..887a7b0 100644 --- a/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorView.scala +++ b/editor/shared/src/main/scala/com/flowtick/graphs/editor/EditorView.scala @@ -1,11 +1,14 @@ package com.flowtick.graphs.editor import cats.effect.IO -import cats.effect.concurrent.Ref +import cats.effect.unsafe.implicits.global + import cats.implicits._ + import com.flowtick.graphs.editor.view.GraphElement import com.flowtick.graphs.layout.PointSpec import com.flowtick.graphs.{Edge, Node} +import cats.effect.kernel.Ref final case class PanContext( mouseAnchorX: Double, @@ -96,9 +99,9 @@ trait Page[T, E] { Some(newDragStart) case None => None } - .unsafeRunSync() + .unsafeToFuture() - def endDrag: E => Option[DragStart[T]] = _ => dragStartRef.getAndUpdate(_ => None).unsafeRunSync() + def endDrag: E => IO[Option[DragStart[T]]] = _ => dragStartRef.getAndUpdate(_ => None) } final case class ViewModel[T](graphElements: Map[ElementRef, GraphElement[T]]) @@ -132,8 +135,7 @@ trait EditorView[T, E] extends EditorComponent { def handleDrag(drag: Option[DragStart[T]]): IO[Unit] = (for { dragEvent <- drag.filter(value => Math.abs(value.deltaY) > 0 || Math.abs(value.deltaX) > 0) - } yield messageBus.publish(MoveBy(dragEvent.deltaX, dragEvent.deltaY)).void) - .getOrElse(IO.unit) + } yield messageBus.publish(MoveBy(dragEvent.deltaX, dragEvent.deltaY)).void).getOrElse(IO.unit) def appendEdge( editorModel: EditorModel @@ -283,10 +285,10 @@ trait EditorView[T, E] extends EditorComponent { oldSelections.foreach(elem => vm.graphElements .get(elem) - .foreach(p.unsetSelection(_).unsafeRunSync()) + .foreach(p.unsetSelection(_).unsafeToFuture()) ) newSelections.foreach(elem => - vm.graphElements.get(elem).foreach(p.setSelection(_).unsafeRunSync()) + vm.graphElements.get(elem).foreach(p.setSelection(_).unsafeToFuture()) ) } } yield () diff --git a/editor/shared/src/main/scala/com/flowtick/graphs/editor/feature/PaletteFeature.scala b/editor/shared/src/main/scala/com/flowtick/graphs/editor/feature/PaletteFeature.scala index ccb4d00..c6a3bc7 100644 --- a/editor/shared/src/main/scala/com/flowtick/graphs/editor/feature/PaletteFeature.scala +++ b/editor/shared/src/main/scala/com/flowtick/graphs/editor/feature/PaletteFeature.scala @@ -1,10 +1,12 @@ package com.flowtick.graphs.editor.feature import cats.effect.IO -import cats.effect.concurrent.Ref +import cats.effect.unsafe.implicits.global + import com.flowtick.graphs.editor._ import java.util.UUID +import cats.effect.kernel.Ref trait PaletteFeature extends EditorComponent { def messageBus: EditorMessageBus @@ -52,17 +54,17 @@ trait PaletteFeature extends EditorComponent { } yield () def selectPaletteItem(paletteItem: Stencil): Unit = { - currentStencilItemRef.set(Some(paletteItem)).attempt.unsafeRunSync() + currentStencilItemRef.set(Some(paletteItem)).attempt.unsafeToFuture() } def selectConnectorItem(connectorItem: Connector): Unit = { - currentConnectorItemRef.set(Some(connectorItem)).attempt.unsafeRunSync() + currentConnectorItemRef.set(Some(connectorItem)).attempt.unsafeToFuture() } def createFromStencil(paletteItem: Stencil): Unit = { messageBus .publish(AddNode(UUID.randomUUID().toString, Some(paletteItem.id))) .attempt - .unsafeRunSync() + .unsafeToFuture() } } diff --git a/editor/shared/src/main/scala/com/flowtick/graphs/editor/view/SVGPage.scala b/editor/shared/src/main/scala/com/flowtick/graphs/editor/view/SVGPage.scala index 983c437..bb55c6f 100644 --- a/editor/shared/src/main/scala/com/flowtick/graphs/editor/view/SVGPage.scala +++ b/editor/shared/src/main/scala/com/flowtick/graphs/editor/view/SVGPage.scala @@ -1,6 +1,8 @@ package com.flowtick.graphs.editor.view import cats.effect.IO +import cats.effect.unsafe.implicits.global + import com.flowtick.graphs.editor._ import com.flowtick.graphs.layout.PointSpec import com.flowtick.graphs.{Edge, Node} @@ -48,7 +50,7 @@ class SVGPage[Builder, T <: Frag, Frag, E, M]( case _ => false } - def stopPan(event: E): Unit = { + def stopPan(event: E): IO[Unit] = IO { panStart = None } @@ -98,14 +100,14 @@ class SVGPage[Builder, T <: Frag, Frag, E, M]( case _ => } - def click: E => Option[ElementRef] = evt => + def click: E => IO[Option[ElementRef]] = evt => IO { val elem = eventLike.target(evt) renderer.selectable(elem) - }.unsafeRunSync() + } - def startDrag: E => Option[DragStart[T]] = evt => - (for { + def startDrag: E => IO[Option[DragStart[T]]] = evt => + for { dragStart <- IO { val elem = eventLike.target(evt) @@ -131,7 +133,7 @@ class SVGPage[Builder, T <: Frag, Frag, E, M]( } } _ <- dragStartRef.set(dragStart) - } yield dragStart).unsafeRunSync() + } yield dragStart override def addEdge( edge: Edge[EditorGraphEdge], diff --git a/examples/jvm/src/main/scala/ExamplesJvm.scala b/examples/jvm/src/main/scala/ExamplesJvm.scala index d77dcfa..375f005 100644 --- a/examples/jvm/src/main/scala/ExamplesJvm.scala +++ b/examples/jvm/src/main/scala/ExamplesJvm.scala @@ -1,4 +1,4 @@ -import cats.effect.IO +import cats.effect.{ExitCode, IO, IOApp} import com.flowtick.graphs.editor.view.{SVGRendererJvm, SVGRendererOptions, SVGTranscoder} import com.flowtick.graphs.layout.{ForceDirectedLayout, GraphLayoutOps} import com.flowtick.graphs.style._ @@ -16,13 +16,11 @@ object SimpleGraphExampleApp extends SimpleGraphExample with App object TopologicalSortingExampleApp extends TopologicalSortingExample with App object JsonExampleApp extends JsonExample with App -object LayoutExampleApp extends LayoutExample with App { +object LayoutExampleApp extends LayoutExample with IOApp { import com.flowtick.graphs.defaults._ import com.flowtick.graphs.defaults.label._ import com.flowtick.graphs.style.defaults._ - implicit val contextShift = - IO.contextShift(scala.concurrent.ExecutionContext.Implicits.global) override def layoutOps: GraphLayoutOps = ForceDirectedLayout def writeToFile(path: String, content: Array[Byte]): IO[Unit] = IO { @@ -75,5 +73,7 @@ object LayoutExampleApp extends LayoutExample with App { _ <- writeToFile("target/layout_example.png", pngData) } yield () - renderImages.unsafeRunSync() + override def run(args: List[String]): IO[ExitCode] = for { + _ <- renderImages + } yield ExitCode.Success } diff --git a/json/jvm/src/test/scala/com/flowtick/graphs/json/JsonSpec.scala b/json/jvm/src/test/scala/com/flowtick/graphs/json/JsonSpec.scala index a4ca240..9c8a36b 100644 --- a/json/jvm/src/test/scala/com/flowtick/graphs/json/JsonSpec.scala +++ b/json/jvm/src/test/scala/com/flowtick/graphs/json/JsonSpec.scala @@ -105,6 +105,7 @@ class JsonSpec extends AnyFlatSpec with Matchers with Diagrams { println("foo") parsed.edgeId should be(expected.edgeId) parsed should equal(expected) + case Left(error) => fail(error) } } diff --git a/project/plugins.sbt b/project/plugins.sbt index f282716..b271408 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -5,7 +5,7 @@ addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.9.2") addSbtPlugin("io.github.jonas" % "sbt-paradox-material-theme" % "0.6.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.6.0") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.20.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.3") addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.13.0") \ No newline at end of file