package main import ( "citrons.xyz/talk/client/buffer" "citrons.xyz/talk/client/window" "citrons.xyz/talk/proto" "citrons.xyz/talk/tui" "strconv" "time" ) type channelLocation struct { id string } type channelWindow struct { window.DefaultWindow location channelLocation watchedUsers map[string]bool loadingHistory bool endOfHistory bool } type channelMsg struct { proto.Object window *channelWindow } func (cl channelLocation) CreateWindow() window.Window { if !globalApp.connected || !globalApp.authenticated { return nil } cw := &channelWindow { location: cl, watchedUsers: make(map[string]bool), } globalApp.cache.Watch(cl.id) cw.loadMoreHistory() return cw } func (cw *channelWindow) getChannel() *proto.Object { return globalApp.cache.Get(cw.location.id) } func (cw *channelWindow) watchUser(uid string) { if !cw.watchedUsers[uid] { globalApp.cache.Watch(uid) cw.watchedUsers[uid] = true } } func (cw *channelWindow) username(uid string) string { cw.watchUser(uid) u := globalApp.cache.Get(uid) if u != nil { return u.Fields[""] } return "..." } func (cw *channelWindow) isGone(uid string) bool { cw.watchUser(uid) u := globalApp.cache.Get(uid) return u != nil && u.Kind == "gone" } func (cw *channelWindow) put(msg proto.Object) { cw.Buf.Add(channelMsg {msg, cw}) } func (cw *channelWindow) Location() window.Location { return cw.location } func (cw *channelWindow) Kill() { globalApp.cache.Unwatch(cw.location.id) for u := range cw.watchedUsers { globalApp.cache.Unwatch(u) } } func (cw *channelWindow) Buffer() *buffer.Buffer { if cw.Buf.AtTop() { cw.loadMoreHistory() } return &cw.Buf } func (cw *channelWindow) Send(text string) { if !globalApp.connected || !globalApp.authenticated { return } cb := func(response proto.Command) { switch response.Kind { case "p": if len(response.Args) > 0 { cw.put(response.Args[0]) } case "fail": if len(response.Args) > 0 { globalApp.cmdWindow.fail(response.Args[0]) } } } msg := proto.Object {"m", "", map[string]string {"": text}} globalApp.Request(proto.NewCmd("p", cw.location.id, msg), cb) cw.In.SetText("") } func (cw *channelWindow) leaveChannel() { globalApp.Request(proto.NewCmd("leave", cw.location.id), nil) globalApp.windowCache.Evict(cw.location) globalApp.removeFromHistory(cw.location) } func (cw *channelWindow) renameChannel(newName string) { ch := proto.Object { "channel", cw.location.id, map[string]string {"": newName}, } globalApp.sendUpdate(ch, func(response proto.Command) { if response.Kind == "fail" { if len(response.Args) > 0 { f := proto.Fail(response.Args[0]) globalApp.cmdWindow.err(f.Error()) } } }) } func (cw *channelWindow) loadMoreHistory() { if cw.loadingHistory || cw.endOfHistory { return } cw.loadingHistory = true rq := proto.Object {Fields: make(map[string]string)} top := cw.Buf.Top() if top != nil { rq.Kind = "before" rq.Fields[""] = top.Msg().(channelMsg).Object.Id } else { rq.Kind = "latest" } cb := func(response proto.Command) { cw.loadingHistory = false switch response.Kind { case "history": if len(response.Args) == 0 { cw.endOfHistory = true } for i := len(response.Args) - 1; i >= 0; i-- { cw.Buf.AddTop(channelMsg {response.Args[i], cw}) } case "fail": cw.endOfHistory = true } } globalApp.Request(proto.NewCmd("history", cw.location.id, rq), cb) } type userListMsg struct { index int channelName string names []string } func (u userListMsg) Id() string { return "user list." + strconv.Itoa(u.index) } func (u userListMsg) Show(odd bool) { tui.Push("", tui.Box { Width: tui.Fill, Height: tui.Children, Dir: tui.Right, Style: &tui.Style {Bg: colorCmd[odd], Fg: tui.White}, }) nameStyle := &tui.Style {Bg: colorCmd[odd], Fg: tui.White, Bold: true} tui.Push("", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) tui.Text("* ", nil) tui.Pop() tui.Push("", tui.Box {Width: tui.Fill, Height: tui.Children}) tui.Push("", tui.Box {Width: tui.Fill, Height: tui.TextSize}) tui.Text(strconv.Itoa(len(u.names)), nil) tui.Text(" users in ", nil) tui.Text(u.channelName, nameStyle) tui.Pop() for _, name := range u.names { tui.Push("", tui.Box { Width: tui.Fill, Height: tui.Children, Dir: tui.Right, }) tui.Push("", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) tui.Text(" * ", nil) tui.Pop() tui.Push("", tui.Box {Width: tui.TextSize, Height: tui.TextSize}) tui.Text(name, nameStyle) tui.Pop() tui.Pop() } tui.Pop() tui.Pop() } func (cw *channelWindow) userList(callback func(userListMsg)) { ch := cw.getChannel() cb := func(response proto.Command) { if response.Kind != "list" { return } var names []string for _, u := range response.Args { names = append(names, u.Fields[""]) } lastIndex++ callback(userListMsg {lastIndex, ch.Fields[""], names}) } globalApp.Request(proto.NewCmd("list", cw.location.id), cb) } func (cw *channelWindow) ShowStatusLine() { ch := cw.getChannel() if ch == nil { return } tui.Text(ch.Fields[""], nil) if cw.loadingHistory { tui.Text(" ...", nil) } } func (m channelMsg) Id() string { return "buffer." + m.Object.Id } func (m channelMsg) showDate(bg int32) { unix, _ := strconv.Atoi(m.Fields["t"]) time := time.Unix(int64(unix), 0).Format(time.DateTime) tui.Push("", tui.Box {Width: tui.TextSize, Height: 1, NoWrap: true}) tui.Text(" " + time, &tui.Style {Fg: tui.BrightBlack, Bg: bg}) tui.Pop() tui.Push("", tui.Box {Width: tui.Fill, Height: 1}) tui.Pop() } func (m channelMsg) showName(bg int32) { nameStyle := tui.Style {Bg: bg, Fg: tui.White} if m.Fields["f"] == globalApp.uid { nameStyle.Fg = tui.Cyan } if m.window.isGone(m.Fields["f"]) { nameStyle.Italic = true } else { nameStyle.Bold = true } tui.Push(m.Id() + ".name", tui.Box { Width: tui.TextSize, Height: 1, NoWrap: true, }) tui.Text(m.window.username(m.Fields["f"]), &nameStyle) tui.Pop() } func (m channelMsg) Show(odd bool) { var bg int32 = colorDefault[odd] switch m.Kind { case "join", "leave": tui.Push("", tui.Box { Width: tui.Fill, Height: tui.Children, Dir: tui.Left, }) m.showDate(bg) tui.Push("", tui.Box {Width: tui.Children, Height: 1, Dir: tui.Right}) tui.Push("", tui.Box { Width: tui.TextSize, Height: tui.TextSize, NoWrap: true, }) tui.Text("<- ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) tui.Text(m.Kind, &tui.Style {Fg: tui.Blue, Bg: bg}) tui.Text(": ", &tui.Style {Fg: tui.BrightBlack, Bg: bg}) tui.Pop() m.showName(bg) tui.Pop() tui.Pop() case "m": tui.Push("", tui.Box {Width: tui.Fill, Height: 1, Dir: tui.Left}) m.showDate(bg) m.showName(bg) tui.Pop() tui.Push("", tui.Box { Width: tui.TextSize, Height: tui.TextSize, Margins: [4]int {1, 0, 0, 0}, }) tui.Text(m.Fields[""], nil) tui.Pop() default: tui.Push("", tui.Box {Width: tui.Fill, Height: tui.TextSize}) tui.Text("[", nil) tui.Text(m.Kind, &tui.Style {Fg: tui.White, Bg: bg, Italic: true}) tui.Text("]", nil) tui.Pop() } }