Something went wrong. Try again.
a Bluesky client for PICO-8 picosky.vinnymac.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170-- app state + messages
-- screensscr_boot=0scr_timeline=1scr_thread=2scr_compose=3scr_auth=4scr_demo=5scr_splash=6
scr=scr_splashscr_wt=-1 -- time() a screen change started its slide (-1 = none yet)lastscr=-1 -- last scr seen, for edge-detecting screen changestrans_dir=1 -- 1=forward (cards from right), -1=back (cards from left)prev_was_thread=false -- title to roll out on next transitionprev_was_compose=false -- true when closing the compose drawerfeed={} -- timeline poststhread={} -- open thread postsposts=feed -- active list pointersel=1scroll=0prev_sel=1prev_scroll=0just_switched=falsediscard_loading=false -- true while discarding an in-flight REQ_MORE after screen changethread_focus=0 -- refid to auto-focus after a thread loads (0 = none)loading=truehasmore=falsestatus="connecting to bluesky..."status_t=0stik=0
-- compose statedraft=""reply_to=0 -- 0 = new post, else refidquote_of=0 -- 0 = not a quote, else the quoted post's refidquote_name="" -- display name of the quoted post's author (compose header)maxchars=300
theme=0 -- 0=day,1=nightlast_pt=-1 -- last page-theme bit seen on pin 126 (edge-detect)mi_track=-1 mi_theme=-1 -- last track/theme reflected in the pause menu labels-- feed switcher: names from js (idx 1=following). active_feed is the picked-- index; loaded_feed is what the timeline currently shows (reload when they-- differ); mi_feed shadows the pause-menu label registration.feeds={"following"} feedsm={"following"} active_feed=1 loaded_feed=1 mi_feed=-1alt_ref=0 -- refid whose alt-text marquee clock is runningalt_t0=0 -- time() the active post's alt marquee started
function set_status(s) status=s status_t=time()end
function parse_post(s) local f=split(s,"\1",false) return { name=f[1] or "?", handle=f[2] or "", reltime=f[3] or "", text=f[4] or "", likes=f[5] or "0", reposts=f[6] or "0", replies=f[7] or "0", refid=tonum(f[8]) or 0, depth=tonum(f[9]) or 0, -- img_state: 0=no image, 1=loading, 2=ready img_state=(tonum(f[10]) or 0)==1 and 1 or 0, linktext=f[11] or "", liked=(tonum(f[12]) or 0)==1, -- viewer already liked this post? alt=f[13] or "", -- image alt text (marquee'd when long + selected) reposted=(tonum(f[14]) or 0)==1, -- viewer already reposted this post? avatar_pixels=nil, }end
-- run fn(p) on every post matching id across the timeline + open thread - the-- same id can exist in both when uriRefs reuses a timeline id in a thread post.function eachpost(id,fn) for l in all({feed,thread}) do for p in all(l) do if p.refid==id then fn(p) end end endend
-- unpack 2px/byte nibble-packed bytes (hi nibble first) into a 0-15 pixel listfunction unpack_px(packed) local px={} for b in all(packed) do add(px,(b>>4)&0xf) add(px,b&0xf) end return pxend
-- update all posts with this refid (image/avatar can land in feed + thread both);-- px is unpacked once on first match, then shared via the closure upvalue.function on_image(refid,packed) local px eachpost(refid,function(p) px=px or unpack_px(packed) p.pixels=px p.img_state=2 end)end
function on_image_fail(refid) eachpost(refid,function(p) p.img_state=0 end)end
function on_avatar(refid,packed) local px eachpost(refid,function(p) px=px or unpack_px(packed) p.avatar_pixels=px end)end
function on_message(op,arg,s) if op==op_demo_stop then exit_demo() return end if not ONLINE then return end if op==op_resp_post then if not discard_loading then add(posts,parse_post(s)) loading=false end elseif op==op_resp_end then if discard_loading then discard_loading=false -- REQ_MORE fully discarded; next response is REQ_THREAD else loading=false hasmore=(arg!=0) if #posts>0 then sfx(8) end -- new-posts chime -- auto-focus a specific post after a thread loads (e.g. self-reply from timeline) if scr==scr_thread and thread_focus>0 then for i=1,#thread do if thread[i].refid==thread_focus then sel=i scroll=max(0,i-2) break end end thread_focus=0 end end elseif op==op_resp_ok then set_status"posted!" reset_draft() sfx(6) -- success open_timeline(true) elseif op==op_resp_error then loading=false set_status(sub(s,1,40)) sfx(7) -- error -- a failed send leaves the draft intact: drop back into compose so it can -- be edited and resent (a fetch error has no draft, so this is a no-op). if #draft>0 then scr=scr_compose poke(0x5f2d,1) end elseif op==op_resp_feeds then -- rebuild the feed switcher from js: names arrive interleaved [header,menu,...]. -- keep active/loaded in range and equal so arriving feeds never trigger a -- spurious reload; mi_feed=-1 forces re-register. local a=split(s,"\1",false) feeds={} feedsm={} for i=1,#a,2 do add(feeds,a[i]) add(feedsm,a[i+1]) end active_feed=mid(1,active_feed,#feeds) loaded_feed=active_feed mi_feed=-1 elseif op==op_resp_auth_lost then scr=scr_auth endend