Skip to content

Commit

Permalink
Frontend work to display typing notifications.
Browse files Browse the repository at this point in the history
Display/stop displaying "X is typing" notification
on receiving start/stop typing event.
  • Loading branch information
chdinesh1089 committed Mar 14, 2021
1 parent 0fd1400 commit 07ced24
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 32 deletions.
36 changes: 34 additions & 2 deletions frontend_tests/node_tests/dispatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ run_test("typing", (override) => {
let event = event_fixtures.typing__start;
{
const stub = make_stub();
override(typing_events, "display_notification", stub.f);
override(typing_events, "display_pms_notification", stub.f);
dispatch(event);
assert.equal(stub.num_calls, 1);
const args = stub.get_args("event");
Expand All @@ -587,7 +587,7 @@ run_test("typing", (override) => {
event = event_fixtures.typing__stop;
{
const stub = make_stub();
override(typing_events, "hide_notification", stub.f);
override(typing_events, "hide_pms_notification", stub.f);
dispatch(event);
assert.equal(stub.num_calls, 1);
const args = stub.get_args("event");
Expand All @@ -598,6 +598,38 @@ run_test("typing", (override) => {
page_params.user_id = typing_person1.user_id;
event = event_fixtures.typing__start;
dispatch(event);
page_params.user_id = undefined; // above change effects stream_typing tests below
});

run_test("stream_typing", (override) => {
let event = event_fixtures.stream_typing__start;
{
const stub = make_stub();
override(typing_events, "display_stream_notification", stub.f);
dispatch(event);
assert.equal(stub.num_calls, 1);
const args = stub.get_args("event");
assert_same(args.event.sender.user_id, typing_person1.user_id);
assert_same(args.event.stream_id, events.stream_typing_in_id);
assert_same(args.event.topic, events.topic_typing_in);
}

event = event_fixtures.stream_typing__stop;
{
const stub = make_stub();
override(typing_events, "hide_stream_notification", stub.f);
dispatch(event);
assert.equal(stub.num_calls, 1);
const args = stub.get_args("event");
assert_same(args.event.sender.user_id, typing_person1.user_id);
assert_same(args.event.stream_id, events.stream_typing_in_id);
assert_same(args.event.topic, events.topic_typing_in);
}

// Get line coverage--we ignore our own typing events.
page_params.user_id = typing_person1.user_id;
event = event_fixtures.stream_typing__start;
dispatch(event);
});

run_test("update_display_settings", (override) => {
Expand Down
18 changes: 18 additions & 0 deletions frontend_tests/node_tests/lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const typing_person2 = {

exports.typing_person1 = typing_person1;
exports.typing_person2 = typing_person2;
exports.stream_typing_in_id = 1;
exports.topic_typing_in = "Typing topic";

const fake_then = 1596710000;
const fake_now = 1596713966;
Expand Down Expand Up @@ -581,6 +583,22 @@ exports.fixtures = {
recipients: [typing_person2],
},

stream_typing__start: {
type: "stream_typing",
op: "start",
sender: typing_person1,
stream_id: this.stream_typing_in_id,
topic: this.topic_typing_in,
},

stream_typing__stop: {
type: "stream_typing",
op: "stop",
sender: typing_person1,
stream_id: this.stream_typing_in_id,
topic: this.topic_typing_in,
},

update_display_settings__color_scheme_automatic: {
type: "update_display_settings",
setting_name: "color_scheme",
Expand Down
24 changes: 12 additions & 12 deletions frontend_tests/node_tests/typing_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,43 @@ run_test("basics", () => {
// user ids being in arbitrary sorting order and
// possibly in string form instead of integer. So all
// the apparent randomness in these tests has a purpose.
typing_data.add_typist([5, 10, 15], 15);
typing_data.add_pms_typist([5, 10, 15], 15);
assert.deepEqual(typing_data.get_group_typists([15, 10, 5]), [15]);

// test that you can add twice
typing_data.add_typist([5, 10, 15], 15);
typing_data.add_pms_typist([5, 10, 15], 15);

// add another id to our first group
typing_data.add_typist([5, 10, 15], "10");
typing_data.add_pms_typist([5, 10, 15], "10");
assert.deepEqual(typing_data.get_group_typists([10, 15, 5]), [10, 15]);

// start adding to a new group
typing_data.add_typist([7, 15], 7);
typing_data.add_typist([7, "15"], 15);
typing_data.add_pms_typist([7, 15], 7);
typing_data.add_pms_typist([7, "15"], 15);

// test get_all_typists
assert.deepEqual(typing_data.get_all_typists(), [7, 10, 15]);

// test basic removal
assert(typing_data.remove_typist([15, 7], "7"));
assert(typing_data.remove_pms_typist([15, 7], "7"));
assert.deepEqual(typing_data.get_group_typists([7, 15]), [15]);

// test removing an id that is not there
assert(!typing_data.remove_typist([15, 7], 7));
assert(!typing_data.remove_pms_typist([15, 7], 7));
assert.deepEqual(typing_data.get_group_typists([7, 15]), [15]);
assert.deepEqual(typing_data.get_all_typists(), [10, 15]);

// remove user from one group, but "15" will still be among
// "all typists"
assert(typing_data.remove_typist(["15", 7], "15"));
assert(typing_data.remove_pms_typist(["15", 7], "15"));
assert.deepEqual(typing_data.get_all_typists(), [10, 15]);

// now remove from the other group
assert(typing_data.remove_typist([5, 15, 10], 15));
assert(typing_data.remove_pms_typist([5, 15, 10], 15));
assert.deepEqual(typing_data.get_all_typists(), [10]);

// test duplicate ids in a groups
typing_data.add_typist([20, 40, 20], 20);
typing_data.add_pms_typist([20, 40, 20], 20);
assert.deepEqual(typing_data.get_group_typists([20, 40]), [20]);
});

Expand Down Expand Up @@ -80,12 +80,12 @@ run_test("timers", () => {

function kickstart() {
reset_events();
typing_data.kickstart_inbound_timer(stub_group, stub_delay, stub_f);
typing_data.kickstart_pms_inbound_timer(stub_group, stub_delay, stub_f);
}

function clear() {
reset_events();
typing_data.clear_inbound_timer(stub_group);
typing_data.clear_pms_inbound_timer(stub_group);
}

set_global("setTimeout", set_timeout);
Expand Down
8 changes: 8 additions & 0 deletions static/js/narrow_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ export function stream() {
return undefined;
}

export function stream_id() {
const stream_name = stream();
if (stream_name === undefined) {
return undefined;
}
return stream_data.get_stream_id(stream_name);
}

export function stream_sub() {
if (current_filter === undefined) {
return undefined;
Expand Down
18 changes: 16 additions & 2 deletions static/js/server_events_dispatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,9 +437,23 @@ export function dispatch_normal_event(event) {
}

if (event.op === "start") {
typing_events.display_notification(event);
typing_events.display_pms_notification(event);
} else if (event.op === "stop") {
typing_events.hide_notification(event);
typing_events.hide_pms_notification(event);
}
break;

case "stream_typing":
if (event.sender.user_id === page_params.user_id) {
// typing notifications are sent to the user who is typing
// as well as recipients; we ignore such self-generated events.
return;
}

if (event.op === "start") {
typing_events.display_stream_notification(event);
} else if (event.op === "stop") {
typing_events.hide_stream_notification(event);
}
break;

Expand Down
52 changes: 45 additions & 7 deletions static/js/typing_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ function get_key(group) {
return ids.join(",");
}

export function add_typist(group, typist) {
const key = get_key(group);
function update_typist_dct(key, typist) {
const current = typist_dct.get(key) || [];
typist = to_int(typist);
if (!current.includes(typist)) {
Expand All @@ -26,8 +25,17 @@ export function add_typist(group, typist) {
typist_dct.set(key, util.sorted_ids(current));
}

export function remove_typist(group, typist) {
export function add_pms_typist(group, typist) {
const key = get_key(group);
update_typist_dct(key, typist);
}

export function add_streams_typist(stream_id, topic, typist) {
const key = JSON.stringify({stream_id, topic});
update_typist_dct(key, typist);
}

function remove_typist(key, typist) {
let current = typist_dct.get(key) || [];

typist = to_int(typist);
Expand All @@ -41,6 +49,16 @@ export function remove_typist(group, typist) {
return true;
}

export function remove_pms_typist(group, typist) {
const key = get_key(group);
return remove_typist(key, typist);
}

export function remove_streams_typist(stream_id, topic, typist) {
const key = JSON.stringify({stream_id, topic});
return remove_typist(key, typist);
}

export function get_group_typists(group) {
const key = get_key(group);
return typist_dct.get(key) || [];
Expand All @@ -53,20 +71,40 @@ export function get_all_typists() {
return typists;
}

export function get_stream_typists(stream_id, topic) {
return typist_dct.get(JSON.stringify({stream_id, topic})) || [];
}

// The next functions aren't pure data, but it is easy
// enough to mock the setTimeout/clearTimeout functions.
export function clear_inbound_timer(group) {
const key = get_key(group);
function clear_inbound_timer(key) {
const timer = inbound_timer_dict.get(key);
if (timer) {
clearTimeout(timer);
inbound_timer_dict.set(key, undefined);
}
}

export function kickstart_inbound_timer(group, delay, callback) {
export function clear_pms_inbound_timer(group) {
const key = get_key(group);
clear_inbound_timer(group);
clear_inbound_timer(key);
}

export function clear_streams_inbound_timer(stream_id, topic) {
const key = JSON.stringify({stream_id, topic});
clear_inbound_timer(key);
}

export function kickstart_pms_inbound_timer(group, delay, callback) {
const key = get_key(group);
clear_pms_inbound_timer(group);
const timer = setTimeout(callback, delay);
inbound_timer_dict.set(key, timer);
}

export function kickstart_streams_inbound_timer(stream_id, topic, delay, callback) {
const key = JSON.stringify({stream_id, topic});
clear_streams_inbound_timer(stream_id, topic);
const timer = setTimeout(callback, delay);
inbound_timer_dict.set(key, timer);
}
54 changes: 45 additions & 9 deletions static/js/typing_events.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ const TYPING_STARTED_EXPIRY_PERIOD = 15000; // 15s
// that make typing indicators work.

function get_users_typing_for_narrow() {
if (narrow_state.narrowed_to_topic()) {
return typing_data.get_stream_typists(narrow_state.stream_id(), narrow_state.topic());
}

if (!narrow_state.narrowed_to_pms()) {
// Narrow is neither pm-with nor is: private
// Narrow is neither pm-with nor is: private nor topic
return [];
}

Expand Down Expand Up @@ -57,31 +61,63 @@ export function render_notifications_for_narrow() {
}
}

export function hide_notification(event) {
export function hide_pms_notification(event) {
const recipients = event.recipients.map((user) => user.user_id);
recipients.sort();

typing_data.clear_inbound_timer(recipients);
typing_data.clear_pms_inbound_timer(recipients);

const removed = typing_data.remove_typist(recipients, event.sender.user_id);
const removed = typing_data.remove_pms_typist(recipients, event.sender.user_id);

if (removed) {
render_notifications_for_narrow();
}
}

export function display_notification(event) {
export function display_pms_notification(event) {
const sender_id = event.sender.user_id;
event.sender.name = people.get_by_user_id(sender_id).full_name;

const recipients = event.recipients.map((user) => user.user_id);
recipients.sort();

typing_data.add_pms_typist(recipients, sender_id);

render_notifications_for_narrow();

typing_data.kickstart_pms_inbound_timer(recipients, TYPING_STARTED_EXPIRY_PERIOD, () => {
hide_pms_notification(event);
});
}

export function hide_stream_notification(event) {
typing_data.clear_streams_inbound_timer(event.stream_id, event.topic);

const removed = typing_data.remove_streams_typist(
event.stream_id,
event.topic,
event.sender.user_id,
);

if (removed) {
render_notifications_for_narrow();
}
}

export function display_stream_notification(event) {
const sender_id = event.sender.user_id;
event.sender.name = people.get_by_user_id(sender_id).full_name;

typing_data.add_typist(recipients, sender_id);
typing_data.add_streams_typist(event.stream_id, event.topic, sender_id);

render_notifications_for_narrow();

typing_data.kickstart_inbound_timer(recipients, TYPING_STARTED_EXPIRY_PERIOD, () => {
hide_notification(event);
});
typing_data.kickstart_streams_inbound_timer(
event.stream_id,
event.topic,
TYPING_STARTED_EXPIRY_PERIOD,
() => {
hide_stream_notification(event);
},
);
}

0 comments on commit 07ced24

Please sign in to comment.