From ca25bbda5e09c106e464259142a1e27a7c4afcad Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 12 Nov 2024 10:28:20 -0600 Subject: [PATCH 001/149] wip: refactoring hooks --- desk/app/channels-server.hoon | 277 ++++++++++++++++++++++++++++++++-- desk/lib/channel-utils.hoon | 59 +++++++- desk/sur/hooks.hoon | 156 +++++++++++++++++++ 3 files changed, 477 insertions(+), 15 deletions(-) create mode 100644 desk/sur/hooks.hoon diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 8ba00a11d6..a16ac4b1bf 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -2,7 +2,7 @@ :: :: this is the server-side from which /app/channels gets its data. :: -/- c=channels, g=groups +/- c=channels, g=groups, h=hooks /+ utils=channel-utils, imp=import-aid /+ default-agent, verb, dbug, neg=negotiate :: @@ -16,8 +16,9 @@ |% +$ card card:agent:gall +$ current-state - $: %6 + $: %7 =v-channels:c + hooks=(map nest:c hooks:h) =pimp:imp == -- @@ -92,12 +93,22 @@ =? old ?=(%3 -.old) (state-3-to-4 old) =? old ?=(%4 -.old) (state-4-to-5 old) =? old ?=(%5 -.old) (state-5-to-6 old) - ?> ?=(%6 -.old) + =? old ?=(%6 -.old) (state-6-to-7 old) + ?> ?=(%7 -.old) =. state old inflate-io :: - +$ versioned-state $%(state-6 state-5 state-4 state-3 state-2 state-1 state-0) - +$ state-6 current-state + +$ versioned-state $%(state-7 state-6 state-5 state-4 state-3 state-2 state-1 state-0) + +$ state-7 current-state + +$ state-6 + $: %6 + =v-channels:c + =pimp:imp + == + ++ state-6-to-7 + |= state-6 + ^- state-7 + [%7 v-channels ~ pimp] +$ state-5 $: %5 =v-channels:v6:old:c @@ -321,6 +332,10 @@ [~ %| *] ~& [dap.bowl %overwriting-pending-import] cor(pimp `|+egg-any) == + :: + %hook-action-0 + =+ !<([=nest:c =action:h] vase) + ho-abet:(ho-action:(ho-abed:ho-core nest) action) == :: ++ run-import @@ -646,16 +661,26 @@ |= =c-post:c ^- [(unit u-channel:c) _posts.channel] ?> (can-write:ca-perms src.bowl writers.perm.perm.channel) + =/ =context:h (get-context channel) ?- -.c-post %add ?> =(src.bowl author.essay.c-post) ?> =(kind.nest -.kind-data.essay.c-post) + =^ result=(each event:h tang) cor + =/ =event:h [%on-post %add essay.c-post] + %- ho-run:(ho-abed:ho-core nest) + [event context 'post blocked'] + ?: ?=(%.n -.result) + ((slog p.result) [~ posts.channel]) + =/ =essay:c + ?> ?=([%on-post %add *] p.result) + essay.p.result =/ id=id-post:c |- =/ post (get:on-v-posts:c posts.channel now.bowl) ?~ post now.bowl $(now.bowl `@da`(add now.bowl ^~((div ~s1 (bex 16))))) - =/ new=v-post:c [[id ~ ~] 0 essay.c-post] + =/ new=v-post:c [[id ~ ~] 0 essay] :- `[%post id %set ~ new] (put:on-v-posts:c posts.channel id ~ new) :: @@ -666,8 +691,17 @@ ?~ post `posts.channel ?~ u.post `posts.channel ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) + =^ result=(each event:h tang) cor + =/ =event:h [%on-post %edit u.u.post essay.c-post] + %- ho-run:(ho-abed:ho-core nest) + [event context 'edit blocked'] + ?: ?=(%.n -.result) + ((slog p.result) [~ posts.channel]) + =/ =essay:c + ?> ?=([%on-post %edit *] p.result) + essay.p.result ::TODO could optimize and no-op if the edit is identical to current - =/ new=v-post:c [-.u.u.post +(rev.u.u.post) essay.c-post] + =/ new=v-post:c [-.u.u.post +(rev.u.u.post) essay] :- `[%post id.c-post %set ~ new] (put:on-v-posts:c posts.channel id.c-post ~ new) :: @@ -676,6 +710,11 @@ ?~ post `(put:on-v-posts:c posts.channel id.c-post ~) ?~ u.post `posts.channel ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) + =^ result=(each event:h tang) cor + =/ =event:h [%on-post %del u.u.post] + %- ho-run:(ho-abed:ho-core nest) + [event context 'delete blocked'] + ?> =(& -.result) :- `[%post id.c-post %set ~] (put:on-v-posts:c posts.channel id.c-post ~) :: @@ -683,7 +722,23 @@ =/ post (get:on-v-posts:c posts.channel id.c-post) ?~ post `posts.channel ?~ u.post `posts.channel - =/ [update=? reacts=v-reacts:c] (ca-c-react reacts.u.u.post c-post) + =^ result=(each event:h tang) cor + =/ =event:h + :* %on-post %react u.u.post + ?: ?=(%del-react -.c-post) [p.c-post ~] + [p `q]:c-post + == + %- ho-run:(ho-abed:ho-core nest) + [event context 'react action blocked'] + ?: ?=(%.n -.result) + ((slog p.result) [~ posts.channel]) + =/ new=c-post:c + ?> ?=([%on-post %react *] p.result) + ?~ react.p.result [%del-react id.c-post ship.p.result] + [%add-react id.c-post [ship u.react]:p.result] + =/ [update=? reacts=v-reacts:c] + %+ ca-c-react reacts.u.u.post + ?>(?=(?(%add-react %del-react) -.new) new) ?. update `posts.channel :- `[%post id.c-post %reacts reacts] (put:on-v-posts:c posts.channel id.c-post ~ u.u.post(reacts reacts)) @@ -693,25 +748,35 @@ ?~ post `posts.channel ?~ u.post `posts.channel =^ update=(unit u-post:c) replies.u.u.post - (ca-c-reply replies.u.u.post c-reply.c-post) + (ca-c-reply u.u.post c-reply.c-post context) ?~ update `posts.channel :- `[%post id.c-post u.update] (put:on-v-posts:c posts.channel id.c-post ~ u.u.post) == :: ++ ca-c-reply - |= [replies=v-replies:c =c-reply:c] - ^- [(unit u-post:c) _replies] + |= [parent=v-post:c =c-reply:c =context:h] + ^- [(unit u-post:c) v-replies:c] + =* replies replies.parent ?- -.c-reply %add ?> =(src.bowl author.memo.c-reply) + =^ result=(each event:h tang) cor + =/ =event:h [%on-reply %add parent memo.c-reply] + %- ho-run:(ho-abed:ho-core nest) + [event context 'reply blocked'] + ?: ?=(%.n -.result) + ((slog p.result) [~ replies]) + =/ =memo:c + ?> ?=([%on-reply %add *] p.result) + memo.p.result =/ id=id-reply:c |- =/ reply (get:on-v-replies:c replies now.bowl) ?~ reply now.bowl $(now.bowl `@da`(add now.bowl ^~((div ~s1 (bex 16))))) =/ reply-seal=v-reply-seal:c [id ~] - =/ new=v-reply:c [reply-seal 0 memo.c-reply] + =/ new=v-reply:c [reply-seal 0 memo] :- `[%reply id %set ~ new] (put:on-v-replies:c replies id ~ new) :: @@ -720,8 +785,17 @@ ?~ reply `replies ?~ u.reply `replies ?> =(src.bowl author.u.u.reply) + =^ result=(each event:h tang) cor + =/ =event:h [%on-reply %edit parent u.u.reply memo.c-reply] + %- ho-run:(ho-abed:ho-core nest) + [event context 'edit blocked'] + ?: ?=(%.n -.result) + ((slog p.result) [~ replies]) + =/ =memo:c + ?> ?=([%on-reply %edit *] p.result) + memo.p.result ::TODO could optimize and no-op if the edit is identical to current - =/ new=v-reply:c [-.u.u.reply +(rev.u.u.reply) memo.c-reply] + =/ new=v-reply:c [-.u.u.reply +(rev.u.u.reply) memo] :- `[%reply id.c-reply %set ~ new] (put:on-v-replies:c replies id.c-reply ~ new) :: @@ -730,6 +804,11 @@ ?~ reply `(put:on-v-replies:c replies id.c-reply ~) ?~ u.reply `replies ?> |(=(src.bowl author.u.u.reply) (is-admin:ca-perms src.bowl)) + =^ result=(each event:h tang) cor + =/ =event:h [%on-reply %del parent u.u.reply] + %- ho-run:(ho-abed:ho-core nest) + [event context 'delete blocked'] + ?> =(& -.result) :- `[%reply id.c-reply %set ~] (put:on-v-replies:c replies id.c-reply ~) :: @@ -737,7 +816,23 @@ =/ reply (get:on-v-replies:c replies id.c-reply) ?~ reply `replies ?~ u.reply `replies - =/ [update=? reacts=v-reacts:c] (ca-c-react reacts.u.u.reply c-reply) + =^ result=(each event:h tang) cor + =/ =event:h + :* %on-reply %react parent u.u.reply + ?: ?=(%del-react -.c-reply) [p.c-reply ~] + [p `q]:c-reply + == + %- ho-run:(ho-abed:ho-core nest) + [event context 'delete blocked'] + ?: ?=(%.n -.result) + ((slog p.result) [~ replies]) + =/ new=c-reply:c + ?> ?=([%on-reply %react *] p.result) + ?~ react.p.result [%del-react id.c-reply ship.p.result] + [%add-react id.c-reply [ship u.react]:p.result] + =/ [update=? reacts=v-reacts:c] + %+ ca-c-react reacts.u.u.reply + ?>(?=(?(%add-react %del-react) -.new) new) ?. update `replies :- `[%reply id.c-reply %reacts reacts] (put:on-v-replies:c replies id.c-reply ~ u.u.reply(reacts reacts)) @@ -836,4 +931,158 @@ (said:utils nest plan posts.channel) (give %kick ~ ~) -- +++ scry-path + |= [=dude:gall =path] + %+ welp + /(scot %p our.bowl)/[dude]/(scot %da now.bowl) + path +++ get-context + |= =v-channel:c + ^- context:h + =* flag group.perm.perm.v-channel + =/ =group:g + ?. .^(? %gu (scry-path %groups /$)) *group:g + ?. .^(? %gx (scry-path %groups /exists/(scot %p p.flag)/[q.flag]/noun)) + *group:g + .^(group:g %gx (scry-path %groups /groups/(scot %p p.flag)/[q.flag]/v1/noun)) + :* v-channel + v-channels + group + !>(~) :: we default this because each hook will replace with its own + [now our src eny]:bowl + == +:: +++ ho-core + |_ [=nest:c hks=hooks:h ctx=context:h gone=_|] + ++ ho-core . + ++ emit |=(=card ho-core(cor (^emit card))) + ++ emil |=(caz=(list card) ho-core(cor (^emil caz))) + ++ give |=(=gift:agent:gall ho-core(cor (^give gift))) + ++ ho-abet + %_ cor + hooks + ?:(gone (~(del by hooks) nest) (~(put by hooks) nest hks)) + == + :: + ++ ho-abed + |= n=nest:c + ho-core(nest n, hks (~(gut by hooks) n *hooks:h)) + :: + ++ ho-action + |= =action:h + ^+ ho-core + ?> (is-admin:ca-perms:(ca-abed:ca-core nest) src.bowl) + ?- -.action + %add + ~& "adding hook {}" + =/ =id:h (rsh [3 48] eny.bowl) + =/ src=(rev:c (unit @t)) [0 `src.action] + =/ result=(each nock tang) + ~& "compiling hook" + ((compile:utils args:h (return:h *)) `src.action) + ~& "compilation result: {}" + =/ compiled + ?: ?=(%| -.result) ~ + `p.result + =. order.hks + +:(next-rev:c order.hks (snoc +.order.hks id)) + =. hooks.hks + %+ ~(put by hooks.hks) id + [id name.action & src compiled cron.action !>(~)] + ho-core + :: + %edit + ?~ old-hook=(~(get by hooks.hks) id.action) ho-core + =/ hook u.old-hook + =^ src-changed src.hook + (next-rev:c src.hook `src.action) + =/ name-changed !=(name.action name.hook) + =/ cron-changed !=(cron.action cron.hook) + ?. |(src-changed name-changed cron-changed) ho-core + =. name.hook name.action + =. cron.hook cron.action + =. compiled.hook + ?~ +.src.hook ~ + =/ result=(each nock tang) + ((compile:utils args:h return:h) +.src.hook) + ?: ?=(%| -.result) ~ + `p.result + =. hooks.hks (~(put by hooks.hks) id.action hook) + ho-core + :: + %del + :: TODO: make more CRDT + =. hooks.hks (~(del by hooks.hks) id.action) + =/ [* new-order=_order.hks] + %+ next-rev:c order.hks + %+ skim +.order.hks + |= =id:h + !=(id id.action) + =. order.hks new-order + ho-core + :: + %enable + =/ hook (~(got by hooks.hks) id.action) + =. hooks.hks (~(put by hooks.hks) id.action hook(enabled &)) + ho-core + :: + %disable + =/ hook (~(got by hooks.hks) id.action) + =. hooks.hks (~(put by hooks.hks) id.action hook(enabled |)) + ho-core + :: + %order + =^ changed order.hks + (next-rev:c order.hks seq.action) + ho-core + == + ++ ho-run + |= [=event:h =context:h default=cord] + =^ [result=(each event:h tang) effects=(list effect:h)] hks + (run-hooks:utils event context default hks) + =. hooks (~(put by hooks) nest hks) + [result (ho-run-effects effects)] + ++ ho-run-effects + |= effects=(list effect:h) + ^+ cor + |- + ?~ effects cor + =/ =effect:h i.effects + =; new-cor + =. cor new-cor + $(effects t.effects) + ?- -.effect + %channels + =/ =cage channel-action+!>(a-channels.effect) + (emit [%pass /hooks/effect %agent [our.bowl %channels] %poke cage]) + :: + %groups + =/ =cage group-action-3+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %groups] %poke cage]) + :: + %activity + =/ =cage activity-action+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %activity] %poke cage]) + :: + %dm + =/ =cage chat-dm-action+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %chat] %poke cage]) + :: + %club + =/ =cage chat-club-action+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %chat] %poke cage]) + :: + %contacts + =/ =cage contacts-action-1+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %contacts] %poke cage]) + :: + %delay + =/ fires-at (add now.bowl wait.effect) + =. delayed.hks + %+ ~(put by delayed.hks) id.effect + +:effect(data [data.effect fires-at]) + =/ =wire /hooks/delayed/(scot %uv id.effect) + (emit [%pass wire %arvo %b %wait fires-at]) + == + -- -- diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index 26e070491c..b41dd9419d 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -1,4 +1,4 @@ -/- c=channels, g=groups, ci=cite +/- c=channels, g=groups, ci=cite, h=hooks :: convert a post to a preview for a "said" response :: |% @@ -676,4 +676,61 @@ ;br; == -- +++ subject ^~(!>([..subject ..zuse])) +++ compile + |* [args=mold return=mold] + |= src=(unit @t) + ^- (each nock tang) + ?~ src |+~['no src'] + ~& %a + =/ tonk=(each (pair type nock) tang) + ~& %b + =/ vex=(like hoon) ((full vest) [0 0] (trip u.src)) + ~& %c + ?~ q.vex |+~[leaf+"\{{} {}}" 'syntax error'] + ~& %d + %- mule + |.((~(mint ut -:subject) %noun p.u.q.vex)) + ~& %e + ~& "parsed hoon: {<-.tonk>}" + ~& %f + ?: ?=(%| -.tonk) + ~& "returning error" + tonk + &+q.p.tonk +++ execute + |* prod=mold + |= [=nock simp=*] + ^- (unit prod) + %- (soft prod) + (slum .*(+:subject nock) simp) +:: +++ run-hooks + |= [=event:h =context:h default=cord hks=hooks:h] + ^- [[(each event:h tang) (list effect:h)] hooks:h] + =/ current-event event + =| effects=(list effect:h) + =* order +.order.hks + |- + ?~ order + [[&+current-event effects] hks] + =* next $(order t.order) + =/ hook (~(got by hooks.hks) i.order) + ?~ compiled.hook next + =/ =args:h [current-event context(state state.hook)] + =/ outcome=(unit outcome:h) + ((execute outcome:h) u.compiled.hook args) + ~& "{(trip name.hook)} hook run: {}" + ?~ outcome next + ?: ?=(%.n -.u.outcome) + ~& "hook failed:" + %- (slog p.u.outcome) + next + =* result result.p.u.outcome + =. effects (weld effects effects.p.u.outcome) + =. hooks.hks (~(put by hooks.hks) i.order hook(state new-state.p.u.outcome)) + ?: ?=(%denied -.result) + [[|+~[(fall msg.result default)] effects] hks] + =. current-event new.result + next -- diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon new file mode 100644 index 0000000000..77a0bf2efc --- /dev/null +++ b/desk/sur/hooks.hoon @@ -0,0 +1,156 @@ +/- *channels, g=groups, a=activity, ch=chat, co=contacts +|% +:: $id: a unique identifier for a hook ++$ id @uv +:: +:: $hook: a pure function that runs on triggers in a channel +:: +:: $id: a unique identifier for the hook +:: $name: a human-readable name for the hook +:: $origin: whether or not this hook was added by us or came through an update +:: $src: the source code of the hook +:: $compiled: the compiled nock of the hook +:: $cron: the cron schedule for the hook if it has one +:: $state: the current state of the hook +:: +++ hook + $: =id + name=@t + enabled=? + src=(rev src=(unit @t)) + compiled=(unit nock) + cron=(unit @dr) + state=vase + == +:: $hooks: collection of hooks, the order they should be run in, and +:: any delayed hooks that need to be run +++ hooks + $: hooks=(map id hook) + order=(rev (list id)) + delayed=(map id delayed-hook) + == +:: $delayed-hook: metadata for when a delayed hook fires from the timer ++$ delayed-hook + $: =id + hook=id + wait=@dr + data=vase + fires-at=time + == +:: ++$ action + $% [%add name=@t src=@t cron=(unit @dr)] + [%edit =id name=@t src=@t cron=(unit @dr)] + [%del =id] + [%enable =id] + [%disable =id] + [%order seq=(list id)] + == ++$ response + $% [%set =id name=@t src=(unit @t) error=(unit tang)] + [%order seq=(list id)] + == +:: $context: ambient state that a hook should know about not +:: necessarily tied to a specific event +:: +:: $channel: the channel that the hook is operating on +:: $channels: all the channels in the group +:: $group: the group that the channel belongs to +:: $state: the current state of the hook +:: $now: the current time +:: $our: the ship that the hook is running on +:: $src: the ship that triggered the hook +:: $eny: entropy for random number generation or key derivation +:: ++$ context + $: v-channel + channels=v-channels + =group:g + state=vase + now=time + our=ship + src=ship + eny=@ + == +:: +:: $on-post: a hook event that fires when posts are interacted with ++$ on-post + $% [%add =essay] + [%edit original=v-post =essay] + [%del original=v-post] + [%react post=v-post =ship react=(unit react)] + == +:: +:: $on-reply: a hook event that fires when replies are interacted with ++$ on-reply + $% [%add parent=v-post =memo] + [%edit parent=v-post original=v-reply =memo] + [%del parent=v-post original=v-reply] + [%react parent=v-post reply=v-reply =ship react=(unit react)] + == +:: $event-type: the type of event that triggers a hook ++$ event-type ?(%on-post %on-reply %cron %delay) +:: +:: $event: the data associated with the trigger of a hook +:: +:: $on-post: a post was added, edited, deleted, or reacted to +:: $on-reply: a reply was added, edited, deleted, or reacted to +:: $cron: a scheduled wake-up +:: $delay: a delayed invocation of the hook called with metadata about +:: when it fired, its id, and the event it should run with +:: ++$ event + $% [%on-post on-post] + [%on-reply on-reply] + [%cron ~] + [%delay delayed-hook] + == +:: +:: $args: the arguments passed to a hook ++$ args + $: event=event + context=context + == +:: +:: $result: the result of a hook running +:: +:: $allowed: represents the action being allowed to go through, and the +:: new value of the action +:: $denied: represents the action being denied along with the reason +:: that the action was denied +:: $error: represents an error that occurred while running the hook +:: ++$ result + $% [%allowed new=event] + [%denied msg=(unit cord)] + == +:: +:: $effect: an effect that a hook can have, limited to agents in +:: the %groups desk. $delay is a special effect that will wake up the +:: same hook at a later time. ++$ effect + $% [%channels =a-channels] + [%groups =action:g] + [%activity =action:a] + [%dm =action:dm:ch] + [%club =action:club:ch] + [%contacts =action:co] + [%delay =id hook=id wait=@dr data=vase] + == +:: +:: $return: the data returned from a hook +:: +:: $result: whether the action was allowed or denied, any new values, +:: or an error message if something went wrong +:: $actions: any actions that should be taken on other agents or delay +:: $new-state: the new state of the hook after running +:: ++$ return + $: $: =result + effects=(list effect) + == + new-state=vase + == +:: ++$ outcome (each return tang) +-- \ No newline at end of file From fb4daf2a5bedbe40d87d591996a109d792e1113e Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 12 Nov 2024 11:31:33 -0600 Subject: [PATCH 002/149] channels-server: fix compile issue --- desk/app/channels-server.hoon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index a16ac4b1bf..71d54ad770 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1048,8 +1048,8 @@ |- ?~ effects cor =/ =effect:h i.effects - =; new-cor - =. cor new-cor + =; new-cor=_ho-core + =. ho-core new-cor $(effects t.effects) ?- -.effect %channels From 724ea8cf78875a14c3159295e76e43865e74d1c7 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 14 Nov 2024 14:47:24 -0600 Subject: [PATCH 003/149] channels-server: hooks mostly working, delay needs testing --- desk/app/channels-server.hoon | 189 ++++++++++++++++++++++++++-------- desk/gen/hooks/create.hoon | 13 +++ desk/gen/hooks/truncate.hoon | 97 +++++++++++++++++ desk/lib/channel-utils.hoon | 36 ++++--- desk/mar/hook/action-0.hoon | 12 +++ desk/sur/hooks.hoon | 2 +- 6 files changed, 290 insertions(+), 59 deletions(-) create mode 100644 desk/gen/hooks/create.hoon create mode 100644 desk/gen/hooks/truncate.hoon create mode 100644 desk/mar/hook/action-0.hoon diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 71d54ad770..ac95188c32 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -67,7 +67,12 @@ abet:(agent:cor wire sign) [cards this] :: - ++ on-arvo on-arvo:def + ++ on-arvo + |= [=wire sign=sign-arvo] + ^- (quip card _this) + =^ cards state + abet:(arvo:cor wire sign) + [cards this] -- :: |_ [=bowl:gall cards=(list card)] @@ -287,7 +292,20 @@ inflate-io :: ++ inflate-io - (safe-watch /groups [our.bowl %groups] /groups) + =. cor (safe-watch /groups [our.bowl %groups] /groups) + %+ roll ~(tap by hooks) + |= [[=nest:c hks=hooks:h] cr=_cor] + %+ roll ~(tap by hooks.hks) + |= [[=id:h =hook:h] co=_cr] + ?~ cron.hook co + ?~ compiled.hook co + ?. enabled.hook co + ?^ delay=(~(get by delayed.hks) id) co + :: only start timers for crons that haven't already been started + =/ fires-at (add now.bowl u.cron.hook) + =- ho-abet.- + %- ho-schedule:(ho-abed:ho-core:co nest) + [%cron id id u.cron.hook !>(~) fires-at] :: ++ poke |= [=mark =vase] @@ -437,6 +455,14 @@ %- (slog 'diary-server: poke failure' >wire< u.p.sign) cor == + :: + [%hooks %effect ~] + ?+ -.sign !! + %poke-ack + ?~ p.sign cor + %- (slog 'hook effect: poke failure' >wire< u.p.sign) + cor + == :: [%groups ~] ?+ -.sign !! @@ -474,6 +500,16 @@ == == :: +++ arvo + |= [=(pole knot) sign=sign-arvo] + ^+ cor + ?+ pole ~|(bad-arvo-take/pole !!) + [%hooks =kind:c ship=@ name=@ rest=*] + =/ ship (slav %p ship.pole) + =/ =nest:c [kind.pole ship name.pole] + ho-abet:(ho-arvo:(ho-abed:ho-core nest) rest.pole) + == +:: ++ watch-groups (safe-watch /groups [our.bowl %groups] /groups) ++ take-groups |= =action:g @@ -651,7 +687,7 @@ (ca-update %perm perm.channel) :: %post - =^ update=(unit u-channel:c) posts.channel + =^ update=(unit u-channel:c) ca-core (ca-c-post c-post.c-channel) ?~ update ca-core (ca-update u.update) @@ -659,9 +695,10 @@ :: ++ ca-c-post |= =c-post:c - ^- [(unit u-channel:c) _posts.channel] + ^- [(unit u-channel:c) _ca-core] ?> (can-write:ca-perms src.bowl writers.perm.perm.channel) =/ =context:h (get-context channel) + =* no-op `ca-core ?- -.c-post %add ?> =(src.bowl author.essay.c-post) @@ -671,7 +708,7 @@ %- ho-run:(ho-abed:ho-core nest) [event context 'post blocked'] ?: ?=(%.n -.result) - ((slog p.result) [~ posts.channel]) + ((slog p.result) [~ ca-core]) =/ =essay:c ?> ?=([%on-post %add *] p.result) essay.p.result @@ -682,33 +719,33 @@ $(now.bowl `@da`(add now.bowl ^~((div ~s1 (bex 16))))) =/ new=v-post:c [[id ~ ~] 0 essay] :- `[%post id %set ~ new] - (put:on-v-posts:c posts.channel id ~ new) + ca-core(posts.channel (put:on-v-posts:c posts.channel id ~ new)) :: %edit ?> |(=(src.bowl author.essay.c-post) (is-admin:ca-perms src.bowl)) ?> =(kind.nest -.kind-data.essay.c-post) =/ post (get:on-v-posts:c posts.channel id.c-post) - ?~ post `posts.channel - ?~ u.post `posts.channel + ?~ post no-op + ?~ u.post no-op ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-post %edit u.u.post essay.c-post] %- ho-run:(ho-abed:ho-core nest) [event context 'edit blocked'] ?: ?=(%.n -.result) - ((slog p.result) [~ posts.channel]) + ((slog p.result) no-op) =/ =essay:c ?> ?=([%on-post %edit *] p.result) essay.p.result ::TODO could optimize and no-op if the edit is identical to current =/ new=v-post:c [-.u.u.post +(rev.u.u.post) essay] :- `[%post id.c-post %set ~ new] - (put:on-v-posts:c posts.channel id.c-post ~ new) + ca-core(posts.channel (put:on-v-posts:c posts.channel id.c-post ~ new)) :: %del =/ post (get:on-v-posts:c posts.channel id.c-post) - ?~ post `(put:on-v-posts:c posts.channel id.c-post ~) - ?~ u.post `posts.channel + ?~ post `ca-core(posts.channel (put:on-v-posts:c posts.channel id.c-post ~)) + ?~ u.post no-op ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-post %del u.u.post] @@ -716,12 +753,12 @@ [event context 'delete blocked'] ?> =(& -.result) :- `[%post id.c-post %set ~] - (put:on-v-posts:c posts.channel id.c-post ~) + ca-core(posts.channel (put:on-v-posts:c posts.channel id.c-post ~)) :: ?(%add-react %del-react) =/ post (get:on-v-posts:c posts.channel id.c-post) - ?~ post `posts.channel - ?~ u.post `posts.channel + ?~ post no-op + ?~ u.post no-op =^ result=(each event:h tang) cor =/ =event:h :* %on-post %react u.u.post @@ -731,7 +768,7 @@ %- ho-run:(ho-abed:ho-core nest) [event context 'react action blocked'] ?: ?=(%.n -.result) - ((slog p.result) [~ posts.channel]) + ((slog p.result) no-op) =/ new=c-post:c ?> ?=([%on-post %react *] p.result) ?~ react.p.result [%del-react id.c-post ship.p.result] @@ -739,19 +776,25 @@ =/ [update=? reacts=v-reacts:c] %+ ca-c-react reacts.u.u.post ?>(?=(?(%add-react %del-react) -.new) new) - ?. update `posts.channel + ?. update no-op :- `[%post id.c-post %reacts reacts] - (put:on-v-posts:c posts.channel id.c-post ~ u.u.post(reacts reacts)) + %= ca-core + posts.channel + (put:on-v-posts:c posts.channel id.c-post ~ u.u.post(reacts reacts)) + == :: %reply =/ post (get:on-v-posts:c posts.channel id.c-post) - ?~ post `posts.channel - ?~ u.post `posts.channel + ?~ post no-op + ?~ u.post no-op =^ update=(unit u-post:c) replies.u.u.post (ca-c-reply u.u.post c-reply.c-post context) - ?~ update `posts.channel + ?~ update no-op :- `[%post id.c-post u.update] - (put:on-v-posts:c posts.channel id.c-post ~ u.u.post) + %= ca-core + posts.channel + (put:on-v-posts:c posts.channel id.c-post ~ u.u.post) + == == :: ++ ca-c-reply @@ -940,15 +983,15 @@ |= =v-channel:c ^- context:h =* flag group.perm.perm.v-channel - =/ =group:g - ?. .^(? %gu (scry-path %groups /$)) *group:g + =/ =group-ui:g + ?. .^(? %gu (scry-path %groups /$)) *group-ui:g ?. .^(? %gx (scry-path %groups /exists/(scot %p p.flag)/[q.flag]/noun)) - *group:g - .^(group:g %gx (scry-path %groups /groups/(scot %p p.flag)/[q.flag]/v1/noun)) + *group-ui:g + .^(group-ui:g %gx (scry-path %groups /groups/(scot %p p.flag)/[q.flag]/v1/noun)) :* v-channel v-channels - group - !>(~) :: we default this because each hook will replace with its own + group-ui + *hook:h :: we default this because each hook will replace with itself [now our src eny]:bowl == :: @@ -979,11 +1022,17 @@ =/ src=(rev:c (unit @t)) [0 `src.action] =/ result=(each nock tang) ~& "compiling hook" - ((compile:utils args:h (return:h *)) `src.action) - ~& "compilation result: {}" + ((compile:utils args:h outcome:h) `src.action) =/ compiled - ?: ?=(%| -.result) ~ + ?: ?=(%| -.result) + ((slog 'compilation result:' p.result) ~) + ~& "compilation result: {}" `p.result + =. ho-core + ?~ cron.action ho-core + =/ fires-at (add now.bowl u.cron.action) + =/ dh [id id u.cron.action !>(~) fires-at] + (ho-schedule %cron dh) =. order.hks +:(next-rev:c order.hks (snoc +.order.hks id)) =. hooks.hks @@ -1008,10 +1057,17 @@ ?: ?=(%| -.result) ~ `p.result =. hooks.hks (~(put by hooks.hks) id.action hook) - ho-core + ?. cron-changed ho-core + ?~ cron.action ho-core + =. ho-core (ho-unschedule id.action %cron) + =/ fires-at (add now.bowl u.cron.action) + =/ dh [id.action id.action u.cron.action !>(~) fires-at] + (ho-schedule %cron dh) :: %del :: TODO: make more CRDT + ?~ hook=(~(get by hooks.hks) id.action) ho-core + =. ho-core (ho-unschedule id.action %cron) =. hooks.hks (~(del by hooks.hks) id.action) =/ [* new-order=_order.hks] %+ next-rev:c order.hks @@ -1024,12 +1080,15 @@ %enable =/ hook (~(got by hooks.hks) id.action) =. hooks.hks (~(put by hooks.hks) id.action hook(enabled &)) - ho-core + ?~ cron.hook ho-core + =/ fires-at (add now.bowl u.cron.hook) + =/ dh [id.action id.action u.cron.hook !>(~) fires-at] + (ho-schedule %cron dh) :: %disable =/ hook (~(got by hooks.hks) id.action) =. hooks.hks (~(put by hooks.hks) id.action hook(enabled |)) - ho-core + (ho-unschedule id.action %cron) :: %order =^ changed order.hks @@ -1040,13 +1099,26 @@ |= [=event:h =context:h default=cord] =^ [result=(each event:h tang) effects=(list effect:h)] hks (run-hooks:utils event context default hks) - =. hooks (~(put by hooks) nest hks) - [result (ho-run-effects effects)] + [result ho-abet:(ho-run-effects effects)] + ++ ho-run-single + |= [=event:h prefix=tape =hook:h] + ?~ channel=(~(get by v-channels) nest) ho-core + =/ =context:h (get-context u.channel) + =/ return=(unit return:h) + (run-hook:utils event context hook) + ?~ return + ~& "{prefix} {} failed" + ho-core + ~& "{prefix} {} ran" + =. hooks.hks + (~(put by hooks.hks) id.hook hook(state new-state.u.return)) + (ho-run-effects effects.u.return) ++ ho-run-effects |= effects=(list effect:h) - ^+ cor + ^+ ho-core |- - ?~ effects cor + ?~ effects + ho-core =/ =effect:h i.effects =; new-cor=_ho-core =. ho-core new-cor @@ -1078,11 +1150,42 @@ :: %delay =/ fires-at (add now.bowl wait.effect) - =. delayed.hks - %+ ~(put by delayed.hks) id.effect - +:effect(data [data.effect fires-at]) - =/ =wire /hooks/delayed/(scot %uv id.effect) - (emit [%pass wire %arvo %b %wait fires-at]) + =/ dh +:effect(data [data.effect fires-at]) + =. ho-core (ho-unschedule id.effect %delayed) + (ho-schedule %delayed dh) + == + ++ ho-schedule + |= [type=@tas dh=delayed-hook:h] + ^+ ho-core + ~& "scheduling hook" + =. delayed.hks (~(put by delayed.hks) id.dh dh) + =/ =wire (welp ho-prefix /[type]/(scot %uv id.dh)) + (emit [%pass wire %arvo %b %wait fires-at.dh]) + ++ ho-unschedule + |= [=id:h type=@tas] + ?~ previous=(~(get by delayed.hks) id) ho-core + =/ =wire (welp ho-prefix /[type]/(scot %uv id)) + (emit [%pass wire %arvo %b %rest fires-at.u.previous]) + ++ ho-arvo + |= =(pole knot) + ^+ ho-core + ?+ pole ~|(bad-arvo-take/pole !!) + [%delayed id=@ ~] + =/ =id:h (slav %uv id.pole) + ?~ delay=(~(get by delayed.hks) id) ho-core + ?~ hook=(~(get by hooks.hks) hook.u.delay) ho-core + (ho-run-single [%delay u.delay] "delayed hook" u.hook) + :: + [%cron id=@ ~] + =/ =id:h (slav %uv id.pole) + ?~ delay=(~(get by delayed.hks) id) ho-core + ?~ hook=(~(get by hooks.hks) id) ho-core + :: if unscheduled, ignore + ?~ cron.u.hook ho-core + =/ next (add now.bowl u.cron.u.hook) + =. ho-core (ho-schedule %cron u.delay(fires-at next)) + (ho-run-single [%cron ~] "cron job" u.hook) == + ++ ho-prefix /hooks/[kind.nest]/(scot %p ship.nest)/[name.nest] -- -- diff --git a/desk/gen/hooks/create.hoon b/desk/gen/hooks/create.hoon new file mode 100644 index 0000000000..3b02b7d70e --- /dev/null +++ b/desk/gen/hooks/create.hoon @@ -0,0 +1,13 @@ +/- c=channels, h=hooks +:- %say +|= $: [now=@da eny=@uvJ =beak] + [[=nest:c name=@t src=@t cron=(unit @dr) ~] ~] + == +:- %hook-action-0 +^- [nest:c action:h] +:* nest + %add + name + src + cron +== \ No newline at end of file diff --git a/desk/gen/hooks/truncate.hoon b/desk/gen/hooks/truncate.hoon new file mode 100644 index 0000000000..af66032416 --- /dev/null +++ b/desk/gen/hooks/truncate.hoon @@ -0,0 +1,97 @@ +/- h=hooks, c=channels +:- %say +|= $: [now=@da eny=@uvJ =beak] + [[=event:h ~] ~] + == +:- %noun +^- outcome:h +=| count=@ud +=/ max 140 +=/ new-content=story:c ~ +=* no-op &+[[[%allowed event] ~] !>(~)] +?. ?=(%on-post -.event) no-op +=* on-post +.event +?. ?=(?(%add %edit) -.on-post) no-op +=/ verses + ?- -.on-post + %add content.essay.on-post + %edit content.essay.on-post + == +|^ +:: made it to the end +=* return + =- &+[[[%allowed -] ~] !>(~)] + ?- event + [%on-post %add *] event(content.essay new-content) + [%on-post %edit *] event(content.essay new-content) + == +?~ verses return +?: (gte count max) return +=* next $(verses t.verses) +=/ verse i.verses +:: remove blocks +?: ?=(%block -.verse) next +=/ [new-inlines=(list inline:c) new-count=@ud] + (run-list p.verse count) +$(new-content (snoc new-content [%inline new-inlines]), verses t.verses, count new-count) +++ run-list + |= [inlines=(list inline:c) count=@ud] + ^- [(list inline:c) @ud] + =/ new-inlines=(list inline:c) ~ + |- + ?~ inlines + :: made it all the way through + [new-inlines count] + =* next $(inlines t.inlines) + =/ inline i.inlines + ?: (gte count max) + [new-inlines count] + ?@ inline + =/ new-string (trim-cord inline count) + ?~ new-string $(inlines ~) ::done + $(new-inlines (snoc new-inlines u.new-string), inlines t.inlines, count (add count (met 3 u.new-string))) + =/ [new-inline=(unit inline:c) new-count=@ud] + (run-special-inlines inline count) + ?~ new-inline $(inlines ~) ::done + $(new-inlines (snoc new-inlines u.new-inline), inlines t.inlines, count new-count) +++ run-special-inlines + |= [=inline:c count=@ud] + ^- [(unit inline:c) @ud] + ?+ -.inline [~ count] + %break [`inline +(count)] + :: + %ship + ?: (gth (add count 14) max) [~ count] + [`inline (add count 14)] + :: + %link + =/ new-string=(unit cord) (trim-cord q.inline count) + ?~ new-string [~ count] + =/ new-inline=inline:c inline(q u.new-string) + [(some new-inline) (add count (met 3 u.new-string))] + :: + %inline-code + =/ new-string=(unit cord) (trim-cord p.inline count) + ?~ new-string [~ count] + [(some inline(p u.new-string)) (add count (met 3 u.new-string))] + :: + ?(%italics %bold %strike %blockquote) + =/ [new-inlines=(list inline:c) new-count=@ud] (run-list p.inline count) + ?~ new-inlines [~ count] + [(some inline(p new-inlines)) new-count] + == +++ trim-cord + |= [=cord count=@ud] + ^- (unit ^cord) + =/ string (trip cord) + =/ length (lent string) + =/ total (add length count) + ?: (gth total max) + :: truncate + =/ remainder (sub total max) + :: no room for anything + ?: =(length remainder) ~ + =/ new-length (sub length remainder) + `(crip (scag new-length string)) + `cord +-- \ No newline at end of file diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index b41dd9419d..a1a49fc1e5 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -695,7 +695,7 @@ ~& "parsed hoon: {<-.tonk>}" ~& %f ?: ?=(%| -.tonk) - ~& "returning error" + %- (slog 'returning error' p.tonk) tonk &+q.p.tonk ++ execute @@ -705,30 +705,36 @@ %- (soft prod) (slum .*(+:subject nock) simp) :: +++ run-hook + |= [=event:h =context:h =hook:h] + ^- (unit return:h) + ?. enabled.hook ~ + ?~ compiled.hook ~ + =/ =args:h [event context(state state.hook)] + =/ outcome=(unit outcome:h) + ((execute outcome:h) u.compiled.hook args) + ~& "{(trip name.hook)} hook run: {}" + ?~ outcome ~ + ?: ?=(%.y -.u.outcome) `p.u.outcome + ~& "hook failed:" + ((slog p.u.outcome) ~) ++ run-hooks |= [=event:h =context:h default=cord hks=hooks:h] ^- [[(each event:h tang) (list effect:h)] hooks:h] =/ current-event event =| effects=(list effect:h) - =* order +.order.hks + =/ order +.order.hks |- ?~ order [[&+current-event effects] hks] =* next $(order t.order) =/ hook (~(got by hooks.hks) i.order) - ?~ compiled.hook next - =/ =args:h [current-event context(state state.hook)] - =/ outcome=(unit outcome:h) - ((execute outcome:h) u.compiled.hook args) - ~& "{(trip name.hook)} hook run: {}" - ?~ outcome next - ?: ?=(%.n -.u.outcome) - ~& "hook failed:" - %- (slog p.u.outcome) - next - =* result result.p.u.outcome - =. effects (weld effects effects.p.u.outcome) - =. hooks.hks (~(put by hooks.hks) i.order hook(state new-state.p.u.outcome)) + =/ return=(unit return:h) + (run-hook current-event context hook) + ?~ return next + =* result result.u.return + =. effects (weld effects effects.u.return) + =. hooks.hks (~(put by hooks.hks) i.order hook(state new-state.u.return)) ?: ?=(%denied -.result) [[|+~[(fall msg.result default)] effects] hks] =. current-event new.result diff --git a/desk/mar/hook/action-0.hoon b/desk/mar/hook/action-0.hoon new file mode 100644 index 0000000000..325b647171 --- /dev/null +++ b/desk/mar/hook/action-0.hoon @@ -0,0 +1,12 @@ +/- h=hooks, c=channels +|_ [=nest:c =action:h] +++ grad %noun +++ grow + |% + ++ noun [nest action] + -- +++ grab + |% + ++ noun [=nest:c =action:h] + -- +-- diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 77a0bf2efc..1a3696ec38 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -65,7 +65,7 @@ +$ context $: v-channel channels=v-channels - =group:g + =group-ui:g state=vase now=time our=ship From 938383bea818940ab69aeb99e41aab2a192b9d6c Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 14 Nov 2024 17:35:10 -0600 Subject: [PATCH 004/149] wip: trying to use delay hooks --- desk/app/channels-server.hoon | 40 +++++++++++++++++------------------ desk/gen/hooks/confirm.hoon | 19 +++++++++++++++++ desk/lib/channel-utils.hoon | 3 ++- desk/sur/hooks.hoon | 10 ++++----- 4 files changed, 46 insertions(+), 26 deletions(-) create mode 100644 desk/gen/hooks/confirm.hoon diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index ac95188c32..ae398bde7b 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -703,21 +703,21 @@ %add ?> =(src.bowl author.essay.c-post) ?> =(kind.nest -.kind-data.essay.c-post) + =/ id=id-post:c + |- + =/ post (get:on-v-posts:c posts.channel now.bowl) + ?~ post now.bowl + $(now.bowl `@da`(add now.bowl ^~((div ~s1 (bex 16))))) + =/ new=v-post:c [[id ~ ~] 0 essay.c-post] =^ result=(each event:h tang) cor - =/ =event:h [%on-post %add essay.c-post] + =/ =event:h [%on-post %add new] %- ho-run:(ho-abed:ho-core nest) [event context 'post blocked'] ?: ?=(%.n -.result) ((slog p.result) [~ ca-core]) - =/ =essay:c + =. new ?> ?=([%on-post %add *] p.result) - essay.p.result - =/ id=id-post:c - |- - =/ post (get:on-v-posts:c posts.channel now.bowl) - ?~ post now.bowl - $(now.bowl `@da`(add now.bowl ^~((div ~s1 (bex 16))))) - =/ new=v-post:c [[id ~ ~] 0 essay] + post.p.result :- `[%post id %set ~ new] ca-core(posts.channel (put:on-v-posts:c posts.channel id ~ new)) :: @@ -804,22 +804,22 @@ ?- -.c-reply %add ?> =(src.bowl author.memo.c-reply) - =^ result=(each event:h tang) cor - =/ =event:h [%on-reply %add parent memo.c-reply] - %- ho-run:(ho-abed:ho-core nest) - [event context 'reply blocked'] - ?: ?=(%.n -.result) - ((slog p.result) [~ replies]) - =/ =memo:c - ?> ?=([%on-reply %add *] p.result) - memo.p.result =/ id=id-reply:c |- =/ reply (get:on-v-replies:c replies now.bowl) ?~ reply now.bowl $(now.bowl `@da`(add now.bowl ^~((div ~s1 (bex 16))))) =/ reply-seal=v-reply-seal:c [id ~] - =/ new=v-reply:c [reply-seal 0 memo] + =/ new=v-reply:c [reply-seal 0 memo.c-reply] + =^ result=(each event:h tang) cor + =/ =event:h [%on-reply %add parent new] + %- ho-run:(ho-abed:ho-core nest) + [event context 'reply blocked'] + ?: ?=(%.n -.result) + ((slog p.result) [~ replies]) + =. new + ?> ?=([%on-reply %add *] p.result) + reply.p.result :- `[%reply id %set ~ new] (put:on-v-replies:c replies id ~ new) :: @@ -1023,10 +1023,10 @@ =/ result=(each nock tang) ~& "compiling hook" ((compile:utils args:h outcome:h) `src.action) + ~& "compilation result: {<-.result>}" =/ compiled ?: ?=(%| -.result) ((slog 'compilation result:' p.result) ~) - ~& "compilation result: {}" `p.result =. ho-core ?~ cron.action ho-core diff --git a/desk/gen/hooks/confirm.hoon b/desk/gen/hooks/confirm.hoon new file mode 100644 index 0000000000..ea6c1f228c --- /dev/null +++ b/desk/gen/hooks/confirm.hoon @@ -0,0 +1,19 @@ +/- h=hooks, c=channels +:- %say +|= $: [now=@da eny=@uvJ =beak] + [[=event:h =context:h ~] ~] + == +:- %noun +^- outcome:h +=- &+[[[%allowed event] -] state.hook.context] +^- (list effect:h) +?. ?=(?(%delay %on-post) -.event) ~ +?: ?=(%delay -.event) + =/ =nest:c [%chat ~bospur-davmyl-nocsyx-lassul %welcome-8458] + =+ !<(trigger=event:h data.event) + ?. ?=([%on-post %add *] trigger) ~ + =* post post.trigger + =/ =c-react:c [%add-react id.post author.post ':thumbs-up:'] + ~[[%channels %channel nest %post c-react]] +=/ id (rsh [3 48] eny.context) +~[[%delay id id.hook.context ~s30 !>(event)]] \ No newline at end of file diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index a1a49fc1e5..054cd30205 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -710,7 +710,8 @@ ^- (unit return:h) ?. enabled.hook ~ ?~ compiled.hook ~ - =/ =args:h [event context(state state.hook)] + ~& "running hook: {} {}" + =/ =args:h [event context(hook hook)] =/ outcome=(unit outcome:h) ((execute outcome:h) u.compiled.hook args) ~& "{(trip name.hook)} hook run: {}" diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 1a3696ec38..9373ffa43b 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -66,7 +66,7 @@ $: v-channel channels=v-channels =group-ui:g - state=vase + =hook now=time our=ship src=ship @@ -75,7 +75,7 @@ :: :: $on-post: a hook event that fires when posts are interacted with +$ on-post - $% [%add =essay] + $% [%add post=v-post] [%edit original=v-post =essay] [%del original=v-post] [%react post=v-post =ship react=(unit react)] @@ -83,7 +83,7 @@ :: :: $on-reply: a hook event that fires when replies are interacted with +$ on-reply - $% [%add parent=v-post =memo] + $% [%add parent=v-post reply=v-reply] [%edit parent=v-post original=v-reply =memo] [%del parent=v-post original=v-reply] [%react parent=v-post reply=v-reply =ship react=(unit react)] @@ -108,8 +108,8 @@ :: :: $args: the arguments passed to a hook +$ args - $: event=event - context=context + $: =event + =context == :: :: $result: the result of a hook running From 5c60aee2a657792feaf00b4c8d996c59ece5f6ee Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 21 Nov 2024 16:03:56 -0600 Subject: [PATCH 005/149] hooks: responses and usage threads --- desk/app/channels-server.hoon | 500 +++++++++++++++++++--------------- desk/app/channels.hoon | 3 + desk/gen/hooks/create.hoon | 13 - desk/gen/hooks/truncate.hoon | 4 +- desk/lib/channel-utils.hoon | 60 +--- desk/mar/hook/action-0.hoon | 6 +- desk/mar/hook/response-0.hoon | 12 + desk/sur/hooks.hoon | 50 ++-- desk/ted/hooks/add.hoon | 21 ++ desk/ted/hooks/del.hoon | 19 ++ desk/ted/hooks/edit.hoon | 23 ++ desk/ted/hooks/order.hoon | 19 ++ desk/ted/hooks/run.hoon | 65 +++++ 13 files changed, 487 insertions(+), 308 deletions(-) delete mode 100644 desk/gen/hooks/create.hoon create mode 100644 desk/mar/hook/response-0.hoon create mode 100644 desk/ted/hooks/add.hoon create mode 100644 desk/ted/hooks/del.hoon create mode 100644 desk/ted/hooks/edit.hoon create mode 100644 desk/ted/hooks/order.hoon create mode 100644 desk/ted/hooks/run.hoon diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index ae398bde7b..9e705c24f4 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -18,7 +18,7 @@ +$ current-state $: %7 =v-channels:c - hooks=(map nest:c hooks:h) + =hooks:h =pimp:imp == -- @@ -113,7 +113,7 @@ ++ state-6-to-7 |= state-6 ^- state-7 - [%7 v-channels ~ pimp] + [%7 v-channels *hooks:h pimp] +$ state-5 $: %5 =v-channels:v6:old:c @@ -293,19 +293,17 @@ :: ++ inflate-io =. cor (safe-watch /groups [our.bowl %groups] /groups) - %+ roll ~(tap by hooks) - |= [[=nest:c hks=hooks:h] cr=_cor] - %+ roll ~(tap by hooks.hks) - |= [[=id:h =hook:h] co=_cr] - ?~ cron.hook co - ?~ compiled.hook co - ?. enabled.hook co - ?^ delay=(~(get by delayed.hks) id) co + %+ roll ~(tap by crons.hooks) + |= [[=id:h schedules=(map origin:h cron:h)] cr=_cor] + %+ roll ~(tap by schedules) + |= [[=origin:h cron:h] co=_cr] + ?~ hook=(~(get by hooks.hooks) id) co + ?~ compiled.u.hook co + ?^ delay=(~(get by delayed.hooks) delay-id) co :: only start timers for crons that haven't already been started - =/ fires-at (add now.bowl u.cron.hook) - =- ho-abet.- - %- ho-schedule:(ho-abed:ho-core:co nest) - [%cron id id u.cron.hook !>(~) fires-at] + =/ fires-at (add now.bowl schedule) + =/ =wire (hook-cron-wire id origin) + (schedule-hook wire origin delay-id id !>(~) fires-at) :: ++ poke |= [=mark =vase] @@ -352,8 +350,33 @@ == :: %hook-action-0 - =+ !<([=nest:c =action:h] vase) - ho-abet:(ho-action:(ho-abed:ho-core nest) action) + =+ !<(=action:h vase) + ?- -.action + %add + ho-abet:(ho-add:ho-core [name src]:action) + :: + %edit + ho-abet:(ho-edit:(ho-abed:ho-core id.action) [name src]:action) + :: + %del + ho-abet:ho-del:(ho-abed:ho-core id.action) + :: + %order + =/ seq + %+ skim + seq.action + |= =id:h + (~(has by hooks.hooks) id) + =. order.hooks (~(put by order.hooks) nest.action seq) + (give-hook-response %order nest.action seq) + :: + %wait + =/ args [origin schedule]:action + ho-abet:(ho-wait:(ho-abed:ho-core id.action) args) + :: + %rest + ho-abet:(ho-rest:(ho-abed:ho-core id.action) origin.action) + == == :: ++ run-import @@ -399,6 +422,8 @@ ^+ cor ~| watch-path=`path`pole ?+ pole ~|(%bad-watch-path !!) + [%hooks %v0 ~] cor + :: [=kind:c name=@ %create ~] ?> =(our src):bowl =* nest [kind.pole our.bowl name.pole] @@ -504,10 +529,8 @@ |= [=(pole knot) sign=sign-arvo] ^+ cor ?+ pole ~|(bad-arvo-take/pole !!) - [%hooks =kind:c ship=@ name=@ rest=*] - =/ ship (slav %p ship.pole) - =/ =nest:c [kind.pole ship name.pole] - ho-abet:(ho-arvo:(ho-abed:ho-core nest) rest.pole) + [%hooks rest=*] + (wakeup-hook rest.pole) == :: ++ watch-groups (safe-watch /groups [our.bowl %groups] /groups) @@ -651,6 +674,7 @@ |= =c-channel:c ^+ ca-core ?> am-host:ca-perms + ~& "received command {}" ?- -.c-channel %view ?> (is-admin:ca-perms src.bowl) @@ -689,6 +713,7 @@ %post =^ update=(unit u-channel:c) ca-core (ca-c-post c-post.c-channel) + ~& "received post update {}" ?~ update ca-core (ca-update u.update) == @@ -697,12 +722,13 @@ |= =c-post:c ^- [(unit u-channel:c) _ca-core] ?> (can-write:ca-perms src.bowl writers.perm.perm.channel) - =/ =context:h (get-context channel) + ~& "running post command" + =/ =context:h (get-hook-context `[nest channel]) =* no-op `ca-core ?- -.c-post %add - ?> =(src.bowl author.essay.c-post) - ?> =(kind.nest -.kind-data.essay.c-post) + ~& "adding post" + ?> |(=(src.bowl our.bowl) =(src.bowl author.essay.c-post)) =/ id=id-post:c |- =/ post (get:on-v-posts:c posts.channel now.bowl) @@ -711,8 +737,8 @@ =/ new=v-post:c [[id ~ ~] 0 essay.c-post] =^ result=(each event:h tang) cor =/ =event:h [%on-post %add new] - %- ho-run:(ho-abed:ho-core nest) - [event context 'post blocked'] + ~& "running post hooks" + (run-hooks event context nest 'post blocked') ?: ?=(%.n -.result) ((slog p.result) [~ ca-core]) =. new @@ -723,15 +749,13 @@ :: %edit ?> |(=(src.bowl author.essay.c-post) (is-admin:ca-perms src.bowl)) - ?> =(kind.nest -.kind-data.essay.c-post) =/ post (get:on-v-posts:c posts.channel id.c-post) ?~ post no-op ?~ u.post no-op ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-post %edit u.u.post essay.c-post] - %- ho-run:(ho-abed:ho-core nest) - [event context 'edit blocked'] + (run-hooks event context nest 'edit blocked') ?: ?=(%.n -.result) ((slog p.result) no-op) =/ =essay:c @@ -749,8 +773,7 @@ ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-post %del u.u.post] - %- ho-run:(ho-abed:ho-core nest) - [event context 'delete blocked'] + (run-hooks event context nest 'delete blocked') ?> =(& -.result) :- `[%post id.c-post %set ~] ca-core(posts.channel (put:on-v-posts:c posts.channel id.c-post ~)) @@ -765,8 +788,7 @@ ?: ?=(%del-react -.c-post) [p.c-post ~] [p `q]:c-post == - %- ho-run:(ho-abed:ho-core nest) - [event context 'react action blocked'] + (run-hooks event context nest 'react action blocked') ?: ?=(%.n -.result) ((slog p.result) no-op) =/ new=c-post:c @@ -813,8 +835,7 @@ =/ new=v-reply:c [reply-seal 0 memo.c-reply] =^ result=(each event:h tang) cor =/ =event:h [%on-reply %add parent new] - %- ho-run:(ho-abed:ho-core nest) - [event context 'reply blocked'] + (run-hooks event context nest 'reply blocked') ?: ?=(%.n -.result) ((slog p.result) [~ replies]) =. new @@ -830,8 +851,7 @@ ?> =(src.bowl author.u.u.reply) =^ result=(each event:h tang) cor =/ =event:h [%on-reply %edit parent u.u.reply memo.c-reply] - %- ho-run:(ho-abed:ho-core nest) - [event context 'edit blocked'] + (run-hooks event context nest 'edit blocked') ?: ?=(%.n -.result) ((slog p.result) [~ replies]) =/ =memo:c @@ -849,8 +869,7 @@ ?> |(=(src.bowl author.u.u.reply) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-reply %del parent u.u.reply] - %- ho-run:(ho-abed:ho-core nest) - [event context 'delete blocked'] + (run-hooks event context nest 'delete blocked') ?> =(& -.result) :- `[%reply id.c-reply %set ~] (put:on-v-replies:c replies id.c-reply ~) @@ -865,8 +884,7 @@ ?: ?=(%del-react -.c-reply) [p.c-reply ~] [p `q]:c-reply == - %- ho-run:(ho-abed:ho-core nest) - [event context 'delete blocked'] + (run-hooks event context nest 'delete blocked') ?: ?=(%.n -.result) ((slog p.result) [~ replies]) =/ new=c-reply:c @@ -885,7 +903,7 @@ |= [reacts=v-reacts:c =c-react:c] ^- [changed=? v-reacts:c] =/ =ship ?:(?=(%add-react -.c-react) p.c-react p.c-react) - ?> =(src.bowl ship) + ?> |(=(src.bowl our.bowl) =(src.bowl ship)) =/ new-react ?:(?=(%add-react -.c-react) `q.c-react ~) =/ [changed=? new-rev=@ud] =/ old-react (~(get by reacts) ship) @@ -979,213 +997,251 @@ %+ welp /(scot %p our.bowl)/[dude]/(scot %da now.bowl) path -++ get-context - |= =v-channel:c +++ get-hook-context + |= channel=(unit [nest:c v-channel:c]) ^- context:h - =* flag group.perm.perm.v-channel - =/ =group-ui:g + =/ group + ?~ channel ~ + =* flag group.perm.perm.+.u.channel + %- some ?. .^(? %gu (scry-path %groups /$)) *group-ui:g ?. .^(? %gx (scry-path %groups /exists/(scot %p p.flag)/[q.flag]/noun)) *group-ui:g .^(group-ui:g %gx (scry-path %groups /groups/(scot %p p.flag)/[q.flag]/v1/noun)) - :* v-channel + :* channel + group v-channels - group-ui *hook:h :: we default this because each hook will replace with itself [now our src eny]:bowl == :: +++ give-hook-response + |= =response:h + ^+ cor + (give %fact ~[/hooks/v0] hook-response-0+!>(response)) ++ ho-core - |_ [=nest:c hks=hooks:h ctx=context:h gone=_|] + |_ [=id:h =hook:h gone=_|] ++ ho-core . ++ emit |=(=card ho-core(cor (^emit card))) ++ emil |=(caz=(list card) ho-core(cor (^emil caz))) ++ give |=(=gift:agent:gall ho-core(cor (^give gift))) ++ ho-abet %_ cor - hooks - ?:(gone (~(del by hooks) nest) (~(put by hooks) nest hks)) + hooks.hooks + ?:(gone (~(del by hooks.hooks) id) (~(put by hooks.hooks) id hook)) == :: ++ ho-abed - |= n=nest:c - ho-core(nest n, hks (~(gut by hooks) n *hooks:h)) + |= i=id:h + ho-core(id i, hook (~(got by hooks.hooks) i)) :: - ++ ho-action - |= =action:h + ++ ho-add + |= [name=@t src=@t] ^+ ho-core - ?> (is-admin:ca-perms:(ca-abed:ca-core nest) src.bowl) - ?- -.action - %add - ~& "adding hook {}" - =/ =id:h (rsh [3 48] eny.bowl) - =/ src=(rev:c (unit @t)) [0 `src.action] - =/ result=(each nock tang) - ~& "compiling hook" - ((compile:utils args:h outcome:h) `src.action) - ~& "compilation result: {<-.result>}" - =/ compiled - ?: ?=(%| -.result) - ((slog 'compilation result:' p.result) ~) - `p.result - =. ho-core - ?~ cron.action ho-core - =/ fires-at (add now.bowl u.cron.action) - =/ dh [id id u.cron.action !>(~) fires-at] - (ho-schedule %cron dh) - =. order.hks - +:(next-rev:c order.hks (snoc +.order.hks id)) - =. hooks.hks - %+ ~(put by hooks.hks) id - [id name.action & src compiled cron.action !>(~)] - ho-core - :: - %edit - ?~ old-hook=(~(get by hooks.hks) id.action) ho-core - =/ hook u.old-hook - =^ src-changed src.hook - (next-rev:c src.hook `src.action) - =/ name-changed !=(name.action name.hook) - =/ cron-changed !=(cron.action cron.hook) - ?. |(src-changed name-changed cron-changed) ho-core - =. name.hook name.action - =. cron.hook cron.action - =. compiled.hook - ?~ +.src.hook ~ - =/ result=(each nock tang) - ((compile:utils args:h return:h) +.src.hook) - ?: ?=(%| -.result) ~ - `p.result - =. hooks.hks (~(put by hooks.hks) id.action hook) - ?. cron-changed ho-core - ?~ cron.action ho-core - =. ho-core (ho-unschedule id.action %cron) - =/ fires-at (add now.bowl u.cron.action) - =/ dh [id.action id.action u.cron.action !>(~) fires-at] - (ho-schedule %cron dh) - :: - %del - :: TODO: make more CRDT - ?~ hook=(~(get by hooks.hks) id.action) ho-core - =. ho-core (ho-unschedule id.action %cron) - =. hooks.hks (~(del by hooks.hks) id.action) - =/ [* new-order=_order.hks] - %+ next-rev:c order.hks - %+ skim +.order.hks - |= =id:h - !=(id id.action) - =. order.hks new-order - ho-core - :: - %enable - =/ hook (~(got by hooks.hks) id.action) - =. hooks.hks (~(put by hooks.hks) id.action hook(enabled &)) - ?~ cron.hook ho-core - =/ fires-at (add now.bowl u.cron.hook) - =/ dh [id.action id.action u.cron.hook !>(~) fires-at] - (ho-schedule %cron dh) - :: - %disable - =/ hook (~(got by hooks.hks) id.action) - =. hooks.hks (~(put by hooks.hks) id.action hook(enabled |)) - (ho-unschedule id.action %cron) - :: - %order - =^ changed order.hks - (next-rev:c order.hks seq.action) - ho-core - == - ++ ho-run - |= [=event:h =context:h default=cord] - =^ [result=(each event:h tang) effects=(list effect:h)] hks - (run-hooks:utils event context default hks) - [result ho-abet:(ho-run-effects effects)] + ~& "adding hook {}" + =. id (rsh [3 48] eny.bowl) + =/ result=(each vase tang) + ~& "compiling hook" + (compile:utils src) + ~& "compilation result: {<-.result>}" + =/ compiled + ?: ?=(%| -.result) + ((slog 'compilation result:' p.result) ~) + `p.result + =. hook [id name %0 src compiled !>(~)] + =. cor + =/ error=(unit tang) + ?:(?=(%& -.result) ~ `p.result) + (give-hook-response [%set id name src error]) + ho-core + ++ ho-edit + |= [name=@t src=@t] + =. src.hook src + =. name.hook name + =/ result=(each vase tang) + (compile:utils src.hook) + =. compiled.hook + ?: ?=(%| -.result) ~ + `p.result + =. cor + =/ error=(unit tang) + ?:(?=(%& -.result) ~ `p.result) + (give-hook-response [%set id name src error]) + ho-core + :: + ++ ho-del + =. gone & + =. cor + %+ roll + ~(tap by (~(gut by crons.hooks) id *(map origin:h cron:h))) + |= [[=origin:h cron:h] cr=_cor] + =/ =wire (hook-cron-wire id origin) + (unschedule-hook:cr delay-id wire) + =. crons.hooks (~(del by crons.hooks) id) + =. order.hooks + %+ roll + ~(tap by order.hooks) + |= [[=nest:c ids=(list id:h)] or=(map nest:c (list id:h))] + =- (~(put by or) nest -) + (skip ids |=(i=id:h =(id i))) + =. delayed.hooks + %+ roll + ~(tap by delayed.hooks) + |= [[=delay-id:h d=[* delayed-hook:h]] dh=_delayed.hooks] + ?. =(id hook.d) dh + (~(del by dh) delay-id) + =. cor (give-hook-response [%gone id]) + ho-core + ++ ho-wait + |= [=origin:h schedule=@dr] + ^+ ho-core + =/ d-id (rsh [3 48] eny.bowl) + =/ crons (~(gut by crons.hooks) id *(map origin:h cron:h)) + =. crons.hooks + =- (~(put by crons.hooks) id.hook -) + (~(put by crons) origin [d-id schedule]) + =/ fires-at (add now.bowl schedule) + =/ dh [d-id id !>(~) fires-at] + =/ =wire (hook-cron-wire id origin) + =. cor (schedule-hook wire origin dh) + =. cor (give-hook-response [%wait id origin schedule]) + ho-core + ++ ho-rest + |= =origin:h + ^+ ho-core + =/ crons (~(got by crons.hooks) id) + =/ cron (~(got by crons) origin) + =. crons.hooks + (~(put by crons.hooks) id (~(del by crons) origin)) + =/ =wire (hook-cron-wire id origin) + =. cor (unschedule-hook delay-id.cron wire) + =. cor (give-hook-response [%rest id origin]) + ho-core ++ ho-run-single - |= [=event:h prefix=tape =hook:h] - ?~ channel=(~(get by v-channels) nest) ho-core - =/ =context:h (get-context u.channel) + |= [=event:h prefix=tape =origin:h] + =/ channel + ?@ origin ~ + ?~ ch=(~(get by v-channels) origin) ~ + `[origin u.ch] + =/ =context:h (get-hook-context channel) =/ return=(unit return:h) (run-hook:utils event context hook) ?~ return - ~& "{prefix} {} failed" - ho-core - ~& "{prefix} {} ran" - =. hooks.hks - (~(put by hooks.hks) id.hook hook(state new-state.u.return)) - (ho-run-effects effects.u.return) - ++ ho-run-effects - |= effects=(list effect:h) - ^+ ho-core - |- - ?~ effects + ~& "{prefix} {} failed" ho-core - =/ =effect:h i.effects - =; new-cor=_ho-core - =. ho-core new-cor - $(effects t.effects) - ?- -.effect - %channels - =/ =cage channel-action+!>(a-channels.effect) - (emit [%pass /hooks/effect %agent [our.bowl %channels] %poke cage]) - :: - %groups - =/ =cage group-action-3+!>(action.effect) - (emit [%pass /hooks/effect %agent [our.bowl %groups] %poke cage]) - :: - %activity - =/ =cage activity-action+!>(action.effect) - (emit [%pass /hooks/effect %agent [our.bowl %activity] %poke cage]) - :: - %dm - =/ =cage chat-dm-action+!>(action.effect) - (emit [%pass /hooks/effect %agent [our.bowl %chat] %poke cage]) - :: - %club - =/ =cage chat-club-action+!>(action.effect) - (emit [%pass /hooks/effect %agent [our.bowl %chat] %poke cage]) - :: - %contacts - =/ =cage contacts-action-1+!>(action.effect) - (emit [%pass /hooks/effect %agent [our.bowl %contacts] %poke cage]) - :: - %delay - =/ fires-at (add now.bowl wait.effect) - =/ dh +:effect(data [data.effect fires-at]) - =. ho-core (ho-unschedule id.effect %delayed) - (ho-schedule %delayed dh) - == - ++ ho-schedule - |= [type=@tas dh=delayed-hook:h] - ^+ ho-core - ~& "scheduling hook" - =. delayed.hks (~(put by delayed.hks) id.dh dh) - =/ =wire (welp ho-prefix /[type]/(scot %uv id.dh)) - (emit [%pass wire %arvo %b %wait fires-at.dh]) - ++ ho-unschedule - |= [=id:h type=@tas] - ?~ previous=(~(get by delayed.hks) id) ho-core - =/ =wire (welp ho-prefix /[type]/(scot %uv id)) - (emit [%pass wire %arvo %b %rest fires-at.u.previous]) - ++ ho-arvo - |= =(pole knot) - ^+ ho-core - ?+ pole ~|(bad-arvo-take/pole !!) - [%delayed id=@ ~] - =/ =id:h (slav %uv id.pole) - ?~ delay=(~(get by delayed.hks) id) ho-core - ?~ hook=(~(get by hooks.hks) hook.u.delay) ho-core - (ho-run-single [%delay u.delay] "delayed hook" u.hook) - :: - [%cron id=@ ~] - =/ =id:h (slav %uv id.pole) - ?~ delay=(~(get by delayed.hks) id) ho-core - ?~ hook=(~(get by hooks.hks) id) ho-core - :: if unscheduled, ignore - ?~ cron.u.hook ho-core - =/ next (add now.bowl u.cron.u.hook) - =. ho-core (ho-schedule %cron u.delay(fires-at next)) - (ho-run-single [%cron ~] "cron job" u.hook) - == - ++ ho-prefix /hooks/[kind.nest]/(scot %p ship.nest)/[name.nest] + ~& "{prefix} {} ran" + =. hook hook(state new-state.u.return) + =. cor (run-hook-effects effects.u.return origin) + ho-core -- +++ run-hooks + |= [=event:h =context:h =nest:c default=cord] + ^- [(each event:h tang) _cor] + =; [result=(each event:h tang) effects=(list effect:h)] + [result (run-hook-effects effects nest)] + =/ current-event event + =| effects=(list effect:h) + =/ order (~(got by order.hooks) nest) + ~& "got orders {}" + |- + ?~ order + [&+current-event effects] + =* next $(order t.order) + ~& "getting hook" + =/ hook (~(got by hooks.hooks) i.order) + =/ return=(unit return:h) + (run-hook:utils current-event context hook) + ?~ return next + =* result result.u.return + =. effects (weld effects effects.u.return) + =. hooks.hooks (~(put by hooks.hooks) i.order hook(state new-state.u.return)) + ?: ?=(%denied -.result) + [|+~[(fall msg.result default)] effects] + =. current-event new.result + next +++ wakeup-hook + |= =(pole knot) + ^+ cor + ?+ pole ~|(bad-arvo-take/pole !!) + [%delayed id=@ ~] + =/ =id:h (slav %uv id.pole) + ?~ delay=(~(get by delayed.hooks) id) cor + :: make sure we clean up + =. delayed.hooks (~(del by delayed.hooks) id) + =/ args [[%wake +.u.delay] "delayed hook" origin.u.delay] + ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.delay) args) + :: + [%cron id=@ kind=?(%chat %diary %heap) ship=@ name=@ ~] + =/ =id:h (slav %uv id.pole) + =/ =origin:h [kind.pole (slav %p ship.pole) name.pole] + :: if unscheduled, ignore + ?~ crons=(~(get by crons.hooks) id) cor + ?~ cron=(~(get by u.crons) origin) cor + ?~ delay=(~(get by delayed.hooks) delay-id.u.cron) cor + =. delayed.hooks (~(del by delayed.hooks) delay-id.u.cron) + =/ next (add now.bowl schedule.u.cron) + =/ =wire (hook-cron-wire id origin) + =. cor + (schedule-hook wire origin +.u.delay(fires-at next)) + =/ args [[%cron ~] "cron job" origin] + ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.delay) args) + == +++ hook-cron-wire + |= [=id:h =origin:h] + ^- wire + %+ welp /hooks/cron/(scot %uv id) + ?@ origin ~ + /[kind.origin]/(scot %p ship.origin)/[name.origin] +++ schedule-hook + |= [=wire =origin:h dh=delayed-hook:h] + ^+ cor + ~& "scheduling hook" + =. delayed.hooks (~(put by delayed.hooks) id.dh [origin dh]) + (emit [%pass wire %arvo %b %wait fires-at.dh]) +++ unschedule-hook + |= [=id:h =wire] + ^+ cor + ?~ previous=(~(get by delayed.hooks) id) cor + ~& "unscheduling hook" + (emit [%pass wire %arvo %b %rest fires-at.u.previous]) +++ run-hook-effects + |= [effects=(list effect:h) =origin:h] + ^+ cor + |- + ?~ effects + cor + =/ =effect:h i.effects + =; new-cor=_cor + =. cor new-cor + $(effects t.effects) + ?- -.effect + %channels + =/ =cage channel-action+!>(a-channels.effect) + (emit [%pass /hooks/effect %agent [our.bowl %channels] %poke cage]) + :: + %groups + =/ =cage group-action-3+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %groups] %poke cage]) + :: + %activity + =/ =cage activity-action+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %activity] %poke cage]) + :: + %dm + =/ =cage chat-dm-action+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %chat] %poke cage]) + :: + %club + =/ =cage chat-club-action+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %chat] %poke cage]) + :: + %contacts + =/ =cage contacts-action-1+!>(action.effect) + (emit [%pass /hooks/effect %agent [our.bowl %contacts] %poke cage]) + :: + %wait + =/ =wire /hooks/delayed/(scot %uv id.effect) + =. cor (unschedule-hook id.effect wire) + (schedule-hook wire origin +:effect) + == -- diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 364f628953..2c8b4f38e5 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -734,6 +734,9 @@ :: [%x %v2 %channels full=?(~ [%full ~])] ``channels-2+!>((uv-channels-2:utils v-channels ?=(^ full.pole))) + :: + [%x %v3 %v-channels ~] + ``noun+!>(v-channels) :: [%x ?(%v0 %v1) %init ~] ``noun+!>([unreads (uv-channels-1:utils v-channels)]) [%x %v2 %init ~] ``noun+!>([unreads (uv-channels-2:utils v-channels |)]) diff --git a/desk/gen/hooks/create.hoon b/desk/gen/hooks/create.hoon deleted file mode 100644 index 3b02b7d70e..0000000000 --- a/desk/gen/hooks/create.hoon +++ /dev/null @@ -1,13 +0,0 @@ -/- c=channels, h=hooks -:- %say -|= $: [now=@da eny=@uvJ =beak] - [[=nest:c name=@t src=@t cron=(unit @dr) ~] ~] - == -:- %hook-action-0 -^- [nest:c action:h] -:* nest - %add - name - src - cron -== \ No newline at end of file diff --git a/desk/gen/hooks/truncate.hoon b/desk/gen/hooks/truncate.hoon index af66032416..538738a432 100644 --- a/desk/gen/hooks/truncate.hoon +++ b/desk/gen/hooks/truncate.hoon @@ -14,7 +14,7 @@ ?. ?=(?(%add %edit) -.on-post) no-op =/ verses ?- -.on-post - %add content.essay.on-post + %add content.essay.post.on-post %edit content.essay.on-post == |^ @@ -22,7 +22,7 @@ =* return =- &+[[[%allowed -] ~] !>(~)] ?- event - [%on-post %add *] event(content.essay new-content) + [%on-post %add *] event(content.essay.post new-content) [%on-post %edit *] event(content.essay new-content) == ?~ verses return diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index 054cd30205..1fa0dae925 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -676,68 +676,38 @@ ;br; == -- -++ subject ^~(!>([..subject ..zuse])) +++ subject ^~(!>(..compile)) ++ compile - |* [args=mold return=mold] - |= src=(unit @t) - ^- (each nock tang) - ?~ src |+~['no src'] + |= src=@t + ^- (each vase tang) ~& %a - =/ tonk=(each (pair type nock) tang) + =/ tonk=(each vase tang) ~& %b - =/ vex=(like hoon) ((full vest) [0 0] (trip u.src)) + =/ vex=(like hoon) ((full vest) [0 0] (trip src)) ~& %c ?~ q.vex |+~[leaf+"\{{} {}}" 'syntax error'] ~& %d %- mule - |.((~(mint ut -:subject) %noun p.u.q.vex)) + |.((slap subject p.u.q.vex)) ~& %e ~& "parsed hoon: {<-.tonk>}" ~& %f ?: ?=(%| -.tonk) %- (slog 'returning error' p.tonk) tonk - &+q.p.tonk -++ execute - |* prod=mold - |= [=nock simp=*] - ^- (unit prod) - %- (soft prod) - (slum .*(+:subject nock) simp) -:: + &+p.tonk ++ run-hook |= [=event:h =context:h =hook:h] ^- (unit return:h) - ?. enabled.hook ~ - ?~ compiled.hook ~ ~& "running hook: {} {}" + ?~ compiled.hook + ~&("hook not compiled" ~) + :: ~& "nock: {}" =/ =args:h [event context(hook hook)] - =/ outcome=(unit outcome:h) - ((execute outcome:h) u.compiled.hook args) - ~& "{(trip name.hook)} hook run: {}" - ?~ outcome ~ - ?: ?=(%.y -.u.outcome) `p.u.outcome + =+ !<(=outcome:h (slam u.compiled.hook !>(args))) + ~& "{(trip name.hook)} hook run:" + ~& outcome + ?: ?=(%.y -.outcome) `p.outcome ~& "hook failed:" - ((slog p.u.outcome) ~) -++ run-hooks - |= [=event:h =context:h default=cord hks=hooks:h] - ^- [[(each event:h tang) (list effect:h)] hooks:h] - =/ current-event event - =| effects=(list effect:h) - =/ order +.order.hks - |- - ?~ order - [[&+current-event effects] hks] - =* next $(order t.order) - =/ hook (~(got by hooks.hks) i.order) - =/ return=(unit return:h) - (run-hook current-event context hook) - ?~ return next - =* result result.u.return - =. effects (weld effects effects.u.return) - =. hooks.hks (~(put by hooks.hks) i.order hook(state new-state.u.return)) - ?: ?=(%denied -.result) - [[|+~[(fall msg.result default)] effects] hks] - =. current-event new.result - next + ((slog p.outcome) ~) -- diff --git a/desk/mar/hook/action-0.hoon b/desk/mar/hook/action-0.hoon index 325b647171..bab332196b 100644 --- a/desk/mar/hook/action-0.hoon +++ b/desk/mar/hook/action-0.hoon @@ -1,12 +1,12 @@ /- h=hooks, c=channels -|_ [=nest:c =action:h] +|_ =action:h ++ grad %noun ++ grow |% - ++ noun [nest action] + ++ noun action -- ++ grab |% - ++ noun [=nest:c =action:h] + ++ noun action:h -- -- diff --git a/desk/mar/hook/response-0.hoon b/desk/mar/hook/response-0.hoon new file mode 100644 index 0000000000..88f7887db3 --- /dev/null +++ b/desk/mar/hook/response-0.hoon @@ -0,0 +1,12 @@ +/- h=hooks, c=channels +|_ =response:h +++ grad %noun +++ grow + |% + ++ noun response + -- +++ grab + |% + ++ noun response:h + -- +-- diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 9373ffa43b..3ada49b788 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -7,48 +7,52 @@ :: :: $id: a unique identifier for the hook :: $name: a human-readable name for the hook -:: $origin: whether or not this hook was added by us or came through an update +:: $version: the version the hook was compiled with :: $src: the source code of the hook -:: $compiled: the compiled nock of the hook -:: $cron: the cron schedule for the hook if it has one +:: $compiled: the compiled version of the hook :: $state: the current state of the hook :: ++ hook $: =id name=@t - enabled=? - src=(rev src=(unit @t)) - compiled=(unit nock) - cron=(unit @dr) + version=%0 + src=@t + compiled=(unit vase) state=vase == :: $hooks: collection of hooks, the order they should be run in, and :: any delayed hooks that need to be run ++ hooks $: hooks=(map id hook) - order=(rev (list id)) - delayed=(map id delayed-hook) + order=(map nest (list id)) + crons=(map id (map origin cron)) + delayed=(map delay-id [=origin delayed-hook]) == ++$ origin $@(~ nest) ++$ delay-id id ++$ cron [=delay-id schedule=@dr] :: $delayed-hook: metadata for when a delayed hook fires from the timer +$ delayed-hook - $: =id + $: id=delay-id hook=id - wait=@dr data=vase fires-at=time == :: +$ action - $% [%add name=@t src=@t cron=(unit @dr)] - [%edit =id name=@t src=@t cron=(unit @dr)] + $% [%add name=@t src=@t] + [%edit =id name=@t src=@t] [%del =id] - [%enable =id] - [%disable =id] - [%order seq=(list id)] + [%order =nest seq=(list id)] + [%wait =id =origin schedule=@dr] + [%rest =id =origin] == +$ response - $% [%set =id name=@t src=(unit @t) error=(unit tang)] - [%order seq=(list id)] + $% [%set =id name=@t src=@t error=(unit tang)] + [%gone =id] + [%order =nest seq=(list id)] + [%wait =id =origin schedule=@dr] + [%rest =id =origin] == :: $context: ambient state that a hook should know about not :: necessarily tied to a specific event @@ -63,9 +67,9 @@ :: $eny: entropy for random number generation or key derivation :: +$ context - $: v-channel + $: channel=(unit [=nest v-channel]) + group=(unit group-ui:g) channels=v-channels - =group-ui:g =hook now=time our=ship @@ -96,14 +100,14 @@ :: $on-post: a post was added, edited, deleted, or reacted to :: $on-reply: a reply was added, edited, deleted, or reacted to :: $cron: a scheduled wake-up -:: $delay: a delayed invocation of the hook called with metadata about +:: $wake: a delayed invocation of the hook called with metadata about :: when it fired, its id, and the event it should run with :: +$ event $% [%on-post on-post] [%on-reply on-reply] [%cron ~] - [%delay delayed-hook] + [%wake delayed-hook] == :: :: $args: the arguments passed to a hook @@ -135,7 +139,7 @@ [%dm =action:dm:ch] [%club =action:club:ch] [%contacts =action:co] - [%delay =id hook=id wait=@dr data=vase] + [%wait delayed-hook] == :: :: $return: the data returned from a hook diff --git a/desk/ted/hooks/add.hoon b/desk/ted/hooks/add.hoon new file mode 100644 index 0000000000..cafb458e84 --- /dev/null +++ b/desk/ted/hooks/add.hoon @@ -0,0 +1,21 @@ +/- spider, h=hooks +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +=+ !<([~ name=@t src=@t] arg) +;< our=@p bind:m get-our:s +;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +=/ =cage hook-action-0+!>(`action:h`[%add name src]) +;< ~ bind:m (poke-our:s %channels-server cage) +;< =^cage bind:m (take-fact:s /responses) +?> ?=(%hook-response-0 p.cage) +=+ !<(=response:h q.cage) +?> ?=(%set -.response) +~& "hook {} added with id {}" +?~ error.response (pure:m !>(~)) +~& "compilation error:" +%- (slog u.error.response) +(pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hooks/del.hoon b/desk/ted/hooks/del.hoon new file mode 100644 index 0000000000..0ffc680d72 --- /dev/null +++ b/desk/ted/hooks/del.hoon @@ -0,0 +1,19 @@ +/- spider, h=hooks +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +=+ !<([~ =id:h] arg) +;< our=@p bind:m get-our:s +;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +=/ =cage hook-action-0+!>(`action:h`[%del id]) +;< ~ bind:m (poke-our:s %channels-server cage) +;< =^cage bind:m (take-fact:s /responses) +?> ?=(%hook-response-0 p.cage) +=+ !<(=response:h q.cage) +?> ?=(%gone -.response) +?> =(id id.response) +~& "hook {} deleted" +(pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hooks/edit.hoon b/desk/ted/hooks/edit.hoon new file mode 100644 index 0000000000..05e1090f95 --- /dev/null +++ b/desk/ted/hooks/edit.hoon @@ -0,0 +1,23 @@ +/- spider, h=hooks +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +=+ !<([~ =id:h name=@t src=@t] arg) +;< our=@p bind:m get-our:s +;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +=/ =cage hook-action-0+!>(`action:h`[%edit id name src]) +;< ~ bind:m (poke-our:s %channels-server cage) +;< =^cage bind:m (take-fact:s /responses) +?> ?=(%hook-response-0 p.cage) +=+ !<(=response:h q.cage) +?> ?=(%set -.response) +?~ error.response + ~& "hook {} edited successfully" + (pure:m !>(~)) +~& "hook {} edited" +~& "compilation error:" +%- (slog u.error.response) +(pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hooks/order.hoon b/desk/ted/hooks/order.hoon new file mode 100644 index 0000000000..e9980c1994 --- /dev/null +++ b/desk/ted/hooks/order.hoon @@ -0,0 +1,19 @@ +/- spider, h=hooks, c=channels +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +=+ !<([~ =nest:c seq=(list id:h)] arg) +;< our=@p bind:m get-our:s +;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +=/ =cage hook-action-0+!>(`action:h`[%order nest seq]) +;< ~ bind:m (poke-our:s %channels-server cage) +;< =^cage bind:m (take-fact:s /responses) +?> ?=(%hook-response-0 p.cage) +=+ !<(=response:h q.cage) +?> ?=(%order -.response) +~& "new hook order for {}" +~& seq.response +(pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hooks/run.hoon b/desk/ted/hooks/run.hoon new file mode 100644 index 0000000000..1f9ea6f685 --- /dev/null +++ b/desk/ted/hooks/run.hoon @@ -0,0 +1,65 @@ +/- spider, h=hooks, c=channels, g=groups +/+ s=strandio, utils=channel-utils +=, strand=strand:spider +^- thread:spider +|= arg=vase +|^ +=/ m (strand ,vase) +^- form:m +=+ !<([~ =event:h =context-option src=@t] arg) +;< our=@p bind:m get-our:s +=/ compiled=(each vase tang) (compile:utils src) +?. ?=(%& -.compiled) + ~& "compilation error:" + %- (slog p.compiled) + (pure:m !>(~)) +~& "compiled successfully" +;< ctx=context:h bind:m (get-context context-option) +=+ !<(=outcome:h (slam p.compiled !>([event ctx]))) +?: ?=(%.y -.outcome) + ~& "hook ran successfully" + (pure:m !>(p.outcome)) +~& "hook failed:" +%- (slog p.outcome) +(pure:m !>(~)) ++$ event-option + $% [%ref path=@t] + [%event event:h] + == ++$ context-option + $% [%origin =origin:h state=(unit vase)] + [%context =context:h] + == +++ get-context + |= =context-option + =/ m (strand ,context:h) + ^- form:m + ?: ?=(%context -.context-option) (pure:m context.context-option) + =/ [=origin:h state=(unit vase)] +.context-option + ;< =v-channels:c bind:m + (scry:s v-channels:c /gx/channels/v3/v-channels/noun) + =/ channel=(unit [=nest:c vc=v-channel:c]) + ?~ origin ~ + `[origin (~(gut by v-channels) origin *v-channel:c)] + ;< group=(unit group-ui:g) bind:m + =/ n (strand (unit group-ui:g)) + ?~ channel (pure:n ~) + =* flag group.perm.perm.vc.u.channel + ;< live=? bind:n (scry:s ? /gu/groups/$) + ?. live (pure:n `*group-ui:g) + ;< exists=? bind:n + (scry:s ? /gx/groups/exists/(scot %p p.flag)/[q.flag]/noun) + ?. exists (pure:n `*group-ui:g) + ;< =group-ui:g bind:n + (scry:s group-ui:g /groups/groups/(scot %p p.flag)/[q.flag]/v1/noun) + (pure:n (some group-ui)) + ;< =bowl:spider bind:m get-bowl:s + =/ hook *hook:h + %- pure:m + :* channel + group + v-channels + hook(state ?~(state !>(~) u.state)) + [now our src eny]:bowl + == +-- From 87ec95d71487671ba38ec21f9d00a3f6db4e303d Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Fri, 22 Nov 2024 12:07:28 -0600 Subject: [PATCH 006/149] hooks: adding configuration --- desk/app/channels-server.hoon | 64 +++++++++++++++++++++-------------- desk/lib/channel-utils.hoon | 3 +- desk/sur/hooks.hoon | 19 ++++++++--- desk/ted/hooks/configure.hoon | 18 ++++++++++ 4 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 desk/ted/hooks/configure.hoon diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 9e705c24f4..100e52b4b4 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -369,10 +369,12 @@ (~(has by hooks.hooks) id) =. order.hooks (~(put by order.hooks) nest.action seq) (give-hook-response %order nest.action seq) + :: + %configure + ho-abet:(ho-configure:(ho-abed:ho-core id.action) +.+.action) :: %wait - =/ args [origin schedule]:action - ho-abet:(ho-wait:(ho-abed:ho-core id.action) args) + ho-abet:(ho-wait:(ho-abed:ho-core id.action) +.+.action) :: %rest ho-abet:(ho-rest:(ho-abed:ho-core id.action) origin.action) @@ -723,7 +725,6 @@ ^- [(unit u-channel:c) _ca-core] ?> (can-write:ca-perms src.bowl writers.perm.perm.channel) ~& "running post command" - =/ =context:h (get-hook-context `[nest channel]) =* no-op `ca-core ?- -.c-post %add @@ -738,7 +739,7 @@ =^ result=(each event:h tang) cor =/ =event:h [%on-post %add new] ~& "running post hooks" - (run-hooks event context nest 'post blocked') + (run-hooks event nest 'post blocked') ?: ?=(%.n -.result) ((slog p.result) [~ ca-core]) =. new @@ -755,7 +756,7 @@ ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-post %edit u.u.post essay.c-post] - (run-hooks event context nest 'edit blocked') + (run-hooks event nest 'edit blocked') ?: ?=(%.n -.result) ((slog p.result) no-op) =/ =essay:c @@ -773,7 +774,7 @@ ?> |(=(src.bowl author.u.u.post) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-post %del u.u.post] - (run-hooks event context nest 'delete blocked') + (run-hooks event nest 'delete blocked') ?> =(& -.result) :- `[%post id.c-post %set ~] ca-core(posts.channel (put:on-v-posts:c posts.channel id.c-post ~)) @@ -788,7 +789,7 @@ ?: ?=(%del-react -.c-post) [p.c-post ~] [p `q]:c-post == - (run-hooks event context nest 'react action blocked') + (run-hooks event nest 'react action blocked') ?: ?=(%.n -.result) ((slog p.result) no-op) =/ new=c-post:c @@ -810,7 +811,7 @@ ?~ post no-op ?~ u.post no-op =^ update=(unit u-post:c) replies.u.u.post - (ca-c-reply u.u.post c-reply.c-post context) + (ca-c-reply u.u.post c-reply.c-post) ?~ update no-op :- `[%post id.c-post u.update] %= ca-core @@ -820,7 +821,7 @@ == :: ++ ca-c-reply - |= [parent=v-post:c =c-reply:c =context:h] + |= [parent=v-post:c =c-reply:c] ^- [(unit u-post:c) v-replies:c] =* replies replies.parent ?- -.c-reply @@ -835,7 +836,7 @@ =/ new=v-reply:c [reply-seal 0 memo.c-reply] =^ result=(each event:h tang) cor =/ =event:h [%on-reply %add parent new] - (run-hooks event context nest 'reply blocked') + (run-hooks event nest 'reply blocked') ?: ?=(%.n -.result) ((slog p.result) [~ replies]) =. new @@ -851,7 +852,7 @@ ?> =(src.bowl author.u.u.reply) =^ result=(each event:h tang) cor =/ =event:h [%on-reply %edit parent u.u.reply memo.c-reply] - (run-hooks event context nest 'edit blocked') + (run-hooks event nest 'edit blocked') ?: ?=(%.n -.result) ((slog p.result) [~ replies]) =/ =memo:c @@ -869,7 +870,7 @@ ?> |(=(src.bowl author.u.u.reply) (is-admin:ca-perms src.bowl)) =^ result=(each event:h tang) cor =/ =event:h [%on-reply %del parent u.u.reply] - (run-hooks event context nest 'delete blocked') + (run-hooks event nest 'delete blocked') ?> =(& -.result) :- `[%reply id.c-reply %set ~] (put:on-v-replies:c replies id.c-reply ~) @@ -884,7 +885,7 @@ ?: ?=(%del-react -.c-reply) [p.c-reply ~] [p `q]:c-reply == - (run-hooks event context nest 'delete blocked') + (run-hooks event nest 'delete blocked') ?: ?=(%.n -.result) ((slog p.result) [~ replies]) =/ new=c-reply:c @@ -998,7 +999,7 @@ /(scot %p our.bowl)/[dude]/(scot %da now.bowl) path ++ get-hook-context - |= channel=(unit [nest:c v-channel:c]) + |= [channel=(unit [nest:c v-channel:c]) =config:h] ^- context:h =/ group ?~ channel ~ @@ -1012,6 +1013,7 @@ group v-channels *hook:h :: we default this because each hook will replace with itself + config [now our src eny]:bowl == :: @@ -1048,7 +1050,7 @@ ?: ?=(%| -.result) ((slog 'compilation result:' p.result) ~) `p.result - =. hook [id name %0 src compiled !>(~)] + =. hook [id name %0 src compiled !>(~) ~] =. cor =/ error=(unit tang) ?:(?=(%& -.result) ~ `p.result) @@ -1092,19 +1094,25 @@ (~(del by dh) delay-id) =. cor (give-hook-response [%gone id]) ho-core + ++ ho-configure + |= [=nest:c =config:h] + ^+ ho-core + =. config.hook (~(put by config.hook) nest config) + =. cor (give-hook-response [%configure id nest config]) + ho-core ++ ho-wait - |= [=origin:h schedule=@dr] + |= [=origin:h schedule=@dr =config:h] ^+ ho-core =/ d-id (rsh [3 48] eny.bowl) =/ crons (~(gut by crons.hooks) id *(map origin:h cron:h)) =. crons.hooks =- (~(put by crons.hooks) id.hook -) - (~(put by crons) origin [d-id schedule]) + (~(put by crons) origin [d-id schedule config]) =/ fires-at (add now.bowl schedule) =/ dh [d-id id !>(~) fires-at] =/ =wire (hook-cron-wire id origin) =. cor (schedule-hook wire origin dh) - =. cor (give-hook-response [%wait id origin schedule]) + =. cor (give-hook-response [%wait id origin schedule config]) ho-core ++ ho-rest |= =origin:h @@ -1118,14 +1126,14 @@ =. cor (give-hook-response [%rest id origin]) ho-core ++ ho-run-single - |= [=event:h prefix=tape =origin:h] + |= [=event:h prefix=tape =origin:h =config:h] =/ channel ?@ origin ~ ?~ ch=(~(get by v-channels) origin) ~ `[origin u.ch] - =/ =context:h (get-hook-context channel) + =/ =context:h (get-hook-context channel config) =/ return=(unit return:h) - (run-hook:utils event context hook) + (run-hook:utils [event context(hook hook)] hook) ?~ return ~& "{prefix} {} failed" ho-core @@ -1135,7 +1143,7 @@ ho-core -- ++ run-hooks - |= [=event:h =context:h =nest:c default=cord] + |= [=event:h =nest:c default=cord] ^- [(each event:h tang) _cor] =; [result=(each event:h tang) effects=(list effect:h)] [result (run-hook-effects effects nest)] @@ -1143,14 +1151,17 @@ =| effects=(list effect:h) =/ order (~(got by order.hooks) nest) ~& "got orders {}" + =/ channel `[nest (~(got by v-channels) nest)] + =/ =context:h (get-hook-context channel *config:h) |- ?~ order [&+current-event effects] =* next $(order t.order) ~& "getting hook" =/ hook (~(got by hooks.hooks) i.order) + =/ ctx context(hook hook, config (~(gut by config.hook) nest ~)) =/ return=(unit return:h) - (run-hook:utils current-event context hook) + (run-hook:utils [current-event ctx] hook) ?~ return next =* result result.u.return =. effects (weld effects effects.u.return) @@ -1168,7 +1179,10 @@ ?~ delay=(~(get by delayed.hooks) id) cor :: make sure we clean up =. delayed.hooks (~(del by delayed.hooks) id) - =/ args [[%wake +.u.delay] "delayed hook" origin.u.delay] + =* origin origin.u.delay + =/ hook (~(got by hooks.hooks) hook.u.delay) + =/ config ?@(origin ~ (~(gut by config.hook) origin ~)) + =/ args [[%wake +.u.delay] "delayed hook" origin config] ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.delay) args) :: [%cron id=@ kind=?(%chat %diary %heap) ship=@ name=@ ~] @@ -1183,7 +1197,7 @@ =/ =wire (hook-cron-wire id origin) =. cor (schedule-hook wire origin +.u.delay(fires-at next)) - =/ args [[%cron ~] "cron job" origin] + =/ args [[%cron ~] "cron job" origin config.u.cron] ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.delay) args) == ++ hook-cron-wire diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index 1fa0dae925..fa645b5932 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -697,13 +697,12 @@ tonk &+p.tonk ++ run-hook - |= [=event:h =context:h =hook:h] + |= [=args:h =hook:h] ^- (unit return:h) ~& "running hook: {} {}" ?~ compiled.hook ~&("hook not compiled" ~) :: ~& "nock: {}" - =/ =args:h [event context(hook hook)] =+ !<(=outcome:h (slam u.compiled.hook !>(args))) ~& "{(trip name.hook)} hook run:" ~& outcome diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 3ada49b788..cf17d92462 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -11,6 +11,7 @@ :: $src: the source code of the hook :: $compiled: the compiled version of the hook :: $state: the current state of the hook +:: $config: any configuration data for the hook :: ++ hook $: =id @@ -19,6 +20,7 @@ src=@t compiled=(unit vase) state=vase + config=(map nest config) == :: $hooks: collection of hooks, the order they should be run in, and :: any delayed hooks that need to be run @@ -30,7 +32,11 @@ == +$ origin $@(~ nest) +$ delay-id id -+$ cron [=delay-id schedule=@dr] ++$ cron + $: =delay-id + schedule=@dr + =config + == :: $delayed-hook: metadata for when a delayed hook fires from the timer +$ delayed-hook $: id=delay-id @@ -39,19 +45,22 @@ fires-at=time == :: ++$ config (map @t vase) +$ action $% [%add name=@t src=@t] [%edit =id name=@t src=@t] [%del =id] [%order =nest seq=(list id)] - [%wait =id =origin schedule=@dr] + [%configure =id =nest =config] + [%wait =id =origin schedule=@dr =config] [%rest =id =origin] == +$ response $% [%set =id name=@t src=@t error=(unit tang)] [%gone =id] [%order =nest seq=(list id)] - [%wait =id =origin schedule=@dr] + [%configure =id =nest =config] + [%wait =id =origin schedule=@dr =config] [%rest =id =origin] == :: $context: ambient state that a hook should know about not @@ -60,7 +69,8 @@ :: $channel: the channel that the hook is operating on :: $channels: all the channels in the group :: $group: the group that the channel belongs to -:: $state: the current state of the hook +:: $hook: the hook that's running +:: $config: the configuration data for this instance of the hook :: $now: the current time :: $our: the ship that the hook is running on :: $src: the ship that triggered the hook @@ -71,6 +81,7 @@ group=(unit group-ui:g) channels=v-channels =hook + =config now=time our=ship src=ship diff --git a/desk/ted/hooks/configure.hoon b/desk/ted/hooks/configure.hoon new file mode 100644 index 0000000000..72754085d5 --- /dev/null +++ b/desk/ted/hooks/configure.hoon @@ -0,0 +1,18 @@ +/- spider, h=hooks, c=channels +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +=+ !<([~ =id:h =nest:c =config:h] arg) +;< our=@p bind:m get-our:s +;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +=/ =cage hook-action-0+!>(`action:h`[%configure id nest config]) +;< ~ bind:m (poke-our:s %channels-server cage) +;< =^cage bind:m (take-fact:s /responses) +?> ?=(%hook-response-0 p.cage) +=+ !<(=response:h q.cage) +?> ?=(%configure -.response) +~& "hook {} running on {} configured" +(pure:m !>(~)) \ No newline at end of file From bc4abcb3d1e05790e859b05dc35a60d2dc8f67ff Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Fri, 22 Nov 2024 16:57:56 -0600 Subject: [PATCH 007/149] hooks: scheduling thread --- desk/ted/hooks/schedule.hoon | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 desk/ted/hooks/schedule.hoon diff --git a/desk/ted/hooks/schedule.hoon b/desk/ted/hooks/schedule.hoon new file mode 100644 index 0000000000..30eafe4df9 --- /dev/null +++ b/desk/ted/hooks/schedule.hoon @@ -0,0 +1,34 @@ +/- spider, h=hooks +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +|^ +=+ !<([~ =id:h =origin:h =action] arg) +;< our=@p bind:m get-our:s +;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +=/ =cage + :- %hook-action-0 + !> + ^- action:h + ?: ?=(%stop -.action) [%rest id origin] + [%wait id origin +.action] +;< ~ bind:m (poke-our:s %channels-server cage) +;< =^cage bind:m (take-fact:s /responses) +?> ?=(%hook-response-0 p.cage) +=+ !<(=response:h q.cage) +?> ?=(?(%wait %rest) -.response) +?: ?=(%rest -.response) + ~& "stopped scheduled hook {} running on {}" + (pure:m !>(~)) +;< now=time bind:m get-time:s +=/ fires-at (add now schedule.response) +~& "starting hook {}, scheduled to run on {} at {}" +(pure:m !>(~)) ++$ action + $% [%stop ~] + [%start schedule=@dr =config:h] + == +-- \ No newline at end of file From e58131c427730fdac2ae3182efe206a9218a3c83 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 25 Nov 2024 14:13:49 -0600 Subject: [PATCH 008/149] hooks: refactor cron to be separate, clean up types --- desk/app/channels-server.hoon | 91 ++++++++++++++++--------------- desk/lib/channel-utils.hoon | 9 +-- desk/sur/hooks.hoon | 100 +++++++++++++++++----------------- 3 files changed, 97 insertions(+), 103 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 100e52b4b4..ba027de722 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -292,18 +292,7 @@ inflate-io :: ++ inflate-io - =. cor (safe-watch /groups [our.bowl %groups] /groups) - %+ roll ~(tap by crons.hooks) - |= [[=id:h schedules=(map origin:h cron:h)] cr=_cor] - %+ roll ~(tap by schedules) - |= [[=origin:h cron:h] co=_cr] - ?~ hook=(~(get by hooks.hooks) id) co - ?~ compiled.u.hook co - ?^ delay=(~(get by delayed.hooks) delay-id) co - :: only start timers for crons that haven't already been started - =/ fires-at (add now.bowl schedule) - =/ =wire (hook-cron-wire id origin) - (schedule-hook wire origin delay-id id !>(~) fires-at) + (safe-watch /groups [our.bowl %groups] /groups) :: ++ poke |= [=mark =vase] @@ -370,7 +359,7 @@ =. order.hooks (~(put by order.hooks) nest.action seq) (give-hook-response %order nest.action seq) :: - %configure + %config ho-abet:(ho-configure:(ho-abed:ho-core id.action) +.+.action) :: %wait @@ -1076,9 +1065,8 @@ =. cor %+ roll ~(tap by (~(gut by crons.hooks) id *(map origin:h cron:h))) - |= [[=origin:h cron:h] cr=_cor] - =/ =wire (hook-cron-wire id origin) - (unschedule-hook:cr delay-id wire) + |= [[=origin:h =cron:h] cr=_cor] + (unschedule-cron:cr origin cron) =. crons.hooks (~(del by crons.hooks) id) =. order.hooks %+ roll @@ -1098,20 +1086,20 @@ |= [=nest:c =config:h] ^+ ho-core =. config.hook (~(put by config.hook) nest config) - =. cor (give-hook-response [%configure id nest config]) + =. cor (give-hook-response [%config id nest config]) ho-core ++ ho-wait - |= [=origin:h schedule=@dr =config:h] + |= [=origin:h =schedule:h =config:h] ^+ ho-core - =/ d-id (rsh [3 48] eny.bowl) + =/ fires-at + ?@ schedule (add now.bowl schedule) + start.schedule =/ crons (~(gut by crons.hooks) id *(map origin:h cron:h)) + =/ =cron:h [id schedule config fires-at] =. crons.hooks =- (~(put by crons.hooks) id.hook -) - (~(put by crons) origin [d-id schedule config]) - =/ fires-at (add now.bowl schedule) - =/ dh [d-id id !>(~) fires-at] - =/ =wire (hook-cron-wire id origin) - =. cor (schedule-hook wire origin dh) + (~(put by crons) origin cron) + =. cor (schedule-cron origin cron) =. cor (give-hook-response [%wait id origin schedule config]) ho-core ++ ho-rest @@ -1121,8 +1109,7 @@ =/ cron (~(got by crons) origin) =. crons.hooks (~(put by crons.hooks) id (~(del by crons) origin)) - =/ =wire (hook-cron-wire id origin) - =. cor (unschedule-hook delay-id.cron wire) + =. cor (unschedule-cron origin cron) =. cor (give-hook-response [%rest id origin]) ho-core ++ ho-run-single @@ -1191,32 +1178,46 @@ :: if unscheduled, ignore ?~ crons=(~(get by crons.hooks) id) cor ?~ cron=(~(get by u.crons) origin) cor - ?~ delay=(~(get by delayed.hooks) delay-id.u.cron) cor - =. delayed.hooks (~(del by delayed.hooks) delay-id.u.cron) - =/ next (add now.bowl schedule.u.cron) - =/ =wire (hook-cron-wire id origin) + =/ next + ?@ schedule.u.cron + (add now.bowl schedule.u.cron) + (add now.bowl repeat.schedule.u.cron) + =/ new-cron u.cron(fires-at next) + =. crons.hooks + %+ ~(put by crons.hooks) id + (~(put by u.crons) origin new-cron) =. cor - (schedule-hook wire origin +.u.delay(fires-at next)) + (schedule-cron origin new-cron) =/ args [[%cron ~] "cron job" origin config.u.cron] - ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.delay) args) + ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.cron) args) == -++ hook-cron-wire - |= [=id:h =origin:h] - ^- wire - %+ welp /hooks/cron/(scot %uv id) - ?@ origin ~ - /[kind.origin]/(scot %p ship.origin)/[name.origin] -++ schedule-hook - |= [=wire =origin:h dh=delayed-hook:h] +++ schedule-cron + |= [=origin:h =cron:h] ^+ cor ~& "scheduling hook" - =. delayed.hooks (~(put by delayed.hooks) id.dh [origin dh]) + =/ wire + %+ welp /hooks/cron/(scot %uv hook.cron) + ?@ origin ~ + /[kind.origin]/(scot %p ship.origin)/[name.origin] + (emit [%pass wire %arvo %b %wait fires-at.cron]) +++ unschedule-cron + |= [=origin:h =cron:h] + ~& "unscheduling hook" + =/ wire + %+ welp /hooks/cron/(scot %uv hook.cron) + ?@ origin ~ + /[kind.origin]/(scot %p ship.origin)/[name.origin] + (emit [%pass wire %arvo %b %rest fires-at.cron]) +++ schedule-delay + |= dh=delayed-hook:h + =/ =wire /hooks/delayed/(scot %uv id.dh) (emit [%pass wire %arvo %b %wait fires-at.dh]) -++ unschedule-hook - |= [=id:h =wire] +++ unschedule-delay + |= =id:h ^+ cor ?~ previous=(~(get by delayed.hooks) id) cor ~& "unscheduling hook" + =/ =wire /hooks/delayed/(scot %uv id.u.previous) (emit [%pass wire %arvo %b %rest fires-at.u.previous]) ++ run-hook-effects |= [effects=(list effect:h) =origin:h] @@ -1255,7 +1256,7 @@ :: %wait =/ =wire /hooks/delayed/(scot %uv id.effect) - =. cor (unschedule-hook id.effect wire) - (schedule-hook wire origin +:effect) + =. cor (unschedule-delay id.effect) + (schedule-delay +:effect) == -- diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index fa645b5932..44df979919 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -680,18 +680,12 @@ ++ compile |= src=@t ^- (each vase tang) - ~& %a =/ tonk=(each vase tang) - ~& %b =/ vex=(like hoon) ((full vest) [0 0] (trip src)) - ~& %c ?~ q.vex |+~[leaf+"\{{} {}}" 'syntax error'] - ~& %d %- mule |.((slap subject p.u.q.vex)) - ~& %e ~& "parsed hoon: {<-.tonk>}" - ~& %f ?: ?=(%| -.tonk) %- (slog 'returning error' p.tonk) tonk @@ -707,6 +701,5 @@ ~& "{(trip name.hook)} hook run:" ~& outcome ?: ?=(%.y -.outcome) `p.outcome - ~& "hook failed:" - ((slog p.outcome) ~) + ((slog 'hook failed:' p.outcome) ~) -- diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index cf17d92462..40be172626 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -23,7 +23,7 @@ config=(map nest config) == :: $hooks: collection of hooks, the order they should be run in, and -:: any delayed hooks that need to be run +:: any delayed hooks that need to be run ++ hooks $: hooks=(map id hook) order=(map nest (list id)) @@ -32,10 +32,12 @@ == +$ origin $@(~ nest) +$ delay-id id ++$ schedule $@(@dr [start=@da repeat=@dr]) +$ cron - $: =delay-id - schedule=@dr + $: hook=id + =schedule =config + fires-at=time == :: $delayed-hook: metadata for when a delayed hook fires from the timer +$ delayed-hook @@ -45,30 +47,30 @@ fires-at=time == :: -+$ config (map @t vase) ++$ config (map @t *) +$ action $% [%add name=@t src=@t] [%edit =id name=@t src=@t] [%del =id] [%order =nest seq=(list id)] - [%configure =id =nest =config] - [%wait =id =origin schedule=@dr =config] + [%config =id =nest =config] + [%wait =id =origin =schedule =config] [%rest =id =origin] == +$ response $% [%set =id name=@t src=@t error=(unit tang)] [%gone =id] [%order =nest seq=(list id)] - [%configure =id =nest =config] - [%wait =id =origin schedule=@dr =config] + [%config =id =nest =config] + [%wait =id =origin =schedule =config] [%rest =id =origin] == :: $context: ambient state that a hook should know about not -:: necessarily tied to a specific event +:: necessarily tied to a specific event :: :: $channel: the channel that the hook is operating on -:: $channels: all the channels in the group :: $group: the group that the channel belongs to +:: $channels: all the channels in the group :: $hook: the hook that's running :: $config: the configuration data for this instance of the hook :: $now: the current time @@ -88,6 +90,21 @@ eny=@ == :: +:: $event: the data associated with the trigger of a hook +:: +:: $on-post: a post was added, edited, deleted, or reacted to +:: $on-reply: a reply was added, edited, deleted, or reacted to +:: $cron: a scheduled wake-up +:: $wake: a delayed invocation of the hook called with metadata about +:: when it fired, its id, and the event it should run with +:: ++$ event + $% [%on-post on-post] + [%on-reply on-reply] + [%cron ~] + [%wake delayed-hook] + == +:: :: $on-post: a hook event that fires when posts are interacted with +$ on-post $% [%add post=v-post] @@ -103,37 +120,36 @@ [%del parent=v-post original=v-reply] [%react parent=v-post reply=v-reply =ship react=(unit react)] == -:: $event-type: the type of event that triggers a hook -+$ event-type ?(%on-post %on-reply %cron %delay) -:: -:: $event: the data associated with the trigger of a hook -:: -:: $on-post: a post was added, edited, deleted, or reacted to -:: $on-reply: a reply was added, edited, deleted, or reacted to -:: $cron: a scheduled wake-up -:: $wake: a delayed invocation of the hook called with metadata about -:: when it fired, its id, and the event it should run with -:: -+$ event - $% [%on-post on-post] - [%on-reply on-reply] - [%cron ~] - [%wake delayed-hook] - == :: :: $args: the arguments passed to a hook +$ args $: =event =context == +:: $outcome: the result of a hook running ++$ outcome (each return tang) +:: +:: $return: the data returned from a hook +:: +:: $result: whether the action was allowed or denied and any +:: transformed values +:: $actions: any actions that should be taken on other agents or delay +:: $new-state: the new state of the hook after running +:: ++$ return + $: $: =result + effects=(list effect) + == + new-state=vase + == :: -:: $result: the result of a hook running +:: $result: whether to allow the action, and any transformations to +:: the event :: -:: $allowed: represents the action being allowed to go through, and the -:: new value of the action +:: $allowed: represents the action being allowed to go through, and +:: the new value of the action :: $denied: represents the action being denied along with the reason -:: that the action was denied -:: $error: represents an error that occurred while running the hook +:: that the action was denied :: +$ result $% [%allowed new=event] @@ -141,8 +157,8 @@ == :: :: $effect: an effect that a hook can have, limited to agents in -:: the %groups desk. $delay is a special effect that will wake up the -:: same hook at a later time. +:: the %groups desk. $delay is a special effect that will wake +:: up the same hook at a later time. +$ effect $% [%channels =a-channels] [%groups =action:g] @@ -152,20 +168,4 @@ [%contacts =action:co] [%wait delayed-hook] == -:: -:: $return: the data returned from a hook -:: -:: $result: whether the action was allowed or denied, any new values, -:: or an error message if something went wrong -:: $actions: any actions that should be taken on other agents or delay -:: $new-state: the new state of the hook after running -:: -+$ return - $: $: =result - effects=(list effect) - == - new-state=vase - == -:: -+$ outcome (each return tang) -- \ No newline at end of file From 5285523d467aa72752ee0091255d1ca7213c5dd5 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 25 Nov 2024 21:30:03 -0600 Subject: [PATCH 009/149] hooks: cleaning up marks, adding metadata, making edit unitized --- desk/app/channels-server.hoon | 41 +++-- desk/lib/hooks-json.hoon | 216 +++++++++++++++++++++++ desk/mar/{hook => hooks}/action-0.hoon | 2 + desk/mar/hooks/full.hoon | 14 ++ desk/mar/{hook => hooks}/response-0.hoon | 2 + desk/sur/hooks.hoon | 10 +- desk/ted/hooks/edit.hoon | 6 +- 7 files changed, 272 insertions(+), 19 deletions(-) create mode 100644 desk/lib/hooks-json.hoon rename desk/mar/{hook => hooks}/action-0.hoon (75%) create mode 100644 desk/mar/hooks/full.hoon rename desk/mar/{hook => hooks}/response-0.hoon (70%) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index ba027de722..910c3fc790 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -2,9 +2,10 @@ :: :: this is the server-side from which /app/channels gets its data. :: -/- c=channels, g=groups, h=hooks +/- c=channels, g=groups, h=hooks, m=meta /+ utils=channel-utils, imp=import-aid /+ default-agent, verb, dbug, neg=negotiate +/+ hj=hooks-json :: %- %- agent:neg [| [~.channels^%1 ~ ~] ~] @@ -57,7 +58,7 @@ abet:(watch:cor path) [cards this] :: - ++ on-peek on-peek:def + ++ on-peek peek:cor ++ on-leave on-leave:def ++ on-fail on-fail:def ++ on-agent @@ -338,14 +339,14 @@ cor(pimp `|+egg-any) == :: - %hook-action-0 + %hooks-action-0 =+ !<(=action:h vase) ?- -.action %add ho-abet:(ho-add:ho-core [name src]:action) :: %edit - ho-abet:(ho-edit:(ho-abed:ho-core id.action) [name src]:action) + ho-abet:(ho-edit:(ho-abed:ho-core id.action) +.+.action) :: %del ho-abet:ho-del:(ho-abed:ho-core id.action) @@ -413,7 +414,11 @@ ^+ cor ~| watch-path=`path`pole ?+ pole ~|(%bad-watch-path !!) - [%hooks %v0 ~] cor + [%v0 %hooks ~] cor + :: + [%v0 %hooks %full ~] + =. cor (give %fact ~ hooks-full+!>(hooks)) + (give %kick ~ ~) :: [=kind:c name=@ %create ~] ?> =(our src):bowl @@ -516,6 +521,16 @@ == == :: +++ peek + |= =(pole knot) + ^- (unit (unit cage)) + =? +.pole !?=([%v0 *] +.pole) + [%v0 +.pole] + ?+ pole [~ ~] + [%x %v0 %hooks ~] + ``hooks-full+!>(hooks) + == +:: ++ arvo |= [=(pole knot) sign=sign-arvo] ^+ cor @@ -1009,7 +1024,7 @@ ++ give-hook-response |= =response:h ^+ cor - (give %fact ~[/hooks/v0] hook-response-0+!>(response)) + (give %fact ~[/hooks/v0] hooks-response-0+!>(response)) ++ ho-core |_ [=id:h =hook:h gone=_|] ++ ho-core . @@ -1039,16 +1054,17 @@ ?: ?=(%| -.result) ((slog 'compilation result:' p.result) ~) `p.result - =. hook [id name %0 src compiled !>(~) ~] + =. hook [id %0 name *data:m src compiled !>(~) ~] =. cor =/ error=(unit tang) ?:(?=(%& -.result) ~ `p.result) - (give-hook-response [%set id name src error]) + (give-hook-response [%set id name src meta.hook error]) ho-core ++ ho-edit - |= [name=@t src=@t] - =. src.hook src - =. name.hook name + |= [name=(unit @t) src=(unit @t) meta=(unit data:m)] + =? src.hook ?=(^ src) u.src + =? name.hook ?=(^ name) u.name + =? meta.hook ?=(^ meta) u.meta =/ result=(each vase tang) (compile:utils src.hook) =. compiled.hook @@ -1057,7 +1073,8 @@ =. cor =/ error=(unit tang) ?:(?=(%& -.result) ~ `p.result) - (give-hook-response [%set id name src error]) + %- give-hook-response + [%set id name.hook src.hook meta.hook error] ho-core :: ++ ho-del diff --git a/desk/lib/hooks-json.hoon b/desk/lib/hooks-json.hoon new file mode 100644 index 0000000000..bf725d7696 --- /dev/null +++ b/desk/lib/hooks-json.hoon @@ -0,0 +1,216 @@ +/- h=hooks, c=channels, m=meta +/+ cj=channel-json, gj=groups-json +=* z ..zuse +|% +++ enjs + =, enjs:format + |% + ++ id + |= i=id:h + s+(scot %uv i) + ++ hooks + |= hks=hooks:h + %- pairs + :~ hooks+(hook-map hooks.hks) + order+(order order.hks) + crons+(crons crons.hks) + == + :: + ++ hook-map + |= hks=(map id:h hook:h) + %- pairs + %+ turn + ~(tap by hks) + |= [=id:h hk=hook:h] + [(scot %uv id) (hook hk)] + :: + ++ hook + |= hk=hook:h + %- pairs + :~ id+(id id.hk) + version+s+`@tas`version.hk + name+s+name.hk + meta+(meta:enjs:gj meta.hk) + src+s+src.hk + compiled+b+?=(^ compiled.hk) + config+(config-map config.hk) + == + :: + ++ config-map + |= cfg=(map nest:c config:h) + %- pairs + %+ turn + ~(tap by cfg) + |= [=nest:c con=config:h] + [(nest-cord:enjs:cj nest) (config con)] + ++ config + |= con=config:h + %- pairs + %+ turn + ~(tap by con) + |= [key=@t noun=*] + [key s+(scot %uw `@uw`(jam noun))] + ++ order + |= ord=(map nest:c (list id:h)) + %- pairs + %+ turn + ~(tap by ord) + |= [=nest:c seq=(list id:h)] + [(nest-cord:enjs:cj nest) a+(turn seq id)] + ++ crons + |= crs=(map id:h (map origin:h cron:h)) + %- pairs + %+ turn + ~(tap by crs) + |= [=id:h cr=(map origin:h cron:h)] + [(scot %uv id) (cron-map cr)] + ++ cron-map + |= cr=(map origin:h cron:h) + %- pairs + %+ turn + ~(tap by cr) + |= [=origin:h crn=cron:h] + :_ (cron crn) + ?@(origin 'global' (nest-cord:enjs:cj origin)) + ++ cron + |= crn=cron:h + %- pairs + :~ hook+(id hook.crn) + schedule+(schedule schedule.crn) + config+(config config.crn) + fires-at+s+(scot %da fires-at.crn) + == + ++ schedule + |= sch=schedule:h + ?@ sch s+(scot %dr sch) + %- pairs + :~ start+s+(scot %da start.sch) + repeat+s+(scot %dr repeat.sch) + == + ++ response + |= r=response:h + %+ frond -.r + ?- -.r + %set (set-rsp +.r) + %gone (id id.r) + %order (order-rsp +.r) + %config (config-rsp +.r) + %wait (wait-rsp +.r) + %rest (rest-rsp +.r) + == + ++ set-rsp + |= [i=id:h name=@t src=@t meta=data:m error=(unit ^tang)] + %- pairs + :~ id+(id i) + name+s+name + src+s+src + meta+(meta:enjs:gj meta) + error+?~(error ~ (tang u.error)) + == + ++ order-rsp + |= [=nest:c seq=(list id:h)] + %- pairs + :~ nest+(nest:enjs:cj nest) + seq+a+(turn seq id) + == + ++ config-rsp + |= [i=id:h =nest:c con=config:h] + %- pairs + :~ id+(id i) + nest+(nest:enjs:cj nest) + config+(config con) + == + ++ wait-rsp + |= [i=id:h or=origin:h sch=schedule:h con=config:h] + %- pairs + :~ id+(id i) + origin+s+?~(or 'global' (nest-cord:enjs:cj or)) + schedule+(schedule sch) + config+(config con) + == + ++ rest-rsp + |= [i=id:h or=origin:h] + %- pairs + :~ id+(id i) + origin+s+?~(or 'global' (nest-cord:enjs:cj or)) + == + ++ tang + |= t=^tang + :- %s + %- crip + %+ roll t + |= [tk=^tank tp=^tape] + ~! tk + =/ next=^tape ~(ram re tk) + (welp (snoc tp '\0a') next) + -- +:: +++ dejs + =, dejs:format + |% + ++ id (se %uv) + ++ action + %- of + :~ add/add + edit/edit + del/id + order/order + config/config + wait/wait + rest/rest + == + ++ add + %- ot + :~ name/so + src/so + == + ++ edit + %- ot + :~ id/id + name/(mu so) + src/(mu so) + meta/(mu meta:dejs:gj) + == + ++ order + %- ot + :~ nest/nest:dejs:cj + seq/(ar id) + == + ++ config + %- ot + :~ id/id + nest/nest:dejs:cj + config/(om noun) + == + ++ noun + |= j=json + (cue ((se %uw) j)) + ++ origin + |= j=json + ?~(j ~ (nest:dejs:cj j)) + ++ wait + %- ot + :~ id/id + origin/origin + schedule/schedule + config/(om noun) + == + ++ rest + %- ot + :~ id/id + origin/origin + == + ++ schedule + |= j=json + ?+ j !! + [%s *] ((se %dr) j) + :: + [%o *] + %. j + %- ot + :~ start/(se %da) + repeat/(se %dr) + == + == + -- +-- diff --git a/desk/mar/hook/action-0.hoon b/desk/mar/hooks/action-0.hoon similarity index 75% rename from desk/mar/hook/action-0.hoon rename to desk/mar/hooks/action-0.hoon index bab332196b..45838b60b0 100644 --- a/desk/mar/hook/action-0.hoon +++ b/desk/mar/hooks/action-0.hoon @@ -1,4 +1,5 @@ /- h=hooks, c=channels +/+ hj=hooks-json |_ =action:h ++ grad %noun ++ grow @@ -8,5 +9,6 @@ ++ grab |% ++ noun action:h + ++ json action:dejs:hj -- -- diff --git a/desk/mar/hooks/full.hoon b/desk/mar/hooks/full.hoon new file mode 100644 index 0000000000..6d9dd00ba5 --- /dev/null +++ b/desk/mar/hooks/full.hoon @@ -0,0 +1,14 @@ +/- h=hooks, c=channels +/+ hj=hooks-json +|_ =hooks:h +++ grad %noun +++ grow + |% + ++ noun hooks + ++ json (hooks:enjs:hj hooks) + -- +++ grab + |% + ++ noun hooks:h + -- +-- diff --git a/desk/mar/hook/response-0.hoon b/desk/mar/hooks/response-0.hoon similarity index 70% rename from desk/mar/hook/response-0.hoon rename to desk/mar/hooks/response-0.hoon index 88f7887db3..e4eac8ff34 100644 --- a/desk/mar/hook/response-0.hoon +++ b/desk/mar/hooks/response-0.hoon @@ -1,9 +1,11 @@ /- h=hooks, c=channels +/+ hj=hooks-json |_ =response:h ++ grad %noun ++ grow |% ++ noun response + ++ json (response:enjs:hj response) -- ++ grab |% diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 40be172626..de00bf9d4a 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -1,4 +1,4 @@ -/- *channels, g=groups, a=activity, ch=chat, co=contacts +/- *channels, g=groups, a=activity, ch=chat, co=contacts, m=meta |% :: $id: a unique identifier for a hook +$ id @uv @@ -15,8 +15,9 @@ :: ++ hook $: =id - name=@t version=%0 + name=@t + meta=data:m src=@t compiled=(unit vase) state=vase @@ -50,7 +51,7 @@ +$ config (map @t *) +$ action $% [%add name=@t src=@t] - [%edit =id name=@t src=@t] + [%edit =id name=(unit @t) src=(unit @t) meta=(unit data:m)] [%del =id] [%order =nest seq=(list id)] [%config =id =nest =config] @@ -58,7 +59,7 @@ [%rest =id =origin] == +$ response - $% [%set =id name=@t src=@t error=(unit tang)] + $% [%set =id name=@t src=@t meta=data:m error=(unit tang)] [%gone =id] [%order =nest seq=(list id)] [%config =id =nest =config] @@ -168,4 +169,5 @@ [%contacts =action:co] [%wait delayed-hook] == +:: -- \ No newline at end of file diff --git a/desk/ted/hooks/edit.hoon b/desk/ted/hooks/edit.hoon index 05e1090f95..8730b0cfc0 100644 --- a/desk/ted/hooks/edit.hoon +++ b/desk/ted/hooks/edit.hoon @@ -1,14 +1,14 @@ -/- spider, h=hooks +/- spider, h=hooks, m=meta /+ s=strandio =, strand=strand:spider ^- thread:spider |= arg=vase =/ m (strand ,vase) ^- form:m -=+ !<([~ =id:h name=@t src=@t] arg) +=+ !<([~ =id:h name=(unit @t) src=(unit @t) meta=(unit data:m)] arg) ;< our=@p bind:m get-our:s ;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) -=/ =cage hook-action-0+!>(`action:h`[%edit id name src]) +=/ =cage hook-action-0+!>(`action:h`[%edit id name src meta]) ;< ~ bind:m (poke-our:s %channels-server cage) ;< =^cage bind:m (take-fact:s /responses) ?> ?=(%hook-response-0 p.cage) From 840fa02247b93e9aa28e87e92912b27050343627 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 10:09:13 -0600 Subject: [PATCH 010/149] hooks: depluralizing folders --- desk/app/channels-server.hoon | 8 ++++---- desk/mar/{hooks => hook}/action-0.hoon | 0 desk/mar/{hooks => hook}/full.hoon | 0 desk/mar/{hooks => hook}/response-0.hoon | 0 desk/ted/{hooks => hook}/add.hoon | 0 desk/ted/{hooks => hook}/configure.hoon | 0 desk/ted/{hooks => hook}/del.hoon | 0 desk/ted/{hooks => hook}/edit.hoon | 0 desk/ted/{hooks => hook}/order.hoon | 0 desk/ted/{hooks => hook}/run.hoon | 0 desk/ted/{hooks => hook}/schedule.hoon | 0 11 files changed, 4 insertions(+), 4 deletions(-) rename desk/mar/{hooks => hook}/action-0.hoon (100%) rename desk/mar/{hooks => hook}/full.hoon (100%) rename desk/mar/{hooks => hook}/response-0.hoon (100%) rename desk/ted/{hooks => hook}/add.hoon (100%) rename desk/ted/{hooks => hook}/configure.hoon (100%) rename desk/ted/{hooks => hook}/del.hoon (100%) rename desk/ted/{hooks => hook}/edit.hoon (100%) rename desk/ted/{hooks => hook}/order.hoon (100%) rename desk/ted/{hooks => hook}/run.hoon (100%) rename desk/ted/{hooks => hook}/schedule.hoon (100%) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 910c3fc790..182a437f47 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -339,7 +339,7 @@ cor(pimp `|+egg-any) == :: - %hooks-action-0 + %hook-action-0 =+ !<(=action:h vase) ?- -.action %add @@ -417,7 +417,7 @@ [%v0 %hooks ~] cor :: [%v0 %hooks %full ~] - =. cor (give %fact ~ hooks-full+!>(hooks)) + =. cor (give %fact ~ hook-full+!>(hooks)) (give %kick ~ ~) :: [=kind:c name=@ %create ~] @@ -528,7 +528,7 @@ [%v0 +.pole] ?+ pole [~ ~] [%x %v0 %hooks ~] - ``hooks-full+!>(hooks) + ``hook-full+!>(hooks) == :: ++ arvo @@ -1024,7 +1024,7 @@ ++ give-hook-response |= =response:h ^+ cor - (give %fact ~[/hooks/v0] hooks-response-0+!>(response)) + (give %fact ~[/hooks/v0] hook-response-0+!>(response)) ++ ho-core |_ [=id:h =hook:h gone=_|] ++ ho-core . diff --git a/desk/mar/hooks/action-0.hoon b/desk/mar/hook/action-0.hoon similarity index 100% rename from desk/mar/hooks/action-0.hoon rename to desk/mar/hook/action-0.hoon diff --git a/desk/mar/hooks/full.hoon b/desk/mar/hook/full.hoon similarity index 100% rename from desk/mar/hooks/full.hoon rename to desk/mar/hook/full.hoon diff --git a/desk/mar/hooks/response-0.hoon b/desk/mar/hook/response-0.hoon similarity index 100% rename from desk/mar/hooks/response-0.hoon rename to desk/mar/hook/response-0.hoon diff --git a/desk/ted/hooks/add.hoon b/desk/ted/hook/add.hoon similarity index 100% rename from desk/ted/hooks/add.hoon rename to desk/ted/hook/add.hoon diff --git a/desk/ted/hooks/configure.hoon b/desk/ted/hook/configure.hoon similarity index 100% rename from desk/ted/hooks/configure.hoon rename to desk/ted/hook/configure.hoon diff --git a/desk/ted/hooks/del.hoon b/desk/ted/hook/del.hoon similarity index 100% rename from desk/ted/hooks/del.hoon rename to desk/ted/hook/del.hoon diff --git a/desk/ted/hooks/edit.hoon b/desk/ted/hook/edit.hoon similarity index 100% rename from desk/ted/hooks/edit.hoon rename to desk/ted/hook/edit.hoon diff --git a/desk/ted/hooks/order.hoon b/desk/ted/hook/order.hoon similarity index 100% rename from desk/ted/hooks/order.hoon rename to desk/ted/hook/order.hoon diff --git a/desk/ted/hooks/run.hoon b/desk/ted/hook/run.hoon similarity index 100% rename from desk/ted/hooks/run.hoon rename to desk/ted/hook/run.hoon diff --git a/desk/ted/hooks/schedule.hoon b/desk/ted/hook/schedule.hoon similarity index 100% rename from desk/ted/hooks/schedule.hoon rename to desk/ted/hook/schedule.hoon From 75abc7290c5397e89526030e7a49f86cbfac043c Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 10:44:45 -0600 Subject: [PATCH 011/149] hooks: better scheduling --- desk/app/channels-server.hoon | 37 +++++++++++++++++++---------------- desk/lib/hooks-json.hoon | 10 ++++------ desk/sur/hooks.hoon | 7 +++---- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 182a437f47..2a0e003c45 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -346,7 +346,7 @@ ho-abet:(ho-add:ho-core [name src]:action) :: %edit - ho-abet:(ho-edit:(ho-abed:ho-core id.action) +.+.action) + ho-abet:(ho-edit:(ho-abed:ho-core id.action) +>.action) :: %del ho-abet:ho-del:(ho-abed:ho-core id.action) @@ -361,10 +361,10 @@ (give-hook-response %order nest.action seq) :: %config - ho-abet:(ho-configure:(ho-abed:ho-core id.action) +.+.action) + ho-abet:(ho-configure:(ho-abed:ho-core id.action) +>.action) :: %wait - ho-abet:(ho-wait:(ho-abed:ho-core id.action) +.+.action) + ho-abet:(ho-wait:(ho-abed:ho-core id.action) +>.action) :: %rest ho-abet:(ho-rest:(ho-abed:ho-core id.action) origin.action) @@ -1106,13 +1106,13 @@ =. cor (give-hook-response [%config id nest config]) ho-core ++ ho-wait - |= [=origin:h =schedule:h =config:h] + |= [=origin:h schedule=$@(@dr schedule:h) =config:h] ^+ ho-core - =/ fires-at - ?@ schedule (add now.bowl schedule) - start.schedule + =/ schedule + ?: ?=(@ schedule) [now.bowl schedule] + schedule =/ crons (~(gut by crons.hooks) id *(map origin:h cron:h)) - =/ =cron:h [id schedule config fires-at] + =/ =cron:h [id schedule config] =. crons.hooks =- (~(put by crons.hooks) id.hook -) (~(put by crons) origin cron) @@ -1195,16 +1195,19 @@ :: if unscheduled, ignore ?~ crons=(~(get by crons.hooks) id) cor ?~ cron=(~(get by u.crons) origin) cor - =/ next - ?@ schedule.u.cron - (add now.bowl schedule.u.cron) - (add now.bowl repeat.schedule.u.cron) - =/ new-cron u.cron(fires-at next) + =. next.schedule.u.cron + :: we don't want to run the cron for every iteration it would + :: have run 'offline', so we check here to make sure that the + :: next fire time is in the future + =/ next (add [next repeat]:schedule.u.cron) + |- + ?: (gte next now.bowl) next + $(next (add next repeat.schedule.u.cron)) =. crons.hooks %+ ~(put by crons.hooks) id - (~(put by u.crons) origin new-cron) + (~(put by u.crons) origin u.cron) =. cor - (schedule-cron origin new-cron) + (schedule-cron origin u.cron) =/ args [[%cron ~] "cron job" origin config.u.cron] ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.cron) args) == @@ -1216,7 +1219,7 @@ %+ welp /hooks/cron/(scot %uv hook.cron) ?@ origin ~ /[kind.origin]/(scot %p ship.origin)/[name.origin] - (emit [%pass wire %arvo %b %wait fires-at.cron]) + (emit [%pass wire %arvo %b %wait next.schedule.cron]) ++ unschedule-cron |= [=origin:h =cron:h] ~& "unscheduling hook" @@ -1224,7 +1227,7 @@ %+ welp /hooks/cron/(scot %uv hook.cron) ?@ origin ~ /[kind.origin]/(scot %p ship.origin)/[name.origin] - (emit [%pass wire %arvo %b %rest fires-at.cron]) + (emit [%pass wire %arvo %b %rest next.schedule.cron]) ++ schedule-delay |= dh=delayed-hook:h =/ =wire /hooks/delayed/(scot %uv id.dh) diff --git a/desk/lib/hooks-json.hoon b/desk/lib/hooks-json.hoon index bf725d7696..ece631a223 100644 --- a/desk/lib/hooks-json.hoon +++ b/desk/lib/hooks-json.hoon @@ -78,13 +78,11 @@ :~ hook+(id hook.crn) schedule+(schedule schedule.crn) config+(config config.crn) - fires-at+s+(scot %da fires-at.crn) == ++ schedule |= sch=schedule:h - ?@ sch s+(scot %dr sch) %- pairs - :~ start+s+(scot %da start.sch) + :~ next+s+(scot %da next.sch) repeat+s+(scot %dr repeat.sch) == ++ response @@ -121,11 +119,11 @@ config+(config con) == ++ wait-rsp - |= [i=id:h or=origin:h sch=schedule:h con=config:h] + |= [i=id:h or=origin:h sch=$@(@dr schedule:h) con=config:h] %- pairs :~ id+(id i) origin+s+?~(or 'global' (nest-cord:enjs:cj or)) - schedule+(schedule sch) + schedule+?@(sch s+(scot %dr sch) (schedule sch)) config+(config con) == ++ rest-rsp @@ -208,7 +206,7 @@ [%o *] %. j %- ot - :~ start/(se %da) + :~ next/(se %da) repeat/(se %dr) == == diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index de00bf9d4a..984c08f4d1 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -33,12 +33,11 @@ == +$ origin $@(~ nest) +$ delay-id id -+$ schedule $@(@dr [start=@da repeat=@dr]) ++$ schedule [next=@da repeat=@dr] +$ cron $: hook=id =schedule =config - fires-at=time == :: $delayed-hook: metadata for when a delayed hook fires from the timer +$ delayed-hook @@ -55,7 +54,7 @@ [%del =id] [%order =nest seq=(list id)] [%config =id =nest =config] - [%wait =id =origin =schedule =config] + [%wait =id =origin schedule=$@(@dr schedule) =config] [%rest =id =origin] == +$ response @@ -63,7 +62,7 @@ [%gone =id] [%order =nest seq=(list id)] [%config =id =nest =config] - [%wait =id =origin =schedule =config] + [%wait =id =origin schedule=$@(@dr schedule) =config] [%rest =id =origin] == :: $context: ambient state that a hook should know about not From 83727c369556a066a01d9d0c5b5290278660e278 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 10:59:54 -0600 Subject: [PATCH 012/149] channels-server: restoring kind check for now --- desk/app/channels-server.hoon | 1 + 1 file changed, 1 insertion(+) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 2a0e003c45..6c013029c7 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -734,6 +734,7 @@ %add ~& "adding post" ?> |(=(src.bowl our.bowl) =(src.bowl author.essay.c-post)) + ?> =(kind.nest -.kind-data.essay.c-post) =/ id=id-post:c |- =/ post (get:on-v-posts:c posts.channel now.bowl) From b8f8d021fca20b233136c073687cd16c173a7d8b Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 11:42:00 -0600 Subject: [PATCH 013/149] hooks: cleaning up logging and ids --- desk/app/channels-server.hoon | 16 +++++----------- desk/lib/channel-utils.hoon | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 6c013029c7..f4c5275e91 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1045,12 +1045,11 @@ ++ ho-add |= [name=@t src=@t] ^+ ho-core - ~& "adding hook {}" - =. id (rsh [3 48] eny.bowl) + =. id + =+ i=(end 7 eny.bowl) + |-(?:((~(has by hooks.hooks) i) $(i +(i)) i)) =/ result=(each vase tang) - ~& "compiling hook" (compile:utils src) - ~& "compilation result: {<-.result>}" =/ compiled ?: ?=(%| -.result) ((slog 'compilation result:' p.result) ~) @@ -1140,9 +1139,9 @@ =/ return=(unit return:h) (run-hook:utils [event context(hook hook)] hook) ?~ return - ~& "{prefix} {} failed" + %- (slog (crip "{prefix} {} failed, running on {}") ~) ho-core - ~& "{prefix} {} ran" + %- (slog (crip "{prefix} {} ran on {}") ~) =. hook hook(state new-state.u.return) =. cor (run-hook-effects effects.u.return origin) ho-core @@ -1155,14 +1154,12 @@ =/ current-event event =| effects=(list effect:h) =/ order (~(got by order.hooks) nest) - ~& "got orders {}" =/ channel `[nest (~(got by v-channels) nest)] =/ =context:h (get-hook-context channel *config:h) |- ?~ order [&+current-event effects] =* next $(order t.order) - ~& "getting hook" =/ hook (~(got by hooks.hooks) i.order) =/ ctx context(hook hook, config (~(gut by config.hook) nest ~)) =/ return=(unit return:h) @@ -1215,7 +1212,6 @@ ++ schedule-cron |= [=origin:h =cron:h] ^+ cor - ~& "scheduling hook" =/ wire %+ welp /hooks/cron/(scot %uv hook.cron) ?@ origin ~ @@ -1223,7 +1219,6 @@ (emit [%pass wire %arvo %b %wait next.schedule.cron]) ++ unschedule-cron |= [=origin:h =cron:h] - ~& "unscheduling hook" =/ wire %+ welp /hooks/cron/(scot %uv hook.cron) ?@ origin ~ @@ -1237,7 +1232,6 @@ |= =id:h ^+ cor ?~ previous=(~(get by delayed.hooks) id) cor - ~& "unscheduling hook" =/ =wire /hooks/delayed/(scot %uv id.u.previous) (emit [%pass wire %arvo %b %rest fires-at.u.previous]) ++ run-hook-effects diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index 44df979919..1d3d8f79c3 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -684,8 +684,8 @@ =/ vex=(like hoon) ((full vest) [0 0] (trip src)) ?~ q.vex |+~[leaf+"\{{} {}}" 'syntax error'] %- mule - |.((slap subject p.u.q.vex)) - ~& "parsed hoon: {<-.tonk>}" + |.((~(mint ut p:subject) %noun p.u.q.vex)) + %- (slog (crip "parsed hoon: {<-.tonk>}") ~) ?: ?=(%| -.tonk) %- (slog 'returning error' p.tonk) tonk @@ -693,13 +693,14 @@ ++ run-hook |= [=args:h =hook:h] ^- (unit return:h) - ~& "running hook: {} {}" - ?~ compiled.hook - ~&("hook not compiled" ~) - :: ~& "nock: {}" - =+ !<(=outcome:h (slam u.compiled.hook !>(args))) - ~& "{(trip name.hook)} hook run:" - ~& outcome + %- (slog (crip "running hook: {} {}") ~) + %- ?~ channel.context.args same + (slog (crip "on channel: {}") ~) + ?~ compiled.hook ~ + =/ gate [p.u.compiled.hook .*(q:subject q.u.compiled.hook)] + =+ !<(=outcome:h (slam gate !>(args))) + %- (slog (crip "{(trip name.hook)} {} hook run:") ~) + %- (slog (crip "{}") ~) ?: ?=(%.y -.outcome) `p.outcome ((slog 'hook failed:' p.outcome) ~) -- From 9f85a72532f5d66aa94d4be8fedd4df33a5e5796 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 11:49:06 -0600 Subject: [PATCH 014/149] hooks: fail edit on bad compile --- desk/app/channels-server.hoon | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index f4c5275e91..bc817fcb7a 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1063,18 +1063,19 @@ ++ ho-edit |= [name=(unit @t) src=(unit @t) meta=(unit data:m)] =? src.hook ?=(^ src) u.src - =? name.hook ?=(^ name) u.name - =? meta.hook ?=(^ meta) u.meta =/ result=(each vase tang) (compile:utils src.hook) - =. compiled.hook - ?: ?=(%| -.result) ~ - `p.result + ?: ?=(%| -.result) + =. cor + %- give-hook-response + [%set id name.hook src.hook meta.hook `p.result] + ho-core + =? name.hook ?=(^ name) u.name + =? meta.hook ?=(^ meta) u.meta + =. compiled.hook `p.result =. cor - =/ error=(unit tang) - ?:(?=(%& -.result) ~ `p.result) %- give-hook-response - [%set id name.hook src.hook meta.hook error] + [%set id name.hook src.hook meta.hook ~] ho-core :: ++ ho-del From 424ee7fcba68d8b7793475e673050e6f102d7263 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 13:00:00 -0600 Subject: [PATCH 015/149] hooks: ignore premature firing --- desk/app/channels-server.hoon | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index bc817fcb7a..9506a2030d 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1182,6 +1182,8 @@ ?~ delay=(~(get by delayed.hooks) id) cor :: make sure we clean up =. delayed.hooks (~(del by delayed.hooks) id) + :: ignore premature fires + ?: (lth now.bowl fires-at.u.delay) cor =* origin origin.u.delay =/ hook (~(got by hooks.hooks) hook.u.delay) =/ config ?@(origin ~ (~(gut by config.hook) origin ~)) @@ -1194,6 +1196,8 @@ :: if unscheduled, ignore ?~ crons=(~(get by crons.hooks) id) cor ?~ cron=(~(get by u.crons) origin) cor + :: ignore premature fires + ?: (lth now.bowl next.schedule.u.cron) cor =. next.schedule.u.cron :: we don't want to run the cron for every iteration it would :: have run 'offline', so we check here to make sure that the From 05792c8d90e4fcdd6666e8102095270e981b771b Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 13:22:56 -0600 Subject: [PATCH 016/149] channel-utils: simpler log --- desk/lib/channel-utils.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index 1d3d8f79c3..7bdcc0987b 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -700,7 +700,7 @@ =/ gate [p.u.compiled.hook .*(q:subject q.u.compiled.hook)] =+ !<(=outcome:h (slam gate !>(args))) %- (slog (crip "{(trip name.hook)} {} hook run:") ~) - %- (slog (crip "{}") ~) + %- (slog >outcome< ~) ?: ?=(%.y -.outcome) `p.outcome ((slog 'hook failed:' p.outcome) ~) -- From 8923a79a338107c486eda16a3ac16f0cf08fb45c Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 26 Nov 2024 14:59:54 -0600 Subject: [PATCH 017/149] desktop: fix ChatList/ChannelList height/space weirdness --- .../components/ListItem/ChannelListItem.tsx | 9 +++---- .../src/components/ListItem/GroupListItem.tsx | 6 ++--- .../ui/src/components/ListItem/ListItem.tsx | 27 ++++++++++--------- packages/ui/src/components/TextV2/Text.tsx | 2 +- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 279763bef8..4d6aef6943 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -3,7 +3,6 @@ import * as logic from '@tloncorp/shared/logic'; import { useMemo } from 'react'; import { View } from 'tamagui'; import { isWeb } from 'tamagui'; -import { getVariableValue } from 'tamagui'; import * as utils from '../../utils'; import { capitalize } from '../../utils'; @@ -102,9 +101,7 @@ export function ChannelListItem({ )} @@ -112,13 +109,13 @@ export function ChannelListItem({ {isWeb && ( - + diff --git a/packages/ui/src/components/ListItem/GroupListItem.tsx b/packages/ui/src/components/ListItem/GroupListItem.tsx index ab897d5d0b..5a5a550dc8 100644 --- a/packages/ui/src/components/ListItem/GroupListItem.tsx +++ b/packages/ui/src/components/ListItem/GroupListItem.tsx @@ -68,7 +68,7 @@ export const GroupListItem = ({ )} @@ -77,13 +77,13 @@ export const GroupListItem = ({ {isWeb && !isPending && ( - + diff --git a/packages/ui/src/components/ListItem/ListItem.tsx b/packages/ui/src/components/ListItem/ListItem.tsx index b243fb237f..8edd5989fe 100644 --- a/packages/ui/src/components/ListItem/ListItem.tsx +++ b/packages/ui/src/components/ListItem/ListItem.tsx @@ -3,12 +3,11 @@ import * as db from '@tloncorp/shared/db'; import { ComponentProps, ReactElement, useMemo } from 'react'; import { getVariableValue, - isWeb, styled, useTheme, withStaticProperties, } from 'tamagui'; -import { SizableText, Stack, View, XStack, YStack } from 'tamagui'; +import { Stack, View, XStack, YStack } from 'tamagui'; import { numberWithMax } from '../../utils'; import { @@ -20,6 +19,7 @@ import { } from '../Avatar'; import ContactName from '../ContactName'; import { Icon, IconType } from '../Icon'; +import { Text } from '../TextV2'; export interface BaseListItemProps { model: T; @@ -80,14 +80,16 @@ const ListItemImageIcon = ImageAvatar; const ListItemMainContent = styled(YStack, { name: 'ListItemMainContent', flex: 1, - justifyContent: 'space-evenly', - height: isWeb ? '$5xl' : '$4xl', + justifyContent: 'space-around', + height: '$4xl', }); -const ListItemTitle = styled(SizableText, { +const ListItemTitle = styled(Text, { name: 'ListItemTitle', color: '$primaryText', numberOfLines: 1, + size: '$label/l', + paddingBottom: 1, variants: { dimmed: { true: { @@ -121,19 +123,19 @@ const ListItemSubtitleIcon = styled(Icon, { size: '$s', }); -const ListItemSubtitle = styled(SizableText, { +const ListItemSubtitle = styled(Text, { name: 'ListItemSubtitle', numberOfLines: 1, - size: '$s', + size: '$label/m', color: '$tertiaryText', }); -export const ListItemTimeText = styled(SizableText, { +export const ListItemTimeText = styled(Text, { name: 'ListItemTimeText', numberOfLines: 1, color: '$tertiaryText', - size: '$s', - lineHeight: '$xs', + size: '$label/m', + paddingBottom: '$xs', }); const ListItemTime = ListItemTimeText.styleable<{ @@ -185,9 +187,9 @@ const ListItemCount = ({ {muted && ( )} - + {numberWithMax(count, 99)} - + ); @@ -197,6 +199,7 @@ const ListItemCountNumber = styled(XStack, { name: 'ListItemCountNumber', gap: '$s', alignItems: 'center', + paddingVertical: '$s', variants: { hidden: { true: { diff --git a/packages/ui/src/components/TextV2/Text.tsx b/packages/ui/src/components/TextV2/Text.tsx index 8ac9b42e0b..995f376269 100644 --- a/packages/ui/src/components/TextV2/Text.tsx +++ b/packages/ui/src/components/TextV2/Text.tsx @@ -59,7 +59,7 @@ export const typeStyles = { fontSize: 16, lineHeight: 24, letterSpacing: -0.2, - fontWeight: '500', + fontWeight: '400', }, '$label/xl': { fontSize: 17, From 2379d807efd008c8476875d2b572d724f0f6616d Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 26 Nov 2024 15:17:23 -0600 Subject: [PATCH 018/149] hooks: cleanup threads and run everything --- desk/app/channels-server.hoon | 6 ++++-- desk/ted/hook/add.hoon | 7 +++---- desk/ted/hook/configure.hoon | 8 ++++---- desk/ted/hook/del.hoon | 4 ++-- desk/ted/hook/edit.hoon | 13 ++++++------- desk/ted/hook/order.hoon | 4 ++-- desk/ted/hook/run.hoon | 26 ++++++++++++++------------ desk/ted/hook/schedule.hoon | 12 +++++++----- 8 files changed, 42 insertions(+), 38 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 875df5066b..edf10cdfec 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1031,7 +1031,7 @@ ++ give-hook-response |= =response:h ^+ cor - (give %fact ~[/hooks/v0] hook-response-0+!>(response)) + (give %fact ~[/v0/hooks] hook-response-0+!>(response)) ++ ho-core |_ [=id:h =hook:h gone=_|] ++ ho-core . @@ -1283,6 +1283,8 @@ %wait =/ =wire /hooks/delayed/(scot %uv id.effect) =. cor (unschedule-delay id.effect) - (schedule-delay +:effect) + =. delayed.hooks + (~(put by delayed.hooks) id.effect [origin +.effect]) + (schedule-delay +.effect) == -- diff --git a/desk/ted/hook/add.hoon b/desk/ted/hook/add.hoon index cafb458e84..2e66c7b2ed 100644 --- a/desk/ted/hook/add.hoon +++ b/desk/ted/hook/add.hoon @@ -7,15 +7,14 @@ ^- form:m =+ !<([~ name=@t src=@t] arg) ;< our=@p bind:m get-our:s -;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%add name src]) ;< ~ bind:m (poke-our:s %channels-server cage) ;< =^cage bind:m (take-fact:s /responses) ?> ?=(%hook-response-0 p.cage) =+ !<(=response:h q.cage) ?> ?=(%set -.response) -~& "hook {} added with id {}" +%- (slog (crip "hook {} added with id {}") ~) ?~ error.response (pure:m !>(~)) -~& "compilation error:" -%- (slog u.error.response) +%- (slog 'compilation error:' u.error.response) (pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hook/configure.hoon b/desk/ted/hook/configure.hoon index 72754085d5..20927f9f85 100644 --- a/desk/ted/hook/configure.hoon +++ b/desk/ted/hook/configure.hoon @@ -7,12 +7,12 @@ ^- form:m =+ !<([~ =id:h =nest:c =config:h] arg) ;< our=@p bind:m get-our:s -;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) -=/ =cage hook-action-0+!>(`action:h`[%configure id nest config]) +;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) +=/ =cage hook-action-0+!>(`action:h`[%config id nest config]) ;< ~ bind:m (poke-our:s %channels-server cage) ;< =^cage bind:m (take-fact:s /responses) ?> ?=(%hook-response-0 p.cage) =+ !<(=response:h q.cage) -?> ?=(%configure -.response) -~& "hook {} running on {} configured" +?> ?=(%config -.response) +%- (slog (crip "hook {} running on {} configured") ~) (pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hook/del.hoon b/desk/ted/hook/del.hoon index 0ffc680d72..b955c86621 100644 --- a/desk/ted/hook/del.hoon +++ b/desk/ted/hook/del.hoon @@ -7,7 +7,7 @@ ^- form:m =+ !<([~ =id:h] arg) ;< our=@p bind:m get-our:s -;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%del id]) ;< ~ bind:m (poke-our:s %channels-server cage) ;< =^cage bind:m (take-fact:s /responses) @@ -15,5 +15,5 @@ =+ !<(=response:h q.cage) ?> ?=(%gone -.response) ?> =(id id.response) -~& "hook {} deleted" +%- (slog (crip "hook {} deleted") ~) (pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hook/edit.hoon b/desk/ted/hook/edit.hoon index 8730b0cfc0..04fbbdc369 100644 --- a/desk/ted/hook/edit.hoon +++ b/desk/ted/hook/edit.hoon @@ -1,13 +1,13 @@ -/- spider, h=hooks, m=meta +/- spider, h=hooks, meta /+ s=strandio =, strand=strand:spider ^- thread:spider |= arg=vase =/ m (strand ,vase) ^- form:m -=+ !<([~ =id:h name=(unit @t) src=(unit @t) meta=(unit data:m)] arg) +=+ !<([~ =id:h name=(unit @t) src=(unit @t) meta=(unit data:meta)] arg) ;< our=@p bind:m get-our:s -;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%edit id name src meta]) ;< ~ bind:m (poke-our:s %channels-server cage) ;< =^cage bind:m (take-fact:s /responses) @@ -15,9 +15,8 @@ =+ !<(=response:h q.cage) ?> ?=(%set -.response) ?~ error.response - ~& "hook {} edited successfully" + %- (slog (crip "hook {} edited successfully") ~) (pure:m !>(~)) -~& "hook {} edited" -~& "compilation error:" -%- (slog u.error.response) +%- (slog (crip "hook {} edited") ~) +%- (slog 'compilation error:' u.error.response) (pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hook/order.hoon b/desk/ted/hook/order.hoon index e9980c1994..d31736f891 100644 --- a/desk/ted/hook/order.hoon +++ b/desk/ted/hook/order.hoon @@ -7,13 +7,13 @@ ^- form:m =+ !<([~ =nest:c seq=(list id:h)] arg) ;< our=@p bind:m get-our:s -;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%order nest seq]) ;< ~ bind:m (poke-our:s %channels-server cage) ;< =^cage bind:m (take-fact:s /responses) ?> ?=(%hook-response-0 p.cage) =+ !<(=response:h q.cage) ?> ?=(%order -.response) -~& "new hook order for {}" +%- (slog (crip "new hook order for {}") ~) ~& seq.response (pure:m !>(~)) \ No newline at end of file diff --git a/desk/ted/hook/run.hoon b/desk/ted/hook/run.hoon index 1f9ea6f685..1efcc6e1d5 100644 --- a/desk/ted/hook/run.hoon +++ b/desk/ted/hook/run.hoon @@ -10,24 +10,22 @@ ;< our=@p bind:m get-our:s =/ compiled=(each vase tang) (compile:utils src) ?. ?=(%& -.compiled) - ~& "compilation error:" - %- (slog p.compiled) + %- (slog 'compilation error:' p.compiled) (pure:m !>(~)) -~& "compiled successfully" ;< ctx=context:h bind:m (get-context context-option) -=+ !<(=outcome:h (slam p.compiled !>([event ctx]))) +=/ gate [p.p.compiled .*(q:subject:utils q.p.compiled)] +=+ !<(=outcome:h (slam gate !>([event ctx]))) ?: ?=(%.y -.outcome) - ~& "hook ran successfully" + %- (slog 'hook ran successfully' ~) (pure:m !>(p.outcome)) -~& "hook failed:" -%- (slog p.outcome) +%- (slog 'hook failed:' p.outcome) (pure:m !>(~)) +$ event-option $% [%ref path=@t] [%event event:h] == +$ context-option - $% [%origin =origin:h state=(unit vase)] + $% [%origin =origin:h state=(unit vase) config=(unit config:h)] [%context =context:h] == ++ get-context @@ -35,24 +33,27 @@ =/ m (strand ,context:h) ^- form:m ?: ?=(%context -.context-option) (pure:m context.context-option) - =/ [=origin:h state=(unit vase)] +.context-option + =/ [=origin:h state=(unit vase) config=(unit config:h)] +.context-option ;< =v-channels:c bind:m (scry:s v-channels:c /gx/channels/v3/v-channels/noun) - =/ channel=(unit [=nest:c vc=v-channel:c]) + =/ channel=(unit [=nest:c v-channel:c]) ?~ origin ~ `[origin (~(gut by v-channels) origin *v-channel:c)] ;< group=(unit group-ui:g) bind:m =/ n (strand (unit group-ui:g)) ?~ channel (pure:n ~) - =* flag group.perm.perm.vc.u.channel + =* flag group.perm.perm.u.channel ;< live=? bind:n (scry:s ? /gu/groups/$) ?. live (pure:n `*group-ui:g) ;< exists=? bind:n (scry:s ? /gx/groups/exists/(scot %p p.flag)/[q.flag]/noun) ?. exists (pure:n `*group-ui:g) ;< =group-ui:g bind:n - (scry:s group-ui:g /groups/groups/(scot %p p.flag)/[q.flag]/v1/noun) + (scry:s group-ui:g /gx/groups/groups/(scot %p p.flag)/[q.flag]/v1/noun) (pure:n (some group-ui)) + =/ cfg=config:h + ?~ config ~ + u.config ;< =bowl:spider bind:m get-bowl:s =/ hook *hook:h %- pure:m @@ -60,6 +61,7 @@ group v-channels hook(state ?~(state !>(~) u.state)) + ?~(origin ~ (~(gut by config.hook) origin ~)) [now our src eny]:bowl == -- diff --git a/desk/ted/hook/schedule.hoon b/desk/ted/hook/schedule.hoon index 30eafe4df9..be3a15225a 100644 --- a/desk/ted/hook/schedule.hoon +++ b/desk/ted/hook/schedule.hoon @@ -8,7 +8,7 @@ |^ =+ !<([~ =id:h =origin:h =action] arg) ;< our=@p bind:m get-our:s -;< ~ bind:m (watch:s /responses [our %channels-server] /hooks/v0) +;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage :- %hook-action-0 !> @@ -21,14 +21,16 @@ =+ !<(=response:h q.cage) ?> ?=(?(%wait %rest) -.response) ?: ?=(%rest -.response) - ~& "stopped scheduled hook {} running on {}" + %- (slog (crip "stopped scheduled hook {} running on {}") ~) (pure:m !>(~)) ;< now=time bind:m get-time:s -=/ fires-at (add now schedule.response) -~& "starting hook {}, scheduled to run on {} at {}" +=/ fires-at + ?@ schedule.response (add now schedule.response) + next.schedule.response +%- (slog (crip "starting hook {}, scheduled to run on {} at {}") ~) (pure:m !>(~)) +$ action $% [%stop ~] - [%start schedule=@dr =config:h] + [%start schedule=$@(@dr schedule:h) =config:h] == -- \ No newline at end of file From a099d1c98b921466bc0a24b21bb4399880565512 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 26 Nov 2024 15:38:28 -0600 Subject: [PATCH 019/149] desktop: reduce padding on Group/ChannelListItem --- packages/ui/src/components/ListItem/ChannelListItem.tsx | 5 ++++- packages/ui/src/components/ListItem/GroupListItem.tsx | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 4d6aef6943..01b3f8362c 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -4,6 +4,7 @@ import { useMemo } from 'react'; import { View } from 'tamagui'; import { isWeb } from 'tamagui'; +import useIsWindowNarrow from '../../hooks/useIsWindowNarrow'; import * as utils from '../../utils'; import { capitalize } from '../../utils'; import { Badge } from '../Badge'; @@ -59,6 +60,8 @@ export function ChannelListItem({ } }, [model, firstMemberId, memberCount]); + const isWindowNarrow = useIsWindowNarrow(); + return ( - + - + {title} From f3b4410b2c8e86af7a27bc4631d6a58a6aa44741 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 26 Nov 2024 15:41:20 -0600 Subject: [PATCH 020/149] add pressable to GalleryPost, fixes tlon-3279 --- .../components/GalleryPost/GalleryPost.tsx | 87 +++++++++---------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/packages/ui/src/components/GalleryPost/GalleryPost.tsx b/packages/ui/src/components/GalleryPost/GalleryPost.tsx index 5768046552..e6e952c639 100644 --- a/packages/ui/src/components/GalleryPost/GalleryPost.tsx +++ b/packages/ui/src/components/GalleryPost/GalleryPost.tsx @@ -17,6 +17,7 @@ import { PostContent, usePostContent, } from '../PostContent/contentUtils'; +import Pressable from '../Pressable'; import { SendPostRetrySheet } from '../SendPostRetrySheet'; import { Text } from '../TextV2'; @@ -76,50 +77,48 @@ export function GalleryPost({ } return ( - - - {showAuthor && !post.hidden && !post.isDeleted && ( - - - - {deliveryFailed && ( - - Tap to retry - - )} - - - )} - - + + + + {showAuthor && !post.hidden && !post.isDeleted && ( + + + + {deliveryFailed && ( + + Tap to retry + + )} + + + )} + + + ); } From 5b4498f0b3c3f5cb95c5db292dbc943392f43217 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 26 Nov 2024 15:47:28 -0600 Subject: [PATCH 021/149] fixed padding, flipped m/l --- packages/ui/src/components/ListItem/ChannelListItem.tsx | 2 +- packages/ui/src/components/ListItem/GroupListItem.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 01b3f8362c..eda49e628c 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -69,7 +69,7 @@ export function ChannelListItem({ onPress={handlePress} onLongPress={handleLongPress} > - + From 0f5f622116d3d1b70b2b5a0126562e5a6a21214e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Paraniak?= Date: Wed, 27 Nov 2024 11:12:52 +0800 Subject: [PATCH 022/149] channels: fix +ca-recheck crash due to an invalid scry path --- desk/app/channels.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 57e365c395..d2627c408e 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -2383,7 +2383,7 @@ |= sects=(set sect:g) =/ =flag:g group.perm.perm.channel =/ exists-path - (scry-path %groups /exists/(scot %p p.flag)/[q.flag]) + (scry-path %groups /exists/(scot %p p.flag)/[q.flag]/noun) =+ .^(exists=? %gx exists-path) ?. exists ca-core =/ =path From 69879c2aad9259a5bf1818b7c647ad1e526eddb2 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 27 Nov 2024 06:13:35 -0600 Subject: [PATCH 023/149] Revert ListItem padding changes (it was worth a shot :)) --- packages/ui/src/components/ListItem/ChannelListItem.tsx | 4 +--- packages/ui/src/components/ListItem/GroupListItem.tsx | 9 +-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index eda49e628c..04dc2d66de 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -60,8 +60,6 @@ export function ChannelListItem({ } }, [model, firstMemberId, memberCount]); - const isWindowNarrow = useIsWindowNarrow(); - return ( - + - + {title} From 3b379f2c04ffe1d0ae7304f37f54d9ac6aa48129 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 27 Nov 2024 07:01:32 -0600 Subject: [PATCH 024/149] desktop: fix routing/crash issues in settings, add contacts, add themes --- .../app/features/settings/AppInfoScreen.tsx | 17 +++++++++++------ packages/app/features/settings/ThemeScreen.tsx | 4 ++-- .../features/settings/UserBugReportScreen.tsx | 2 +- packages/app/features/top/ContactsScreen.tsx | 2 +- packages/app/hooks/useTelemetry.ts | 2 +- .../app/navigation/desktop/HomeNavigator.tsx | 8 +++++--- .../desktop/ProfileScreenNavigator.tsx | 8 ++++++-- .../app/navigation/desktop/TopLevelDrawer.tsx | 3 +++ 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/packages/app/features/settings/AppInfoScreen.tsx b/packages/app/features/settings/AppInfoScreen.tsx index 584c120e2c..39c9dace94 100644 --- a/packages/app/features/settings/AppInfoScreen.tsx +++ b/packages/app/features/settings/AppInfoScreen.tsx @@ -1,6 +1,5 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { useDebugStore } from '@tloncorp/shared'; -import { getCurrentUserId } from '@tloncorp/shared/api'; import * as store from '@tloncorp/shared/store'; import { AppSetting, @@ -23,6 +22,7 @@ import { getEmailClients, openComposer } from 'react-native-email-link'; import { ScrollView } from 'react-native-gesture-handler'; import { NOTIFY_PROVIDER, NOTIFY_SERVICE } from '../../constants'; +import { useCurrentUserId } from '../../hooks/useCurrentUser'; import { useTelemetry } from '../../hooks/useTelemetry'; import { setDebug } from '../../lib/debug'; import { getEasUpdateDisplay } from '../../lib/platformHelpers'; @@ -32,13 +32,17 @@ const BUILD_VERSION = `${Platform.OS === 'ios' ? 'iOS' : 'Android'} ${Applicatio type Props = NativeStackScreenProps; -function makeDebugEmail(appInfo: any, platformInfo: any) { +function makeDebugEmail( + appInfo: any, + platformInfo: any, + currentUserId: string +) { return ` ---------------------------------------------- Insert description of problem here. ---------------------------------------------- -Tlon ID: ${getCurrentUserId()} +Tlon ID: ${currentUserId} Platform Information: ${JSON.stringify(platformInfo)} @@ -54,6 +58,7 @@ export function AppInfoScreen(props: Props) { const easUpdateDisplay = useMemo(() => getEasUpdateDisplay(Updates), []); const [hasClients, setHasClients] = useState(true); const telemetry = useTelemetry(); + const currentUserId = useCurrentUserId(); const [telemetryDisabled, setTelemetryDisabled] = useState( telemetry.optedOut ); @@ -105,10 +110,10 @@ export function AppInfoScreen(props: Props) { openComposer({ to: 'support@tlon.io', - subject: `${getCurrentUserId()} uploaded logs ${id}`, - body: makeDebugEmail(appInfo, platformInfo), + subject: `${currentUserId} uploaded logs ${id}`, + body: makeDebugEmail(appInfo, platformInfo, currentUserId), }); - }, [hasClients]); + }, [hasClients, currentUserId]); return ( diff --git a/packages/app/features/settings/ThemeScreen.tsx b/packages/app/features/settings/ThemeScreen.tsx index 363681a29b..824948c006 100644 --- a/packages/app/features/settings/ThemeScreen.tsx +++ b/packages/app/features/settings/ThemeScreen.tsx @@ -71,7 +71,7 @@ export function ThemeScreen(props: Props) { }, []); return ( - <> + props.navigation.goBack()} @@ -106,6 +106,6 @@ export function ThemeScreen(props: Props) { ))} - + ); } diff --git a/packages/app/features/settings/UserBugReportScreen.tsx b/packages/app/features/settings/UserBugReportScreen.tsx index f303819521..c6cb3ced34 100644 --- a/packages/app/features/settings/UserBugReportScreen.tsx +++ b/packages/app/features/settings/UserBugReportScreen.tsx @@ -43,7 +43,7 @@ export function UserBugReportScreen({ navigation }: Props) { ); return ( - + navigation.goBack()} diff --git a/packages/app/features/top/ContactsScreen.tsx b/packages/app/features/top/ContactsScreen.tsx index b9b570b101..9368e8b51c 100644 --- a/packages/app/features/top/ContactsScreen.tsx +++ b/packages/app/features/top/ContactsScreen.tsx @@ -63,7 +63,7 @@ export default function ContactsScreen(props: Props) { return ( - + { headerShown: false, drawerStyle: { width: 340, + backgroundColor: getVariableValue(useTheme().background), }, }} > @@ -68,9 +70,9 @@ function MainStack() { screenOptions={{ headerShown: false, }} - initialRouteName="ChatList" + initialRouteName="Home" > - + ; } diff --git a/packages/app/navigation/desktop/ProfileScreenNavigator.tsx b/packages/app/navigation/desktop/ProfileScreenNavigator.tsx index 99476e3a8b..0c813d1d55 100644 --- a/packages/app/navigation/desktop/ProfileScreenNavigator.tsx +++ b/packages/app/navigation/desktop/ProfileScreenNavigator.tsx @@ -7,7 +7,9 @@ import { FeatureFlagScreen } from '../../features/settings/FeatureFlagScreen'; import { ManageAccountScreen } from '../../features/settings/ManageAccountScreen'; import ProfileScreen from '../../features/settings/ProfileScreen'; import { PushNotificationSettingsScreen } from '../../features/settings/PushNotificationSettingsScreen'; +import { ThemeScreen } from '../../features/settings/ThemeScreen'; import { UserBugReportScreen } from '../../features/settings/UserBugReportScreen'; +import ContactsScreen from '../../features/top/ContactsScreen'; import { UserProfileScreen } from '../../features/top/UserProfileScreen'; const ProfileScreenStack = createNativeStackNavigator(); @@ -15,13 +17,14 @@ const ProfileScreenStack = createNativeStackNavigator(); export const ProfileScreenNavigator = () => { return ( + @@ -53,6 +56,7 @@ export const ProfileScreenNavigator = () => { name="EditProfile" component={EditProfileScreen} /> + ); }; diff --git a/packages/app/navigation/desktop/TopLevelDrawer.tsx b/packages/app/navigation/desktop/TopLevelDrawer.tsx index 6ff8c53641..2e8e3b414a 100644 --- a/packages/app/navigation/desktop/TopLevelDrawer.tsx +++ b/packages/app/navigation/desktop/TopLevelDrawer.tsx @@ -4,6 +4,7 @@ import { } from '@react-navigation/drawer'; import * as store from '@tloncorp/shared/store'; import { AvatarNavIcon, NavIcon, YStack, useWebAppUpdate } from '@tloncorp/ui'; +import { getVariableValue, useTheme } from 'tamagui'; import { ActivityScreen } from '../../features/top/ActivityScreen'; import { useCurrentUserId } from '../../hooks/useCurrentUser'; @@ -72,6 +73,8 @@ export const TopLevelDrawer = () => { headerShown: false, drawerStyle: { width: 48, + backgroundColor: getVariableValue(useTheme().background), + borderRightColor: getVariableValue(useTheme().border), }, }} > From 45e9266de9d1fc826bd205b3af9cb434921abc76 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 15:00:52 +0000 Subject: [PATCH 025/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index e92b9d7141..2222b2ddde 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.e7n2f.8e8kv.4a2da.o8jm3.2f5q6.glob' 0v6.e7n2f.8e8kv.4a2da.o8jm3.2f5q6] + glob-http+['https://bootstrap.urbit.org/glob-0v6.i9laj.n1cm9.1vmbd.j8m35.34am1.glob' 0v6.i9laj.n1cm9.1vmbd.j8m35.34am1] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 5087a29e79d12a616cb39595ea21086e9d5e7622 Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Wed, 27 Nov 2024 10:07:14 -0500 Subject: [PATCH 026/149] refactor chat query (#4225) * refactor chat query * cleanup --- packages/app/features/top/ChatListScreen.tsx | 76 +++--- packages/app/fixtures/GroupList.fixture.tsx | 94 +++++--- packages/app/fixtures/fakeData.ts | 9 - packages/app/hooks/useBootSequence.ts | 2 +- packages/app/hooks/useGroupContext.ts | 2 +- packages/shared/src/api/activityApi.ts | 14 +- packages/shared/src/db/queries.test.ts | 10 +- packages/shared/src/db/queries.ts | 226 +++++++----------- packages/shared/src/db/types.ts | 23 +- packages/shared/src/logic/utils.ts | 19 -- packages/shared/src/store/activityActions.ts | 36 ++- packages/shared/src/store/channelActions.ts | 27 ++- packages/shared/src/store/dbHooks.ts | 45 +--- packages/shared/src/store/sync.test.ts | 6 +- packages/ui/src/components/ChatList.tsx | 114 +++++---- .../ui/src/components/ChatOptionsSheet.tsx | 2 +- .../components/ListItem/ChannelListItem.tsx | 9 +- .../src/components/ListItem/ChatListItem.tsx | 78 +----- .../src/components/ListItem/GroupListItem.tsx | 4 +- .../ListItem/InteractableChatListItem.tsx | 22 +- packages/ui/src/contexts/chatOptions.tsx | 2 +- 21 files changed, 356 insertions(+), 464 deletions(-) diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx index 949c6f4af9..de48a4cca7 100644 --- a/packages/app/features/top/ChatListScreen.tsx +++ b/packages/app/features/top/ChatListScreen.tsx @@ -54,23 +54,17 @@ export function ChatListScreenView({ const [screenTitle, setScreenTitle] = useState('Home'); const [inviteSheetGroup, setInviteSheetGroup] = useState(); const chatOptionsSheetRef = useRef(null); - const [longPressedChat, setLongPressedChat] = useState< - db.Channel | db.Group | null - >(null); + const [longPressedChat, setLongPressedChat] = useState(null); const chatOptionsGroupId = useMemo(() => { - if (!longPressedChat) { - return; - } - return logic.isGroup(longPressedChat) - ? longPressedChat.id - : longPressedChat.group?.id; + return longPressedChat?.type === 'group' + ? longPressedChat.group.id + : undefined; }, [longPressedChat]); const chatOptionsChannelId = useMemo(() => { - if (!longPressedChat || logic.isGroup(longPressedChat)) { - return; - } - return longPressedChat.id; + return longPressedChat?.type === 'channel' + ? longPressedChat.channel.id + : undefined; }, [longPressedChat]); const [activeTab, setActiveTab] = useState<'all' | 'groups' | 'messages'>( @@ -87,9 +81,7 @@ export function ChatListScreenView({ enabled: isFocused, }); const pinned = useMemo(() => pins ?? [], [pins]); - const { data: pendingChats } = store.usePendingChats({ - enabled: isFocused, - }); + const { data: chats } = store.useCurrentChats({ enabled: isFocused, }); @@ -149,9 +141,9 @@ export function ChatListScreenView({ return { pinned: chats?.pinned ?? [], unpinned: chats?.unpinned ?? [], - pendingChats: pendingChats ?? [], + pending: chats?.pending ?? [], }; - }, [chats, pendingChats]); + }, [chats]); const handleNavigateToFindGroups = useCallback(() => { setAddGroupOpen(false); @@ -183,41 +175,39 @@ export function ChatListScreenView({ const [isChannelSwitcherEnabled] = useFeatureFlag('channelSwitcher'); const onPressChat = useCallback( - (item: db.Channel | db.Group) => { - if (logic.isGroup(item)) { + (item: db.Chat) => { + if (item.type === 'group' && item.isPending) { setSelectedGroupId(item.id); - } else if ( - item.group && - !isChannelSwitcherEnabled && - // Should navigate to channel if it's pinned as a channel - (!item.pin || item.pin.type === 'group') - ) { + } else if (item.type === 'group' && !isChannelSwitcherEnabled) { navigation.navigate('GroupChannels', { groupId: item.group.id }); + } else if (item.type === 'group') { + if (!item.group.channels?.length) { + throw new Error('cant open group with no channels'); + } + navigation.navigate('Channel', { + channelId: item.group.channels[0].id, + groupId: item.group.id, + }); } else { const screenName = screenNameFromChannelId(item.id); - navigation.navigate(screenName, { channelId: item.id, - selectedPostId: item.firstUnreadPostId, }); } }, [isChannelSwitcherEnabled, navigation] ); - const onLongPressChat = useCallback((item: db.Channel | db.Group) => { - if (logic.isChannel(item) && !item.isDmInvite) { - setLongPressedChat(item); - if (item.pin?.type === 'channel' || !item.group) { - chatOptionsSheetRef.current?.open(item.id, item.type); - } else { - chatOptionsSheetRef.current?.open( - item.group.id, - 'group', - item.group.unread?.count ?? undefined - ); - } + const onLongPressChat = useCallback((item: db.Chat) => { + if (item.isPending) { + return; } + setLongPressedChat(item); + chatOptionsSheetRef.current?.open( + item.id, + item.type === 'channel' ? item.channel.type : 'group', + item.unreadCount + ); }, []); const handleGroupPreviewSheetOpenChange = useCallback((open: boolean) => { @@ -234,7 +224,9 @@ export function ChatListScreenView({ const isTlonEmployee = useMemo(() => { const allChats = [...resolvedChats.pinned, ...resolvedChats.unpinned]; - return !!allChats.find((obj) => obj.groupId === TLON_EMPLOYEE_GROUP); + return !!allChats.find( + (chat) => chat.type === 'group' && chat.group.id === TLON_EMPLOYEE_GROUP + ); }, [resolvedChats]); useEffect(() => { @@ -336,7 +328,7 @@ export function ChatListScreenView({ setActiveTab={setActiveTab} pinned={resolvedChats.pinned} unpinned={resolvedChats.unpinned} - pendingChats={resolvedChats.pendingChats} + pending={resolvedChats.pending} onLongPressItem={onLongPressChat} onPressItem={onPressChat} onSectionChange={handleSectionChange} diff --git a/packages/app/fixtures/GroupList.fixture.tsx b/packages/app/fixtures/GroupList.fixture.tsx index 215eef729f..dfaa964810 100644 --- a/packages/app/fixtures/GroupList.fixture.tsx +++ b/packages/app/fixtures/GroupList.fixture.tsx @@ -13,7 +13,7 @@ import { let id = 0; -function makeChannelSummary({ +function makeChat({ group, channel, lastPost, @@ -23,37 +23,56 @@ function makeChannelSummary({ group?: db.Group; lastPost?: db.Post; members?: (db.ChatMember & { contact: db.Contact | null })[]; -}): db.Channel { - return { - id: 'channel-' + id++, - type: 'chat', - title: '', - description: '', - groupId: group?.id ?? null, - iconImage: null, - iconImageColor: null, - coverImage: null, - coverImageColor: null, - addedToGroupAt: null, - currentUserIsMember: null, - postCount: null, - unreadCount: group?.unreadCount ?? null, - firstUnreadPostId: null, - lastPostId: group?.lastPostId ?? null, - lastPostAt: group?.lastPostAt ?? null, - syncedAt: null, - remoteUpdatedAt: null, - ...channel, +}): db.Chat { + if (group) { + const groupId = 'group-' + id++; + return { + type: 'group', + id: groupId, + group: { ...group, id: groupId }, + isPending: false, + pin: group.pin ?? null, + timestamp: group.unread?.updatedAt ?? group.lastPostAt ?? 0, + volumeSettings: group.volumeSettings ?? null, + unreadCount: 0, + }; + } - group: group ?? null, - unread: null, - lastPost: lastPost ?? group?.lastPost ?? null, + const channelId = 'channel-' + id++; + return { + type: 'channel', + id: channelId, + timestamp: lastPost?.sentAt ?? 0, + isPending: !!channel?.isDmInvite, + volumeSettings: channel?.volumeSettings ?? null, pin: null, - members: members ?? null, + unreadCount: 0, + channel: { + id: channelId, + type: 'chat', + title: '', + description: '', + iconImage: null, + iconImageColor: null, + coverImage: null, + coverImageColor: null, + addedToGroupAt: null, + currentUserIsMember: null, + postCount: null, + firstUnreadPostId: null, + syncedAt: null, + remoteUpdatedAt: null, + ...channel, + group: group ?? null, + unread: null, + lastPost: lastPost ?? null, + pin: null, + members: members ?? null, + }, }; } -const dmSummary = makeChannelSummary({ +const dmSummary = makeChat({ channel: { type: 'dm' }, lastPost: createFakePost(), members: [ @@ -67,7 +86,7 @@ const dmSummary = makeChannelSummary({ ], }); -const groupDmSummary = makeChannelSummary({ +const groupDmSummary = makeChat({ channel: { type: 'groupDm' }, lastPost: createFakePost(), group: groupWithLongTitle, @@ -98,7 +117,7 @@ const groupDmSummary = makeChannelSummary({ export default { basic: ( - + {}} @@ -106,21 +125,20 @@ export default { onSearchQueryChange={() => {}} showSearchInput={false} pinned={[groupWithLongTitle, groupWithImage].map((g) => - makeChannelSummary({ group: g }) + makeChat({ group: g }) )} unpinned={[ groupWithColorAndNoImage, groupWithImage, - groupWithSvgImage, groupWithNoColorOrImage, - ].map((g) => makeChannelSummary({ group: g }))} - pendingChats={[]} + ].map((g) => makeChat({ group: g }))} + pending={[]} onSearchToggle={() => {}} /> ), emptyPinned: ( - + {}} @@ -131,8 +149,8 @@ export default { groupWithImage, groupWithSvgImage, groupWithNoColorOrImage, - ].map((g) => makeChannelSummary({ group: g }))} - pendingChats={[]} + ].map((g) => makeChat({ group: g }))} + pending={[]} searchQuery="" onSearchQueryChange={() => {}} onSearchToggle={() => {}} @@ -140,14 +158,14 @@ export default { ), loading: ( - + {}} showSearchInput={false} pinned={[]} unpinned={[]} - pendingChats={[]} + pending={[]} searchQuery="" onSearchQueryChange={() => {}} onSearchToggle={() => {}} diff --git a/packages/app/fixtures/fakeData.ts b/packages/app/fixtures/fakeData.ts index 5ff3e8224b..aaaf7b413a 100644 --- a/packages/app/fixtures/fakeData.ts +++ b/packages/app/fixtures/fakeData.ts @@ -804,7 +804,6 @@ export const groupWithColorAndNoImage: db.Group = { currentUserIsHost: false, title: 'Test Group', privacy: 'private', - unreadCount: 1, iconImage: null, iconImageColor: '#FF00FF', coverImage: null, @@ -813,7 +812,6 @@ export const groupWithColorAndNoImage: db.Group = { currentUserIsMember: true, lastPostId: 'test-post', lastPostAt: dates.now, - lastChannel: tlonLocalSupport.title, lastPost: { ...createFakePost() }, }; @@ -822,7 +820,6 @@ export const groupWithLongTitle: db.Group = { id: '~nibset-napwyn/tlon/long-title', title: 'And here, a reallly long title, wazzup, ok', lastPostAt: dates.earlierToday, - lastChannel: tlonLocalSupport.title, lastPost: { ...createFakePost(), textContent: @@ -836,8 +833,6 @@ export const groupWithNoColorOrImage: db.Group = { iconImageColor: null, lastPost: createFakePost(), lastPostAt: dates.yesterday, - lastChannel: tlonLocalSupport.title, - unreadCount: Math.floor(random() * 20), }; export const groupWithImage: db.Group = { @@ -847,8 +842,6 @@ export const groupWithImage: db.Group = { 'https://dans-gifts.s3.amazonaws.com/dans-gifts/solfer-magfed/2024.4.6..15.49.54..4a7e.f9db.22d0.e560-IMG_4770.jpg', lastPost: createFakePost(), lastPostAt: dates.lastWeek, - lastChannel: tlonLocalSupport.title, - unreadCount: Math.floor(random() * 20), }; export const groupWithSvgImage: db.Group = { @@ -857,8 +850,6 @@ export const groupWithSvgImage: db.Group = { iconImage: 'https://tlon.io/local-icon.svg', lastPost: createFakePost(), lastPostAt: dates.lastMonth, - lastChannel: tlonLocalSupport.title, - unreadCount: Math.floor(random() * 20), }; function randInt(min: number, max: number) { diff --git a/packages/app/hooks/useBootSequence.ts b/packages/app/hooks/useBootSequence.ts index 64d00576d8..747901dac4 100644 --- a/packages/app/hooks/useBootSequence.ts +++ b/packages/app/hooks/useBootSequence.ts @@ -221,7 +221,7 @@ export function useBootSequence({ if (dmIsGood && groupIsGood) { logger.crumb('successfully accepted invites'); if (updatedTlonTeamDm) { - store.pinItem(updatedTlonTeamDm); + store.pinChannel(updatedTlonTeamDm); } return NodeBootPhase.READY; } diff --git a/packages/app/hooks/useGroupContext.ts b/packages/app/hooks/useGroupContext.ts index 0ffae7b75b..8ea6dc9cb4 100644 --- a/packages/app/hooks/useGroupContext.ts +++ b/packages/app/hooks/useGroupContext.ts @@ -231,7 +231,7 @@ export const useGroupContext = ({ const togglePinned = useCallback(async () => { if (group && group.channels[0]) { - group.pin ? store.unpinItem(group.pin) : store.pinItem(group.channels[0]); + group.pin ? store.unpinItem(group.pin) : store.pinGroup(group); } }, [group]); diff --git a/packages/shared/src/api/activityApi.ts b/packages/shared/src/api/activityApi.ts index 80c0dbe44f..4a87b0597f 100644 --- a/packages/shared/src/api/activityApi.ts +++ b/packages/shared/src/api/activityApi.ts @@ -847,17 +847,19 @@ export function getThreadSource({ return { source, sourceId }; } -export function getRootSourceFromChannel(channel: db.Channel): { +export function getRootSourceFromChat(chat: db.Chat): { source: ub.Source; sourceId: string; } { let source: ub.Source; - if (channel.type === 'dm') { - source = { dm: { ship: channel.id } }; - } else if (channel.type === 'groupDm') { - source = { dm: { club: channel.id } }; + if (chat.type === 'group') { + source = { group: chat.id }; + } else if (chat.channel.type === 'dm') { + source = { dm: { ship: chat.channel.id } }; + } else if (chat.channel.type === 'groupDm') { + source = { dm: { club: chat.channel.id } }; } else { - source = { group: channel.groupId! }; + throw new Error('Cannot get source for non-dm channel chat'); } const sourceId = ub.sourceToString(source); diff --git a/packages/shared/src/db/queries.test.ts b/packages/shared/src/db/queries.test.ts index a45fda48ac..a58e31ea71 100644 --- a/packages/shared/src/db/queries.test.ts +++ b/packages/shared/src/db/queries.test.ts @@ -42,11 +42,15 @@ test('uses init data to get chat list', async () => { await syncInitData(); const result = await queries.getChats(); - expect(result.map((r) => r.id).slice(0, 8)).toEqual([ + + expect(result.pinned.map((r) => r.id)).toEqual([ '0v4.00000.qd6oi.a3f6t.5sd9v.fjmp2', - 'chat/~nibset-napwyn/commons', + ]); + + expect(result.unpinned.map((r) => r.id).slice(0, 7)).toEqual([ + '~nibset-napwyn/tlon', '~nocsyx-lassul', - 'chat/~pondus-watbel/new-channel', + '~pondus-watbel/testing-facility', '~ravseg-nosduc', '~solfer-magfed', '~hansel-ribbur', diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index 25b4974385..b7732993a1 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -79,7 +79,7 @@ import { ActivityEvent, Channel, ChannelUnread, - ChatMember, + Chat, ClientMeta, Contact, Group, @@ -196,33 +196,6 @@ export const getGroups = createReadQuery( ] ); -export const getPendingChats = createReadQuery( - 'getPendingChats', - async (ctx: QueryCtx) => { - const pendingGroups = await ctx.db.query.groups.findMany({ - where: or( - eq($groups.haveInvite, true), - isNotNull($groups.joinStatus), - eq($groups.haveRequestedInvite, true) - ), - }); - - const pendingChannels = await ctx.db.query.channels.findMany({ - where: eq($channels.isDmInvite, true), - with: { - members: { - with: { - contact: true, - }, - }, - }, - }); - - return [...pendingChannels, ...pendingGroups]; - }, - ['groups', 'channels'] -); - export const getUnjoinedGroupChannels = createReadQuery( 'getUnjoinedGroupChannels', async (groupId: string, ctx: QueryCtx) => { @@ -315,119 +288,106 @@ export const getAllChannels = createReadQuery( export const getChats = createReadQuery( 'getChats', - async (ctx: QueryCtx): Promise => { - const partitionedGroupsQuery = ctx.db - .select({ - ...getTableColumns($channels), - rowNumber: - sql`ROW_NUMBER() OVER(PARTITION BY ${$channels.groupId} ORDER BY COALESCE(${$channels.lastPostAt}, ${$channelUnreads.updatedAt}) DESC)`.as( - 'row_number' - ), - }) - .from($channels) - .where( - and(isNotNull($channels.groupId), eq($groups.currentUserIsMember, true)) - ) - .leftJoin($channelUnreads, eq($channelUnreads.channelId, $channels.id)) - .leftJoin($groups, eq($groups.id, $channels.groupId)) - .as('q'); - - const groupChannels = ctx.db - .select() - .from(partitionedGroupsQuery) - .where(eq(partitionedGroupsQuery.rowNumber, 1)); + async ( + ctx: QueryCtx + ): Promise<{ pinned: Chat[]; pending: Chat[]; unpinned: Chat[] }> => { + const groups = await ctx.db.query.groups.findMany({ + where: or( + eq($groups.currentUserIsMember, true), + eq($groups.isNew, true), + isNotNull($groups.joinStatus) + ), + with: { + volumeSettings: true, + unread: true, + channels: { + orderBy: [desc($channels.lastPostAt)], + with: { + lastPost: true, + }, + }, + // Just need the first 3 members for possible title generation purposes + members: { + limit: 3, + orderBy: [asc($chatMembers.joinedAt)], + with: { + contact: true, + }, + }, + pin: true, + lastPost: true, + }, + }); - const allChannels = ctx.db - .select({ - ...getTableColumns($channels), - rowNumber: sql`0`.as('row_number'), - }) - .from($channels) - .where(and(isNull($channels.groupId), eq($channels.isDmInvite, false))) - .union(groupChannels) - .as('ac'); + const channels = await ctx.db.query.channels.findMany({ + where: isNull($channels.groupId), + with: { + volumeSettings: true, + unread: true, + members: { + with: { + contact: true, + }, + }, + pin: true, + lastPost: true, + }, + }); - const $groupVolumeSettings = ctx.db - .select() - .from($volumeSettings) - .where(eq($volumeSettings.itemType, 'group')) - .as('gvs'); + const groupChats: Chat[] = groups.map((g) => ({ + id: g.id, + type: 'group', + pin: g.pin, + timestamp: g.unread?.updatedAt ?? g.lastPostAt ?? 0, + volumeSettings: g.volumeSettings, + unreadCount: g.unread?.count ?? 0, + group: g, + isPending: + g.haveInvite === true || + !!g.joinStatus || + g.haveRequestedInvite || + false, + })); - const result = await ctx.db - .select({ - ...allQueryColumns(allChannels), - group: getTableColumns($groups), - groupUnread: getTableColumns($groupUnreads), - groupVolumeSettings: allQueryColumns($groupVolumeSettings), - volumeSettings: getTableColumns($volumeSettings), - unread: getTableColumns($channelUnreads), - pin: getTableColumns($pins), - lastPost: getTableColumns($posts), - member: { - ...getTableColumns($chatMembers), - }, - contact: getTableColumns($contacts), - }) - .from(allChannels) - .leftJoin( - $groupVolumeSettings, - eq($groupVolumeSettings.itemId, allChannels.groupId) - ) - .leftJoin($volumeSettings, eq($volumeSettings.itemId, allChannels.id)) - .leftJoin($groups, eq($groups.id, allChannels.groupId)) - .leftJoin($groupUnreads, eq($groupUnreads.groupId, allChannels.groupId)) - .leftJoin($channelUnreads, eq($channelUnreads.channelId, allChannels.id)) - .leftJoin( - $pins, - or( - eq(allChannels.groupId, $pins.itemId), - eq(allChannels.id, $pins.itemId) - ) - ) - .leftJoin($posts, eq($posts.id, allChannels.lastPostId)) - .leftJoin($chatMembers, eq($chatMembers.chatId, allChannels.id)) - .leftJoin($contacts, eq($contacts.id, $chatMembers.contactId)) - .orderBy( - ascNullsLast($pins.index), - sql`(CASE WHEN ${$groups.isNew} = 1 THEN 1 ELSE 0 END) DESC`, - sql`COALESCE(${$channelUnreads.updatedAt}, ${allChannels.lastPostAt}) DESC` - ); + const channelChats: Chat[] = channels.map((c) => ({ + id: c.id, + type: 'channel', + channel: c, + pin: c.pin, + volumeSettings: c.volumeSettings, + unreadCount: c.unread?.count ?? 0, + timestamp: c.unread?.updatedAt ?? c.lastPostAt ?? 0, + isPending: !!c.isDmInvite, + })); - const [chatMembers, filteredChannels] = result.reduce< - [ - Record, - typeof result, - ] - >( - ([members, filteredChannels], channel) => { - if (!channel.member || !members[channel.id]) { - filteredChannels.push(channel); - } - if (channel.member) { - members[channel.id] ||= []; - members[channel.id].push({ - ...channel.member, - contact: channel.contact ?? null, - }); + const { pinnedChats, pendingChats, otherChats } = [ + ...channelChats, + ...groupChats, + ].reduce( + (acc, chat) => { + if (chat.pin) { + acc.pinnedChats.push(chat); + } else if (chat.isPending) { + acc.pendingChats.push(chat); + } else { + acc.otherChats.push(chat); } - return [members, filteredChannels]; + return acc; }, - [{}, [] as typeof result] + { + pinnedChats: [], + pendingChats: [], + otherChats: [], + } as Record ); - return filteredChannels.map((c) => { - return { - ...c, - members: chatMembers[c.id] ?? null, - group: !c.group - ? null - : { - ...c.group, - unread: c.groupUnread, - volumeSettings: c.groupVolumeSettings, - }, - }; - }); + return { + pinned: pinnedChats.sort( + (a, b) => (a.pin?.index ?? 0) - (b.pin?.index ?? 0) + ), + pending: pendingChats, + unpinned: otherChats.sort((a, b) => b.timestamp - a.timestamp), + }; }, [ 'groups', diff --git a/packages/shared/src/db/types.ts b/packages/shared/src/db/types.ts index 5a7933f91a..21a6cb194f 100644 --- a/packages/shared/src/db/types.ts +++ b/packages/shared/src/db/types.ts @@ -44,12 +44,8 @@ export type ActivityEvent = BaseModel<'activityEvents'>; export type ActivityEventContactUpdateGroups = ActivityEvent['contactUpdateGroups']; export type ActivityBucket = schema.ActivityBucket; -// TODO: We need to include unread count here because it's returned by the chat -// list query, but doesn't feel great. -export type Group = BaseModel<'groups'> & { - unreadCount?: number | null; - lastChannel?: string | null; -}; +export type Group = BaseModel<'groups'>; + export type ClientMeta = Pick< Group, | 'title' @@ -100,6 +96,21 @@ export type Settings = BaseModel<'settings'>; export type PostWindow = BaseModel<'postWindows'>; export type VolumeSettings = BaseModel<'volumeSettings'>; +export type Chat = { + id: string; + pin: Pin | null; + volumeSettings: VolumeSettings | null; + timestamp: number; + isPending: boolean; + unreadCount: number; +} & ({ type: 'group'; group: Group } | { type: 'channel'; channel: Channel }); + +export interface GroupedChats { + pinned: Chat[]; + unpinned: Chat[]; + pending: Chat[]; +} + export interface GroupEvent extends ActivityEvent { type: 'group-ask'; group: Group; diff --git a/packages/shared/src/logic/utils.ts b/packages/shared/src/logic/utils.ts index d997b58ed7..be198ef6fe 100644 --- a/packages/shared/src/logic/utils.ts +++ b/packages/shared/src/logic/utils.ts @@ -212,25 +212,6 @@ export function normalizeUrbitColor(color: string): string { return `#${lengthAdjustedColor}`; } -export function getPinPartial(channel: db.Channel): { - type: db.PinType; - itemId: string; -} { - if (channel.groupId) { - return { type: 'group', itemId: channel.groupId }; - } - - if (channel.type === 'dm') { - return { type: 'dm', itemId: channel.id }; - } - - if (channel.type === 'groupDm') { - return { type: 'groupDm', itemId: channel.id }; - } - - return { type: 'channel', itemId: channel.id }; -} - const MS_PER_DAY = 24 * 60 * 60 * 1000; const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000; diff --git a/packages/shared/src/store/activityActions.ts b/packages/shared/src/store/activityActions.ts index 3b9fdb233f..5d427e97fb 100644 --- a/packages/shared/src/store/activityActions.ts +++ b/packages/shared/src/store/activityActions.ts @@ -7,26 +7,26 @@ import { whomIsMultiDm } from '../urbit'; const logger = createDevLogger('activityActions', false); -export async function muteChat(channel: db.Channel) { - const initialSettings = await getChatVolumeSettings(channel); - const muteLevel = channel.groupId ? 'soft' : 'hush'; +export async function muteChat(chat: db.Chat) { + const initialSettings = await getChatVolumeSettings(chat); + const muteLevel = chat.type === 'group' ? 'soft' : 'hush'; db.setVolumes({ volumes: [ { - itemId: channel.groupId ?? channel.id, - itemType: channel.groupId ? 'group' : 'channel', + itemId: chat.id, + itemType: chat.type, level: muteLevel, }, ], }); try { - const { source } = api.getRootSourceFromChannel(channel); + const { source } = api.getRootSourceFromChat(chat); const volume = ub.getVolumeMap(muteLevel, true); await api.adjustVolumeSetting(source, volume); } catch (e) { - logger.log(`failed to mute group ${channel.id}`, e); + logger.log(`failed to mute chat ${chat.id}`, e); // revert the optimistic update if (initialSettings) { await db.setVolumes({ volumes: [initialSettings] }); @@ -34,24 +34,24 @@ export async function muteChat(channel: db.Channel) { } } -export async function unmuteChat(channel: db.Channel) { - const initialSettings = await getChatVolumeSettings(channel); +export async function unmuteChat(chat: db.Chat) { + const initialSettings = await getChatVolumeSettings(chat); db.setVolumes({ volumes: [ { - itemId: channel.groupId ?? channel.id, - itemType: channel.groupId ? 'group' : 'channel', + itemId: chat.id, + itemType: chat.type, level: 'default', }, ], }); try { - const { source } = api.getRootSourceFromChannel(channel); + const { source } = api.getRootSourceFromChat(chat); await api.adjustVolumeSetting(source, null); } catch (e) { - logger.log(`failed to unmute chat ${channel.id}`, e); + logger.log(`failed to unmute chat ${chat.id}`, e); // revert the optimistic update if (initialSettings) { await db.setVolumes({ volumes: [initialSettings] }); @@ -59,14 +59,8 @@ export async function unmuteChat(channel: db.Channel) { } } -async function getChatVolumeSettings(chat: db.Channel) { - if (chat.groupId) { - return ( - chat.group?.volumeSettings ?? (await db.getVolumeSetting(chat.groupId)) - ); - } else { - return chat.volumeSettings ?? (await db.getVolumeSetting(chat.id)); - } +async function getChatVolumeSettings(chat: db.Chat) { + return chat.volumeSettings ?? (await db.getVolumeSetting(chat.id)); } export async function muteThread({ diff --git a/packages/shared/src/store/channelActions.ts b/packages/shared/src/store/channelActions.ts index 6201e6ef35..58d2cfcd08 100644 --- a/packages/shared/src/store/channelActions.ts +++ b/packages/shared/src/store/channelActions.ts @@ -149,17 +149,32 @@ export async function updateChannel({ } } -export async function pinItem(channel: db.Channel) { - // optimistic update - const partialPin = logic.getPinPartial(channel); - db.insertPinnedItem(partialPin); +export async function pinChat(chat: db.Chat) { + return chat.type === 'group' + ? pinGroup(chat.group) + : pinChannel(chat.channel); +} + +export async function pinGroup(group: db.Group) { + return savePin({ type: 'group', itemId: group.id }); +} + +export async function pinChannel(channel: db.Channel) { + const type = + channel.type === 'dm' || channel.type === 'groupDm' + ? channel.type + : 'channel'; + return savePin({ type, itemId: channel.id }); +} +async function savePin(pin: { type: db.PinType; itemId: string }) { + db.insertPinnedItem(pin); try { - await api.pinItem(partialPin.itemId); + await api.pinItem(pin.itemId); } catch (e) { console.error('Failed to pin item', e); // rollback optimistic update - db.deletePinnedItem(partialPin); + db.deletePinnedItem(pin); } } diff --git a/packages/shared/src/store/dbHooks.ts b/packages/shared/src/store/dbHooks.ts index af7f356359..e357e5f33b 100644 --- a/packages/shared/src/store/dbHooks.ts +++ b/packages/shared/src/store/dbHooks.ts @@ -7,6 +7,7 @@ import { useMemo } from 'react'; import * as api from '../api'; import * as db from '../db'; +import { GroupedChats } from '../db/types'; import * as ub from '../urbit'; import { hasCustomS3Creds, hasHostingUploadCreds } from './storage'; import { @@ -18,14 +19,6 @@ import { keyFromQueryDeps, useKeyFromQueryDeps } from './useKeyFromQueryDeps'; export * from './useChannelSearch'; -// Assorted small hooks for fetching data from the database. -// Can break em out as they get bigger. - -export interface CurrentChats { - pinned: db.Channel[]; - unpinned: db.Channel[]; -} - export type CustomQueryConfig = Pick< UseQueryOptions, 'enabled' @@ -41,43 +34,13 @@ export const useAllChannels = ({ enabled }: { enabled?: boolean }) => { }; export const useCurrentChats = ( - queryConfig?: CustomQueryConfig -): UseQueryResult => { + queryConfig?: CustomQueryConfig +): UseQueryResult => { return useQuery({ queryFn: async () => { - const channels = await db.getChats(); - return { channels }; + return db.getChats(); }, queryKey: ['currentChats', useKeyFromQueryDeps(db.getChats)], - select({ channels }) { - for (let i = 0; i < channels.length; ++i) { - if (!channels[i].pin) { - return { - pinned: channels.slice(0, i), - unpinned: channels.slice(i), - }; - } - } - return { - pinned: channels, - unpinned: [], - }; - }, - ...queryConfig, - }); -}; - -export type PendingChats = (db.Group | db.Channel)[]; - -export const usePendingChats = ( - queryConfig?: CustomQueryConfig -): UseQueryResult => { - return useQuery({ - queryFn: async () => { - const pendingChats = await db.getPendingChats(); - return pendingChats; - }, - queryKey: ['pendingChats', useKeyFromQueryDeps(db.getPendingChats)], ...queryConfig, }); }; diff --git a/packages/shared/src/store/sync.test.ts b/packages/shared/src/store/sync.test.ts index 44f3479a5d..6dd2cbe530 100644 --- a/packages/shared/src/store/sync.test.ts +++ b/packages/shared/src/store/sync.test.ts @@ -296,9 +296,11 @@ test('syncs last posts', async () => { await syncLatestPosts(); const chats = await db.getChats(); const NUM_EMPTY_TEST_GROUPS = 6; - const chatsWithLatestPosts = chats.filter((c) => c.lastPost); + const chatsWithLatestPosts = chats.unpinned.filter((c) => + c.type === 'channel' ? c.channel.lastPost : c.group.lastPost + ); expect(chatsWithLatestPosts.length).toEqual( - chats.length - NUM_EMPTY_TEST_GROUPS + chats.unpinned.length - NUM_EMPTY_TEST_GROUPS ); }); diff --git a/packages/ui/src/components/ChatList.tsx b/packages/ui/src/components/ChatList.tsx index 06c64cced0..7542228ad2 100644 --- a/packages/ui/src/components/ChatList.tsx +++ b/packages/ui/src/components/ChatList.tsx @@ -1,7 +1,5 @@ import { FlashList, ListRenderItem } from '@shopify/flash-list'; import * as db from '@tloncorp/shared/db'; -import * as logic from '@tloncorp/shared/logic'; -import * as store from '@tloncorp/shared/store'; import Fuse from 'fuse.js'; import { debounce } from 'lodash'; import React, { @@ -19,6 +17,7 @@ import Animated, { } from 'react-native-reanimated'; import { Text, View, YStack, getTokenValue } from 'tamagui'; +import { useCalm } from '../contexts'; import { interactionWithTiming } from '../utils/animation'; import { TextInputWithIconAndButton } from './Form'; import { ChatListItem, InteractableChatListItem } from './ListItem'; @@ -26,17 +25,15 @@ import Pressable from './Pressable'; import { SectionListHeader } from './SectionList'; import { Tabs } from './Tabs'; -export type Chat = db.Channel | db.Group; - export type TabName = 'all' | 'groups' | 'messages'; type SectionHeaderData = { type: 'sectionHeader'; title: string }; -type ChatListItemData = Chat | SectionHeaderData; +type ChatListItemData = db.Chat | SectionHeaderData; export const ChatList = React.memo(function ChatListComponent({ pinned, unpinned, - pendingChats, + pending, onLongPressItem, onPressItem, activeTab, @@ -45,10 +42,9 @@ export const ChatList = React.memo(function ChatListComponent({ searchQuery, onSearchQueryChange, onSearchToggle, -}: store.CurrentChats & { - pendingChats: store.PendingChats; - onPressItem?: (chat: Chat) => void; - onLongPressItem?: (chat: Chat) => void; +}: db.GroupedChats & { + onPressItem?: (chat: db.Chat) => void; + onLongPressItem?: (chat: db.Chat) => void; onSectionChange?: (title: string) => void; activeTab: TabName; setActiveTab: (tab: TabName) => void; @@ -60,7 +56,7 @@ export const ChatList = React.memo(function ChatListComponent({ const displayData = useFilteredChats({ pinned, unpinned, - pending: pendingChats, + pending, searchQuery, activeTab, }); @@ -92,7 +88,7 @@ export const ChatList = React.memo(function ChatListComponent({ {item.title} ); - } else if (logic.isChannel(item)) { + } else if (item.type === 'channel' || !item.isPending) { return ( performSearch(debouncedQuery), @@ -331,42 +314,53 @@ function useFilteredChats({ }, [activeTab, pending, searchQuery, searchResults, unpinned, pinned]); } -function filterPendingChats(pending: Chat[], activeTab: TabName) { +function filterPendingChats(pending: db.Chat[], activeTab: TabName) { if (activeTab === 'all') return pending; return pending.filter((chat) => { - const isGroupChannel = logic.isGroup(chat); - return activeTab === 'groups' ? isGroupChannel : !isGroupChannel; + const isGroup = chat.type === 'group'; + return activeTab === 'groups' ? isGroup : !isGroup; }); } -function filterChats(chats: Chat[], activeTab: TabName) { +function filterChats(chats: db.Chat[], activeTab: TabName) { if (activeTab === 'all') return chats; return chats.filter((chat) => { - const isGroupChannel = logic.isGroupChannelId(chat.id); - return activeTab === 'groups' ? isGroupChannel : !isGroupChannel; + const isGroup = chat.type === 'group'; + return activeTab === 'groups' ? isGroup : !isGroup; }); } function useChatSearch({ pinned, unpinned, + pending, }: { - pinned: Chat[]; - unpinned: Chat[]; + pinned: db.Chat[]; + unpinned: db.Chat[]; + pending: db.Chat[]; }) { + const { disableNicknames } = useCalm(); + const fuse = useMemo(() => { - const allData = [...pinned, ...unpinned]; + const allData = [...pinned, ...unpinned, ...pending]; return new Fuse(allData, { keys: [ - 'id', - 'group.title', - 'contact.nickname', - 'members.contact.nickname', - 'members.contact.id', + { + name: 'title', + getFn: (chat: db.Chat) => { + const title = getChatTitle(chat, disableNicknames); + return Array.isArray(title) + ? title.map(normalizeString) + : normalizeString(title); + }, + }, ], - threshold: 0.3, }); - }, [pinned, unpinned]); + }, [pinned, unpinned, pending, disableNicknames]); + + function normalizeString(str: string) { + return str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); + } const performSearch = useCallback( (query: string) => { @@ -381,6 +375,34 @@ function useChatSearch({ return performSearch; } +function getChatTitle( + chat: db.Chat, + disableNicknames: boolean +): string | string[] { + if (chat.type === 'channel') { + if (chat.channel.title) { + return chat.channel.title; + } else if (chat.channel.members) { + return chat.channel.members + .map((member) => { + const nickname = member.contact + ? (member.contact as db.Contact).nickname + : null; + return nickname && !disableNicknames ? nickname : member.contactId; + }) + .join(', '); + } else { + return []; + } + } else { + if (chat.group.title) { + return chat.group.title; + } else { + return []; + } + } +} + function useDebouncedValue(input: T, delay: number) { const [value, setValue] = useState(input); const debouncedSetValue = useMemo( diff --git a/packages/ui/src/components/ChatOptionsSheet.tsx b/packages/ui/src/components/ChatOptionsSheet.tsx index 8de60c3b64..caf158f489 100644 --- a/packages/ui/src/components/ChatOptionsSheet.tsx +++ b/packages/ui/src/components/ChatOptionsSheet.tsx @@ -665,7 +665,7 @@ export function ChannelOptions({ } channel.pin ? store.unpinItem(channel.pin) - : store.pinItem(channel); + : store.pinChannel(channel); }, }, ], diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 04dc2d66de..497a937a05 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -1,15 +1,12 @@ import type * as db from '@tloncorp/shared/db'; import * as logic from '@tloncorp/shared/logic'; import { useMemo } from 'react'; -import { View } from 'tamagui'; -import { isWeb } from 'tamagui'; +import { View, isWeb } from 'tamagui'; -import useIsWindowNarrow from '../../hooks/useIsWindowNarrow'; import * as utils from '../../utils'; import { capitalize } from '../../utils'; import { Badge } from '../Badge'; import { Button } from '../Button'; -import { Chat } from '../ChatList'; import { Icon } from '../Icon'; import Pressable from '../Pressable'; import { ListItem, type ListItemProps } from './ListItem'; @@ -27,7 +24,7 @@ export function ChannelListItem({ useTypeIcon?: boolean; customSubtitle?: string; dimmed?: boolean; -} & ListItemProps & { model: db.Channel }) { +} & ListItemProps) { const unreadCount = model.unread?.count ?? 0; const title = utils.useChannelTitle(model); const firstMemberId = model.members?.[0]?.contactId ?? ''; @@ -82,7 +79,7 @@ export function ChannelListItem({ {subtitle} )} - {model.lastPost && ( + {model.lastPost && !model.isDmInvite && ( ) { +}: ListItemProps) { const handlePress = logic.useMutableCallback(() => { onPress?.(model); }); @@ -21,74 +20,23 @@ export const ChatListItem = React.memo(function ChatListItemComponent({ onLongPress?.(model); }); - // if the chat list item is a group, it's pending - if (logic.isGroup(model)) { + if (model.type === 'group') { return ( + ); + } else { + return ( + ); } - - if (logic.isChannel(model)) { - if ( - model.type === 'dm' || - model.type === 'groupDm' || - model.pin?.type === 'channel' - ) { - return ( - - ); - } else if (model.group) { - return ( - - ); - } - } - - console.warn('unable to render chat list item', model.id, model); - return null; }); - -function GroupListItemAdapter({ - model, - groupModel, - onPress, - onLongPress, - ...props -}: ListItemProps & { - groupModel: db.Group; - onPress: () => void; - onLongPress: () => void; -}) { - const resolvedModel = useMemo(() => { - return { - ...groupModel, - unreadCount: model.unread?.count, - lastPost: model.lastPost, - lastChannel: model.title, - }; - }, [model, groupModel]); - return ( - - ); -} diff --git a/packages/ui/src/components/ListItem/GroupListItem.tsx b/packages/ui/src/components/ListItem/GroupListItem.tsx index 5a5a550dc8..1258e56fad 100644 --- a/packages/ui/src/components/ListItem/GroupListItem.tsx +++ b/packages/ui/src/components/ListItem/GroupListItem.tsx @@ -43,11 +43,11 @@ export const GroupListItem = ({ {customSubtitle && ( {customSubtitle} )} - {model.lastPost && !customSubtitle && ( + {model.lastPost && model.channels?.length && !customSubtitle && ( - {model.lastChannel} + {model.channels[0].title} )} {!isPending && model.lastPost ? ( diff --git a/packages/ui/src/components/ListItem/InteractableChatListItem.tsx b/packages/ui/src/components/ListItem/InteractableChatListItem.tsx index 838e768cd4..b02ab42a82 100644 --- a/packages/ui/src/components/ListItem/InteractableChatListItem.tsx +++ b/packages/ui/src/components/ListItem/InteractableChatListItem.tsx @@ -20,7 +20,6 @@ import Animated, { import { ColorTokens, Stack, View, getTokenValue, isWeb } from 'tamagui'; import * as utils from '../../utils'; -import { Chat } from '../ChatList'; import { Icon, IconType } from '../Icon'; import { ChatListItem } from './ChatListItem'; import { ListItemProps } from './ListItem'; @@ -30,19 +29,14 @@ function BaseInteractableChatRow({ model, onPress, onLongPress, -}: ListItemProps & { model: db.Channel }) { +}: ListItemProps) { const swipeableRef = useRef(null); const [currentSwipeDirection, setCurrentSwipeDirection] = useState< 'left' | 'right' | null >(null); const isMuted = useMemo(() => { - if (model.group) { - return logic.isMuted(model.group.volumeSettings?.level, 'group'); - } else if (model.type === 'dm' || model.type === 'groupDm') { - return logic.isMuted(model.volumeSettings?.level, 'channel'); - } - return false; + return logic.isMuted(model.volumeSettings?.level, model.type); }, [model]); // prevent color flicker when unmuting @@ -60,16 +54,16 @@ function BaseInteractableChatRow({ utils.triggerHaptic('swipeAction'); switch (actionId) { case 'pin': - model.pin ? store.unpinItem(model.pin) : store.pinItem(model); + model.pin ? store.unpinItem(model.pin) : store.pinChat(model); break; case 'mute': isMuted ? store.unmuteChat(model) : store.muteChat(model); break; case 'markRead': - if (model.group) { + if (model.type === 'group') { store.markGroupRead(model.group, true); } else { - store.markChannelRead(model); + store.markChannelRead(model.channel); } break; default: @@ -89,9 +83,7 @@ function BaseInteractableChatRow({ const renderLeftActions = useCallback( (progress: SharedValue, drag: SharedValue) => { - const hasUnread = model.group - ? (model.group.unread?.count ?? 0) > 0 - : (model.unread?.count ?? 0) > 0; + const hasUnread = model.unreadCount > 0; if (currentSwipeDirection === 'right' || !hasUnread) { return ; @@ -105,7 +97,7 @@ function BaseInteractableChatRow({ /> ); }, - [handleAction, model.group, model.unread?.count, currentSwipeDirection] + [model.unreadCount, currentSwipeDirection, handleAction] ); const renderRightActions = useCallback( diff --git a/packages/ui/src/contexts/chatOptions.tsx b/packages/ui/src/contexts/chatOptions.tsx index fbed826993..f69af88afb 100644 --- a/packages/ui/src/contexts/chatOptions.tsx +++ b/packages/ui/src/contexts/chatOptions.tsx @@ -74,7 +74,7 @@ export const ChatOptionsProvider = ({ const onTogglePinned = useCallback(() => { if (group && group.channels[0]) { - group.pin ? store.unpinItem(group.pin) : store.pinItem(group.channels[0]); + group.pin ? store.unpinItem(group.pin) : store.pinGroup(group); } }, [group]); From a1b9bee1a355acecbf8badd811e438ca22bdad84 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 15:13:49 +0000 Subject: [PATCH 027/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index c574fb2399..410a7903e4 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.fu63g.0sq11.err1r.f2b33.8neao.glob' 0v6.fu63g.0sq11.err1r.f2b33.8neao] + glob-http+['https://bootstrap.urbit.org/glob-0v5.sk7ei.f8cfj.0osok.hapmm.rb9ng.glob' 0v5.sk7ei.f8cfj.0osok.hapmm.rb9ng] base+'groups' version+[6 4 2] website+'https://tlon.io' From d05326340c024fe7b2bfa557e5c037a45cd51e12 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 15:14:06 +0000 Subject: [PATCH 028/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 2222b2ddde..b6082fc01f 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.i9laj.n1cm9.1vmbd.j8m35.34am1.glob' 0v6.i9laj.n1cm9.1vmbd.j8m35.34am1] + glob-http+['https://bootstrap.urbit.org/glob-0v3.o6vqk.mkt3j.bgf7f.n04r1.97gam.glob' 0v3.o6vqk.mkt3j.bgf7f.n04r1.97gam] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 34f183a24032faf48bd2e90dd68b7b87e790d204 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 15:15:29 +0000 Subject: [PATCH 029/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index b6082fc01f..9e22d51ace 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v3.o6vqk.mkt3j.bgf7f.n04r1.97gam.glob' 0v3.o6vqk.mkt3j.bgf7f.n04r1.97gam] + glob-http+['https://bootstrap.urbit.org/glob-0v6.sf1ku.j727t.hkgu4.3mt9a.rbvm5.glob' 0v6.sf1ku.j727t.hkgu4.3mt9a.rbvm5] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 8c1b7964ba67e3e623c191c89160c017086ccd14 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 27 Nov 2024 11:39:31 -0600 Subject: [PATCH 030/149] hooks: only host can modify --- desk/app/channels-server.hoon | 1 + 1 file changed, 1 insertion(+) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index edf10cdfec..ddbfe13fa5 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -346,6 +346,7 @@ :: %hook-action-0 =+ !<(=action:h vase) + ?> =(our src):bowl ?- -.action %add ho-abet:(ho-add:ho-core [name src]:action) From 60f8d57ef43922c326c59001de7d71e61c0a3e82 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 27 Nov 2024 12:03:23 -0600 Subject: [PATCH 031/149] desktop: fix issue with stale data breaking the app on first load --- apps/tlon-web-new/src/logic/useAppUpdates.ts | 2 ++ apps/tlon-web-new/src/main.tsx | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/tlon-web-new/src/logic/useAppUpdates.ts b/apps/tlon-web-new/src/logic/useAppUpdates.ts index 5487edea9c..8324e81c3d 100644 --- a/apps/tlon-web-new/src/logic/useAppUpdates.ts +++ b/apps/tlon-web-new/src/logic/useAppUpdates.ts @@ -1,3 +1,4 @@ +import { queryClient } from '@tloncorp/shared'; import { createContext, useCallback, useEffect, useState } from 'react'; import { useRegisterSW } from 'virtual:pwa-register/react'; @@ -78,6 +79,7 @@ export default function useAppUpdates() { : `${window.location.href}?updatedAt=${Date.now()}`; if (needRefresh) { + queryClient.clear(); try { await updateServiceWorker(false); } catch (e) { diff --git a/apps/tlon-web-new/src/main.tsx b/apps/tlon-web-new/src/main.tsx index 7d94a02cb2..278636433d 100644 --- a/apps/tlon-web-new/src/main.tsx +++ b/apps/tlon-web-new/src/main.tsx @@ -57,8 +57,8 @@ setupDb().then(() => { From e96b66df40f09b23f16025b77945c774820c6050 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 27 Nov 2024 12:22:34 -0600 Subject: [PATCH 032/149] hooks: updating type names pt1 --- desk/app/channels-server.hoon | 76 +++++++++++------------ desk/gen/hooks/confirm.hoon | 19 ------ desk/gen/hooks/truncate.hoon | 97 ----------------------------- desk/lib/hooks-json.hoon | 32 +++++----- desk/sur/hooks.hoon | 112 ++++++++++++++++++---------------- desk/ted/hook/schedule.hoon | 6 +- 6 files changed, 116 insertions(+), 226 deletions(-) delete mode 100644 desk/gen/hooks/confirm.hoon delete mode 100644 desk/gen/hooks/truncate.hoon diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index edf10cdfec..2e5c5dbf57 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -360,7 +360,7 @@ =/ seq %+ skim seq.action - |= =id:h + |= id=id-hook:h (~(has by hooks.hooks) id) =. order.hooks (~(put by order.hooks) nest.action seq) (give-hook-response %order nest.action seq) @@ -368,8 +368,8 @@ %config ho-abet:(ho-configure:(ho-abed:ho-core id.action) +>.action) :: - %wait - ho-abet:(ho-wait:(ho-abed:ho-core id.action) +>.action) + %cron + ho-abet:(ho-cron:(ho-abed:ho-core id.action) +>.action) :: %rest ho-abet:(ho-rest:(ho-abed:ho-core id.action) origin.action) @@ -1033,7 +1033,7 @@ ^+ cor (give %fact ~[/v0/hooks] hook-response-0+!>(response)) ++ ho-core - |_ [=id:h =hook:h gone=_|] + |_ [id=id-hook:h =hook:h gone=_|] ++ ho-core . ++ emit |=(=card ho-core(cor (^emit card))) ++ emil |=(caz=(list card) ho-core(cor (^emil caz))) @@ -1045,7 +1045,7 @@ == :: ++ ho-abed - |= i=id:h + |= i=id-hook:h ho-core(id i, hook (~(got by hooks.hooks) i)) :: ++ ho-add @@ -1095,15 +1095,15 @@ =. order.hooks %+ roll ~(tap by order.hooks) - |= [[=nest:c ids=(list id:h)] or=(map nest:c (list id:h))] + |= [[=nest:c ids=(list id-hook:h)] or=(map nest:c (list id-hook:h))] =- (~(put by or) nest -) - (skip ids |=(i=id:h =(id i))) - =. delayed.hooks + (skip ids |=(i=id-hook:h =(id i))) + =. waiting.hooks %+ roll - ~(tap by delayed.hooks) - |= [[=delay-id:h d=[* delayed-hook:h]] dh=_delayed.hooks] - ?. =(id hook.d) dh - (~(del by dh) delay-id) + ~(tap by waiting.hooks) + |= [[=id-wait:h d=[* waiting-hook:h]] wh=_waiting.hooks] + ?. =(id hook.d) wh + (~(del by wh) id-wait) =. cor (give-hook-response [%gone id]) ho-core ++ ho-configure @@ -1112,7 +1112,7 @@ =. config.hook (~(put by config.hook) nest config) =. cor (give-hook-response [%config id nest config]) ho-core - ++ ho-wait + ++ ho-cron |= [=origin:h schedule=$@(@dr schedule:h) =config:h] ^+ ho-core =/ schedule @@ -1124,7 +1124,7 @@ =- (~(put by crons.hooks) id.hook -) (~(put by crons) origin cron) =. cor (schedule-cron origin cron) - =. cor (give-hook-response [%wait id origin schedule config]) + =. cor (give-hook-response [%cron id origin schedule config]) ho-core ++ ho-rest |= =origin:h @@ -1177,27 +1177,27 @@ =. hooks.hooks (~(put by hooks.hooks) i.order hook(state new-state.u.return)) ?: ?=(%denied -.result) [|+~[(fall msg.result default)] effects] - =. current-event new.result + =. current-event event.result next ++ wakeup-hook |= =(pole knot) ^+ cor ?+ pole ~|(bad-arvo-take/pole !!) - [%delayed id=@ ~] - =/ =id:h (slav %uv id.pole) - ?~ delay=(~(get by delayed.hooks) id) cor + [%waiting id=@ ~] + =/ id=id-hook:h (slav %uv id.pole) + ?~ wh=(~(get by waiting.hooks) id) cor :: make sure we clean up - =. delayed.hooks (~(del by delayed.hooks) id) + =. waiting.hooks (~(del by waiting.hooks) id) :: ignore premature fires - ?: (lth now.bowl fires-at.u.delay) cor - =* origin origin.u.delay - =/ hook (~(got by hooks.hooks) hook.u.delay) + ?: (lth now.bowl fires-at.u.wh) cor + =* origin origin.u.wh + =/ hook (~(got by hooks.hooks) hook.u.wh) =/ config ?@(origin ~ (~(gut by config.hook) origin ~)) - =/ args [[%wake +.u.delay] "delayed hook" origin config] - ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.delay) args) + =/ args [[%wake +.u.wh] "waiting hook" origin config] + ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.wh) args) :: [%cron id=@ kind=?(%chat %diary %heap) ship=@ name=@ ~] - =/ =id:h (slav %uv id.pole) + =/ id=id-hook:h (slav %uv id.pole) =/ =origin:h [kind.pole (slav %p ship.pole) name.pole] :: if unscheduled, ignore ?~ crons=(~(get by crons.hooks) id) cor @@ -1235,15 +1235,15 @@ ?@ origin ~ /[kind.origin]/(scot %p ship.origin)/[name.origin] (emit [%pass wire %arvo %b %rest next.schedule.cron]) -++ schedule-delay - |= dh=delayed-hook:h - =/ =wire /hooks/delayed/(scot %uv id.dh) - (emit [%pass wire %arvo %b %wait fires-at.dh]) -++ unschedule-delay - |= =id:h +++ schedule-waiting + |= wh=waiting-hook:h + =/ =wire /hooks/waiting/(scot %uv id.wh) + (emit [%pass wire %arvo %b %wait fires-at.wh]) +++ unschedule-waiting + |= id=id-hook:h ^+ cor - ?~ previous=(~(get by delayed.hooks) id) cor - =/ =wire /hooks/delayed/(scot %uv id.u.previous) + ?~ previous=(~(get by waiting.hooks) id) cor + =/ =wire /hooks/waiting/(scot %uv id.u.previous) (emit [%pass wire %arvo %b %rest fires-at.u.previous]) ++ run-hook-effects |= [effects=(list effect:h) =origin:h] @@ -1281,10 +1281,10 @@ (emit [%pass /hooks/effect %agent [our.bowl %contacts] %poke cage]) :: %wait - =/ =wire /hooks/delayed/(scot %uv id.effect) - =. cor (unschedule-delay id.effect) - =. delayed.hooks - (~(put by delayed.hooks) id.effect [origin +.effect]) - (schedule-delay +.effect) + =/ =wire /hooks/waiting/(scot %uv id.effect) + =. cor (unschedule-waiting id.effect) + =. waiting.hooks + (~(put by waiting.hooks) id.effect [origin +.effect]) + (schedule-waiting +.effect) == -- diff --git a/desk/gen/hooks/confirm.hoon b/desk/gen/hooks/confirm.hoon deleted file mode 100644 index ea6c1f228c..0000000000 --- a/desk/gen/hooks/confirm.hoon +++ /dev/null @@ -1,19 +0,0 @@ -/- h=hooks, c=channels -:- %say -|= $: [now=@da eny=@uvJ =beak] - [[=event:h =context:h ~] ~] - == -:- %noun -^- outcome:h -=- &+[[[%allowed event] -] state.hook.context] -^- (list effect:h) -?. ?=(?(%delay %on-post) -.event) ~ -?: ?=(%delay -.event) - =/ =nest:c [%chat ~bospur-davmyl-nocsyx-lassul %welcome-8458] - =+ !<(trigger=event:h data.event) - ?. ?=([%on-post %add *] trigger) ~ - =* post post.trigger - =/ =c-react:c [%add-react id.post author.post ':thumbs-up:'] - ~[[%channels %channel nest %post c-react]] -=/ id (rsh [3 48] eny.context) -~[[%delay id id.hook.context ~s30 !>(event)]] \ No newline at end of file diff --git a/desk/gen/hooks/truncate.hoon b/desk/gen/hooks/truncate.hoon deleted file mode 100644 index 538738a432..0000000000 --- a/desk/gen/hooks/truncate.hoon +++ /dev/null @@ -1,97 +0,0 @@ -/- h=hooks, c=channels -:- %say -|= $: [now=@da eny=@uvJ =beak] - [[=event:h ~] ~] - == -:- %noun -^- outcome:h -=| count=@ud -=/ max 140 -=/ new-content=story:c ~ -=* no-op &+[[[%allowed event] ~] !>(~)] -?. ?=(%on-post -.event) no-op -=* on-post +.event -?. ?=(?(%add %edit) -.on-post) no-op -=/ verses - ?- -.on-post - %add content.essay.post.on-post - %edit content.essay.on-post - == -|^ -:: made it to the end -=* return - =- &+[[[%allowed -] ~] !>(~)] - ?- event - [%on-post %add *] event(content.essay.post new-content) - [%on-post %edit *] event(content.essay new-content) - == -?~ verses return -?: (gte count max) return -=* next $(verses t.verses) -=/ verse i.verses -:: remove blocks -?: ?=(%block -.verse) next -=/ [new-inlines=(list inline:c) new-count=@ud] - (run-list p.verse count) -$(new-content (snoc new-content [%inline new-inlines]), verses t.verses, count new-count) -++ run-list - |= [inlines=(list inline:c) count=@ud] - ^- [(list inline:c) @ud] - =/ new-inlines=(list inline:c) ~ - |- - ?~ inlines - :: made it all the way through - [new-inlines count] - =* next $(inlines t.inlines) - =/ inline i.inlines - ?: (gte count max) - [new-inlines count] - ?@ inline - =/ new-string (trim-cord inline count) - ?~ new-string $(inlines ~) ::done - $(new-inlines (snoc new-inlines u.new-string), inlines t.inlines, count (add count (met 3 u.new-string))) - =/ [new-inline=(unit inline:c) new-count=@ud] - (run-special-inlines inline count) - ?~ new-inline $(inlines ~) ::done - $(new-inlines (snoc new-inlines u.new-inline), inlines t.inlines, count new-count) -++ run-special-inlines - |= [=inline:c count=@ud] - ^- [(unit inline:c) @ud] - ?+ -.inline [~ count] - %break [`inline +(count)] - :: - %ship - ?: (gth (add count 14) max) [~ count] - [`inline (add count 14)] - :: - %link - =/ new-string=(unit cord) (trim-cord q.inline count) - ?~ new-string [~ count] - =/ new-inline=inline:c inline(q u.new-string) - [(some new-inline) (add count (met 3 u.new-string))] - :: - %inline-code - =/ new-string=(unit cord) (trim-cord p.inline count) - ?~ new-string [~ count] - [(some inline(p u.new-string)) (add count (met 3 u.new-string))] - :: - ?(%italics %bold %strike %blockquote) - =/ [new-inlines=(list inline:c) new-count=@ud] (run-list p.inline count) - ?~ new-inlines [~ count] - [(some inline(p new-inlines)) new-count] - == -++ trim-cord - |= [=cord count=@ud] - ^- (unit ^cord) - =/ string (trip cord) - =/ length (lent string) - =/ total (add length count) - ?: (gth total max) - :: truncate - =/ remainder (sub total max) - :: no room for anything - ?: =(length remainder) ~ - =/ new-length (sub length remainder) - `(crip (scag new-length string)) - `cord --- \ No newline at end of file diff --git a/desk/lib/hooks-json.hoon b/desk/lib/hooks-json.hoon index ece631a223..3af6644113 100644 --- a/desk/lib/hooks-json.hoon +++ b/desk/lib/hooks-json.hoon @@ -6,7 +6,7 @@ =, enjs:format |% ++ id - |= i=id:h + |= i=id-hook:h s+(scot %uv i) ++ hooks |= hks=hooks:h @@ -17,11 +17,11 @@ == :: ++ hook-map - |= hks=(map id:h hook:h) + |= hks=(map id-hook:h hook:h) %- pairs %+ turn ~(tap by hks) - |= [=id:h hk=hook:h] + |= [id=id-hook:h hk=hook:h] [(scot %uv id) (hook hk)] :: ++ hook @@ -51,18 +51,18 @@ |= [key=@t noun=*] [key s+(scot %uw `@uw`(jam noun))] ++ order - |= ord=(map nest:c (list id:h)) + |= ord=(map nest:c (list id-hook:h)) %- pairs %+ turn ~(tap by ord) - |= [=nest:c seq=(list id:h)] + |= [=nest:c seq=(list id-hook:h)] [(nest-cord:enjs:cj nest) a+(turn seq id)] ++ crons - |= crs=(map id:h (map origin:h cron:h)) + |= crs=(map id-hook:h (map origin:h cron:h)) %- pairs %+ turn ~(tap by crs) - |= [=id:h cr=(map origin:h cron:h)] + |= [id=id-hook:h cr=(map origin:h cron:h)] [(scot %uv id) (cron-map cr)] ++ cron-map |= cr=(map origin:h cron:h) @@ -93,11 +93,11 @@ %gone (id id.r) %order (order-rsp +.r) %config (config-rsp +.r) - %wait (wait-rsp +.r) + %cron (cron-rsp +.r) %rest (rest-rsp +.r) == ++ set-rsp - |= [i=id:h name=@t src=@t meta=data:m error=(unit ^tang)] + |= [i=id-hook:h name=@t src=@t meta=data:m error=(unit ^tang)] %- pairs :~ id+(id i) name+s+name @@ -106,20 +106,20 @@ error+?~(error ~ (tang u.error)) == ++ order-rsp - |= [=nest:c seq=(list id:h)] + |= [=nest:c seq=(list id-hook:h)] %- pairs :~ nest+(nest:enjs:cj nest) seq+a+(turn seq id) == ++ config-rsp - |= [i=id:h =nest:c con=config:h] + |= [i=id-hook:h =nest:c con=config:h] %- pairs :~ id+(id i) nest+(nest:enjs:cj nest) config+(config con) == - ++ wait-rsp - |= [i=id:h or=origin:h sch=$@(@dr schedule:h) con=config:h] + ++ cron-rsp + |= [i=id-hook:h or=origin:h sch=$@(@dr schedule:h) con=config:h] %- pairs :~ id+(id i) origin+s+?~(or 'global' (nest-cord:enjs:cj or)) @@ -127,7 +127,7 @@ config+(config con) == ++ rest-rsp - |= [i=id:h or=origin:h] + |= [i=id-hook:h or=origin:h] %- pairs :~ id+(id i) origin+s+?~(or 'global' (nest-cord:enjs:cj or)) @@ -154,7 +154,7 @@ del/id order/order config/config - wait/wait + cron/cron rest/rest == ++ add @@ -186,7 +186,7 @@ ++ origin |= j=json ?~(j ~ (nest:dejs:cj j)) - ++ wait + ++ cron %- ot :~ id/id origin/origin diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 984c08f4d1..e3f0b97119 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -1,20 +1,22 @@ /- *channels, g=groups, a=activity, ch=chat, co=contacts, m=meta |% -:: $id: a unique identifier for a hook -+$ id @uv +:: $id-hook: a unique identifier for a hook ++$ id-hook @uv :: -:: $hook: a pure function that runs on triggers in a channel +:: $hook: a function that runs on triggers in a channel, can produce +:: effects, and change its own state :: -:: $id: a unique identifier for the hook -:: $name: a human-readable name for the hook -:: $version: the version the hook was compiled with -:: $src: the source code of the hook -:: $compiled: the compiled version of the hook -:: $state: the current state of the hook -:: $config: any configuration data for the hook +:: .id: a unique identifier for the hook +:: .name: a human-readable name for the hook +:: .version: the version the hook was compiled with +:: .src: the source code of the hook +:: .compiled: the compiled hook +:: .state: the current state of the hook +:: .config: any configuration data for the instance of the hook +:: running on a channel :: ++ hook - $: =id + $: id=id-hook version=%0 name=@t meta=data:m @@ -23,60 +25,64 @@ state=vase config=(map nest config) == -:: $hooks: collection of hooks, the order they should be run in, and -:: any delayed hooks that need to be run +:: $hooks: collection of hooks, the order they should be run in, hooks +:: running on a schedule, and any hooks waiting to run ++ hooks - $: hooks=(map id hook) - order=(map nest (list id)) - crons=(map id (map origin cron)) - delayed=(map delay-id [=origin delayed-hook]) + $: hooks=(map id-hook hook) + order=(map nest (list id-hook)) + crons=(map id-hook (map origin cron)) + waiting=(map id-wait [=origin waiting-hook]) == +$ origin $@(~ nest) -+$ delay-id id -+$ schedule [next=@da repeat=@dr] +$ cron - $: hook=id + $: hook=id-hook =schedule =config == -:: $delayed-hook: metadata for when a delayed hook fires from the timer -+$ delayed-hook - $: id=delay-id - hook=id ++$ schedule [next=@da repeat=@dr] ++$ id-wait @uv +:: $waiting-hook: metadata for when a waiting hook fires from the timer ++$ waiting-hook + $: id=id-wait + hook=id-hook data=vase fires-at=time == -:: +:: $config: configuration data for a hook instance +$ config (map @t *) +:: +:: $action: what we can do with a hook +$ action $% [%add name=@t src=@t] - [%edit =id name=(unit @t) src=(unit @t) meta=(unit data:m)] - [%del =id] - [%order =nest seq=(list id)] - [%config =id =nest =config] - [%wait =id =origin schedule=$@(@dr schedule) =config] - [%rest =id =origin] + [%edit id=id-hook name=(unit @t) src=(unit @t) meta=(unit data:m)] + [%del id=id-hook] + [%order =nest seq=(list id-hook)] + [%config id=id-hook =nest =config] + [%cron id=id-hook =origin schedule=$@(@dr schedule) =config] + [%rest id=id-hook =origin] == +:: +:: $response: the result of an action on a hook +$ response - $% [%set =id name=@t src=@t meta=data:m error=(unit tang)] - [%gone =id] - [%order =nest seq=(list id)] - [%config =id =nest =config] - [%wait =id =origin schedule=$@(@dr schedule) =config] - [%rest =id =origin] + $% [%set id=id-hook name=@t src=@t meta=data:m error=(unit tang)] + [%gone id=id-hook] + [%order =nest seq=(list id-hook)] + [%config id=id-hook =nest =config] + [%cron id=id-hook =origin schedule=$@(@dr schedule) =config] + [%rest id=id-hook =origin] == :: $context: ambient state that a hook should know about not :: necessarily tied to a specific event :: -:: $channel: the channel that the hook is operating on -:: $group: the group that the channel belongs to -:: $channels: all the channels in the group -:: $hook: the hook that's running -:: $config: the configuration data for this instance of the hook -:: $now: the current time -:: $our: the ship that the hook is running on -:: $src: the ship that triggered the hook -:: $eny: entropy for random number generation or key derivation +:: .channel: the channel that the hook is operating on +:: .group: the group that the channel belongs to +:: .channels: all the channels in the group +:: .hook: the hook that's running +:: .config: the configuration data for this instance of the hook +:: .now: the current time +:: .our: the ship that the hook is running on +:: .src: the ship that triggered the hook +:: .eny: entropy for random number generation or key derivation :: +$ context $: channel=(unit [=nest v-channel]) @@ -102,7 +108,7 @@ $% [%on-post on-post] [%on-reply on-reply] [%cron ~] - [%wake delayed-hook] + [%wake waiting-hook] == :: :: $on-post: a hook event that fires when posts are interacted with @@ -133,17 +139,17 @@ :: :: $result: whether the action was allowed or denied and any :: transformed values -:: $actions: any actions that should be taken on other agents or delay +:: $effects: any actions that should be taken on other agents or wait :: $new-state: the new state of the hook after running :: +$ return - $: $: =result + $: $: result=event-result effects=(list effect) == new-state=vase == :: -:: $result: whether to allow the action, and any transformations to +:: $event-result: whether to allow the action, and any transformations to :: the event :: :: $allowed: represents the action being allowed to go through, and @@ -151,8 +157,8 @@ :: $denied: represents the action being denied along with the reason :: that the action was denied :: -+$ result - $% [%allowed new=event] ++$ event-result + $% [%allowed =event] [%denied msg=(unit cord)] == :: @@ -166,7 +172,7 @@ [%dm =action:dm:ch] [%club =action:club:ch] [%contacts =action:co] - [%wait delayed-hook] + [%wait waiting-hook] == :: -- \ No newline at end of file diff --git a/desk/ted/hook/schedule.hoon b/desk/ted/hook/schedule.hoon index be3a15225a..863f28758e 100644 --- a/desk/ted/hook/schedule.hoon +++ b/desk/ted/hook/schedule.hoon @@ -14,12 +14,12 @@ !> ^- action:h ?: ?=(%stop -.action) [%rest id origin] - [%wait id origin +.action] + [%cron id origin +.action] ;< ~ bind:m (poke-our:s %channels-server cage) ;< =^cage bind:m (take-fact:s /responses) ?> ?=(%hook-response-0 p.cage) =+ !<(=response:h q.cage) -?> ?=(?(%wait %rest) -.response) +?> ?=(?(%cron %rest) -.response) ?: ?=(%rest -.response) %- (slog (crip "stopped scheduled hook {} running on {}") ~) (pure:m !>(~)) @@ -33,4 +33,4 @@ $% [%stop ~] [%start schedule=$@(@dr schedule:h) =config:h] == --- \ No newline at end of file +-- From db6f11bc9d4f1e7174f6f0f603f39c68a9bad9f2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 18:28:18 +0000 Subject: [PATCH 033/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 9e22d51ace..56c68777e1 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.sf1ku.j727t.hkgu4.3mt9a.rbvm5.glob' 0v6.sf1ku.j727t.hkgu4.3mt9a.rbvm5] + glob-http+['https://bootstrap.urbit.org/glob-0vjkg1r.fhh7a.4um0o.v4oa9.8bpuq.glob' 0vjkg1r.fhh7a.4um0o.v4oa9.8bpuq] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 1ac90e0349b402404bec94a3ec1531bc2386d897 Mon Sep 17 00:00:00 2001 From: James Acklin Date: Wed, 27 Nov 2024 14:30:00 -0500 Subject: [PATCH 034/149] queries: omit you from suggestions --- packages/shared/src/db/queries.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index b7732993a1..cce64ed748 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -2811,10 +2811,12 @@ export const getUserContacts = createReadQuery( export const getSuggestedContacts = createReadQuery( 'getSuggestedContacts', async (ctx: QueryCtx) => { + const currentUserId = getCurrentUserId(); return ctx.db.query.contacts.findMany({ where: and( eq($contacts.isContact, false), - eq($contacts.isContactSuggestion, true) + eq($contacts.isContactSuggestion, true), + not(eq($contacts.id, currentUserId)) ), with: { pinnedGroups: { From b1ca91a26e962f77a3fa04d6b3184c840cef1da3 Mon Sep 17 00:00:00 2001 From: James Acklin Date: Wed, 27 Nov 2024 14:30:16 -0500 Subject: [PATCH 035/149] ContactsScreenView: better "you" logic --- packages/ui/src/components/ContactsScreenView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/ContactsScreenView.tsx b/packages/ui/src/components/ContactsScreenView.tsx index d5ebce9c04..a93ff9aa30 100644 --- a/packages/ui/src/components/ContactsScreenView.tsx +++ b/packages/ui/src/components/ContactsScreenView.tsx @@ -54,7 +54,7 @@ export function ContactsScreenView(props: Props) { if (trimmedSuggested.length > 0) { result.push({ - title: 'Suggested by Pals and DMs', + title: 'Suggested from %pals and DMs', data: trimmedSuggested, }); } @@ -72,7 +72,7 @@ export function ContactsScreenView(props: Props) { showNickname showEndContent endContent={ - item.isContactSuggestion ? ( + item.isContactSuggestion && !isSelf ? ( ) : isSelf ? ( From 47c55d01486e1f70c9d1b3c38495f38d99fd1a9e Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 27 Nov 2024 13:31:59 -0600 Subject: [PATCH 036/149] add + icon to contacts screen for quicker adding --- .../features/contacts/AddContactsScreen.tsx | 34 +++++++++++ packages/app/features/top/ContactsScreen.tsx | 6 ++ packages/app/navigation/RootStack.tsx | 2 + packages/app/navigation/types.ts | 1 + packages/shared/src/api/contactsApi.ts | 8 +++ packages/shared/src/store/contactActions.ts | 24 ++++++++ .../ui/src/components/AddContactsView.tsx | 59 +++++++++++++++++++ packages/ui/src/components/ContactBook.tsx | 12 +++- packages/ui/src/components/ContactRow.tsx | 10 +++- packages/ui/src/index.tsx | 1 + 10 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 packages/app/features/contacts/AddContactsScreen.tsx create mode 100644 packages/ui/src/components/AddContactsView.tsx diff --git a/packages/app/features/contacts/AddContactsScreen.tsx b/packages/app/features/contacts/AddContactsScreen.tsx new file mode 100644 index 0000000000..7fea97b321 --- /dev/null +++ b/packages/app/features/contacts/AddContactsScreen.tsx @@ -0,0 +1,34 @@ +import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import * as store from '@tloncorp/shared/store'; +import { AddContactsView } from '@tloncorp/ui'; +import { useCallback } from 'react'; + +import type { RootStackParamList } from '../../navigation/types'; + +type Props = NativeStackScreenProps; + +export function AddContactsScreen(props: Props) { + // const handleGoToChannel = useCallback( + // (channel: db.Channel) => { + // props.navigation.reset({ + // index: 1, + // routes: [ + // { name: 'ChatList' }, + // { name: 'Channel', params: { channelId: channel.id } }, + // ], + // }); + // }, + // [props.navigation] + // ); + + const handleAddContacts = useCallback((addIds: string[]) => { + store.addContacts(addIds); + }, []); + + return ( + props.navigation.goBack()} + addContacts={handleAddContacts} + /> + ); +} diff --git a/packages/app/features/top/ContactsScreen.tsx b/packages/app/features/top/ContactsScreen.tsx index 9368e8b51c..67a8ebd6a4 100644 --- a/packages/app/features/top/ContactsScreen.tsx +++ b/packages/app/features/top/ContactsScreen.tsx @@ -66,6 +66,12 @@ export default function ContactsScreen(props: Props) { navigate('AddContacts')} + /> + } rightControls={ {/* individual screens */} + { }); }; +// TODO: once we can add in bulk from the backend, do so +export const addUserContacts = async (contactIds: string[]) => { + const promises = contactIds.map((contactId) => { + return addContact(contactId); + }); + return Promise.all(promises); +}; + export const removeContact = async (contactId: string) => { return poke({ app: 'contacts', diff --git a/packages/shared/src/store/contactActions.ts b/packages/shared/src/store/contactActions.ts index 8216b20382..edbd8658eb 100644 --- a/packages/shared/src/store/contactActions.ts +++ b/packages/shared/src/store/contactActions.ts @@ -21,6 +21,30 @@ export async function addContact(contactId: string) { } } +export async function addContacts(contacts: string[]) { + const optimisticUpdates = contacts.map((contactId) => + db.updateContact({ + id: contactId, + isContact: true, + isContactSuggestion: false, + }) + ); + await Promise.all(optimisticUpdates); + + try { + await api.addUserContacts(contacts); + } catch (e) { + // Rollback the update + const rolbacks = contacts.map((contactId) => + db.updateContact({ + id: contactId, + isContact: false, + }) + ); + await Promise.all(rolbacks); + } +} + export async function removeContact(contactId: string) { // Optimistic update await db.updateContact({ id: contactId, isContact: false }); diff --git a/packages/ui/src/components/AddContactsView.tsx b/packages/ui/src/components/AddContactsView.tsx new file mode 100644 index 0000000000..2109933c60 --- /dev/null +++ b/packages/ui/src/components/AddContactsView.tsx @@ -0,0 +1,59 @@ +import * as store from '@tloncorp/shared/store'; +import { useCallback, useMemo, useState } from 'react'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { View, YStack } from 'tamagui'; + +import { Button } from './Button'; +import { ContactBook } from './ContactBook'; +import { ScreenHeader } from './ScreenHeader'; + +export function AddContactsView(props: { + goBack: () => void; + addContacts: (ids: string[]) => void; +}) { + const { bottom } = useSafeAreaInsets(); + const [newContacts, setNewContacts] = useState([]); + const handleAddContacts = useCallback(() => { + props.addContacts(newContacts); + props.goBack(); + }, [newContacts, props]); + + const { data: existingContacts } = store.useUserContacts(); + const existingIds = useMemo(() => { + return existingContacts?.map((c) => c.id) ?? []; + }, [existingContacts]); + + console.log(`existingIds`, existingIds); + + return ( + + + + + + + + + ); +} diff --git a/packages/ui/src/components/ContactBook.tsx b/packages/ui/src/components/ContactBook.tsx index bc1b89973a..fb97444b0d 100644 --- a/packages/ui/src/components/ContactBook.tsx +++ b/packages/ui/src/components/ContactBook.tsx @@ -27,6 +27,7 @@ export function ContactBook({ searchPlaceholder = '', onSelect, multiSelect = false, + immutableIds = [], onSelectedChange, onScrollChange, explanationComponent, @@ -34,6 +35,7 @@ export function ContactBook({ height, width, }: { + immutableIds?: string[]; searchPlaceholder?: string; searchable?: boolean; onSelect?: (contactId: string) => void; @@ -46,6 +48,7 @@ export function ContactBook({ width?: number; }) { const contacts = useContacts(); + const immutableSet = useMemo(() => new Set(immutableIds), [immutableIds]); const contactsIndex = useContactIndex(); const segmentedContacts = useAlphabeticallySegmentedContacts( contacts ?? [], @@ -71,6 +74,10 @@ export function ContactBook({ const [selected, setSelected] = useState([]); const handleSelect = useCallback( (contactId: string) => { + if (immutableSet.has(contactId)) { + return; + } + if (multiSelect) { if (selected.includes(contactId)) { const newSelected = selected.filter((id) => id !== contactId); @@ -85,7 +92,7 @@ export function ContactBook({ onSelect?.(contactId); } }, - [multiSelect, onSelect, onSelectedChange, selected] + [immutableSet, multiSelect, onSelect, onSelectedChange, selected] ); const renderItem = useCallback( @@ -96,6 +103,7 @@ export function ContactBook({ backgroundColor={'$secondaryBackground'} key={item.id} contact={item} + immutable={immutableSet.has(item.id)} selectable={multiSelect} selected={isSelected} onPress={handleSelect} @@ -103,7 +111,7 @@ export function ContactBook({ /> ); }, - [selected, multiSelect, handleSelect] + [selected, immutableSet, multiSelect, handleSelect] ); const scrollPosition = useRef(0); diff --git a/packages/ui/src/components/ContactRow.tsx b/packages/ui/src/components/ContactRow.tsx index 261b0a5378..75e7d064ec 100644 --- a/packages/ui/src/components/ContactRow.tsx +++ b/packages/ui/src/components/ContactRow.tsx @@ -12,6 +12,7 @@ function ContactRowItemRaw({ contact, selected = false, selectable = false, + immutable = false, onPress, pressStyle, backgroundColor, @@ -21,6 +22,7 @@ function ContactRowItemRaw({ onPress: (id: string) => void; selectable?: boolean; selected?: boolean; + immutable?: boolean; } & Omit) { const displayName = useMemo(() => getDisplayName(contact), [contact]); @@ -56,8 +58,12 @@ function ContactRowItemRaw({ height="$4xl" width="$4xl" > - {selected ? ( - + {selected || immutable ? ( + ) : ( Date: Wed, 27 Nov 2024 13:33:29 -0600 Subject: [PATCH 037/149] remove commented out method --- .../app/features/contacts/AddContactsScreen.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/packages/app/features/contacts/AddContactsScreen.tsx b/packages/app/features/contacts/AddContactsScreen.tsx index 7fea97b321..c38d0de685 100644 --- a/packages/app/features/contacts/AddContactsScreen.tsx +++ b/packages/app/features/contacts/AddContactsScreen.tsx @@ -8,19 +8,6 @@ import type { RootStackParamList } from '../../navigation/types'; type Props = NativeStackScreenProps; export function AddContactsScreen(props: Props) { - // const handleGoToChannel = useCallback( - // (channel: db.Channel) => { - // props.navigation.reset({ - // index: 1, - // routes: [ - // { name: 'ChatList' }, - // { name: 'Channel', params: { channelId: channel.id } }, - // ], - // }); - // }, - // [props.navigation] - // ); - const handleAddContacts = useCallback((addIds: string[]) => { store.addContacts(addIds); }, []); From a65bd388bfeec4159a257b99e22a4c9ce12017de Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 27 Nov 2024 13:40:14 -0600 Subject: [PATCH 038/149] prevent ever marking yourself a suggestion when deserializing --- packages/shared/src/api/contactsApi.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/api/contactsApi.ts b/packages/shared/src/api/contactsApi.ts index 5e837d46ba..283ea74340 100644 --- a/packages/shared/src/api/contactsApi.ts +++ b/packages/shared/src/api/contactsApi.ts @@ -247,6 +247,7 @@ export const v0PeerToClientProfile = ( isContactSuggestion?: boolean; } ): db.Contact => { + const currentUserId = getCurrentUserId(); return { id, peerNickname: contact?.nickname ?? null, @@ -262,7 +263,7 @@ export const v0PeerToClientProfile = ( })) ?? [], isContact: false, - isContactSuggestion: config?.isContactSuggestion, + isContactSuggestion: config?.isContactSuggestion && id !== currentUserId, }; }; @@ -287,6 +288,7 @@ export const v1PeerToClientProfile = ( isContactSuggestion?: boolean; } ): db.Contact => { + const currentUserId = getCurrentUserId(); return { id, peerNickname: contact.nickname?.value ?? null, @@ -301,7 +303,8 @@ export const v1PeerToClientProfile = ( contactId: id, })) ?? [], isContact: config?.isContact, - isContactSuggestion: config?.isContactSuggestion && !config?.isContact, + isContactSuggestion: + config?.isContactSuggestion && !config?.isContact && id !== currentUserId, }; }; From b85eeca589d14c8d21379d4c8a2c6e20d36d974c Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 27 Nov 2024 13:44:59 -0600 Subject: [PATCH 039/149] add to profile screen navigator for new web --- packages/app/navigation/desktop/ProfileScreenNavigator.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/app/navigation/desktop/ProfileScreenNavigator.tsx b/packages/app/navigation/desktop/ProfileScreenNavigator.tsx index 0c813d1d55..9e46d44e71 100644 --- a/packages/app/navigation/desktop/ProfileScreenNavigator.tsx +++ b/packages/app/navigation/desktop/ProfileScreenNavigator.tsx @@ -1,5 +1,6 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { AddContactsScreen } from '../../features/contacts/AddContactsScreen'; import { AppInfoScreen } from '../../features/settings/AppInfoScreen'; import { BlockedUsersScreen } from '../../features/settings/BlockedUsersScreen'; import { EditProfileScreen } from '../../features/settings/EditProfileScreen'; @@ -24,9 +25,10 @@ export const ProfileScreenNavigator = () => { > + Date: Wed, 27 Nov 2024 13:48:42 -0600 Subject: [PATCH 040/149] ops: bump 4.2.4 --- apps/tlon-mobile/android/app/build.gradle | 2 +- apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/tlon-mobile/android/app/build.gradle b/apps/tlon-mobile/android/app/build.gradle index 0268fbb891..af13d348a5 100644 --- a/apps/tlon-mobile/android/app/build.gradle +++ b/apps/tlon-mobile/android/app/build.gradle @@ -88,7 +88,7 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion versionCode 108 - versionName "4.2.3" + versionName "4.2.4" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) } diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index ec04793a11..5d30e4e75d 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -1427,7 +1427,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.3; + MARKETING_VERSION = 4.2.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1465,7 +1465,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.3; + MARKETING_VERSION = 4.2.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1689,7 +1689,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.3; + MARKETING_VERSION = 4.2.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1732,7 +1732,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.3; + MARKETING_VERSION = 4.2.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", From 39daf803e8013bf217523ed0d71bcf68d388e73f Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 19:55:56 +0000 Subject: [PATCH 041/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 410a7903e4..dce125ff4d 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v5.sk7ei.f8cfj.0osok.hapmm.rb9ng.glob' 0v5.sk7ei.f8cfj.0osok.hapmm.rb9ng] + glob-http+['https://bootstrap.urbit.org/glob-0v1.q6jaf.bme3f.povqt.eo08j.d6qtq.glob' 0v1.q6jaf.bme3f.povqt.eo08j.d6qtq] base+'groups' version+[6 4 2] website+'https://tlon.io' From 843d131dc3937388df274d8e5f10988c6b0f1ae0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 19:56:21 +0000 Subject: [PATCH 042/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 56c68777e1..2a8788a36d 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0vjkg1r.fhh7a.4um0o.v4oa9.8bpuq.glob' 0vjkg1r.fhh7a.4um0o.v4oa9.8bpuq] + glob-http+['https://bootstrap.urbit.org/glob-0v4.hjquo.to0hs.v3ebc.0hmmb.j64mf.glob' 0v4.hjquo.to0hs.v3ebc.0hmmb.j64mf] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From f5cd0c4f09ae1fb81f96183b0f0a79534372d4a8 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 27 Nov 2024 14:14:49 -0600 Subject: [PATCH 043/149] fix color deserializer --- packages/shared/src/logic/utils.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/shared/src/logic/utils.ts b/packages/shared/src/logic/utils.ts index be198ef6fe..2d44a104f2 100644 --- a/packages/shared/src/logic/utils.ts +++ b/packages/shared/src/logic/utils.ts @@ -207,8 +207,9 @@ export function normalizeUrbitColor(color: string): string { return color; } - const colorString = color.slice(2).replace('.', '').toUpperCase(); - const lengthAdjustedColor = colorString.padStart(6, '0'); + const noDots = color.replace('.', ''); + const prefixStripped = color.startsWith('0x') ? noDots.slice(2) : noDots; + const lengthAdjustedColor = prefixStripped.toUpperCase().padStart(6, '0'); return `#${lengthAdjustedColor}`; } From 773bbecf08bb28e40dff0f3bc6acfd54f15d0382 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 27 Nov 2024 14:30:31 -0600 Subject: [PATCH 044/149] fix display name for empty string nicknames --- packages/ui/src/utils/user.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/utils/user.ts b/packages/ui/src/utils/user.ts index 3c7481fd19..3783b8cc35 100644 --- a/packages/ui/src/utils/user.ts +++ b/packages/ui/src/utils/user.ts @@ -23,5 +23,10 @@ export function formatUserId( } export function getDisplayName(contact: db.Contact) { - return contact.nickname ?? contact.id; + if (contact.nickname && contact.nickname.length) { + return contact.nickname; + } + + const formatted = formatUserId(contact.id); + return formatted?.display ?? contact.id; } From 48549cd3e38395bb89dee1a0ed6d313e0c4079bc Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 27 Nov 2024 20:55:22 +0000 Subject: [PATCH 045/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 2a8788a36d..77e9d58704 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.hjquo.to0hs.v3ebc.0hmmb.j64mf.glob' 0v4.hjquo.to0hs.v3ebc.0hmmb.j64mf] + glob-http+['https://bootstrap.urbit.org/glob-0v6.ar89l.ohn8c.72n4o.krb3l.hb512.glob' 0v6.ar89l.ohn8c.72n4o.krb3l.hb512] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From ea6ba9ff604508799d52eb3bb76ee67cdf00c889 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 27 Nov 2024 16:03:44 -0600 Subject: [PATCH 046/149] hooks: syntax cleanup --- desk/app/channels-server.hoon | 98 ++++++++++++++--------------------- desk/lib/hooks-json.hoon | 2 +- desk/sur/hooks.hoon | 2 +- desk/ted/hook/add.hoon | 2 +- desk/ted/hook/configure.hoon | 2 +- desk/ted/hook/del.hoon | 2 +- desk/ted/hook/edit.hoon | 2 +- desk/ted/hook/order.hoon | 2 +- 8 files changed, 47 insertions(+), 65 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 2e5c5dbf57..7b0a9c1c40 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -360,10 +360,10 @@ =/ seq %+ skim seq.action - |= id=id-hook:h - (~(has by hooks.hooks) id) + ~(has by hooks.hooks) =. order.hooks (~(put by order.hooks) nest.action seq) - (give-hook-response %order nest.action seq) + =/ =response:h [%order nest.action seq] + (give %fact ~[/v0/hooks] hook-response-0+!>(response)) :: %config ho-abet:(ho-configure:(ho-abed:ho-core id.action) +>.action) @@ -542,7 +542,7 @@ ^+ cor ?+ pole ~|(bad-arvo-take/pole !!) [%hooks rest=*] - (wakeup-hook rest.pole) + (wake-hook rest.pole) == :: ++ watch-groups (safe-watch /groups [our.bowl %groups] /groups) @@ -725,7 +725,6 @@ %post =^ update=(unit u-channel:c) ca-core (ca-c-post c-post.c-channel) - ~& "received post update {}" ?~ update ca-core (ca-update u.update) == @@ -734,11 +733,9 @@ |= =c-post:c ^- [(unit u-channel:c) _ca-core] ?> (can-write:ca-perms src.bowl writers.perm.perm.channel) - ~& "running post command" =* no-op `ca-core ?- -.c-post %add - ~& "adding post" ?> |(=(src.bowl our.bowl) =(src.bowl author.essay.c-post)) ?> =(kind.nest -.kind-data.essay.c-post) =/ id=id-post:c @@ -749,7 +746,6 @@ =/ new=v-post:c [[id ~ ~] 0 essay.c-post] =^ result=(each event:h tang) cor =/ =event:h [%on-post %add new] - ~& "running post hooks" (run-hooks event nest 'post blocked') ?: ?=(%.n -.result) ((slog p.result) [~ ca-core]) @@ -803,17 +799,16 @@ (run-hooks event nest 'react action blocked') ?: ?=(%.n -.result) ((slog p.result) no-op) - =/ new=c-post:c + =/ new=$>(?(%add-react %del-react) c-post:c) ?> ?=([%on-post %react *] p.result) - ?~ react.p.result [%del-react id.c-post ship.p.result] + ?~ react.p.result + [%del-react id.c-post ship.p.result] [%add-react id.c-post [ship u.react]:p.result] =/ [update=? reacts=v-reacts:c] - %+ ca-c-react reacts.u.u.post - ?>(?=(?(%add-react %del-react) -.new) new) + (ca-c-react reacts.u.u.post new) ?. update no-op :- `[%post id.c-post %reacts reacts] - %= ca-core - posts.channel + %= ca-core posts.channel (put:on-v-posts:c posts.channel id.c-post ~ u.u.post(reacts reacts)) == :: @@ -825,8 +820,7 @@ (ca-c-reply u.u.post c-reply.c-post) ?~ update no-op :- `[%post id.c-post u.update] - %= ca-core - posts.channel + %= ca-core posts.channel (put:on-v-posts:c posts.channel id.c-post ~ u.u.post) == == @@ -899,13 +893,12 @@ (run-hooks event nest 'delete blocked') ?: ?=(%.n -.result) ((slog p.result) [~ replies]) - =/ new=c-reply:c + =/ new=$>(?(%add-react %del-react) c-reply:c) ?> ?=([%on-reply %react *] p.result) ?~ react.p.result [%del-react id.c-reply ship.p.result] [%add-react id.c-reply [ship u.react]:p.result] =/ [update=? reacts=v-reacts:c] - %+ ca-c-react reacts.u.u.reply - ?>(?=(?(%add-react %del-react) -.new) new) + (ca-c-react reacts.u.u.reply new) ?. update `replies :- `[%reply id.c-reply %reacts reacts] (put:on-v-replies:c replies id.c-reply ~ u.u.reply(reacts reacts)) @@ -1028,10 +1021,6 @@ [now our src eny]:bowl == :: -++ give-hook-response - |= =response:h - ^+ cor - (give %fact ~[/v0/hooks] hook-response-0+!>(response)) ++ ho-core |_ [id=id-hook:h =hook:h gone=_|] ++ ho-core . @@ -1061,28 +1050,22 @@ ((slog 'compilation result:' p.result) ~) `p.result =. hook [id %0 name *data:m src compiled !>(~) ~] - =. cor - =/ error=(unit tang) - ?:(?=(%& -.result) ~ `p.result) - (give-hook-response [%set id name src meta.hook error]) - ho-core + =/ error=(unit tang) + ?:(?=(%& -.result) ~ `p.result) + (ho-give-response [%set id name src meta.hook error]) ++ ho-edit |= [name=(unit @t) src=(unit @t) meta=(unit data:m)] =? src.hook ?=(^ src) u.src =/ result=(each vase tang) (compile:utils src.hook) ?: ?=(%| -.result) - =. cor - %- give-hook-response - [%set id name.hook src.hook meta.hook `p.result] - ho-core + %- ho-give-response + [%set id name.hook src.hook meta.hook `p.result] =? name.hook ?=(^ name) u.name =? meta.hook ?=(^ meta) u.meta =. compiled.hook `p.result - =. cor - %- give-hook-response - [%set id name.hook src.hook meta.hook ~] - ho-core + %- ho-give-response + [%set id name.hook src.hook meta.hook ~] :: ++ ho-del =. gone & @@ -1101,31 +1084,28 @@ =. waiting.hooks %+ roll ~(tap by waiting.hooks) - |= [[=id-wait:h d=[* waiting-hook:h]] wh=_waiting.hooks] - ?. =(id hook.d) wh + |= [[=id-wait:h w=[* waiting-hook:h]] wh=_waiting.hooks] + ?. =(id hook.w) wh (~(del by wh) id-wait) - =. cor (give-hook-response [%gone id]) - ho-core + (ho-give-response [%gone id]) ++ ho-configure |= [=nest:c =config:h] ^+ ho-core =. config.hook (~(put by config.hook) nest config) - =. cor (give-hook-response [%config id nest config]) - ho-core + (ho-give-response [%config id nest config]) ++ ho-cron |= [=origin:h schedule=$@(@dr schedule:h) =config:h] ^+ ho-core - =/ schedule - ?: ?=(@ schedule) [now.bowl schedule] - schedule + =? schedule ?=(@ schedule) + [now.bowl schedule] + ?> ?=(^ schedule) =/ crons (~(gut by crons.hooks) id *(map origin:h cron:h)) =/ =cron:h [id schedule config] =. crons.hooks =- (~(put by crons.hooks) id.hook -) (~(put by crons) origin cron) =. cor (schedule-cron origin cron) - =. cor (give-hook-response [%cron id origin schedule config]) - ho-core + (ho-give-response [%cron id origin schedule config]) ++ ho-rest |= =origin:h ^+ ho-core @@ -1134,8 +1114,7 @@ =. crons.hooks (~(put by crons.hooks) id (~(del by crons) origin)) =. cor (unschedule-cron origin cron) - =. cor (give-hook-response [%rest id origin]) - ho-core + (ho-give-response [%rest id origin]) ++ ho-run-single |= [=event:h prefix=tape =origin:h =config:h] =/ channel @@ -1152,37 +1131,39 @@ =. hook hook(state new-state.u.return) =. cor (run-hook-effects effects.u.return origin) ho-core + ++ ho-give-response + |= =response:h + (give %fact ~[/v0/hooks] hook-response-0+!>(response)) -- ++ run-hooks |= [=event:h =nest:c default=cord] ^- [(each event:h tang) _cor] =; [result=(each event:h tang) effects=(list effect:h)] [result (run-hook-effects effects nest)] - =/ current-event event =| effects=(list effect:h) =/ order (~(got by order.hooks) nest) =/ channel `[nest (~(got by v-channels) nest)] =/ =context:h (get-hook-context channel *config:h) |- ?~ order - [&+current-event effects] + [&+event effects] =* next $(order t.order) =/ hook (~(got by hooks.hooks) i.order) =/ ctx context(hook hook, config (~(gut by config.hook) nest ~)) =/ return=(unit return:h) - (run-hook:utils [current-event ctx] hook) + (run-hook:utils [event ctx] hook) ?~ return next =* result result.u.return =. effects (weld effects effects.u.return) =. hooks.hooks (~(put by hooks.hooks) i.order hook(state new-state.u.return)) ?: ?=(%denied -.result) [|+~[(fall msg.result default)] effects] - =. current-event event.result + =. event event.result next -++ wakeup-hook +++ wake-hook |= =(pole knot) ^+ cor - ?+ pole ~|(bad-arvo-take/pole !!) + ?+ pole ~|(bad-arvo-take+pole !!) [%waiting id=@ ~] =/ id=id-hook:h (slav %uv id.pole) ?~ wh=(~(get by waiting.hooks) id) cor @@ -1205,6 +1186,7 @@ :: ignore premature fires ?: (lth now.bowl next.schedule.u.cron) cor =. next.schedule.u.cron + :: :: we don't want to run the cron for every iteration it would :: have run 'offline', so we check here to make sure that the :: next fire time is in the future @@ -1218,20 +1200,20 @@ =. cor (schedule-cron origin u.cron) =/ args [[%cron ~] "cron job" origin config.u.cron] - ho-abet:(ho-run-single:(ho-abed:ho-core hook.u.cron) args) + ho-abet:(ho-run-single:(ho-abed:ho-core id-hook.u.cron) args) == ++ schedule-cron |= [=origin:h =cron:h] ^+ cor =/ wire - %+ welp /hooks/cron/(scot %uv hook.cron) + %+ welp /hooks/cron/(scot %uv id-hook.cron) ?@ origin ~ /[kind.origin]/(scot %p ship.origin)/[name.origin] (emit [%pass wire %arvo %b %wait next.schedule.cron]) ++ unschedule-cron |= [=origin:h =cron:h] =/ wire - %+ welp /hooks/cron/(scot %uv hook.cron) + %+ welp /hooks/cron/(scot %uv id-hook.cron) ?@ origin ~ /[kind.origin]/(scot %p ship.origin)/[name.origin] (emit [%pass wire %arvo %b %rest next.schedule.cron]) diff --git a/desk/lib/hooks-json.hoon b/desk/lib/hooks-json.hoon index 3af6644113..51868d233f 100644 --- a/desk/lib/hooks-json.hoon +++ b/desk/lib/hooks-json.hoon @@ -75,7 +75,7 @@ ++ cron |= crn=cron:h %- pairs - :~ hook+(id hook.crn) + :~ hook+(id id-hook.crn) schedule+(schedule schedule.crn) config+(config config.crn) == diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index e3f0b97119..178ddcc980 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -35,7 +35,7 @@ == +$ origin $@(~ nest) +$ cron - $: hook=id-hook + $: =id-hook =schedule =config == diff --git a/desk/ted/hook/add.hoon b/desk/ted/hook/add.hoon index 2e66c7b2ed..b743a0ff85 100644 --- a/desk/ted/hook/add.hoon +++ b/desk/ted/hook/add.hoon @@ -17,4 +17,4 @@ %- (slog (crip "hook {} added with id {}") ~) ?~ error.response (pure:m !>(~)) %- (slog 'compilation error:' u.error.response) -(pure:m !>(~)) \ No newline at end of file +(pure:m !>(~)) diff --git a/desk/ted/hook/configure.hoon b/desk/ted/hook/configure.hoon index 20927f9f85..574666a24f 100644 --- a/desk/ted/hook/configure.hoon +++ b/desk/ted/hook/configure.hoon @@ -15,4 +15,4 @@ =+ !<(=response:h q.cage) ?> ?=(%config -.response) %- (slog (crip "hook {} running on {} configured") ~) -(pure:m !>(~)) \ No newline at end of file +(pure:m !>(~)) diff --git a/desk/ted/hook/del.hoon b/desk/ted/hook/del.hoon index b955c86621..04d5cbafad 100644 --- a/desk/ted/hook/del.hoon +++ b/desk/ted/hook/del.hoon @@ -16,4 +16,4 @@ ?> ?=(%gone -.response) ?> =(id id.response) %- (slog (crip "hook {} deleted") ~) -(pure:m !>(~)) \ No newline at end of file +(pure:m !>(~)) diff --git a/desk/ted/hook/edit.hoon b/desk/ted/hook/edit.hoon index 04fbbdc369..dd3b7e6eb2 100644 --- a/desk/ted/hook/edit.hoon +++ b/desk/ted/hook/edit.hoon @@ -19,4 +19,4 @@ (pure:m !>(~)) %- (slog (crip "hook {} edited") ~) %- (slog 'compilation error:' u.error.response) -(pure:m !>(~)) \ No newline at end of file +(pure:m !>(~)) diff --git a/desk/ted/hook/order.hoon b/desk/ted/hook/order.hoon index d31736f891..7f44547200 100644 --- a/desk/ted/hook/order.hoon +++ b/desk/ted/hook/order.hoon @@ -16,4 +16,4 @@ ?> ?=(%order -.response) %- (slog (crip "new hook order for {}") ~) ~& seq.response -(pure:m !>(~)) \ No newline at end of file +(pure:m !>(~)) From 14b6e27a9e6ae71dfc5623221575074a2cead439 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 27 Nov 2024 16:06:31 -0600 Subject: [PATCH 047/149] hooks: missing newline EOF --- desk/sur/hooks.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 178ddcc980..3f2af394c8 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -175,4 +175,4 @@ [%wait waiting-hook] == :: --- \ No newline at end of file +-- From 0e4d0fb6e400883174209537b13b1d7abbce1c7c Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Sat, 30 Nov 2024 09:26:11 -0600 Subject: [PATCH 048/149] hooks: allow no-op'ing if none --- desk/app/channels-server.hoon | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index edf10cdfec..76f17703a5 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -686,7 +686,6 @@ |= =c-channel:c ^+ ca-core ?> am-host:ca-perms - ~& "received command {}" ?- -.c-channel %view ?> (is-admin:ca-perms src.bowl) @@ -725,7 +724,6 @@ %post =^ update=(unit u-channel:c) ca-core (ca-c-post c-post.c-channel) - ~& "received post update {}" ?~ update ca-core (ca-update u.update) == @@ -734,11 +732,9 @@ |= =c-post:c ^- [(unit u-channel:c) _ca-core] ?> (can-write:ca-perms src.bowl writers.perm.perm.channel) - ~& "running post command" =* no-op `ca-core ?- -.c-post %add - ~& "adding post" ?> |(=(src.bowl our.bowl) =(src.bowl author.essay.c-post)) ?> =(kind.nest -.kind-data.essay.c-post) =/ id=id-post:c @@ -749,7 +745,6 @@ =/ new=v-post:c [[id ~ ~] 0 essay.c-post] =^ result=(each event:h tang) cor =/ =event:h [%on-post %add new] - ~& "running post hooks" (run-hooks event nest 'post blocked') ?: ?=(%.n -.result) ((slog p.result) [~ ca-core]) @@ -1160,7 +1155,7 @@ [result (run-hook-effects effects nest)] =/ current-event event =| effects=(list effect:h) - =/ order (~(got by order.hooks) nest) + =/ order (~(gut by order.hooks) nest ~) =/ channel `[nest (~(got by v-channels) nest)] =/ =context:h (get-hook-context channel *config:h) |- From ba8f6940fdef0b2c6c1eea60d9ac893791afdc53 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 2 Dec 2024 11:10:07 -0600 Subject: [PATCH 049/149] hooks: cron renaming --- desk/app/channels-server.hoon | 60 +++++++++++++++++------------------ desk/lib/channel-utils.hoon | 4 +-- desk/lib/hooks-json.hoon | 24 +++++++------- desk/sur/hooks.hoon | 17 +++++----- 4 files changed, 53 insertions(+), 52 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 7b0a9c1c40..2dcb961045 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1002,9 +1002,9 @@ %+ welp /(scot %p our.bowl)/[dude]/(scot %da now.bowl) path -++ get-hook-context +++ get-hook-bowl |= [channel=(unit [nest:c v-channel:c]) =config:h] - ^- context:h + ^- bowl:h =/ group ?~ channel ~ =* flag group.perm.perm.+.u.channel @@ -1071,9 +1071,9 @@ =. gone & =. cor %+ roll - ~(tap by (~(gut by crons.hooks) id *(map origin:h cron:h))) - |= [[=origin:h =cron:h] cr=_cor] - (unschedule-cron:cr origin cron) + ~(tap by (~(gut by crons.hooks) id *cron:h)) + |= [[=origin:h =job:h] cr=_cor] + (unschedule-cron:cr origin job) =. crons.hooks (~(del by crons.hooks) id) =. order.hooks %+ roll @@ -1099,12 +1099,12 @@ =? schedule ?=(@ schedule) [now.bowl schedule] ?> ?=(^ schedule) - =/ crons (~(gut by crons.hooks) id *(map origin:h cron:h)) - =/ =cron:h [id schedule config] + =/ crons (~(gut by crons.hooks) id *cron:h) + =/ =job:h [id schedule config] =. crons.hooks =- (~(put by crons.hooks) id.hook -) - (~(put by crons) origin cron) - =. cor (schedule-cron origin cron) + (~(put by crons) origin job) + =. cor (schedule-cron origin job) (ho-give-response [%cron id origin schedule config]) ++ ho-rest |= =origin:h @@ -1121,9 +1121,9 @@ ?@ origin ~ ?~ ch=(~(get by v-channels) origin) ~ `[origin u.ch] - =/ =context:h (get-hook-context channel config) + =/ =bowl:h (get-hook-bowl channel config) =/ return=(unit return:h) - (run-hook:utils [event context(hook hook)] hook) + (run-hook:utils [event bowl(hook hook)] hook) ?~ return %- (slog (crip "{prefix} {} failed, running on {}") ~) ho-core @@ -1143,15 +1143,15 @@ =| effects=(list effect:h) =/ order (~(got by order.hooks) nest) =/ channel `[nest (~(got by v-channels) nest)] - =/ =context:h (get-hook-context channel *config:h) + =/ =bowl:h (get-hook-bowl channel *config:h) |- ?~ order [&+event effects] =* next $(order t.order) =/ hook (~(got by hooks.hooks) i.order) - =/ ctx context(hook hook, config (~(gut by config.hook) nest ~)) + =. bowl bowl(hook hook, config (~(gut by config.hook) nest ~)) =/ return=(unit return:h) - (run-hook:utils [event ctx] hook) + (run-hook:utils [event bowl] hook) ?~ return next =* result result.u.return =. effects (weld effects effects.u.return) @@ -1181,42 +1181,42 @@ =/ id=id-hook:h (slav %uv id.pole) =/ =origin:h [kind.pole (slav %p ship.pole) name.pole] :: if unscheduled, ignore - ?~ crons=(~(get by crons.hooks) id) cor - ?~ cron=(~(get by u.crons) origin) cor + ?~ cron=(~(get by crons.hooks) id) cor + ?~ job=(~(get by u.cron) origin) cor :: ignore premature fires - ?: (lth now.bowl next.schedule.u.cron) cor - =. next.schedule.u.cron + ?: (lth now.bowl next.schedule.u.job) cor + =. next.schedule.u.job :: :: we don't want to run the cron for every iteration it would :: have run 'offline', so we check here to make sure that the :: next fire time is in the future - =/ next (add [next repeat]:schedule.u.cron) + =/ next (add [next repeat]:schedule.u.job) |- ?: (gte next now.bowl) next - $(next (add next repeat.schedule.u.cron)) + $(next (add next repeat.schedule.u.job)) =. crons.hooks %+ ~(put by crons.hooks) id - (~(put by u.crons) origin u.cron) + (~(put by u.cron) origin u.job) =. cor - (schedule-cron origin u.cron) - =/ args [[%cron ~] "cron job" origin config.u.cron] - ho-abet:(ho-run-single:(ho-abed:ho-core id-hook.u.cron) args) + (schedule-cron origin u.job) + =/ args [[%cron ~] "cron job" origin config.u.job] + ho-abet:(ho-run-single:(ho-abed:ho-core id-hook.u.job) args) == ++ schedule-cron - |= [=origin:h =cron:h] + |= [=origin:h =job:h] ^+ cor =/ wire - %+ welp /hooks/cron/(scot %uv id-hook.cron) + %+ welp /hooks/cron/(scot %uv id-hook.job) ?@ origin ~ /[kind.origin]/(scot %p ship.origin)/[name.origin] - (emit [%pass wire %arvo %b %wait next.schedule.cron]) + (emit [%pass wire %arvo %b %wait next.schedule.job]) ++ unschedule-cron - |= [=origin:h =cron:h] + |= [=origin:h =job:h] =/ wire - %+ welp /hooks/cron/(scot %uv id-hook.cron) + %+ welp /hooks/cron/(scot %uv id-hook.job) ?@ origin ~ /[kind.origin]/(scot %p ship.origin)/[name.origin] - (emit [%pass wire %arvo %b %rest next.schedule.cron]) + (emit [%pass wire %arvo %b %rest next.schedule.job]) ++ schedule-waiting |= wh=waiting-hook:h =/ =wire /hooks/waiting/(scot %uv id.wh) diff --git a/desk/lib/channel-utils.hoon b/desk/lib/channel-utils.hoon index 7bdcc0987b..5d6b11c68d 100644 --- a/desk/lib/channel-utils.hoon +++ b/desk/lib/channel-utils.hoon @@ -694,8 +694,8 @@ |= [=args:h =hook:h] ^- (unit return:h) %- (slog (crip "running hook: {} {}") ~) - %- ?~ channel.context.args same - (slog (crip "on channel: {}") ~) + %- ?~ channel.bowl.args same + (slog (crip "on channel: {}") ~) ?~ compiled.hook ~ =/ gate [p.u.compiled.hook .*(q:subject q.u.compiled.hook)] =+ !<(=outcome:h (slam gate !>(args))) diff --git a/desk/lib/hooks-json.hoon b/desk/lib/hooks-json.hoon index 51868d233f..dabca9af5d 100644 --- a/desk/lib/hooks-json.hoon +++ b/desk/lib/hooks-json.hoon @@ -58,26 +58,26 @@ |= [=nest:c seq=(list id-hook:h)] [(nest-cord:enjs:cj nest) a+(turn seq id)] ++ crons - |= crs=(map id-hook:h (map origin:h cron:h)) + |= crs=(map id-hook:h cron:h) %- pairs %+ turn ~(tap by crs) - |= [id=id-hook:h cr=(map origin:h cron:h)] - [(scot %uv id) (cron-map cr)] - ++ cron-map - |= cr=(map origin:h cron:h) + |= [id=id-hook:h cr=cron:h] + [(scot %uv id) (cron cr)] + ++ cron + |= cr=cron:h %- pairs %+ turn ~(tap by cr) - |= [=origin:h crn=cron:h] - :_ (cron crn) + |= [=origin:h jb=job:h] + :_ (job jb) ?@(origin 'global' (nest-cord:enjs:cj origin)) - ++ cron - |= crn=cron:h + ++ job + |= jb=job:h %- pairs - :~ hook+(id id-hook.crn) - schedule+(schedule schedule.crn) - config+(config config.crn) + :~ hook+(id id-hook.jb) + schedule+(schedule schedule.jb) + config+(config config.jb) == ++ schedule |= sch=schedule:h diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 3f2af394c8..93a6aa3344 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -30,11 +30,12 @@ ++ hooks $: hooks=(map id-hook hook) order=(map nest (list id-hook)) - crons=(map id-hook (map origin cron)) + crons=(map id-hook cron) waiting=(map id-wait [=origin waiting-hook]) == +$ origin $@(~ nest) -+$ cron ++$ cron (map origin job) ++$ job $: =id-hook =schedule =config @@ -71,7 +72,7 @@ [%cron id=id-hook =origin schedule=$@(@dr schedule) =config] [%rest id=id-hook =origin] == -:: $context: ambient state that a hook should know about not +:: $bowl: ambient state that a hook should know about not :: necessarily tied to a specific event :: :: .channel: the channel that the hook is operating on @@ -84,7 +85,7 @@ :: .src: the ship that triggered the hook :: .eny: entropy for random number generation or key derivation :: -+$ context ++$ bowl $: channel=(unit [=nest v-channel]) group=(unit group-ui:g) channels=v-channels @@ -130,17 +131,17 @@ :: $args: the arguments passed to a hook +$ args $: =event - =context + =bowl == :: $outcome: the result of a hook running +$ outcome (each return tang) :: :: $return: the data returned from a hook :: -:: $result: whether the action was allowed or denied and any +:: .result: whether the action was allowed or denied and any :: transformed values -:: $effects: any actions that should be taken on other agents or wait -:: $new-state: the new state of the hook after running +:: .effects: any actions that should be taken on other agents or wait +:: .new-state: the new state of the hook after running :: +$ return $: $: result=event-result From 7c215f1dd7108151a25cbe109875d7e53ebdda0a Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 2 Dec 2024 11:13:29 -0600 Subject: [PATCH 050/149] channels-server: removing errant sigpam --- desk/app/channels-server.hoon | 1 - 1 file changed, 1 deletion(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 2dcb961045..8f12886f47 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -686,7 +686,6 @@ |= =c-channel:c ^+ ca-core ?> am-host:ca-perms - ~& "received command {}" ?- -.c-channel %view ?> (is-admin:ca-perms src.bowl) From d3a83f1e37d4537b54e93e24304bec9d936ce1a7 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 2 Dec 2024 11:16:13 -0600 Subject: [PATCH 051/149] hooks: little more cron naming cleanup --- desk/app/channels-server.hoon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 8f12886f47..154f855d7b 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1098,11 +1098,11 @@ =? schedule ?=(@ schedule) [now.bowl schedule] ?> ?=(^ schedule) - =/ crons (~(gut by crons.hooks) id *cron:h) + =/ =cron:h (~(gut by crons.hooks) id *cron:h) =/ =job:h [id schedule config] =. crons.hooks =- (~(put by crons.hooks) id.hook -) - (~(put by crons) origin job) + (~(put by cron) origin job) =. cor (schedule-cron origin job) (ho-give-response [%cron id origin schedule config]) ++ ho-rest From 6f8139724cad518cf870228ac1c12ea63f6f55b4 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 2 Dec 2024 14:00:31 -0600 Subject: [PATCH 052/149] desktop: fix big input, and other desktop web issues --- packages/ui/src/components/AuthorRow.tsx | 2 +- packages/ui/src/components/BigInput.tsx | 41 ++---- packages/ui/src/components/Channel/index.tsx | 12 +- .../ChatMessageActions/MessageContainer.tsx | 2 +- packages/ui/src/components/Image.tsx | 5 +- .../MessageInput/MessageInputBase.tsx | 1 + .../ui/src/components/MessageInput/index.tsx | 92 ++++++++----- .../components/NotebookPost/NotebookPost.tsx | 126 ++++++++++-------- packages/ui/src/components/PostScreenView.tsx | 1 - 9 files changed, 159 insertions(+), 123 deletions(-) diff --git a/packages/ui/src/components/AuthorRow.tsx b/packages/ui/src/components/AuthorRow.tsx index 2f6836d14e..04944762d3 100644 --- a/packages/ui/src/components/AuthorRow.tsx +++ b/packages/ui/src/components/AuthorRow.tsx @@ -160,7 +160,7 @@ export function ChatAuthorRow({ ) : null} - {deliveryStatus && deliveryStatus !== 'failed' ? ( + {!!deliveryStatus && deliveryStatus !== 'failed' ? ( ) : null} diff --git a/packages/ui/src/components/BigInput.tsx b/packages/ui/src/components/BigInput.tsx index 3a3b58a498..7f736e9eb0 100644 --- a/packages/ui/src/components/BigInput.tsx +++ b/packages/ui/src/components/BigInput.tsx @@ -1,11 +1,9 @@ // import { EditorBridge } from '@10play/tentap-editor'; import * as db from '@tloncorp/shared/db'; -import { useMemo, useRef, useState } from 'react'; -import { Dimensions, KeyboardAvoidingView, Platform } from 'react-native'; +import { useMemo, useState } from 'react'; import { TouchableOpacity } from 'react-native-gesture-handler'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; // TODO: replace input with our own input component -import { Input, ScrollView, View, YStack, getTokenValue } from 'tamagui'; +import { Input, View, YStack, getTokenValue } from 'tamagui'; import { ImageAttachment, useAttachmentContext } from '../contexts/attachment'; import AttachmentSheet from './AttachmentSheet'; @@ -37,16 +35,8 @@ export function BigInput({ } & MessageInputProps) { const [title, setTitle] = useState(editingPost?.title ?? ''); const [showAttachmentSheet, setShowAttachmentSheet] = useState(false); - // const editorRef = useRef<{ - // editor: TlonEditorBridge | null; - // setEditor: (editor: any) => void; - // }>(null); - const { top } = useSafeAreaInsets(); - const { width } = Dimensions.get('screen'); const titleInputHeight = getTokenValue('$4xl', 'size'); const imageButtonHeight = getTokenValue('$4xl', 'size'); - const keyboardVerticalOffset = - Platform.OS === 'ios' ? top + titleInputHeight : top; const { attachments, attachAssets } = useAttachmentContext(); const imageAttachment = useMemo(() => { @@ -90,11 +80,13 @@ export function BigInput({ > {imageAttachment ? ( )} - - + {/* channelType === 'notebook' && editorRef.current && editorRef.current.editor && ( diff --git a/packages/ui/src/components/Channel/index.tsx b/packages/ui/src/components/Channel/index.tsx index 6eb23dd62d..ebc496fb47 100644 --- a/packages/ui/src/components/Channel/index.tsx +++ b/packages/ui/src/components/Channel/index.tsx @@ -316,10 +316,7 @@ export function Channel({ initialAttachments={initialAttachments} uploadAsset={uploadAsset} > - + - + ); } diff --git a/packages/ui/src/components/Image.tsx b/packages/ui/src/components/Image.tsx index 19f579229a..9b707de86c 100644 --- a/packages/ui/src/components/Image.tsx +++ b/packages/ui/src/components/Image.tsx @@ -16,6 +16,8 @@ const WebImage = ({ source, style, alt, onLoad, ...props }: any) => { } }; + const { contentFit } = props; + return ( { style={{ ...StyleSheet.flatten(style), maxWidth: '100%', - height: 'auto', + height: props.height ? props.height : 'auto', + objectFit: contentFit ? contentFit : 'contain', }} onLoad={handleLoad} {...props} diff --git a/packages/ui/src/components/MessageInput/MessageInputBase.tsx b/packages/ui/src/components/MessageInput/MessageInputBase.tsx index b636830e70..ba41c8ac81 100644 --- a/packages/ui/src/components/MessageInput/MessageInputBase.tsx +++ b/packages/ui/src/components/MessageInput/MessageInputBase.tsx @@ -132,6 +132,7 @@ export const MessageInputContainer = memo( gap="$l" alignItems="flex-end" justifyContent="space-between" + backgroundColor="$background" > {goBack ? ( diff --git a/packages/ui/src/components/MessageInput/index.tsx b/packages/ui/src/components/MessageInput/index.tsx index 523c41d1cf..e2cda27cfe 100644 --- a/packages/ui/src/components/MessageInput/index.tsx +++ b/packages/ui/src/components/MessageInput/index.tsx @@ -7,7 +7,6 @@ import HardBreak from '@tiptap/extension-hard-break'; import History from '@tiptap/extension-history'; import Italic from '@tiptap/extension-italic'; import Link from '@tiptap/extension-link'; -import Mention from '@tiptap/extension-mention'; import Paragraph from '@tiptap/extension-paragraph'; import Placeholder from '@tiptap/extension-placeholder'; import Strike from '@tiptap/extension-strike'; @@ -26,9 +25,7 @@ import { import * as db from '@tloncorp/shared/db'; import { Block, - Image, Inline, - JSONContent, Story, citeToPath, constructStory, @@ -37,14 +34,14 @@ import { } from '@tloncorp/shared/urbit'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Keyboard } from 'react-native'; -import { View, YStack } from 'tamagui'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { View, YStack, getTokenValue, useWindowDimensions } from 'tamagui'; import { Attachment, UploadedImageAttachment, useAttachmentContext, } from '../../contexts/attachment'; -import { Input } from '../Input'; import { AttachmentPreviewList } from './AttachmentPreviewList'; import { MessageInputContainer, MessageInputProps } from './MessageInputBase'; @@ -88,15 +85,28 @@ export function MessageInput({ waitForAttachmentUploads, } = useAttachmentContext(); - const [containerHeight, setContainerHeight] = useState(initialHeight); const [isSending, setIsSending] = useState(false); const [sendError, setSendError] = useState(false); const [hasSetInitialContent, setHasSetInitialContent] = useState(false); - const [imageOnEditedPost, setImageOnEditedPost] = useState(); const [editorIsEmpty, setEditorIsEmpty] = useState(attachments.length === 0); const [showMentionPopup, setShowMentionPopup] = useState(false); const [mentionText, setMentionText] = useState(); + const { bottom, top } = useSafeAreaInsets(); + const { height } = useWindowDimensions(); + const headerHeight = 48; + const titleInputHeight = 48; + const inputBasePadding = getTokenValue('$s', 'space'); + const imageInputButtonHeight = 50; + const basicOffset = useMemo( + () => top + headerHeight + titleInputHeight + imageInputButtonHeight, + [top, headerHeight, titleInputHeight, imageInputButtonHeight] + ); + const bigInputHeightBasic = useMemo( + () => height - basicOffset - bottom - inputBasePadding * 2, + [height, basicOffset, bottom, inputBasePadding] + ); + const extensions = [ Blockquote, Bold, @@ -159,12 +169,7 @@ export function MessageInput({ blocks, } = extractContentTypesFromPost(editingPost); - if ( - !story || - story?.length === 0 || - !postReferences || - blocks.length === 0 - ) { + if (story === null && !postReferences && blocks.length === 0) { return; } @@ -195,12 +200,27 @@ export function MessageInput({ resetAttachments(attachments); + if (story === null) { + return; + } + const tiptapContent = tiptap.diaryMixedToJSON( story.filter( (c) => !('type' in c) && !('block' in c && 'image' in c.block) ) as Story ); editor.commands.setContent(tiptapContent); + + if (editingPost.image) { + addAttachment({ + type: 'image', + file: { + uri: editingPost.image, + height: 0, + width: 0, + }, + }); + } } }); } catch (e) { @@ -209,7 +229,14 @@ export function MessageInput({ setHasSetInitialContent(true); } } - }, [editor, getDraft, hasSetInitialContent, editingPost, resetAttachments]); + }, [ + editor, + getDraft, + hasSetInitialContent, + editingPost, + resetAttachments, + addAttachment, + ]); useEffect(() => { if (editor && shouldBlur && editor.isFocused) { @@ -451,17 +478,6 @@ export function MessageInput({ return []; }); - if (imageOnEditedPost) { - blocks.push({ - image: { - src: imageOnEditedPost.image.src, - height: imageOnEditedPost.image.height, - width: imageOnEditedPost.image.width, - alt: imageOnEditedPost.image.alt, - }, - }); - } - if (blocks && blocks.length > 0) { if (channelType === 'chat') { story.unshift(...blocks.map((block) => ({ block }))); @@ -470,11 +486,27 @@ export function MessageInput({ } } + const metadata: db.PostMetadata = {}; + if (title && title.length > 0) { + metadata['title'] = title; + } + + if (image) { + const attachment = finalAttachments.find( + (a): a is UploadedImageAttachment => + a.type === 'image' && a.file.uri === image.uri + ); + if (!attachment) { + throw new Error('unable to attach image'); + } + metadata['image'] = attachment.uploadState.remoteUri; + } + if (isEdit && editingPost) { if (editingPost.parentId) { - await editPost?.(editingPost, story, editingPost.parentId); + await editPost?.(editingPost, story, editingPost.parentId, metadata); } - await editPost?.(editingPost, story); + await editPost?.(editingPost, story, undefined, metadata); setEditingPost?.(undefined); } else { const metadata: db.PostMetadata = {}; @@ -503,14 +535,12 @@ export function MessageInput({ clearAttachments(); clearDraft(); setShowBigInput?.(false); - setImageOnEditedPost(null); }, [ json, onSend, editor, waitForAttachmentUploads, - imageOnEditedPost, editingPost, clearAttachments, clearDraft, @@ -560,7 +590,7 @@ export function MessageInput({ setShouldBlur={setShouldBlur} onPressSend={handleSend} onPressEdit={handleEdit} - containerHeight={containerHeight} + containerHeight={initialHeight} mentionText={mentionText} groupMembers={groupMembers} onSelectMention={onSelectMention} @@ -585,7 +615,7 @@ export function MessageInput({ borderRadius="$xl" > {showInlineAttachments && } - + void; @@ -48,6 +53,7 @@ export function NotebookPost({ viewMode?: 'activity'; isHighlighted?: boolean; size?: '$l' | '$s' | '$xs'; + hideOverflowMenu?: boolean; }) { const [showRetrySheet, setShowRetrySheet] = useState(false); const handleLongPress = useCallback(() => { @@ -89,69 +95,77 @@ export function NotebookPost({ const hasReplies = post.replyCount && post.replyTime && post.replyContactIds; return ( <> - - {post.hidden ? ( - - - You have hidden this post. - - - ) : ( - <> - - - {viewMode !== 'activity' && ( - - {post.textContent} + + {post.hidden ? ( + + + You have hidden this post. - )} - - {showReplies && hasReplies ? ( - + ) : ( + <> + - ) : null} - - )} - {post.deliveryStatus === 'failed' ? ( - - - Message failed to send - - - ) : null} - - + {viewMode !== 'activity' && ( + + {post.textContent} + + )} + + {showReplies && hasReplies ? ( + + ) : null} + + )} + + {post.deliveryStatus === 'failed' ? ( + + + Message failed to send + + + ) : null} + + + + {isWeb && !hideOverflowMenu && ( + + + + )} ); } @@ -174,7 +188,7 @@ function NotebookPostHeader({ return ( - {post.image && size !== '$xs' && ( + {!!post.image && size !== '$xs' && ( { if (isEditingParent) { - console.log('setEditingPost', undefined); setEditingPost?.(undefined); if (channel.type !== 'notebook') { goBack?.(); From ac1f90aeec606749b80772677b1e45bf7702c8a5 Mon Sep 17 00:00:00 2001 From: James Acklin Date: Mon, 2 Dec 2024 15:07:55 -0500 Subject: [PATCH 053/149] ProfileStatusSheet: add send button --- .../ui/src/components/ProfileStatusSheet.tsx | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/packages/ui/src/components/ProfileStatusSheet.tsx b/packages/ui/src/components/ProfileStatusSheet.tsx index c7983b6c80..3c3ea79c03 100644 --- a/packages/ui/src/components/ProfileStatusSheet.tsx +++ b/packages/ui/src/components/ProfileStatusSheet.tsx @@ -1,11 +1,13 @@ import { useCallback, useRef } from 'react'; import { useForm } from 'react-hook-form'; import { Keyboard } from 'react-native'; -import { YStack } from 'tamagui'; +import { XStack, YStack } from 'tamagui'; import { useContact, useCurrentUserId } from '../contexts'; import { ActionSheet } from './ActionSheet'; +import { Button } from './Button'; import { ControlledTextField } from './Form'; +import { Icon } from './Icon'; export default function ProfileStatusSheet({ open, @@ -57,27 +59,38 @@ export default function ProfileStatusSheet({ snapPoints={[60]} > - - + + + + + From 9fd7c11d7478a597ad264bbe1ff7d39ab62ad965 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 2 Dec 2024 15:34:52 -0600 Subject: [PATCH 054/149] desktop: add overflow menu button to gallery posts, default web image height to 100% rather than auto --- .../components/GalleryPost/GalleryPost.tsx | 103 ++++++++++-------- packages/ui/src/components/Image.tsx | 4 +- .../components/NotebookPost/NotebookPost.tsx | 2 +- 3 files changed, 63 insertions(+), 46 deletions(-) diff --git a/packages/ui/src/components/GalleryPost/GalleryPost.tsx b/packages/ui/src/components/GalleryPost/GalleryPost.tsx index e6e952c639..d96fa59c2c 100644 --- a/packages/ui/src/components/GalleryPost/GalleryPost.tsx +++ b/packages/ui/src/components/GalleryPost/GalleryPost.tsx @@ -3,10 +3,11 @@ import * as db from '@tloncorp/shared/db'; import { truncate } from 'lodash'; import { ComponentProps, useCallback, useMemo, useState } from 'react'; import { PropsWithChildren } from 'react'; -import { View, XStack, styled } from 'tamagui'; +import { View, XStack, isWeb, styled } from 'tamagui'; import { DetailViewAuthorRow } from '../AuthorRow'; import { ContactAvatar } from '../Avatar'; +import { Button } from '../Button'; import { Icon } from '../Icon'; import { useBoundHandler } from '../ListItem/listItemUtils'; import { createContentRenderer } from '../PostContent/ContentRenderer'; @@ -35,6 +36,7 @@ export function GalleryPost({ onPressRetry, onPressDelete, showAuthor = true, + hideOverflowMenu, ...props }: { post: db.Post; @@ -44,6 +46,7 @@ export function GalleryPost({ onPressDelete?: (post: db.Post) => void; showAuthor?: boolean; isHighlighted?: boolean; + hideOverflowMenu?: boolean; } & Omit, 'onPress' | 'onLongPress'>) { const [showRetrySheet, setShowRetrySheet] = useState(false); @@ -77,48 +80,62 @@ export function GalleryPost({ } return ( - - - - {showAuthor && !post.hidden && !post.isDeleted && ( - + + + + {showAuthor && !post.hidden && !post.isDeleted && ( + + + + {deliveryFailed && ( + + Tap to retry + + )} + + + )} + + + + {isWeb && !hideOverflowMenu && ( + + + + )} + ); } @@ -338,7 +355,7 @@ const SmallContentRenderer = createContentRenderer({ }, image: { height: '100%', - imageProps: { aspectRatio: 'unset', height: '100%' }, + imageProps: { aspectRatio: 'unset', height: '100%', contentFit: 'cover' }, ...noWrapperPadding, }, video: { diff --git a/packages/ui/src/components/Image.tsx b/packages/ui/src/components/Image.tsx index 9b707de86c..bd72d302e3 100644 --- a/packages/ui/src/components/Image.tsx +++ b/packages/ui/src/components/Image.tsx @@ -25,8 +25,8 @@ const WebImage = ({ source, style, alt, onLoad, ...props }: any) => { style={{ ...StyleSheet.flatten(style), maxWidth: '100%', - height: props.height ? props.height : 'auto', - objectFit: contentFit ? contentFit : 'contain', + height: props.height ? props.height : '100%', + objectFit: contentFit ? contentFit : undefined, }} onLoad={handleLoad} {...props} diff --git a/packages/ui/src/components/NotebookPost/NotebookPost.tsx b/packages/ui/src/components/NotebookPost/NotebookPost.tsx index eeafdb998b..3235adc90a 100644 --- a/packages/ui/src/components/NotebookPost/NotebookPost.tsx +++ b/packages/ui/src/components/NotebookPost/NotebookPost.tsx @@ -8,8 +8,8 @@ import { YStack, createStyledContext, styled, + isWeb, } from 'tamagui'; -import { isWeb } from 'tamagui'; import { DetailViewAuthorRow } from '../AuthorRow'; import { Button } from '../Button'; From de72f3217b2e75ceb6bc904e00d608f8b3d9b606 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 3 Dec 2024 07:00:46 -0600 Subject: [PATCH 055/149] make new OverflowMenuButton component, move it inside the frames, disable Pressable while we're hovering over the overflow button --- .../components/ChatMessage/ChatMessage.tsx | 15 +- .../components/GalleryPost/GalleryPost.tsx | 124 +++++++------- .../components/NotebookPost/NotebookPost.tsx | 154 ++++++++++-------- .../ui/src/components/OverflowMenuButton.tsx | 38 +++++ 4 files changed, 195 insertions(+), 136 deletions(-) create mode 100644 packages/ui/src/components/OverflowMenuButton.tsx diff --git a/packages/ui/src/components/ChatMessage/ChatMessage.tsx b/packages/ui/src/components/ChatMessage/ChatMessage.tsx index ec606de16c..4b9133b167 100644 --- a/packages/ui/src/components/ChatMessage/ChatMessage.tsx +++ b/packages/ui/src/components/ChatMessage/ChatMessage.tsx @@ -4,8 +4,8 @@ import { ComponentProps, memo, useCallback, useMemo, useState } from 'react'; import { View, XStack, YStack, isWeb } from 'tamagui'; import AuthorRow from '../AuthorRow'; -import { Button } from '../Button'; import { Icon } from '../Icon'; +import { OverflowMenuButton } from '../OverflowMenuButton'; import { createContentRenderer } from '../PostContent/ContentRenderer'; import { usePostContent, @@ -196,12 +196,13 @@ const ChatMessage = ({ onPressDelete={handleDeletePressed} /> - {isWeb && !hideOverflowMenu && showOverflowOnHover && ( - - - + {!hideOverflowMenu && showOverflowOnHover && ( + )} ); diff --git a/packages/ui/src/components/GalleryPost/GalleryPost.tsx b/packages/ui/src/components/GalleryPost/GalleryPost.tsx index d96fa59c2c..d77d0012b6 100644 --- a/packages/ui/src/components/GalleryPost/GalleryPost.tsx +++ b/packages/ui/src/components/GalleryPost/GalleryPost.tsx @@ -3,13 +3,13 @@ import * as db from '@tloncorp/shared/db'; import { truncate } from 'lodash'; import { ComponentProps, useCallback, useMemo, useState } from 'react'; import { PropsWithChildren } from 'react'; -import { View, XStack, isWeb, styled } from 'tamagui'; +import { View, XStack, styled } from 'tamagui'; import { DetailViewAuthorRow } from '../AuthorRow'; import { ContactAvatar } from '../Avatar'; -import { Button } from '../Button'; import { Icon } from '../Icon'; import { useBoundHandler } from '../ListItem/listItemUtils'; +import { OverflowMenuButton } from '../OverflowMenuButton'; import { createContentRenderer } from '../PostContent/ContentRenderer'; import { BlockData, @@ -49,6 +49,7 @@ export function GalleryPost({ hideOverflowMenu?: boolean; } & Omit, 'onPress' | 'onLongPress'>) { const [showRetrySheet, setShowRetrySheet] = useState(false); + const [disableHandlePress, setDisableHandlePress] = useState(false); const handleRetryPressed = useCallback(() => { onPressRetry?.(post); @@ -75,67 +76,76 @@ export function GalleryPost({ const handleLongPress = useBoundHandler(post, onLongPress); + const onPressOverflow = useCallback(() => { + handleLongPress(); + }, [handleLongPress]); + + const onHoverIntoOverflow = useCallback(() => { + setDisableHandlePress(true); + }, []); + + const onHoverOutOfOverflow = useCallback(() => { + setDisableHandlePress(false); + }, []); + if (post.isDeleted) { return null; } return ( - <> - - - - {showAuthor && !post.hidden && !post.isDeleted && ( - - - - {deliveryFailed && ( - - Tap to retry - - )} - - - )} - - - - {isWeb && !hideOverflowMenu && ( - - - - )} - + + + {deliveryFailed && ( + + Tap to retry + + )} + + + )} + + {!hideOverflowMenu && ( + + )} + + ); } diff --git a/packages/ui/src/components/NotebookPost/NotebookPost.tsx b/packages/ui/src/components/NotebookPost/NotebookPost.tsx index 3235adc90a..c2f1c948f9 100644 --- a/packages/ui/src/components/NotebookPost/NotebookPost.tsx +++ b/packages/ui/src/components/NotebookPost/NotebookPost.tsx @@ -8,14 +8,12 @@ import { YStack, createStyledContext, styled, - isWeb, } from 'tamagui'; import { DetailViewAuthorRow } from '../AuthorRow'; -import { Button } from '../Button'; import { ChatMessageReplySummary } from '../ChatMessage/ChatMessageReplySummary'; -import { Icon } from '../Icon'; import { Image } from '../Image'; +import { OverflowMenuButton } from '../OverflowMenuButton'; import { createContentRenderer } from '../PostContent/ContentRenderer'; import { usePostContent, @@ -56,6 +54,8 @@ export function NotebookPost({ hideOverflowMenu?: boolean; }) { const [showRetrySheet, setShowRetrySheet] = useState(false); + const [disableHandlePress, setDisableHandlePress] = useState(false); + const handleLongPress = useCallback(() => { onLongPress?.(post); }, [post, onLongPress]); @@ -88,85 +88,95 @@ export function NotebookPost({ onPress?.(post); }, [post, onPress, deliveryFailed]); + const onPressOverflow = useCallback(() => { + handleLongPress(); + }, [handleLongPress]); + + const onHoverIntoOverflow = useCallback(() => { + setDisableHandlePress(true); + }, []); + + const onHoverOutOfOverflow = useCallback(() => { + setDisableHandlePress(false); + }, []); + if (!post || post.isDeleted) { return null; } const hasReplies = post.replyCount && post.replyTime && post.replyContactIds; return ( - <> - - - {post.hidden ? ( - - - You have hidden this post. + + + {post.hidden ? ( + + + You have hidden this post. + + + ) : ( + <> + + + {viewMode !== 'activity' && ( + + {post.textContent} - - ) : ( - <> - + ) : null} + + )} - {viewMode !== 'activity' && ( - - {post.textContent} - - )} - - {showReplies && hasReplies ? ( - - ) : null} - - )} - - {post.deliveryStatus === 'failed' ? ( - - - Message failed to send - - - ) : null} - - - - {isWeb && !hideOverflowMenu && ( - - - - )} - + {post.deliveryStatus === 'failed' ? ( + + + Message failed to send + + + ) : null} + {!hideOverflowMenu && ( + + )} + + + ); } diff --git a/packages/ui/src/components/OverflowMenuButton.tsx b/packages/ui/src/components/OverflowMenuButton.tsx new file mode 100644 index 0000000000..34910c171c --- /dev/null +++ b/packages/ui/src/components/OverflowMenuButton.tsx @@ -0,0 +1,38 @@ +import { ComponentProps } from 'react'; +import { View, isWeb } from 'tamagui'; + +import { Button } from './Button'; +import { Icon } from './Icon'; + +export function OverflowMenuButton({ + onPress, + backgroundColor, + ...viewProps +}: { + onPress: () => void; + backgroundColor?: string; +} & ComponentProps) { + if (!isWeb) { + return null; + } + + return ( + + + + ); +} From 1e652557cd358eb7a9bd6c988e850c21b0f6f915 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 3 Dec 2024 13:50:07 +0000 Subject: [PATCH 056/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 77e9d58704..60af1a7c96 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.ar89l.ohn8c.72n4o.krb3l.hb512.glob' 0v6.ar89l.ohn8c.72n4o.krb3l.hb512] + glob-http+['https://bootstrap.urbit.org/glob-0v1.267eb.hn3gf.ludgo.1kct0.eas77.glob' 0v1.267eb.hn3gf.ludgo.1kct0.eas77] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 2ca7a124f6b038ba0e239639e9b76579f797b308 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Tue, 3 Dec 2024 08:43:03 -0600 Subject: [PATCH 057/149] use token padding --- packages/ui/src/components/ProfileStatusSheet.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/ProfileStatusSheet.tsx b/packages/ui/src/components/ProfileStatusSheet.tsx index 3c3ea79c03..1b0198c2bb 100644 --- a/packages/ui/src/components/ProfileStatusSheet.tsx +++ b/packages/ui/src/components/ProfileStatusSheet.tsx @@ -86,7 +86,7 @@ export default function ProfileStatusSheet({ hero onPress={handleSave} disabled={!isValid} - padding={11.5} + paddingHorizontal="$l" > From c722f56ea58c929accb5cb8ac64eaa51c0c375f5 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Tue, 3 Dec 2024 09:11:13 -0600 Subject: [PATCH 058/149] use contacts in contact book, display @p for contacts with nickname --- packages/ui/src/components/ContactBook.tsx | 10 +++++++++- packages/ui/src/components/ContactRow.tsx | 7 ++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/ContactBook.tsx b/packages/ui/src/components/ContactBook.tsx index fb97444b0d..047a2b485f 100644 --- a/packages/ui/src/components/ContactBook.tsx +++ b/packages/ui/src/components/ContactBook.tsx @@ -48,10 +48,18 @@ export function ContactBook({ width?: number; }) { const contacts = useContacts(); + const contactsForBook = useMemo(() => { + const markedContacts = + contacts?.filter((contact) => contact.isContact) ?? []; + if (markedContacts.length) { + return markedContacts; + } + return contacts; + }, [contacts]); const immutableSet = useMemo(() => new Set(immutableIds), [immutableIds]); const contactsIndex = useContactIndex(); const segmentedContacts = useAlphabeticallySegmentedContacts( - contacts ?? [], + contactsForBook ?? [], contactsIndex ?? {} ); diff --git a/packages/ui/src/components/ContactRow.tsx b/packages/ui/src/components/ContactRow.tsx index 75e7d064ec..86f0b7d625 100644 --- a/packages/ui/src/components/ContactRow.tsx +++ b/packages/ui/src/components/ContactRow.tsx @@ -46,9 +46,10 @@ function ContactRowItemRaw({ - - {displayName} - + {displayName} + {contact?.nickname && ( + {contact.id} + )} {selectable && ( From d80dc001bb48c6394704052b3c2ede69bc841c6e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 3 Dec 2024 15:18:58 +0000 Subject: [PATCH 059/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 60af1a7c96..8212f9923d 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v1.267eb.hn3gf.ludgo.1kct0.eas77.glob' 0v1.267eb.hn3gf.ludgo.1kct0.eas77] + glob-http+['https://bootstrap.urbit.org/glob-0v4.rimg2.i6af8.kvff9.flpan.vt06t.glob' 0v4.rimg2.i6af8.kvff9.flpan.vt06t] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 73d91e2eb77faf10d307a1aa38cb5bcc6f6f318a Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Tue, 3 Dec 2024 12:15:44 -0500 Subject: [PATCH 060/149] flatten single channel nav (#4233) * rough flattened nav * fix thread crash * omit use of skiptoken * only flatten single-channel groups * fix build * fix android crash --- .../src/components/AuthenticatedApp.tsx | 10 +- .../src/hooks/useNotificationListener.ts | 18 +-- .../channels/ChannelMembersScreen.tsx | 2 +- packages/app/features/top/ChannelScreen.tsx | 12 -- .../app/features/top/ChannelSearchScreen.tsx | 4 +- packages/app/features/top/ChatListScreen.tsx | 65 ++-------- .../app/features/top/GroupChannelsScreen.tsx | 13 +- packages/app/features/top/PostScreen.tsx | 67 ++++++----- packages/app/hooks/useChannelNavigation.ts | 3 +- packages/app/hooks/useGroupActions.tsx | 6 +- packages/app/hooks/useGroupNavigation.ts | 4 +- packages/app/navigation/types.ts | 7 +- packages/app/navigation/utils.ts | 44 ++++++- packages/shared/src/db/keyValue.ts | 27 ++--- packages/shared/src/db/queries.ts | 10 +- packages/shared/src/store/dbHooks.ts | 43 +++---- .../shared/src/store/useChannelContext.ts | 6 +- .../src/components/Channel/BaubleHeader.tsx | 18 +-- .../src/components/Channel/ChannelHeader.tsx | 20 ++-- packages/ui/src/components/Channel/index.tsx | 1 + .../src/components/ChannelSwitcherSheet.tsx | 38 +++--- packages/ui/src/components/ChatList.tsx | 18 ++- .../ui/src/components/ChatOptionsSheet.tsx | 71 +++-------- .../components/GroupChannelsScreenView.tsx | 40 ++----- packages/ui/src/components/Pressable.tsx | 2 +- packages/ui/src/contexts/chatOptions.tsx | 111 +++++++++++++----- packages/ui/src/utils/channelUtils.tsx | 2 +- 27 files changed, 309 insertions(+), 353 deletions(-) diff --git a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx index c60cab2bd4..8bf201dffb 100644 --- a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx +++ b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx @@ -9,7 +9,7 @@ import { useUpdatePresentedNotifications } from '@tloncorp/app/lib/notifications import { RootStack } from '@tloncorp/app/navigation/RootStack'; import { AppDataProvider } from '@tloncorp/app/provider/AppDataProvider'; import { sync } from '@tloncorp/shared'; -import { ZStack } from '@tloncorp/ui'; +import { PortalProvider, ZStack } from '@tloncorp/ui'; import { useCallback, useEffect } from 'react'; import { AppStateStatus } from 'react-native'; @@ -58,7 +58,13 @@ function AuthenticatedApp() { export default function ConnectedAuthenticatedApp() { return ( - + {/* + This portal provider overrides the root portal provider + to ensure that sheets have access to `AppDataContext` + */} + + + ); } diff --git a/apps/tlon-mobile/src/hooks/useNotificationListener.ts b/apps/tlon-mobile/src/hooks/useNotificationListener.ts index 5f0af562e9..4b49dc5a57 100644 --- a/apps/tlon-mobile/src/hooks/useNotificationListener.ts +++ b/apps/tlon-mobile/src/hooks/useNotificationListener.ts @@ -6,6 +6,7 @@ import { connectNotifications } from '@tloncorp/app/lib/notifications'; import { RootStackParamList } from '@tloncorp/app/navigation/types'; import { createTypedReset, + getMainGroupRoute, screenNameFromChannelId, } from '@tloncorp/app/navigation/utils'; import * as posthog from '@tloncorp/app/utils/posthog'; @@ -154,17 +155,18 @@ export default function useNotificationListener() { } const routeStack: RouteStack = [{ name: 'ChatList' }]; - if (channel.groupId && !channelSwitcherEnabled) { + if (channel.groupId) { + const mainGroupRoute = await getMainGroupRoute(channel.groupId); + routeStack.push(mainGroupRoute); + } + // Only push the channel if it wasn't already handled by the main group stack + if (routeStack[routeStack.length - 1].name !== 'Channel') { + const screenName = screenNameFromChannelId(channelId); routeStack.push({ - name: 'GroupChannels', - params: { groupId: channel.groupId }, + name: screenName, + params: { channelId: channel.id }, }); } - const screenName = screenNameFromChannelId(channelId); - routeStack.push({ - name: screenName, - params: { channelId: channel.id }, - }); // if we have a post id, try to navigate to the thread if (postInfo) { diff --git a/packages/app/features/channels/ChannelMembersScreen.tsx b/packages/app/features/channels/ChannelMembersScreen.tsx index baef4a6fbd..7910155d57 100644 --- a/packages/app/features/channels/ChannelMembersScreen.tsx +++ b/packages/app/features/channels/ChannelMembersScreen.tsx @@ -8,7 +8,7 @@ type Props = NativeStackScreenProps; export function ChannelMembersScreen(props: Props) { const { channelId } = props.route.params; - const channelQuery = store.useChannelWithRelations({ + const channelQuery = store.useChannel({ id: channelId, }); diff --git a/packages/app/features/top/ChannelScreen.tsx b/packages/app/features/top/ChannelScreen.tsx index c5e49946b7..148237ba04 100644 --- a/packages/app/features/top/ChannelScreen.tsx +++ b/packages/app/features/top/ChannelScreen.tsx @@ -319,16 +319,6 @@ export default function ChannelScreen(props: Props) { const canUpload = useCanUpload(); - const isFocused = useIsFocused(); - - const { data: pins } = store.usePins({ - enabled: isFocused, - }); - - const pinnedItems = useMemo(() => { - return pins ?? []; - }, [pins]); - const chatOptionsNavProps = useChatSettingsNavigation(); const handleGoToUserProfile = useCallback( @@ -350,8 +340,6 @@ export default function ChannelScreen(props: Props) { return ( { setInviteSheetGroup(group); diff --git a/packages/app/features/top/ChannelSearchScreen.tsx b/packages/app/features/top/ChannelSearchScreen.tsx index 32c2bf4d1b..3e8d89ce52 100644 --- a/packages/app/features/top/ChannelSearchScreen.tsx +++ b/packages/app/features/top/ChannelSearchScreen.tsx @@ -1,5 +1,5 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; -import { useChannelSearch, useChannelWithRelations } from '@tloncorp/shared'; +import { useChannel, useChannelSearch } from '@tloncorp/shared'; import type * as db from '@tloncorp/shared/db'; import { Button, SearchBar, SearchResults, XStack, YStack } from '@tloncorp/ui'; import { useCallback, useState } from 'react'; @@ -13,7 +13,7 @@ type Props = NativeStackScreenProps; export default function ChannelSearchScreen(props: Props) { const channelId = props.route.params.channelId; const groupId = props.route.params.groupId; - const channelQuery = useChannelWithRelations({ + const channelQuery = useChannel({ id: channelId, }); diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx index de48a4cca7..dabd2a5873 100644 --- a/packages/app/features/top/ChatListScreen.tsx +++ b/packages/app/features/top/ChatListScreen.tsx @@ -6,14 +6,11 @@ import { import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { createDevLogger } from '@tloncorp/shared'; import * as db from '@tloncorp/shared/db'; -import * as logic from '@tloncorp/shared/logic'; import * as store from '@tloncorp/shared/store'; import { AddGroupSheet, ChatList, ChatOptionsProvider, - ChatOptionsSheet, - ChatOptionsSheetMethods, GroupPreviewAction, GroupPreviewSheet, InviteUsersSheet, @@ -29,9 +26,11 @@ import { TLON_EMPLOYEE_GROUP } from '../../constants'; import { useChatSettingsNavigation } from '../../hooks/useChatSettingsNavigation'; import { useCurrentUserId } from '../../hooks/useCurrentUser'; import { useGroupActions } from '../../hooks/useGroupActions'; -import { useFeatureFlag } from '../../lib/featureFlags'; import type { RootStackParamList } from '../../navigation/types'; -import { screenNameFromChannelId } from '../../navigation/utils'; +import { + screenNameFromChannelId, + useNavigateToGroup, +} from '../../navigation/utils'; import { identifyTlonEmployee } from '../../utils/posthog'; import { isSplashDismissed, setSplashDismissed } from '../../utils/splash'; @@ -53,19 +52,6 @@ export function ChatListScreenView({ const [addGroupOpen, setAddGroupOpen] = useState(false); const [screenTitle, setScreenTitle] = useState('Home'); const [inviteSheetGroup, setInviteSheetGroup] = useState(); - const chatOptionsSheetRef = useRef(null); - const [longPressedChat, setLongPressedChat] = useState(null); - const chatOptionsGroupId = useMemo(() => { - return longPressedChat?.type === 'group' - ? longPressedChat.group.id - : undefined; - }, [longPressedChat]); - - const chatOptionsChannelId = useMemo(() => { - return longPressedChat?.type === 'channel' - ? longPressedChat.channel.id - : undefined; - }, [longPressedChat]); const [activeTab, setActiveTab] = useState<'all' | 'groups' | 'messages'>( 'all' @@ -77,10 +63,6 @@ export function ChatListScreenView({ const [showSearchInput, setShowSearchInput] = useState(false); const isFocused = useIsFocused(); - const { data: pins } = store.usePins({ - enabled: isFocused, - }); - const pinned = useMemo(() => pins ?? [], [pins]); const { data: chats } = store.useCurrentChats({ enabled: isFocused, @@ -172,22 +154,16 @@ export function ChatListScreenView({ } }, []); - const [isChannelSwitcherEnabled] = useFeatureFlag('channelSwitcher'); + const navigateToGroup = useNavigateToGroup(); const onPressChat = useCallback( - (item: db.Chat) => { - if (item.type === 'group' && item.isPending) { - setSelectedGroupId(item.id); - } else if (item.type === 'group' && !isChannelSwitcherEnabled) { - navigation.navigate('GroupChannels', { groupId: item.group.id }); - } else if (item.type === 'group') { - if (!item.group.channels?.length) { - throw new Error('cant open group with no channels'); + async (item: db.Chat) => { + if (item.type === 'group') { + if (item.isPending) { + setSelectedGroupId(item.id); + } else { + navigateToGroup(item.group.id); } - navigation.navigate('Channel', { - channelId: item.group.channels[0].id, - groupId: item.group.id, - }); } else { const screenName = screenNameFromChannelId(item.id); navigation.navigate(screenName, { @@ -195,21 +171,9 @@ export function ChatListScreenView({ }); } }, - [isChannelSwitcherEnabled, navigation] + [navigateToGroup, navigation] ); - const onLongPressChat = useCallback((item: db.Chat) => { - if (item.isPending) { - return; - } - setLongPressedChat(item); - chatOptionsSheetRef.current?.open( - item.id, - item.type === 'channel' ? item.channel.type : 'group', - item.unreadCount - ); - }, []); - const handleGroupPreviewSheetOpenChange = useCallback((open: boolean) => { if (!open) { setSelectedGroupId(null); @@ -298,9 +262,6 @@ export function ChatListScreenView({ useGroup={store.useGroupPreview} > { setInviteSheetGroup(group); @@ -329,7 +290,6 @@ export function ChatListScreenView({ pinned={resolvedChats.pinned} unpinned={resolvedChats.unpinned} pending={resolvedChats.pending} - onLongPressItem={onLongPressChat} onPressItem={onPressChat} onSectionChange={handleSectionChange} showSearchInput={showSearchInput} @@ -343,7 +303,6 @@ export function ChatListScreenView({ open={splashVisible} onOpenChange={handleWelcomeOpenChange} /> - >(); - const isFocused = useIsFocused(); - const { data: pins } = store.usePins({ - enabled: isFocused, - }); const [inviteSheetGroup, setInviteSheetGroup] = useState( null ); @@ -43,10 +39,6 @@ export function GroupChannelsScreenContent({ group?.id ?? '' ); - const pinnedItems = useMemo(() => { - return pins ?? []; - }, [pins]); - const handleChannelSelected = useCallback( (channel: db.Channel) => { navigation.navigate('Channel', { @@ -79,9 +71,6 @@ export function GroupChannelsScreenContent({ return ( { setInviteSheetGroup(group); }} diff --git a/packages/app/features/top/PostScreen.tsx b/packages/app/features/top/PostScreen.tsx index 51e5659fdd..178151584a 100644 --- a/packages/app/features/top/PostScreen.tsx +++ b/packages/app/features/top/PostScreen.tsx @@ -3,10 +3,15 @@ import { useChannelContext } from '@tloncorp/shared'; import * as db from '@tloncorp/shared/db'; import * as store from '@tloncorp/shared/store'; import * as urbit from '@tloncorp/shared/urbit'; -import { PostScreenView, useCurrentUserId } from '@tloncorp/ui'; +import { + ChatOptionsProvider, + PostScreenView, + useCurrentUserId, +} from '@tloncorp/ui'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useChannelNavigation } from '../../hooks/useChannelNavigation'; +import { useChatSettingsNavigation } from '../../hooks/useChatSettingsNavigation'; import { useGroupActions } from '../../hooks/useGroupActions'; import { useFeatureFlag } from '../../lib/featureFlags'; import type { RootStackParamList } from '../../navigation/types'; @@ -134,34 +139,38 @@ export default function PostScreen(props: Props) { [props.navigation] ); + const chatOptionsNavProps = useChatSettingsNavigation(); + return currentUserId && channel && post ? ( - + + + ) : null; } diff --git a/packages/app/hooks/useChannelNavigation.ts b/packages/app/hooks/useChannelNavigation.ts index 455b385c89..ca761f2db1 100644 --- a/packages/app/hooks/useChannelNavigation.ts +++ b/packages/app/hooks/useChannelNavigation.ts @@ -7,8 +7,7 @@ import { useCallback } from 'react'; import { RootStackParamList } from '../navigation/types'; export const useChannelNavigation = ({ channelId }: { channelId: string }) => { - // Model context - const channelQuery = store.useChannelWithRelations({ + const channelQuery = store.useChannel({ id: channelId, }); diff --git a/packages/app/hooks/useGroupActions.tsx b/packages/app/hooks/useGroupActions.tsx index f00ae8945a..506c3c0cb4 100644 --- a/packages/app/hooks/useGroupActions.tsx +++ b/packages/app/hooks/useGroupActions.tsx @@ -5,19 +5,19 @@ import { useCallback } from 'react'; import { useGroupNavigation } from './useGroupNavigation'; export const useGroupActions = () => { - const { goToHome, goToGroupChannels } = useGroupNavigation(); + const { goToHome, goToGroup } = useGroupNavigation(); const performGroupAction = useCallback( async (action: GroupPreviewAction, updatedGroup: db.Group) => { if (action === 'goTo') { - goToGroupChannels(updatedGroup.id); + goToGroup(updatedGroup.id); } if (action === 'joined') { goToHome(); } }, - [goToGroupChannels, goToHome] + [goToGroup, goToHome] ); return { diff --git a/packages/app/hooks/useGroupNavigation.ts b/packages/app/hooks/useGroupNavigation.ts index e62d7998c4..df7597c72f 100644 --- a/packages/app/hooks/useGroupNavigation.ts +++ b/packages/app/hooks/useGroupNavigation.ts @@ -26,7 +26,7 @@ export const useGroupNavigation = () => { [navigation] ); - const goToGroupChannels = useCallback( + const goToGroup = useCallback( async (groupId: string) => { resetToGroup(groupId); }, @@ -48,6 +48,6 @@ export const useGroupNavigation = () => { goToChannel, goToHome, goToContactHostedGroups, - goToGroupChannels, + goToGroup, }; }; diff --git a/packages/app/navigation/types.ts b/packages/app/navigation/types.ts index 2340cc3917..73bbc1aa16 100644 --- a/packages/app/navigation/types.ts +++ b/packages/app/navigation/types.ts @@ -1,4 +1,7 @@ -import type { NavigatorScreenParams } from '@react-navigation/native'; +import type { + NavigationProp, + NavigatorScreenParams, +} from '@react-navigation/native'; export type RootStackParamList = { Contacts: undefined; @@ -66,6 +69,8 @@ export type RootStackParamList = { }; }; +export type RootStackNavigationProp = NavigationProp; + export type RootDrawerParamList = { Home: NavigatorScreenParams; } & Pick; diff --git a/packages/app/navigation/utils.ts b/packages/app/navigation/utils.ts index 13348316aa..e6f0c493e0 100644 --- a/packages/app/navigation/utils.ts +++ b/packages/app/navigation/utils.ts @@ -3,10 +3,13 @@ import { NavigationProp, useNavigation, } from '@react-navigation/native'; +import * as db from '@tloncorp/shared/db'; import * as logic from '@tloncorp/shared/logic'; import * as store from '@tloncorp/shared/store'; +import { useCallback } from 'react'; -import { RootStackParamList } from './types'; +import { useFeatureFlagStore } from '../lib/featureFlags'; +import { RootStackNavigationProp, RootStackParamList } from './types'; type ResetRouteConfig> = { name: Extract; @@ -83,14 +86,43 @@ export function useResetToDm() { export function useResetToGroup() { const reset = useTypedReset(); - return function resetToGroup(groupId: string) { - reset([ - { name: 'ChatList' }, - { name: 'GroupChannels', params: { groupId } }, - ]); + return async function resetToGroup(groupId: string) { + reset([{ name: 'ChatList' }, await getMainGroupRoute(groupId)]); }; } +export function useNavigateToGroup() { + const navigation = useNavigation(); + const navigationRef = logic.useMutableRef(navigation); + return useCallback( + async (groupId: string) => { + navigationRef.current.navigate(await getMainGroupRoute(groupId)); + }, + [navigationRef] + ); +} + +export async function getMainGroupRoute(groupId: string) { + const group = await db.getGroup({ id: groupId }); + const channelSwitcherEnabled = + useFeatureFlagStore.getState().flags.channelSwitcher; + if ( + group && + group.channels && + (group.channels.length === 1 || channelSwitcherEnabled) + ) { + return { + name: 'Channel', + params: { channelId: group.channels[0].id, groupId }, + } as const; + } else { + return { + name: 'GroupChannels', + params: { groupId }, + } as const; + } +} + export function screenNameFromChannelId(channelId: string) { return logic.isDmChannelId(channelId) ? 'DM' diff --git a/packages/shared/src/db/keyValue.ts b/packages/shared/src/db/keyValue.ts index 2ca38c6472..77da3c9889 100644 --- a/packages/shared/src/db/keyValue.ts +++ b/packages/shared/src/db/keyValue.ts @@ -30,26 +30,6 @@ export const BASE_VOLUME_SETTING_QUERY_KEY = ['volume', 'base']; export const SHOW_BENEFITS_SHEET_QUERY_KEY = ['showBenefitsSheet']; export const THEME_STORAGE_KEY = '@user_theme'; -export type ChannelSortPreference = 'recency' | 'arranged'; -export async function storeChannelSortPreference( - sortPreference: ChannelSortPreference -) { - try { - await AsyncStorage.setItem('channelSortPreference', sortPreference); - } catch (error) { - logger.error('storeChannelSortPreference', error); - } -} - -export async function getChannelSortPreference() { - try { - const value = await AsyncStorage.getItem('channelSortPreference'); - return (value ?? 'recency') as ChannelSortPreference; - } catch (error) { - logger.error('getChannelSortPreference', error); - } -} - export async function getActivitySeenMarker() { const marker = await AsyncStorage.getItem('activitySeenMarker'); return Number(marker) ?? 1; @@ -312,3 +292,10 @@ export const themeSettings = createStorageItem({ key: THEME_STORAGE_KEY, defaultValue: null, }); + +export type ChannelSortPreference = 'recency' | 'arranged'; + +export const channelSortPreference = createStorageItem({ + key: 'channelSortPreference', + defaultValue: 'recency', +}); diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index cce64ed748..4a7f93516a 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -1429,16 +1429,9 @@ export const getAllSingleDms = createReadQuery( [] ); -export interface GetChannelWithRelations { - id: string; -} - export const getChannelWithRelations = createReadQuery( 'getChannelWithRelations', - async ( - { id }: GetChannelWithRelations, - ctx: QueryCtx - ): Promise => { + async ({ id }: { id: string }, ctx: QueryCtx): Promise => { const result = await ctx.db.query.channels.findFirst({ where: eq($channels.id, id), with: { @@ -2609,6 +2602,7 @@ export const getGroup = createReadQuery( .findFirst({ where: (groups, { eq }) => eq(groups.id, id), with: { + unread: true, pin: true, channels: { where: (channels, { eq }) => eq(channels.currentUserIsMember, true), diff --git a/packages/shared/src/store/dbHooks.ts b/packages/shared/src/store/dbHooks.ts index e357e5f33b..a42e8659d4 100644 --- a/packages/shared/src/store/dbHooks.ts +++ b/packages/shared/src/store/dbHooks.ts @@ -312,18 +312,15 @@ export const useGroups = (options: db.GetGroupsOptions) => { }); }; -export const useGroup = (options: { id?: string }) => { +export const useGroup = ({ id }: { id?: string }) => { return useQuery({ - enabled: !!options.id, - queryKey: [['group', options], useKeyFromQueryDeps(db.getGroup, options)], + enabled: !!id, + queryKey: [['group', { id }], useKeyFromQueryDeps(db.getGroup, { id })], queryFn: () => { - if (!options.id) { - // This should never actually get thrown as the query is disabled if id - // is missing - throw new Error('missing id'); + if (!id) { + throw new Error('missing group id'); } - const enabledOptions = options as { id: string }; - return db.getGroup(enabledOptions); + return db.getGroup({ id }); }, }); }; @@ -444,26 +441,24 @@ export const useChannelSearchResults = ( }); }; -export const useChannelWithRelations = ( - options: db.GetChannelWithRelations -) => { - const tableDeps = useKeyFromQueryDeps(db.getChannelWithRelations); +export const useChannel = (options: { id?: string }) => { + const { id } = options; return useQuery({ - queryKey: ['channelWithRelations', tableDeps, options], - queryFn: async () => { - const channel = await db.getChannelWithRelations(options); - return channel ?? null; + enabled: !!id, + queryKey: [ + 'channelWithRelations', + useKeyFromQueryDeps(db.getChannelWithRelations), + options, + ], + queryFn: () => { + if (!id) { + throw new Error('missing channel id'); + } + return db.getChannelWithRelations({ id }); }, }); }; -export const useChannel = (options: { id: string }) => { - return useQuery({ - queryKey: [['channel', options]], - queryFn: () => db.getChannelWithRelations(options), - }); -}; - export const usePostWithThreadUnreads = (options: { id: string }) => { const tableDeps = useKeyFromQueryDeps(db.getPostWithRelations); return useQuery({ diff --git a/packages/shared/src/store/useChannelContext.ts b/packages/shared/src/store/useChannelContext.ts index 20444f21db..45d4424865 100644 --- a/packages/shared/src/store/useChannelContext.ts +++ b/packages/shared/src/store/useChannelContext.ts @@ -19,12 +19,10 @@ export const useChannelContext = ({ // need to populate this from feature flags :( isChannelSwitcherEnabled: boolean; }) => { - // const storage = useStorageUnsafelyUnwrapped(); - - // Model context - const channelQuery = dbHooks.useChannelWithRelations({ + const channelQuery = dbHooks.useChannel({ id: channelId, }); + const groupQuery = dbHooks.useGroup({ id: channelQuery.data?.groupId ?? '', }); diff --git a/packages/ui/src/components/Channel/BaubleHeader.tsx b/packages/ui/src/components/Channel/BaubleHeader.tsx index 317dde5ea6..bfd260208e 100644 --- a/packages/ui/src/components/Channel/BaubleHeader.tsx +++ b/packages/ui/src/components/Channel/BaubleHeader.tsx @@ -1,7 +1,7 @@ import { LinearGradient } from '@tamagui/linear-gradient'; import * as db from '@tloncorp/shared/db'; import { BlurView } from 'expo-blur'; -import { useCallback, useRef } from 'react'; +import { useCallback } from 'react'; import { OpaqueColorValue } from 'react-native'; import Animated, { Easing, @@ -21,7 +21,6 @@ import { Spinner, Text, View } from 'tamagui'; import { useChatOptions } from '../../contexts/chatOptions'; import { useScrollContext } from '../../contexts/scroll'; import { ContactAvatar } from '../Avatar'; -import { ChatOptionsSheet, ChatOptionsSheetMethods } from '../ChatOptionsSheet'; import { Icon } from '../Icon'; import { Image } from '../Image'; import Pressable from '../Pressable'; @@ -37,12 +36,10 @@ export function BaubleHeader({ showSpinner?: boolean; group?: db.Group | null; }) { + const chatOptions = useChatOptions(); const [scrollValue] = useScrollContext(); const insets = useSafeAreaInsets(); const frame = useSafeAreaFrame(); - const groupOptions = useChatOptions(); - const isGroupContext = !!group && !!groupOptions; - const chatOptionsSheetRef = useRef(null); const easedValue = useDerivedValue( () => Easing.ease(scrollValue.value), @@ -67,12 +64,12 @@ export function BaubleHeader({ }, [easedValue, insets.top]); const handlePress = useCallback(() => { - if (group && groupOptions) { - chatOptionsSheetRef.current?.open(group.id, 'group'); + if (group) { + chatOptions.open(group.id, 'group'); } else { - chatOptionsSheetRef.current?.open(channel.id, channel.type); + chatOptions.open(channel.id, 'channel'); } - }, [channel.id, channel.type, group, groupOptions]); + }, [channel.id, group, chatOptions]); return ( )} - {isGroupContext && groupOptions && ( - - )} ); } diff --git a/packages/ui/src/components/Channel/ChannelHeader.tsx b/packages/ui/src/components/Channel/ChannelHeader.tsx index 5c8cc1c846..1b36626f18 100644 --- a/packages/ui/src/components/Channel/ChannelHeader.tsx +++ b/packages/ui/src/components/Channel/ChannelHeader.tsx @@ -4,12 +4,11 @@ import { useCallback, useContext, useEffect, - useRef, useState, } from 'react'; -import useIsWindowNarrow from '../../hooks/useIsWindowNarrow'; -import { ChatOptionsSheet, ChatOptionsSheetMethods } from '../ChatOptionsSheet'; +import { useChatOptions } from '../../contexts'; +import Pressable from '../Pressable'; import { ScreenHeader } from '../ScreenHeader'; import { BaubleHeader } from './BaubleHeader'; @@ -80,6 +79,7 @@ export function ChannelHeader({ goBack, goToSearch, goToEdit, + goToChannels, showSpinner, showSearchButton = true, showMenuButton = false, @@ -92,17 +92,18 @@ export function ChannelHeader({ goBack?: () => void; goToSearch?: () => void; goToEdit?: () => void; + goToChannels?: () => void; showSpinner?: boolean; showSearchButton?: boolean; showMenuButton?: boolean; showEditButton?: boolean; post?: db.Post; }) { - const chatOptionsSheetRef = useRef(null); + const chatOptions = useChatOptions(); const handlePressOverflowMenu = useCallback(() => { - chatOptionsSheetRef.current?.open(channel.id, channel.type); - }, [channel.id, channel.type]); + chatOptions.open(channel.id, 'channel'); + }, [channel.id, chatOptions]); const contextItems = useContext(ChannelHeaderItemsContext)?.items ?? []; @@ -125,7 +126,11 @@ export function ChannelHeader({ return ( <> + {title} + + } titleWidth={titleWidth()} showSessionStatus isLoading={showSpinner} @@ -150,7 +155,6 @@ export function ChannelHeader({ } /> - ); } diff --git a/packages/ui/src/components/Channel/index.tsx b/packages/ui/src/components/Channel/index.tsx index ebc496fb47..aa4b3bf12c 100644 --- a/packages/ui/src/components/Channel/index.tsx +++ b/packages/ui/src/components/Channel/index.tsx @@ -337,6 +337,7 @@ export function Channel({ } showSearchButton={isChatChannel} goToSearch={goToSearch} + goToChannels={goToChannels} showSpinner={isLoadingPosts} showMenuButton={true} /> diff --git a/packages/ui/src/components/ChannelSwitcherSheet.tsx b/packages/ui/src/components/ChannelSwitcherSheet.tsx index 5f0147a325..f61989ed03 100644 --- a/packages/ui/src/components/ChannelSwitcherSheet.tsx +++ b/packages/ui/src/components/ChannelSwitcherSheet.tsx @@ -4,8 +4,10 @@ import { TouchableOpacity } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { SizableText, Text, XStack } from 'tamagui'; +import { useChatOptions } from '../contexts'; import ChannelNavSections from './ChannelNavSections'; import { Icon } from './Icon'; +import Pressable from './Pressable'; import { Sheet } from './Sheet'; interface Props { @@ -24,29 +26,23 @@ export function ChannelSwitcherSheet({ onSelect, }: Props) { const [hasOpened, setHasOpened] = useState(open); - const [sortBy, setSortBy] = useState('recency'); + const chatOptions = useChatOptions(); + const sortBy = db.channelSortPreference.useValue(); const { bottom } = useSafeAreaInsets(); useEffect(() => { setHasOpened(open); }, [open]); - useEffect(() => { - const getSortByPreference = async () => { - const preference = await db.getChannelSortPreference(); - setSortBy(preference ?? 'recency'); - }; - - getSortByPreference(); - }, [setSortBy]); + const handleSortByToggled = useCallback(() => { + const newSortBy = sortBy === 'recency' ? 'arranged' : 'recency'; + db.channelSortPreference.setValue(newSortBy); + }, [sortBy]); - const handleSortByChanged = useCallback( - (newSortBy: 'recency' | 'arranged') => { - setSortBy(newSortBy); - db.storeChannelSortPreference(newSortBy); - }, - [] - ); + const handlePressSettings = useCallback(() => { + onOpenChange(false); + chatOptions.open(group.id, 'group'); + }, [chatOptions, group.id, onOpenChange]); return ( {group?.title} - { - const newSortBy = sortBy === 'recency' ? 'arranged' : 'recency'; - handleSortByChanged(newSortBy); - }} - > + + + + {hasOpened && ( void; - onLongPressItem?: (chat: db.Chat) => void; onSectionChange?: (title: string) => void; activeTab: TabName; setActiveTab: (tab: TabName) => void; @@ -72,6 +70,14 @@ export const ChatList = React.memo(function ChatListComponent({ [displayData] ); + const chatOptions = useChatOptions(); + const handleLongPress = useCallback( + (item: db.Chat) => { + chatOptions.open(item.id, item.type); + }, + [chatOptions] + ); + // removed the use of useStyle here because it was causing FlashList to // peg the CPU and freeze the app on web // see: https://github.com/Shopify/flash-list/pull/852 @@ -93,7 +99,7 @@ export const ChatList = React.memo(function ChatListComponent({ ); } else { @@ -101,12 +107,12 @@ export const ChatList = React.memo(function ChatListComponent({ ); } }, - [onPressItem, onLongPressItem] + [onPressItem, handleLongPress] ); const handlePressTryAll = useCallback(() => { diff --git a/packages/ui/src/components/ChatOptionsSheet.tsx b/packages/ui/src/components/ChatOptionsSheet.tsx index caf158f489..4d017b52b5 100644 --- a/packages/ui/src/components/ChatOptionsSheet.tsx +++ b/packages/ui/src/components/ChatOptionsSheet.tsx @@ -8,7 +8,6 @@ import React, { ReactElement, useCallback, useEffect, - useImperativeHandle, useMemo, useState, } from 'react'; @@ -23,42 +22,20 @@ import { Action, ActionGroup, ActionSheet } from './ActionSheet'; import { IconButton } from './IconButton'; import { ListItem } from './ListItem'; -export type ChatType = 'group' | db.ChannelType; - -export type ChatOptionsSheetMethods = { - open: (chatId: string, chatType: ChatType, unreadCount?: number) => void; -}; - -export type ChatOptionsSheetRef = React.Ref; - type ChatOptionsSheetProps = { - // We pass in setSortBy from GroupChannelsScreenView to live-update the sort - // preference in the channel list. - setSortBy?: (sortBy: db.ChannelSortPreference) => void; -}; - -const ChatOptionsSheetComponent = React.forwardRef< - ChatOptionsSheetMethods, - ChatOptionsSheetProps ->(function ChatOptionsSheetImpl(props, ref) { - const [open, setOpen] = useState(false); - const [chat, setChat] = useState<{ - type: ChatType; + open: boolean; + onOpenChange: (open: boolean) => void; + chat?: { + type: 'group' | 'channel'; id: string; - unreadCount?: number; - } | null>(null); - - useImperativeHandle( - ref, - () => ({ - open: (chatId, chatType, unreadCount) => { - setOpen(true); - setChat({ id: chatId, type: chatType, unreadCount }); - }, - }), - [] - ); + } | null; +}; +export const ChatOptionsSheet = React.memo(function ChatOptionsSheet({ + open, + onOpenChange, + chat, +}: ChatOptionsSheetProps) { if (!chat || !open) { return null; } @@ -68,9 +45,7 @@ const ChatOptionsSheetComponent = React.forwardRef< ); } @@ -79,25 +54,19 @@ const ChatOptionsSheetComponent = React.forwardRef< ); }); -export const ChatOptionsSheet = React.memo(ChatOptionsSheetComponent); - export function GroupOptionsSheetLoader({ groupId, open, onOpenChange, - setSortBy, - unreadCount, }: { groupId: string; open: boolean; onOpenChange: (open: boolean) => void; - setSortBy?: (sortBy: db.ChannelSortPreference) => void; - unreadCount?: number; }) { const groupQuery = store.useGroup({ id: groupId }); const [pane, setPane] = useState< @@ -119,9 +88,8 @@ export function GroupOptionsSheetLoader({ group={groupQuery.data} pane={pane} setPane={setPane} - setSortBy={setSortBy} onOpenChange={onOpenChange} - unreadCount={unreadCount} + unreadCount={groupQuery.data.unread?.count} /> ) : null; @@ -131,16 +99,14 @@ export function GroupOptions({ group, pane, setPane, - setSortBy, onOpenChange, unreadCount, }: { group: db.Group; pane: 'initial' | 'edit' | 'notifications' | 'sort'; setPane: (pane: 'initial' | 'edit' | 'notifications' | 'sort') => void; - setSortBy?: (sortBy: db.ChannelSortPreference) => void; onOpenChange: (open: boolean) => void; - unreadCount?: number; + unreadCount?: number | null; }) { const currentUser = useCurrentUserId(); const { data: currentVolumeLevel } = store.useGroupVolumeLevel(group.id); @@ -419,7 +385,6 @@ export function GroupOptions({ title: 'Sort by recency', action: () => { onSelectSort?.('recency'); - setSortBy?.('recency'); onOpenChange(false); }, }, @@ -427,14 +392,13 @@ export function GroupOptions({ title: 'Sort by arrangement', action: () => { onSelectSort?.('arranged'); - setSortBy?.('arranged'); onOpenChange(false); }, }, ], }, ]; - }, [onSelectSort, setSortBy, onOpenChange]); + }, [onSelectSort, onOpenChange]); const memberCount = group?.members?.length ? group.members.length.toLocaleString() @@ -504,7 +468,7 @@ export function ChannelOptionsSheetLoader({ onOpenChange: (open: boolean) => void; }) { const [pane, setPane] = useState<'initial' | 'notifications'>('initial'); - const channelQuery = store.useChannelWithRelations({ + const channelQuery = store.useChannel({ id: channelId, }); @@ -551,7 +515,6 @@ export function ChannelOptions({ onPressChannelMeta, onPressManageChannels, onPressInvite, - onPressLeave, } = useChatOptions() ?? {}; const currentUserIsHost = useMemo( diff --git a/packages/ui/src/components/GroupChannelsScreenView.tsx b/packages/ui/src/components/GroupChannelsScreenView.tsx index 596b91fbdc..7b61f2102a 100644 --- a/packages/ui/src/components/GroupChannelsScreenView.tsx +++ b/packages/ui/src/components/GroupChannelsScreenView.tsx @@ -1,20 +1,12 @@ import * as db from '@tloncorp/shared/db'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { - Button, - ScrollView, - View, - YStack, - getVariableValue, - useTheme, -} from 'tamagui'; +import { ScrollView, View, YStack, getVariableValue, useTheme } from 'tamagui'; -import { useCurrentUserId } from '../contexts'; +import { useChatOptions, useCurrentUserId } from '../contexts'; import { useIsAdmin } from '../utils/channelUtils'; import { Badge } from './Badge'; import ChannelNavSections from './ChannelNavSections'; -import { ChatOptionsSheet, ChatOptionsSheetMethods } from './ChatOptionsSheet'; import { ChannelListItem } from './ListItem/ChannelListItem'; import { LoadingSpinner } from './LoadingSpinner'; import { CreateChannelSheet } from './ManageChannels/CreateChannelSheet'; @@ -38,39 +30,30 @@ export function GroupChannelsScreenView({ onBackPressed, enableCustomChannels = false, }: GroupChannelsScreenViewProps) { - const chatOptionsSheetRef = useRef(null); const [showCreateChannel, setShowCreateChannel] = useState(false); - const [sortBy, setSortBy] = useState('recency'); + const sortBy = db.channelSortPreference.useValue(); const insets = useSafeAreaInsets(); const userId = useCurrentUserId(); const isGroupAdmin = useIsAdmin(group?.id ?? '', userId); - useEffect(() => { - const getSortByPreference = async () => { - const preference = await db.getChannelSortPreference(); - setSortBy(preference ?? 'recency'); - }; - - getSortByPreference(); - }, [setSortBy]); - + const chatOptions = useChatOptions(); const handlePressOverflowButton = useCallback(() => { if (group) { - chatOptionsSheetRef.current?.open(group.id, 'group'); + chatOptions.open(group.id, 'group'); } - }, [group]); - - const title = group ? group?.title ?? 'Untitled' : ''; + }, [group, chatOptions]); const handleOpenChannelOptions = useCallback( (channel: db.Channel) => { if (group) { - chatOptionsSheetRef.current?.open(channel.id, channel.type); + chatOptions.open(channel.id, 'channel'); } }, - [group] + [group, chatOptions] ); + const title = group ? group?.title ?? 'Untitled' : ''; + const titleWidth = useCallback(() => { if (isGroupAdmin) { return 55; @@ -165,7 +148,6 @@ export function GroupChannelsScreenView({ enableCustomChannels={enableCustomChannels} /> )} - ); } diff --git a/packages/ui/src/components/Pressable.tsx b/packages/ui/src/components/Pressable.tsx index c2386575fd..54ccf64cf8 100644 --- a/packages/ui/src/components/Pressable.tsx +++ b/packages/ui/src/components/Pressable.tsx @@ -23,7 +23,7 @@ const StackComponent = ({ return ( void; onPressGroupMembers: (groupId: string) => void; @@ -24,19 +26,22 @@ export type ChatOptionsContextValue = { onTogglePinned: () => void; onPressLeave: () => Promise; onSelectSort?: (sortBy: 'recency' | 'arranged') => void; + open: (chatId: string, chatType: 'group' | 'channel') => void; } | null; const ChatOptionsContext = createContext(null); export const useChatOptions = () => { - return useContext(ChatOptionsContext); + const value = useContext(ChatOptionsContext); + if (!value) { + throw new Error('useChatOptions used outside of ChatOptions context'); + } + return value; }; type ChatOptionsProviderProps = { children: ReactNode; - groupId?: string; - channelId?: string; - pinned: db.Pin[]; + useChannel?: typeof store.useChannel; useGroup?: typeof store.useGroup; onPressGroupMeta: (groupId: string) => void; onPressGroupMembers: (groupId: string) => void; @@ -47,13 +52,12 @@ type ChatOptionsProviderProps = { onPressChannelMeta: (channelId: string) => void; onPressRoles: (groupId: string) => void; onSelectSort?: (sortBy: 'recency' | 'arranged') => void; - navigateOnLeave?: () => void; + onLeaveGroup?: () => void; }; export const ChatOptionsProvider = ({ children, - groupId, - pinned = [], + useChannel = store.useChannel, useGroup = store.useGroup, onPressGroupMeta, onPressGroupMembers, @@ -63,17 +67,30 @@ export const ChatOptionsProvider = ({ onPressChannelMembers, onPressChannelMeta, onPressRoles, - navigateOnLeave, + onLeaveGroup: navigateOnLeave, }: ChatOptionsProviderProps) => { - const groupQuery = useGroup({ id: groupId ?? '' }); - const group = groupId ? groupQuery.data ?? null : null; + const [sheetOpen, setSheetOpen] = useState(false); + const [chat, setChat] = useState<{ + id: string; + type: 'group' | 'channel'; + } | null>(null); + + const isChannel = chat?.type === 'channel'; + const isGroup = chat?.type === 'group'; + + const { data: channel } = useChannel({ + id: isChannel ? chat.id : undefined, + }); + const { data: group } = useGroup({ + id: isGroup ? chat.id : channel?.groupId ?? undefined, + }); const groupChannels = useMemo(() => { return group?.channels ?? []; }, [group?.channels]); const onTogglePinned = useCallback(() => { - if (group && group.channels[0]) { + if (group && group.channels?.[0]) { group.pin ? store.unpinItem(group.pin) : store.pinGroup(group); } }, [group]); @@ -86,30 +103,62 @@ export const ChatOptionsProvider = ({ }, [group, navigateOnLeave]); const onSelectSort = useCallback((sortBy: 'recency' | 'arranged') => { - db.storeChannelSortPreference(sortBy); + db.channelSortPreference.setValue(sortBy); }, []); - const contextValue: ChatOptionsContextValue = { - pinned, - useGroup, - group, - groupChannels, - onPressGroupMeta, - onPressGroupMembers, - onPressManageChannels, - onPressInvite, - onPressGroupPrivacy, - onPressRoles, - onPressLeave, - onTogglePinned, - onPressChannelMembers, - onPressChannelMeta, - onSelectSort, - }; + const open = useCallback((chatId: string, chatType: 'group' | 'channel') => { + setChat({ + id: chatId, + type: chatType, + }); + setSheetOpen(true); + }, []); + + const contextValue: ChatOptionsContextValue = useMemo( + () => ({ + useGroup, + group, + groupChannels, + onPressGroupMeta, + onPressGroupMembers, + onPressManageChannels, + onPressInvite, + onPressGroupPrivacy, + onPressRoles, + onPressLeave, + onTogglePinned, + onPressChannelMembers, + onPressChannelMeta, + onSelectSort, + open, + }), + [ + group, + groupChannels, + onPressChannelMembers, + onPressChannelMeta, + onPressGroupMembers, + onPressGroupMeta, + onPressGroupPrivacy, + onPressInvite, + onPressLeave, + onPressManageChannels, + onPressRoles, + onSelectSort, + onTogglePinned, + open, + useGroup, + ] + ); return ( {children} + ); }; diff --git a/packages/ui/src/utils/channelUtils.tsx b/packages/ui/src/utils/channelUtils.tsx index dc72b2bbab..811758c391 100644 --- a/packages/ui/src/utils/channelUtils.tsx +++ b/packages/ui/src/utils/channelUtils.tsx @@ -4,7 +4,7 @@ import { useMemberRoles } from '@tloncorp/shared/store'; import { useMemo } from 'react'; import type { IconType } from '../components/Icon'; -import { useCalm } from '../contexts'; +import { useCalm } from '../contexts/appDataContext'; export function getChannelMemberName( member: db.ChatMember, From d870ac0c6679aafbab5249aecfa03fff4e2a5e07 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 3 Dec 2024 17:22:15 +0000 Subject: [PATCH 061/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index dce125ff4d..64e6b3000d 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v1.q6jaf.bme3f.povqt.eo08j.d6qtq.glob' 0v1.q6jaf.bme3f.povqt.eo08j.d6qtq] + glob-http+['https://bootstrap.urbit.org/glob-0v6.88lmm.hdoo6.b26b4.d75rp.9r46j.glob' 0v6.88lmm.hdoo6.b26b4.d75rp.9r46j] base+'groups' version+[6 4 2] website+'https://tlon.io' From 0797b0e29c9a7cf6b3ea2475e1189a65fa5a7c03 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 3 Dec 2024 17:22:34 +0000 Subject: [PATCH 062/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 8212f9923d..2eb8734568 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.rimg2.i6af8.kvff9.flpan.vt06t.glob' 0v4.rimg2.i6af8.kvff9.flpan.vt06t] + glob-http+['https://bootstrap.urbit.org/glob-0v4.a59j6.f5hoa.kdtkj.hoder.us26q.glob' 0v4.a59j6.f5hoa.kdtkj.hoder.us26q] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 8eff69e3c86d866418d0ebffcfd5260d88d4e6ba Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 3 Dec 2024 19:08:27 +0000 Subject: [PATCH 063/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 2eb8734568..d2df7477e5 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.a59j6.f5hoa.kdtkj.hoder.us26q.glob' 0v4.a59j6.f5hoa.kdtkj.hoder.us26q] + glob-http+['https://bootstrap.urbit.org/glob-0v1.5pauj.5up6l.b7m7o.g1qjn.v0c73.glob' 0v1.5pauj.5up6l.b7m7o.g1qjn.v0c73] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From cebe57830d76a3229d09924538617682c6e13e65 Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Tue, 3 Dec 2024 15:09:11 -0500 Subject: [PATCH 064/149] remove persist client (#4241) --- apps/tlon-web-new/package.json | 1 - apps/tlon-web-new/src/indexedDBPersistor.ts | 23 ---------- apps/tlon-web-new/src/main.tsx | 16 ++----- pnpm-lock.yaml | 48 +++++++++------------ 4 files changed, 23 insertions(+), 65 deletions(-) delete mode 100644 apps/tlon-web-new/src/indexedDBPersistor.ts diff --git a/apps/tlon-web-new/package.json b/apps/tlon-web-new/package.json index d82baf62c5..ddb37e5325 100644 --- a/apps/tlon-web-new/package.json +++ b/apps/tlon-web-new/package.json @@ -40,7 +40,6 @@ "@tamagui/vite-plugin": "~1.112.12", "@tanstack/react-query": "^5.32.1", "@tanstack/react-query-devtools": "^5.32.1", - "@tanstack/react-query-persist-client": "^5.32.1", "@tiptap/core": "^2.6.6", "@tiptap/extension-blockquote": "^2.6.6", "@tiptap/extension-bold": "^2.6.6", diff --git a/apps/tlon-web-new/src/indexedDBPersistor.ts b/apps/tlon-web-new/src/indexedDBPersistor.ts deleted file mode 100644 index e5b3529aa1..0000000000 --- a/apps/tlon-web-new/src/indexedDBPersistor.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - PersistedClient, - Persister, -} from '@tanstack/react-query-persist-client'; -import { del, get, set } from 'idb-keyval'; - -/** - * Creates an Indexed DB persister - * @see https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API - */ -export default function createIDBPersister( - idbValidKey: IDBValidKey = window.our -) { - return { - persistClient: async (client: PersistedClient) => { - set(idbValidKey, client); - }, - restoreClient: async () => get(idbValidKey), - removeClient: async () => { - await del(idbValidKey); - }, - } as Persister; -} diff --git a/apps/tlon-web-new/src/main.tsx b/apps/tlon-web-new/src/main.tsx index 278636433d..34ecab9440 100644 --- a/apps/tlon-web-new/src/main.tsx +++ b/apps/tlon-web-new/src/main.tsx @@ -8,17 +8,13 @@ // This is a temporary fix until we can figure out why this is happening. // This was most likely caused by a recent dependency change. import regeneratorRuntime from '@babel/runtime/regenerator'; -import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'; import { EditorView } from '@tiptap/pm/view'; import { setupDb } from '@tloncorp/app/lib/webDb'; -import { queryClient } from '@tloncorp/shared/api'; +import { QueryClientProvider, queryClient } from '@tloncorp/shared/api'; import { PostHogProvider } from 'posthog-js/react'; -import React from 'react'; import { createRoot } from 'react-dom/client'; -import _api from './api'; import App from './app'; -import indexedDBPersistor from './indexedDBPersistor'; import { analyticsClient, captureError } from './logic/analytics'; import './styles/index.css'; @@ -54,17 +50,11 @@ setupDb().then(() => { root.render( // Strict mode disabled as it breaks react-navigation on web // - + - + // ); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7cbbc8eb5..67792eb023 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1001,9 +1001,6 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.32.1 version: 5.59.20(@tanstack/react-query@5.32.1(react@18.2.0))(react@18.2.0) - '@tanstack/react-query-persist-client': - specifier: ^5.32.1 - version: 5.59.20(@tanstack/react-query@5.32.1(react@18.2.0))(react@18.2.0) '@tiptap/core': specifier: ^2.6.6 version: 2.6.6(@tiptap/pm@2.6.6) @@ -1388,7 +1385,7 @@ importers: version: link:../ui expo-notifications: specifier: ~0.27.6 - version: 0.27.6(encoding@0.1.13)(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2)))(encoding@0.1.13)) + version: 0.27.6(encoding@0.1.13)(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2)))(encoding@0.1.13)) lodash: specifier: ^4.17.21 version: 4.17.21 @@ -6257,18 +6254,12 @@ packages: '@tanstack/query-core@5.32.1': resolution: {integrity: sha512-mCWa1wdGb1jiny4+qYegbSeadcFj+Nq65KFSs4A1DRveoIq7SrTwUhqu7hrB6d54cQH5x59DfJvxusn3w1Cj/g==} - '@tanstack/query-core@5.59.20': - resolution: {integrity: sha512-e8vw0lf7KwfGe1if4uPFhvZRWULqHjFcz3K8AebtieXvnMOz5FSzlZe3mTLlPuUBcydCnBRqYs2YJ5ys68wwLg==} - '@tanstack/query-devtools@5.59.20': resolution: {integrity: sha512-vxhuQ+8VV4YWQSFxQLsuM+dnEKRY7VeRzpNabFXdhEwsBYLrjXlF1pM38A8WyKNLqZy8JjyRO8oP4Wd/oKHwuQ==} '@tanstack/query-persist-client-core@4.27.0': resolution: {integrity: sha512-A+dPA7zG0MJOMDeBc/2WcKXW4wV2JMkeBVydobPW9G02M4q0yAj7vI+7SmM2dFuXyIvxXp4KulCywN6abRKDSQ==} - '@tanstack/query-persist-client-core@5.59.20': - resolution: {integrity: sha512-RUaDys2zyhCw8MGcp0tirbpp8IjU7zrtdMaEYQ6WetrNvn/IOg9Y2Zpk55P7gjBq8fEyFlmuRM3cHVNn/Usg8w==} - '@tanstack/react-query-devtools@4.29.0': resolution: {integrity: sha512-bzotqin4Wa/GlPgJ2dI7eggQcbMDLIOwEClHGrkyie76DbT8vEEmEV9Kbh6kriKVSqCLpa9ZrgG/f8/Bx1zIwA==} peerDependencies: @@ -6287,12 +6278,6 @@ packages: peerDependencies: '@tanstack/react-query': 4.28.0 - '@tanstack/react-query-persist-client@5.59.20': - resolution: {integrity: sha512-7dV9wGs9f8IvGysfmsmLcQztHjULOMB30HmERo8yjwVHRKhoQvdtKTvQ9aX2lSP5zzP1Qd1/IHF2hBB11Q/rwA==} - peerDependencies: - '@tanstack/react-query': ^5.59.20 - react: ^18 || ^19 - '@tanstack/react-query@4.36.1': resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} peerDependencies: @@ -22024,18 +22009,12 @@ snapshots: '@tanstack/query-core@5.32.1': {} - '@tanstack/query-core@5.59.20': {} - '@tanstack/query-devtools@5.59.20': {} '@tanstack/query-persist-client-core@4.27.0': dependencies: '@tanstack/query-core': 4.27.0 - '@tanstack/query-persist-client-core@5.59.20': - dependencies: - '@tanstack/query-core': 5.59.20 - '@tanstack/react-query-devtools@4.29.0(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/match-sorter-utils': 8.8.4 @@ -22056,12 +22035,6 @@ snapshots: '@tanstack/query-persist-client-core': 4.27.0 '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0) - '@tanstack/react-query-persist-client@5.59.20(@tanstack/react-query@5.32.1(react@18.2.0))(react@18.2.0)': - dependencies: - '@tanstack/query-persist-client-core': 5.59.20 - '@tanstack/react-query': 5.32.1(react@18.2.0) - react: 18.2.0 - '@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.9(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2))(encoding@0.1.13)(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/query-core': 4.36.1 @@ -25207,6 +25180,10 @@ snapshots: dependencies: expo: 50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2)))(encoding@0.1.13) + expo-application@5.8.3(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2)))(encoding@0.1.13)): + dependencies: + expo: 50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2)))(encoding@0.1.13) + expo-asset@9.0.2(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2)))(encoding@0.1.13)): dependencies: '@react-native/assets-registry': 0.73.1 @@ -25416,6 +25393,21 @@ snapshots: - encoding - supports-color + expo-notifications@0.27.6(encoding@0.1.13)(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2)))(encoding@0.1.13)): + dependencies: + '@expo/image-utils': 0.4.1(encoding@0.1.13) + '@ide/backoff': 1.0.0 + abort-controller: 3.0.0 + assert: 2.1.0 + badgin: 1.2.3 + expo: 50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2)))(encoding@0.1.13) + expo-application: 5.8.3(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2)))(encoding@0.1.13)) + expo-constants: 15.4.5(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.26.0(@babel/core@7.25.2)))(encoding@0.1.13)) + fs-extra: 9.1.0 + transitivePeerDependencies: + - encoding + - supports-color + expo-secure-store@12.8.1(expo@50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2)))(encoding@0.1.13)): dependencies: expo: 50.0.6(@babel/core@7.25.2)(@react-native/babel-preset@0.73.21(@babel/core@7.25.2)(@babel/preset-env@7.23.7(@babel/core@7.25.2)))(encoding@0.1.13) From 89e623aadcbd77aadc673eea2c5057cdab76e06c Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 3 Dec 2024 20:15:53 +0000 Subject: [PATCH 065/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index d2df7477e5..f53e8e7c11 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v1.5pauj.5up6l.b7m7o.g1qjn.v0c73.glob' 0v1.5pauj.5up6l.b7m7o.g1qjn.v0c73] + glob-http+['https://bootstrap.urbit.org/glob-0v5.4lprj.7br43.c988m.3l0j7.cdlg9.glob' 0v5.4lprj.7br43.c988m.3l0j7.cdlg9] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 6c6f77b67fd98806357dd715ae9f9932ae548020 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Tue, 3 Dec 2024 14:15:57 -0600 Subject: [PATCH 066/149] desktop: show loading spinner while we wait for the db --- apps/tlon-web-new/src/app.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/tlon-web-new/src/app.tsx b/apps/tlon-web-new/src/app.tsx index ca969fbe03..15f4a51906 100644 --- a/apps/tlon-web-new/src/app.tsx +++ b/apps/tlon-web-new/src/app.tsx @@ -17,6 +17,7 @@ import { Provider as TamaguiProvider } from '@tloncorp/app/provider'; import { AppDataProvider } from '@tloncorp/app/provider/AppDataProvider'; import { sync } from '@tloncorp/shared'; import * as store from '@tloncorp/shared/store'; +import { LoadingSpinner, View } from '@tloncorp/ui'; import cookies from 'browser-cookies'; import { usePostHog } from 'posthog-js/react'; import React, { PropsWithChildren, useEffect, useState } from 'react'; @@ -181,7 +182,18 @@ const App = React.memo(function AppComponent() { - {dbIsLoaded && } + {dbIsLoaded ? ( + + ) : ( + + + + )} From 6240ec62badb4cd6691b7c6784287b1134589a97 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 00:34:04 -0600 Subject: [PATCH 067/149] initial try --- .../src/components/AuthenticatedApp.tsx | 2 + .../src/hooks/useFindContactSuggestions.ts | 10 ++ desk/app/groups-ui.hoon | 28 +++- desk/mar/ships.hoon | 1 + desk/mar/ui/add-contact-suggestions.hoon | 2 + packages/app/features/top/ContactsScreen.tsx | 48 +++--- packages/shared/src/api/contactsApi.ts | 15 +- packages/shared/src/db/keyValue.ts | 5 + packages/shared/src/db/queries.ts | 26 ++++ packages/shared/src/store/contactActions.ts | 142 +++++++++++++++++- packages/shared/src/store/dbHooks.ts | 8 + .../ui/src/components/ContactsScreenView.tsx | 12 +- 12 files changed, 259 insertions(+), 40 deletions(-) create mode 100644 apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts create mode 100644 desk/mar/ui/add-contact-suggestions.hoon diff --git a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx index c60cab2bd4..add787441a 100644 --- a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx +++ b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx @@ -15,6 +15,7 @@ import { AppStateStatus } from 'react-native'; import { useCheckAppUpdated } from '../hooks/analytics'; import { useDeepLinkListener } from '../hooks/useDeepLinkListener'; +import useFindContactSuggestions from '../hooks/useFindContactSuggestions'; import useNotificationListener from '../hooks/useNotificationListener'; function AuthenticatedApp() { @@ -29,6 +30,7 @@ function AuthenticatedApp() { useNavigationLogging(); useNetworkLogger(); useCheckAppUpdated(); + useFindContactSuggestions(); useEffect(() => { configureClient(); diff --git a/apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts b/apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts new file mode 100644 index 0000000000..f335958915 --- /dev/null +++ b/apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts @@ -0,0 +1,10 @@ +import * as store from '@tloncorp/shared/store'; +import { useEffect } from 'react'; + +export default function useFindContactSuggestions() { + const { data: numJoinedGroups } = store.useJoinedGroupsCount(); + + useEffect(() => { + store.findContactSuggestions(); + }, [numJoinedGroups]); +} diff --git a/desk/app/groups-ui.hoon b/desk/app/groups-ui.hoon index 193615b82f..050911842e 100644 --- a/desk/app/groups-ui.hoon +++ b/desk/app/groups-ui.hoon @@ -7,8 +7,9 @@ |% +$ card card:agent:gall +$ current-state - $: %2 + $: %3 hidden-contact-suggestions=(set ship) + manual-contact-suggestions=(set ship) pins=(list whom:u) first-load=? == @@ -90,18 +91,27 @@ =? old ?=(~ old) *current-state =? old ?=(%0 -.old) (state-0-to-1 old) =? old ?=(%1 -.old) (state-1-to-2 old) - ?> ?=(%2 -.old) + =? old ?=(%2 -.old) (state-2-to-3 old) + ?> ?=(%3 -.old) =. state old init :: - +$ versioned-state $@(~ $%(state-2 state-1 state-0)) - +$ state-2 current-state + +$ versioned-state $@(~ $%(state-3 state-2 state-1 state-0)) + +$ state-3 current-state + +$ state-2 + $: %2 + hidden-contact-suggestions=(set ship) + pins=(list whom:u) + first-load=? + == +$ state-1 $: %1 pins=(list whom:u) first-load=? == - :: + :: + ++ state-2-to-3 + |=(state-2 [%3 hidden-contact-suggestions ~ pins first-load]) ++ state-1-to-2 |=(state-1 [%2 ~ pins first-load]) +$ state-0 [%0 first-load=?] @@ -227,6 +237,12 @@ =. hidden-contact-suggestions (~(put in hidden-contact-suggestions) ship) cor + :: + %ui-add-contact-suggestions + =+ ship-list=!<((list @p) vase) + =. manual-contact-suggestions + (~(uni in manual-contact-suggestions) (sy ship-list)) + cor :: %ui-vita-toggle =+ !<(=vita-enabled:u vase) @@ -288,6 +304,8 @@ =? suggestions pals-running =+ .^(targets=(set ship) (scry %gx %pals /targets/noun)) (~(uni in suggestions) targets) + =. suggestions + (~(uni in suggestions) manual-contact-suggestions) (~(dif in suggestions) hidden-contact-suggestions) ++ import-pals =+ .^(pals-running=? (scry %gu %pals /$)) diff --git a/desk/mar/ships.hoon b/desk/mar/ships.hoon index e8a882d113..c04d494559 100644 --- a/desk/mar/ships.hoon +++ b/desk/mar/ships.hoon @@ -10,5 +10,6 @@ ++ grab |% ++ noun ,(set ship) + ++ json (ar:dejs:format ship:dejs:j) -- -- diff --git a/desk/mar/ui/add-contact-suggestions.hoon b/desk/mar/ui/add-contact-suggestions.hoon new file mode 100644 index 0000000000..5e6a1552f7 --- /dev/null +++ b/desk/mar/ui/add-contact-suggestions.hoon @@ -0,0 +1,2 @@ +/= mark /mar/ships +mark diff --git a/packages/app/features/top/ContactsScreen.tsx b/packages/app/features/top/ContactsScreen.tsx index 67a8ebd6a4..29ad9215b8 100644 --- a/packages/app/features/top/ContactsScreen.tsx +++ b/packages/app/features/top/ContactsScreen.tsx @@ -37,30 +37,34 @@ export default function ContactsScreen(props: Props) { ); const onContactLongPress = useCallback((contact: db.Contact) => { - if (!isWeb && contact.isContactSuggestion) { - Alert.alert(`Add ${getDisplayName(contact)}?`, '', [ - { - text: 'Cancel', - style: 'cancel', - }, - { - text: 'Add Contact', - style: 'default', - onPress: () => { - store.addContact(contact.id); - }, - }, - { - text: 'Decline Suggestion', - style: 'destructive', - onPress: () => { - store.removeContactSuggestion(contact.id); - }, - }, - ]); - } + store.addContactSuggestions([contact.id]); }, []); + // const onContactLongPress = useCallback((contact: db.Contact) => { + // if (!isWeb && contact.isContactSuggestion) { + // Alert.alert(`Add ${getDisplayName(contact)}?`, '', [ + // { + // text: 'Cancel', + // style: 'cancel', + // }, + // { + // text: 'Add Contact', + // style: 'default', + // onPress: () => { + // store.addContact(contact.id); + // }, + // }, + // { + // text: 'Decline Suggestion', + // style: 'destructive', + // onPress: () => { + // store.removeContactSuggestion(contact.id); + // }, + // }, + // ]); + // } + // }, []); + return ( diff --git a/packages/shared/src/api/contactsApi.ts b/packages/shared/src/api/contactsApi.ts index 9f720645e0..887c01b0c7 100644 --- a/packages/shared/src/api/contactsApi.ts +++ b/packages/shared/src/api/contactsApi.ts @@ -47,11 +47,20 @@ export const removeContactSuggestion = async (contactId: string) => { }); }; -export const addContacts = async (contactIds: string[]) => { +export const addContactSuggestions = async (contactIds: string[]) => { + console.log(`firing poke with`, contactIds); + return poke({ + app: 'groups-ui', + mark: 'ui-add-contact-suggestions', + json: contactIds, + }); +}; + +export const syncUserProfiles = async (userIds: string[]) => { return poke({ app: 'contacts', - mark: 'contact-action', - json: { heed: contactIds }, + mark: 'contact-action-1', + json: { meet: userIds }, }); }; diff --git a/packages/shared/src/db/keyValue.ts b/packages/shared/src/db/keyValue.ts index 2ca38c6472..c18055201b 100644 --- a/packages/shared/src/db/keyValue.ts +++ b/packages/shared/src/db/keyValue.ts @@ -298,6 +298,11 @@ export const finishingSelfHostedLogin = createStorageItem({ defaultValue: false, }); +export const groupsUsedForSuggestions = createStorageItem({ + key: 'groupsUsedForSuggestions', + defaultValue: [], +}); + export const postDraft = (opts: { key: string; type: 'caption' | 'text' | undefined; // matches GalleryDraftType diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index cce64ed748..e341ff5742 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -153,6 +153,32 @@ export const getGroupPreviews = createReadQuery( ['groups'] ); +export const getJoinedGroupsCount = createReadQuery( + 'getJoinedGroupCount', + async (ctx: QueryCtx) => { + const joinedGroups = await ctx.db.query.groups.findMany({ + where: eq($groups.currentUserIsMember, true), + }); + return joinedGroups.length; + }, + ['groups'] +); + +export const getGroupsWithMemberThreshold = createReadQuery( + 'getGroupsWithMemberThreshold', + async (threshold: number, ctx: QueryCtx) => { + const allJoinedWithMembers = await ctx.db.query.groups.findMany({ + where: eq($groups.currentUserIsMember, true), + with: { + members: true, + }, + }); + + return allJoinedWithMembers.filter((g) => g.members.length <= threshold); + }, + ['groups'] +); + export const getGroups = createReadQuery( 'getGroups', async ( diff --git a/packages/shared/src/store/contactActions.ts b/packages/shared/src/store/contactActions.ts index edbd8658eb..0ef53e4a37 100644 --- a/packages/shared/src/store/contactActions.ts +++ b/packages/shared/src/store/contactActions.ts @@ -1,8 +1,9 @@ import * as api from '../api'; import * as db from '../db'; import { createDevLogger } from '../debug'; +import { syncContacts, syncGroup } from './sync'; -const logger = createDevLogger('ContactActions', false); +const logger = createDevLogger('ContactActions', true); export async function addContact(contactId: string) { // Optimistic update @@ -71,6 +72,145 @@ export async function removeContactSuggestion(contactId: string) { } } +export async function addContactSuggestions(contactIds: string[]) { + // optimistic update + const contacts = await db.getContacts(); + const toUpdate = contacts.filter( + (c) => contactIds.includes(c.id) && !c.isContact + ); + const optimisticUpdates = toUpdate.map((contact) => + db.updateContact({ id: contact.id, isContactSuggestion: true }) + ); + await Promise.all(optimisticUpdates); + + try { + await api.addContactSuggestions(contactIds); + } catch (e) { + // Rollback the update + const rolbacks = toUpdate.map((contact) => + db.updateContact({ id: contact.id, isContactSuggestion: false }) + ); + await Promise.all(rolbacks); + } +} + +export async function findContactSuggestions() { + const runContext: Record = {}; + const currentUserId = api.getCurrentUserId(); + const GROUP_SIZE_LIMIT = 32; // arbitrary + const MAX_SUGGESTIONS = 6; // arbitrary + + try { + // first see if we have any joined groups and seem to be a somewhat + // new user + const groups = await db.getGroups({ includeUnjoined: false }); + runContext.joinedGroups = groups.length; + const hasFewGroups = groups.length < 4; + runContext.hasFewGroups = hasFewGroups; + + if (groups.length > 0 && hasFewGroups) { + logger.crumb('Found joined groups'); + // if yes, see if we have new groups and if some are small enough that + // grabbing suggestions at random might be worthwhile + const groupSyncs = groups.map((group) => syncGroup(group.id)); // sync member lists + await Promise.all(groupSyncs); + + const groupchats = + await db.getGroupsWithMemberThreshold(GROUP_SIZE_LIMIT); + runContext.groupsWithinSizeLimit = groupchats.length; + const groupsFromLastRun = await db.groupsUsedForSuggestions.getValue(); + const haveSomeNewGroups = groupchats.some( + (gc) => !groupsFromLastRun.includes(gc.id) + ); + runContext.haveSomeNewGroups = haveSomeNewGroups; + if (groupchats.length > 0 && haveSomeNewGroups) { + logger.crumb('Found groups under size limit'); + // if some are, load the profiles of all(?) members + const allRelevantMembers = groupchats + .reduce((acc, group) => { + return acc.concat(group.members.map((mem) => mem.contactId)); + }, [] as string[]) + .filter((mem) => mem !== currentUserId); + + logger.crumb(`Found ${allRelevantMembers.length} relevant members`); + + await api.syncUserProfiles(allRelevantMembers); + // hack: we don't track when the profiles actually populate, so wait a bit then resync + await new Promise((resolve) => setTimeout(resolve, 5000)); + await syncContacts(); + + logger.crumb('Synced profiles and contacts'); + + const contacts = await db.getContacts(); + const memberSet = new Set(allRelevantMembers); + const memberContacts = contacts.filter( + (c) => memberSet.has(c.id) && !c.isContact && !c.isContactSuggestion + ); + runContext.relevantMembers = memberContacts.length; + + // welcome to my suggestion ranking algorithm + const contactScores = memberContacts.map((contact) => { + let score = 0; + if (contact.nickname) { + score += 10; + } + + if (contact.pinnedGroups.length > 0) { + score += 5; + } + + if (contact.avatarImage) { + score += 3; + } + + if (contact.bio) { + score += 2; + } + + if (contact.status) { + score += 1; + } + + return { userId: contact.id, score }; + }); + + contactScores + .filter((item) => item.score > 0) + .sort((a, b) => b.score - a.score); + logger.crumb('Scored relevant members'); + + const suggestions = contactScores + .slice(0, MAX_SUGGESTIONS) + .map((s) => s.userId); + runContext.suggestions = suggestions.length; + + logger.crumb(`Found ${suggestions.length} suggestions`); + db.groupsUsedForSuggestions.setValue(groupchats.map((g) => g.id)); + + if (suggestions.length > 0) { + await addContactSuggestions(suggestions); + logger.trackEvent('Client Contact Suggestions', { + ...runContext, + suggestionsFound: true, + }); + return true; + } + } + } + logger.trackEvent('Client Contact Suggestions', { + ...runContext, + suggestionsFound: false, + }); + } catch (e) { + logger.trackError('Client Contact Suggestions Failure', { + errorMessage: e.message, + errorStack: e.stack, + }); + } + logger.log('No suggestions added'); + return false; +} + export async function updateContactMetadata( contactId: string, metadata: { diff --git a/packages/shared/src/store/dbHooks.ts b/packages/shared/src/store/dbHooks.ts index e357e5f33b..b1f1768554 100644 --- a/packages/shared/src/store/dbHooks.ts +++ b/packages/shared/src/store/dbHooks.ts @@ -328,6 +328,14 @@ export const useGroup = (options: { id?: string }) => { }); }; +export const useJoinedGroupsCount = () => { + const deps = useKeyFromQueryDeps(db.getJoinedGroupsCount); + return useQuery({ + queryKey: ['joinedGroupsCount', deps], + queryFn: () => db.getJoinedGroupsCount(), + }); +}; + export const useGroupByChannel = (channelId: string) => { return useQuery({ queryKey: [['group', channelId]], diff --git a/packages/ui/src/components/ContactsScreenView.tsx b/packages/ui/src/components/ContactsScreenView.tsx index a93ff9aa30..f4a5b4b424 100644 --- a/packages/ui/src/components/ContactsScreenView.tsx +++ b/packages/ui/src/components/ContactsScreenView.tsx @@ -25,12 +25,6 @@ interface Section { export function ContactsScreenView(props: Props) { const currentUserId = useCurrentUserId(); const userContact = useContact(currentUserId); - const trimmedSuggested = useMemo(() => { - if (props.suggestions.length < 4 || props.contacts.length === 0) { - return props.suggestions; - } - return props.suggestions.slice(0, 4); - }, [props.contacts, props.suggestions]); const sortedContacts = useSortedContacts({ contacts: props.contacts, @@ -52,15 +46,15 @@ export function ContactsScreenView(props: Props) { }); } - if (trimmedSuggested.length > 0) { + if (props.suggestions.length > 0) { result.push({ title: 'Suggested from %pals and DMs', - data: trimmedSuggested, + data: props.suggestions, }); } return result; - }, [userContact, sortedContacts, trimmedSuggested]); + }, [userContact, sortedContacts, props.suggestions]); const renderItem = useCallback( ({ item }: { item: db.Contact }) => { From 8a33678dd137b8bbe3be65ed87db5123da637a57 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 00:36:56 -0600 Subject: [PATCH 068/149] cleanup --- packages/app/features/top/ContactsScreen.tsx | 48 +++++++++----------- packages/shared/src/store/contactActions.ts | 2 +- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/packages/app/features/top/ContactsScreen.tsx b/packages/app/features/top/ContactsScreen.tsx index 29ad9215b8..67a8ebd6a4 100644 --- a/packages/app/features/top/ContactsScreen.tsx +++ b/packages/app/features/top/ContactsScreen.tsx @@ -37,34 +37,30 @@ export default function ContactsScreen(props: Props) { ); const onContactLongPress = useCallback((contact: db.Contact) => { - store.addContactSuggestions([contact.id]); + if (!isWeb && contact.isContactSuggestion) { + Alert.alert(`Add ${getDisplayName(contact)}?`, '', [ + { + text: 'Cancel', + style: 'cancel', + }, + { + text: 'Add Contact', + style: 'default', + onPress: () => { + store.addContact(contact.id); + }, + }, + { + text: 'Decline Suggestion', + style: 'destructive', + onPress: () => { + store.removeContactSuggestion(contact.id); + }, + }, + ]); + } }, []); - // const onContactLongPress = useCallback((contact: db.Contact) => { - // if (!isWeb && contact.isContactSuggestion) { - // Alert.alert(`Add ${getDisplayName(contact)}?`, '', [ - // { - // text: 'Cancel', - // style: 'cancel', - // }, - // { - // text: 'Add Contact', - // style: 'default', - // onPress: () => { - // store.addContact(contact.id); - // }, - // }, - // { - // text: 'Decline Suggestion', - // style: 'destructive', - // onPress: () => { - // store.removeContactSuggestion(contact.id); - // }, - // }, - // ]); - // } - // }, []); - return ( diff --git a/packages/shared/src/store/contactActions.ts b/packages/shared/src/store/contactActions.ts index 0ef53e4a37..b780321fc6 100644 --- a/packages/shared/src/store/contactActions.ts +++ b/packages/shared/src/store/contactActions.ts @@ -3,7 +3,7 @@ import * as db from '../db'; import { createDevLogger } from '../debug'; import { syncContacts, syncGroup } from './sync'; -const logger = createDevLogger('ContactActions', true); +const logger = createDevLogger('ContactActions', false); export async function addContact(contactId: string) { // Optimistic update From 83ff238eab4141b5a2e0a4cd571600c029d21c73 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 00:54:47 -0600 Subject: [PATCH 069/149] stray log --- packages/shared/src/api/contactsApi.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shared/src/api/contactsApi.ts b/packages/shared/src/api/contactsApi.ts index 887c01b0c7..84eabbdb05 100644 --- a/packages/shared/src/api/contactsApi.ts +++ b/packages/shared/src/api/contactsApi.ts @@ -48,7 +48,6 @@ export const removeContactSuggestion = async (contactId: string) => { }; export const addContactSuggestions = async (contactIds: string[]) => { - console.log(`firing poke with`, contactIds); return poke({ app: 'groups-ui', mark: 'ui-add-contact-suggestions', From 236dbf36ba813c0a8baaf7156e1f111ed1fa0aad Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 4 Dec 2024 12:03:21 +0000 Subject: [PATCH 070/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index f53e8e7c11..212cb7fb62 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v5.4lprj.7br43.c988m.3l0j7.cdlg9.glob' 0v5.4lprj.7br43.c988m.3l0j7.cdlg9] + glob-http+['https://bootstrap.urbit.org/glob-0v4.i1kok.3mut2.9mias.a25ls.2tirb.glob' 0v4.i1kok.3mut2.9mias.a25ls.2tirb] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 28a2e22b740da7db549c1f1cf447ddd16904ae95 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 09:37:46 -0600 Subject: [PATCH 071/149] always show self on contact screen --- packages/ui/src/components/ContactsScreenView.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/components/ContactsScreenView.tsx b/packages/ui/src/components/ContactsScreenView.tsx index a93ff9aa30..a283e4eeed 100644 --- a/packages/ui/src/components/ContactsScreenView.tsx +++ b/packages/ui/src/components/ContactsScreenView.tsx @@ -40,11 +40,9 @@ export function ContactsScreenView(props: Props) { const sections = useMemo(() => { const result: Section[] = []; - if (userContact) { - result.push({ - data: [userContact], - }); - } + result.push({ + data: [userContact ?? db.getFallbackContact(currentUserId)], + }); if (sortedContacts.length > 0) { result.push({ @@ -60,11 +58,11 @@ export function ContactsScreenView(props: Props) { } return result; - }, [userContact, sortedContacts, trimmedSuggested]); + }, [userContact, currentUserId, sortedContacts, trimmedSuggested]); const renderItem = useCallback( ({ item }: { item: db.Contact }) => { - const isSelf = item.id === userContact?.id; + const isSelf = item.id === currentUserId; return ( Date: Wed, 4 Dec 2024 10:59:25 -0600 Subject: [PATCH 072/149] ding the bell for non-notifying contact events --- packages/shared/src/db/queries.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index 4a7f93516a..bb92971c53 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -3180,16 +3180,27 @@ export const getUnreadUnseenActivityEvents = createReadQuery( .where( and( gt($activityEvents.timestamp, seenMarker), - eq($activityEvents.shouldNotify, true), or( - and(eq($activityEvents.type, 'reply'), gt($threadUnreads.count, 0)), - and(eq($activityEvents.type, 'post'), gt($channelUnreads.count, 0)), + eq($activityEvents.type, 'contact'), and( - gt($groupUnreads.notifyCount, 0), + eq($activityEvents.shouldNotify, true), or( - eq($activityEvents.type, 'group-ask'), - eq($activityEvents.type, 'flag-post'), - eq($activityEvents.type, 'flag-reply') + and( + eq($activityEvents.type, 'reply'), + gt($threadUnreads.count, 0) + ), + and( + eq($activityEvents.type, 'post'), + gt($channelUnreads.count, 0) + ), + and( + gt($groupUnreads.notifyCount, 0), + or( + eq($activityEvents.type, 'group-ask'), + eq($activityEvents.type, 'flag-post'), + eq($activityEvents.type, 'flag-reply') + ) + ) ) ) ) From ed83c2324f7322de90160c2059f14e834f87e64a Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 4 Dec 2024 11:39:03 -0600 Subject: [PATCH 073/149] hooks: pr cleanup --- desk/lib/hooks-json.hoon | 12 ++++++------ desk/sur/hooks.hoon | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/desk/lib/hooks-json.hoon b/desk/lib/hooks-json.hoon index dabca9af5d..10946c0d81 100644 --- a/desk/lib/hooks-json.hoon +++ b/desk/lib/hooks-json.hoon @@ -69,15 +69,15 @@ %- pairs %+ turn ~(tap by cr) - |= [=origin:h jb=job:h] - :_ (job jb) + |= [=origin:h =job:h] + :_ (^job job) ?@(origin 'global' (nest-cord:enjs:cj origin)) ++ job - |= jb=job:h + |= =job:h %- pairs - :~ hook+(id id-hook.jb) - schedule+(schedule schedule.jb) - config+(config config.jb) + :~ hook+(id id-hook.job) + schedule+(schedule schedule.job) + config+(config config.job) == ++ schedule |= sch=schedule:h diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 93a6aa3344..02875011de 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -73,7 +73,7 @@ [%rest id=id-hook =origin] == :: $bowl: ambient state that a hook should know about not -:: necessarily tied to a specific event +:: necessarily tied to a specific event :: :: .channel: the channel that the hook is operating on :: .group: the group that the channel belongs to From 5a32dbd6d8b5f121b6883c0db626652b5853acc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?b=E1=B5=A3=E1=B5=A2=E2=82=90=E2=82=99?= <90741358+latter-bolden@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:02:58 -0600 Subject: [PATCH 074/149] Update desk/app/groups-ui.hoon Co-authored-by: fang --- desk/app/groups-ui.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/app/groups-ui.hoon b/desk/app/groups-ui.hoon index 050911842e..5b397d27af 100644 --- a/desk/app/groups-ui.hoon +++ b/desk/app/groups-ui.hoon @@ -241,7 +241,7 @@ %ui-add-contact-suggestions =+ ship-list=!<((list @p) vase) =. manual-contact-suggestions - (~(uni in manual-contact-suggestions) (sy ship-list)) + (~(gas in manual-contact-suggestions) ship-list) cor :: %ui-vita-toggle From 752e0a3106a54fbecf9cdbc3de508a8f15875546 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 12:10:07 -0600 Subject: [PATCH 075/149] initialize variable to manual suggestions rather than bunt + add them in later --- desk/app/groups-ui.hoon | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/desk/app/groups-ui.hoon b/desk/app/groups-ui.hoon index 5b397d27af..4646bef2b3 100644 --- a/desk/app/groups-ui.hoon +++ b/desk/app/groups-ui.hoon @@ -285,7 +285,7 @@ == ++ get-suggested-contacts =+ .^(chat-running=? (scry %gu %chat /$)) - =| suggestions=(set ship) + =/ suggestions=(set ship) manual-contact-suggestions =? suggestions chat-running =+ .^ [dms=(map ship dm:c) *] (scry %gx %chat /full/noun) @@ -304,8 +304,6 @@ =? suggestions pals-running =+ .^(targets=(set ship) (scry %gx %pals /targets/noun)) (~(uni in suggestions) targets) - =. suggestions - (~(uni in suggestions) manual-contact-suggestions) (~(dif in suggestions) hidden-contact-suggestions) ++ import-pals =+ .^(pals-running=? (scry %gu %pals /$)) From afa2b0e78ac2bc41430b732792b7a381abe21fd4 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 14:22:17 -0600 Subject: [PATCH 076/149] move findContacts to sync start, remove unneeded hooks, make note of member query perf for future optimization --- apps/tlon-mobile/src/components/AuthenticatedApp.tsx | 2 -- .../src/hooks/useFindContactSuggestions.ts | 10 ---------- packages/shared/src/db/queries.ts | 12 +----------- packages/shared/src/store/contactActions.ts | 7 ++----- packages/shared/src/store/dbHooks.ts | 8 -------- packages/shared/src/store/sync.ts | 5 +++++ 6 files changed, 8 insertions(+), 36 deletions(-) delete mode 100644 apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts diff --git a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx index add787441a..c60cab2bd4 100644 --- a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx +++ b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx @@ -15,7 +15,6 @@ import { AppStateStatus } from 'react-native'; import { useCheckAppUpdated } from '../hooks/analytics'; import { useDeepLinkListener } from '../hooks/useDeepLinkListener'; -import useFindContactSuggestions from '../hooks/useFindContactSuggestions'; import useNotificationListener from '../hooks/useNotificationListener'; function AuthenticatedApp() { @@ -30,7 +29,6 @@ function AuthenticatedApp() { useNavigationLogging(); useNetworkLogger(); useCheckAppUpdated(); - useFindContactSuggestions(); useEffect(() => { configureClient(); diff --git a/apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts b/apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts deleted file mode 100644 index f335958915..0000000000 --- a/apps/tlon-mobile/src/hooks/useFindContactSuggestions.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as store from '@tloncorp/shared/store'; -import { useEffect } from 'react'; - -export default function useFindContactSuggestions() { - const { data: numJoinedGroups } = store.useJoinedGroupsCount(); - - useEffect(() => { - store.findContactSuggestions(); - }, [numJoinedGroups]); -} diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index e341ff5742..373aa48ff9 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -153,17 +153,7 @@ export const getGroupPreviews = createReadQuery( ['groups'] ); -export const getJoinedGroupsCount = createReadQuery( - 'getJoinedGroupCount', - async (ctx: QueryCtx) => { - const joinedGroups = await ctx.db.query.groups.findMany({ - where: eq($groups.currentUserIsMember, true), - }); - return joinedGroups.length; - }, - ['groups'] -); - +// TODO: inefficient, should optimize export const getGroupsWithMemberThreshold = createReadQuery( 'getGroupsWithMemberThreshold', async (threshold: number, ctx: QueryCtx) => { diff --git a/packages/shared/src/store/contactActions.ts b/packages/shared/src/store/contactActions.ts index b780321fc6..93a5bf333c 100644 --- a/packages/shared/src/store/contactActions.ts +++ b/packages/shared/src/store/contactActions.ts @@ -86,11 +86,8 @@ export async function addContactSuggestions(contactIds: string[]) { try { await api.addContactSuggestions(contactIds); } catch (e) { - // Rollback the update - const rolbacks = toUpdate.map((contact) => - db.updateContact({ id: contact.id, isContactSuggestion: false }) - ); - await Promise.all(rolbacks); + // Intentionally unhandled, make a best effort to persist the suggestions + // failure is acceptable } } diff --git a/packages/shared/src/store/dbHooks.ts b/packages/shared/src/store/dbHooks.ts index b1f1768554..e357e5f33b 100644 --- a/packages/shared/src/store/dbHooks.ts +++ b/packages/shared/src/store/dbHooks.ts @@ -328,14 +328,6 @@ export const useGroup = (options: { id?: string }) => { }); }; -export const useJoinedGroupsCount = () => { - const deps = useKeyFromQueryDeps(db.getJoinedGroupsCount); - return useQuery({ - queryKey: ['joinedGroupsCount', deps], - queryFn: () => db.getJoinedGroupsCount(), - }); -}; - export const useGroupByChannel = (channelId: string) => { return useQuery({ queryKey: [['group', channelId]], diff --git a/packages/shared/src/store/sync.ts b/packages/shared/src/store/sync.ts index bed4de2db6..034e4ace09 100644 --- a/packages/shared/src/store/sync.ts +++ b/packages/shared/src/store/sync.ts @@ -12,6 +12,7 @@ import { INFINITE_ACTIVITY_QUERY_KEY, resetActivityFetchers, } from '../store/useActivityFetchers'; +import { findContactSuggestions } from './contactActions'; import { useLureState } from './lure'; import { getSyncing, updateIsSyncing, updateSession } from './session'; import { SyncCtx, SyncPriority, syncQueue } from './syncQueue'; @@ -1174,6 +1175,10 @@ export const syncStart = async (alreadySubscribed?: boolean) => { }); updateIsSyncing(false); + + // finding contacts is a bit of an outlier here, but it's work we need to do + // that can roughly be batched whenever we sync + findContactSuggestions(); }; export const setupHighPrioritySubscriptions = async (ctx?: SyncCtx) => { From 7b8beeb17397becbf3ea228ea485b551d3cd27c2 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Wed, 4 Dec 2024 14:29:40 -0600 Subject: [PATCH 077/149] channels-server: wrong side spacer --- desk/app/channels-server.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 154f855d7b..142f725ec1 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1185,10 +1185,10 @@ :: ignore premature fires ?: (lth now.bowl next.schedule.u.job) cor =. next.schedule.u.job - :: :: we don't want to run the cron for every iteration it would :: have run 'offline', so we check here to make sure that the :: next fire time is in the future + :: =/ next (add [next repeat]:schedule.u.job) |- ?: (gte next now.bowl) next From a49e05f11c27eb57ff05a73c0e22056c5eb70ddc Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 4 Dec 2024 20:39:20 +0000 Subject: [PATCH 078/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 212cb7fb62..1a2420cd5f 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.i1kok.3mut2.9mias.a25ls.2tirb.glob' 0v4.i1kok.3mut2.9mias.a25ls.2tirb] + glob-http+['https://bootstrap.urbit.org/glob-0v4.kp54k.so8v3.2j5tk.il63s.mncik.glob' 0v4.kp54k.so8v3.2j5tk.il63s.mncik] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 20bff2c40f570d1c5b1a741a9140e43469c3e6f6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 4 Dec 2024 20:39:30 +0000 Subject: [PATCH 079/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index d970d9b615..fcc5591c04 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.88lmm.hdoo6.b26b4.d75rp.9r46j.glob' 0v6.88lmm.hdoo6.b26b4.d75rp.9r46j] + glob-http+['https://bootstrap.urbit.org/glob-0v4.2d1ml.0k3no.2240e.tfud1.uhn4l.glob' 0v4.2d1ml.0k3no.2240e.tfud1.uhn4l] base+'groups' version+[6 5 0] website+'https://tlon.io' From 29560c5682bbf86cbedb246cdf43dcf10e2261f5 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 4 Dec 2024 14:47:05 -0600 Subject: [PATCH 080/149] desktop: highlight current channel in sidebar, fixes tlon-3307 --- packages/app/features/top/ChatListScreen.tsx | 3 +++ packages/app/features/top/GroupChannelsScreen.tsx | 3 +++ packages/app/navigation/desktop/HomeNavigator.tsx | 12 ++++++++++++ packages/ui/src/components/ChannelNavSection.tsx | 3 +++ packages/ui/src/components/ChannelNavSections.tsx | 5 +++++ packages/ui/src/components/ChatList.tsx | 6 +++++- .../ui/src/components/GroupChannelsScreenView.tsx | 3 +++ .../ui/src/components/ListItem/ChannelListItem.tsx | 2 ++ .../components/ListItem/InteractableChatListItem.tsx | 9 ++++++++- packages/ui/src/components/ListItem/ListItem.tsx | 1 + 10 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx index dabd2a5873..66afcd6d51 100644 --- a/packages/app/features/top/ChatListScreen.tsx +++ b/packages/app/features/top/ChatListScreen.tsx @@ -45,8 +45,10 @@ export default function ChatListScreen(props: Props) { export function ChatListScreenView({ previewGroupId, + focusedChannelId, }: { previewGroupId?: string; + focusedChannelId?: string; }) { const navigation = useNavigation>(); const [addGroupOpen, setAddGroupOpen] = useState(false); @@ -296,6 +298,7 @@ export function ChatListScreenView({ onSearchToggle={handleSearchInputToggled} searchQuery={searchQuery} onSearchQueryChange={setSearchQuery} + focusedChannelId={focusedChannelId} /> ) : null} diff --git a/packages/app/features/top/GroupChannelsScreen.tsx b/packages/app/features/top/GroupChannelsScreen.tsx index 0dfaef0236..3cc6038c9f 100644 --- a/packages/app/features/top/GroupChannelsScreen.tsx +++ b/packages/app/features/top/GroupChannelsScreen.tsx @@ -26,8 +26,10 @@ export function GroupChannelsScreen({ route }: Props) { export function GroupChannelsScreenContent({ groupId: id, + focusedChannelId, }: { groupId: string; + focusedChannelId?: string; }) { const navigation = useNavigation>(); const isFocused = useIsFocused(); @@ -83,6 +85,7 @@ export function GroupChannelsScreenContent({ group={group} unjoinedChannels={unjoinedChannels} enableCustomChannels={enableCustomChannels} + focusedChannelId={focusedChannelId} /> + ); + } return ; + } else if (focusedRoute.params && 'channelId' in focusedRoute.params) { + return ( + + ); } else { return ; } diff --git a/packages/ui/src/components/ChannelNavSection.tsx b/packages/ui/src/components/ChannelNavSection.tsx index ddd036086a..62910bcffb 100644 --- a/packages/ui/src/components/ChannelNavSection.tsx +++ b/packages/ui/src/components/ChannelNavSection.tsx @@ -9,11 +9,13 @@ export default function ChannelNavSection({ channels, onSelect, onLongPress, + focusedChannelId, }: { section: db.GroupNavSection; channels: db.Channel[]; onSelect: (channel: any) => void; onLongPress?: (channel: any) => void; + focusedChannelId?: string; }) { const sectionChannels = useMemo( () => @@ -51,6 +53,7 @@ export default function ChannelNavSection({ useTypeIcon={true} onPress={onSelect} onLongPress={onLongPress} + isFocused={focusedChannelId === item.channelId} /> ))} diff --git a/packages/ui/src/components/ChannelNavSections.tsx b/packages/ui/src/components/ChannelNavSections.tsx index 0a3f7a68c9..ec765b5b30 100644 --- a/packages/ui/src/components/ChannelNavSections.tsx +++ b/packages/ui/src/components/ChannelNavSections.tsx @@ -12,6 +12,7 @@ export default function ChannelNavSections({ sortBy, paddingBottom, onLongPress, + focusedChannelId, }: { group: db.Group; channels: db.Channel[]; @@ -19,6 +20,7 @@ export default function ChannelNavSections({ sortBy: 'recency' | 'arranged'; paddingBottom?: number; onLongPress?: (channel: any) => void; + focusedChannelId?: string; }) { const unGroupedChannels = useMemo( () => @@ -61,6 +63,7 @@ export default function ChannelNavSections({ onPress={onSelect} useTypeIcon={true} onLongPress={onLongPress} + isFocused={item.id === focusedChannelId} /> ))} @@ -85,6 +88,7 @@ export default function ChannelNavSections({ channels={sectionChannels} onSelect={onSelect} onLongPress={onLongPress} + focusedChannelId={focusedChannelId} /> ); })} @@ -105,6 +109,7 @@ export default function ChannelNavSections({ onPress={onSelect} onLongPress={onLongPress} useTypeIcon={true} + isFocused={item.id === focusedChannelId} /> ))} diff --git a/packages/ui/src/components/ChatList.tsx b/packages/ui/src/components/ChatList.tsx index 19ff7d79c3..4cb468798e 100644 --- a/packages/ui/src/components/ChatList.tsx +++ b/packages/ui/src/components/ChatList.tsx @@ -41,6 +41,7 @@ export const ChatList = React.memo(function ChatListComponent({ searchQuery, onSearchQueryChange, onSearchToggle, + focusedChannelId, }: db.GroupedChats & { onPressItem?: (chat: db.Chat) => void; onSectionChange?: (title: string) => void; @@ -50,6 +51,7 @@ export const ChatList = React.memo(function ChatListComponent({ searchQuery: string; onSearchQueryChange: (query: string) => void; onSearchToggle: () => void; + focusedChannelId?: string; }) { const displayData = useFilteredChats({ pinned, @@ -100,6 +102,7 @@ export const ChatList = React.memo(function ChatListComponent({ model={item} onPress={onPressItem} onLongPress={handleLongPress} + isFocused={item.id === focusedChannelId} /> ); } else { @@ -108,11 +111,12 @@ export const ChatList = React.memo(function ChatListComponent({ model={item} onPress={onPressItem} onLongPress={handleLongPress} + isFocused={item.id === focusedChannelId} /> ); } }, - [onPressItem, handleLongPress] + [onPressItem, handleLongPress, focusedChannelId] ); const handlePressTryAll = useCallback(() => { diff --git a/packages/ui/src/components/GroupChannelsScreenView.tsx b/packages/ui/src/components/GroupChannelsScreenView.tsx index 7b61f2102a..7182008d6f 100644 --- a/packages/ui/src/components/GroupChannelsScreenView.tsx +++ b/packages/ui/src/components/GroupChannelsScreenView.tsx @@ -20,6 +20,7 @@ type GroupChannelsScreenViewProps = { onJoinChannel: (channel: db.Channel) => void; onBackPressed: () => void; enableCustomChannels?: boolean; + focusedChannelId?: string; }; export function GroupChannelsScreenView({ @@ -29,6 +30,7 @@ export function GroupChannelsScreenView({ onJoinChannel, onBackPressed, enableCustomChannels = false, + focusedChannelId, }: GroupChannelsScreenViewProps) { const [showCreateChannel, setShowCreateChannel] = useState(false); const sortBy = db.channelSortPreference.useValue(); @@ -106,6 +108,7 @@ export function GroupChannelsScreenView({ onSelect={onChannelPressed} sortBy={sortBy || 'recency'} onLongPress={handleOpenChannelOptions} + focusedChannelId={focusedChannelId} /> {unjoinedChannels.length > 0 && ( diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 497a937a05..0d51a0f091 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -19,6 +19,7 @@ export function ChannelListItem({ onLongPress, EndContent, dimmed, + isFocused, ...props }: { useTypeIcon?: boolean; @@ -63,6 +64,7 @@ export function ChannelListItem({ borderRadius="$xl" onPress={handlePress} onLongPress={handleLongPress} + backgroundColor={isFocused ? '$secondaryBackground' : undefined} > ) { const swipeableRef = useRef(null); const [currentSwipeDirection, setCurrentSwipeDirection] = useState< @@ -136,12 +137,18 @@ function BaseInteractableChatRow({ model={model} onPress={onPress} onLongPress={onLongPress} + {...props} /> ); } else { return ( - + ); } } diff --git a/packages/ui/src/components/ListItem/ListItem.tsx b/packages/ui/src/components/ListItem/ListItem.tsx index 8edd5989fe..5d3d4bb237 100644 --- a/packages/ui/src/components/ListItem/ListItem.tsx +++ b/packages/ui/src/components/ListItem/ListItem.tsx @@ -29,6 +29,7 @@ export interface BaseListItemProps { onPress?: (model: T) => void; onLongPress?: (model: T) => void; unreadCount?: number; + isFocused?: boolean; } export type ListItemProps = BaseListItemProps & From ff8a4e6209a3caced7738689056a1c1e8c31aaf4 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 14:51:41 -0600 Subject: [PATCH 081/149] remove badging for now --- packages/app/lib/notifications.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/app/lib/notifications.ts b/packages/app/lib/notifications.ts index 680117b10e..a3cd7ec8a6 100644 --- a/packages/app/lib/notifications.ts +++ b/packages/app/lib/notifications.ts @@ -117,9 +117,10 @@ async function updatePresentedNotifications(badgeCount?: number) { }) ); - const count = - badgeCount ?? (await Notifications.getPresentedNotificationsAsync()).length; - await Notifications.setBadgeCountAsync(count); + // NOTE: removing badging for now + // const count = + // badgeCount ?? (await Notifications.getPresentedNotificationsAsync()).length; + // await Notifications.setBadgeCountAsync(count); } export function useUpdatePresentedNotifications() { From 4f561db2f3a9cccf311d2550fa2360b05c60f2ac Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Wed, 4 Dec 2024 15:04:37 -0600 Subject: [PATCH 082/149] comment out notification manager code as well --- .../Shared/Notifications/PushNotificationManager.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift b/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift index ef83f5128e..42eeb7a5a7 100644 --- a/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift +++ b/apps/tlon-mobile/ios/Shared/Notifications/PushNotificationManager.swift @@ -138,11 +138,11 @@ enum NotificationCategory: String { content.threadIdentifier = yarn.rope.thread content.title = await yarn.getTitle() content.body = yarn.body - content.badge = await withUnsafeContinuation { cnt in - UNUserNotificationCenter.current().getDeliveredNotifications { notifs in - cnt.resume(returning: NSNumber(value: notifs.count + 1)) - } - } + // content.badge = await withUnsafeContinuation { cnt in + // UNUserNotificationCenter.current().getDeliveredNotifications { notifs in + // cnt.resume(returning: NSNumber(value: notifs.count + 1)) + // } + // } content.categoryIdentifier = yarn.category.rawValue content.userInfo = yarn.userInfo content.sound = UNNotificationSound.default From ddc290022ed5c1a4c945df1a9fb8567c50793fe0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 4 Dec 2024 21:20:49 +0000 Subject: [PATCH 083/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index fcc5591c04..111ab94919 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.2d1ml.0k3no.2240e.tfud1.uhn4l.glob' 0v4.2d1ml.0k3no.2240e.tfud1.uhn4l] + glob-http+['https://bootstrap.urbit.org/glob-0v4.irfsv.sq9vg.l6b96.rj7vs.74118.glob' 0v4.irfsv.sq9vg.l6b96.rj7vs.74118] base+'groups' version+[6 5 0] website+'https://tlon.io' From e083e18421b34eecdc914c165bbf74091af3e33f Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 4 Dec 2024 21:21:22 +0000 Subject: [PATCH 084/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 1a2420cd5f..79ad23b890 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.kp54k.so8v3.2j5tk.il63s.mncik.glob' 0v4.kp54k.so8v3.2j5tk.il63s.mncik] + glob-http+['https://bootstrap.urbit.org/glob-0v1.rkfda.u301l.vk9sa.av06i.1gbmf.glob' 0v1.rkfda.u301l.vk9sa.av06i.1gbmf] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 273b6821c81f736e861e55b60365317e9ab8ed19 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Wed, 4 Dec 2024 15:25:24 -0600 Subject: [PATCH 085/149] desktop: top level buttons should reset navigation, fixes tlon-3344 --- packages/app/navigation/desktop/TopLevelDrawer.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/app/navigation/desktop/TopLevelDrawer.tsx b/packages/app/navigation/desktop/TopLevelDrawer.tsx index 2e8e3b414a..ebb89736e5 100644 --- a/packages/app/navigation/desktop/TopLevelDrawer.tsx +++ b/packages/app/navigation/desktop/TopLevelDrawer.tsx @@ -34,19 +34,25 @@ const DrawerContent = (props: DrawerContentComponentProps) => { // hasUnreads={(unreadCount?.channels ?? 0) > 0} // intentionally leave undotted for now hasUnreads={false} - onPress={() => props.navigation.navigate('Home')} + onPress={() => + props.navigation.reset({ index: 0, routes: [{ name: 'Home' }] }) + } /> props.navigation.navigate('Activity')} + onPress={() => + props.navigation.reset({ index: 0, routes: [{ name: 'Activity' }] }) + } /> props.navigation.navigate('Profile')} + onPress={() => + props.navigation.reset({ index: 0, routes: [{ name: 'Profile' }] }) + } /> {webAppNeedsUpdate && ( Date: Wed, 4 Dec 2024 15:55:03 -0600 Subject: [PATCH 086/149] desktop: navigate user to last visited channel when navigating into a group, fixes tlon-3305 --- packages/app/features/top/ChannelScreen.tsx | 10 ++++++++- packages/app/navigation/utils.ts | 22 +++++++++++++++---- ...ly_tinkerer.sql => 0000_loving_namora.sql} | 3 ++- .../src/db/migrations/meta/0000_snapshot.json | 9 +++++++- .../src/db/migrations/meta/_journal.json | 4 ++-- .../shared/src/db/migrations/migrations.js | 2 +- packages/shared/src/db/queries.ts | 14 ++++++++++++ packages/shared/src/db/schema.ts | 1 + 8 files changed, 55 insertions(+), 10 deletions(-) rename packages/shared/src/db/migrations/{0000_lowly_tinkerer.sql => 0000_loving_namora.sql} (99%) diff --git a/packages/app/features/top/ChannelScreen.tsx b/packages/app/features/top/ChannelScreen.tsx index 148237ba04..1cd05348f6 100644 --- a/packages/app/features/top/ChannelScreen.tsx +++ b/packages/app/features/top/ChannelScreen.tsx @@ -71,6 +71,14 @@ export default function ChannelScreen(props: Props) { store.syncChannelThreadUnreads(channel.id, { priority: store.SyncPriority.High, }); + if (group) { + // Update the last visited channel in the group so we can return to it + // when we come back to the group + db.updateLastVisitedChannelInGroup({ + groupId: group.id, + channelId: channel.id, + }); + } } // Mark the channel as visited when we unfocus/leave this screen () => { @@ -78,7 +86,7 @@ export default function ChannelScreen(props: Props) { store.markChannelVisited(channel); } }; - }, [channel]) + }, [channel, group]) ); const [channelNavOpen, setChannelNavOpen] = React.useState(false); diff --git a/packages/app/navigation/utils.ts b/packages/app/navigation/utils.ts index e6f0c493e0..5d159aff34 100644 --- a/packages/app/navigation/utils.ts +++ b/packages/app/navigation/utils.ts @@ -6,6 +6,7 @@ import { import * as db from '@tloncorp/shared/db'; import * as logic from '@tloncorp/shared/logic'; import * as store from '@tloncorp/shared/store'; +import { useIsWindowNarrow } from '@tloncorp/ui'; import { useCallback } from 'react'; import { useFeatureFlagStore } from '../lib/featureFlags'; @@ -92,25 +93,38 @@ export function useResetToGroup() { } export function useNavigateToGroup() { + const isWindowNarrow = useIsWindowNarrow(); const navigation = useNavigation(); const navigationRef = logic.useMutableRef(navigation); return useCallback( async (groupId: string) => { - navigationRef.current.navigate(await getMainGroupRoute(groupId)); + navigationRef.current.navigate( + await getMainGroupRoute(groupId, isWindowNarrow) + ); }, - [navigationRef] + [navigationRef, isWindowNarrow] ); } -export async function getMainGroupRoute(groupId: string) { +export async function getMainGroupRoute( + groupId: string, + isWindowNarrow?: boolean +) { const group = await db.getGroup({ id: groupId }); const channelSwitcherEnabled = useFeatureFlagStore.getState().flags.channelSwitcher; if ( group && group.channels && - (group.channels.length === 1 || channelSwitcherEnabled) + (group.channels.length === 1 || channelSwitcherEnabled || !isWindowNarrow) ) { + if (!isWindowNarrow && group.lastVisitedChannelId) { + return { + name: 'Channel', + params: { channelId: group.lastVisitedChannelId, groupId }, + } as const; + } + return { name: 'Channel', params: { channelId: group.channels[0].id, groupId }, diff --git a/packages/shared/src/db/migrations/0000_lowly_tinkerer.sql b/packages/shared/src/db/migrations/0000_loving_namora.sql similarity index 99% rename from packages/shared/src/db/migrations/0000_lowly_tinkerer.sql rename to packages/shared/src/db/migrations/0000_loving_namora.sql index 3391562df1..6953459b7d 100644 --- a/packages/shared/src/db/migrations/0000_lowly_tinkerer.sql +++ b/packages/shared/src/db/migrations/0000_loving_namora.sql @@ -226,7 +226,8 @@ CREATE TABLE `groups` ( `is_new` integer, `join_status` text, `last_post_id` text, - `last_post_at` integer + `last_post_at` integer, + `last_visited_channel_id` text ); --> statement-breakpoint CREATE TABLE `pins` ( diff --git a/packages/shared/src/db/migrations/meta/0000_snapshot.json b/packages/shared/src/db/migrations/meta/0000_snapshot.json index 30cf78b3ab..f63b6445e7 100644 --- a/packages/shared/src/db/migrations/meta/0000_snapshot.json +++ b/packages/shared/src/db/migrations/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "2ec6d840-6e53-4d97-bf4c-b4d00a248728", + "id": "74260723-19aa-401e-92d0-c790bcc15a53", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "activity_event_contact_group_pins": { @@ -1520,6 +1520,13 @@ "primaryKey": false, "notNull": false, "autoincrement": false + }, + "last_visited_channel_id": { + "name": "last_visited_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false } }, "indexes": {}, diff --git a/packages/shared/src/db/migrations/meta/_journal.json b/packages/shared/src/db/migrations/meta/_journal.json index 252394b402..ee7b772857 100644 --- a/packages/shared/src/db/migrations/meta/_journal.json +++ b/packages/shared/src/db/migrations/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1732574199336, - "tag": "0000_lowly_tinkerer", + "when": 1733348853492, + "tag": "0000_loving_namora", "breakpoints": true } ] diff --git a/packages/shared/src/db/migrations/migrations.js b/packages/shared/src/db/migrations/migrations.js index 34781b5268..94bde2c6a2 100644 --- a/packages/shared/src/db/migrations/migrations.js +++ b/packages/shared/src/db/migrations/migrations.js @@ -1,7 +1,7 @@ // This file is required for Expo/React Native SQLite migrations - https://orm.drizzle.team/quick-sqlite/expo import journal from './meta/_journal.json'; -import m0000 from './0000_lowly_tinkerer.sql'; +import m0000 from './0000_loving_namora.sql'; export default { journal, diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index 4a7f93516a..e71dc245a3 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -646,6 +646,20 @@ export const deleteGroup = createWriteQuery( ['groups', 'channels'] ); +export const updateLastVisitedChannelInGroup = createWriteQuery( + 'updateLastVisitedChannelInGroup', + async ( + { groupId, channelId }: { groupId: string; channelId: string }, + ctx: QueryCtx + ) => { + return ctx.db + .update($groups) + .set({ lastVisitedChannelId: channelId }) + .where(eq($groups.id, groupId)); + }, + ['groups'] +); + export const insertUnjoinedGroups = createWriteQuery( 'insertUnjoinedGroups', async (groups: Group[], ctx: QueryCtx) => { diff --git a/packages/shared/src/db/schema.ts b/packages/shared/src/db/schema.ts index fce95dd775..f7c8550134 100644 --- a/packages/shared/src/db/schema.ts +++ b/packages/shared/src/db/schema.ts @@ -311,6 +311,7 @@ export const groups = sqliteTable('groups', { joinStatus: text('join_status').$type(), lastPostId: text('last_post_id'), lastPostAt: timestamp('last_post_at'), + lastVisitedChannelId: text('last_visited_channel_id'), }); export const groupsRelations = relations(groups, ({ one, many }) => ({ From f5aa3dfa8eb1b5ee50ef153ea382458c8636c86c Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Thu, 5 Dec 2024 11:43:29 -0600 Subject: [PATCH 087/149] ops: use deploy keys in all the places --- .github/workflows/deploy-canary.yml | 1 + .github/workflows/sync-dev.yml | 4 +++- .github/workflows/sync.yml | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-canary.yml b/.github/workflows/deploy-canary.yml index 5ec0a1a027..26fd419ed8 100644 --- a/.github/workflows/deploy-canary.yml +++ b/.github/workflows/deploy-canary.yml @@ -69,6 +69,7 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ env.tag }} + ssh-key: ${{ secrets.DEPLOY_KEY }} - uses: actions/download-artifact@v3 with: name: "ui-dist" diff --git a/.github/workflows/sync-dev.yml b/.github/workflows/sync-dev.yml index a8fcc50cf4..b5eed61dcd 100644 --- a/.github/workflows/sync-dev.yml +++ b/.github/workflows/sync-dev.yml @@ -10,11 +10,13 @@ jobs: name: "Syncs the latest staging deploy from staging to develop" steps: - uses: actions/checkout@v3 + with: + ssh-key: ${{ secrets.DEPLOY_KEY }} - name: Set Git config run: | git config --local user.email "actions@github.com" git config --local user.name "Github Actions" - - name: Merge Dev to Master + - name: Merge Staging to Develop run: | git fetch --unshallow git checkout staging diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 1b3e3198f9..0a82613b58 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -10,6 +10,8 @@ jobs: name: "Syncs the latest livenet deploy from develop to master" steps: - uses: actions/checkout@v3 + with: + ssh-key: ${{ secrets.DEPLOY_KEY }} - name: Set Git config run: | git config --local user.email "actions@github.com" @@ -22,4 +24,4 @@ jobs: git checkout master git pull git merge --no-ff staging - git push \ No newline at end of file + git push From 79b1da8e984e6932ea00cc60471de33ff23e5f08 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Thu, 5 Dec 2024 13:38:19 -0600 Subject: [PATCH 088/149] add keyvalue update lock --- .../screens/Onboarding/ReserveShipScreen.tsx | 4 +-- .../screens/Onboarding/SetNicknameScreen.tsx | 2 +- packages/shared/src/db/keyValue.ts | 35 +++++++++++-------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx index 7ad2c25cae..527bb3b992 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/ReserveShipScreen.tsx @@ -1,6 +1,5 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { useLureMetadata } from '@tloncorp/app/contexts/branch'; -import { useSignupContext } from '.././../lib/signupContext'; import { NodeBootPhase } from '@tloncorp/app/lib/bootHelpers'; import { ArvosDiscussing, @@ -14,6 +13,7 @@ import { } from '@tloncorp/ui'; import { useEffect, useMemo } from 'react'; +import { useSignupContext } from '../../lib/signupContext'; import type { OnboardingStackParamList } from '../../types'; type Props = NativeStackScreenProps; @@ -36,7 +36,7 @@ export const ReserveShipScreen = ({ navigation }: Props) => { signupContext.setOnboardingValues({ didCompleteOnboarding: true }); } signupContext.kickOffBootSequence(); - }, []); + }, [signupContext]); return ( diff --git a/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx b/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx index 832c479b1d..c5f6ca2a6d 100644 --- a/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx +++ b/apps/tlon-mobile/src/screens/Onboarding/SetNicknameScreen.tsx @@ -16,8 +16,8 @@ import { import { useEffect } from 'react'; import { Controller, useForm } from 'react-hook-form'; +import { useSignupContext } from '../../lib/signupContext'; import type { OnboardingStackParamList } from '../../types'; -import { useSignupContext } from '.././../lib/signupContext'; type Props = NativeStackScreenProps; diff --git a/packages/shared/src/db/keyValue.ts b/packages/shared/src/db/keyValue.ts index 3678ebf8d2..9c1f57e59c 100644 --- a/packages/shared/src/db/keyValue.ts +++ b/packages/shared/src/db/keyValue.ts @@ -200,6 +200,7 @@ const createStorageItem = (config: StorageItem) => { deserialize = JSON.parse, } = config; const storage = getStorageMethods(config.isSecure ?? false); + let updateLock = Promise.resolve(); const getValue = async (): Promise => { const value = await storage.getItem(key); @@ -207,24 +208,30 @@ const createStorageItem = (config: StorageItem) => { }; const resetValue = async (): Promise => { - await storage.setItem(key, serialize(defaultValue)); - queryClient.invalidateQueries({ queryKey: [key] }); - logger.log(`reset value ${key}`); + updateLock = updateLock.then(async () => { + await storage.setItem(key, serialize(defaultValue)); + queryClient.invalidateQueries({ queryKey: [key] }); + logger.log(`reset value ${key}`); + }); + await updateLock; return defaultValue; }; const setValue = async (valueInput: T | ((curr: T) => T)): Promise => { - let newValue: T; - if (valueInput instanceof Function) { - const currValue = await getValue(); - newValue = valueInput(currValue); - } else { - newValue = valueInput; - } - - await storage.setItem(key, serialize(newValue)); - queryClient.invalidateQueries({ queryKey: [key] }); - logger.log(`set value ${key}`, newValue); + updateLock = updateLock.then(async () => { + let newValue: T; + if (valueInput instanceof Function) { + const currValue = await getValue(); + newValue = valueInput(currValue); + } else { + newValue = valueInput; + } + + await storage.setItem(key, serialize(newValue)); + queryClient.invalidateQueries({ queryKey: [key] }); + logger.log(`set value ${key}`, newValue); + }); + await updateLock; }; function useValue() { From a9bf03e71c8be011da1432bb9bec87404cf89688 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Thu, 5 Dec 2024 14:02:17 -0600 Subject: [PATCH 089/149] desktop: preserve navigation state when resizing window --- packages/app/navigation/BasePathNavigator.tsx | 109 +++++++++++++++++- .../app/navigation/desktop/TopLevelDrawer.tsx | 6 +- packages/app/navigation/types.ts | 2 +- packages/app/navigation/utils.ts | 2 +- packages/app/utils/lastScreen.ts | 20 ++++ 5 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 packages/app/utils/lastScreen.ts diff --git a/packages/app/navigation/BasePathNavigator.tsx b/packages/app/navigation/BasePathNavigator.tsx index 2ebf352ff9..6722067a38 100644 --- a/packages/app/navigation/BasePathNavigator.tsx +++ b/packages/app/navigation/BasePathNavigator.tsx @@ -1,9 +1,15 @@ -import { NavigatorScreenParams } from '@react-navigation/native'; +import { + NavigatorScreenParams, + useNavigationState, +} from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { useEffect, useMemo, useRef } from 'react'; +import { getLastScreen, setLastScreen } from '../utils/lastScreen'; import { RootStack } from './RootStack'; import { TopLevelDrawer } from './desktop/TopLevelDrawer'; import { RootDrawerParamList, RootStackParamList } from './types'; +import { useResetToChannel, useTypedReset } from './utils'; export type MobileBasePathStackParamList = { Root: NavigatorScreenParams; @@ -27,6 +33,107 @@ export function BasePathNavigator({ isMobile }: { isMobile: boolean }) { ? MobileBasePathStackNavigator : DesktopBasePathStackNavigator; + const navState = useNavigationState((state) => state); + const currentRoute = navState?.routes[navState.index]; + const rootState = currentRoute?.state; + const lastWasMobile = useRef(isMobile); + + const currentScreenAndParams = useMemo(() => { + if (isMobile !== lastWasMobile.current) { + return undefined; + } + + const isHome = + rootState?.index === 0 && rootState?.routes[0].name === 'Home'; + const isContacts = + rootState?.index === 2 && rootState?.routes[2].name === 'Contacts'; + if (isHome) { + const homeState = rootState.routes[0].state; + if (!homeState || homeState.index === undefined) { + return { + name: 'Home', + params: {}, + }; + } + // capture the current screen and params within the home tab + return { + name: homeState.routes[homeState.index].name, + params: homeState.routes[homeState.index].params, + }; + } + + if (isContacts) { + // contacts tab is the third tab + const contactsState = rootState.routes[2].state; + if (!contactsState || contactsState.index === undefined) { + return { + name: 'Contacts', + params: {}, + }; + } + return { + name: contactsState.routes[contactsState.index].name, + params: contactsState.routes[contactsState.index].params, + }; + } + + if (rootState && rootState.routes && rootState.index !== undefined) { + const screen = rootState.routes[rootState.index]; + return { + name: screen.name, + params: screen.params, + }; + } + + return undefined; + }, [isMobile, rootState]); + + const resetToChannel = useResetToChannel(); + const reset = useTypedReset(); + + useEffect(() => { + const lastScreen = async () => getLastScreen(); + + // if we're switching between mobile and desktop, we want to reset the + // navigator to the last screen that was open in the other mode + if (lastWasMobile.current !== isMobile) { + setTimeout(() => { + lastScreen().then((lastScreen) => { + if (!lastScreen) { + return; + } + + if ( + lastScreen.name === 'Channel' || + lastScreen.name === 'GroupDM' || + lastScreen.name === 'DM' + ) { + resetToChannel(lastScreen.params.channelId, { + groupId: lastScreen.params.groupId, + }); + } else if (isMobile && lastScreen.name === 'Home') { + reset([{ name: 'ChatList' }]); + } + if (isMobile && lastScreen.name === 'Profile') { + // if we're on mobile and the last screen was profile, we want to + // be able to go back to the contacts screen when we press back + reset([{ name: 'Contacts' }, { name: 'Profile' }]); + } else { + reset([lastScreen]); + } + + lastWasMobile.current = isMobile; + }); + }, 1); // tiny delay to let navigator mount. otherwise it doesn't work. + } + }, [isMobile, resetToChannel, rootState, reset]); + + useEffect(() => { + if (currentScreenAndParams && isMobile === lastWasMobile.current) { + setLastScreen(currentScreenAndParams); + } + }, [currentScreenAndParams, isMobile]); + return ( { /> props.navigation.navigate('Profile')} + focused={isRouteActive('Contacts')} + onPress={() => props.navigation.navigate('Contacts')} /> {webAppNeedsUpdate && ( { > - + ); }; diff --git a/packages/app/navigation/types.ts b/packages/app/navigation/types.ts index 73bbc1aa16..f71d7c706a 100644 --- a/packages/app/navigation/types.ts +++ b/packages/app/navigation/types.ts @@ -73,7 +73,7 @@ export type RootStackNavigationProp = NavigationProp; export type RootDrawerParamList = { Home: NavigatorScreenParams; -} & Pick; +} & Pick; export type HomeDrawerParamList = Pick< RootStackParamList, diff --git a/packages/app/navigation/utils.ts b/packages/app/navigation/utils.ts index e6f0c493e0..ec7567ecd9 100644 --- a/packages/app/navigation/utils.ts +++ b/packages/app/navigation/utils.ts @@ -126,7 +126,7 @@ export async function getMainGroupRoute(groupId: string) { export function screenNameFromChannelId(channelId: string) { return logic.isDmChannelId(channelId) ? 'DM' - : logic.isGroupChannelId(channelId) + : logic.isGroupDmChannelId(channelId) ? 'GroupDM' : 'Channel'; } diff --git a/packages/app/utils/lastScreen.ts b/packages/app/utils/lastScreen.ts new file mode 100644 index 0000000000..770b215524 --- /dev/null +++ b/packages/app/utils/lastScreen.ts @@ -0,0 +1,20 @@ +import storage from '../lib/storage'; + +export const setLastScreen = async (screen: { name: string; params: any }) => { + await storage.save({ + key: 'lastScreen', + data: screen, + }); +}; + +export const getLastScreen = async () => { + try { + const result = await storage.load({ key: 'lastScreen' }); + return result; + } catch (err) { + if (err instanceof Error && err.name !== 'NotFoundError') { + console.error(err); + } + return null; + } +}; From 330fcaff9ccd47323535d261e9bf3ee0eaa6efc4 Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Thu, 5 Dec 2024 15:03:37 -0500 Subject: [PATCH 090/149] fix pending group display (#4254) --- packages/shared/src/db/queries.test.ts | 6 ++++++ packages/shared/src/db/queries.ts | 2 ++ packages/shared/src/test/init.json | 15 ++++++++++++--- .../ui/src/components/ListItem/listItemUtils.tsx | 16 ++++++++-------- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/shared/src/db/queries.test.ts b/packages/shared/src/db/queries.test.ts index a58e31ea71..7ffaf29abc 100644 --- a/packages/shared/src/db/queries.test.ts +++ b/packages/shared/src/db/queries.test.ts @@ -56,6 +56,12 @@ test('uses init data to get chat list', async () => { '~hansel-ribbur', '~pondus-watbel', ]); + + expect(result.pending.map((r) => r.id)).toEqual([ + '~fabled-faster/new-york', + '~barmyl-sigted/network-being', + '~salfer-biswed/gamers', + ]); }); const refDate = Date.now(); diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index 380d05ff79..cdd9544656 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -311,6 +311,8 @@ export const getChats = createReadQuery( where: or( eq($groups.currentUserIsMember, true), eq($groups.isNew, true), + eq($groups.haveInvite, true), + eq($groups.haveRequestedInvite, true), isNotNull($groups.joinStatus) ), with: { diff --git a/packages/shared/src/test/init.json b/packages/shared/src/test/init.json index 4b7b0bd44b..7b08f0903d 100644 --- a/packages/shared/src/test/init.json +++ b/packages/shared/src/test/init.json @@ -11600,7 +11600,10 @@ "~fabled-faster/new-york": { "preview": null, "invite": null, - "claim": null + "claim": { + "progress": "knocking", + "join-all": false + } }, "~barmyl-sigted/network-being": { "preview": { @@ -11615,7 +11618,10 @@ "description": "A group to discuss Heidegger, cybernetics, and the phenomenology of being online. " } }, - "invite": null, + "invite": { + "flag": "~dacler-ricwyt/new-new-new-york", + "ship": "~solfer-magfed" + }, "claim": null }, "~salfer-biswed/gamers": { @@ -11632,7 +11638,10 @@ } }, "invite": null, - "claim": null + "claim": { + "progress": "adding", + "join-all": true + } }, "~dacler-ricwyt/new-new-new-york": { "preview": { diff --git a/packages/ui/src/components/ListItem/listItemUtils.tsx b/packages/ui/src/components/ListItem/listItemUtils.tsx index 547c8f5624..6d239074ff 100644 --- a/packages/ui/src/components/ListItem/listItemUtils.tsx +++ b/packages/ui/src/components/ListItem/listItemUtils.tsx @@ -28,14 +28,14 @@ export function getGroupStatus(group: db.Group) { const state = isNew ? 'new' - : isRequested - ? 'requested' - : isInvite - ? 'invited' - : isErrored - ? 'errored' - : isJoining - ? 'joining' + : isErrored + ? 'errored' + : isJoining + ? 'joining' + : isRequested + ? 'requested' + : isInvite + ? 'invited' : 'joined'; const labels = { From 9ceb955b029d7acf7e196739327101455debbb87 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Dec 2024 20:10:17 +0000 Subject: [PATCH 091/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 111ab94919..a850637dd7 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.irfsv.sq9vg.l6b96.rj7vs.74118.glob' 0v4.irfsv.sq9vg.l6b96.rj7vs.74118] + glob-http+['https://bootstrap.urbit.org/glob-0v4.v0830.h3vla.sg9ok.r54nr.tld8p.glob' 0v4.v0830.h3vla.sg9ok.r54nr.tld8p] base+'groups' version+[6 5 0] website+'https://tlon.io' From 4ed12c5f90a0db369079ff25b0eedca9ecdd466c Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Thu, 5 Dec 2024 14:28:12 -0600 Subject: [PATCH 092/149] use updateGroup instead of a separate query function --- packages/app/features/top/ChannelScreen.tsx | 6 +++--- packages/shared/src/db/queries.ts | 14 -------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/packages/app/features/top/ChannelScreen.tsx b/packages/app/features/top/ChannelScreen.tsx index 1cd05348f6..f954a539d3 100644 --- a/packages/app/features/top/ChannelScreen.tsx +++ b/packages/app/features/top/ChannelScreen.tsx @@ -74,9 +74,9 @@ export default function ChannelScreen(props: Props) { if (group) { // Update the last visited channel in the group so we can return to it // when we come back to the group - db.updateLastVisitedChannelInGroup({ - groupId: group.id, - channelId: channel.id, + db.updateGroup({ + id: group.id, + lastVisitedChannelId: channel.id, }); } } diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index e71dc245a3..4a7f93516a 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -646,20 +646,6 @@ export const deleteGroup = createWriteQuery( ['groups', 'channels'] ); -export const updateLastVisitedChannelInGroup = createWriteQuery( - 'updateLastVisitedChannelInGroup', - async ( - { groupId, channelId }: { groupId: string; channelId: string }, - ctx: QueryCtx - ) => { - return ctx.db - .update($groups) - .set({ lastVisitedChannelId: channelId }) - .where(eq($groups.id, groupId)); - }, - ['groups'] -); - export const insertUnjoinedGroups = createWriteQuery( 'insertUnjoinedGroups', async (groups: Group[], ctx: QueryCtx) => { From 0d1523f7a258dac47533f1b697d81e8272b0d0a7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Dec 2024 20:37:07 +0000 Subject: [PATCH 093/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index a850637dd7..b5dec73b6b 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.v0830.h3vla.sg9ok.r54nr.tld8p.glob' 0v4.v0830.h3vla.sg9ok.r54nr.tld8p] + glob-http+['https://bootstrap.urbit.org/glob-0v6.5oacf.ktbkv.06qqe.b2ggm.anrts.glob' 0v6.5oacf.ktbkv.06qqe.b2ggm.anrts] base+'groups' version+[6 5 0] website+'https://tlon.io' From 26cb73549cbd3e0676061a525cb66969beead079 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Dec 2024 20:37:19 +0000 Subject: [PATCH 094/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 79ad23b890..708ca24cb9 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v1.rkfda.u301l.vk9sa.av06i.1gbmf.glob' 0v1.rkfda.u301l.vk9sa.av06i.1gbmf] + glob-http+['https://bootstrap.urbit.org/glob-0v4.es9ls.slgag.en7p5.p38ml.njp7m.glob' 0v4.es9ls.slgag.en7p5.p38ml.njp7m] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From f95d1eba5d47b988ac596d5f38fc4176c104ce8d Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Thu, 5 Dec 2024 14:44:14 -0600 Subject: [PATCH 095/149] put focusedChannelId in navigation provider, use that rather than prop drilling --- packages/app/features/top/ChatListScreen.tsx | 100 +++++++++--------- .../app/features/top/GroupChannelsScreen.tsx | 20 ++-- .../ui/src/components/ChannelNavSection.tsx | 3 - .../ui/src/components/ChannelNavSections.tsx | 5 - packages/ui/src/components/ChatList.tsx | 6 +- .../components/GroupChannelsScreenView.tsx | 3 - .../components/ListItem/ChannelListItem.tsx | 4 +- packages/ui/src/contexts/navigation.tsx | 19 +++- 8 files changed, 83 insertions(+), 77 deletions(-) diff --git a/packages/app/features/top/ChatListScreen.tsx b/packages/app/features/top/ChatListScreen.tsx index 66afcd6d51..c85e019909 100644 --- a/packages/app/features/top/ChatListScreen.tsx +++ b/packages/app/features/top/ChatListScreen.tsx @@ -15,6 +15,7 @@ import { GroupPreviewSheet, InviteUsersSheet, NavBarView, + NavigationProvider, RequestsProvider, ScreenHeader, View, @@ -269,56 +270,57 @@ export function ChatListScreenView({ setInviteSheetGroup(group); }} > - - - - setAddGroupOpen(true)} - /> - - } - /> - {chats && chats.unpinned.length ? ( - + + + + setAddGroupOpen(true)} + /> + + } /> - ) : null} - - - - setInviteSheetGroup(null)} - group={inviteSheetGroup ?? undefined} - /> - + {chats && chats.unpinned.length ? ( + + ) : null} + + + + setInviteSheetGroup(null)} + group={inviteSheetGroup ?? undefined} + /> + + { navigation.navigate('Contacts'); diff --git a/packages/app/features/top/GroupChannelsScreen.tsx b/packages/app/features/top/GroupChannelsScreen.tsx index 3cc6038c9f..f6e2a1ab91 100644 --- a/packages/app/features/top/GroupChannelsScreen.tsx +++ b/packages/app/features/top/GroupChannelsScreen.tsx @@ -10,6 +10,7 @@ import { ChatOptionsProvider, GroupChannelsScreenView, InviteUsersSheet, + NavigationProvider, } from '@tloncorp/ui'; import { useCallback, useState } from 'react'; @@ -78,15 +79,16 @@ export function GroupChannelsScreenContent({ }} {...useChatSettingsNavigation()} > - + + + { diff --git a/packages/ui/src/components/ChannelNavSection.tsx b/packages/ui/src/components/ChannelNavSection.tsx index 62910bcffb..ddd036086a 100644 --- a/packages/ui/src/components/ChannelNavSection.tsx +++ b/packages/ui/src/components/ChannelNavSection.tsx @@ -9,13 +9,11 @@ export default function ChannelNavSection({ channels, onSelect, onLongPress, - focusedChannelId, }: { section: db.GroupNavSection; channels: db.Channel[]; onSelect: (channel: any) => void; onLongPress?: (channel: any) => void; - focusedChannelId?: string; }) { const sectionChannels = useMemo( () => @@ -53,7 +51,6 @@ export default function ChannelNavSection({ useTypeIcon={true} onPress={onSelect} onLongPress={onLongPress} - isFocused={focusedChannelId === item.channelId} /> ))} diff --git a/packages/ui/src/components/ChannelNavSections.tsx b/packages/ui/src/components/ChannelNavSections.tsx index ec765b5b30..0a3f7a68c9 100644 --- a/packages/ui/src/components/ChannelNavSections.tsx +++ b/packages/ui/src/components/ChannelNavSections.tsx @@ -12,7 +12,6 @@ export default function ChannelNavSections({ sortBy, paddingBottom, onLongPress, - focusedChannelId, }: { group: db.Group; channels: db.Channel[]; @@ -20,7 +19,6 @@ export default function ChannelNavSections({ sortBy: 'recency' | 'arranged'; paddingBottom?: number; onLongPress?: (channel: any) => void; - focusedChannelId?: string; }) { const unGroupedChannels = useMemo( () => @@ -63,7 +61,6 @@ export default function ChannelNavSections({ onPress={onSelect} useTypeIcon={true} onLongPress={onLongPress} - isFocused={item.id === focusedChannelId} /> ))} @@ -88,7 +85,6 @@ export default function ChannelNavSections({ channels={sectionChannels} onSelect={onSelect} onLongPress={onLongPress} - focusedChannelId={focusedChannelId} /> ); })} @@ -109,7 +105,6 @@ export default function ChannelNavSections({ onPress={onSelect} onLongPress={onLongPress} useTypeIcon={true} - isFocused={item.id === focusedChannelId} /> ))} diff --git a/packages/ui/src/components/ChatList.tsx b/packages/ui/src/components/ChatList.tsx index 4cb468798e..19ff7d79c3 100644 --- a/packages/ui/src/components/ChatList.tsx +++ b/packages/ui/src/components/ChatList.tsx @@ -41,7 +41,6 @@ export const ChatList = React.memo(function ChatListComponent({ searchQuery, onSearchQueryChange, onSearchToggle, - focusedChannelId, }: db.GroupedChats & { onPressItem?: (chat: db.Chat) => void; onSectionChange?: (title: string) => void; @@ -51,7 +50,6 @@ export const ChatList = React.memo(function ChatListComponent({ searchQuery: string; onSearchQueryChange: (query: string) => void; onSearchToggle: () => void; - focusedChannelId?: string; }) { const displayData = useFilteredChats({ pinned, @@ -102,7 +100,6 @@ export const ChatList = React.memo(function ChatListComponent({ model={item} onPress={onPressItem} onLongPress={handleLongPress} - isFocused={item.id === focusedChannelId} /> ); } else { @@ -111,12 +108,11 @@ export const ChatList = React.memo(function ChatListComponent({ model={item} onPress={onPressItem} onLongPress={handleLongPress} - isFocused={item.id === focusedChannelId} /> ); } }, - [onPressItem, handleLongPress, focusedChannelId] + [onPressItem, handleLongPress] ); const handlePressTryAll = useCallback(() => { diff --git a/packages/ui/src/components/GroupChannelsScreenView.tsx b/packages/ui/src/components/GroupChannelsScreenView.tsx index 7182008d6f..7b61f2102a 100644 --- a/packages/ui/src/components/GroupChannelsScreenView.tsx +++ b/packages/ui/src/components/GroupChannelsScreenView.tsx @@ -20,7 +20,6 @@ type GroupChannelsScreenViewProps = { onJoinChannel: (channel: db.Channel) => void; onBackPressed: () => void; enableCustomChannels?: boolean; - focusedChannelId?: string; }; export function GroupChannelsScreenView({ @@ -30,7 +29,6 @@ export function GroupChannelsScreenView({ onJoinChannel, onBackPressed, enableCustomChannels = false, - focusedChannelId, }: GroupChannelsScreenViewProps) { const [showCreateChannel, setShowCreateChannel] = useState(false); const sortBy = db.channelSortPreference.useValue(); @@ -108,7 +106,6 @@ export function GroupChannelsScreenView({ onSelect={onChannelPressed} sortBy={sortBy || 'recency'} onLongPress={handleOpenChannelOptions} - focusedChannelId={focusedChannelId} /> {unjoinedChannels.length > 0 && ( diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 0d51a0f091..357433072c 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -3,6 +3,7 @@ import * as logic from '@tloncorp/shared/logic'; import { useMemo } from 'react'; import { View, isWeb } from 'tamagui'; +import { useNavigation } from '../../contexts'; import * as utils from '../../utils'; import { capitalize } from '../../utils'; import { Badge } from '../Badge'; @@ -19,7 +20,6 @@ export function ChannelListItem({ onLongPress, EndContent, dimmed, - isFocused, ...props }: { useTypeIcon?: boolean; @@ -58,6 +58,8 @@ export function ChannelListItem({ } }, [model, firstMemberId, memberCount]); + const isFocused = useNavigation().focusedChannelId === model.id; + return ( void; onPressGoToDm?: (participants: string[]) => void; onGoToUserProfile?: (userId: string) => void; + focusedChannelId?: string; }; type ContextValue = State; @@ -41,16 +42,30 @@ export const NavigationProvider = ({ onPressGroupRef, onPressGoToDm, onGoToUserProfile, + focusedChannelId, }: { children: React.ReactNode; onPressRef?: (channel: db.Channel, post: db.Post) => void; onPressGroupRef?: (group: db.Group) => void; onPressGoToDm?: (participants: string[]) => void; onGoToUserProfile?: (userId: string) => void; + focusedChannelId?: string; }) => { const value = useMemo( - () => ({ onPressRef, onPressGroupRef, onPressGoToDm, onGoToUserProfile }), - [onPressRef, onPressGroupRef, onPressGoToDm, onGoToUserProfile] + () => ({ + onPressRef, + onPressGroupRef, + onPressGoToDm, + onGoToUserProfile, + focusedChannelId, + }), + [ + onPressRef, + onPressGroupRef, + onPressGoToDm, + onGoToUserProfile, + focusedChannelId, + ] ); return {children}; }; From e347cc251ad6c92eec5e6576fdc1970c993673e5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Dec 2024 21:25:04 +0000 Subject: [PATCH 096/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index b5dec73b6b..6dc3b2a09a 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.5oacf.ktbkv.06qqe.b2ggm.anrts.glob' 0v6.5oacf.ktbkv.06qqe.b2ggm.anrts] + glob-http+['https://bootstrap.urbit.org/glob-0v7.7poub.47mss.qdmh7.akufd.46968.glob' 0v7.7poub.47mss.qdmh7.akufd.46968] base+'groups' version+[6 5 0] website+'https://tlon.io' From 6f5b80a7462bc399e6c7853dc32690fe788a7f86 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Dec 2024 21:25:22 +0000 Subject: [PATCH 097/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 708ca24cb9..431f0edb28 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.es9ls.slgag.en7p5.p38ml.njp7m.glob' 0v4.es9ls.slgag.en7p5.p38ml.njp7m] + glob-http+['https://bootstrap.urbit.org/glob-0vcsj53.c9fuu.fmkqi.d8kqq.vu1k4.glob' 0vcsj53.c9fuu.fmkqi.d8kqq.vu1k4] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 60149ae880413931801e0cc1d1edf418a60b22dc Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Thu, 5 Dec 2024 16:12:24 -0600 Subject: [PATCH 098/149] suggested contact: move back to new joined groups hook based trigger, only add new suggestions once per day --- .../tlon-mobile/src/components/AuthenticatedApp.tsx | 2 ++ apps/tlon-web-new/src/app.tsx | 2 ++ packages/app/hooks/useFindSuggestedContacts.ts | 10 ++++++++++ packages/shared/src/db/keyValue.ts | 5 +++++ packages/shared/src/db/queries.ts | 13 +++++++++++++ packages/shared/src/store/contactActions.ts | 13 +++++++++++-- packages/shared/src/store/dbHooks.ts | 8 ++++++++ 7 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 packages/app/hooks/useFindSuggestedContacts.ts diff --git a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx index 8bf201dffb..8c18a82bd4 100644 --- a/apps/tlon-mobile/src/components/AuthenticatedApp.tsx +++ b/apps/tlon-mobile/src/components/AuthenticatedApp.tsx @@ -2,6 +2,7 @@ import { useShip } from '@tloncorp/app/contexts/ship'; import { useAppStatusChange } from '@tloncorp/app/hooks/useAppStatusChange'; import { useConfigureUrbitClient } from '@tloncorp/app/hooks/useConfigureUrbitClient'; import { useCurrentUserId } from '@tloncorp/app/hooks/useCurrentUser'; +import { useFindSuggestedContacts } from '@tloncorp/app/hooks/useFindSuggestedContacts'; import { useNavigationLogging } from '@tloncorp/app/hooks/useNavigationLogger'; import { useNetworkLogger } from '@tloncorp/app/hooks/useNetworkLogger'; import { useTelemetry } from '@tloncorp/app/hooks/useTelemetry'; @@ -29,6 +30,7 @@ function AuthenticatedApp() { useNavigationLogging(); useNetworkLogger(); useCheckAppUpdated(); + useFindSuggestedContacts(); useEffect(() => { configureClient(); diff --git a/apps/tlon-web-new/src/app.tsx b/apps/tlon-web-new/src/app.tsx index 15f4a51906..c2e2f702ea 100644 --- a/apps/tlon-web-new/src/app.tsx +++ b/apps/tlon-web-new/src/app.tsx @@ -6,6 +6,7 @@ import { } from '@react-navigation/native'; import { useConfigureUrbitClient } from '@tloncorp/app/hooks/useConfigureUrbitClient'; import { useCurrentUserId } from '@tloncorp/app/hooks/useCurrentUser'; +import { useFindSuggestedContacts } from '@tloncorp/app/hooks/useFindSuggestedContacts'; import { useIsDarkMode } from '@tloncorp/app/hooks/useIsDarkMode'; import { checkDb, useMigrations } from '@tloncorp/app/lib/webDb'; import { BasePathNavigator } from '@tloncorp/app/navigation/BasePathNavigator'; @@ -135,6 +136,7 @@ const App = React.memo(function AppComponent() { const [dbIsLoaded, setDbIsLoaded] = useState(false); const [startedSync, setStartedSync] = useState(false); const configureClient = useConfigureUrbitClient(); + useFindSuggestedContacts(); useEffect(() => { handleError(() => { diff --git a/packages/app/hooks/useFindSuggestedContacts.ts b/packages/app/hooks/useFindSuggestedContacts.ts new file mode 100644 index 0000000000..5bc2a54a1e --- /dev/null +++ b/packages/app/hooks/useFindSuggestedContacts.ts @@ -0,0 +1,10 @@ +import * as store from '@tloncorp/shared/store'; +import { useEffect } from 'react'; + +export function useFindSuggestedContacts() { + const { data: joinedGroupCount } = store.useJoinedGroupsCount(); + + useEffect(() => { + store.findContactSuggestions(); + }, [joinedGroupCount]); +} diff --git a/packages/shared/src/db/keyValue.ts b/packages/shared/src/db/keyValue.ts index 9c1f57e59c..6e613f1d0e 100644 --- a/packages/shared/src/db/keyValue.ts +++ b/packages/shared/src/db/keyValue.ts @@ -290,6 +290,11 @@ export const groupsUsedForSuggestions = createStorageItem({ defaultValue: [], }); +export const lastAddedSuggestionsAt = createStorageItem({ + key: 'lastAddedSuggestionsAt', + defaultValue: 0, +}); + export const postDraft = (opts: { key: string; type: 'caption' | 'text' | undefined; // matches GalleryDraftType diff --git a/packages/shared/src/db/queries.ts b/packages/shared/src/db/queries.ts index cdd9544656..37c443cf40 100644 --- a/packages/shared/src/db/queries.ts +++ b/packages/shared/src/db/queries.ts @@ -153,6 +153,19 @@ export const getGroupPreviews = createReadQuery( ['groups'] ); +export const getJoinedGroupsCount = createReadQuery( + 'getJoinedGroupCount', + async (ctx: QueryCtx) => { + const result = await ctx.db + .select({ count: count() }) + .from($groups) + .where(eq($groups.currentUserIsMember, true)); + + return result[0]?.count ?? 0; + }, + ['groups'] +); + // TODO: inefficient, should optimize export const getGroupsWithMemberThreshold = createReadQuery( 'getGroupsWithMemberThreshold', diff --git a/packages/shared/src/store/contactActions.ts b/packages/shared/src/store/contactActions.ts index 93a5bf333c..8ec4c905bb 100644 --- a/packages/shared/src/store/contactActions.ts +++ b/packages/shared/src/store/contactActions.ts @@ -98,6 +98,14 @@ export async function findContactSuggestions() { const MAX_SUGGESTIONS = 6; // arbitrary try { + // if we've already added suggestions recently, don't do it again + const lastAddedSuggestionsAt = await db.lastAddedSuggestionsAt.getValue(); + const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000; + if (lastAddedSuggestionsAt > oneDayAgo) { + logger.log('Suggestions added recently, skipping'); + return; + } + // first see if we have any joined groups and seem to be a somewhat // new user const groups = await db.getGroups({ includeUnjoined: false }); @@ -182,10 +190,11 @@ export async function findContactSuggestions() { runContext.suggestions = suggestions.length; logger.crumb(`Found ${suggestions.length} suggestions`); - db.groupsUsedForSuggestions.setValue(groupchats.map((g) => g.id)); if (suggestions.length > 0) { - await addContactSuggestions(suggestions); + addContactSuggestions(suggestions); + db.groupsUsedForSuggestions.setValue(groupchats.map((g) => g.id)); + db.lastAddedSuggestionsAt.setValue(Date.now()); logger.trackEvent('Client Contact Suggestions', { ...runContext, suggestionsFound: true, diff --git a/packages/shared/src/store/dbHooks.ts b/packages/shared/src/store/dbHooks.ts index a42e8659d4..3ceee7aec2 100644 --- a/packages/shared/src/store/dbHooks.ts +++ b/packages/shared/src/store/dbHooks.ts @@ -325,6 +325,14 @@ export const useGroup = ({ id }: { id?: string }) => { }); }; +export const useJoinedGroupsCount = () => { + const deps = useKeyFromQueryDeps(db.getJoinedGroupsCount); + return useQuery({ + queryKey: ['joinedGroupsCount', deps], + queryFn: () => db.getJoinedGroupsCount(), + }); +}; + export const useGroupByChannel = (channelId: string) => { return useQuery({ queryKey: [['group', channelId]], From dcd26a024b03b3c05f821424b53e82dbf30adb83 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Thu, 5 Dec 2024 16:21:12 -0600 Subject: [PATCH 099/149] remove from sync start --- packages/shared/src/store/sync.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/shared/src/store/sync.ts b/packages/shared/src/store/sync.ts index 034e4ace09..bed4de2db6 100644 --- a/packages/shared/src/store/sync.ts +++ b/packages/shared/src/store/sync.ts @@ -12,7 +12,6 @@ import { INFINITE_ACTIVITY_QUERY_KEY, resetActivityFetchers, } from '../store/useActivityFetchers'; -import { findContactSuggestions } from './contactActions'; import { useLureState } from './lure'; import { getSyncing, updateIsSyncing, updateSession } from './session'; import { SyncCtx, SyncPriority, syncQueue } from './syncQueue'; @@ -1175,10 +1174,6 @@ export const syncStart = async (alreadySubscribed?: boolean) => { }); updateIsSyncing(false); - - // finding contacts is a bit of an outlier here, but it's work we need to do - // that can roughly be batched whenever we sync - findContactSuggestions(); }; export const setupHighPrioritySubscriptions = async (ctx?: SyncCtx) => { From 7cb5f521f2c61db287801c188da0c6c1108c1b1d Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Thu, 5 Dec 2024 16:23:12 -0600 Subject: [PATCH 100/149] ops: bump 5.0.0 --- apps/tlon-mobile/android/app/build.gradle | 2 +- apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/tlon-mobile/android/app/build.gradle b/apps/tlon-mobile/android/app/build.gradle index af13d348a5..656bcf5376 100644 --- a/apps/tlon-mobile/android/app/build.gradle +++ b/apps/tlon-mobile/android/app/build.gradle @@ -88,7 +88,7 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion compileSdk rootProject.ext.compileSdkVersion versionCode 108 - versionName "4.2.4" + versionName "5.0.0" buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString()) } diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index 5d30e4e75d..b97ea9f88a 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -1427,7 +1427,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1465,7 +1465,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1689,7 +1689,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1732,7 +1732,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.2.4; + MARKETING_VERSION = 5.0.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", From dcb957b1c24cc199a7043f4623ed4843792f19bc Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Dec 2024 22:47:45 +0000 Subject: [PATCH 101/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 6dc3b2a09a..7c5efe7802 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v7.7poub.47mss.qdmh7.akufd.46968.glob' 0v7.7poub.47mss.qdmh7.akufd.46968] + glob-http+['https://bootstrap.urbit.org/glob-0v2.imu38.n1u9d.o2plp.8juvp.5t2ao.glob' 0v2.imu38.n1u9d.o2plp.8juvp.5t2ao] base+'groups' version+[6 5 0] website+'https://tlon.io' From f2d770eabcd52a7c7ee49d0f804f7b630ed8ae71 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 5 Dec 2024 22:47:47 +0000 Subject: [PATCH 102/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 431f0edb28..bfacb2664c 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0vcsj53.c9fuu.fmkqi.d8kqq.vu1k4.glob' 0vcsj53.c9fuu.fmkqi.d8kqq.vu1k4] + glob-http+['https://bootstrap.urbit.org/glob-0vr4p9n.vrsur.04pus.od75v.hpc8t.glob' 0vr4p9n.vrsur.04pus.od75v.hpc8t] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From a3e77276df1d16d01e9d3600b7e55c7cd653d48f Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Thu, 5 Dec 2024 18:51:29 -0600 Subject: [PATCH 103/149] key value: add createStorageItem config for persist or not after logout --- packages/app/hooks/useHandleLogout.native.ts | 4 +-- packages/app/hooks/useHandleLogout.ts | 3 +- packages/shared/src/db/keyValue.ts | 31 ++++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/app/hooks/useHandleLogout.native.ts b/packages/app/hooks/useHandleLogout.native.ts index 3ff963c1f9..22ace8d1f4 100644 --- a/packages/app/hooks/useHandleLogout.native.ts +++ b/packages/app/hooks/useHandleLogout.native.ts @@ -1,6 +1,6 @@ import { createDevLogger } from '@tloncorp/shared'; import * as api from '@tloncorp/shared/api'; -import { finishingSelfHostedLogin } from '@tloncorp/shared/db'; +import { clearNonPersistentStorageItems } from '@tloncorp/shared/db'; import * as store from '@tloncorp/shared/store'; import { useCallback } from 'react'; @@ -28,7 +28,7 @@ export function useHandleLogout({ resetDb }: { resetDb: () => void }) { clearDeepLink(); clearSplashDismissed(); clearTelemetry(); - finishingSelfHostedLogin.resetValue(); + clearNonPersistentStorageItems(); if (!resetDb) { logger.trackError('could not reset db on logout'); return; diff --git a/packages/app/hooks/useHandleLogout.ts b/packages/app/hooks/useHandleLogout.ts index 54a52b374d..8cb2827909 100644 --- a/packages/app/hooks/useHandleLogout.ts +++ b/packages/app/hooks/useHandleLogout.ts @@ -4,6 +4,7 @@ // which isn't made for web. import { createDevLogger } from '@tloncorp/shared'; import * as api from '@tloncorp/shared/api'; +import { clearNonPersistentStorageItems } from '@tloncorp/shared/db'; import * as store from '@tloncorp/shared/store'; import { useCallback } from 'react'; @@ -33,7 +34,7 @@ export function useHandleLogout({ resetDb }: { resetDb?: () => void }) { removeHostingUserId(); removeHostingAuthTracking(); clearSplashDismissed(); - // signupContext.clear(); + clearNonPersistentStorageItems(); if (!resetDb) { logger.trackError('could not reset db on logout'); return; diff --git a/packages/shared/src/db/keyValue.ts b/packages/shared/src/db/keyValue.ts index 6e613f1d0e..be89ee3778 100644 --- a/packages/shared/src/db/keyValue.ts +++ b/packages/shared/src/db/keyValue.ts @@ -188,14 +188,16 @@ type StorageItem = { key: string; defaultValue: T; isSecure?: boolean; + persistAfterLogout?: boolean; serialize?: (value: T) => string; deserialize?: (value: string) => T; }; -const createStorageItem = (config: StorageItem) => { +const createStorageItemImpl = (config: StorageItem) => { const { key, defaultValue, + persistAfterLogout = false, serialize = JSON.stringify, deserialize = JSON.parse, } = config; @@ -248,7 +250,30 @@ const createStorageItem = (config: StorageItem) => { }; } - return { getValue, setValue, resetValue, useValue, useStorageItem }; + return { + getValue, + setValue, + resetValue, + useValue, + useStorageItem, + __persistAfterLogout: persistAfterLogout, + }; +}; + +const storageItems: Array>> = []; +export const clearNonPersistentStorageItems = async (): Promise => { + const clearPromises = storageItems + .filter((item) => !item.__persistAfterLogout) + .map((item) => item.resetValue()); + + await Promise.all(clearPromises); + logger.log('Cleared all non-persistent storage items'); +}; + +const createStorageItem = (config: StorageItem) => { + const storageItem = createStorageItemImpl(config); + storageItems.push(storageItem); + return storageItem; }; export const signupData = createStorageItem({ @@ -263,11 +288,13 @@ export const signupData = createStorageItem({ export const lastAppVersion = createStorageItem({ key: 'lastAppVersion', defaultValue: null, + persistAfterLogout: true, }); export const didSignUp = createStorageItem({ key: 'didSignUp', defaultValue: false, + persistAfterLogout: true, }); export const didInitializeTelemetry = createStorageItem({ From c8d7a2d5cb7db7cf47ea7f58def7f31f1de7a299 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Thu, 5 Dec 2024 23:14:21 -0600 Subject: [PATCH 104/149] add store context and edit profile fixture, use new context hook in edit profile screen --- apps/tlon-mobile/src/App.main.tsx | 69 ++++--- cosmos.imports.ts | 190 +++++++++--------- .../features/settings/EditProfileScreen.tsx | 36 +--- .../fixtures/EditProfileScreen.fixture.tsx | 46 +++++ packages/app/fixtures/cosmos.decorator.tsx | 6 +- .../src/components/EditProfileScreenView.tsx | 32 +-- packages/ui/src/contexts/index.ts | 1 + packages/ui/src/contexts/storeContext.tsx | 48 +++++ 8 files changed, 251 insertions(+), 177 deletions(-) create mode 100644 packages/app/fixtures/EditProfileScreen.fixture.tsx create mode 100644 packages/ui/src/contexts/storeContext.tsx diff --git a/apps/tlon-mobile/src/App.main.tsx b/apps/tlon-mobile/src/App.main.tsx index c2093e32a2..79eee1f594 100644 --- a/apps/tlon-mobile/src/App.main.tsx +++ b/apps/tlon-mobile/src/App.main.tsx @@ -24,6 +24,7 @@ import { finishingSelfHostedLogin as selfHostedLoginStatus } from '@tloncorp/sha import { LoadingSpinner, PortalProvider, + StoreProvider, Text, View, usePreloadedEmojis, @@ -127,39 +128,43 @@ export default function ConnectedApp() { theme={isDarkMode ? DarkTheme : DefaultTheme} ref={navigationContainerRef} > - - - - - - - - - - + + + + + + + + + + + - {__DEV__ && ( - - )} - - - - - - - + {__DEV__ && ( + + )} + + + + + + + + diff --git a/cosmos.imports.ts b/cosmos.imports.ts index 64e191862d..c0c1d3de9d 100644 --- a/cosmos.imports.ts +++ b/cosmos.imports.ts @@ -12,58 +12,60 @@ import * as fixture5 from './packages/app/fixtures/ScreenHeader.fixture'; import * as fixture6 from './packages/app/fixtures/ReferenceSkeleton.fixture'; import * as fixture7 from './packages/app/fixtures/ProfileSheet.fixture'; import * as fixture8 from './packages/app/fixtures/ProfileBlock.fixture'; -import * as fixture9 from './packages/app/fixtures/PostScreen.fixture'; -import * as fixture10 from './packages/app/fixtures/PostReference.fixture'; -import * as fixture11 from './packages/app/fixtures/ParentAgnosticKeyboardAvoidingView.fixture'; -import * as fixture12 from './packages/app/fixtures/OutsideEmbed.fixture'; -import * as fixture13 from './packages/app/fixtures/MetaEditorScreen.fixture'; -import * as fixture14 from './packages/app/fixtures/MessageInput.fixture'; -import * as fixture15 from './packages/app/fixtures/MessageActions.fixture'; -import * as fixture16 from './packages/app/fixtures/InviteUsersSheet.fixture'; -import * as fixture17 from './packages/app/fixtures/Input.fixture'; -import * as fixture18 from './packages/app/fixtures/ImageViewer.fixture'; -import * as fixture19 from './packages/app/fixtures/GroupListItem.fixture'; -import * as fixture20 from './packages/app/fixtures/GroupList.fixture'; -import * as fixture21 from './packages/app/fixtures/GalleryPost.fixture'; -import * as fixture22 from './packages/app/fixtures/Form.fixture'; -import * as fixture23 from './packages/app/fixtures/FindGroups.fixture'; -import * as fixture24 from './packages/app/fixtures/CreateGroup.fixture'; -import * as fixture25 from './packages/app/fixtures/ContactList.fixture'; -import * as fixture26 from './packages/app/fixtures/ChatMessage.fixture'; -import * as fixture27 from './packages/app/fixtures/ChannelSwitcherSheet.fixture'; -import * as fixture28 from './packages/app/fixtures/ChannelHeader.fixture'; -import * as fixture29 from './packages/app/fixtures/ChannelDivider.fixture'; -import * as fixture30 from './packages/app/fixtures/Channel.fixture'; -import * as fixture31 from './packages/app/fixtures/Button.fixture'; -import * as fixture32 from './packages/app/fixtures/BlockSectionList.fixture'; -import * as fixture33 from './packages/app/fixtures/Avatar.fixture'; -import * as fixture34 from './packages/app/fixtures/AudioEmbed.fixture'; -import * as fixture35 from './packages/app/fixtures/AttachmentPreviewList.fixture'; -import * as fixture36 from './packages/app/fixtures/AddGroupSheet.fixture'; -import * as fixture37 from './packages/app/fixtures/Activity.fixture'; -import * as fixture38 from './apps/tlon-mobile/src/App.fixture'; -import * as fixture39 from './packages/app/fixtures/DetailView/NotebookDetailView.fixture'; -import * as fixture40 from './packages/app/fixtures/DetailView/GalleryDetailView.fixture'; -import * as fixture41 from './packages/app/fixtures/DetailView/ChatDetailView.fixture'; -import * as fixture42 from './packages/app/fixtures/ActionSheet/SendPostRetrySheet.fixture'; -import * as fixture43 from './packages/app/fixtures/ActionSheet/ProfileSheet.fixture'; -import * as fixture44 from './packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture'; -import * as fixture45 from './packages/app/fixtures/ActionSheet/GroupJoinRequestSheet.fixture'; -import * as fixture46 from './packages/app/fixtures/ActionSheet/GenericActionSheet.fixture'; -import * as fixture47 from './packages/app/fixtures/ActionSheet/EditSectionNameSheet.fixture'; -import * as fixture48 from './packages/app/fixtures/ActionSheet/DeleteSheet.fixture'; -import * as fixture49 from './packages/app/fixtures/ActionSheet/CreateChannelSheet.fixture'; -import * as fixture50 from './packages/app/fixtures/ActionSheet/AttachmentSheet.fixture'; -import * as fixture51 from './packages/app/fixtures/ActionSheet/AddGalleryPostSheet.fixture'; -import * as fixture52 from './apps/tlon-mobile/src/fixtures/SetNicknameScreen.fixture'; -import * as fixture53 from './apps/tlon-mobile/src/fixtures/Onboarding.fixture'; -import * as fixture54 from './apps/tlon-mobile/src/fixtures/InputToolbar.fixture'; +import * as fixture9 from './packages/app/fixtures/Pressable.fixture'; +import * as fixture10 from './packages/app/fixtures/PostScreen.fixture'; +import * as fixture11 from './packages/app/fixtures/PostReference.fixture'; +import * as fixture12 from './packages/app/fixtures/ParentAgnosticKeyboardAvoidingView.fixture'; +import * as fixture13 from './packages/app/fixtures/OutsideEmbed.fixture'; +import * as fixture14 from './packages/app/fixtures/MetaEditorScreen.fixture'; +import * as fixture15 from './packages/app/fixtures/MessageInput.fixture'; +import * as fixture16 from './packages/app/fixtures/MessageActions.fixture'; +import * as fixture17 from './packages/app/fixtures/InviteUsersSheet.fixture'; +import * as fixture18 from './packages/app/fixtures/Input.fixture'; +import * as fixture19 from './packages/app/fixtures/ImageViewer.fixture'; +import * as fixture20 from './packages/app/fixtures/GroupListItem.fixture'; +import * as fixture21 from './packages/app/fixtures/GroupList.fixture'; +import * as fixture22 from './packages/app/fixtures/GalleryPost.fixture'; +import * as fixture23 from './packages/app/fixtures/Form.fixture'; +import * as fixture24 from './packages/app/fixtures/FindGroups.fixture'; +import * as fixture25 from './packages/app/fixtures/EditProfileScreen.fixture'; +import * as fixture26 from './packages/app/fixtures/CreateGroup.fixture'; +import * as fixture27 from './packages/app/fixtures/ContactList.fixture'; +import * as fixture28 from './packages/app/fixtures/ChatMessage.fixture'; +import * as fixture29 from './packages/app/fixtures/ChannelSwitcherSheet.fixture'; +import * as fixture30 from './packages/app/fixtures/ChannelHeader.fixture'; +import * as fixture31 from './packages/app/fixtures/ChannelDivider.fixture'; +import * as fixture32 from './packages/app/fixtures/Channel.fixture'; +import * as fixture33 from './packages/app/fixtures/Button.fixture'; +import * as fixture34 from './packages/app/fixtures/BlockSectionList.fixture'; +import * as fixture35 from './packages/app/fixtures/Avatar.fixture'; +import * as fixture36 from './packages/app/fixtures/AudioEmbed.fixture'; +import * as fixture37 from './packages/app/fixtures/AttachmentPreviewList.fixture'; +import * as fixture38 from './packages/app/fixtures/AddGroupSheet.fixture'; +import * as fixture39 from './packages/app/fixtures/Activity.fixture'; +import * as fixture40 from './packages/app/fixtures/DetailView/NotebookDetailView.fixture'; +import * as fixture41 from './packages/app/fixtures/DetailView/GalleryDetailView.fixture'; +import * as fixture42 from './packages/app/fixtures/DetailView/ChatDetailView.fixture'; +import * as fixture43 from './packages/app/fixtures/ActionSheet/SendPostRetrySheet.fixture'; +import * as fixture44 from './packages/app/fixtures/ActionSheet/ProfileSheet.fixture'; +import * as fixture45 from './packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture'; +import * as fixture46 from './packages/app/fixtures/ActionSheet/GroupJoinRequestSheet.fixture'; +import * as fixture47 from './packages/app/fixtures/ActionSheet/GenericActionSheet.fixture'; +import * as fixture48 from './packages/app/fixtures/ActionSheet/EditSectionNameSheet.fixture'; +import * as fixture49 from './packages/app/fixtures/ActionSheet/DeleteSheet.fixture'; +import * as fixture50 from './packages/app/fixtures/ActionSheet/CreateChannelSheet.fixture'; +import * as fixture51 from './packages/app/fixtures/ActionSheet/AttachmentSheet.fixture'; +import * as fixture52 from './packages/app/fixtures/ActionSheet/AddGalleryPostSheet.fixture'; +import * as fixture53 from './apps/tlon-mobile/src/App.fixture'; +import * as fixture54 from './apps/tlon-mobile/src/fixtures/SetNicknameScreen.fixture'; +import * as fixture55 from './apps/tlon-mobile/src/fixtures/Onboarding.fixture'; +import * as fixture56 from './apps/tlon-mobile/src/fixtures/InputToolbar.fixture'; import * as decorator0 from './packages/app/fixtures/cosmos.decorator'; import * as decorator1 from './apps/tlon-mobile/src/fixtures/cosmos.decorator'; export const rendererConfig: RendererConfig = { - "playgroundUrl": "http://localhost:5000", + "playgroundUrl": "http://localhost:5001", "rendererUrl": null }; @@ -77,52 +79,54 @@ const fixtures = { 'packages/app/fixtures/ReferenceSkeleton.fixture.tsx': { module: fixture6 }, 'packages/app/fixtures/ProfileSheet.fixture.tsx': { module: fixture7 }, 'packages/app/fixtures/ProfileBlock.fixture.tsx': { module: fixture8 }, - 'packages/app/fixtures/PostScreen.fixture.tsx': { module: fixture9 }, - 'packages/app/fixtures/PostReference.fixture.tsx': { module: fixture10 }, - 'packages/app/fixtures/ParentAgnosticKeyboardAvoidingView.fixture.tsx': { module: fixture11 }, - 'packages/app/fixtures/OutsideEmbed.fixture.tsx': { module: fixture12 }, - 'packages/app/fixtures/MetaEditorScreen.fixture.tsx': { module: fixture13 }, - 'packages/app/fixtures/MessageInput.fixture.tsx': { module: fixture14 }, - 'packages/app/fixtures/MessageActions.fixture.tsx': { module: fixture15 }, - 'packages/app/fixtures/InviteUsersSheet.fixture.tsx': { module: fixture16 }, - 'packages/app/fixtures/Input.fixture.tsx': { module: fixture17 }, - 'packages/app/fixtures/ImageViewer.fixture.tsx': { module: fixture18 }, - 'packages/app/fixtures/GroupListItem.fixture.tsx': { module: fixture19 }, - 'packages/app/fixtures/GroupList.fixture.tsx': { module: fixture20 }, - 'packages/app/fixtures/GalleryPost.fixture.tsx': { module: fixture21 }, - 'packages/app/fixtures/Form.fixture.tsx': { module: fixture22 }, - 'packages/app/fixtures/FindGroups.fixture.tsx': { module: fixture23 }, - 'packages/app/fixtures/CreateGroup.fixture.tsx': { module: fixture24 }, - 'packages/app/fixtures/ContactList.fixture.tsx': { module: fixture25 }, - 'packages/app/fixtures/ChatMessage.fixture.tsx': { module: fixture26 }, - 'packages/app/fixtures/ChannelSwitcherSheet.fixture.tsx': { module: fixture27 }, - 'packages/app/fixtures/ChannelHeader.fixture.tsx': { module: fixture28 }, - 'packages/app/fixtures/ChannelDivider.fixture.tsx': { module: fixture29 }, - 'packages/app/fixtures/Channel.fixture.tsx': { module: fixture30 }, - 'packages/app/fixtures/Button.fixture.tsx': { module: fixture31 }, - 'packages/app/fixtures/BlockSectionList.fixture.tsx': { module: fixture32 }, - 'packages/app/fixtures/Avatar.fixture.tsx': { module: fixture33 }, - 'packages/app/fixtures/AudioEmbed.fixture.tsx': { module: fixture34 }, - 'packages/app/fixtures/AttachmentPreviewList.fixture.tsx': { module: fixture35 }, - 'packages/app/fixtures/AddGroupSheet.fixture.tsx': { module: fixture36 }, - 'packages/app/fixtures/Activity.fixture.tsx': { module: fixture37 }, - 'apps/tlon-mobile/src/App.fixture.tsx': { module: fixture38 }, - 'packages/app/fixtures/DetailView/NotebookDetailView.fixture.tsx': { module: fixture39 }, - 'packages/app/fixtures/DetailView/GalleryDetailView.fixture.tsx': { module: fixture40 }, - 'packages/app/fixtures/DetailView/ChatDetailView.fixture.tsx': { module: fixture41 }, - 'packages/app/fixtures/ActionSheet/SendPostRetrySheet.fixture.tsx': { module: fixture42 }, - 'packages/app/fixtures/ActionSheet/ProfileSheet.fixture.tsx': { module: fixture43 }, - 'packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture.tsx': { module: fixture44 }, - 'packages/app/fixtures/ActionSheet/GroupJoinRequestSheet.fixture.tsx': { module: fixture45 }, - 'packages/app/fixtures/ActionSheet/GenericActionSheet.fixture.tsx': { module: fixture46 }, - 'packages/app/fixtures/ActionSheet/EditSectionNameSheet.fixture.tsx': { module: fixture47 }, - 'packages/app/fixtures/ActionSheet/DeleteSheet.fixture.tsx': { module: fixture48 }, - 'packages/app/fixtures/ActionSheet/CreateChannelSheet.fixture.tsx': { module: fixture49 }, - 'packages/app/fixtures/ActionSheet/AttachmentSheet.fixture.tsx': { module: fixture50 }, - 'packages/app/fixtures/ActionSheet/AddGalleryPostSheet.fixture.tsx': { module: fixture51 }, - 'apps/tlon-mobile/src/fixtures/SetNicknameScreen.fixture.tsx': { module: fixture52 }, - 'apps/tlon-mobile/src/fixtures/Onboarding.fixture.tsx': { module: fixture53 }, - 'apps/tlon-mobile/src/fixtures/InputToolbar.fixture.tsx': { module: fixture54 } + 'packages/app/fixtures/Pressable.fixture.tsx': { module: fixture9 }, + 'packages/app/fixtures/PostScreen.fixture.tsx': { module: fixture10 }, + 'packages/app/fixtures/PostReference.fixture.tsx': { module: fixture11 }, + 'packages/app/fixtures/ParentAgnosticKeyboardAvoidingView.fixture.tsx': { module: fixture12 }, + 'packages/app/fixtures/OutsideEmbed.fixture.tsx': { module: fixture13 }, + 'packages/app/fixtures/MetaEditorScreen.fixture.tsx': { module: fixture14 }, + 'packages/app/fixtures/MessageInput.fixture.tsx': { module: fixture15 }, + 'packages/app/fixtures/MessageActions.fixture.tsx': { module: fixture16 }, + 'packages/app/fixtures/InviteUsersSheet.fixture.tsx': { module: fixture17 }, + 'packages/app/fixtures/Input.fixture.tsx': { module: fixture18 }, + 'packages/app/fixtures/ImageViewer.fixture.tsx': { module: fixture19 }, + 'packages/app/fixtures/GroupListItem.fixture.tsx': { module: fixture20 }, + 'packages/app/fixtures/GroupList.fixture.tsx': { module: fixture21 }, + 'packages/app/fixtures/GalleryPost.fixture.tsx': { module: fixture22 }, + 'packages/app/fixtures/Form.fixture.tsx': { module: fixture23 }, + 'packages/app/fixtures/FindGroups.fixture.tsx': { module: fixture24 }, + 'packages/app/fixtures/EditProfileScreen.fixture.tsx': { module: fixture25 }, + 'packages/app/fixtures/CreateGroup.fixture.tsx': { module: fixture26 }, + 'packages/app/fixtures/ContactList.fixture.tsx': { module: fixture27 }, + 'packages/app/fixtures/ChatMessage.fixture.tsx': { module: fixture28 }, + 'packages/app/fixtures/ChannelSwitcherSheet.fixture.tsx': { module: fixture29 }, + 'packages/app/fixtures/ChannelHeader.fixture.tsx': { module: fixture30 }, + 'packages/app/fixtures/ChannelDivider.fixture.tsx': { module: fixture31 }, + 'packages/app/fixtures/Channel.fixture.tsx': { module: fixture32 }, + 'packages/app/fixtures/Button.fixture.tsx': { module: fixture33 }, + 'packages/app/fixtures/BlockSectionList.fixture.tsx': { module: fixture34 }, + 'packages/app/fixtures/Avatar.fixture.tsx': { module: fixture35 }, + 'packages/app/fixtures/AudioEmbed.fixture.tsx': { module: fixture36 }, + 'packages/app/fixtures/AttachmentPreviewList.fixture.tsx': { module: fixture37 }, + 'packages/app/fixtures/AddGroupSheet.fixture.tsx': { module: fixture38 }, + 'packages/app/fixtures/Activity.fixture.tsx': { module: fixture39 }, + 'packages/app/fixtures/DetailView/NotebookDetailView.fixture.tsx': { module: fixture40 }, + 'packages/app/fixtures/DetailView/GalleryDetailView.fixture.tsx': { module: fixture41 }, + 'packages/app/fixtures/DetailView/ChatDetailView.fixture.tsx': { module: fixture42 }, + 'packages/app/fixtures/ActionSheet/SendPostRetrySheet.fixture.tsx': { module: fixture43 }, + 'packages/app/fixtures/ActionSheet/ProfileSheet.fixture.tsx': { module: fixture44 }, + 'packages/app/fixtures/ActionSheet/GroupPreviewSheet.fixture.tsx': { module: fixture45 }, + 'packages/app/fixtures/ActionSheet/GroupJoinRequestSheet.fixture.tsx': { module: fixture46 }, + 'packages/app/fixtures/ActionSheet/GenericActionSheet.fixture.tsx': { module: fixture47 }, + 'packages/app/fixtures/ActionSheet/EditSectionNameSheet.fixture.tsx': { module: fixture48 }, + 'packages/app/fixtures/ActionSheet/DeleteSheet.fixture.tsx': { module: fixture49 }, + 'packages/app/fixtures/ActionSheet/CreateChannelSheet.fixture.tsx': { module: fixture50 }, + 'packages/app/fixtures/ActionSheet/AttachmentSheet.fixture.tsx': { module: fixture51 }, + 'packages/app/fixtures/ActionSheet/AddGalleryPostSheet.fixture.tsx': { module: fixture52 }, + 'apps/tlon-mobile/src/App.fixture.tsx': { module: fixture53 }, + 'apps/tlon-mobile/src/fixtures/SetNicknameScreen.fixture.tsx': { module: fixture54 }, + 'apps/tlon-mobile/src/fixtures/Onboarding.fixture.tsx': { module: fixture55 }, + 'apps/tlon-mobile/src/fixtures/InputToolbar.fixture.tsx': { module: fixture56 } }; const decorators = { diff --git a/packages/app/features/settings/EditProfileScreen.tsx b/packages/app/features/settings/EditProfileScreen.tsx index 0425f18a51..450f315cda 100644 --- a/packages/app/features/settings/EditProfileScreen.tsx +++ b/packages/app/features/settings/EditProfileScreen.tsx @@ -1,48 +1,18 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack'; -import * as api from '@tloncorp/shared/api'; import * as store from '@tloncorp/shared/store'; import { AttachmentProvider, EditProfileScreenView, GroupsProvider, } from '@tloncorp/ui'; -import { useCallback } from 'react'; -import { useCurrentUserId } from '../../hooks/useCurrentUser'; import { RootStackParamList } from '../../navigation/types'; type Props = NativeStackScreenProps; export function EditProfileScreen({ route, navigation }: Props) { - const currentUserId = useCurrentUserId(); - const { data: groups } = store.useGroups({ includeUnjoined: true }); - - const onSaveProfile = useCallback( - (update: api.ProfileUpdate | null) => { - if (update) { - if (route.params.userId === currentUserId) { - store.updateCurrentUserProfile(update); - } else { - store.updateContactMetadata(route.params.userId, { - nickname: update.nickname, - avatarImage: update.avatarImage, - }); - } - } - navigation.goBack(); - }, - [currentUserId, navigation, route.params.userId] - ); - - const onUpdateCoverImage = useCallback((coverImage: string) => { - store.updateCurrentUserProfile({ coverImage }); - }, []); - - const onUpdateAvatarImage = useCallback((avatarImage: string) => { - store.updateCurrentUserProfile({ avatarImage }); - }, []); - const canUpload = store.useCanUpload(); + const { data: groups } = store.useGroups({ includeUnjoined: true }); return ( @@ -50,10 +20,6 @@ export function EditProfileScreen({ route, navigation }: Props) { navigation.goBack()} - onSaveProfile={onSaveProfile} - onUpdatePinnedGroups={store.updateProfilePinnedGroups} - onUpdateCoverImage={onUpdateCoverImage} - onUpdateAvatarImage={onUpdateAvatarImage} /> diff --git a/packages/app/fixtures/EditProfileScreen.fixture.tsx b/packages/app/fixtures/EditProfileScreen.fixture.tsx new file mode 100644 index 0000000000..63cf567385 --- /dev/null +++ b/packages/app/fixtures/EditProfileScreen.fixture.tsx @@ -0,0 +1,46 @@ +import { faker } from '@faker-js/faker'; +import * as db from '@tloncorp/shared/db'; +import { + AppDataContextProvider, + EditProfileScreenView, +} from '@tloncorp/ui/src'; +import { useFixtureInput } from 'react-cosmos/client'; + +import { FixtureWrapper } from './FixtureWrapper'; +import { group } from './fakeData'; + +const exampleContacts: Record = { + base: { + id: '~solfer-magfed', + nickname: 'Dan b', + color: faker.color.rgb(), + bio: faker.lorem.paragraphs(2), + avatarImage: faker.image.avatar(), + pinnedGroups: [ + { + contactId: '~fabled-faster', + groupId: group.id, + group, + }, + ], + }, +}; + +function EditProfileScreen({ contactId }: { contactId: string }) { + const [isCurrentUser] = useFixtureInput('isCurrentUser', true); + + return ( + + + {}} /> + + + ); +} + +export default { + 'Full Profile': , +}; diff --git a/packages/app/fixtures/cosmos.decorator.tsx b/packages/app/fixtures/cosmos.decorator.tsx index 57275d9cd7..e4d4890339 100644 --- a/packages/app/fixtures/cosmos.decorator.tsx +++ b/packages/app/fixtures/cosmos.decorator.tsx @@ -1,10 +1,12 @@ -import { TamaguiProvider, config } from '@tloncorp/ui'; +import { StoreProvider, TamaguiProvider, config } from '@tloncorp/ui'; import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; // eslint-disable-next-line export default ({ children }: { children: React.ReactNode }) => ( - {children} + + {children} + ); diff --git a/packages/ui/src/components/EditProfileScreenView.tsx b/packages/ui/src/components/EditProfileScreenView.tsx index dffd9e52e8..cdf65c6f8d 100644 --- a/packages/ui/src/components/EditProfileScreenView.tsx +++ b/packages/ui/src/components/EditProfileScreenView.tsx @@ -1,5 +1,5 @@ -import * as api from '@tloncorp/shared/api'; import * as db from '@tloncorp/shared/db'; +import { useStore } from '@tloncorp/ui'; import { useCallback, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { Alert } from 'react-native'; @@ -23,13 +23,10 @@ import { BioDisplay, PinnedGroupsDisplay } from './UserProfileScreenView'; interface Props { userId: string; onGoBack: () => void; - onSaveProfile: (update: api.ProfileUpdate | null) => void; - onUpdatePinnedGroups: (groups: db.Group[]) => void; - onUpdateCoverImage: (coverImage: string) => void; - onUpdateAvatarImage: (avatarImage: string) => void; } export function EditProfileScreenView(props: Props) { + const store = useStore(); const insets = useSafeAreaInsets(); const currentUserId = useCurrentUserId(); const userContact = useContact(props.userId); @@ -104,16 +101,24 @@ export function EditProfileScreenView(props: Props) { ? null // clear existing : undefined, }; - props.onSaveProfile(update); + + if (isCurrUser) { + store.updateCurrentUserProfile(update); + } else { + store.updateContactMetadata(props.userId, { + nickname: update.nickname, + avatarImage: update.avatarImage, + }); + } })(); - } else { - props.onGoBack(); } + props.onGoBack(); }, [ handleSubmit, isCurrUser, isDirty, props, + store, userContact?.avatarImage, userContact?.customAvatarImage, userContact?.customNickname, @@ -140,13 +145,10 @@ export function EditProfileScreenView(props: Props) { } }; - const handleUpdatePinnedGroups = useCallback( - (groups: db.Group[]) => { - setPinnedGroups(groups); - props.onUpdatePinnedGroups(groups); - }, - [props] - ); + const handleUpdatePinnedGroups = useCallback((groups: db.Group[]) => { + setPinnedGroups(groups); + store.updateProfilePinnedGroups(groups); + }, []); return ( diff --git a/packages/ui/src/contexts/index.ts b/packages/ui/src/contexts/index.ts index 7cf6bb7612..5020cad780 100644 --- a/packages/ui/src/contexts/index.ts +++ b/packages/ui/src/contexts/index.ts @@ -6,3 +6,4 @@ export * from './channel'; export * from './appDataContext'; export * from './attachment'; export * from './requests'; +export * from './storeContext'; diff --git a/packages/ui/src/contexts/storeContext.tsx b/packages/ui/src/contexts/storeContext.tsx new file mode 100644 index 0000000000..bb97982736 --- /dev/null +++ b/packages/ui/src/contexts/storeContext.tsx @@ -0,0 +1,48 @@ +import { createDevLogger } from '@tloncorp/shared'; +import * as store from '@tloncorp/shared/store'; +import React, { createContext, useContext } from 'react'; + +type StoreType = typeof store; +type StoreContextType = StoreType; + +const logger = createDevLogger('StoreContext', true); + +function createNoOpFunction(key: string) { + return new Proxy(() => {}, { + apply: () => undefined, + get: () => createNoOpFunction(`${key}.property`), + }); +} + +const StoreContext = createContext(null); + +interface StoreProviderProps { + children: React.ReactNode; + stub?: boolean; +} + +export function StoreProvider({ children, stub = false }: StoreProviderProps) { + const storeValue = React.useMemo(() => { + if (stub) { + return new Proxy({} as StoreType, { + get: (target, prop) => { + logger.log('Mocked store call', prop.toString()); + return createNoOpFunction(prop.toString()); + }, + }); + } + return store; + }, [stub]); + + return ( + {children} + ); +} + +export function useStore(): StoreType { + const context = useContext(StoreContext); + if (context === null) { + throw new Error('useStore must be used within a StoreProvider'); + } + return context; +} From eac8164bb0bda74f0f4fbba8a2d3a8bffd24270c Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 6 Dec 2024 06:04:23 -0600 Subject: [PATCH 105/149] fix type issue in linking module --- packages/app/navigation/linking.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/navigation/linking.ts b/packages/app/navigation/linking.ts index be68849eda..9b09d4e154 100644 --- a/packages/app/navigation/linking.ts +++ b/packages/app/navigation/linking.ts @@ -87,7 +87,7 @@ export const getDesktopLinkingConfig = ( initialRouteName: 'Home', screens: { Activity: 'activity', - Profile: 'profile', + Contacts: 'contacts', Home: { screens: { ChatList: '', From 6fc6ef0403916929e8b317960ec9918b9091b4b6 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 6 Dec 2024 07:07:09 -0600 Subject: [PATCH 106/149] web/chat input: reset chat input height on send --- packages/ui/src/components/BareChatInput/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/BareChatInput/index.tsx b/packages/ui/src/components/BareChatInput/index.tsx index b237bc7b4d..b1b2bad80f 100644 --- a/packages/ui/src/components/BareChatInput/index.tsx +++ b/packages/ui/src/components/BareChatInput/index.tsx @@ -371,6 +371,7 @@ export default function BareChatInput({ clearAttachments(); clearDraft(); setHasSetInitialContent(false); + setInputHeight(initialHeight); }, [ onSend, @@ -387,6 +388,7 @@ export default function BareChatInput({ send, channelId, setMentions, + initialHeight, ] ); @@ -558,7 +560,8 @@ export default function BareChatInput({ setControlledText(''); clearDraft(); clearAttachments(); - }, [setEditingPost, clearDraft, clearAttachments]); + setInputHeight(initialHeight); + }, [setEditingPost, clearDraft, clearAttachments, initialHeight]); const theme = useTheme(); From c4ef7863f90795105661a639f1750a6b55e252ee Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 6 Dec 2024 07:16:46 -0600 Subject: [PATCH 107/149] desktop: set a max height on the invite people modal --- packages/ui/src/components/InviteUsersSheet.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/InviteUsersSheet.tsx b/packages/ui/src/components/InviteUsersSheet.tsx index bb18c0e367..8a47b16e6a 100644 --- a/packages/ui/src/components/InviteUsersSheet.tsx +++ b/packages/ui/src/components/InviteUsersSheet.tsx @@ -1,6 +1,7 @@ import * as db from '@tloncorp/shared/db'; import React, { useRef } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { getTokenValue, useWindowDimensions } from 'tamagui'; import { ActionSheet } from './ActionSheet'; import { InviteUsersWidget } from './InviteUsersWidget'; @@ -18,6 +19,8 @@ const InviteUsersSheetComponent = ({ }) => { const { bottom } = useSafeAreaInsets(); const hasOpened = useRef(open); + const { height } = useWindowDimensions(); + const maxHeight = height - bottom - getTokenValue('$2xl'); if (!hasOpened.current && open) { hasOpened.current = true; @@ -32,7 +35,12 @@ const InviteUsersSheetComponent = ({ snapPoints={[85]} snapPointsMode="percent" > - + From 6f5530bd704d8981a0034badfefd86cba92ad799 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 6 Dec 2024 09:42:25 -0600 Subject: [PATCH 108/149] set max height for all ActionSheetContent instead of just InviteUsersSheet --- packages/ui/src/components/ActionSheet.tsx | 15 +++++++++++++-- packages/ui/src/components/InviteUsersSheet.tsx | 4 ---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/ui/src/components/ActionSheet.tsx b/packages/ui/src/components/ActionSheet.tsx index a5215b80a5..fc3a513b19 100644 --- a/packages/ui/src/components/ActionSheet.tsx +++ b/packages/ui/src/components/ActionSheet.tsx @@ -8,7 +8,7 @@ import { useMemo, useRef, } from 'react'; -import { Modal } from 'react-native'; +import { Modal, useWindowDimensions } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Dialog, @@ -190,7 +190,18 @@ const ActionSheetHeader = ActionSheetHeaderFrame.styleable( const ActionSheetContent = YStack.styleable((props, ref) => { const contentStyle = useContentStyle(); - return ; + const { bottom } = useSafeAreaInsets(); + const { height } = useWindowDimensions(); + const maxHeight = height - bottom - getTokenValue('$2xl'); + return ( + + ); }); const ActionSheetScrollableContent = ({ diff --git a/packages/ui/src/components/InviteUsersSheet.tsx b/packages/ui/src/components/InviteUsersSheet.tsx index 8a47b16e6a..f726bc50e9 100644 --- a/packages/ui/src/components/InviteUsersSheet.tsx +++ b/packages/ui/src/components/InviteUsersSheet.tsx @@ -1,7 +1,6 @@ import * as db from '@tloncorp/shared/db'; import React, { useRef } from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { getTokenValue, useWindowDimensions } from 'tamagui'; import { ActionSheet } from './ActionSheet'; import { InviteUsersWidget } from './InviteUsersWidget'; @@ -19,8 +18,6 @@ const InviteUsersSheetComponent = ({ }) => { const { bottom } = useSafeAreaInsets(); const hasOpened = useRef(open); - const { height } = useWindowDimensions(); - const maxHeight = height - bottom - getTokenValue('$2xl'); if (!hasOpened.current && open) { hasOpened.current = true; @@ -37,7 +34,6 @@ const InviteUsersSheetComponent = ({ > From bf68409ada1bb9a2671d0c1775360580b1d44604 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 6 Dec 2024 15:49:34 +0000 Subject: [PATCH 109/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index bfacb2664c..dbd77cab3b 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0vr4p9n.vrsur.04pus.od75v.hpc8t.glob' 0vr4p9n.vrsur.04pus.od75v.hpc8t] + glob-http+['https://bootstrap.urbit.org/glob-0v4.p5grl.md5m3.2bjar.0da7n.ph2b6.glob' 0v4.p5grl.md5m3.2bjar.0da7n.ph2b6] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 1ea0367960635d5f5a49c81d886191487331f45e Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 6 Dec 2024 16:06:47 +0000 Subject: [PATCH 110/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index dbd77cab3b..a0c693ff4c 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.p5grl.md5m3.2bjar.0da7n.ph2b6.glob' 0v4.p5grl.md5m3.2bjar.0da7n.ph2b6] + glob-http+['https://bootstrap.urbit.org/glob-0v7.17h4l.4fedf.fnlbn.5jhet.nc4rm.glob' 0v7.17h4l.4fedf.fnlbn.5jhet.nc4rm] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From b3bb24cd18cf421a156f0a2c326a35981d65494d Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 6 Dec 2024 10:52:12 -0600 Subject: [PATCH 111/149] images: provide default fallback for errored images --- packages/ui/src/components/Image.tsx | 58 ++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/packages/ui/src/components/Image.tsx b/packages/ui/src/components/Image.tsx index bd72d302e3..f9be0305ff 100644 --- a/packages/ui/src/components/Image.tsx +++ b/packages/ui/src/components/Image.tsx @@ -1,9 +1,38 @@ import { Image as BaseImage, ImageErrorEventData } from 'expo-image'; import { ReactElement, useCallback, useState } from 'react'; import { Platform, StyleSheet } from 'react-native'; -import { styled } from 'tamagui'; +import { SizableText, View, styled } from 'tamagui'; + +import { Icon } from './Icon'; + +const DefaultImageFallback = () => ( + + + Unable to load image + +); + +const WebImage = ({ + source, + style, + alt, + onLoad, + onError, + fallback, + ...props +}: any) => { + const [hasError, setHasError] = useState(false); + + const handleError = (e: React.SyntheticEvent) => { + setHasError(true); + if (onError) { + onError({ + error: new Error('Image loading failed'), + target: e.currentTarget, + }); + } + }; -const WebImage = ({ source, style, alt, onLoad, ...props }: any) => { const handleLoad = (e: React.SyntheticEvent) => { if (onLoad) { // Mimic expo-image's onLoad event structure @@ -16,7 +45,15 @@ const WebImage = ({ source, style, alt, onLoad, ...props }: any) => { } }; - const { contentFit } = props; + const { contentFit } = props; + + if (hasError && fallback) { + return fallback; + } + + if (hasError) { + return ; + } return ( { objectFit: contentFit ? contentFit : undefined, }} onLoad={handleLoad} + onError={handleError} {...props} /> ); @@ -51,11 +89,15 @@ export const ImageWithFallback = StyledBaseImage.styleable<{ [onError] ); - return hasErrored ? ( - fallback - ) : ( - - ); + if (hasErrored && fallback) { + return fallback; + } + + if (hasErrored) { + return ; + } + + return ; }, { staticConfig: { From f1805f73558bea51864873c5fc5faa744a78946a Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Fri, 6 Dec 2024 11:48:28 -0600 Subject: [PATCH 112/149] remove contact find from sync --- packages/shared/src/store/sync.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/shared/src/store/sync.ts b/packages/shared/src/store/sync.ts index 034e4ace09..e3546cf02d 100644 --- a/packages/shared/src/store/sync.ts +++ b/packages/shared/src/store/sync.ts @@ -1175,10 +1175,6 @@ export const syncStart = async (alreadySubscribed?: boolean) => { }); updateIsSyncing(false); - - // finding contacts is a bit of an outlier here, but it's work we need to do - // that can roughly be batched whenever we sync - findContactSuggestions(); }; export const setupHighPrioritySubscriptions = async (ctx?: SyncCtx) => { From 98af227053432c457b3233da66e5a59c4b23f9c6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 6 Dec 2024 17:55:30 +0000 Subject: [PATCH 113/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 7c5efe7802..52539581e4 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v2.imu38.n1u9d.o2plp.8juvp.5t2ao.glob' 0v2.imu38.n1u9d.o2plp.8juvp.5t2ao] + glob-http+['https://bootstrap.urbit.org/glob-0v2.3gbss.14f1b.93jfu.0mgq2.33ufj.glob' 0v2.3gbss.14f1b.93jfu.0mgq2.33ufj] base+'groups' version+[6 5 0] website+'https://tlon.io' From 0c07bc0783f40edf717ba4d4d10fa66d90d94a45 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 6 Dec 2024 17:55:40 +0000 Subject: [PATCH 114/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index a0c693ff4c..5a8a3e7ad3 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v7.17h4l.4fedf.fnlbn.5jhet.nc4rm.glob' 0v7.17h4l.4fedf.fnlbn.5jhet.nc4rm] + glob-http+['https://bootstrap.urbit.org/glob-0v7.1nch1.27gg6.6jedi.qof24.1cj50.glob' 0v7.1nch1.27gg6.6jedi.qof24.1cj50] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From a0c6f9979d42aa49c8a2119b1c08b53ed7b435f5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 6 Dec 2024 19:17:08 +0000 Subject: [PATCH 115/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 5a8a3e7ad3..e93ce509ac 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v7.1nch1.27gg6.6jedi.qof24.1cj50.glob' 0v7.1nch1.27gg6.6jedi.qof24.1cj50] + glob-http+['https://bootstrap.urbit.org/glob-0v4.7e796.tbvdk.1geoj.obbim.nt8bs.glob' 0v4.7e796.tbvdk.1geoj.obbim.nt8bs] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 07d78a07be756ab1ea36b7d5a597d3018deb3371 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Fri, 6 Dec 2024 13:31:14 -0600 Subject: [PATCH 116/149] fix typing for noOpStore --- packages/ui/src/contexts/storeContext.tsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/packages/ui/src/contexts/storeContext.tsx b/packages/ui/src/contexts/storeContext.tsx index bb97982736..fe560ccc32 100644 --- a/packages/ui/src/contexts/storeContext.tsx +++ b/packages/ui/src/contexts/storeContext.tsx @@ -4,13 +4,23 @@ import React, { createContext, useContext } from 'react'; type StoreType = typeof store; type StoreContextType = StoreType; +type NoOpFunction = (() => void) & { [key: string]: NoOpFunction }; const logger = createDevLogger('StoreContext', true); -function createNoOpFunction(key: string) { +function createNoOpFunction(key: string): NoOpFunction { return new Proxy(() => {}, { apply: () => undefined, get: () => createNoOpFunction(`${key}.property`), + }) as unknown as NoOpFunction; +} + +function createNoOpStore(): StoreType { + return new Proxy({} as StoreType, { + get: (target, prop) => { + logger.log('Mocked store call', prop.toString()); + return createNoOpFunction(prop.toString()); + }, }); } @@ -24,12 +34,7 @@ interface StoreProviderProps { export function StoreProvider({ children, stub = false }: StoreProviderProps) { const storeValue = React.useMemo(() => { if (stub) { - return new Proxy({} as StoreType, { - get: (target, prop) => { - logger.log('Mocked store call', prop.toString()); - return createNoOpFunction(prop.toString()); - }, - }); + return createNoOpStore(); } return store; }, [stub]); From e08502e653349a49700b14e3647a57de84b7bffd Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Fri, 6 Dec 2024 13:33:04 -0600 Subject: [PATCH 117/149] add stub store to both decorators --- apps/tlon-mobile/src/fixtures/cosmos.decorator.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/tlon-mobile/src/fixtures/cosmos.decorator.tsx b/apps/tlon-mobile/src/fixtures/cosmos.decorator.tsx index 57275d9cd7..e4d4890339 100644 --- a/apps/tlon-mobile/src/fixtures/cosmos.decorator.tsx +++ b/apps/tlon-mobile/src/fixtures/cosmos.decorator.tsx @@ -1,10 +1,12 @@ -import { TamaguiProvider, config } from '@tloncorp/ui'; +import { StoreProvider, TamaguiProvider, config } from '@tloncorp/ui'; import React from 'react'; import { SafeAreaProvider } from 'react-native-safe-area-context'; // eslint-disable-next-line export default ({ children }: { children: React.ReactNode }) => ( - {children} + + {children} + ); From 89f58f2214f9e28496c5b8f1a6c2039cc72ac132 Mon Sep 17 00:00:00 2001 From: ~latter-bolden Date: Fri, 6 Dec 2024 13:35:05 -0600 Subject: [PATCH 118/149] include in new web --- apps/tlon-web-new/src/app.tsx | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/tlon-web-new/src/app.tsx b/apps/tlon-web-new/src/app.tsx index c2e2f702ea..41cd019b98 100644 --- a/apps/tlon-web-new/src/app.tsx +++ b/apps/tlon-web-new/src/app.tsx @@ -18,7 +18,7 @@ import { Provider as TamaguiProvider } from '@tloncorp/app/provider'; import { AppDataProvider } from '@tloncorp/app/provider/AppDataProvider'; import { sync } from '@tloncorp/shared'; import * as store from '@tloncorp/shared/store'; -import { LoadingSpinner, View } from '@tloncorp/ui'; +import { LoadingSpinner, StoreProvider, View } from '@tloncorp/ui'; import cookies from 'browser-cookies'; import { usePostHog } from 'posthog-js/react'; import React, { PropsWithChildren, useEffect, useState } from 'react'; @@ -184,18 +184,20 @@ const App = React.memo(function AppComponent() { - {dbIsLoaded ? ( - - ) : ( - - - - )} + + {dbIsLoaded ? ( + + ) : ( + + + + )} + From 9f1701b29f4c7d8ca10e9093d5600f95e48b50f5 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Fri, 6 Dec 2024 14:59:16 -0600 Subject: [PATCH 119/149] gallery: fix item size issues on desktop --- .../ui/src/components/Channel/Scroller.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Channel/Scroller.tsx b/packages/ui/src/components/Channel/Scroller.tsx index 2f6b68c534..bb2e078dcf 100644 --- a/packages/ui/src/components/Channel/Scroller.tsx +++ b/packages/ui/src/components/Channel/Scroller.tsx @@ -32,7 +32,7 @@ import { } from 'react-native'; import Animated from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { View, styled, useStyle, useTheme } from 'tamagui'; +import { View, getTokenValue, styled, useStyle, useTheme } from 'tamagui'; import { RenderItemType } from '../../contexts/componentsKits'; import { useLivePost } from '../../contexts/requests'; @@ -201,13 +201,16 @@ const Scroller = forwardRef( const theme = useTheme(); - // Used to hide the scroller until we've found the anchor post. const style = useMemo(() => { return { backgroundColor: theme.background.val, + // Used to hide the scroller until we've found the anchor post. opacity: readyToDisplayPosts ? 1 : 0, + // Necessary to prevent content from flowing off the right side of the + // screen when the scroller is in two-column mode. + paddingRight: collectionLayoutType === 'grid' ? getTokenValue('$l') : 0, }; - }, [readyToDisplayPosts, theme.background.val]); + }, [readyToDisplayPosts, theme.background.val, collectionLayoutType]); const postsWithNeighbors: PostWithNeighbors[] | undefined = useMemo( () => @@ -268,6 +271,7 @@ const Scroller = forwardRef( messageRef={activeMessageRefs.current[post.id]} dividersEnabled={collectionLayout.dividersEnabled} itemAspectRatio={collectionLayout.itemAspectRatio ?? undefined} + columnCount={collectionLayout.columnCount} {...anchorScrollLockScrollerItemProps} /> ); @@ -580,6 +584,7 @@ const BaseScrollerItem = ({ isLastPostOfBlock, dividersEnabled, itemAspectRatio, + columnCount, }: { showUnreadDivider: boolean; showAuthor: boolean; @@ -603,6 +608,7 @@ const BaseScrollerItem = ({ isLastPostOfBlock: boolean; dividersEnabled: boolean; itemAspectRatio?: number; + columnCount: number; }) => { const post = useLivePost(item); @@ -652,7 +658,11 @@ const BaseScrollerItem = ({ }, [dividerType, post, unreadCount, showDayDivider]); return ( - + {divider} Date: Fri, 6 Dec 2024 22:44:34 +0100 Subject: [PATCH 120/149] profile: use new contacts type & endpoint This was still using the old contacts types and scry endpoint. Here, we use the latest instead, and move contact data unpacking up to the top of the file while we're at it. --- desk/app/profile/widgets.hoon | 72 ++++++++++++++++------------------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/desk/app/profile/widgets.hoon b/desk/app/profile/widgets.hoon index a5d3d63327..21b8a2ed84 100644 --- a/desk/app/profile/widgets.hoon +++ b/desk/app/profile/widgets.hoon @@ -1,21 +1,23 @@ :: profile: construct stock widgets :: -/- co=contacts-0 +/- co=contacts /+ sigil :: |= =bowl:gall -=/ ours=(unit contact-0:co) - =, co - ::NOTE we scry for the full rolodex, because we are not guaranteed to - :: have an entry for ourselves, and contacts doesn't expose a "safe" - :: (as in crashless) endpoint for checking - =+ .^ =rolodex - /gx/(scot %p our.bowl)/contacts/(scot %da now.bowl)/all/contact-rolodex - == - =/ =foreign-0 (~(gut by rolodex) our.bowl *foreign-0) - ?: ?=([[@ ^] *] foreign-0) - `con.for.foreign-0 - ~ +=+ .^ =contact:co + /gx/(scot %p our.bowl)/contacts/(scot %da now.bowl)/v1/self/contact-1 + == +::NOTE can't quite make a nice helper for this, wetness not wet enough... +=/ nickname=(unit @t) =+ a=(~(gut by contact) %nickname %text '') + ?:(&(?=(%text -.a) !=('' +.a)) `+.a ~) +=/ bio=(unit @t) =+ a=(~(gut by contact) %bio %text '') + ?:(&(?=(%text -.a) !=('' +.a)) `+.a ~) +=/ color=@ux =+ a=(~(gut by contact) %color %tint 0x0) + ?:(?=(%tint -.a) +.a 0x0) +=/ avatar=(unit @ta) =+ a=(~(gut by contact) %avatar %look '') + ?:(&(?=(%look -.a) !=('' +.a)) `+.a ~) +=/ cover=(unit @ta) =+ a=(~(gut by contact) %cover %look '') + ?:(&(?=(%look -.a) !=('' +.a)) `+.a ~) |^ %- ~(gas by *(map term [%0 @t %marl marl])) :~ [%profile %0 'Profile Header' %marl profile-widget] [%profile-bio %0 'Profile Bio' %marl profile-bio] @@ -69,10 +71,9 @@ ;p(style "margin-bottom: 0;") ;em:"A {class} flying through space since {since}..." == - ?~ ours stand-in - ?: =('' bio.u.ours) stand-in + ?~ bio stand-in %+ join `manx`;br; - %+ turn (to-wain:format bio.u.ours) + %+ turn (to-wain:format u.bio) |= p=@t ^- manx [[%$ [%$ (trip p)] ~] ~] == @@ -95,7 +96,7 @@ flex-direction: column; } @media screen and (min-width: 15em) { - .profile-headline { + .profile-headline { flex-direction: row; } } @@ -147,20 +148,14 @@ :~ ;style:"{(trip style)}" :: - =/ src=(unit @t) - ?~ ours ~ - ?~ cover.u.ours ~ - ?: =('' u.cover.u.ours) ~ - `u.cover.u.ours - ?~ src + ?~ cover ;div.profile-without-header ;div.profile-headline - ;+ ?: &(?=(^ ours) ?=(^ avatar.u.ours) !=('' u.avatar.u.ours)) + ;+ ?^ avatar ;img.profile-headline-avatar - =src "{(trip u.avatar.u.ours)}" + =src "{(trip u.avatar)}" =alt "Avatar"; - =/ value=@ux ?~(ours 0x0 color.u.ours) - =/ color=tape ((x-co:^co 6) value) + =/ color=tape ((x-co:^co 6) color) ;div.profile-headline-avatar-sigil(style "background-color: #{color}") ;+ %. our.bowl %_ sigil @@ -174,25 +169,23 @@ ;div.profile-headline-title ;* =* name (cite:title our.bowl) =* plain ;h1.profile-headline-nickname(title "{(scow %p our.bowl)}"):"{name}" - ?~ ours [plain]~ - ?: =('' nickname.u.ours) [plain]~ - :~ ;h1.profile-headline-nickname:"{(trip nickname.u.ours)}" + ?~ nickname [plain]~ + :~ ;h1.profile-headline-nickname:"{(trip u.nickname)}" ;p.profile-headline-username(title "{(scow %p our.bowl)}"):"{name}" + == == == - == == ;div.profile-with-header ;img#profile-background - =src "{(trip u.src)}" + =src "{(trip u.cover)}" =alt "Background"; ;div.profile-headline - ;+ ?: &(?=(^ ours) ?=(^ avatar.u.ours) !=('' u.avatar.u.ours)) + ;+ ?^ avatar ;img.profile-headline-avatar - =src "{(trip u.avatar.u.ours)}" + =src "{(trip u.avatar)}" =alt "Avatar"; - =/ value=@ux ?~(ours 0x0 color.u.ours) - =/ color=tape ((x-co:^co 6) value) + =/ color=tape ((x-co:^co 6) color) ;div.profile-headline-avatar-sigil(style "background-color: #{color}") ;+ %. our.bowl %_ sigil @@ -206,13 +199,12 @@ ;div.profile-headline-title ;* =* name (cite:title our.bowl) =* plain ;h1.profile-headline-nickname(title "{(scow %p our.bowl)}"):"{name}" - ?~ ours [plain]~ - ?: =('' nickname.u.ours) [plain]~ - :~ ;h1.profile-headline-nickname:"{(trip nickname.u.ours)}" + ?~ nickname [plain]~ + :~ ;h1.profile-headline-nickname:"{(trip u.nickname)}" ;p.profile-headline-username(title "{(scow %p our.bowl)}"):"{name}" + == == == - == == == :: From 3ddd38f5110441be75c7c013b22a852476824525 Mon Sep 17 00:00:00 2001 From: fang Date: Fri, 6 Dec 2024 23:00:14 +0100 Subject: [PATCH 121/149] profile: dedupe profile widget rendering code --- desk/app/profile/widgets.hoon | 78 +++++++++++++---------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/desk/app/profile/widgets.hoon b/desk/app/profile/widgets.hoon index 21b8a2ed84..bd28e626b8 100644 --- a/desk/app/profile/widgets.hoon +++ b/desk/app/profile/widgets.hoon @@ -148,63 +148,41 @@ :~ ;style:"{(trip style)}" :: - ?~ cover - ;div.profile-without-header - ;div.profile-headline - ;+ ?^ avatar - ;img.profile-headline-avatar - =src "{(trip u.avatar)}" - =alt "Avatar"; - =/ color=tape ((x-co:^co 6) color) - ;div.profile-headline-avatar-sigil(style "background-color: #{color}") - ;+ %. our.bowl - %_ sigil - bg '#'^color - ::REVIEW groups fe caps the color's lightness, instead of - :: choosing between white/black fg. should we, too? - fg "white" ::?:((gth (div (roll (rip 3 value) add) 3) 127) "black" "white") - == - == - :: - ;div.profile-headline-title - ;* =* name (cite:title our.bowl) - =* plain ;h1.profile-headline-nickname(title "{(scow %p our.bowl)}"):"{name}" - ?~ nickname [plain]~ - :~ ;h1.profile-headline-nickname:"{(trip u.nickname)}" - ;p.profile-headline-username(title "{(scow %p our.bowl)}"):"{name}" - == + =* profile-inner + ;div.profile-headline + ;+ ?^ avatar + ;img.profile-headline-avatar + =src "{(trip u.avatar)}" + =alt "Avatar"; + =/ color=tape ((x-co:^co 6) color) + ;div.profile-headline-avatar-sigil(style "background-color: #{color}") + ;+ %. our.bowl + %_ sigil + bg '#'^color + ::REVIEW groups fe caps the color's lightness, instead of + :: choosing between white/black fg. should we, too? + fg "white" ::?:((gth (div (roll (rip 3 value) add) 3) 127) "black" "white") == == + :: + ;div.profile-headline-title + ;* =* name (cite:title our.bowl) + =* plain ;h1.profile-headline-nickname(title "{(scow %p our.bowl)}"):"{name}" + ?~ nickname [plain]~ + :~ ;h1.profile-headline-nickname:"{(trip u.nickname)}" + ;p.profile-headline-username(title "{(scow %p our.bowl)}"):"{name}" + == + == + == + ?~ cover + ;div.profile-without-header + ;+ profile-inner == ;div.profile-with-header ;img#profile-background =src "{(trip u.cover)}" =alt "Background"; - ;div.profile-headline - ;+ ?^ avatar - ;img.profile-headline-avatar - =src "{(trip u.avatar)}" - =alt "Avatar"; - =/ color=tape ((x-co:^co 6) color) - ;div.profile-headline-avatar-sigil(style "background-color: #{color}") - ;+ %. our.bowl - %_ sigil - bg '#'^color - ::REVIEW groups fe caps the color's lightness, instead of - :: choosing between white/black fg. should we, too? - fg "white" ::?:((gth (div (roll (rip 3 value) add) 3) 127) "black" "white") - == - == - :: - ;div.profile-headline-title - ;* =* name (cite:title our.bowl) - =* plain ;h1.profile-headline-nickname(title "{(scow %p our.bowl)}"):"{name}" - ?~ nickname [plain]~ - :~ ;h1.profile-headline-nickname:"{(trip u.nickname)}" - ;p.profile-headline-username(title "{(scow %p our.bowl)}"):"{name}" - == - == - == + ;+ profile-inner == == :: From eccc67b300dcfd9e5e6d3181637c4a1bbd149f72 Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 9 Dec 2024 08:46:07 -0600 Subject: [PATCH 122/149] desktop: fix welcome sheet image, remove duplicate web component for welcome sheet --- packages/ui/src/components/WelcomeSheet.tsx | 8 +- .../ui/src/components/WelcomeSheet.web.tsx | 111 ------------------ 2 files changed, 6 insertions(+), 113 deletions(-) delete mode 100644 packages/ui/src/components/WelcomeSheet.web.tsx diff --git a/packages/ui/src/components/WelcomeSheet.tsx b/packages/ui/src/components/WelcomeSheet.tsx index 29abd49c73..08bc67c7e3 100644 --- a/packages/ui/src/components/WelcomeSheet.tsx +++ b/packages/ui/src/components/WelcomeSheet.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from 'react'; import { Image } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Text, View, XStack, YStack } from 'tamagui'; +import { Text, View, XStack, YStack, isWeb } from 'tamagui'; import { Icon } from './Icon'; import { Sheet } from './Sheet'; @@ -41,7 +41,11 @@ function WelcomeSheetComponent({ diff --git a/packages/ui/src/components/WelcomeSheet.web.tsx b/packages/ui/src/components/WelcomeSheet.web.tsx deleted file mode 100644 index 302751b0c6..0000000000 --- a/packages/ui/src/components/WelcomeSheet.web.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import React, { useCallback } from 'react'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Text, View, XStack, YStack } from 'tamagui'; - -import { Icon } from './Icon'; -import { Sheet } from './Sheet'; - -function WelcomeSheetComponent({ - open, - onOpenChange, -}: { - open: boolean; - onOpenChange: (open: boolean) => void; -}) { - const handleDismiss = useCallback(() => { - onOpenChange(false); - }, [onOpenChange]); - - const inset = useSafeAreaInsets(); - - return ( - - - - - - - - - - - Welcome to TM - - A messenger you can finally trust. - - - - - - - - - - Control every bit - - Whatever you do, say, and make on Tlon is yours to keep - - - - - - - - - - - From now until forever - - With Tlon you can always take your data with you and continue - using it elsewhere - - - - - - - - - - - Connect with calm - - Tlon is designed to maximize genuine connection, not addictive - engagement - - - - - - - - ); -} - -export const WelcomeSheet = React.memo(WelcomeSheetComponent); From ea5d6eb719e04dba589fa9ce6e5d12e53b93ca3a Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 9 Dec 2024 09:48:47 -0600 Subject: [PATCH 123/149] desktop chat input: autofocus and handle image pastes --- .../ui/src/components/BareChatInput/index.tsx | 45 +++++++++++++++++++ .../src/components/draftInputs/ChatInput.tsx | 6 ++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/BareChatInput/index.tsx b/packages/ui/src/components/BareChatInput/index.tsx index b1b2bad80f..3bb94f943b 100644 --- a/packages/ui/src/components/BareChatInput/index.tsx +++ b/packages/ui/src/components/BareChatInput/index.tsx @@ -554,6 +554,51 @@ export default function BareChatInput({ setMentions, ]); + // Handle pastes on web + useEffect(() => { + // For now, we only check to make sure we're on web, + // we don't check if the input is focused. This allows users to paste + // images before they select the input. We may want to change this behavior + // if this feels weird, but it feels like a nice quality of life improvement. + // We can do this because there is only ever one input on the screen at a time, + // unlike the old app where you could have both the main chat input and the + // thread input on screen at the same time. + if (!isWeb) return; + + const handlePaste = async (e: ClipboardEvent) => { + const items = Array.from(e.clipboardData?.items || []); + const image = items.find((item) => item.type.includes('image')); + + if (!image) return; + + const file = image.getAsFile(); + if (!file) return; + + const uri = URL.createObjectURL(file); + + const img = new Image(); + + img.onload = () => { + addAttachment({ + type: 'image', + file: { + uri, + height: img.height, + width: img.width, + }, + }); + }; + + img.src = uri; + }; + + document.addEventListener('paste', handlePaste); + + return () => { + document.removeEventListener('paste', handlePaste); + }; + }, [addAttachment]); + const handleCancelEditing = useCallback(() => { setEditingPost?.(undefined); setHasSetInitialContent(false); diff --git a/packages/ui/src/components/draftInputs/ChatInput.tsx b/packages/ui/src/components/draftInputs/ChatInput.tsx index 9f3f14cd55..55f99d8a84 100644 --- a/packages/ui/src/components/draftInputs/ChatInput.tsx +++ b/packages/ui/src/components/draftInputs/ChatInput.tsx @@ -1,5 +1,7 @@ import { SafeAreaView } from 'react-native-safe-area-context'; +import { isWeb } from 'tamagui'; +import useIsWindowNarrow from '../../hooks/useIsWindowNarrow'; import BareChatInput from '../BareChatInput'; import { ParentAgnosticKeyboardAvoidingView } from '../ParentAgnosticKeyboardAvoidingView'; import { DraftInputContext } from './shared'; @@ -23,6 +25,8 @@ export function ChatInput({ storeDraft, } = draftInputContext; + const isWindowNarrow = useIsWindowNarrow(); + return ( @@ -39,7 +43,7 @@ export function ChatInput({ setEditingPost={setEditingPost} editPost={editPost} channelType={channel.type} - shouldAutoFocus={!!editingPost} + shouldAutoFocus={!!editingPost || (isWeb && !isWindowNarrow)} showInlineAttachments showAttachmentButton /> From 050a1faaf95a74b050118166bba64a6ffc1668b9 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Dec 2024 18:26:49 +0000 Subject: [PATCH 124/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index e93ce509ac..cd5f566600 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v4.7e796.tbvdk.1geoj.obbim.nt8bs.glob' 0v4.7e796.tbvdk.1geoj.obbim.nt8bs] + glob-http+['https://bootstrap.urbit.org/glob-0v5.7lsg2.eaq56.cpuc2.7i6po.r9tlb.glob' 0v5.7lsg2.eaq56.cpuc2.7i6po.r9tlb] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From ca8190407929a2e0df92b8ab908f9fe1c50f4dde Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Dec 2024 18:27:54 +0000 Subject: [PATCH 125/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index cd5f566600..a3170ba792 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v5.7lsg2.eaq56.cpuc2.7i6po.r9tlb.glob' 0v5.7lsg2.eaq56.cpuc2.7i6po.r9tlb] + glob-http+['https://bootstrap.urbit.org/glob-0ve356m.ntkpu.cg9e6.gcae5.stnu8.glob' 0ve356m.ntkpu.cg9e6.gcae5.stnu8] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 86950b3ea952d4e985e9f090a1d71432a94f902d Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Dec 2024 18:41:37 +0000 Subject: [PATCH 126/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index a3170ba792..2687a3422c 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0ve356m.ntkpu.cg9e6.gcae5.stnu8.glob' 0ve356m.ntkpu.cg9e6.gcae5.stnu8] + glob-http+['https://bootstrap.urbit.org/glob-0v6.ohc7a.7f7g9.1h28f.hkgf9.kgdis.glob' 0v6.ohc7a.7f7g9.1h28f.hkgf9.kgdis] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From db76b4c118c12703f76b1f98e4a60c4e2986ab6e Mon Sep 17 00:00:00 2001 From: Patrick O'Sullivan Date: Mon, 9 Dec 2024 13:56:51 -0600 Subject: [PATCH 127/149] address dan's nits --- packages/ui/src/components/Channel/Scroller.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/components/Channel/Scroller.tsx b/packages/ui/src/components/Channel/Scroller.tsx index bb2e078dcf..489b71a071 100644 --- a/packages/ui/src/components/Channel/Scroller.tsx +++ b/packages/ui/src/components/Channel/Scroller.tsx @@ -206,11 +206,8 @@ const Scroller = forwardRef( backgroundColor: theme.background.val, // Used to hide the scroller until we've found the anchor post. opacity: readyToDisplayPosts ? 1 : 0, - // Necessary to prevent content from flowing off the right side of the - // screen when the scroller is in two-column mode. - paddingRight: collectionLayoutType === 'grid' ? getTokenValue('$l') : 0, }; - }, [readyToDisplayPosts, theme.background.val, collectionLayoutType]); + }, [readyToDisplayPosts, theme.background.val]); const postsWithNeighbors: PostWithNeighbors[] | undefined = useMemo( () => @@ -294,6 +291,7 @@ const Scroller = forwardRef( showDividers, collectionLayout.dividersEnabled, collectionLayout.itemAspectRatio, + collectionLayout.columnCount, ] ); @@ -339,6 +337,9 @@ const Scroller = forwardRef( : { gap: '$l', width: '100%', + // Necessary to prevent content from flowing off the right side of the + // screen when the scroller is in two-column mode. + paddingRight: '$l', } ) as StyleProp; From 0d7723055c4610aa6db062393aed459baf2d325c Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 9 Dec 2024 20:15:21 +0000 Subject: [PATCH 128/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 2687a3422c..69238ee225 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v6.ohc7a.7f7g9.1h28f.hkgf9.kgdis.glob' 0v6.ohc7a.7f7g9.1h28f.hkgf9.kgdis] + glob-http+['https://bootstrap.urbit.org/glob-0v3.0qjcu.adg9o.fmipl.3jjug.9sg90.glob' 0v3.0qjcu.adg9o.fmipl.3jjug.9sg90] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 89e58a340f23480c39a60912ea47352d01523f57 Mon Sep 17 00:00:00 2001 From: James Acklin Date: Mon, 9 Dec 2024 19:18:57 -0500 Subject: [PATCH 129/149] ChannelListItem, channelUtils: fix bare patp display in ChatList --- packages/ui/src/components/ListItem/ChannelListItem.tsx | 5 +++-- packages/ui/src/utils/channelUtils.tsx | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/ListItem/ChannelListItem.tsx b/packages/ui/src/components/ListItem/ChannelListItem.tsx index 357433072c..4614e435a0 100644 --- a/packages/ui/src/components/ListItem/ChannelListItem.tsx +++ b/packages/ui/src/components/ListItem/ChannelListItem.tsx @@ -78,11 +78,12 @@ export function ChannelListItem({ {title} {customSubtitle ? ( {customSubtitle} - ) : ( + ) : (model.type === 'dm' || model.type === 'groupDm') && + utils.hasNickname(model.members?.[0]?.contact) ? ( {subtitle} - )} + ) : null} {model.lastPost && !model.isDmInvite && ( 0; +} From a0a51e4ce28563f28af764d605d8f86a31b8570b Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 10 Dec 2024 01:47:24 +0000 Subject: [PATCH 130/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 69238ee225..2a8d7d22ca 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v3.0qjcu.adg9o.fmipl.3jjug.9sg90.glob' 0v3.0qjcu.adg9o.fmipl.3jjug.9sg90] + glob-http+['https://bootstrap.urbit.org/glob-0v2.odvja.jq5n8.vlsnn.u2fr6.dild5.glob' 0v2.odvja.jq5n8.vlsnn.u2fr6.dild5] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From e7e9a30c6407e1ae5f72a0af0f11ec728847f48c Mon Sep 17 00:00:00 2001 From: Dan Brewster Date: Tue, 10 Dec 2024 13:22:36 -0500 Subject: [PATCH 131/149] fix desktop chatlist jank (#4273) --- packages/ui/src/components/ChatList.tsx | 32 +++++++++++++++++-- .../ui/src/components/ListItem/ListItem.tsx | 1 + 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/ChatList.tsx b/packages/ui/src/components/ChatList.tsx index 19ff7d79c3..5f1efec263 100644 --- a/packages/ui/src/components/ChatList.tsx +++ b/packages/ui/src/components/ChatList.tsx @@ -7,6 +7,7 @@ import React, { useEffect, useLayoutEffect, useMemo, + useRef, useState, } from 'react'; import { LayoutChangeEvent } from 'react-native'; @@ -86,11 +87,33 @@ export const ChatList = React.memo(function ChatListComponent({ paddingBottom: 100, // bottom nav height + some cushion }; + const sizeRefs = useRef({ + sectionHeader: 28, + chatListItem: 72, + }); + + const handleHeaderLayout = useCallback((e: LayoutChangeEvent) => { + sizeRefs.current.sectionHeader = e.nativeEvent.layout.height; + }, []); + + const handleItemLayout = useCallback((e: LayoutChangeEvent) => { + sizeRefs.current.chatListItem = e.nativeEvent.layout.height; + }, []); + + const handleOverrideLayout = useCallback( + (layout: { span?: number; size?: number }, item: ChatListItemData) => { + layout.size = isSectionHeader(item) + ? sizeRefs.current.sectionHeader + : sizeRefs.current.chatListItem; + }, + [] + ); + const renderItem: ListRenderItem = useCallback( ({ item }) => { if (isSectionHeader(item)) { return ( - + {item.title} ); @@ -100,6 +123,7 @@ export const ChatList = React.memo(function ChatListComponent({ model={item} onPress={onPressItem} onLongPress={handleLongPress} + onLayout={handleItemLayout} /> ); } else { @@ -108,11 +132,12 @@ export const ChatList = React.memo(function ChatListComponent({ model={item} onPress={onPressItem} onLongPress={handleLongPress} + onLayout={handleItemLayout} /> ); } }, - [onPressItem, handleLongPress] + [handleHeaderLayout, onPressItem, handleLongPress, handleItemLayout] ); const handlePressTryAll = useCallback(() => { @@ -150,7 +175,8 @@ export const ChatList = React.memo(function ChatListComponent({ keyExtractor={getChatKey} renderItem={renderItem} getItemType={getItemType} - estimatedItemSize={getTokenValue('$6xl', 'size')} + estimatedItemSize={sizeRefs.current.chatListItem} + overrideItemLayout={handleOverrideLayout} /> )} diff --git a/packages/ui/src/components/ListItem/ListItem.tsx b/packages/ui/src/components/ListItem/ListItem.tsx index 5d3d4bb237..136960e6d8 100644 --- a/packages/ui/src/components/ListItem/ListItem.tsx +++ b/packages/ui/src/components/ListItem/ListItem.tsx @@ -45,6 +45,7 @@ export const ListItemFrame = styled(XStack, { justifyContent: 'space-between', alignItems: 'stretch', backgroundColor: '$transparent', + height: '$6xl', }); const ListItemIconContainer = styled(View, { From 9c02ca05f4781104fba393ab73d2b1c386f2f940 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 10 Dec 2024 18:29:26 +0000 Subject: [PATCH 132/149] update new frontend glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 2a8d7d22ca..5f07c379d2 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v2.odvja.jq5n8.vlsnn.u2fr6.dild5.glob' 0v2.odvja.jq5n8.vlsnn.u2fr6.dild5] + glob-http+['https://bootstrap.urbit.org/glob-0v7.9h46p.ne45g.s9atj.cp1rs.5fcjn.glob' 0v7.9h46p.ne45g.s9atj.cp1rs.5fcjn] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 224c9bcb69c5caf140f0d00e3cb6e3f245b412f6 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 10 Dec 2024 15:33:01 -0600 Subject: [PATCH 133/149] ops: version bump --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index 1068cf6dca..f74edb38be 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -4,7 +4,7 @@ image+'https://bootstrap.urbit.org/tlon.svg?v=1' glob-http+['https://bootstrap.urbit.org/glob-0v2.3gbss.14f1b.93jfu.0mgq2.33ufj.glob' 0v2.3gbss.14f1b.93jfu.0mgq2.33ufj] base+'groups' - version+[6 6 0] + version+[6 7 0] website+'https://tlon.io' license+'MIT' == From e1193ceb82191d577feefd7c0409531339613b4d Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Fri, 6 Dec 2024 11:08:58 -0600 Subject: [PATCH 134/149] wip: hook templates --- desk/app/channels-server.hoon | 67 +++++++++++++++++++++++++++++++++++ desk/sur/hooks.hoon | 7 ++++ 2 files changed, 74 insertions(+) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 13589d93af..7234632277 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -343,6 +343,11 @@ [~ %| *] ~& [dap.bowl %overwriting-pending-import] cor(pimp `|+egg-any) == + :: + %hook-setup-template + ?> =(src our):bowl + =+ !<([=nest:c =template:h] vase) + (setup-hook-template nest template) :: %hook-action-0 =+ !<(=action:h vase) @@ -425,6 +430,9 @@ [%v0 %hooks %full ~] =. cor (give %fact ~ hook-full+!>(hooks)) (give %kick ~ ~) + :: + [%hooks %v0 %template =kind:c name=@ ~] + (get-hook-template kind.pole our.bowl name.pole) :: [=kind:c name=@ %create ~] ?> =(our src):bowl @@ -1269,4 +1277,63 @@ (~(put by waiting.hooks) id.effect [origin +.effect]) (schedule-waiting +.effect) == +++ get-hook-template + |= =nest:c + ^+ cor + =/ order=(list id:h) (~(got by order.hooks) nest) + =/ crons=(list [id:h job:h]) + %+ roll ~(tap by crons.hooks) + |= [[=id:h =cron:h] cr=(list [id:h job:h])] + ?~ job=(~(get by cron) nest) cr + (snoc cr [id u.job]) + =/ ids=(list id:h) (welp order (turn crons head)) + =/ hooks=(map id:h hook:h) + %- ~(gas by *(map id:h hook:h)) + ^- (list [id:h hook:h]) + %+ turn ids + |= =id:h + ^- [id:h hook:h] + =/ hook (~(got by hooks.hooks) id) + =/ config (~(gut by config.hook) nest ~) + =/ config-map (~(put by *(map nest:c config:h)) nest config) + :- id + hook(compiled ~, state !>(~), config config-map) + =/ =template:h + :* nest + *data:m + hooks + order + crons + == + =. cor (give %fact ~ hook-template+!>(template)) + (give %kick ~ ~) +++ setup-hook-template + |= [=nest:c =template:h] + ^+ cor + =. order.hooks + (~(put by order.hooks) nest order.template) + =. crons.hooks + %- ~(gas by crons.hooks) + %+ turn crons.template + |= [=id:h =job:h] + :- id + (~(put by *cron:h) nest job) + =. hooks.hooks + %- ~(gas by hooks.hooks) + %+ turn + ~(tap by hooks.template) + |= [=id:h =hook:h] + ?~ old-config=(~(get by config.hook) from.template) + [id hook(config ~)] + [id hook(config (my [nest u.old-config] ~))] + =/ crons crons.template + |- + ?~ crons cor + =* cron +.i.crons + =/ fires-at + ?@ schedule.cron + (add now.bowl schedule.cron) + (add now.bowl repeat.schedule.cron) + =. cor (schedule-cron nest cron(fires-at fires-at)) + $(crons t.crons) -- diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 02875011de..cff700f5cf 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -176,4 +176,11 @@ [%wait waiting-hook] == :: ++$ template + $: from=nest + meta=data:m + hooks=(map id hook) + order=(list id) + crons=(list [id job]) + == -- From eed53c61a57f16a06ab26f4ce43bcc6b3f8c57ac Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Tue, 10 Dec 2024 17:30:41 -0600 Subject: [PATCH 135/149] channels: working hook preview and template flow --- apps/tlon-mobile/ios/Podfile.lock | 2 +- desk/app/channels-server.hoon | 79 +++++++++++-------- desk/app/channels.hoon | 25 ++++++ desk/lib/channel-json.hoon | 6 ++ desk/lib/hooks-json.hoon | 10 +++ desk/mar/channel/create-from-template.hoon | 14 ++++ desk/mar/hook/channel-preview.hoon | 14 ++++ desk/sur/hooks.hoon | 8 +- desk/ted/channel/create-from-template.hoon | 33 ++++++++ desk/ted/hook/configure.hoon | 2 +- desk/ted/hook/del.hoon | 2 +- desk/ted/hook/edit.hoon | 2 +- desk/ted/hook/order.hoon | 2 +- desk/ted/hook/run.hoon | 6 +- desk/ted/hook/schedule.hoon | 6 +- .../app/hooks/useChatSettingsNavigation.ts | 11 ++- packages/app/navigation/types.ts | 4 + packages/shared/src/api/channelsApi.ts | 12 ++- packages/shared/src/api/urbit.ts | 17 +++- packages/shared/src/http-api/Urbit.ts | 10 ++- packages/shared/src/http-api/types.ts | 1 + packages/shared/src/store/index.ts | 1 + .../src/store/useChannelHooksPreview.ts | 13 +++ packages/shared/src/urbit/channel.ts | 3 + .../components/ChannelFromTemplateView.tsx | 14 ++++ .../ui/src/components/ChatOptionsSheet.tsx | 27 ++++++- packages/ui/src/contexts/chatOptions.tsx | 5 ++ 27 files changed, 268 insertions(+), 61 deletions(-) create mode 100644 desk/mar/channel/create-from-template.hoon create mode 100644 desk/mar/hook/channel-preview.hoon create mode 100644 desk/ted/channel/create-from-template.hoon create mode 100644 packages/shared/src/store/useChannelHooksPreview.ts create mode 100644 packages/ui/src/components/ChannelFromTemplateView.tsx diff --git a/apps/tlon-mobile/ios/Podfile.lock b/apps/tlon-mobile/ios/Podfile.lock index 716c600b86..9c74a78eb5 100644 --- a/apps/tlon-mobile/ios/Podfile.lock +++ b/apps/tlon-mobile/ios/Podfile.lock @@ -1884,4 +1884,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 0cb7a78e5777e69c86c1bf4bb5135fd660376dbe -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 7234632277..d3f01fd2d2 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -428,11 +428,17 @@ [%v0 %hooks ~] cor :: [%v0 %hooks %full ~] - =. cor (give %fact ~ hook-full+!>(hooks)) - (give %kick ~ ~) + (give %fact ~ hook-full+!>(hooks)) + :: + [%v0 %hooks %preview =kind:c name=@ ~] + =/ cp=channel-preview:h + (get-channel-hooks-preview kind.pole our.bowl name.pole) + (give %fact ~ hook-channel-preview+!>(cp)) :: - [%hooks %v0 %template =kind:c name=@ ~] - (get-hook-template kind.pole our.bowl name.pole) + [%v0 %hooks %template =kind:c name=@ ~] + =/ =template:h + (get-hook-template kind.pole our.bowl name.pole) + (give %fact ~ hook-template+!>(template)) :: [=kind:c name=@ %create ~] ?> =(our src):bowl @@ -1279,34 +1285,31 @@ == ++ get-hook-template |= =nest:c - ^+ cor - =/ order=(list id:h) (~(got by order.hooks) nest) - =/ crons=(list [id:h job:h]) + ^- template:h + =/ order=(list id-hook:h) (~(got by order.hooks) nest) + =/ crons=(list [id-hook:h job:h]) %+ roll ~(tap by crons.hooks) - |= [[=id:h =cron:h] cr=(list [id:h job:h])] + |= [[=id-hook:h =cron:h] cr=(list [id-hook:h job:h])] ?~ job=(~(get by cron) nest) cr - (snoc cr [id u.job]) - =/ ids=(list id:h) (welp order (turn crons head)) - =/ hooks=(map id:h hook:h) - %- ~(gas by *(map id:h hook:h)) - ^- (list [id:h hook:h]) + (snoc cr [id-hook u.job]) + =/ ids=(list id-hook:h) (welp order (turn crons head)) + =/ hooks=(map id-hook:h hook:h) + %- ~(gas by *(map id-hook:h hook:h)) + ^- (list [id-hook:h hook:h]) %+ turn ids - |= =id:h - ^- [id:h hook:h] - =/ hook (~(got by hooks.hooks) id) + |= =id-hook:h + ^- [id-hook:h hook:h] + =/ hook (~(got by hooks.hooks) id-hook) =/ config (~(gut by config.hook) nest ~) =/ config-map (~(put by *(map nest:c config:h)) nest config) - :- id + :- id-hook hook(compiled ~, state !>(~), config config-map) - =/ =template:h - :* nest - *data:m - hooks - order - crons - == - =. cor (give %fact ~ hook-template+!>(template)) - (give %kick ~ ~) + :* nest + *data:m + hooks + order + crons + == ++ setup-hook-template |= [=nest:c =template:h] ^+ cor @@ -1315,25 +1318,33 @@ =. crons.hooks %- ~(gas by crons.hooks) %+ turn crons.template - |= [=id:h =job:h] - :- id + |= [=id-hook:h =job:h] + :- id-hook (~(put by *cron:h) nest job) =. hooks.hooks %- ~(gas by hooks.hooks) %+ turn ~(tap by hooks.template) - |= [=id:h =hook:h] + |= [=id-hook:h =hook:h] ?~ old-config=(~(get by config.hook) from.template) - [id hook(config ~)] - [id hook(config (my [nest u.old-config] ~))] + [id-hook hook(config ~)] + [id-hook hook(config (my [nest u.old-config] ~))] =/ crons crons.template |- ?~ crons cor =* cron +.i.crons =/ fires-at - ?@ schedule.cron - (add now.bowl schedule.cron) + ?: (gth next.schedule.cron now.bowl) + next.schedule.cron (add now.bowl repeat.schedule.cron) - =. cor (schedule-cron nest cron(fires-at fires-at)) + =. cor (schedule-cron nest cron(next.schedule fires-at)) $(crons t.crons) +++ get-channel-hooks-preview + |= =nest:c + ^- channel-preview:h + =/ =template:h (get-hook-template nest) + %+ turn + ~(tap by hooks.template) + |= [=id-hook:h =hook:h] + [name.hook meta.hook] -- diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 3ea8afa5aa..f5af3006f1 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -551,6 +551,15 @@ [~ %| *] ~& [dap.bowl %overwriting-pending-import] cor(pimp `|+egg-any) == + :: + %channel-create-from-template + =+ !<(args=[nest:c flag:g (unit @t)] vase) + %- emit + :* %pass /create-template + %arvo %k %fard + q.byk.bowl %channel-create-from-template %noun + !>(`args) + == == ++ toggle-post |= toggle=post-toggle:c @@ -592,6 +601,12 @@ [?(%v0 %v1) ~] ?>(from-self cor) [?(%v0 %v1) %unreads ~] ?>(from-self cor) [?(%v0 %v1) =kind:c ship=@ name=@ ~] ?>(from-self cor) + :: + [%v1 %hooks %preview =kind:c host=@ name=@ ~] + =/ host=ship (slav %p host.pole) + =/ =path /v0/hooks/preview/[kind.pole]/[name.pole] + ((safe-watch pole [host %channels-server] path) |) + :: [?(%v0 %v1) %said =kind:c host=@ name=@ %post time=@ reply=?(~ [@ ~])] =/ host=ship (slav %p host.pole) =/ =nest:c [kind.pole host name.pole] @@ -698,6 +713,16 @@ %- (slog 'channels: migration poke failure' >wire< u.p.sign) cor == + :: + [%v1 %hooks %preview =kind:c host=@ name=@ ~] + ?+ -.sign !! + %kick cor + %fact (give %fact ~[pole] cage.sign) + :: + %watch-ack + ?~ p.sign cor + ((slog leaf+"Preview failed" u.p.sign) cor) + == == :: ++ watch-groups (safe-watch /groups [our.bowl %groups] /groups) diff --git a/desk/lib/channel-json.hoon b/desk/lib/channel-json.hoon index 579836550c..c3b1a07eaf 100644 --- a/desk/lib/channel-json.hoon +++ b/desk/lib/channel-json.hoon @@ -861,5 +861,11 @@ :~ hide/(se %ud) show/(se %ud) == + ++ template-create + %- ot + :~ nest/nest + flag/flag + name/(mu so) + == -- -- diff --git a/desk/lib/hooks-json.hoon b/desk/lib/hooks-json.hoon index 10946c0d81..aa9132608c 100644 --- a/desk/lib/hooks-json.hoon +++ b/desk/lib/hooks-json.hoon @@ -141,6 +141,16 @@ ~! tk =/ next=^tape ~(ram re tk) (welp (snoc tp '\0a') next) + ++ channel-preview + |= cp=channel-preview:h + :- %a + %+ turn + cp + |= [name=@t meta=data:m] + %- pairs + :~ name+s+name + meta+(meta:enjs:gj meta) + == -- :: ++ dejs diff --git a/desk/mar/channel/create-from-template.hoon b/desk/mar/channel/create-from-template.hoon new file mode 100644 index 0000000000..5881f2e8b2 --- /dev/null +++ b/desk/mar/channel/create-from-template.hoon @@ -0,0 +1,14 @@ +/- c=channels, g=groups +/+ j=channel-json +|_ [=nest:c =flag:g name=(unit @t)] +++ grad %noun +++ grow + |% + ++ noun [nest flag name] + -- +++ grab + |% + ++ noun [=nest:c =flag:g name=(unit @t)] + ++ json template-create:dejs:j + -- +-- diff --git a/desk/mar/hook/channel-preview.hoon b/desk/mar/hook/channel-preview.hoon new file mode 100644 index 0000000000..5b91789261 --- /dev/null +++ b/desk/mar/hook/channel-preview.hoon @@ -0,0 +1,14 @@ +/- h=hooks +/+ hj=hooks-json +|_ =channel-preview:h +++ grad %noun +++ grow + |% + ++ noun channel-preview + ++ json (channel-preview:enjs:hj channel-preview) + -- +++ grab + |% + ++ noun channel-preview:h + -- +-- diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index cff700f5cf..2d9be5a098 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -176,11 +176,13 @@ [%wait waiting-hook] == :: ++$ channel-preview (list [name=@t meta=data:m]) +:: +$ template $: from=nest meta=data:m - hooks=(map id hook) - order=(list id) - crons=(list [id job]) + hooks=(map id-hook hook) + order=(list id-hook) + crons=(list [id-hook job]) == -- diff --git a/desk/ted/channel/create-from-template.hoon b/desk/ted/channel/create-from-template.hoon new file mode 100644 index 0000000000..d9e18abd18 --- /dev/null +++ b/desk/ted/channel/create-from-template.hoon @@ -0,0 +1,33 @@ +/- spider, h=hooks, c=channels, g=groups +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +=+ !<([~ =nest:c =flag:g name=(unit @t)] arg) +;< our=@p bind:m get-our:s +;< eny=@ bind:m get-entropy:s +=/ id (end 3^3 eny) +=/ nest-name=term (cat 3 'channel-' id) +=/ new-nest=nest:c [kind.nest our nest-name] +=/ =create-channel:c + :* kind.nest + nest-name + flag + (fall name '') + '' + ~ + ~ + == +=/ create-cage=cage + channel-action+!>(`a-channels:c`[%create create-channel]) +;< ~ bind:m (poke-our:s %channels create-cage) +;< ~ bind:m + (watch:s /template [ship.nest %channels-server] /v0/hooks/template/[kind.nest]/[name.nest]) +;< =^cage bind:m (take-fact:s /template) +?> ?=(%hook-template p.cage) +=+ !<(=template:h q.cage) +=/ =cage hook-setup-template+!>([new-nest template]) +;< ~ bind:m (poke-our:s %channels-server cage) +(pure:m !>(~)) diff --git a/desk/ted/hook/configure.hoon b/desk/ted/hook/configure.hoon index 574666a24f..ba6b5df885 100644 --- a/desk/ted/hook/configure.hoon +++ b/desk/ted/hook/configure.hoon @@ -5,7 +5,7 @@ |= arg=vase =/ m (strand ,vase) ^- form:m -=+ !<([~ =id:h =nest:c =config:h] arg) +=+ !<([~ id=id-hook:h =nest:c =config:h] arg) ;< our=@p bind:m get-our:s ;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%config id nest config]) diff --git a/desk/ted/hook/del.hoon b/desk/ted/hook/del.hoon index 04d5cbafad..eb99044450 100644 --- a/desk/ted/hook/del.hoon +++ b/desk/ted/hook/del.hoon @@ -5,7 +5,7 @@ |= arg=vase =/ m (strand ,vase) ^- form:m -=+ !<([~ =id:h] arg) +=+ !<([~ id=id-hook:h] arg) ;< our=@p bind:m get-our:s ;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%del id]) diff --git a/desk/ted/hook/edit.hoon b/desk/ted/hook/edit.hoon index dd3b7e6eb2..896df18b32 100644 --- a/desk/ted/hook/edit.hoon +++ b/desk/ted/hook/edit.hoon @@ -5,7 +5,7 @@ |= arg=vase =/ m (strand ,vase) ^- form:m -=+ !<([~ =id:h name=(unit @t) src=(unit @t) meta=(unit data:meta)] arg) +=+ !<([~ id=id-hook:h name=(unit @t) src=(unit @t) meta=(unit data:meta)] arg) ;< our=@p bind:m get-our:s ;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%edit id name src meta]) diff --git a/desk/ted/hook/order.hoon b/desk/ted/hook/order.hoon index 7f44547200..5e0f5b07bf 100644 --- a/desk/ted/hook/order.hoon +++ b/desk/ted/hook/order.hoon @@ -5,7 +5,7 @@ |= arg=vase =/ m (strand ,vase) ^- form:m -=+ !<([~ =nest:c seq=(list id:h)] arg) +=+ !<([~ =nest:c seq=(list id-hook:h)] arg) ;< our=@p bind:m get-our:s ;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage hook-action-0+!>(`action:h`[%order nest seq]) diff --git a/desk/ted/hook/run.hoon b/desk/ted/hook/run.hoon index 1efcc6e1d5..6bf09a7877 100644 --- a/desk/ted/hook/run.hoon +++ b/desk/ted/hook/run.hoon @@ -12,7 +12,7 @@ ?. ?=(%& -.compiled) %- (slog 'compilation error:' p.compiled) (pure:m !>(~)) -;< ctx=context:h bind:m (get-context context-option) +;< ctx=bowl:h bind:m (get-bowl context-option) =/ gate [p.p.compiled .*(q:subject:utils q.p.compiled)] =+ !<(=outcome:h (slam gate !>([event ctx]))) ?: ?=(%.y -.outcome) @@ -26,11 +26,11 @@ == +$ context-option $% [%origin =origin:h state=(unit vase) config=(unit config:h)] - [%context =context:h] + [%context =bowl:h] == ++ get-context |= =context-option - =/ m (strand ,context:h) + =/ m (strand ,bowl:h) ^- form:m ?: ?=(%context -.context-option) (pure:m context.context-option) =/ [=origin:h state=(unit vase) config=(unit config:h)] +.context-option diff --git a/desk/ted/hook/schedule.hoon b/desk/ted/hook/schedule.hoon index 863f28758e..e9e910b97e 100644 --- a/desk/ted/hook/schedule.hoon +++ b/desk/ted/hook/schedule.hoon @@ -6,7 +6,7 @@ =/ m (strand ,vase) ^- form:m |^ -=+ !<([~ =id:h =origin:h =action] arg) +=+ !<([~ id=id-hook:h =origin:h =action] arg) ;< our=@p bind:m get-our:s ;< ~ bind:m (watch:s /responses [our %channels-server] /v0/hooks) =/ =cage @@ -24,9 +24,7 @@ %- (slog (crip "stopped scheduled hook {} running on {}") ~) (pure:m !>(~)) ;< now=time bind:m get-time:s -=/ fires-at - ?@ schedule.response (add now schedule.response) - next.schedule.response +=/ fires-at next.schedule.response %- (slog (crip "starting hook {}, scheduled to run on {} at {}") ~) (pure:m !>(~)) +$ action diff --git a/packages/app/hooks/useChatSettingsNavigation.ts b/packages/app/hooks/useChatSettingsNavigation.ts index 585e808e46..a70cef08a8 100644 --- a/packages/app/hooks/useChatSettingsNavigation.ts +++ b/packages/app/hooks/useChatSettingsNavigation.ts @@ -71,16 +71,21 @@ export const useChatSettingsNavigation = () => { [navigation] ); - const navigateOnLeave = useCallback( - () => { - navigation.navigate('ChatList'); + const onPressChannelTemplate = useCallback( + (channelId: string) => { + navigation.navigate('ChannelTemplate', { channelId }); }, [navigation] ); + const navigateOnLeave = useCallback(() => { + navigation.navigate('ChatList'); + }, [navigation]); + return { onPressChannelMembers, onPressChannelMeta, + onPressChannelTemplate, onPressGroupMeta, onPressGroupMembers, onPressManageChannels, diff --git a/packages/app/navigation/types.ts b/packages/app/navigation/types.ts index f71d7c706a..3b21d50cc7 100644 --- a/packages/app/navigation/types.ts +++ b/packages/app/navigation/types.ts @@ -67,6 +67,9 @@ export type RootStackParamList = { ChannelMeta: { channelId: string; }; + ChannelTemplate: { + channelId: string; + }; }; export type RootStackNavigationProp = NavigationProp; @@ -92,6 +95,7 @@ export type DesktopChannelStackParamList = Pick< | 'EditProfile' | 'ChannelMembers' | 'ChannelMeta' + | 'ChannelTemplate' > & { ChannelRoot: RootStackParamList['Channel'] }; export type GroupSettingsStackParamList = { diff --git a/packages/shared/src/api/channelsApi.ts b/packages/shared/src/api/channelsApi.ts index 20d95fb8b0..cbdfeb01b2 100644 --- a/packages/shared/src/api/channelsApi.ts +++ b/packages/shared/src/api/channelsApi.ts @@ -12,7 +12,7 @@ import { isGroupChannelId, } from './apiUtils'; import { toPostData, toPostReplyData, toReactionsData } from './postsApi'; -import { scry, subscribe, trackedPoke } from './urbit'; +import { scry, subscribe, subscribeOnce, trackedPoke } from './urbit'; const logger = createDevLogger('channelsSub', false); @@ -417,3 +417,13 @@ export const joinChannel = async (channelId: string, groupId: string) => { } ); }; + +export async function getChannelHooksPreview(channelId: string) { + return subscribeOnce( + { + app: 'channels', + path: `/v1/hooks/preview/${channelId}`, + }, + 10_000 + ); +} diff --git a/packages/shared/src/api/urbit.ts b/packages/shared/src/api/urbit.ts index d210eed10a..2a92009eb7 100644 --- a/packages/shared/src/api/urbit.ts +++ b/packages/shared/src/api/urbit.ts @@ -231,7 +231,8 @@ export async function subscribe( export async function subscribeOnce( endpoint: UrbitEndpoint, - timeout?: number + timeout?: number, + ship?: string ) { if (!config.client) { throw new Error('Client not initialized'); @@ -241,7 +242,12 @@ export async function subscribeOnce( } logger.log('subscribing once to', printEndpoint(endpoint)); try { - return config.client.subscribeOnce(endpoint.app, endpoint.path, timeout); + return config.client.subscribeOnce( + endpoint.app, + endpoint.path, + ship, + timeout + ); } catch (err) { if (err !== 'timeout' && err !== 'quit') { logger.trackError(`bad subscribeOnce ${printEndpoint(endpoint)}`, { @@ -258,7 +264,12 @@ export async function subscribeOnce( } await reauth(); - return config.client.subscribeOnce(endpoint.app, endpoint.path, timeout); + return config.client.subscribeOnce( + endpoint.app, + endpoint.path, + ship, + timeout + ); } } diff --git a/packages/shared/src/http-api/Urbit.ts b/packages/shared/src/http-api/Urbit.ts index 8c2b192352..ac65944d43 100644 --- a/packages/shared/src/http-api/Urbit.ts +++ b/packages/shared/src/http-api/Urbit.ts @@ -545,7 +545,12 @@ export class Urbit { * * @returns The first fact on the subcription */ - async subscribeOnce(app: string, path: string, timeout?: number) { + async subscribeOnce( + app: string, + path: string, + ship?: string, + timeout?: number + ) { return new Promise((resolve, reject) => { let done = false; const quit = () => { @@ -562,6 +567,7 @@ export class Urbit { const request = { app, path, + ship, resubOnQuit: false, event, err: reject, @@ -634,9 +640,9 @@ export class Urbit { err: () => {}, event: () => {}, quit: () => {}, - ship: desig(this.nodeId ?? ''), resubOnQuit: true, ...params, + ship: desig(params.ship ?? this.nodeId ?? ''), }; if (this.lastEventId === 0) { diff --git a/packages/shared/src/http-api/types.ts b/packages/shared/src/http-api/types.ts index d79337d83d..b2eb11f99e 100644 --- a/packages/shared/src/http-api/types.ts +++ b/packages/shared/src/http-api/types.ts @@ -174,6 +174,7 @@ export interface SubscriptionRequestInterface extends SubscriptionInterface { * Whether to resubscribe this exact subscription on quit */ resubOnQuit?: boolean; + ship?: string; } export interface headers { diff --git a/packages/shared/src/store/index.ts b/packages/shared/src/store/index.ts index a8e69d9439..fde487955f 100644 --- a/packages/shared/src/store/index.ts +++ b/packages/shared/src/store/index.ts @@ -3,6 +3,7 @@ export * from './reactQuery'; export * from './sync'; export * from './useChannelPosts'; export * from './useChannelSearch'; +export * from './useChannelHooksPreview'; export * from './useCreateChannel'; export * from './postActions'; export * from './channelActions'; diff --git a/packages/shared/src/store/useChannelHooksPreview.ts b/packages/shared/src/store/useChannelHooksPreview.ts new file mode 100644 index 0000000000..9f4f60cf72 --- /dev/null +++ b/packages/shared/src/store/useChannelHooksPreview.ts @@ -0,0 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getChannelHooksPreview } from '../api'; +import * as ub from '../urbit'; + +export function useChannelHooksPreview(channelId: string) { + console.log('useChannelHooksPreview', channelId); + return useQuery({ + queryKey: ['channelHooksPreview', channelId], + queryFn: () => getChannelHooksPreview(channelId), + enabled: ub.idIsNest(channelId), + }); +} diff --git a/packages/shared/src/urbit/channel.ts b/packages/shared/src/urbit/channel.ts index a8a5060b6d..7195e226fe 100644 --- a/packages/shared/src/urbit/channel.ts +++ b/packages/shared/src/urbit/channel.ts @@ -4,6 +4,7 @@ import _ from 'lodash'; import BTree from 'sorted-btree'; import { Inline } from './content'; +import { GroupMeta } from './groups'; import { Flag } from './hark'; export interface CacheId { @@ -725,3 +726,5 @@ export type ChannelHead = { }; export type ChannelHeadsResponse = ChannelHead[]; + +export type ChannelHooksPreview = { name: string; meta: GroupMeta }[]; diff --git a/packages/ui/src/components/ChannelFromTemplateView.tsx b/packages/ui/src/components/ChannelFromTemplateView.tsx new file mode 100644 index 0000000000..4193c3abb3 --- /dev/null +++ b/packages/ui/src/components/ChannelFromTemplateView.tsx @@ -0,0 +1,14 @@ +import { View } from 'tamagui'; + +import { ScreenHeader } from './ScreenHeader'; + +export function ChannelFromTemplateView() { + return ( + + + + ); +} diff --git a/packages/ui/src/components/ChatOptionsSheet.tsx b/packages/ui/src/components/ChatOptionsSheet.tsx index 4d017b52b5..c8b358016d 100644 --- a/packages/ui/src/components/ChatOptionsSheet.tsx +++ b/packages/ui/src/components/ChatOptionsSheet.tsx @@ -458,6 +458,8 @@ export function GroupOptions({ ); } +type ChannelPanes = 'initial' | 'notifications'; + export function ChannelOptionsSheetLoader({ channelId, open, @@ -467,7 +469,7 @@ export function ChannelOptionsSheetLoader({ open: boolean; onOpenChange: (open: boolean) => void; }) { - const [pane, setPane] = useState<'initial' | 'notifications'>('initial'); + const [pane, setPane] = useState('initial'); const channelQuery = store.useChannel({ id: channelId, }); @@ -501,8 +503,8 @@ export function ChannelOptions({ onOpenChange, }: { channel: db.Channel; - pane: 'initial' | 'notifications'; - setPane: (pane: 'initial' | 'notifications') => void; + pane: ChannelPanes; + setPane: (pane: ChannelPanes) => void; onOpenChange: (open: boolean) => void; }) { const { data: group } = store.useGroup({ @@ -515,7 +517,9 @@ export function ChannelOptions({ onPressChannelMeta, onPressManageChannels, onPressInvite, + onPressChannelTemplate, } = useChatOptions() ?? {}; + const { data: hooksPreview } = store.useChannelHooksPreview(channel.id); const currentUserIsHost = useMemo( () => group?.currentUserIsHost ?? false, @@ -746,6 +750,23 @@ export function ChannelOptions({ } as ActionGroup, ] : []), + ...(hooksPreview + ? [ + { + accent: 'neutral', + actions: [ + { + title: 'Use channel as template', + description: 'Create a new channel based on this one', + endIcon: 'Copy', + action: () => { + onPressChannelTemplate(channel.id); + }, + }, + ], + } as ActionGroup, + ] + : []), ...(!currentUserIsHost ? [ { diff --git a/packages/ui/src/contexts/chatOptions.tsx b/packages/ui/src/contexts/chatOptions.tsx index 8384bfe87c..c8a034c889 100644 --- a/packages/ui/src/contexts/chatOptions.tsx +++ b/packages/ui/src/contexts/chatOptions.tsx @@ -23,6 +23,7 @@ export type ChatOptionsContextValue = { onPressRoles: (groupId: string) => void; onPressChannelMembers: (channelId: string) => void; onPressChannelMeta: (channelId: string) => void; + onPressChannelTemplate: (channelId: string) => void; onTogglePinned: () => void; onPressLeave: () => Promise; onSelectSort?: (sortBy: 'recency' | 'arranged') => void; @@ -50,6 +51,7 @@ type ChatOptionsProviderProps = { onPressGroupPrivacy: (groupId: string) => void; onPressChannelMembers: (channelId: string) => void; onPressChannelMeta: (channelId: string) => void; + onPressChannelTemplate: (channelId: string) => void; onPressRoles: (groupId: string) => void; onSelectSort?: (sortBy: 'recency' | 'arranged') => void; onLeaveGroup?: () => void; @@ -66,6 +68,7 @@ export const ChatOptionsProvider = ({ onPressGroupPrivacy, onPressChannelMembers, onPressChannelMeta, + onPressChannelTemplate, onPressRoles, onLeaveGroup: navigateOnLeave, }: ChatOptionsProviderProps) => { @@ -129,6 +132,7 @@ export const ChatOptionsProvider = ({ onTogglePinned, onPressChannelMembers, onPressChannelMeta, + onPressChannelTemplate, onSelectSort, open, }), @@ -137,6 +141,7 @@ export const ChatOptionsProvider = ({ groupChannels, onPressChannelMembers, onPressChannelMeta, + onPressChannelTemplate, onPressGroupMembers, onPressGroupMeta, onPressGroupPrivacy, From 4d90ef9f23bbd1540a2f3d7cb6f28f4bb60c78a6 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Fri, 13 Dec 2024 11:31:52 -0600 Subject: [PATCH 136/149] channels: add flow for copying as template --- desk/app/channels-server.hoon | 20 ++- desk/app/channels.hoon | 16 +- desk/lib/channel-json.hoon | 6 - desk/mar/hook/setup-template-args.hoon | 25 +++ desk/mar/hook/setup-template.hoon | 13 ++ desk/mar/hook/template.hoon | 13 ++ desk/mar/nest.hoon | 15 ++ desk/ted/channel/create-from-template.hoon | 33 ---- desk/ted/channel/setup-from-template.hoon | 21 +++ desk/ted/hook/schedule.hoon | 4 +- .../channels/ChannelTemplateScreen.tsx | 38 +++++ packages/app/navigation/RootStack.tsx | 2 + packages/shared/src/api/channelsApi.ts | 27 ++- packages/shared/src/http-api/Urbit.ts | 4 +- packages/shared/src/store/channelActions.ts | 1 + .../src/store/useChannelHooksPreview.ts | 1 + packages/shared/src/store/useCreateChannel.ts | 28 ++-- .../components/ChannelFromTemplateView.tsx | 156 +++++++++++++++++- .../ui/src/components/ChatOptionsSheet.tsx | 2 + packages/ui/src/components/GroupSelector.tsx | 116 +++++++++++++ .../ui/src/components/GroupSelectorSheet.tsx | 118 +------------ packages/ui/src/index.tsx | 1 + 22 files changed, 470 insertions(+), 190 deletions(-) create mode 100644 desk/mar/hook/setup-template-args.hoon create mode 100644 desk/mar/hook/setup-template.hoon create mode 100644 desk/mar/hook/template.hoon create mode 100644 desk/mar/nest.hoon delete mode 100644 desk/ted/channel/create-from-template.hoon create mode 100644 desk/ted/channel/setup-from-template.hoon create mode 100644 packages/app/features/channels/ChannelTemplateScreen.tsx create mode 100644 packages/ui/src/components/GroupSelector.tsx diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index d3f01fd2d2..fcc675f79b 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -347,6 +347,7 @@ %hook-setup-template ?> =(src our):bowl =+ !<([=nest:c =template:h] vase) + ~& "setting up template for {}" (setup-hook-template nest template) :: %hook-action-0 @@ -428,17 +429,20 @@ [%v0 %hooks ~] cor :: [%v0 %hooks %full ~] - (give %fact ~ hook-full+!>(hooks)) + =. cor (give %fact ~ hook-full+!>(hooks)) + (give %kick ~ ~) :: [%v0 %hooks %preview =kind:c name=@ ~] =/ cp=channel-preview:h (get-channel-hooks-preview kind.pole our.bowl name.pole) - (give %fact ~ hook-channel-preview+!>(cp)) + =. cor (give %fact ~ hook-channel-preview+!>(cp)) + (give %kick ~ ~) :: [%v0 %hooks %template =kind:c name=@ ~] =/ =template:h (get-hook-template kind.pole our.bowl name.pole) - (give %fact ~ hook-template+!>(template)) + =. cor (give %fact ~ hook-template+!>(template)) + (give %kick ~ ~) :: [=kind:c name=@ %create ~] ?> =(our src):bowl @@ -1326,9 +1330,15 @@ %+ turn ~(tap by hooks.template) |= [=id-hook:h =hook:h] + =/ result=(each vase tang) + (compile:utils src.hook) + =/ compiled + ?: ?=(%| -.result) + ((slog 'compilation result:' p.result) ~) + `p.result ?~ old-config=(~(get by config.hook) from.template) - [id-hook hook(config ~)] - [id-hook hook(config (my [nest u.old-config] ~))] + [id-hook hook(config ~, compiled compiled)] + [id-hook hook(config (my [nest u.old-config] ~), compiled compiled)] =/ crons crons.template |- ?~ crons cor diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index f5af3006f1..4d204b7c2f 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -136,6 +136,9 @@ =? old ?=(%6 -.old) (state-6-to-7 old) ?> ?=(%7 -.old) =. state old + =/ =wire /v1/hooks/preview/chat/~simtyc-tirter-nocsyx-lassul/test + =/ =dock [~simtyc-tirter-nocsyx-lassul %channels-server] + =. cor (emit %pass wire %agent dock %leave ~) inflate-io :: +$ versioned-state @@ -551,15 +554,6 @@ [~ %| *] ~& [dap.bowl %overwriting-pending-import] cor(pimp `|+egg-any) == - :: - %channel-create-from-template - =+ !<(args=[nest:c flag:g (unit @t)] vase) - %- emit - :* %pass /create-template - %arvo %k %fard - q.byk.bowl %channel-create-from-template %noun - !>(`args) - == == ++ toggle-post |= toggle=post-toggle:c @@ -717,7 +711,9 @@ [%v1 %hooks %preview =kind:c host=@ name=@ ~] ?+ -.sign !! %kick cor - %fact (give %fact ~[pole] cage.sign) + %fact + =. cor (give %fact ~[pole] cage.sign) + (emit %pass pole %agent [host.pole %channels] %leave ~) :: %watch-ack ?~ p.sign cor diff --git a/desk/lib/channel-json.hoon b/desk/lib/channel-json.hoon index c3b1a07eaf..579836550c 100644 --- a/desk/lib/channel-json.hoon +++ b/desk/lib/channel-json.hoon @@ -861,11 +861,5 @@ :~ hide/(se %ud) show/(se %ud) == - ++ template-create - %- ot - :~ nest/nest - flag/flag - name/(mu so) - == -- -- diff --git a/desk/mar/hook/setup-template-args.hoon b/desk/mar/hook/setup-template-args.hoon new file mode 100644 index 0000000000..a6ac12ff3d --- /dev/null +++ b/desk/mar/hook/setup-template-args.hoon @@ -0,0 +1,25 @@ +/- c=channels +/+ cj=channel-json +|_ [example=nest:c target=nest:c] +++ grad %noun +++ grow + |% + ++ noun [example target] + ++ json + =, enjs:format + %- pairs + :~ example+(nest:enjs:cj example) + target+(nest:enjs:cj target) + == + -- +++ grab + |% + ++ noun [nest:c nest:c] + ++ json + =, dejs:format + %- ot + :~ example/nest:dejs:cj + target/nest:dejs:cj + == + -- +-- diff --git a/desk/mar/hook/setup-template.hoon b/desk/mar/hook/setup-template.hoon new file mode 100644 index 0000000000..49adc4309f --- /dev/null +++ b/desk/mar/hook/setup-template.hoon @@ -0,0 +1,13 @@ +/- h=hooks, c=channels +/+ hj=hooks-json +|_ [=nest:c =template:h] +++ grad %noun +++ grow + |% + ++ noun [nest template] + -- +++ grab + |% + ++ noun [=nest:c =template:h] + -- +-- diff --git a/desk/mar/hook/template.hoon b/desk/mar/hook/template.hoon new file mode 100644 index 0000000000..bcc75c2f42 --- /dev/null +++ b/desk/mar/hook/template.hoon @@ -0,0 +1,13 @@ +/- h=hooks, c=channels +/+ hj=hooks-json +|_ =template:h +++ grad %noun +++ grow + |% + ++ noun template + -- +++ grab + |% + ++ noun template:h + -- +-- diff --git a/desk/mar/nest.hoon b/desk/mar/nest.hoon new file mode 100644 index 0000000000..ccd22ca7cc --- /dev/null +++ b/desk/mar/nest.hoon @@ -0,0 +1,15 @@ +/- c=channels +/+ j=channel-json +|_ =nest:c +++ grad %noun +++ grow + |% + ++ noun nest + ++ json (nest:enjs:j nest) + -- +++ grab + |% + ++ noun nest:c + ++ json nest:dejs:j + -- +-- diff --git a/desk/ted/channel/create-from-template.hoon b/desk/ted/channel/create-from-template.hoon deleted file mode 100644 index d9e18abd18..0000000000 --- a/desk/ted/channel/create-from-template.hoon +++ /dev/null @@ -1,33 +0,0 @@ -/- spider, h=hooks, c=channels, g=groups -/+ s=strandio -=, strand=strand:spider -^- thread:spider -|= arg=vase -=/ m (strand ,vase) -^- form:m -=+ !<([~ =nest:c =flag:g name=(unit @t)] arg) -;< our=@p bind:m get-our:s -;< eny=@ bind:m get-entropy:s -=/ id (end 3^3 eny) -=/ nest-name=term (cat 3 'channel-' id) -=/ new-nest=nest:c [kind.nest our nest-name] -=/ =create-channel:c - :* kind.nest - nest-name - flag - (fall name '') - '' - ~ - ~ - == -=/ create-cage=cage - channel-action+!>(`a-channels:c`[%create create-channel]) -;< ~ bind:m (poke-our:s %channels create-cage) -;< ~ bind:m - (watch:s /template [ship.nest %channels-server] /v0/hooks/template/[kind.nest]/[name.nest]) -;< =^cage bind:m (take-fact:s /template) -?> ?=(%hook-template p.cage) -=+ !<(=template:h q.cage) -=/ =cage hook-setup-template+!>([new-nest template]) -;< ~ bind:m (poke-our:s %channels-server cage) -(pure:m !>(~)) diff --git a/desk/ted/channel/setup-from-template.hoon b/desk/ted/channel/setup-from-template.hoon new file mode 100644 index 0000000000..11f8fbd514 --- /dev/null +++ b/desk/ted/channel/setup-from-template.hoon @@ -0,0 +1,21 @@ +/- spider, h=hooks, c=channels, g=groups +/+ s=strandio +=, strand=strand:spider +^- thread:spider +|= arg=vase +=/ m (strand ,vase) +^- form:m +=/ [example=nest:c target=nest:c] + (need !<((unit [nest:c nest:c]) arg)) +~& [example target] +;< ~ bind:m + (watch:s /template [ship.example %channels-server] /v0/hooks/template/[kind.example]/[name.example]) +~& "getting template" +;< =cage bind:m (take-fact:s /template) +?> ?=(%hook-template p.cage) +~& "received template" +=+ !<(=template:h q.cage) +=/ =^cage hook-setup-template+!>([target template]) +~& "setting up template" +;< ~ bind:m (poke-our:s %channels-server cage) +(pure:m !>(`json`s+'success')) diff --git a/desk/ted/hook/schedule.hoon b/desk/ted/hook/schedule.hoon index e9e910b97e..246cdc710a 100644 --- a/desk/ted/hook/schedule.hoon +++ b/desk/ted/hook/schedule.hoon @@ -24,7 +24,9 @@ %- (slog (crip "stopped scheduled hook {} running on {}") ~) (pure:m !>(~)) ;< now=time bind:m get-time:s -=/ fires-at next.schedule.response +=/ fires-at + ?^ next.schedule.response + (add now schedule.response) %- (slog (crip "starting hook {}, scheduled to run on {} at {}") ~) (pure:m !>(~)) +$ action diff --git a/packages/app/features/channels/ChannelTemplateScreen.tsx b/packages/app/features/channels/ChannelTemplateScreen.tsx new file mode 100644 index 0000000000..50d9bf7e1c --- /dev/null +++ b/packages/app/features/channels/ChannelTemplateScreen.tsx @@ -0,0 +1,38 @@ +import { NativeStackScreenProps } from '@react-navigation/native-stack'; +import * as db from '@tloncorp/shared/db'; +import * as store from '@tloncorp/shared/store'; +import { ChannelFromTemplateView, GroupsProvider } from '@tloncorp/ui'; +import { useCallback } from 'react'; + +import { RootStackParamList } from '../../navigation/types'; + +type Props = NativeStackScreenProps; + +export function ChannelTemplateScreen(props: Props) { + const { channelId } = props.route.params; + const channelQuery = store.useChannel({ id: channelId }); + const { data: groups } = store.useGroups({}); + + const handleGoToChannel = useCallback( + (channel: db.Channel) => { + props.navigation.reset({ + index: 1, + routes: [ + { name: 'ChatList' }, + { name: 'Channel', params: { channelId: channel.id } }, + ], + }); + }, + [props.navigation] + ); + + return ( + + + + ); +} diff --git a/packages/app/navigation/RootStack.tsx b/packages/app/navigation/RootStack.tsx index bcda33b2b7..33684ea323 100644 --- a/packages/app/navigation/RootStack.tsx +++ b/packages/app/navigation/RootStack.tsx @@ -5,6 +5,7 @@ import { Platform, StatusBar } from 'react-native'; import { ChannelMembersScreen } from '../features/channels/ChannelMembersScreen'; import { ChannelMetaScreen } from '../features/channels/ChannelMetaScreen'; +import { ChannelTemplateScreen } from '../features/channels/ChannelTemplateScreen'; import { AddContactsScreen } from '../features/contacts/AddContactsScreen'; import { AppInfoScreen } from '../features/settings/AppInfoScreen'; import { BlockedUsersScreen } from '../features/settings/BlockedUsersScreen'; @@ -120,6 +121,7 @@ export function RootStack() { + ); } diff --git a/packages/shared/src/api/channelsApi.ts b/packages/shared/src/api/channelsApi.ts index cbdfeb01b2..bb973fa4c1 100644 --- a/packages/shared/src/api/channelsApi.ts +++ b/packages/shared/src/api/channelsApi.ts @@ -12,7 +12,14 @@ import { isGroupChannelId, } from './apiUtils'; import { toPostData, toPostReplyData, toReactionsData } from './postsApi'; -import { scry, subscribe, subscribeOnce, trackedPoke } from './urbit'; +import { + client, + getCurrentUserId, + scry, + subscribe, + subscribeOnce, + trackedPoke, +} from './urbit'; const logger = createDevLogger('channelsSub', false); @@ -94,12 +101,28 @@ export const createChannel = async (channelPayload: ub.Create) => { return ( 'create' in event.response && event.nest === - `${channelPayload.kind}/${channelPayload.group}/${channelPayload.name}` + `${channelPayload.kind}/${getCurrentUserId()}/${channelPayload.name}` ); } ); }; +export const setupChannelFromTemplate = async ( + exampleChannelId: string, + targetChannelId: string +) => { + return client.thread({ + desk: 'groups', + inputMark: 'hook-setup-template-args', + outputMark: 'json', + threadName: 'channel-setup-from-template', + body: { + example: exampleChannelId, + target: targetChannelId, + }, + }); +}; + export const subscribeToChannelsUpdates = async ( eventHandler: (update: ChannelsUpdate) => void ) => { diff --git a/packages/shared/src/http-api/Urbit.ts b/packages/shared/src/http-api/Urbit.ts index ac65944d43..bac83e54ac 100644 --- a/packages/shared/src/http-api/Urbit.ts +++ b/packages/shared/src/http-api/Urbit.ts @@ -775,7 +775,7 @@ export class Urbit { throw new Error('Must supply desk to run thread from'); } const res = await this.fetchFn( - `${this.url}/spider/${desk}/${inputMark}/${threadName}/${outputMark}.json`, + `${this.url}/spider/${desk}/${inputMark}/${threadName}/${outputMark}`, { ...this.fetchOptions, method: 'POST', @@ -783,6 +783,8 @@ export class Urbit { } ); + console.log('thread returned', res.status, await res.clone().text()); + return res.json(); } diff --git a/packages/shared/src/store/channelActions.ts b/packages/shared/src/store/channelActions.ts index 58d2cfcd08..8ce7752b40 100644 --- a/packages/shared/src/store/channelActions.ts +++ b/packages/shared/src/store/channelActions.ts @@ -65,6 +65,7 @@ export async function createChannel({ readers: [], writers: [], }); + return newChannel; } catch (e) { console.error('Failed to create channel', e); // rollback optimistic update diff --git a/packages/shared/src/store/useChannelHooksPreview.ts b/packages/shared/src/store/useChannelHooksPreview.ts index 9f4f60cf72..714576a3c0 100644 --- a/packages/shared/src/store/useChannelHooksPreview.ts +++ b/packages/shared/src/store/useChannelHooksPreview.ts @@ -9,5 +9,6 @@ export function useChannelHooksPreview(channelId: string) { queryKey: ['channelHooksPreview', channelId], queryFn: () => getChannelHooksPreview(channelId), enabled: ub.idIsNest(channelId), + staleTime: 30_000, }); } diff --git a/packages/shared/src/store/useCreateChannel.ts b/packages/shared/src/store/useCreateChannel.ts index 19517dbeaf..ce826d7032 100644 --- a/packages/shared/src/store/useCreateChannel.ts +++ b/packages/shared/src/store/useCreateChannel.ts @@ -36,6 +36,10 @@ export function useCreateChannel({ channelType: Omit; contentConfiguration?: ChannelContentConfiguration; }) => { + if (!group) { + return; + } + const { name, id } = assembleNewChannelIdAndName({ title, channelType, @@ -43,19 +47,17 @@ export function useCreateChannel({ currentUserId, }); - if (group) { - await createChannel({ - groupId: group.id, - name, - channelId: id, - title, - description, - channelType, - contentConfiguration: - contentConfiguration ?? - channelContentConfigurationForChannelType(channelType), - }); - } + return createChannel({ + groupId: group.id, + name, + channelId: id, + title, + description, + channelType, + contentConfiguration: + contentConfiguration ?? + channelContentConfigurationForChannelType(channelType), + }); }, [group, currentUserId, existingChannels] ); diff --git a/packages/ui/src/components/ChannelFromTemplateView.tsx b/packages/ui/src/components/ChannelFromTemplateView.tsx index 4193c3abb3..3e89abcf61 100644 --- a/packages/ui/src/components/ChannelFromTemplateView.tsx +++ b/packages/ui/src/components/ChannelFromTemplateView.tsx @@ -1,14 +1,162 @@ -import { View } from 'tamagui'; +import { useChannelHooksPreview, useCreateChannel } from '@tloncorp/shared'; +import * as api from '@tloncorp/shared/api'; +import * as db from '@tloncorp/shared/db'; +import { useCallback, useMemo, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { SizableText, View, XStack, YStack } from 'tamagui'; +import { useCurrentUserId, useGroups } from '../contexts'; +import { useAlphabeticallySegmentedGroups } from '../hooks/groupsSorters'; +import useIsWindowNarrow from '../hooks/useIsWindowNarrow'; +import { Button } from './Button'; +import * as Form from './Form'; +import { GroupSelector } from './GroupSelector'; +import { ListItem } from './ListItem'; import { ScreenHeader } from './ScreenHeader'; -export function ChannelFromTemplateView() { +export function ChannelFromTemplateView({ + channel, + goBack, + navigateToChannel, +}: { + channel?: db.Channel; + goBack: () => void; + navigateToChannel: (channel: db.Channel) => void; +}) { + const { bottom } = useSafeAreaInsets(); + const isWindowNarrow = useIsWindowNarrow(); + const [selectedGroup, setSelectedGroup] = useState(null); + const { data: hookPreviews } = useChannelHooksPreview(channel?.id ?? ''); + const currentUserId = useCurrentUserId(); + const allGroups = useGroups(); + const groups = useMemo(() => { + return allGroups?.filter((g) => g.title && g.currentUserIsHost) ?? []; + }, [allGroups]); + const alphaSegmentedGroups = useAlphabeticallySegmentedGroups({ + groups, + enabled: true, + }); + const { + control, + formState: { isValid, errors }, + handleSubmit, + } = useForm<{ + title: string; + }>({ + defaultValues: { + title: '', + }, + }); + + console.log(errors); + + const createChannel = useCreateChannel({ + group: selectedGroup, + currentUserId, + }); + + const onConfirm = useCallback( + async (data: { title: string }) => { + console.log('onConfirm', channel, selectedGroup); + if (!channel || !selectedGroup) { + return; + } + + console.log('creating channel', data); + // create channel + const newChannel = await createChannel({ + title: data.title, + channelType: channel.type, + }); + console.log('channel created', newChannel); + if (newChannel) { + // send hook template setup + console.log('sending template'); + await api.setupChannelFromTemplate(channel.id, newChannel.id); + console.log('template setup'); + // navigate to channel + navigateToChannel(newChannel); + } + }, + [navigateToChannel, channel, selectedGroup] + ); + return ( - + + + + + Modifications running in this channel: + + + {(hookPreviews || []).map(({ name, meta }, index) => ( + + {meta.image ? ( + + ) : ( + + )} + + {meta.title || name} + {meta.description && ( + {meta.description} + )} + + + ))} + + + + + Choose where to create this channel + + + + + + ); } diff --git a/packages/ui/src/components/ChatOptionsSheet.tsx b/packages/ui/src/components/ChatOptionsSheet.tsx index c8b358016d..d870cc40a0 100644 --- a/packages/ui/src/components/ChatOptionsSheet.tsx +++ b/packages/ui/src/components/ChatOptionsSheet.tsx @@ -760,6 +760,7 @@ export function ChannelOptions({ description: 'Create a new channel based on this one', endIcon: 'Copy', action: () => { + onOpenChange(false); onPressChannelTemplate(channel.id); }, }, @@ -832,6 +833,7 @@ export function ChannelOptions({ currentUserIsAdmin, group, currentUserIsHost, + hooksPreview, setPane, handleMarkRead, onOpenChange, diff --git a/packages/ui/src/components/GroupSelector.tsx b/packages/ui/src/components/GroupSelector.tsx new file mode 100644 index 0000000000..7f268a01dc --- /dev/null +++ b/packages/ui/src/components/GroupSelector.tsx @@ -0,0 +1,116 @@ +import * as db from '@tloncorp/shared/db'; +import React, { useCallback, useRef } from 'react'; +import { + NativeScrollEvent, + NativeSyntheticEvent, + SectionListRenderItemInfo, +} from 'react-native'; +import { Stack, View } from 'tamagui'; + +import { AlphaSegmentedGroups } from '../hooks/groupsSorters'; +import { Icon } from './Icon'; +import { GroupListItem, ListItem } from './ListItem'; +import { BlockSectionList } from './SectionList'; + +interface Props { + onSelect?: (group: db.Group) => void; + selected?: string[]; + onScrollChange?: (scrolling: boolean) => void; + alphaSegmentedGroups: AlphaSegmentedGroups; +} + +export function GroupSelector(props: Props) { + const scrollPosition = useRef(0); + const handleScroll = useCallback( + (event: NativeSyntheticEvent) => { + scrollPosition.current = event.nativeEvent.contentOffset.y; + }, + [] + ); + const onTouchStart = useCallback(() => { + if (scrollPosition.current > 0) { + props.onScrollChange?.(true); + } + }, [props]); + + const onTouchEnd = useCallback(() => props.onScrollChange?.(false), [props]); + + const handleSelect = useCallback( + (group: db.Group) => { + props.onSelect?.(group); + }, + [props] + ); + + const renderItem = useCallback( + ({ item }: SectionListRenderItemInfo) => { + const isSelected = !!(props.selected ?? [])?.includes(item.id); + return ( + + ); + }, + [handleSelect, props] + ); + + return ( + + ); +} + +function SelectableGroupItemComponent(props: { + group: db.Group; + selected: boolean; + selectable: boolean; + onPress?: (group: db.Group) => void; +}) { + const handlePress = useCallback(() => { + if (props.onPress) { + props.onPress(props.group); + } + }, [props]); + + return ( + + + {props.selected ? ( + + ) : ( + + )} + + + ) : null + } + /> + ); +} +const SelectableGroupItem = React.memo(SelectableGroupItemComponent); diff --git a/packages/ui/src/components/GroupSelectorSheet.tsx b/packages/ui/src/components/GroupSelectorSheet.tsx index 4ba117d2a6..89551c9d09 100644 --- a/packages/ui/src/components/GroupSelectorSheet.tsx +++ b/packages/ui/src/components/GroupSelectorSheet.tsx @@ -1,124 +1,13 @@ import * as db from '@tloncorp/shared/db'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { - NativeScrollEvent, - NativeSyntheticEvent, - SectionListRenderItemInfo, -} from 'react-native'; +import React, { useEffect, useState } from 'react'; import { Modal } from 'react-native'; -import { Stack, View, YStack } from 'tamagui'; +import { YStack } from 'tamagui'; import { AlphaSegmentedGroups } from '../hooks/groupsSorters'; import { triggerHaptic } from '../utils'; -import { Icon } from './Icon'; -import { GroupListItem, ListItem } from './ListItem'; -import { BlockSectionList } from './SectionList'; +import { GroupSelector } from './GroupSelector'; import { Sheet } from './Sheet'; -interface Props { - onSelect?: (group: db.Group) => void; - multiSelect?: boolean; - selected?: string[]; - onScrollChange?: (scrolling: boolean) => void; - alphaSegmentedGroups: AlphaSegmentedGroups; -} - -export function GroupSelector(props: Props) { - const scrollPosition = useRef(0); - const handleScroll = useCallback( - (event: NativeSyntheticEvent) => { - scrollPosition.current = event.nativeEvent.contentOffset.y; - }, - [] - ); - const onTouchStart = useCallback(() => { - if (scrollPosition.current > 0) { - props.onScrollChange?.(true); - } - }, [props]); - - const onTouchEnd = useCallback(() => props.onScrollChange?.(false), [props]); - - const handleSelect = useCallback( - (group: db.Group) => { - props.onSelect?.(group); - }, - [props] - ); - - const renderItem = useCallback( - ({ item }: SectionListRenderItemInfo) => { - const isSelected = !!(props.selected ?? [])?.includes(item.id); - return ( - - ); - }, - [handleSelect, props] - ); - - return ( - - ); -} - -function SelectableGroupItemComponent(props: { - group: db.Group; - selected: boolean; - selectable: boolean; - onPress?: (group: db.Group) => void; -}) { - const handlePress = useCallback(() => { - if (props.onPress) { - props.onPress(props.group); - } - }, [props]); - - return ( - - - {props.selected ? ( - - ) : ( - - )} - - - ) : null - } - /> - ); -} -const SelectableGroupItem = React.memo(SelectableGroupItemComponent); - interface SheetProps { open: boolean; onOpenChange: (open: boolean) => void; @@ -158,7 +47,6 @@ export function GroupSelectorSheet(props: SheetProps) { {props.TopContent} Date: Fri, 13 Dec 2024 11:40:10 -0600 Subject: [PATCH 137/149] groups: ignore joins for groups that exist or are currently joining --- desk/app/groups.hoon | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index 8a5ec62412..45292ab464 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -2058,6 +2058,11 @@ ++ ga-start-join |= join-all=? ^+ ga-core + :: already in the group + ?: (~(has by groups) flag) ga-core + :: already valid join in progress + ?. |(?=(@ cam.gang) =(%error progress.u.cam.gang)) + ga-core =. cam.gang `[join-all %adding] =. cor (emit add-self:ga-pass) ga-core From 6c1ebf8c8468a305e0920f77b3bff63eb1823143 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Fri, 13 Dec 2024 17:06:58 -0600 Subject: [PATCH 138/149] hooks: removing extraneous sigpams --- desk/app/channels-server.hoon | 1 - desk/ted/channel/setup-from-template.hoon | 4 ---- 2 files changed, 5 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index fcc675f79b..0cd02b09d9 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -347,7 +347,6 @@ %hook-setup-template ?> =(src our):bowl =+ !<([=nest:c =template:h] vase) - ~& "setting up template for {}" (setup-hook-template nest template) :: %hook-action-0 diff --git a/desk/ted/channel/setup-from-template.hoon b/desk/ted/channel/setup-from-template.hoon index 11f8fbd514..8f28f60f8e 100644 --- a/desk/ted/channel/setup-from-template.hoon +++ b/desk/ted/channel/setup-from-template.hoon @@ -7,15 +7,11 @@ ^- form:m =/ [example=nest:c target=nest:c] (need !<((unit [nest:c nest:c]) arg)) -~& [example target] ;< ~ bind:m (watch:s /template [ship.example %channels-server] /v0/hooks/template/[kind.example]/[name.example]) -~& "getting template" ;< =cage bind:m (take-fact:s /template) ?> ?=(%hook-template p.cage) -~& "received template" =+ !<(=template:h q.cage) =/ =^cage hook-setup-template+!>([target template]) -~& "setting up template" ;< ~ bind:m (poke-our:s %channels-server cage) (pure:m !>(`json`s+'success')) From fb2ccfaedbfdc4bdb79c5f841661c90621fded6f Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 16 Dec 2024 09:34:05 -0600 Subject: [PATCH 139/149] groups: clearer conditional Co-authored-by: fang --- desk/app/groups.hoon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/app/groups.hoon b/desk/app/groups.hoon index 45292ab464..b7bef8e539 100644 --- a/desk/app/groups.hoon +++ b/desk/app/groups.hoon @@ -2061,7 +2061,7 @@ :: already in the group ?: (~(has by groups) flag) ga-core :: already valid join in progress - ?. |(?=(@ cam.gang) =(%error progress.u.cam.gang)) + ?: &(?=(^ cam.gang) !=(%error progress.u.cam.gang)) ga-core =. cam.gang `[join-all %adding] =. cor (emit add-self:ga-pass) From 6174a71db4c1e5104bf240c59ae9922735ce97fb Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 16 Dec 2024 10:41:02 -0600 Subject: [PATCH 140/149] channels: removing dev cleanup --- desk/app/channels.hoon | 3 --- 1 file changed, 3 deletions(-) diff --git a/desk/app/channels.hoon b/desk/app/channels.hoon index 4d204b7c2f..14afa6186f 100644 --- a/desk/app/channels.hoon +++ b/desk/app/channels.hoon @@ -136,9 +136,6 @@ =? old ?=(%6 -.old) (state-6-to-7 old) ?> ?=(%7 -.old) =. state old - =/ =wire /v1/hooks/preview/chat/~simtyc-tirter-nocsyx-lassul/test - =/ =dock [~simtyc-tirter-nocsyx-lassul %channels-server] - =. cor (emit %pass wire %agent dock %leave ~) inflate-io :: +$ versioned-state From 8697274cfbe984129029168d4a343ae4bd298623 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 16 Dec 2024 10:46:46 -0600 Subject: [PATCH 141/149] channels-server: better iteration --- desk/app/channels-server.hoon | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 0cd02b09d9..23ea35c2f7 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1291,10 +1291,10 @@ ^- template:h =/ order=(list id-hook:h) (~(got by order.hooks) nest) =/ crons=(list [id-hook:h job:h]) - %+ roll ~(tap by crons.hooks) - |= [[=id-hook:h =cron:h] cr=(list [id-hook:h job:h])] - ?~ job=(~(get by cron) nest) cr - (snoc cr [id-hook u.job]) + %+ murn ~(tap by crons.hooks) + |= [=id-hook:h =cron:h] + ?~ job=(~(get by cron) nest) ~ + `[id-hook u.job] =/ ids=(list id-hook:h) (welp order (turn crons head)) =/ hooks=(map id-hook:h hook:h) %- ~(gas by *(map id-hook:h hook:h)) From dd89dfd86b9288dd5d9155d143d36b7a2ef4bff1 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 16 Dec 2024 10:49:15 -0600 Subject: [PATCH 142/149] hooks: remove unused metadata field --- desk/app/channels-server.hoon | 1 - desk/sur/hooks.hoon | 1 - 2 files changed, 2 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index 23ea35c2f7..baa891617d 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1308,7 +1308,6 @@ :- id-hook hook(compiled ~, state !>(~), config config-map) :* nest - *data:m hooks order crons diff --git a/desk/sur/hooks.hoon b/desk/sur/hooks.hoon index 2d9be5a098..7d70776525 100644 --- a/desk/sur/hooks.hoon +++ b/desk/sur/hooks.hoon @@ -180,7 +180,6 @@ :: +$ template $: from=nest - meta=data:m hooks=(map id-hook hook) order=(list id-hook) crons=(list [id-hook job]) From 563d1e137317329ee7cd6caddf6f6ad52da9a02b Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 16 Dec 2024 15:30:55 -0600 Subject: [PATCH 143/149] channel-templates: cleanup logs --- packages/shared/src/http-api/Urbit.ts | 2 -- .../src/store/useChannelHooksPreview.ts | 1 - .../components/ChannelFromTemplateView.tsx | 32 +++++++++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/shared/src/http-api/Urbit.ts b/packages/shared/src/http-api/Urbit.ts index bac83e54ac..cb5b6cb6d5 100644 --- a/packages/shared/src/http-api/Urbit.ts +++ b/packages/shared/src/http-api/Urbit.ts @@ -783,8 +783,6 @@ export class Urbit { } ); - console.log('thread returned', res.status, await res.clone().text()); - return res.json(); } diff --git a/packages/shared/src/store/useChannelHooksPreview.ts b/packages/shared/src/store/useChannelHooksPreview.ts index 714576a3c0..fe42aadad3 100644 --- a/packages/shared/src/store/useChannelHooksPreview.ts +++ b/packages/shared/src/store/useChannelHooksPreview.ts @@ -4,7 +4,6 @@ import { getChannelHooksPreview } from '../api'; import * as ub from '../urbit'; export function useChannelHooksPreview(channelId: string) { - console.log('useChannelHooksPreview', channelId); return useQuery({ queryKey: ['channelHooksPreview', channelId], queryFn: () => getChannelHooksPreview(channelId), diff --git a/packages/ui/src/components/ChannelFromTemplateView.tsx b/packages/ui/src/components/ChannelFromTemplateView.tsx index 3e89abcf61..bf21e76958 100644 --- a/packages/ui/src/components/ChannelFromTemplateView.tsx +++ b/packages/ui/src/components/ChannelFromTemplateView.tsx @@ -1,10 +1,14 @@ -import { useChannelHooksPreview, useCreateChannel } from '@tloncorp/shared'; +import { + createDevLogger, + useChannelHooksPreview, + useCreateChannel, +} from '@tloncorp/shared'; import * as api from '@tloncorp/shared/api'; import * as db from '@tloncorp/shared/db'; import { useCallback, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { SizableText, View, XStack, YStack } from 'tamagui'; +import { View, YStack } from 'tamagui'; import { useCurrentUserId, useGroups } from '../contexts'; import { useAlphabeticallySegmentedGroups } from '../hooks/groupsSorters'; @@ -15,6 +19,8 @@ import { GroupSelector } from './GroupSelector'; import { ListItem } from './ListItem'; import { ScreenHeader } from './ScreenHeader'; +const logger = createDevLogger('copy-channel-template', false); + export function ChannelFromTemplateView({ channel, goBack, @@ -49,8 +55,6 @@ export function ChannelFromTemplateView({ }, }); - console.log(errors); - const createChannel = useCreateChannel({ group: selectedGroup, currentUserId, @@ -58,25 +62,33 @@ export function ChannelFromTemplateView({ const onConfirm = useCallback( async (data: { title: string }) => { - console.log('onConfirm', channel, selectedGroup); + logger.log('onConfirm', channel, selectedGroup); if (!channel || !selectedGroup) { return; } - console.log('creating channel', data); + logger.log('creating channel', data); // create channel const newChannel = await createChannel({ title: data.title, channelType: channel.type, }); - console.log('channel created', newChannel); - if (newChannel) { + logger.log('channel created', newChannel); + + if (!newChannel) { + return; + } + + try { // send hook template setup - console.log('sending template'); + logger.log('sending template'); await api.setupChannelFromTemplate(channel.id, newChannel.id); - console.log('template setup'); + logger.log('template setup'); + logger.trackEvent('setupChannelTemplate'); // navigate to channel navigateToChannel(newChannel); + } catch (e) { + logger.trackError('Failed to setup channel template', { stack: e }); } }, [navigateToChannel, channel, selectedGroup] From fe736543af5f1962462be20a1b00e3c227a18928 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 16 Dec 2024 15:59:32 -0600 Subject: [PATCH 144/149] channel-template: better error handling and tracking --- packages/shared/src/api/urbit.ts | 2 +- packages/ui/src/components/ChannelFromTemplateView.tsx | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/api/urbit.ts b/packages/shared/src/api/urbit.ts index 2a92009eb7..b28cf4dbb8 100644 --- a/packages/shared/src/api/urbit.ts +++ b/packages/shared/src/api/urbit.ts @@ -2,7 +2,7 @@ import _ from 'lodash'; import { createDevLogger, escapeLog, runIfDev } from '../debug'; import { AuthError, ChannelStatus, PokeInterface, Urbit } from '../http-api'; -import { desig, preSig } from '../urbit'; +import { preSig } from '../urbit'; import { getLandscapeAuthCookie } from './landscapeApi'; const logger = createDevLogger('urbit', false); diff --git a/packages/ui/src/components/ChannelFromTemplateView.tsx b/packages/ui/src/components/ChannelFromTemplateView.tsx index bf21e76958..670e68a0e5 100644 --- a/packages/ui/src/components/ChannelFromTemplateView.tsx +++ b/packages/ui/src/components/ChannelFromTemplateView.tsx @@ -1,5 +1,6 @@ import { createDevLogger, + deleteChannel, useChannelHooksPreview, useCreateChannel, } from '@tloncorp/shared'; @@ -7,6 +8,7 @@ import * as api from '@tloncorp/shared/api'; import * as db from '@tloncorp/shared/db'; import { useCallback, useMemo, useState } from 'react'; import { useForm } from 'react-hook-form'; +import { Alert } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { View, YStack } from 'tamagui'; @@ -89,6 +91,11 @@ export function ChannelFromTemplateView({ navigateToChannel(newChannel); } catch (e) { logger.trackError('Failed to setup channel template', { stack: e }); + deleteChannel({ + channelId: newChannel.id, + groupId: newChannel.groupId ?? '', + }); + Alert.alert('Channel failed to setup with modifications'); } }, [navigateToChannel, channel, selectedGroup] From 63986fa4252b62b1f05d4408f51b19fd7e165fe3 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Mon, 16 Dec 2024 16:03:04 -0600 Subject: [PATCH 145/149] channel-template: use analytics enum --- packages/shared/src/logic/analytics.ts | 1 + packages/ui/src/components/ChannelFromTemplateView.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/logic/analytics.ts b/packages/shared/src/logic/analytics.ts index 27937125f1..99b69b6810 100644 --- a/packages/shared/src/logic/analytics.ts +++ b/packages/shared/src/logic/analytics.ts @@ -10,4 +10,5 @@ export enum AnalyticsEvent { ContactAdded = 'Contact Added', ContactEdited = 'Contact Edited', InvitedUserFailedInventoryCheck = 'Invited User Failed Inventory Check', + ChannelTemplateSetup = 'Channel Created from Template', } diff --git a/packages/ui/src/components/ChannelFromTemplateView.tsx b/packages/ui/src/components/ChannelFromTemplateView.tsx index 670e68a0e5..c5bbbaff3e 100644 --- a/packages/ui/src/components/ChannelFromTemplateView.tsx +++ b/packages/ui/src/components/ChannelFromTemplateView.tsx @@ -1,4 +1,5 @@ import { + AnalyticsEvent, createDevLogger, deleteChannel, useChannelHooksPreview, @@ -86,7 +87,7 @@ export function ChannelFromTemplateView({ logger.log('sending template'); await api.setupChannelFromTemplate(channel.id, newChannel.id); logger.log('template setup'); - logger.trackEvent('setupChannelTemplate'); + logger.trackEvent(AnalyticsEvent.ChannelTemplateSetup); // navigate to channel navigateToChannel(newChannel); } catch (e) { From 654a8a24465ed5fd8a81640f0140b9871a78551d Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 18 Dec 2024 19:45:21 +0000 Subject: [PATCH 146/149] update glob: [skip actions] --- desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desk/desk.docket-0 b/desk/desk.docket-0 index f74edb38be..f6974de976 100644 --- a/desk/desk.docket-0 +++ b/desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v2.3gbss.14f1b.93jfu.0mgq2.33ufj.glob' 0v2.3gbss.14f1b.93jfu.0mgq2.33ufj] + glob-http+['https://bootstrap.urbit.org/glob-0v7.7donp.7htv6.tp9mc.lbvp8.r4i3h.glob' 0v7.7donp.7htv6.tp9mc.lbvp8.r4i3h] base+'groups' version+[6 7 0] website+'https://tlon.io' From 8bb5383dfd2be04be2cb372dee705b9937624c53 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 18 Dec 2024 20:02:27 +0000 Subject: [PATCH 147/149] update glob: [skip actions] --- tm-alpha-desk/desk.docket-0 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tm-alpha-desk/desk.docket-0 b/tm-alpha-desk/desk.docket-0 index 5f07c379d2..55a7b4cc92 100644 --- a/tm-alpha-desk/desk.docket-0 +++ b/tm-alpha-desk/desk.docket-0 @@ -2,7 +2,7 @@ info+'Start, host, and cultivate communities. Own your communications, organize your resources, and share documents. Tlon is a decentralized platform that offers a full, communal suite of tools for messaging, writing and sharing media with others.' color+0xde.dede image+'https://bootstrap.urbit.org/tlon.svg?v=1' - glob-http+['https://bootstrap.urbit.org/glob-0v7.9h46p.ne45g.s9atj.cp1rs.5fcjn.glob' 0v7.9h46p.ne45g.s9atj.cp1rs.5fcjn] + glob-http+['https://bootstrap.urbit.org/glob-0v5.9cu6g.jcpog.nc64g.8g7e2.7fj71.glob' 0v5.9cu6g.jcpog.nc64g.8g7e2.7fj71] base+'tm-alpha' version+[1 0 0] website+'https://tlon.io' From 86447d855599e3e8200f99400b66f4d43532f55d Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Sat, 21 Dec 2024 14:18:00 -0600 Subject: [PATCH 148/149] hooks: fixing schedule thread --- desk/ted/hook/schedule.hoon | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desk/ted/hook/schedule.hoon b/desk/ted/hook/schedule.hoon index 246cdc710a..0c1fffb53f 100644 --- a/desk/ted/hook/schedule.hoon +++ b/desk/ted/hook/schedule.hoon @@ -25,8 +25,8 @@ (pure:m !>(~)) ;< now=time bind:m get-time:s =/ fires-at - ?^ next.schedule.response - (add now schedule.response) + ?@ schedule.response (add now schedule.response) + next.schedule.response %- (slog (crip "starting hook {}, scheduled to run on {} at {}") ~) (pure:m !>(~)) +$ action From 17462e09420ab722b12a8eecc7d18df11c65c169 Mon Sep 17 00:00:00 2001 From: Hunter Miller Date: Sat, 21 Dec 2024 14:18:16 -0600 Subject: [PATCH 149/149] hooks: making sure state sticks --- desk/app/channels-server.hoon | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/desk/app/channels-server.hoon b/desk/app/channels-server.hoon index baa891617d..6149cb947c 100644 --- a/desk/app/channels-server.hoon +++ b/desk/app/channels-server.hoon @@ -1155,15 +1155,13 @@ ++ run-hooks |= [=event:h =nest:c default=cord] ^- [(each event:h tang) _cor] - =; [result=(each event:h tang) effects=(list effect:h)] - [result (run-hook-effects effects nest)] =| effects=(list effect:h) =/ order (~(gut by order.hooks) nest ~) =/ channel `[nest (~(got by v-channels) nest)] =/ =bowl:h (get-hook-bowl channel *config:h) |- ?~ order - [&+event effects] + [&+event (run-hook-effects effects nest)] =* next $(order t.order) =/ hook (~(got by hooks.hooks) i.order) =. bowl bowl(hook hook, config (~(gut by config.hook) nest ~)) @@ -1174,7 +1172,7 @@ =. effects (weld effects effects.u.return) =. hooks.hooks (~(put by hooks.hooks) i.order hook(state new-state.u.return)) ?: ?=(%denied -.result) - [|+~[(fall msg.result default)] effects] + [|+~[(fall msg.result default)] (run-hook-effects effects nest)] =. event event.result next ++ wake-hook