cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

main
012 / internal / ui / sharecmds.go
sharecmds.go 5.4K
1package ui23import (4 "strings"56 tea "charm.land/bubbletea/v2"78 "src.rest/cat/012/internal/room"9 "src.rest/cat/012/internal/sheet"10 "src.rest/cat/012/internal/ui/picker"11)1213// The commands of a shared workbook: who's here, and handing writing14// over in a one-writer room; what undo says when another's step is in15// the way; and whose change an entry replaced.1617func init() {18 unshared := func(m *Model) bool { return m.share.reg == nil }19 register(20 &command{id: "share.who", macro: macroNever, title: "Who's here", desc: "List who shares this workbook and where each is; pick one to go to their cell",21 hidden: unshared, enabled: (*Model).withOthers, run: (*Model).openWho},22 &command{id: "share.hand", macro: macroNever, title: "Hand over writing", desc: "Let someone else edit the shared workbook, and follow them",23 hidden: unshared, enabled: func(m *Model) bool {24 seat := m.share.seat25 return seat != nil && seat.Mode() == room.View && seat.Writing() && seat.Others() > 026 }, run: (*Model).openHandOver},27 )28}2930// nbReading are the notebook's commands that change nothing a follower31// in a one-writer room may not: moving about and looking.32var nbReading = map[string]bool{"nb.open": true, "nb.goto": true, "nb.toc": true, "nb.toggle_output": true, "nb.toggle_whole": true,33 "nb.open_output": true, "nb.command_mode": true, "nb.copy": true}3435// edits reports whether command c changes the workbook, which a36// follower in a one-writer room may not do.37func edits(c *command) bool {38 if strings.HasPrefix(c.id, "nb.") {39 return !nbReading[c.id]40 }41 return c.macro == macroRecord42}4344// openWho lists who shares the workbook, to go to one of them.45func (m *Model) openWho() tea.Cmd {46 var items []picker.Item47 for _, p := range m.share.seat.Peers() {48 where := m.peerWhere(p)49 desc := "Go to " + p.Name + "'s cell"50 detail, title := where, p.Name51 if p.Agent {52 detail, title = "agent, "+where, peerLabel(p.Name, true)53 }54 switch {55 case p.Writing && m.share.seat.Mode() == room.View:56 detail += ", writing"57 case p.Presence.Editing:58 detail += ", typing"59 }60 items = append(items, picker.Item{Title: title, Name: len(title), Detail: detail, Desc: desc,61 Pick: func() tea.Cmd { m.closeOverlay(); m.goToPeer(p); return nil }})62 }63 pk := m.newPicker("Who's here", "a name", 50, items)64 pk.Action = "go"65 m.openOverlay(pk)66 return nil67}6869// peerWhere is where p is, Sheet2!B3, or the notebook they're on.70func (m *Model) peerWhere(p room.Peer) string {71 s := p.Presence.Sheet72 if s == nil && p.Agent {73 return "reading"74 }75 if s == nil || !s.Live() {76 return "arriving"77 }78 if s.IsNotebook() {79 return s.Name()80 }81 return sheet.Qualified(s.Name(), sheet.Rect{From: p.Presence.Cursor, To: p.Presence.Cursor})82}8384// goToPeer shows p's sheet with the pointer on their cell.85func (m *Model) goToPeer(p room.Peer) {86 s := p.Presence.Sheet87 if s == nil || !s.Live() || s.Hidden() {88 return89 }90 m.showSheet(s)91 if !s.IsNotebook() {92 m.cur = p.Presence.Cursor93 m.clearSelection()94 }95 m.note = "At " + p.Name + "'s cell"96}9798// openHandOver asks whom to hand writing to.99func (m *Model) openHandOver() tea.Cmd {100 var items []picker.Item101 for _, p := range m.share.seat.Peers() {102 items = append(items, picker.Item{Title: p.Name, Name: len(p.Name), Detail: m.peerWhere(p), Desc: "Let " + p.Name + " write; you follow",103 Pick: func() tea.Cmd {104 m.closeOverlay()105 if m.share.seat.HandTo(p.ID) {106 m.note = p.Name + " writes now: you follow"107 }108 return nil109 }})110 }111 pk := m.newPicker("Hand over writing", "a name", 50, items)112 pk.Action = "hand over"113 m.openOverlay(pk)114 return nil115}116117// undoBlocked says why the user's undo (or redo) can't go ahead: a later118// change of someone else's changed the same cells.119func (m *Model) undoBlocked(redo bool) bool {120 if m.share.seat == nil {121 return false122 }123 w := m.book()124 check, verb := w.UndoBlocked, "undo "+w.UndoLabel()125 if redo {126 check, verb = w.RedoBlocked, "redo"127 }128 b, blocked := check()129 if !blocked {130 return false131 }132 m.warn = "Can't " + verb + ": " + m.peerName(b.Author) + " has changed it since, and undo takes back only your own changes"133 return true134}135136// peerName is the name of the participant with id, or "someone" when137// they've left.138func (m *Model) peerName(id int) string {139 for _, p := range m.share.seat.Peers() {140 if p.ID == id {141 return p.Name142 }143 }144 return "someone who left"145}146147// roomLast is the room's latest operation, 0 outside a room.148func (m *Model) roomLast() uint64 {149 if m.share.seat == nil {150 return 0151 }152 return m.share.seat.Last()153}154155// overwrote says whose change the entry just made replaced: someone156// else changed the cell while it was being typed. The server's order157// decides, and the last entry stays.158func (m *Model) overwrote() {159 seat := m.share.seat160 if seat == nil {161 return162 }163 for _, mk := range seat.Marks(m.share.entryFrom) {164 if mk.Author != seat.ID() && mk.Sheet == m.entrySheet() && mk.Rect.Contains(m.cur) && mk.Kind != sheet.OpUndo {165 m.warn = "Your entry replaced " + mk.Name + "'s change to " + m.cur.String() + ", made while you typed"166 }167 }168}169170// roomOwned has what cmd reports go to the room's keeper when the171// workbook is shared, rather than back to this model, which may have172// left by then.173func (m *Model) roomOwned(cmd tea.Cmd) tea.Cmd {174 seat := m.share.seat175 if seat == nil || cmd == nil {176 return cmd177 }178 return func() tea.Msg {179 if msg := cmd(); msg != nil {180 seat.Post(msg)181 }182 return nil183 }184}