Skip to content

Instantly share code, notes, and snippets.

@nafg
Created July 21, 2014 10:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nafg/d955dbef8d8d580075af to your computer and use it in GitHub Desktop.
Save nafg/d955dbef8d8d580075af to your computer and use it in GitHub Desktop.
import java.util.concurrent.atomic.AtomicInteger
import scala.collection.mutable.{ArrayBuffer, SynchronizedBuffer}
import scala.concurrent.{ExecutionContext, Future}
import scala.xml.Elem
import reactive.{Observing, Subscription}
import reactive.Applicative.ApType
import reactive.web.Page
import reactive.web.html.TextInput
class ErrorSourceId(val underlying: Int) extends AnyVal
object Id {
val next: () => ErrorSourceId = {
val current = new AtomicInteger(0)
() => new ErrorSourceId(current.incrementAndGet())
}
}
final case class ErrorMessage(message: String, source: ErrorSourceId)
object ErrorMessage {
/** Create an error message associated with the given reader. */
def create[A](message: String)(reader: Reader[A]): ErrorMessage = apply(message, reader.id)
}
sealed trait Result[+A] {
def isSucess: Boolean = this match {
case Result.Success(_) => true
case Result.Failure(_) => false
}
def map[B](f: A => B): Result[B] = Result.map(f)(this)
}
object Result {
case class Success[+A](a: A) extends Result[A]
case class Failure(errors: List[ErrorMessage]) extends Result[Nothing]
def failWith[A](message: String): Result[A] = Failure(List(ErrorMessage(message, new ErrorSourceId(0))))
def ap[A, B](r1: Result[A => B], r2: Result[A]): Result[B] = (r1, r2) match {
case (Success(f), Success(x)) => Success(f(x))
case (Failure(m), Success(_)) => Failure(m)
case (Success(_), Failure(m)) => Failure(m)
case (Failure(m1), Failure(m2)) => Failure(m1 ++ m2)
}
def join[A](r: Result[Result[A]]): Result[A] = r match {
case Failure(m) => Failure(m)
case Success(Failure(m)) => Failure(m)
case Success(Success(x)) => Success(x)
}
def map[A, B](f: A => B)(ra: Result[A]): Result[B] = ra match {
case Success(x) => Success(f(x))
case Failure(m) => Failure(m)
}
def map2[A, B, C](f: A => B => C)(ra: Result[A])(rb: Result[B]): Result[C] = (ra, rb) match {
case (Success(a), Success(b)) => Success(f(a)(b))
case (Failure(ma), Failure(mb)) => Failure(ma ++ mb)
case (Failure(m), _) => Failure(m)
case (_, Failure(m)) => Failure(m)
}
def iter[A](f: A => Unit) = (_: Result[A]) match {
case Success(x) => f(x)
case _ => ()
}
def bind[A, B](f: A => Result[B]): Result[A] => Result[B] = _ match {
case Success(x) => f(x)
case Failure(m) => Failure(m)
}
}
import Result.{ Failure, Success }
abstract class Reader[+A](val id: ErrorSourceId) {
def latest: Result[A]
def subscribe(f: Result[A] => Unit): Subscription
def subscribeImmediate(f: Result[A] => Unit): Subscription = {
f(latest)
subscribe(f)
}
def through[B](r: Reader[B]): Reader[A] = {
val out = Stream(latest)
r.subscribe {
case Success(_) => out.trigger(latest)
case Failure(msgs) =>
(latest, msgs filter (_.source == id)) match {
case (_, Nil) => out.trigger(latest)
case (Success(x), l) => out.trigger(Failure(l))
case (Failure(l), l2) => out.trigger(Failure(l ++ l2))
}
}
out
}
}
object Reader {
def mapResult[A, B](f: Result[B] => Result[A])(r: Reader[B]): Reader[A] = {
val out = Stream[A](f(r.latest))
r.subscribe(out.trigger compose f)
out
}
def mapResult2[A, B, C](f: Result[B] => Result[C] => Result[A])(rb: Reader[B])(rc: Reader[C]): Reader[A] = {
val out = Stream[A](f(rb.latest)(rc.latest))
rb.subscribe(b => out.trigger(f(b)(rc.latest)))
rc.subscribe(c => out.trigger(f(rb.latest)(c)))
out
}
def map[A, B](f: B => A)(r: Reader[B]): Reader[A] = mapResult[A, B](Result.map[B, A](f) _)(r)
def map2[A, B, C](f: B => C => A)(rb: Reader[B])(rc: Reader[C]): Reader[A] =
mapResult2[A, B, C](b => c => Result.map2[B, C, A](f)(b)(c))(rb)(rc)
def mapToResult[A, B](f: B => Result[A])(r: Reader[B]): Reader[A] = mapResult[A, B](Result.bind(f))(r)
}
trait Writer[-A] {
def trigger: Result[A] => Unit
}
final case class Stream[A](init: Result[A], override val id: ErrorSourceId = Id.next()) extends Reader[A](id) with Writer[A] {
val s = Var[Result[A]](init)
override def latest: Result[A] = s.now
override def subscribe(f: Result[A] => Unit): Subscription = s subscribe f
def trigger: Result[A] => Unit = s.update _
/** Return a new Writer that sends x to this when triggered. */
def write(x: A): Writer[Unit] = new ConcreteWriter[Unit]({
case Failure(m) => trigger(Failure(m))
case Success(()) => trigger(Success(x))
})
}
object Stream {
def ap[A, B](sf: Stream[A => B])(sx: Stream[A]): Stream[B] = {
val out = Stream(Result.ap(sf.latest, sx.latest))
sf.subscribe(f => out.trigger(Result.ap(f, sx.latest)))
sx.subscribe(x => out.trigger(Result.ap(sf.latest, x)))
out
}
def apJoin[A, B](sf: Stream[A => B])(sx: Stream[Result[A]]): Stream[B] = {
val out = Stream(Result.ap(sf.latest, Result.join(sx.latest)))
sf.subscribe(f => out.trigger(Result.ap(f, Result.join(sx.latest))))
sx.subscribe(x => out.trigger(Result.ap(sf.latest, Result.join(x))))
out
}
def map[A, B](a2b: A => B)(b2a: B => A)(s: Stream[A]): Stream[B] = {
val s2 = Stream[B](Result.map(a2b)(s.latest), s.id)
val pa = new AtomicRef(s.latest)
val pb = new AtomicRef(s2.latest)
s.subscribe(a => if (pa.get != a) {
pb.set(Result.map(a2b)(a))
s2.trigger(pb.get)
})
s2.subscribe(b => if (pb.get != b) {
pa.set(Result.map(b2a)(b))
s.trigger(pa.get)
})
s2
}
}
final class ConcreteWriter[A](val trigger: Result[A] => Unit) extends Writer[A]
object ConcreteWriter {
def apply[A](trigger: A => Unit) = new ConcreteWriter[A]({
case Success(x) => trigger(x)
case Failure(_) => ()
})
}
final class ConcreteReader[A](val latest: Result[A], subscribeFn: (Result[A] => Unit) => Subscription) extends Reader[A](Id.next()) {
def subscribe(f: Result[A] => Unit): Subscription = subscribe(f)
}
final class Submitter[A](val input: Reader[A], clearError: Boolean) extends Reader[A](Id.next()) with Writer[Unit] {
val output = Stream[A](Failure(Nil))
val writer: Writer[Unit] = new ConcreteWriter[Unit](UnitIn => (UnitIn, input.latest) match {
case (Failure(m1), Failure(m2)) => output.trigger(Failure(m1 ++ m2))
case (Failure(m), Success(_)) => output.trigger(Failure(m))
case (Success(_), Failure(m)) => output.trigger(Failure(m))
case (Success(()), Success(x)) => output.trigger(Success(x))
})
if (clearError)
input.subscribe(_ => output.trigger(Failure(Nil)))
def trigger = _ => writer.trigger(Success(()))
def latest: Result[A] = output.latest
def subscribe(f: Result[A] => Unit): Subscription = output.subscribe(f)
}
object Pervasives {
implicit class Ext_<<^[A, B, C](v: A => B => C) {
/**
* Push an argument to the view function
*/
def <<^(a: B): A => C = x => v(x)(a)
}
implicit class Ext_>>^[A, B](v: A => B) {
/**
* Map argument(s) to the view function
*/
def >>^[C](f: A): (B => C) => C = g => g(v(f))
}
implicit class Ext_<*>[A, B, C, D](f: Piglet[A => B, C => D]) {
/** Apply a Piglet function to a Piglet value */
def <*>[E](x: Piglet[A, D => E]): Piglet[B, C => E] = Piglet.ap(f)(x)
}
implicit class Ext_<*?>[A, B, C, D](f: Piglet[A => B, C => D]) {
/** Apply a Piglet function to a Piglet Result */
def <*?>[E](x: Piglet[Result[A], D => E]): Piglet[B, C => E] = Piglet(Stream.apJoin(f.stream)(x.stream), f.view andThen x.view)
}
implicit class Ext_|>[A](a: A) {
def |>[B](f: A => B): B = f(a)
}
import Applicative.ApType
trait PigletApBase[V1, VLast, AT <: ApType] {
def apply[Z, V0](f: Piglet[AT#FS[Z], V0 => V1]): Piglet[Z, V0 => VLast]
def applyRet[Z](f: AT#FS[Z]): Piglet[Z, V1 => VLast] = apply[Z, V1](Piglet.`return`[AT#FS[Z], V1](f))
def :@:[B, V0](p: Piglet[B, V0 => V1]): PigletApBase[V0, VLast, ApType.More[B, AT]] =
new PigletApMore[B, V0, V1, VLast, AT](p, this)
}
implicit class PigletApOne[A, V1, VLast](val in: Piglet[A, V1 => VLast]) extends PigletApBase[V1, VLast, ApType.One[A]] {
override def apply[Z, V0](f: Piglet[A => Z, V0 => V1]): Piglet[Z, V0 => VLast] = Piglet.ap(f)(in)
}
class PigletApMore[A, V1, V2, VLast, N <: ApType](val p: Piglet[A, V1 => V2], val next: PigletApBase[V2, VLast, N]) extends PigletApBase[V1, VLast, ApType.More[A, N]] {
override def apply[Z, V0](f: Piglet[A => N#FS[Z], V0 => V1]): Piglet[Z, V0 => VLast] = next.apply(Piglet.ap(f)(p))
}
}
/**
* @param stream the stream associated with the Piglet.
*/
case class Piglet[A, V](stream: Stream[A], view: V) {
def upcastView[V2 >: V]: Piglet[A, V2] = this.copy(view = view)
def map[B](f: A => B) = Piglet.map(f) apply this
}
object Piglet {
import Pervasives._
def ap[A, B, C, D, E](f: Piglet[A => B, C => D])(x: Piglet[A, D => E]): Piglet[B, C => E] =
Piglet(Stream.ap(f.stream)(x.stream), f.view andThen x.view)
/** Create a Piglet from a stream and a view. */
def create[A, V](s: Stream[A])(v: V): Piglet[A, V] = Piglet(s, v)
/** Create a Piglet initialized with x that passes its stream to the view */
def `yield`[A, B](x: A): Piglet[A, (Stream[A] => B) => B] = {
val s = Stream(Success(x))
Piglet(s, f => f(s))
}
/** Create a Piglet initialized with failure that passes its stream to the view */
def yieldFailure[A, B](): Piglet[A, (Stream[A] => B) => B] = {
val s = Stream[A](Failure(Nil))
Piglet(s, f => f(s))
}
/**
* Create a Piglet with optional value initialized with init
* that passes its stream to the view.
* The stream passed is a non-optional stream,
* and the given noneValue is mapped to None.
*/
def yieldOption[A, B](x: Option[A])(none: A): Piglet[Option[A], (Stream[A] => B) => B] =
mapViewArgs(`yield`[Option[A], Stream[A]](x))(
Stream.map[Option[A], A](_ getOrElse none)(Some(_) filter (_ != none)) _
)
/** Create a Piglet initialized with x that doesn't pass any stream to the view */
def `return`[A, B](x: A): Piglet[A, B => B] = Piglet(Stream(Success(x)), identity[B] _)
/** Create a Piglet initialized with x that doesn't pass any stream to the view. */
def returnFailure[A, B](): Piglet[A, B => B] = Piglet(Stream[A](Failure(Nil)), identity[B] _)
/**
* Create a Piglet value that streams the value every time it receives a signal.
* The signaling function is passed to the view.
*/
def withSubmit[A, B, C](pin: Piglet[A, B => Submitter[A] => C]): Piglet[A, B => C] = {
val submitter = new Submitter(pin.stream, clearError = false)
Piglet(submitter.output, pin.view <<^ submitter)
}
/** Piglet that returns many values with an additional piglet used to create new values in the collection */
def manyPiglet[A, V, W, X, Y, Z](inits: Seq[A])(create: (Piglet[A, Y => Z]))(p: (A => Piglet[A, V => W])): Piglet[Seq[A], (Many.Stream[A, V, W, Y, Z] => X) => X] = {
val s = Stream(Success(inits))
val m = new Many.Stream[A, V, W, Y, Z](p, s, create)
Piglet(s, f => f(m))
}
/** Create a Piglet that returns many values, each created according to the given Piglet. */
def many[A, V, W, X](init: A)(p: (A => Piglet[A, V => W])): Piglet[Seq[A], (Many.UnitStream[A, V, W] => X) => X] = manyInit(List(init))(init)(p)
/** Create a Piglet that returns many values, each created according to the given Piglet. */
def manyInit[A, V, W, X](inits: Seq[A])(init: A)(p: (A => Piglet[A, V => W])): Piglet[Seq[A], (Many.UnitStream[A, V, W] => X) => X] = {
val s = Stream(Success(inits))
val _init = p(init)
val m = new Many.UnitStream[A, V, W](p, s, _init, init)
Piglet(s, _(m))
}
/** Create a Piglet that allows the user to choose between several options. */
def choose[I, O, U, V, W, X, Y](chooser: Piglet[I, U => V])(choices: (I => Piglet[O, W => X])): Piglet[O, (Choose.Stream[O, I, U, V, W, X] => Y) => Y] = {
val s = Stream[O](Failure(Nil))
val c = new Choose.Stream[O, I, U, V, W, X](chooser, choices, s)
Piglet(s, f => f(c))
}
/** Create a Piglet value that streams the value every time it receives a signal. */
/** The signaling function is passed to the view. */
/** Any update to the input Piglet passes `Failure []` to the output. */
/** This is useful to clear error messages from a previous submission. */
def withSubmitClearError[A, B, C](pin: Piglet[A, B => Submitter[A] => C]): Piglet[A, B => C] = {
val submitter = new Submitter(pin.stream, clearError = false)
Piglet(submitter.output, pin.view <<^ submitter)
}
/** Pass this Piglet's stream to the view. */
def transmitStream[A, B, C]: Piglet[A, B => Stream[A] => C] => Piglet[A, B => C] =
p => Piglet(p.stream, p.view <<^ p.stream)
/** Pass a reader for this Piglet's stream to the view. */
def transmitReader[A, B, C]: Piglet[A, B => Reader[A] => C] => Piglet[A, B => C] =
p => Piglet(p.stream, p.view <<^ p.stream)
/** Pass a mapped reader for this Piglet's stream to the view. */
def transmitReaderMap[A, B, C, D](f: A => D)(p: Piglet[A, B => Reader[D] => C]): Piglet[A, B => C] =
Piglet(p.stream, p.view <<^ Reader.map(f)(p.stream))
/** Pass a mapped reader for this Piglet's stream to the view. */
def transmitReaderMapResult[A, B, C, D](f: Result[A] => Result[D])(p: Piglet[A, B => Reader[D] => C]): Piglet[A, B => C] =
Piglet(p.stream, p.view <<^ Reader.mapResult(f)(p.stream))
/** Pass a mapped reader for this Piglet's stream to the view. */
def transmitReaderMapToResult[A, B, C, D](f: A => Result[D])(p: Piglet[A, B => Reader[D] => C]): Piglet[A, B => C] =
Piglet(p.stream, p.view <<^ Reader.mapToResult(f)(p.stream))
/** Pass a writer for this Piglet's stream to the view. */
def transmitWriter[A, B, C]: Piglet[A, B => Writer[A] => C] => Piglet[A, B => C] =
p => Piglet(p.stream, p.view <<^ p.stream)
/** Map the value of a Piglet, without changing its view. */
def map[A, B, V](m: A => B): Piglet[A, V] => Piglet[B, V] =
mapResult {
case Failure(msg) => Failure(msg)
case Success(x) => Success(m(x))
}
/** Map the value of a Piglet, without changing its view. */
def mapToResult[A, B, V](m: A => Result[B]): Piglet[A, V] => Piglet[B, V] =
mapResult {
case Failure(msg) => Failure(msg)
case Success(x) => m(x)
}
/** Map the Result of a Piglet, without changing its view. */
def mapResult[A, B, V](m: Result[A] => Result[B])(p: Piglet[A, V]): Piglet[B, V] = {
val out = Stream(m(p.stream.latest))
p.stream.subscribe(out.trigger compose m)
Piglet(out, p.view)
}
/** Map the value of a Piglet, without changing its view. */
def mapAsync[A, B, V](m: A => Future[B])(implicit ec: ExecutionContext): Piglet[A, V] => Piglet[B, V] =
mapAsyncResult {
case Failure(msg) => Future.successful(Failure(msg))
case Success(x) => m(x).map(res => Success(res))
}
/** Map the value of a Piglet, without changing its view. */
def mapToAsyncResult[A, B, V](m: A => Future[Result[B]])(implicit ec: ExecutionContext): Piglet[A, V] => Piglet[B, V] =
mapAsyncResult {
case Failure(msg) => Future.successful(Failure(msg))
case Success(x) => m(x)
}
/** Map the Result of a Piglet, without changing its view. */
def mapAsyncResult[A, B, V](m: Result[A] => Future[Result[B]])(p: Piglet[A, V])(implicit ec: ExecutionContext): Piglet[B, V] = {
val out = Stream[B](Failure(Nil))
p.stream.subscribe { v =>
m(v) foreach out.trigger
}
m(p.stream.latest) foreach out.trigger
Piglet(out, p.view)
}
/** Map the value of a Piglet, without changing its view. */
/** The function can write directly into the output, zero, one or many times. */
def mapWithWriter[A, B, V](f: Writer[B] => A => Unit)(p: Piglet[A, V]): Piglet[B, V] = {
def f2(out: Writer[B])(r: Result[A]) = r match {
case Failure(msgs) => out.trigger(Failure(msgs))
case Success(x) => f(out)(x)
}
mapResultWithWriter(f2)(p)
}
/** Map the Result of a Piglet, without changing its view. */
/** The function can write directly into the output, zero, one or many times. */
def mapResultWithWriter[A, B, V](f: Writer[B] => Result[A] => Unit)(p: Piglet[A, V]): Piglet[B, V] = {
val stream = Stream[B](Failure(Nil))
p.stream.subscribeImmediate(f(stream))
Piglet(stream, p.view)
}
/** Flush error messages, replacing any failing state with a message-less failing state. */
def flushErrors[A, V]: Piglet[A, V] => Piglet[A, V] =
mapResult {
case Failure(_) => Failure(Nil)
case x => x
}
/** Run the action every time the Piglet's stream receives successful data. */
def run[A, B](action: (A => Unit))(p: Piglet[A, B]): Piglet[A, B] =
runResult(Result.iter(action))(p)
/** Run the action every time the Piglet's stream receives data. */
def runResult[A, B](action: (Result[A] => Unit))(p: Piglet[A, B]): Piglet[A, B] = {
p.stream subscribe action
p
}
/** Run a Piglet UI with the given view. */
def render[A, V, Elt]: V => Piglet[A, V => Elt] => Elt =
view => p => p.view(view)
/** Map the arguments passed to the view. */
def mapViewArgs[A, VA, VB, VC](p: Piglet[A, VA => VB])(view: VA): Piglet[A, (VB => VC) => VC] =
Piglet(p.stream, p.view >>^ view)
/**
* Create a Piglet for a double field for confirmation (e.g. for passwords).
* @tparam A the piglet value type
* @param init the initial value
* @param validate a function that transforms a Piglet's view type from `(Stream[A]=>B)=>B` to `(C=>D=>(C,D)) => Stream[A] => E`
* @param nomatch the error message for when the two don't match
*/
def confirm[A, B, C, D, E, F](init: A)(validate: Piglet[A, (Stream[A] => B) => B] => Piglet[A, (C => D => (C, D)) => Stream[A] => E])(nomatch: String): Piglet[A, ((E => F) => F)] = {
val first: Piglet[A, (Stream[A] => B) => B] = `yield`(init)
val second: Piglet[A, (Stream[A] => E) => E] = `yield`(init)
val x1 = validate(first) :@: second applyRet { a => b => (a, b) }
val x2 = Validation.is2(ErrorMessage.create(nomatch)(second.stream))(x1) { case (a, b) => a == b }
mapViewArgs(x2.map(_._1)) { a => b => (a, b) }
}
abstract class Builder {
def bind[I, O, U, V, W, X, Y](p: Piglet[I, U => V], f: (I => Piglet[O, W => X])): Piglet[O, (Choose.Stream[O, I, U, V, W, X] => Y) => Y] =
choose(p)(f)
def `return`[A, B]: A => Piglet[A, B => B] = Piglet.`return`
def returnFrom[A, V]: Piglet[A, V] => Piglet[A, V] = identity
def `yield`[A, B]: A => Piglet[A, (Stream[A] => B) => B] = Piglet.`yield`
def yieldFrom[A, V]: Piglet[A, V] => Piglet[A, V] = identity
def zero[A, B](): Piglet[A, B => B] = returnFailure()
}
object Do extends Builder
object Validation {
/** If the Piglet value passes the predicate, it is passed on; */
/** else, `Failwith msg` is passed on. */
def is[A, B](pred: (A => Boolean))(msg: String)(p: Piglet[A, B]): Piglet[A, B] = {
val s = Stream(p.stream.latest, p.stream.id)
p.stream.subscribe {
case Failure(m) => s.trigger(Failure(m))
case Success(x) if pred(x) => s.trigger(Success(x))
case _ => s.trigger(Failure(List(ErrorMessage(msg, s.id))))
}
p.copy(stream = s)
}
/** If the Piglet value passes the predicate, it is passed on; */
/** else, `Failure [msg]` is passed on. */
def is2[A, B](msg: ErrorMessage)(p: Piglet[A, B])(pred: (A => Boolean)): Piglet[A, B] = {
val s = Stream(p.stream.latest, p.stream.id)
p.stream.subscribe {
case Failure(m) => s.trigger(Failure(m))
case Success(x) if pred(x) => s.trigger(Success(x))
case _ => s.trigger(Failure(List(msg)))
}
p.copy(stream = s)
}
/** Checks that a String is not empty. */
/** Can be used as predicate for Is and Is', eg: */
/** Validation.Is Validation.NotEmpty "Field must not be empty." */
def notEmpty(value: String): Boolean = value != ""
/** Check that a String matches a regexp. */
/** Can be used as predicate for Is and Is', eg: */
/** Validation.Is (Validation.Match "^test.*") "Field must start with 'test'." */
def `match`(regexp: String): (String => Boolean) = _.matches(regexp)
}
}
trait Container[In, Out] {
def add: In => Unit
def remove: Int => Unit
def moveUp: Int => Unit
def container: Out
}
object Many {
class Operations(deleteFn: () => Unit, val moveUp: Submitter[Unit], val moveDown: Submitter[Unit]) {
def delete: Writer[Unit] = ConcreteWriter(_ => deleteFn())
}
class Stream[A, V, W, Y, Z](p: A => Piglet[A, V => W], out: widgets.Stream[Seq[A]], adder: Piglet[A, Y => Z]) extends Reader[Seq[A]](out.id) {
val streams = new ArrayBuffer[widgets.Stream[A]] with SynchronizedBuffer[widgets.Stream[A]]
def update() = {
val res = streams.foldLeft(Success(Nil): Result[List[A]]) {
case (acc, cur) => (acc, cur.latest) match {
case (Success(l), Success(x)) => Success(x :: l)
case (Failure(m), Success(_)) => Failure(m)
case (Success(_), Failure(m)) => Failure(m)
case (Failure(m1), Failure(m2)) => Failure(m1 ++ m2)
}
}.map(_.reverse: Seq[A])
out.trigger(res)
}
def subscribe(f: Result[Seq[A]] => Unit): Subscription = out.subscribe(f)
def latest = out.latest
/**
* Render the element collection inside this Piglet
* inside the given container and with the provided
* rendering function
*/
def render[U](c: Container[W, U])(f: Operations => V): U = {
def add(x: A) = {
val piglet = p(x)
streams += piglet.stream
piglet.stream.subscribeImmediate(_ => update())
def getThisIndex = streams.indexWhere(_.id == piglet.stream.id)
def moveBy(i: Int) = if (i > 0 && i < streams.length) {
val s = streams(i)
streams(i) = streams(i - 1)
streams(i - 1) = s
c.moveUp(i)
update()
}
def moveDown() = moveBy(getThisIndex + 1)
def moveUp() = moveBy(getThisIndex)
def canMoveUp = if (getThisIndex > 0) Success(()) else Failure(Nil)
def canMoveDown = if (getThisIndex < streams.length) Success(()) else Failure(Nil)
val inMoveUp = Stream(canMoveUp)
val inMoveDown = Stream(canMoveDown)
val outSubscription = out.subscribe(_ => {
inMoveUp.trigger(canMoveUp)
inMoveDown.trigger(canMoveDown)
})
val subMoveUp = new Submitter(inMoveUp, false)
val subMoveDown = new Submitter(inMoveDown, false)
val subUpSubscription = subMoveUp.subscribe(Result.iter(_ => moveUp()))
val subDownSubscription = subMoveDown.subscribe(Result.iter(_ => moveDown()))
def delete() = {
val i = getThisIndex
streams.remove(i)
c.remove(i)
outSubscription.unsubscribe()
subUpSubscription.unsubscribe()
subDownSubscription.unsubscribe()
update()
}
c.add(piglet.view(f(new Operations(delete, subMoveUp, subMoveDown))))
}
out.latest match {
case Failure(_) => ()
case Success(xs) => xs foreach add
}
adder.stream.subscribe {
case Failure(_) => ()
case Success(init) => add(init)
}
c.container
}
/** Stream where new elements for the collection are written */
def addA: Writer[A] = adder.stream
/** Function that provides the Adder Piglet with a rendering function */
def addRender(f: Y): Z = adder.view(f)
}
class UnitStream[A, V, W](p: A => Piglet[A, V => W], out: widgets.Stream[Seq[A]], init: Piglet[A, V => W], default: A) extends Stream[A, V, W, V, W](p, out, init) {
val submitStream = {
val submitter = Stream[Unit](Failure(Nil))
val trigger = init.stream.trigger
submitter.subscribe {
case Failure(msgs) => trigger(Failure(msgs))
case Success(()) => trigger(Success(default))
}
submitter
}
/** Add an element to the collection set to the default values */
def addU: Writer[Unit] = submitStream
}
}
object Choose {
class Stream[O, I, U, V, W, X](chooser: Piglet[I, U => V], choice: I => Piglet[O, W => X], out: widgets.Stream[O]) extends Reader[O](out.id) with Subscription {
val pStream = Stream[(I, Piglet[O, W => X])](Failure(Nil))
val choiceSubscriptions = collection.mutable.Map[I, (Piglet[O, W => X], Subscription)]()
val subscriptions = new AtomicRef(
List(
chooser.stream.subscribeImmediate(res => pStream.trigger(
res.map(i =>
(i, if (choiceSubscriptions.keySet.contains(i)) choiceSubscriptions(i)._1 else {
val p = choice(i)
choiceSubscriptions(i) = (p, p.stream.subscribe(out.trigger))
p
})
)
))
)
)
override def latest = out.latest
override def subscribe(f: Result[O] => Unit): Subscription = out.subscribe(f)
/** Render the Piglet that allows the user to choose between different options. */
def chooser(f: U): V = chooser.view(f)
/** Get the stream of the Piglet that allows the user to choose between different options */
def chooserStream: widgets.Stream[I] = chooser.stream
/** Render the Piglet that allows the user to choose the value for the selected option */
def choice[Y](c: Container[X, Y])(f: W): Y = {
val renders = Map[I, Nothing]()
val hasChild = new AtomicRef(false)
subscriptions.transform(
pStream.subscribeImmediate(res => res match {
case Failure(_) => ()
case Success((i, p)) =>
val render = if (renders.keySet contains i) renders(i)
else p.view(f)
out.trigger(p.stream.latest)
if (!hasChild.get()) c.remove(0)
hasChild.set(true)
c.add(render)
}) :: _
)
c.container
}
def cleanUp(): Unit = {
subscriptions.get.foreach(_.unsubscribe())
choiceSubscriptions foreach {
case (_, (_, s)) => s.unsubscribe()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment