Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- @ = require(["mho:std", "mho:app"])
- // TODO move all this into a separate module
- function ArrayDiff(x) {
- @assert.ok(Array.isArray(x))
- return @ObservableVar({
- type: "init",
- array: x
- })
- }
- function pushAt(stream, index, x) {
- stream.modify(function (diff) {
- var array = diff.array
- @assert.ok(index >= 0)
- if (index === array.length) {
- array.push(x)
- } else {
- array.splice(index, 0, x)
- }
- return {
- type: "add",
- array: array,
- index: index,
- value: x
- }
- })
- return index
- }
- function removeAt(stream, index) {
- stream.modify(function (diff) {
- var array = diff.array
- @assert.ok(index >= 0)
- @assert.ok(index < array.length)
- var value = array[index]
- array.splice(index, 1)
- return {
- type: "remove",
- array: array,
- index: index,
- value: value
- }
- })
- return index
- }
- function modifyAt(stream, index, f) {
- stream.modify(function (diff) {
- var array = diff.array
- @assert.ok(index >= 0)
- @assert.ok(index < array.length)
- var value_old = array[index]
- var value_new = f(value_old)
- array[index] = value_new
- return {
- type: "modify",
- array: array,
- index: index,
- value_old: value_old,
- value_new: value_new
- }
- })
- return index
- }
- function moveFromTo(stream, from, to) {
- if (from !== to) {
- stream.modify(function (diff) {
- var array = diff.array
- @assert.ok(from >= 0)
- @assert.ok(from < array.length)
- @assert.ok(to >= 0)
- var value = array[from]
- array.splice(from, 1)
- array.splice(to, 0, value)
- return {
- type: "move",
- array: array,
- index_from: from,
- index_to: to,
- value: value
- }
- })
- }
- return to
- }
- function push(stream, x) {
- var array = @current(stream).array
- return pushAt(stream, array.length, x)
- }
- // TODO allow for a custom comparison function
- function remove(stream, x) {
- var array = @current(stream).array
- var index = array.indexOf(x)
- @assert.isNot(index, -1)
- return removeAt(stream, index)
- }
- function replaceAt(stream, index, x) {
- return modifyAt(stream, index, function () {
- return x
- })
- }
- // TODO allow for a custom comparison function
- function moveTo(stream, x, to) {
- var array = @current(stream).array
- var index = array.indexOf(x)
- @assert.isNot(index, -1)
- return moveFromTo(stream, index, to)
- }
- // TODO this is a hack to work around the fact that Conductance requires a child node (it can't be null)
- function insertBefore(elem, child, value) {
- if (child == null) {
- return elem ..@appendContent(value)
- } else {
- return elem ..@insertBefore(child, value)
- }
- }
- // TODO needs a better name: it takes a `from` and `to` index and returns a corrected `to` index
- function getIndex(from, to) {
- if (from <= to) {
- return to + 1
- } else if (from > to) {
- return to
- }
- }
- // TODO this is probably unnecessary
- function initAnimation(elem) {
- elem.classList.remove("animation-start")
- elem.classList.remove("animation-end")
- }
- // TODO handling for if the animation never occurs
- function startAnimation(elem) {
- elem.classList.add("animation-start")
- elem.classList.remove("animation-end")
- // Force relayout
- getComputedStyle(elem).left
- elem.classList.remove("animation-start")
- elem.classList.add("animation-end")
- elem ..@wait("transitionend")
- elem.classList.remove("animation-end")
- }
- // TODO handling for if the animation never occurs
- function reverseAnimation(elem) {
- elem.classList.add("animation-start")
- elem.classList.add("animation-end")
- // Force relayout
- getComputedStyle(elem).left
- elem.classList.remove("animation-end")
- elem ..@wait("transitionend")
- elem.classList.remove("animation-start")
- }
- function toDOM(stream, f) {
- return @Mechanism(function (elem) {
- var seen = false
- // TODO does this drop events if the block function is suspended?
- // TODO it is necessary to use `each.par` or `each.track` to handle the situations where stuff changes in mid-animation
- // TODO is `each.par` or `each.track` better?
- stream ..@each.par(function (diff) {
- // TODO this is really hacky, there has to be a better way to grab the proper element
- var parent = elem.children[0]
- var children = parent.children
- // TODO more assertions for all this stuff
- if (seen) {
- if (diff.type === "add") {
- var child = children[diff.index]
- // TODO animate these in parallel with each.par?
- parent ..insertBefore(child, f(diff.value)) ..@each(startAnimation)
- } else if (diff.type === "remove") {
- var child = children[diff.index]
- @assert.ok(child)
- child ..reverseAnimation
- child ..@removeNode
- // Do not animate when replacing one node with another
- } else if (diff.type === "modify") {
- var from = children[diff.index]
- var to = children[getIndex(diff.index, diff.index)]
- @assert.ok(from)
- // TODO this waitfor is probably unnecessary, due to the lack of animation
- // TODO does this waitfor cause any performance problems?
- waitfor {
- // TODO can't use DOM methods because this needs to de/attach mechanisms and such
- from ..@removeNode
- } and {
- parent ..insertBefore(to, f(diff.value_new)) ..@each(initAnimation)
- }
- } else if (diff.type === "move") {
- var from = children[diff.index_from]
- var to = children[getIndex(diff.index_from, diff.index_to)]
- @assert.ok(from)
- waitfor {
- from ..reverseAnimation
- from ..@removeNode
- } and {
- // TODO animate these in parallel with each.par?
- parent ..insertBefore(to, f(diff.value)) ..@each(startAnimation)
- }
- } else {
- @assert.fail(diff.type)
- }
- // Don't animate anything the first time we render the DOM
- } else {
- seen = true
- // TODO do these in parallel with each.par?
- parent ..@appendContent(diff.array ..@map(f)) ..@each(initAnimation)
- }
- })
- })
- }
- // The actual app begins here
- var elements = ArrayDiff([
- { id: 1 },
- { id: 2 }
- ])
- var css = @CSS(`
- {
- box-sizing: border-box;
- display: block;
- transition: all 1s ease-in-out;
- border: 1px solid gainsboro;
- overflow: hidden;
- height: 35px;
- padding: 5px;
- margin: 0px;
- margin-bottom: -1px;
- }
- &.animation-start {
- opacity: 0;
- height: 0px;
- border-top-width: 0px;
- border-bottom-width: 0px;
- padding-top: 0px;
- padding-bottom: 0px;
- margin-top: 0px;
- margin-bottom: 0px;
- margin-left: 20px;
- }
- /* This is just to remove the circle from the left of <li> items */
- li {
- display: block;
- }
- `)
- var dom_elements = elements ..toDOM(function (x) {
- return `
- <li>${x.id}</li>
- ` ..css
- })
- waitfor {
- hold(500)
- var index = elements ..push({ id: 3 })
- hold(1100)
- elements ..replaceAt(index, { id: 4 })
- hold(500)
- elements ..push({ id: 5 })
- hold(500)
- elements ..moveFromTo(index, index + 1)
- hold(500)
- elements ..removeAt(index + 1)
- } and {
- @mainContent ..@appendContent(`<ul></ul>` ..dom_elements) { || hold() }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement