Ver código fonte

feat: show users that are currently typing on the form

version-14
prssanna 5 anos atrás
pai
commit
1afdc6b992
8 arquivos alterados com 204 adições e 112 exclusões
  1. +5
    -5
      frappe/public/js/frappe/form/sidebar/form_sidebar.js
  2. +87
    -0
      frappe/public/js/frappe/form/sidebar/form_sidebar_users.js
  3. +0
    -80
      frappe/public/js/frappe/form/sidebar/form_viewers.js
  4. +4
    -0
      frappe/public/js/frappe/form/templates/form_sidebar.html
  5. +16
    -1
      frappe/public/js/frappe/socketio_client.js
  6. +8
    -0
      frappe/public/js/frappe/views/communication.js
  7. +7
    -1
      frappe/public/js/frappe/views/formview.js
  8. +77
    -25
      socketio.js

+ 5
- 5
frappe/public/js/frappe/form/sidebar/form_sidebar.js Ver arquivo

@@ -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
- 0
frappe/public/js/frappe/form/sidebar/form_sidebar_users.js Ver arquivo

@@ -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);
}
}

+ 0
- 80
frappe/public/js/frappe/form/sidebar/form_viewers.js Ver arquivo

@@ -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);
}
}

+ 4
- 0
frappe/public/js/frappe/form/templates/form_sidebar.html Ver arquivo

@@ -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>


+ 16
- 1
frappe/public/js/frappe/socketio_client.js Ver arquivo

@@ -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());


+ 8
- 0
frappe/public/js/frappe/views/communication.js Ver arquivo

@@ -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() {


+ 7
- 1
frappe/public/js/frappe/views/formview.js Ver arquivo

@@ -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');
});
}



+ 77
- 25
socketio.js Ver arquivo

@@ -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
});
}
}

Carregando…
Cancelar
Salvar