aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2022-09-07 19:42:18 +0200
committerPřemysl Eric Janouch <p@janouch.name>2022-09-08 01:28:51 +0200
commit4ba28c6ed3b952a06aba8ae96220c429d4d02365 (patch)
tree30b9ef26d0652451e0c28aa65343950594239c15
parent45aa0e8dfba2a51d26b6a4fe4990a1686ab4af65 (diff)
downloadxK-4ba28c6ed3b952a06aba8ae96220c429d4d02365.tar.gz
xK-4ba28c6ed3b952a06aba8ae96220c429d4d02365.tar.xz
xK-4ba28c6ed3b952a06aba8ae96220c429d4d02365.zip
xC/xP: mark highlights and buffer activity
And more or less finalize out the protocol for this use case.
-rw-r--r--README.adoc2
-rw-r--r--xC-proto18
-rw-r--r--xC.c13
-rw-r--r--xP/public/xP.css13
-rw-r--r--xP/public/xP.js87
5 files changed, 114 insertions, 19 deletions
diff --git a/README.adoc b/README.adoc
index 67a7ea5..6d61bcc 100644
--- a/README.adoc
+++ b/README.adoc
@@ -26,7 +26,7 @@ As a unique bonus, you can launch a full text editor from within.
xP
--
The web frontend for 'xC', making use of its networked relay interface.
-So far it's quite basic, yet usable.
+So far it's somewhat basic, yet usable.
xF
--
diff --git a/xC-proto b/xC-proto
index d25a118..46955e7 100644
--- a/xC-proto
+++ b/xC-proto
@@ -3,6 +3,8 @@ const VERSION = 1;
// From the frontend to the relay.
struct CommandMessage {
+ // The command sequence number will be repeated in responses
+ // in the respective fields.
u32 command_seq;
union CommandData switch (enum Command {
HELLO,
@@ -15,6 +17,9 @@ struct CommandMessage {
} command) {
case HELLO:
u32 version;
+ // If the version check succeeds, the client will receive
+ // an initial stream of BUFFER_UPDATE, BUFFER_LINE,
+ // and finally a BUFFER_ACTIVATE message.
case PING:
void;
case ACTIVE:
@@ -51,6 +56,15 @@ struct EventMessage {
void;
case BUFFER_UPDATE:
string buffer_name;
+ // These are cumulative, even for lines flushed out from buffers.
+ // Updates to these values aren't broadcasted, thus handle:
+ // - BUFFER_LINE by bumping/setting them as appropriate,
+ // - BUFFER_ACTIVATE by clearing them for the previous buffer
+ // (this way, they can be used to mark unread messages).
+ // Any updates received after the initial sync should be ignored.
+ u32 new_messages;
+ u32 new_unimportant_messages;
+ bool highlighted;
case BUFFER_RENAME:
string buffer_name;
string new;
@@ -60,6 +74,7 @@ struct EventMessage {
string buffer_name;
case BUFFER_LINE:
string buffer_name;
+ // Whether the line should also be displayed in the active buffer.
bool leak_to_active;
bool is_unimportant;
bool is_highlight;
@@ -106,7 +121,8 @@ struct EventMessage {
case BUFFER_CLEAR:
string buffer_name;
- // Restriction: command_seq is strictly increasing, across both of these.
+ // Restriction: command_seq strictly follows the sequence received
+ // by the relay, across both of these replies.
case ERROR:
u32 command_seq;
string error;
diff --git a/xC.c b/xC.c
index b7aa712..1a30f85 100644
--- a/xC.c
+++ b/xC.c
@@ -3068,6 +3068,11 @@ relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
e->event = RELAY_EVENT_BUFFER_UPDATE;
e->buffer_name = str_from_cstr (buffer->name);
+ e->new_messages = MIN (UINT32_MAX,
+ buffer->new_messages_count - buffer->new_unimportant_count);
+ e->new_unimportant_messages = MIN (UINT32_MAX,
+ buffer->new_unimportant_count);
+ e->highlighted = buffer->highlighted;
}
static void
@@ -5015,8 +5020,16 @@ buffer_merge (struct app_context *ctx,
buffer->lines_count += n;
// And since there is no log_*() call, send them to relays manually
+ buffer->highlighted |= merged->highlighted;
LIST_FOR_EACH (struct buffer_line, line, start)
{
+ if (buffer->new_messages_count)
+ {
+ buffer->new_messages_count++;
+ if (line->flags & BUFFER_LINE_UNIMPORTANT)
+ buffer->new_unimportant_count++;
+ }
+
relay_prepare_buffer_line (ctx, buffer, line, false);
relay_broadcast (ctx);
}
diff --git a/xP/public/xP.css b/xP/public/xP.css
index a13afc6..81025c9 100644
--- a/xP/public/xP.css
+++ b/xP/public/xP.css
@@ -58,7 +58,13 @@ body {
padding: .05rem .3rem;
cursor: default;
}
-.item.active {
+.item.highlighted {
+ color: #ff5f00;
+}
+.item.activity {
+ font-weight: bold;
+}
+.item.current {
font-style: italic;
background: #f8f8f8;
border-top: 1px solid #eee;
@@ -97,6 +103,11 @@ body {
grid-column: span 2;
font-weight: bold;
}
+.unread {
+ height: 1px;
+ grid-column: span 2;
+ background: #ff5f00;
+}
.time {
padding: .1rem .3rem;
background: #f8f8f8;
diff --git a/xP/public/xP.js b/xP/public/xP.js
index a941752..42a7a55 100644
--- a/xP/public/xP.js
+++ b/xP/public/xP.js
@@ -156,10 +156,13 @@ rpc.addEventListener('close', event => {
rpc.addEventListener('BufferUpdate', event => {
let e = event.detail, b = buffers.get(e.bufferName)
if (b === undefined) {
- b = {lines: []}
- buffers.set(e.bufferName, b)
+ buffers.set(e.bufferName, {
+ lines: [],
+ newMessages: e.newMessages,
+ newUnimportantMessages: e.newUnimportantMessages,
+ highlighted: e.highlighted,
+ })
}
- // TODO: Update any buffer properties.
})
rpc.addEventListener('BufferRename', event => {
@@ -174,8 +177,14 @@ rpc.addEventListener('BufferRemove', event => {
})
rpc.addEventListener('BufferActivate', event => {
- let e = event.detail, b = buffers.get(e.bufferName)
let old = buffers.get(bufferCurrent)
+ if (old !== undefined) {
+ old.newMessages = 0
+ old.newUnimportantMessages = 0
+ old.highlighted = false
+ }
+
+ let e = event.detail, b = buffers.get(e.bufferName)
bufferCurrent = e.bufferName
bufferLog = undefined
bufferAutoscroll = true
@@ -195,12 +204,41 @@ rpc.addEventListener('BufferActivate', event => {
})
rpc.addEventListener('BufferLine', event => {
- let e = event.detail, b = buffers.get(e.bufferName),
- line = {when: e.when, rendition: e.rendition, items: e.items}
- if (b !== undefined)
- b.lines.push({...line})
- if (e.leakToActive && (b = buffers.get(bufferCurrent)) !== undefined)
- b.lines.push({leaked: true, ...line})
+ let e = event.detail, b = buffers.get(e.bufferName), line = {...e}
+ delete line.event
+ delete line.leakToActive
+ if (b === undefined)
+ return
+
+ // Initial sync: skip all other processing, let highlights be.
+ if (bufferCurrent === undefined) {
+ b.lines.push(line)
+ return
+ }
+
+ let visible = e.bufferName == bufferCurrent || e.leakToActive
+ b.lines.push({...line})
+ if (!visible || b.newMessages || b.newUnimportantMessages) {
+ if (line.isUnimportant)
+ b.newUnimportantMessages++
+ else
+ b.newMessages++
+ }
+ if (e.leakToActive) {
+ let bc = buffers.get(bufferCurrent)
+ bc.lines.push({...line, leaked: true})
+ if (bc.newMessages || bc.newUnimportantMessages) {
+ if (line.isUnimportant)
+ bc.newUnimportantMessages++
+ else
+ bc.newMessages++
+ }
+ }
+
+ // TODO: Find some way of highlighting the tab in a browser.
+ // TODO: Also highlight on unseen private messages, like xC does.
+ if (!visible && line.isHighlight)
+ b.highlighted = true
})
rpc.addEventListener('BufferClear', event => {
@@ -267,10 +305,21 @@ let BufferList = {
view: vnode => {
let items = Array.from(buffers, ([name, b]) => {
- let attrs = {onclick: event => BufferList.activate(name)}
- if (name == bufferCurrent)
- attrs.class = 'active'
- return m('.item', attrs, name)
+ let classes = [], displayName = name
+ if (name == bufferCurrent) {
+ classes.push('current')
+ } else {
+ if (b.highlighted)
+ classes.push('highlighted')
+ if (b.newMessages) {
+ classes.push('activity')
+ displayName += ` (${b.newMessages})`
+ }
+ }
+ return m('.item', {
+ onclick: event => BufferList.activate(name),
+ class: classes.join(' '),
+ }, displayName)
})
return m('.list', {}, items)
},
@@ -387,7 +436,9 @@ let Buffer = {
return
let lastDateMark = undefined
- b.lines.forEach(line => {
+ let markBefore = b.lines.length
+ - b.newMessages - b.newUnimportantMessages
+ b.lines.forEach((line, i) => {
let date = new Date(line.when)
let dateMark = date.toLocaleDateString()
if (dateMark !== lastDateMark) {
@@ -395,16 +446,20 @@ let Buffer = {
lastDateMark = dateMark
}
+ if (i == markBefore)
+ lines.push(m('.unread'))
+
let attrs = {}
if (line.leaked)
attrs.class = 'leaked'
+ // TODO: Make use of isUnimportant.
lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))
lines.push(m(Content, {...attrs}, line))
})
let dateMark = new Date().toLocaleDateString()
- if (dateMark !== lastDateMark)
+ if (dateMark !== lastDateMark && lastDateMark !== undefined)
lines.push(m('.date', {}, dateMark))
return m('.buffer', {}, lines)
},