1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
|
# A board two nodes share. A change to a card goes whole to the other node
# (send changed { ..at }), and a card the other node sends is taken whole
# (cards[k] = Card { ..remote }) when it is the later change: by its time,
# then by the node's name. So both boards end the same, whatever order the
# changes come in, and after the nodes were cut apart and joined again.
#
# Each node runs it with a key of its own and links to the other, sealed:
# pure-serve --module shared-board.pmod --store a --key a.key --name a \
# --link-port 7001 --send changed=pure://127.0.0.1:7002/remote \
# --seal changed=B_KEY --sender b.changed=B_KEY
# then tells the app its name: POST /inputs/name {"name": "a"}. Upgraded
# one node after the other, a node takes the other's changes by field name
# (Pure docs/design/014-fields-by-name.md): add a field to the card and the
# output, nowhere else.
use vega
record Card {
title: text
who: text # whose card it is
lane = 0 # 0 to do, 1 doing, 2 done
node: text # the node that changed it last
since = 0 # when: milliseconds since 1970
}
state cards: list of Card by title
state at: index of cards
state me: text = "me"
input add = text
input forward = key ArrowRight
input back = key ArrowLeft
input up = key ArrowUp
input down = key ArrowDown
input name = message {
name: text
}
input assign = message {
title: text
who: text
}
output changed = message {
title: text
who: text
lane: u32
node: text
since: number
}
input remote = message like changed
fn lane_name(lane) = if lane == 0 { "to do" } else if lane == 1 { "doing" } else { "done" }
on add {
cards.add(Card { title: add, since: now, node: me })
at = cards[add]
send changed { ..at }
}
on forward.down {
at.lane = min(at.lane + 1, 2)
at.since = now
at.node = me
send changed { ..at }
}
on back.down {
at.lane = max(at.lane - 1, 0)
at.since = now
at.node = me
send changed { ..at }
}
on up.down { at = at - 1 }
on down.down { at = at + 1 }
on name { me = name.name }
on assign {
require cards.has(assign.title)
cards[assign.title].who = assign.who
cards[assign.title].since = now
cards[assign.title].node = me
send changed { ..cards[assign.title] }
}
on remote {
if !cards.has(remote.title) {
cards.add(Card { ..remote })
} else if (remote.since > cards[remote.title].since ||
(remote.since == cards[remote.title].since && remote.node > cards[remote.title].node)) {
cards[remote.title] = Card { ..remote }
}
}
view title = "{me}'s board"
view counts = "{count(c in cards where c.lane == 0)} to do, {count(c in cards where c.lane == 1)} doing, {count(c in cards where c.lane == 2)} done"
view cards_list = each c in cards {
label = c.title
badge = if c.who == "" { lane_name(c.lane) } else { "{c.who}, {lane_name(c.lane)}" }
tone = if c.lane == 2 { vega.muted } else { vega.when(c.lane == 1, vega.accent) }
}
view cards_list_focus = at.position
|