feat: show users that are currently typing on the form
This commit is contained in:
parent
69fc7812ae
commit
1afdc6b992
8 changed files with 204 additions and 112 deletions
|
|
@ -6,7 +6,7 @@ import './share';
|
|||
import './review';
|
||||
import './document_follow';
|
||||
import './user_image';
|
||||
import './form_viewers';
|
||||
import './form_sidebar_users';
|
||||
|
||||
frappe.ui.form.Sidebar = Class.extend({
|
||||
init: function(opts) {
|
||||
|
|
@ -28,7 +28,7 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
this.make_attachments();
|
||||
this.make_review();
|
||||
this.make_shared();
|
||||
this.make_viewers();
|
||||
this.make_sidebar_users();
|
||||
|
||||
this.make_tags();
|
||||
this.make_like();
|
||||
|
|
@ -177,10 +177,10 @@ frappe.ui.form.Sidebar = Class.extend({
|
|||
parent: this.sidebar.find(".form-shared")
|
||||
});
|
||||
},
|
||||
make_viewers: function() {
|
||||
this.frm.viewers = new frappe.ui.form.Viewers({
|
||||
make_sidebar_users: function() {
|
||||
this.frm.viewers = new frappe.ui.form.SidebarUsers({
|
||||
frm: this.frm,
|
||||
parent: this.sidebar.find(".form-viewers")
|
||||
$wrapper: this.sidebar,
|
||||
});
|
||||
},
|
||||
add_user_action: function(label, click) {
|
||||
|
|
|
|||
87
frappe/public/js/frappe/form/sidebar/form_sidebar_users.js
Normal file
87
frappe/public/js/frappe/form/sidebar/form_sidebar_users.js
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
frappe.ui.form.SidebarUsers = Class.extend({
|
||||
init: function(opts) {
|
||||
$.extend(this, opts);
|
||||
},
|
||||
get_users: function(type) {
|
||||
let docinfo = this.frm.get_docinfo();
|
||||
return docinfo ? docinfo[type] || null: null;
|
||||
},
|
||||
refresh: function(data_updated, type) {
|
||||
this.parent = type == 'viewers'? this.$wrapper.find('.form-viewers'): this.$wrapper.find('.form-typers');
|
||||
this.parent.empty();
|
||||
|
||||
const users = this.get_users(type);
|
||||
users && this.show_in_sidebar(users, type, data_updated);
|
||||
},
|
||||
show_in_sidebar: function(users, type, show_alert) {
|
||||
let sidebar_users = [];
|
||||
let new_users = [];
|
||||
let current_users = [];
|
||||
|
||||
const message = type == 'viewers' ? 'viewing this document': 'typing...';
|
||||
|
||||
users.current.forEach(username => {
|
||||
if (username === frappe.session.user) {
|
||||
// current user
|
||||
return;
|
||||
}
|
||||
|
||||
var user_info = frappe.user_info(username);
|
||||
sidebar_users.push({
|
||||
image: user_info.image,
|
||||
fullname: user_info.fullname,
|
||||
abbr: user_info.abbr,
|
||||
color: user_info.color,
|
||||
title: __("{0} is currently {1}", [user_info.fullname, message])
|
||||
});
|
||||
|
||||
if (users.new.indexOf(username) !== -1) {
|
||||
new_users.push(user_info.fullname);
|
||||
}
|
||||
|
||||
current_users.push(user_info.fullname);
|
||||
});
|
||||
|
||||
if (sidebar_users.length) {
|
||||
this.parent.parent().removeClass('hidden');
|
||||
this.parent.append(frappe.render_template('users_in_sidebar', {'users': sidebar_users}));
|
||||
} else {
|
||||
this.parent.parent().addClass('hidden');
|
||||
}
|
||||
|
||||
// For typers always show the alert
|
||||
// For viewers show the alert to new user viewing this document
|
||||
const alert_users = type == 'viewers' ? new_users : current_users;
|
||||
show_alert && this.show_alert(alert_users, message);
|
||||
},
|
||||
show_alert(users, message) {
|
||||
if (users.length) {
|
||||
if (users.length===1) {
|
||||
frappe.show_alert(__('{0} is currently {1}', [users[0], message]));
|
||||
} else {
|
||||
frappe.show_alert(__('{0} are currently {1}', [frappe.utils.comma_and(users), message]));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.set_users = function(data, type) {
|
||||
const doctype = data.doctype;
|
||||
const docname = data.docname;
|
||||
const docinfo = frappe.model.get_docinfo(doctype, docname);
|
||||
|
||||
const past_users = ((docinfo && docinfo[type]) || {}).past || [];
|
||||
const users = data.users || [];
|
||||
const new_users = users.filter(user => !past_users.includes(user));
|
||||
|
||||
frappe.model.set_docinfo(doctype, docname, type, {
|
||||
past: past_users.concat(new_users),
|
||||
new: new_users,
|
||||
current: users
|
||||
});
|
||||
|
||||
if (cur_frm && cur_frm.doc && cur_frm.doc.doctype===doctype && cur_frm.doc.name==docname) {
|
||||
cur_frm.viewers.refresh(true, type);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
|
||||
|
||||
frappe.ui.form.Viewers = Class.extend({
|
||||
init: function(opts) {
|
||||
$.extend(this, opts);
|
||||
},
|
||||
get_viewers: function() {
|
||||
let docinfo = this.frm.get_docinfo();
|
||||
if (docinfo) {
|
||||
return docinfo.viewers || {};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
refresh: function(data_updated) {
|
||||
this.parent.empty();
|
||||
|
||||
var viewers = this.get_viewers();
|
||||
|
||||
var users = [];
|
||||
var new_users = [];
|
||||
for (var i=0, l=(viewers.current || []).length; i < l; i++) {
|
||||
var username = viewers.current[i];
|
||||
if (username===frappe.session.user) {
|
||||
// current user
|
||||
continue;
|
||||
}
|
||||
|
||||
var user_info = frappe.user_info(username);
|
||||
users.push({
|
||||
image: user_info.image,
|
||||
fullname: user_info.fullname,
|
||||
abbr: user_info.abbr,
|
||||
color: user_info.color,
|
||||
title: __("{0} is currently viewing this document", [user_info.fullname])
|
||||
});
|
||||
|
||||
if (viewers.new.indexOf(username)!==-1) {
|
||||
new_users.push(user_info.fullname);
|
||||
}
|
||||
}
|
||||
|
||||
if (users.length) {
|
||||
this.parent.parent().removeClass("hidden");
|
||||
this.parent.append(frappe.render_template("users_in_sidebar", {"users": users}));
|
||||
} else {
|
||||
this.parent.parent().addClass("hidden");
|
||||
}
|
||||
|
||||
if (data_updated && new_users.length) {
|
||||
// new user viewing this document, who wasn't viewing in the past
|
||||
if (new_users.length===1) {
|
||||
frappe.show_alert(__("{0} is currently viewing this document", [new_users[0]]));
|
||||
} else {
|
||||
frappe.show_alert(__("{0} are currently viewing this document", [frappe.utils.comma_and(new_users)]));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.set_viewers = function(data) {
|
||||
var doctype = data.doctype;
|
||||
var docname = data.docname;
|
||||
var docinfo = frappe.model.get_docinfo(doctype, docname);
|
||||
var past_viewers = ((docinfo && docinfo.viewers) || {}).past || [];
|
||||
var viewers = data.viewers || [];
|
||||
|
||||
var new_viewers = viewers.filter(viewer => !past_viewers.includes(viewer));
|
||||
|
||||
frappe.model.set_docinfo(doctype, docname, "viewers", {
|
||||
past: past_viewers.concat(new_viewers),
|
||||
new: new_viewers,
|
||||
current: viewers
|
||||
});
|
||||
|
||||
if (cur_frm && cur_frm.doc && cur_frm.doc.doctype===doctype && cur_frm.doc.name==docname) {
|
||||
cur_frm.viewers.refresh(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -78,6 +78,10 @@
|
|||
<li class="h6 viewers-label">{%= __("Currently Viewing") %}</li>
|
||||
<li class="form-viewers"></li>
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu">
|
||||
<li class="h6 viewers-label">{%= __("Currently Typing") %}</li>
|
||||
<li class="form-typers"></li>
|
||||
</ul>
|
||||
<ul class="list-unstyled sidebar-menu">
|
||||
<a><li class="auto-repeat-status"><li></a>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -89,6 +89,14 @@ frappe.socketio = {
|
|||
frappe.socketio.doc_close(frm.doctype, frm.docname);
|
||||
});
|
||||
|
||||
$(document).on('form-typing', function(e, frm) {
|
||||
frappe.socketio.form_typing(frm.doctype, frm.docname);
|
||||
});
|
||||
|
||||
$(document).on('form-stopped-typing', function(e, frm) {
|
||||
frappe.socketio.form_stopped_typing(frm.doctype, frm.docname);
|
||||
});
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
if (!cur_frm || cur_frm.is_new()) {
|
||||
return;
|
||||
|
|
@ -162,7 +170,14 @@ frappe.socketio = {
|
|||
// notify that the user has closed this doc
|
||||
frappe.socketio.socket.emit('doc_close', doctype, docname);
|
||||
},
|
||||
|
||||
form_typing: function(doctype, docname) {
|
||||
// notifiy that the user is typing on the doc
|
||||
frappe.socketio.socket.emit('doc_typing', doctype, docname);
|
||||
},
|
||||
form_stopped_typing: function(doctype, docname) {
|
||||
// notifiy that the user has stopped typing
|
||||
frappe.socketio.socket.emit('doc_typing_stopped', doctype, docname);
|
||||
},
|
||||
setup_listeners: function() {
|
||||
frappe.socketio.socket.on('task_status_change', function(data) {
|
||||
frappe.socketio.process_response(data, data.status.toLowerCase());
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
})
|
||||
this.prepare();
|
||||
this.dialog.show();
|
||||
|
||||
if (this.frm) {
|
||||
$(document).trigger('form-typing', [this.frm]);
|
||||
}
|
||||
},
|
||||
|
||||
get_fields: function() {
|
||||
|
|
@ -262,6 +266,10 @@ frappe.views.CommunicationComposer = Class.extend({
|
|||
subject: me.dialog.get_value("subject"),
|
||||
content: me.dialog.get_value("content"),
|
||||
});
|
||||
|
||||
if (me.frm) {
|
||||
$(document).trigger("form-stopped-typing", [me.frm]);
|
||||
}
|
||||
}
|
||||
|
||||
this.dialog.on_page_show = function() {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,13 @@ frappe.views.FormFactory = class FormFactory extends frappe.views.Factory {
|
|||
});
|
||||
|
||||
frappe.realtime.on("doc_viewers", function(data) {
|
||||
frappe.ui.form.set_viewers(data);
|
||||
// set users that currently viewing the form
|
||||
frappe.ui.form.set_users(data, 'viewers');
|
||||
});
|
||||
|
||||
frappe.realtime.on("doc_typers", function(data) {
|
||||
// set users that currently typing on the form
|
||||
frappe.ui.form.set_users(data, 'typers');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
102
socketio.js
102
socketio.js
|
|
@ -141,7 +141,6 @@ io.on('connection', function (socket) {
|
|||
});
|
||||
|
||||
socket.on('doc_open', function (doctype, docname) {
|
||||
// show who is currently viewing the form
|
||||
can_subscribe_doc({
|
||||
socket: socket,
|
||||
sid: sid,
|
||||
|
|
@ -151,11 +150,25 @@ io.on('connection', function (socket) {
|
|||
var room = get_open_doc_room(socket, doctype, docname);
|
||||
socket.join(room);
|
||||
|
||||
send_viewers({
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
});
|
||||
// show who is currently viewing the form
|
||||
send_users(
|
||||
{
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
},
|
||||
'view'
|
||||
);
|
||||
|
||||
// show who is currently typing on the form
|
||||
send_users(
|
||||
{
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
},
|
||||
'type'
|
||||
)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -164,11 +177,44 @@ io.on('connection', function (socket) {
|
|||
// remove this user from the list of 'who is currently viewing the form'
|
||||
var room = get_open_doc_room(socket, doctype, docname);
|
||||
socket.leave(room);
|
||||
send_viewers({
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
});
|
||||
send_users(
|
||||
{
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
},
|
||||
'view'
|
||||
);
|
||||
});
|
||||
|
||||
socket.on('doc_typing', function (doctype, docname) {
|
||||
// show users that are currently typing on the form
|
||||
const room = get_typing_room(socket, doctype, docname);
|
||||
socket.join(room);
|
||||
|
||||
send_users(
|
||||
{
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
},
|
||||
'type'
|
||||
);
|
||||
});
|
||||
|
||||
socket.on('doc_typing_stopped', function (doctype, docname) {
|
||||
// remove this user from the list of users currently typing on the form'
|
||||
const room = get_typing_room(socket, doctype, docname);
|
||||
socket.leave(room);
|
||||
|
||||
send_users(
|
||||
{
|
||||
socket: socket,
|
||||
doctype: doctype,
|
||||
docname: docname,
|
||||
},
|
||||
'type'
|
||||
);
|
||||
});
|
||||
|
||||
socket.on('upload-accept-slice', (data) => {
|
||||
|
|
@ -246,6 +292,10 @@ function get_open_doc_room(socket, doctype, docname) {
|
|||
return get_site_name(socket) + ':open_doc:' + doctype + '/' + docname;
|
||||
}
|
||||
|
||||
function get_typing_room(socket, doctype, docname) {
|
||||
return get_site_name(socket) + ':typing:' + doctype + '/' + docname;
|
||||
}
|
||||
|
||||
function get_user_room(socket, user) {
|
||||
return get_site_name(socket) + ':user:' + user;
|
||||
}
|
||||
|
|
@ -325,36 +375,38 @@ function can_subscribe_doc(args) {
|
|||
});
|
||||
}
|
||||
|
||||
function send_viewers(args) {
|
||||
// send to doc room, 'users currently viewing this document'
|
||||
|
||||
function send_users(args, action) {
|
||||
if (!(args && args.doctype && args.docname)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// open doc room
|
||||
var room = get_open_doc_room(args.socket, args.doctype, args.docname);
|
||||
const open_doc_room = get_open_doc_room(args.socket, args.doctype, args.docname);
|
||||
|
||||
var socketio_room = io.sockets.adapter.rooms[room] || {};
|
||||
const room = action == 'view' ? open_doc_room: get_typing_room(args.socket, args.doctype, args.docname);
|
||||
|
||||
const socketio_room = io.sockets.adapter.rooms[room] || {};
|
||||
// for compatibility with both v1.3.7 and 1.4.4
|
||||
var clients_dict = ("sockets" in socketio_room) ? socketio_room.sockets : socketio_room;
|
||||
const clients_dict = ('sockets' in socketio_room) ? socketio_room.sockets : socketio_room;
|
||||
|
||||
// socket ids connected to this room
|
||||
var clients = Object.keys(clients_dict || {});
|
||||
const clients = Object.keys(clients_dict || {});
|
||||
|
||||
var viewers = [];
|
||||
for (var i in io.sockets.sockets) {
|
||||
var s = io.sockets.sockets[i];
|
||||
let users = [];
|
||||
for (let i in io.sockets.sockets) {
|
||||
const s = io.sockets.sockets[i];
|
||||
if (clients.indexOf(s.id) !== -1) {
|
||||
// this socket is connected to the room
|
||||
viewers.push(s.user);
|
||||
users.push(s.user);
|
||||
}
|
||||
}
|
||||
|
||||
const emit_event = action == 'view' ? 'doc_viewers' : 'doc_typers';
|
||||
|
||||
// notify
|
||||
io.to(room).emit("doc_viewers", {
|
||||
io.to(open_doc_room).emit(emit_event, {
|
||||
doctype: args.doctype,
|
||||
docname: args.docname,
|
||||
viewers: viewers
|
||||
users: users
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue