-
Notifications
You must be signed in to change notification settings - Fork 67
Expand file tree
/
Copy pathroot.nim
More file actions
196 lines (177 loc) · 6.21 KB
/
root.nim
File metadata and controls
196 lines (177 loc) · 6.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import chronos, chronicles, deques, strutils, os
from illwave as iw import nil, `[]`, `[]=`, `==`, width, height
from nimwave as nw import nil
from terminal import nil
import libp2p
import ./scrollingtextbox
import ./context
import ../utils
const
InputPanelHeight: int = 3
ScrollSpeed: int = 2
type InputPanel = ref object of nw.Node
method render(node: InputPanel, ctx: var nw.Context[State]) =
ctx = nw.slice(ctx, 0, 0, iw.width(ctx.tb), InputPanelHeight)
render(
nw.Box(
border: nw.Border.Single,
direction: nw.Direction.Vertical,
children: nw.seq("> " & ctx.data.inputBuffer),
),
ctx,
)
proc resizePanels(
chatPanel: ScrollingTextBox,
peersPanel: ScrollingTextBox,
systemPanel: ScrollingTextBox,
newWidth: int,
newHeight: int,
) =
let
peersPanelWidth = (newWidth / 4).int
topHeight = (newHeight / 2).int
chatPanel.resize(newWidth - peersPanelWidth, topHeight)
peersPanel.resize(peersPanelWidth, topHeight)
systemPanel.resize(newWidth, newHeight - topHeight - InputPanelHeight)
proc runUI*(
gossip: GossipSub,
room: string,
recvQ: AsyncQueue[string],
peerQ: AsyncQueue[(PeerId, PeerEventKind)],
systemQ: AsyncQueue[string],
myPeerId: PeerId,
) {.async: (raises: [Exception]).} =
var
ctx = nw.initContext[State]()
prevTb: iw.TerminalBuffer
mouse: iw.MouseInfo
key: iw.Key
terminal.enableTrueColors()
terminal.hideCursor()
try:
iw.init()
except:
return
ctx.tb = iw.initTerminalBuffer(terminal.terminalWidth(), terminal.terminalHeight())
# TODO: publish my peerid in peerid topic
let
peersPanelWidth = (iw.width(ctx.tb) / 4).int
topHeight = (iw.height(ctx.tb) / 2).int
chatPanel = ScrollingTextBox.new(
title = "Chat", width = iw.width(ctx.tb) - peersPanelWidth, height = topHeight
)
peersPanel = ScrollingTextBox.new(
title = "Peers",
width = peersPanelWidth,
height = topHeight,
text = @[shortPeerId(myPeerId) & " (You)"],
)
systemPanel = ScrollingTextBox.new(
title = "System",
width = iw.width(ctx.tb),
height = iw.height(ctx.tb) - topHeight - InputPanelHeight,
)
# Send chronicle logs to systemPanel
defaultChroniclesStream.output.writer = proc(
logLevel: LogLevel, msg: LogOutputStr
) {.gcsafe.} =
for line in msg.replace("\t", " ").splitLines():
systemPanel.push(line)
ctx.data.inputBuffer = ""
let focusAreas = @[chatPanel, peersPanel, systemPanel]
var focusIndex = 0
var focusedPanel: ScrollingTextBox
while true:
focusedPanel = focusAreas[focusIndex]
focusedPanel.border = nw.Border.Double
key = iw.getKey(mouse)
if key == iw.Key.Mouse:
case mouse.scrollDir
of iw.ScrollDirection.sdUp:
focusedPanel.scrollUp(ScrollSpeed)
of iw.ScrollDirection.sdDown:
focusedPanel.scrollDown(ScrollSpeed)
else:
discard
elif key == iw.Key.Tab:
# unfocus previous panel
focusedPanel.border = nw.Border.Single
focusIndex += 1
if focusIndex >= focusAreas.len:
focusIndex = 0 # wrap around
elif key in {iw.Key.Space .. iw.Key.Tilde}:
ctx.data.inputBuffer.add(cast[char](key.ord))
elif key == iw.Key.Backspace and ctx.data.inputBuffer.len > 0:
ctx.data.inputBuffer.setLen(ctx.data.inputBuffer.len - 1)
elif key == iw.Key.Enter:
# handle /file command to send/publish files
if ctx.data.inputBuffer.startsWith("/file"):
let parts = ctx.data.inputBuffer.split(" ")
if parts.len < 2:
systemPanel.push("Invalid /file command, missing file name")
else:
for path in parts[1 ..^ 1]:
if not fileExists(path):
systemPanel.push("Unable to find file '" & path & "', skipping")
continue
let fileId = path.splitFile().name
# copy file to /tmp/{filename}
copyFile(path, getTempDir().joinPath(fileId))
# publish /tmp/{filename}
try:
discard await gossip.publish(FileChatTopic, cast[seq[byte]](@(fileId)))
systemPanel.push("Offering file " & fileId)
except Exception as exc:
systemPanel.push("Unable to offer file: " & exc.msg)
else:
try:
discard await gossip.publish(room, cast[seq[byte]](@(ctx.data.inputBuffer)))
chatPanel.push("You: " & ctx.data.inputBuffer) # show message in ui
systemPanel.push("Sent chat message")
except Exception as exc:
systemPanel.push("Unable to send chat message: " & exc.msg)
ctx.data.inputBuffer = "" # clear input buffer
elif key != iw.Key.None:
discard
# update peer list if there's a new peer from peerQ
if not peerQ.empty():
let (newPeer, eventKind) = await peerQ.get()
if eventKind == PeerEventKind.Joined and
not peersPanel.text.contains(shortPeerId(newPeer)):
systemPanel.push("Adding peer " & shortPeerId(newPeer))
peersPanel.push(shortPeerId(newPeer))
if eventKind == PeerEventKind.Left and
peersPanel.text.contains(shortPeerId(newPeer)):
systemPanel.push("Removing peer " & shortPeerId(newPeer))
peersPanel.remove(shortPeerId(newPeer))
# update messages if there's a new message from recvQ
if not recvQ.empty():
let msg = await recvQ.get()
chatPanel.push(msg) # show message in ui
# update messages if there's a new message from recvQ
if not systemQ.empty():
let msg = await systemQ.get()
if msg.len > 0:
systemPanel.push(msg) # show message in ui
renderRoot(
nw.Box(
direction: nw.Direction.Vertical,
children: nw.seq(
nw.Box(
direction: nw.Direction.Horizontal, children: nw.seq(chatPanel, peersPanel)
),
systemPanel,
InputPanel(),
),
),
ctx,
)
# render
iw.display(ctx.tb, prevTb)
prevTb = ctx.tb
ctx.tb = iw.initTerminalBuffer(terminal.terminalWidth(), terminal.terminalHeight())
if iw.width(prevTb) != iw.width(ctx.tb) or iw.height(prevTb) != iw.height(ctx.tb):
resizePanels(
chatPanel, peersPanel, systemPanel, iw.width(ctx.tb), iw.height(ctx.tb)
)
await sleepAsync(5.milliseconds)