on master help sign in
Animate delivery status with opacity transitions; simplify server utilities with takeRelation
71b07d1iko
client/ViewChat.fs+56−33
@@ -13,22 +13,34 @@ module ViewChat =
13 open Buttons
13 open Buttons
14 open TextFields
14 open TextFields
15
15
16− let private marchingAntsStyle =
16+ let private ensureMarchingAntsStyle =
17+ lazy
18+ (let s =
19+ Avalonia.Styling.Style(fun sel ->
17− let s = Avalonia.Styling.Style(fun sel -> sel.OfType<Avalonia.Controls.Shapes.Rectangle>())
20+ sel.OfType<Avalonia.Controls.Shapes.Rectangle>().Class("marching"))
21+
18− let anim = Avalonia.Animation.Animation()
22+ let anim = Avalonia.Animation.Animation()
19− anim.Duration <- System.TimeSpan.FromSeconds 3.
23+ anim.Duration <- System.TimeSpan.FromSeconds 3.
20− anim.IterationCount <- Avalonia.Animation.IterationCount.Infinite
24+ anim.IterationCount <- Avalonia.Animation.IterationCount.Infinite
21− anim.Easing <- Avalonia.Animation.Easings.LinearEasing()
25+ anim.Easing <- Avalonia.Animation.Easings.LinearEasing()
22− let kf1 = Avalonia.Animation.KeyFrame()
26+ let kf1 = Avalonia.Animation.KeyFrame()
27+
28+ kf1.Setters.Add(
23− kf1.Setters.Add(Avalonia.Styling.Setter(Avalonia.Controls.Shapes.Shape.StrokeDashOffsetProperty, box 0.))
29+ Avalonia.Styling.Setter(Avalonia.Controls.Shapes.Shape.StrokeDashOffsetProperty, box 0.)
30+ )
31+
24− kf1.Cue <- Avalonia.Animation.Cue(0.)
32+ kf1.Cue <- Avalonia.Animation.Cue(0.)
25− let kf2 = Avalonia.Animation.KeyFrame()
33+ let kf2 = Avalonia.Animation.KeyFrame()
34+
35+ kf2.Setters.Add(
26− kf2.Setters.Add(Avalonia.Styling.Setter(Avalonia.Controls.Shapes.Shape.StrokeDashOffsetProperty, box 6.))
36+ Avalonia.Styling.Setter(Avalonia.Controls.Shapes.Shape.StrokeDashOffsetProperty, box 6.)
37+ )
38+
27− kf2.Cue <- Avalonia.Animation.Cue(1.)
39+ kf2.Cue <- Avalonia.Animation.Cue(1.)
28− anim.Children.Add(kf1)
40+ anim.Children.Add(kf1)
29− anim.Children.Add(kf2)
41+ anim.Children.Add(kf2)
30− s.Animations.Add(anim)
42+ s.Animations.Add(anim)
31− s :> Avalonia.Styling.IStyle
43+ Avalonia.Application.Current.Styles.Add(s))
32
44
33 let private scrollRef = Fabulous.ViewRef<Avalonia.Controls.ScrollViewer>()
45 let private scrollRef = Fabulous.ViewRef<Avalonia.Controls.ScrollViewer>()
34 let mutable private lastMsgCount = 0
46 let mutable private lastMsgCount = 0
@@ -43,28 +55,15 @@ module ViewChat =
43 | None -> ()
55 | None -> ()
44 , Avalonia.Threading.DispatcherPriority.Background) |> ignore
56 , Avalonia.Threading.DispatcherPriority.Background) |> ignore
45
57
58+ let private opacityTransition =
59+ DoubleTransition(Avalonia.Visual.OpacityProperty, System.TimeSpan.FromMilliseconds 500.)
60+
46 let private messageItem m =
61 let private messageItem m =
47 let align, cols, col =
62 let align, cols, col =
48 match m.Direction with
63 match m.Direction with
49 | Outgoing _ -> HorizontalAlignment.Right, [ Dimension.Stars 3.; Dimension.Stars 7. ], 1
64 | Outgoing _ -> HorizontalAlignment.Right, [ Dimension.Stars 3.; Dimension.Stars 7. ], 1
50 | Incoming -> HorizontalAlignment.Left, [ Dimension.Stars 7.; Dimension.Stars 3. ], 0
65 | Incoming -> HorizontalAlignment.Left, [ Dimension.Stars 7.; Dimension.Stars 3. ], 0
51
66
52− let dashed =
53− match m.Direction with
54− | Outgoing DeliveryStatus.Sent -> true
55− | _ -> false
56−
57− let border =
58− if dashed then
59− Rectangle()
60− .stroke(Avalonia.Media.Colors.Black)
61− .strokeThickness(4.)
62− .strokeDashArray([ 4.; 2. ])
63− .isHitTestVisible(false)
64− .style(marchingAntsStyle)
65− else
66− Rectangle().stroke(Avalonia.Media.Colors.Black).strokeThickness(4.).isHitTestVisible (false)
67−
68 Grid(cols, [ Dimension.Auto ]) {
67 Grid(cols, [ Dimension.Auto ]) {
69 Border(
68 Border(
70 (Grid() {
69 (Grid() {
@@ -75,7 +74,31 @@ module ViewChat =
75 .textWrapping(Avalonia.Media.TextWrapping.Wrap)
74 .textWrapping(Avalonia.Media.TextWrapping.Wrap)
76 .padding (8.)
75 .padding (8.)
77
76
78− border
77+ match m.Direction with
78+ | Outgoing status ->
79+ ensureMarchingAntsStyle.Force()
80+ let isSent = status = DeliveryStatus.Sent
81+
82+ Rectangle()
83+ .stroke(Avalonia.Media.Colors.Black)
84+ .strokeThickness(4.)
85+ .isHitTestVisible(false)
86+ .opacity(if isSent then 0. else 1.)
87+ .transition (opacityTransition)
88+
89+ Rectangle()
90+ .stroke(Avalonia.Media.Colors.Black)
91+ .strokeThickness(4.)
92+ .strokeDashArray([ 4.; 2. ])
93+ .isHitTestVisible(false)
94+ .opacity(if isSent then 1. else 0.)
95+ .classes("marching")
96+ .transition (opacityTransition)
97+ | Incoming ->
98+ Rectangle()
99+ .stroke(Avalonia.Media.Colors.Black)
100+ .strokeThickness(4.)
101+ .isHitTestVisible (false)
79 }))
102 }))
80 .horizontalAlignment(align)
103 .horizontalAlignment(align)
81 .margin(4.)
104 .margin(4.)
server.knot+10−43
@@ -438,9 +438,6 @@ isQueued = \ds -> case ds of DsQueued {} -> True {}; _ -> False {}
438nextSeqTs : Timestamp -> Timestamp -> Timestamp
438nextSeqTs : Timestamp -> Timestamp -> Timestamp
439nextSeqTs = \t seqCounter -> if t > seqCounter then t else seqCounter + 1
439nextSeqTs = \t seqCounter -> if t > seqCounter then t else seqCounter + 1
440
440
441−takeN : [a] -> Int -> [a]
442−takeN = \items n -> (fold (\acc e -> if acc.n >= n then acc else {n: acc.n + 1, items: union acc.items [e]}) {n: 0, items: []} items).items
443−
444dedupMessageIds : [MessageIdHex] -> [MessageIdHex]
441dedupMessageIds : [MessageIdHex] -> [MessageIdHex]
445dedupMessageIds = \messageIds ->
442dedupMessageIds = \messageIds ->
446 fold (\acc mid ->
443 fold (\acc mid ->
@@ -448,39 +445,11 @@ dedupMessageIds = \messageIds ->
448 else if any acc (\a -> a == mid) then acc
445 else if any acc (\a -> a == mid) then acc
449 else union acc [mid]) [] messageIds
446 else union acc [mid]) [] messageIds
450
447
451−boundedSmallest = \items n getKey ->
452− (fold (\acc a ->
453− let k = getKey a in
454− if acc.size < n
455− then
456− let newHeap = union acc.heap [a] in
457− let newSize = count newHeap in
458− if newSize > acc.size
459− then {size: newSize, heap: newHeap, heapMax: Just {value: case acc.heapMax of
460− Nothing {} -> k
461− Just {value: v} -> max k v
462− }}
463− else acc
464− else case acc.heapMax of
465− Nothing {} -> acc
466− Just {value: maxVal} ->
467− if k < maxVal
468− then
469− let removed = fold (\r x ->
470− if not r.done && getKey x == maxVal
471− then {done: True {}, items: r.items}
472− else {done: r.done, items: union r.items [x]}) {done: False {}, items: []} acc.heap in
473− let newHeap = union removed.items [a] in
474− let newMax = fold (\best x -> Just {value: case best of
475− Nothing {} -> getKey x
476− Just {value: v} -> max (getKey x) v
477− }) Nothing {} newHeap in
478− {size: acc.size, heap: newHeap, heapMax: newMax}
479− else acc) {size: 0, heap: [], heapMax: Nothing {}} items).heap
480−
481evictOldestAckedIds = \items n ->
448evictOldestAckedIds = \items n ->
482 if n <= 0 then items
449 if n <= 0 then items
450+ else do
451+ ii <- items
483− else diff items (boundedSmallest items n (\a -> a.ackedAt))
452+ diff items (takeRelation n ( sortBy (\a -> a.ackedAt) items))
484
453
485safeCompareLoop : Text -> Text -> Int -> Int -> Int
454safeCompareLoop : Text -> Text -> Int -> Int -> Int
486safeCompareLoop = \s1 s2 n result ->
455safeCompareLoop = \s1 s2 n result ->
@@ -553,7 +522,7 @@ capGossipPresence = \args ->
553 let updatesOnlyAfterOffline = removeOffline updatesOnlyMerged args.trustedOffline args.t in
522 let updatesOnlyAfterOffline = removeOffline updatesOnlyMerged args.trustedOffline args.t in
554 let reclaimSlots = maxPresenceEntries - count updatesOnlyAfterOffline in
523 let reclaimSlots = maxPresenceEntries - count updatesOnlyAfterOffline in
555 let newEntries = do e <- args.trustedDeduped; where none args.presence (\p -> samePubkeyServer p e); yield e in
524 let newEntries = do e <- args.trustedDeduped; where none args.presence (\p -> samePubkeyServer p e); yield e in
556− let reclaimedEntries = takeN newEntries reclaimSlots in
525+ let reclaimedEntries = takeRelation reclaimSlots newEntries in
557 {finalPresence: union updatesOnlyAfterOffline reclaimedEntries,
526 {finalPresence: union updatesOnlyAfterOffline reclaimedEntries,
558 appliedOnline: union
527 appliedOnline: union
559 (do e <- updatesOnly; where none args.presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt); yield (pubkeyServer e))
528 (do e <- updatesOnly; where none args.presence (\p -> samePubkeyServer p e && p.expiresAt >= e.expiresAt); yield (pubkeyServer e))
@@ -751,7 +720,7 @@ forwardQueuedMessages = \targets t -> do
751 where none forwards (\f -> f.messageId == m.messageId && f.toServer == target.server)
720 where none forwards (\f -> f.messageId == m.messageId && f.toServer == target.server)
752 yield {messageId: m.messageId, toServer: target.server, createdAt: t,
721 yield {messageId: m.messageId, toServer: target.server, createdAt: t,
753 toKey: m.toKey, encryptedBlob: m.encryptedBlob, timestamp: m.timestamp}
722 toKey: m.toKey, encryptedBlob: m.encryptedBlob, timestamp: m.timestamp}
754− let capped = takeN candidates (maxForwardsEntries - count forwards)
723+ let capped = takeRelation (maxForwardsEntries - count forwards) candidates
755 let updatedForwards = union forwards capped
724 let updatedForwards = union forwards capped
756 set *forwards = updatedForwards
725 set *forwards = updatedForwards
757 yield capped
726 yield capped
@@ -832,11 +801,10 @@ pollOnce = \token cursor startWakeup -> do
832 atomic do
801 atomic do
833 wakeup <- *pollWakeup
802 wakeup <- *pollWakeup
834 allMessages <- *messages
803 allMessages <- *messages
835− let matchingMsgs = do
804+ let limitedMsgs = takeRelation maxPollEvents (sortBy (\m -> m.receivedAt) (do
836 m <- allMessages
805 m <- allMessages
837 where m.toKey == pubkey && m.receivedAt >= t - messageMaxAge && m.receivedAt > clampedCursor
806 where m.toKey == pubkey && m.receivedAt >= t - messageMaxAge && m.receivedAt > clampedCursor
838− yield m
807+ yield m))
839− let limitedMsgs = boundedSmallest matchingMsgs maxPollEvents (\m -> m.receivedAt)
840 if count limitedMsgs > 0
808 if count limitedMsgs > 0
841 then
809 then
842 let evts = do
810 let evts = do
@@ -945,15 +913,14 @@ serve = \req -> case req of
945 seqCounter <- *seqCounter
913 seqCounter <- *seqCounter
946 let seqTs = nextSeqTs t seqCounter
914 let seqTs = nextSeqTs t seqCounter
947 set *seqCounter = seqTs
915 set *seqCounter = seqTs
948− let updatedMessages = union messages
916+ set *messages = union messages
949 [{messageId: msgId, toKey: to,
917 [{messageId: msgId, toKey: to,
950 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
918 encryptedBlob: encryptedBlob, timestamp: clampedTs, originServer: "", receivedAt: seqTs}]
951− set *messages = updatedMessages
952 let toPresence = do p <- presence; where p.pubkey == to && p.expiresAt > t; yield p
919 let toPresence = do p <- presence; where p.pubkey == to && p.expiresAt > t; yield p
953 let localOnline = any toPresence (\p -> isLocalServer p.server)
920 let localOnline = any toPresence (\p -> isLocalServer p.server)
954 let remoteServers = collectPeerServers toPresence (\p -> p.server)
921 let remoteServers = collectPeerServers toPresence (\p -> p.server)
955 forwards <- *forwards
922 forwards <- *forwards
956− let forwardServers = takeN remoteServers (maxForwardsEntries - count forwards)
923+ let forwardServers = takeRelation (maxForwardsEntries - count forwards) remoteServers
957 let shouldForward = federationEnabled && count forwardServers > 0
924 let shouldForward = federationEnabled && count forwardServers > 0
958 let delivery = if localOnline then DsDelivered {}
925 let delivery = if localOnline then DsDelivered {}
959 else if shouldForward then DsFederated {}
926 else if shouldForward then DsFederated {}
@@ -1004,7 +971,7 @@ serve = \req -> case req of
1004 ackedIds <- *ackedIds
971 ackedIds <- *ackedIds
1005 let combinedAckedIds = union ackedIds ackedFederated
972 let combinedAckedIds = union ackedIds ackedFederated
1006 let evictedEntries = let excessCount = count combinedAckedIds - maxAckedIds in
973 let evictedEntries = let excessCount = count combinedAckedIds - maxAckedIds in
1007− if excessCount <= 0 then [] else boundedSmallest combinedAckedIds excessCount (\a -> a.ackedAt)
974+ if excessCount <= 0 then [] else takeRelation excessCount (sortBy (\a -> a.ackedAt) combinedAckedIds)
1008 let remainingAckedIds = diff combinedAckedIds evictedEntries
975 let remainingAckedIds = diff combinedAckedIds evictedEntries
1009 set *ackedIds = remainingAckedIds
976 set *ackedIds = remainingAckedIds
1010 yield {pr: pr, ack: AckDone {pendingAcks: ackedFederated, evictedEntries: evictedEntries}})
977 yield {pr: pr, ack: AckDone {pendingAcks: ackedFederated, evictedEntries: evictedEntries}})