xC/xP: mark highlights and buffer activity
And more or less finalize out the protocol for this use case.
This commit is contained in:
parent
45aa0e8dfb
commit
4ba28c6ed3
@ -26,7 +26,7 @@ As a unique bonus, you can launch a full text editor from within.
|
|||||||
xP
|
xP
|
||||||
--
|
--
|
||||||
The web frontend for 'xC', making use of its networked relay interface.
|
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
|
xF
|
||||||
--
|
--
|
||||||
|
18
xC-proto
18
xC-proto
@ -3,6 +3,8 @@ const VERSION = 1;
|
|||||||
|
|
||||||
// From the frontend to the relay.
|
// From the frontend to the relay.
|
||||||
struct CommandMessage {
|
struct CommandMessage {
|
||||||
|
// The command sequence number will be repeated in responses
|
||||||
|
// in the respective fields.
|
||||||
u32 command_seq;
|
u32 command_seq;
|
||||||
union CommandData switch (enum Command {
|
union CommandData switch (enum Command {
|
||||||
HELLO,
|
HELLO,
|
||||||
@ -15,6 +17,9 @@ struct CommandMessage {
|
|||||||
} command) {
|
} command) {
|
||||||
case HELLO:
|
case HELLO:
|
||||||
u32 version;
|
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:
|
case PING:
|
||||||
void;
|
void;
|
||||||
case ACTIVE:
|
case ACTIVE:
|
||||||
@ -51,6 +56,15 @@ struct EventMessage {
|
|||||||
void;
|
void;
|
||||||
case BUFFER_UPDATE:
|
case BUFFER_UPDATE:
|
||||||
string buffer_name;
|
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:
|
case BUFFER_RENAME:
|
||||||
string buffer_name;
|
string buffer_name;
|
||||||
string new;
|
string new;
|
||||||
@ -60,6 +74,7 @@ struct EventMessage {
|
|||||||
string buffer_name;
|
string buffer_name;
|
||||||
case BUFFER_LINE:
|
case BUFFER_LINE:
|
||||||
string buffer_name;
|
string buffer_name;
|
||||||
|
// Whether the line should also be displayed in the active buffer.
|
||||||
bool leak_to_active;
|
bool leak_to_active;
|
||||||
bool is_unimportant;
|
bool is_unimportant;
|
||||||
bool is_highlight;
|
bool is_highlight;
|
||||||
@ -106,7 +121,8 @@ struct EventMessage {
|
|||||||
case BUFFER_CLEAR:
|
case BUFFER_CLEAR:
|
||||||
string buffer_name;
|
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:
|
case ERROR:
|
||||||
u32 command_seq;
|
u32 command_seq;
|
||||||
string error;
|
string error;
|
||||||
|
13
xC.c
13
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;
|
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
|
||||||
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
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
|
static void
|
||||||
@ -5015,8 +5020,16 @@ buffer_merge (struct app_context *ctx,
|
|||||||
buffer->lines_count += n;
|
buffer->lines_count += n;
|
||||||
|
|
||||||
// And since there is no log_*() call, send them to relays manually
|
// And since there is no log_*() call, send them to relays manually
|
||||||
|
buffer->highlighted |= merged->highlighted;
|
||||||
LIST_FOR_EACH (struct buffer_line, line, start)
|
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_prepare_buffer_line (ctx, buffer, line, false);
|
||||||
relay_broadcast (ctx);
|
relay_broadcast (ctx);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,13 @@ body {
|
|||||||
padding: .05rem .3rem;
|
padding: .05rem .3rem;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
.item.active {
|
.item.highlighted {
|
||||||
|
color: #ff5f00;
|
||||||
|
}
|
||||||
|
.item.activity {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.item.current {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
@ -97,6 +103,11 @@ body {
|
|||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
.unread {
|
||||||
|
height: 1px;
|
||||||
|
grid-column: span 2;
|
||||||
|
background: #ff5f00;
|
||||||
|
}
|
||||||
.time {
|
.time {
|
||||||
padding: .1rem .3rem;
|
padding: .1rem .3rem;
|
||||||
background: #f8f8f8;
|
background: #f8f8f8;
|
||||||
|
@ -156,10 +156,13 @@ rpc.addEventListener('close', event => {
|
|||||||
rpc.addEventListener('BufferUpdate', event => {
|
rpc.addEventListener('BufferUpdate', event => {
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
let e = event.detail, b = buffers.get(e.bufferName)
|
||||||
if (b === undefined) {
|
if (b === undefined) {
|
||||||
b = {lines: []}
|
buffers.set(e.bufferName, {
|
||||||
buffers.set(e.bufferName, b)
|
lines: [],
|
||||||
|
newMessages: e.newMessages,
|
||||||
|
newUnimportantMessages: e.newUnimportantMessages,
|
||||||
|
highlighted: e.highlighted,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
// TODO: Update any buffer properties.
|
|
||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferRename', event => {
|
rpc.addEventListener('BufferRename', event => {
|
||||||
@ -174,8 +177,14 @@ rpc.addEventListener('BufferRemove', event => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferActivate', event => {
|
rpc.addEventListener('BufferActivate', event => {
|
||||||
let e = event.detail, b = buffers.get(e.bufferName)
|
|
||||||
let old = buffers.get(bufferCurrent)
|
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
|
bufferCurrent = e.bufferName
|
||||||
bufferLog = undefined
|
bufferLog = undefined
|
||||||
bufferAutoscroll = true
|
bufferAutoscroll = true
|
||||||
@ -195,12 +204,41 @@ rpc.addEventListener('BufferActivate', event => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
rpc.addEventListener('BufferLine', event => {
|
rpc.addEventListener('BufferLine', event => {
|
||||||
let e = event.detail, b = buffers.get(e.bufferName),
|
let e = event.detail, b = buffers.get(e.bufferName), line = {...e}
|
||||||
line = {when: e.when, rendition: e.rendition, items: e.items}
|
delete line.event
|
||||||
if (b !== undefined)
|
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})
|
b.lines.push({...line})
|
||||||
if (e.leakToActive && (b = buffers.get(bufferCurrent)) !== undefined)
|
if (!visible || b.newMessages || b.newUnimportantMessages) {
|
||||||
b.lines.push({leaked: true, ...line})
|
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 => {
|
rpc.addEventListener('BufferClear', event => {
|
||||||
@ -267,10 +305,21 @@ let BufferList = {
|
|||||||
|
|
||||||
view: vnode => {
|
view: vnode => {
|
||||||
let items = Array.from(buffers, ([name, b]) => {
|
let items = Array.from(buffers, ([name, b]) => {
|
||||||
let attrs = {onclick: event => BufferList.activate(name)}
|
let classes = [], displayName = name
|
||||||
if (name == bufferCurrent)
|
if (name == bufferCurrent) {
|
||||||
attrs.class = 'active'
|
classes.push('current')
|
||||||
return m('.item', attrs, name)
|
} 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)
|
return m('.list', {}, items)
|
||||||
},
|
},
|
||||||
@ -387,7 +436,9 @@ let Buffer = {
|
|||||||
return
|
return
|
||||||
|
|
||||||
let lastDateMark = undefined
|
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 date = new Date(line.when)
|
||||||
let dateMark = date.toLocaleDateString()
|
let dateMark = date.toLocaleDateString()
|
||||||
if (dateMark !== lastDateMark) {
|
if (dateMark !== lastDateMark) {
|
||||||
@ -395,16 +446,20 @@ let Buffer = {
|
|||||||
lastDateMark = dateMark
|
lastDateMark = dateMark
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i == markBefore)
|
||||||
|
lines.push(m('.unread'))
|
||||||
|
|
||||||
let attrs = {}
|
let attrs = {}
|
||||||
if (line.leaked)
|
if (line.leaked)
|
||||||
attrs.class = 'leaked'
|
attrs.class = 'leaked'
|
||||||
|
|
||||||
|
// TODO: Make use of isUnimportant.
|
||||||
lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))
|
lines.push(m('.time', {...attrs}, date.toLocaleTimeString()))
|
||||||
lines.push(m(Content, {...attrs}, line))
|
lines.push(m(Content, {...attrs}, line))
|
||||||
})
|
})
|
||||||
|
|
||||||
let dateMark = new Date().toLocaleDateString()
|
let dateMark = new Date().toLocaleDateString()
|
||||||
if (dateMark !== lastDateMark)
|
if (dateMark !== lastDateMark && lastDateMark !== undefined)
|
||||||
lines.push(m('.date', {}, dateMark))
|
lines.push(m('.date', {}, dateMark))
|
||||||
return m('.buffer', {}, lines)
|
return m('.buffer', {}, lines)
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user