Skip to content

Commit

Permalink
Align permalink with code and deps
Browse files Browse the repository at this point in the history
  • Loading branch information
daddykotex committed Dec 8, 2023
1 parent aa95ca5 commit ae3d881
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ object CodeEditor {
sealed trait ValidationResult
object ValidationResult {
case object Loading extends ValidationResult
case class Success(content: String, deps: Option[List[Dependency]])
extends ValidationResult
case class Success(editorContent: EditorContent) extends ValidationResult
case class Failed(errors: List[String]) extends ValidationResult
case class UnknownFailure(ex: Throwable) extends ValidationResult
}
Expand All @@ -34,13 +33,37 @@ class CodeEditor(dependencies: EventStream[Either[Throwable, Dependencies]]) {
| @required
| name: String
|}""".stripMargin
val codeContent = Var(
val editorContent = Var(
PermalinkCodec
.readOnce()
.getOrElse(initial)
.getOrElse(EditorContent(initial, Set.empty))
)

val checkedDependencies = Var(Set.empty[Dependency])
val updatePermalinkCode = {
val v = onInput.mapToValue.map(value => editorContent.now().copy(value))
v --> editorContent
}

val updateValueFromPermalinkCode =
value <-- editorContent.signal.map(_.code)

def updatePermalinkDeps(dep: Dependency) = {
val mod = { (isChecked: Boolean) =>
editorContent.update { content =>
val newSet =
if (isChecked) content.deps + dep
else content.deps - dep
content.copy(deps = newSet)
}
}
onChange.mapToChecked --> mod
}

def updateCheckFromPermalinkDeps(dep: Dependency) = {
checked <-- editorContent.signal.map { content =>
content.deps.find(_ == dep).isDefined
}
}

val dependenciesCheckboxes = {
def displayIfHasErrors = styleAttr <-- dependencies.map(res =>
Expand All @@ -59,21 +82,16 @@ class CodeEditor(dependencies: EventStream[Either[Throwable, Dependencies]]) {
fieldSet(
legend("Choose your dependencies"),
deps.value.map { dep =>
val depId = dep.value.replace(":", "_")
div(
input(
cls := "m-2",
`type` := "checkbox",
nameAttr := depId,
idAttr := depId,
onChange.mapToChecked --> { x =>
checkedDependencies.update { currentSet =>
if (x) currentSet + dep
else currentSet - dep
}
}
nameAttr := dep.value,
idAttr := dep.value,
updatePermalinkDeps(dep),
updateCheckFromPermalinkDeps(dep)
),
label(forId := depId, dep.value)
label(forId := dep.value, dep.value)
)
}
)
Expand All @@ -90,10 +108,10 @@ class CodeEditor(dependencies: EventStream[Either[Throwable, Dependencies]]) {
cls := "block p-2.5 w-full h-5/6 text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 font-mono",
onMountFocus,
controlled(
value <-- codeContent,
onInput.mapToValue --> codeContent
updateValueFromPermalinkCode,
updatePermalinkCode
),
PermalinkCodec.read --> codeContent
PermalinkCodec.read --> editorContent
),
div(
cls := "block p-2.5 w-full h-1/6",
Expand All @@ -115,9 +133,9 @@ class CodeEditor(dependencies: EventStream[Either[Throwable, Dependencies]]) {
}
)
val icon = ResultIcon(validationResult.map {
case CodeEditor.ValidationResult.Loading => ResultIcon.State.Loading
case CodeEditor.ValidationResult.Success(_, _) => ResultIcon.State.Success
case CodeEditor.ValidationResult.Failed(_) => ResultIcon.State.Failed
case CodeEditor.ValidationResult.Loading => ResultIcon.State.Loading
case CodeEditor.ValidationResult.Success(_) => ResultIcon.State.Success
case CodeEditor.ValidationResult.Failed(_) => ResultIcon.State.Failed
case CodeEditor.ValidationResult.UnknownFailure(_) =>
ResultIcon.State.Failed
})
Expand All @@ -126,29 +144,63 @@ class CodeEditor(dependencies: EventStream[Either[Throwable, Dependencies]]) {

}

final case class EditorContent(code: String, deps: Set[Dependency])

/** Writes code to the URL hash and provides a stream of its decoded values.
*
* Encoding/decoding of code is handled internally.
*/
object PermalinkCodec {
val hashTag = "#"
val hashTagLength = hashTag.length()
val hashPart = ";"

def readOnce(): Option[String] =
def readOnce(): Option[EditorContent] =
decode(org.scalajs.dom.window.location.hash)

val read: EventStream[String] = windowEvents(_.onHashChange)
val read: EventStream[EditorContent] = windowEvents(_.onHashChange)
.mapTo(org.scalajs.dom.window.location.hash)
.map(decode(_))
.collectSome

def write(code: String): Unit =
org.scalajs.dom.window.location.hash = encode(code)
def write(value: EditorContent): Unit =
org.scalajs.dom.window.location.hash = encode(value)

private class HashPartValue(partName: String) {
private val partKey = s"$partName="
def encode(value: String): String = s"$partKey$value"
def unapply(value: String): Option[String] = {
if (value.startsWith(partKey)) Some(value.drop(partKey.length()))
else None
}
}
private val codePart = new HashPartValue("code")
private val depsPart = new HashPartValue("dependencies")

private def encode(value: EditorContent): String = {
val code =
codePart.encode(lzstring.compressToEncodedURIComponent(value.code))
val deps = depsPart.encode(value.deps.map(_.value).mkString(","))
val hash = List(code, deps).mkString(";")
s"#$hash"
}

private def encode(code: String): String =
s"#code=${lzstring.compressToEncodedURIComponent(code)}"
private def decode(hash: String): Option[EditorContent] = {
if (hash.startsWith(hashTag)) {
val hashParts = hash
.drop(hashTagLength)
.split(hashPart)

private def decode(hash: String): Option[String] = hash match {
case s"#code=$content" =>
Option(lzstring.decompressFromEncodedURIComponent(content))
case _ => None
val maybeCode = hashParts.collectFirst { case codePart(value) =>
Option(lzstring.decompressFromEncodedURIComponent(value))
}.flatten
val deps =
hashParts
.collectFirst { case depsPart(value) =>
value.split(",").toSet.map(Dependency(_))
}
.getOrElse(Set.empty)
maybeCode.map(code => EditorContent(code, deps))
} else None
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,16 @@ object Home {

locally {
implicit val owner = new ManualOwner
editor.codeContent.signal.foreach(PermalinkCodec.write)
editor.editorContent.signal.foreach(PermalinkCodec.write)
}

val validate: EventStream[CodeEditor.ValidationResult] =
editor.codeContent.signal
editor.editorContent.signal
.composeChanges(_.debounce(2000))
.combineWith(editor.checkedDependencies.signal)
.flatMap { case (code, deps) =>
.flatMap { content =>
api
.smithyValidate(code, Some(deps.toList))
.map(_ =>
CodeEditor.ValidationResult.Success(code, Some(deps.toList))
)
.smithyValidate(content.code, Some(content.deps.toList))
.map(_ => CodeEditor.ValidationResult.Success(content))
.recover {
case InvalidSmithyContent(errors) =>
Some(CodeEditor.ValidationResult.Failed(errors))
Expand All @@ -41,12 +38,10 @@ object Home {

val convertedToSmithy4s: EventStream[CodeEditor.Smithy4sConversionResult] =
validate.compose {
_.collect { case ValidationResult.Success(code, deps) =>
(code, deps)
}
.flatMap { case (code, deps) =>
_.collect { case ValidationResult.Success(content) => content }
.flatMap { content =>
api
.smithy4sConvert(code, deps)
.smithy4sConvert(content.code, Some(content.deps.toList))
.map(r =>
CodeEditor.Smithy4sConversionResult.Success(r.generated)
)
Expand Down

0 comments on commit ae3d881

Please sign in to comment.