W.I.P collab Editor:

- Implement user access to a document
- Implement leave session mechanism for user
- Implement single document network for an user (user be able to open a doc multiple times from same or other instances and get them all synced)
This commit is contained in:
Hadi Nategh 2016-08-17 17:23:22 +02:00
parent 94bc949cfa
commit 307e59e7ab
6 changed files with 139 additions and 48 deletions

View File

@ -40,16 +40,37 @@ class filemanager_collab extends filemanager_collab_bo {
*/ */
function join_session ($es_id) function join_session ($es_id)
{ {
$response = $this->initSession($es_id); $paths = explode('/webdav.php', $es_id);
if (Api\Vfs::check_access($paths[1], Api\Vfs::READABLE))
{
$response = $this->initSession($es_id);
$response['success'] = true;
}
$response += array ( $response += array (
'id' => $GLOBALS['egw_info']['user']['account_id'], 'id' => $GLOBALS['egw_info']['user']['account_id'],
'full_name' => $GLOBALS['egw_info']['user']['account_fullname'], 'full_name' => $GLOBALS['egw_info']['user']['account_fullname']
'success' => true
); );
return $response; return $response;
} }
/**
* This function gets called when user leaves the session
*
* @param string $es_id
* @param string $member_id
*
* @return array returns an array of data as response for client-side
*/
function leave_session ($es_id, $member_id)
{
return array (
'session_id' => $es_id,
'memberid' => $member_id,
'success' => $this->OP_removeMember($es_id, $member_id)
);
}
/** /**
* Polling mechanisim to sysncronise data * Polling mechanisim to sysncronise data
* *
@ -63,11 +84,15 @@ class filemanager_collab extends filemanager_collab_bo {
$response = array(); $response = array();
if (is_array($params)) if (is_array($params))
{ {
$paths = explode('/webdav.php', $params['args']['es_id']);
switch ($params['command']) switch ($params['command'])
{ {
case 'join_session': case 'join_session':
$response = $this->join_session($params['args']['es_id'],$params['args']['user_id']); $response = $this->join_session($params['args']['es_id'],$params['args']['user_id']);
break; break;
case 'leave_session':
$response = $this->leave_session($params['args']['es_id'],$params['args']['member_id']);
break;
case 'sync_ops': case 'sync_ops':
try try
{ {
@ -78,7 +103,7 @@ class filemanager_collab extends filemanager_collab_bo {
{ {
$client_ops = $params['args']['client_ops']? $params['args']['client_ops']: []; $client_ops = $params['args']['client_ops']? $params['args']['client_ops']: [];
$current_seq_head = $this->OP_getHeadSeq($es_id); $current_seq_head = $this->OP_getHeadSeq($es_id);
if ($seq_head == $current_seq_head) { if ($seq_head == $current_seq_head && $this->is_collabAllowed($es_id)) {
if (count($client_ops)>0) if (count($client_ops)>0)
{ {
@ -141,4 +166,26 @@ class filemanager_collab extends filemanager_collab_bo {
return $this->OP_getHeadSeq($es_id); return $this->OP_getHeadSeq($es_id);
} }
/**
*
* @param type $es_id
* @param type $action
*/
function ajax_actions ($es_id, $action)
{
switch ($action)
{
case 'save':
$this->SESSION_Save($es_id);
break;
}
}
function is_collabAllowed ($es_id)
{
$paths = explode('/webdav.php', $es_id);
$allowed = Api\Vfs::check_access($paths[1], Api\Vfs::WRITABLE) &&
!preg_match('/\/api\/js\/webodf\/template.odf$/', $es_id);
return $allowed;
}
} }

View File

@ -80,14 +80,12 @@ class filemanager_collab_bo
$result = self::db2id($this->SESSION_add2Db($es_id)); $result = self::db2id($this->SESSION_add2Db($es_id));
} }
if (is_null($result['member_id'] = $this->MEMBER_getUserMemberId($es_id, $user_id)))
{
$this->MEMBER_add2Db($es_id, $user_id, $color);
$result['member_id'] = $this->MEMBER_getLastMember(); $this->MEMBER_add2Db($es_id, $user_id, $color);
$this->OP_addMember($es_id, $result['member_id'], $full_name, $user_id, $color, $imageUrl); $result['member_id'] = $this->MEMBER_getLastMember();
}
$this->OP_addMember($es_id, $result['member_id'], $full_name, $user_id, $color, $imageUrl);
return $result; return $result;
} }
@ -212,6 +210,24 @@ class filemanager_collab_bo
$this->OP_add2Db($op, $es_id); $this->OP_add2Db($op, $es_id);
} }
/**
* Function to remove member from list
*
* @param string $es_id session id
* @param string $member_id membe id
*
* @return boolean returns true if remove member is successful otherwise false
*/
function OP_removeMember ($es_id, $member_id)
{
$op = array (
'optype' => 'RemoveMember',
'memberid' => (string) $member_id,
'timestamp' => self::getTimeStamp()
);
return $this->OP_add2Db($op, $es_id)?true:false;
}
/** /**
* Function to get top head seq for a given session * Function to get top head seq for a given session
* *
@ -302,7 +318,7 @@ class filemanager_collab_bo
'collab_optype' => $op['optype'], 'collab_optype' => $op['optype'],
'collab_opspec' => json_encode($op) 'collab_opspec' => json_encode($op)
); );
$this->db->insert(self::OP_TABLE, $data,false,__LINE__, __FILE__,'filemanager'); return $this->db->insert(self::OP_TABLE, $data,false,__LINE__, __FILE__,'filemanager');
} }
/** /**
@ -344,6 +360,30 @@ class filemanager_collab_bo
return is_array($member)? $member['collab_member_id']: null; return is_array($member)? $member['collab_member_id']: null;
} }
/**
* Function to get member record of specific member id
*
* @param string $member_id member id
*
* @return string member id or null
* @throws Exception throws exception if no member id is given
*/
protected function MEMBER_getUserLastMemberId ($es_id, $user_id)
{
if (!$es_id || !$user_id) throw new Exception (self::EXCEPTION_MESSAGE_NO_SESSION);
$query = $this->db->select(
self::MEMBER_TABLE,
'collab_member_id',
array('collab_es_id' => $es_id, 'collab_uid' => $user_id),
__LINE__,
__FILE__,
false,
"ORDER BY `collab_member_id` DESC LIMIT 1;"
);
$member = $query->fetchRow();
return is_array($member)? $member['collab_member_id']: null;
}
/** /**
* Utility function to map DB fields to ids * Utility function to map DB fields to ids
* *

View File

@ -17,7 +17,7 @@ use EGroupware\Api\Egw;
use EGroupware\Api\Etemplate; use EGroupware\Api\Etemplate;
use EGroupware\Api\Vfs; use EGroupware\Api\Vfs;
use filemanager_collab_bo;
/** /**
* Filemanage user interface class * Filemanage user interface class
*/ */
@ -1485,14 +1485,21 @@ class filemanager_ui
{ {
$tmpl = new Etemplate('filemanager.editor'); $tmpl = new Etemplate('filemanager.editor');
$file_path = $_GET['path']; $file_path = $_GET['path'];
$paths = explode('/webdav.php', $file_path);
// Include css files used by wodocollabeditor // Include css files used by wodocollabeditor
Api\Framework::includeCSS('/api/js/webodf/collab/app/resources/app.css'); Api\Framework::includeCSS('/api/js/webodf/collab/app/resources/app.css');
Api\Framework::includeCSS('/api/js/webodf/collab/wodocollabpane.css'); Api\Framework::includeCSS('/api/js/webodf/collab/wodocollabpane.css');
Api\Framework::includeCSS('/api/js/webodf/collab/wodotexteditor.css'); Api\Framework::includeCSS('/api/js/webodf/collab/wodotexteditor.css');
Api\Framework::includeJS('/filemanager/js/collab.js',null, 'filemanager'); Api\Framework::includeJS('/filemanager/js/collab.js',null, 'filemanager');
$tmpl->setElementAttribute('tools', 'actions', self::getActions_edit()); $actions = self::getActions_edit();
$preserve = $content = array('file_path' => $file_path); if (!Api\Vfs::check_access($paths[1], Api\Vfs::WRITABLE))
{
unset ($actions['save']);
unset ($actions['delete']);
}
$tmpl->setElementAttribute('tools', 'actions', $actions);
$preserve = $content = array('file_path' => $file_path);
$tmpl->exec('filemanager.filemanager_ui.editor',$content,array(),array(),$preserve,2); $tmpl->exec('filemanager.filemanager_ui.editor',$content,array(),array(),$preserve,2);
} }

View File

@ -1016,7 +1016,7 @@ app.classes.filemanager = AppJS.extend(
* *
* @todo: creating new empty odt file * @todo: creating new empty odt file
*/ */
list_editor_new: function (_egwAction) { editor_new: function (_egwAction) {
var self = this, var self = this,
template_url = '/api/js/webodf/template.odt'; template_url = '/api/js/webodf/template.odt';
egw.open_link(egw.link('/index.php', { egw.open_link(egw.link('/index.php', {

View File

@ -8,6 +8,7 @@
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$ * @version $Id$
*/ */
/*egw:uses /*egw:uses
/filemanager/js/collab_config.js; /filemanager/js/collab_config.js;
/api/js/webodf/collab/dojo-amalgamation.js; /api/js/webodf/collab/dojo-amalgamation.js;
@ -23,12 +24,20 @@
*/ */
app.classes.filemanager = app.classes.filemanager.extend({ app.classes.filemanager = app.classes.filemanager.extend({
/* /*
* odf editor object * @var editor odf editor object
*/ */
editor: {}, editor: {},
/**
* @var regexp for acceptable mime types
*/
editor_mime: RegExp(/application\/vnd\.oasis\.opendocument\.text/), editor_mime: RegExp(/application\/vnd\.oasis\.opendocument\.text/),
/**
* @var collab_server server object
*/
collab_server: {},
/** /**
* Destructor * Destructor
*/ */
@ -36,6 +45,7 @@ app.classes.filemanager = app.classes.filemanager.extend({
{ {
delete this.editor; delete this.editor;
delete editor_mime; delete editor_mime;
delete collab_server;
// call parent // call parent
this._super.apply(this, arguments); this._super.apply(this, arguments);
}, },
@ -58,7 +68,7 @@ app.classes.filemanager = app.classes.filemanager.extend({
// need to make body rock solid to avoid extra scrollbars // need to make body rock solid to avoid extra scrollbars
jQuery('body').css({overflow:'hidden'}); jQuery('body').css({overflow:'hidden'});
var self = this; var self = this;
jQuery(window).on('unload', function(){self.editor.leaveSession()}); jQuery(window).on('unload', function(){self.editor_leaveSession()});
this._init_odf_collab_editor (); this._init_odf_collab_editor ();
} }
}, },
@ -136,6 +146,16 @@ app.classes.filemanager = app.classes.filemanager.extend({
} }
}, },
/**
* Function to leave the current editing session
* and as result it will call client-side and server leave session.
*/
editor_leaveSession: function ()
{
this.editor.leaveSession(function(){});
this.collab_server.server.leaveSession(this.collab_server.es_id, this.collab_server.memberid);
},
/** /**
* Method to close an opened document * Method to close an opened document
* *
@ -151,7 +171,7 @@ app.classes.filemanager = app.classes.filemanager.extend({
{ {
var closeFn = function () var closeFn = function ()
{ {
self.editor.leaveSession(function(){}); self.editor_leaveSession();
if (action != 'new') if (action != 'new')
{ {
window.close(); window.close();
@ -184,33 +204,6 @@ app.classes.filemanager = app.classes.filemanager.extend({
} }
}, },
/**
* Method to create a new document
* @param {object} _egwAction egw action object
*
* @todo: creating new empty odt file
*/
editor_new: function (_egwAction) {
var self = this,
template_url = '/api/js/webodf/template.odt';
if (Object.keys(this.editor).length > 0)
{
this.editor_close(_egwAction, function(){
// TODO create new temp file
});
}
else
{
egw.open_link(egw.link('/index.php', {
menuaction: 'filemanager.filemanager_ui.editor',
path: template_url
}), '', egw.link_get_registry('filemanager','view_popup'));
}
},
/** /**
* Method call for saving edited document * Method call for saving edited document
* *
@ -239,6 +232,7 @@ app.classes.filemanager = app.classes.filemanager.extend({
success: function(data) { success: function(data) {
egw(window).message(egw.lang('Document %1 successfully has been saved.', filename[1])); egw(window).message(egw.lang('Document %1 successfully has been saved.', filename[1]));
self.editor.setDocumentModified(false); self.editor.setDocumentModified(false);
egw.json('filemanager.filemanager_collab.ajax_actions',[self.editor_getSessionId(), 'save']).sendRequest();
}, },
error: function () {}, error: function () {},
data: blob, data: blob,
@ -270,7 +264,7 @@ app.classes.filemanager = app.classes.filemanager.extend({
// Add odt extension if not exist // Add odt extension if not exist
if (!file_path.match(/\.odt$/,'ig')) file_path += '.odt'; if (!file_path.match(/\.odt$/,'ig')) file_path += '.odt';
widgetFilePath.set_value(file_path); widgetFilePath.set_value(file_path);
self.editor.leaveSession(function(){}); self.editor_leaveSession();
self.editor.getDocumentAsByteArray(saveByteArrayLocally); self.editor.getDocumentAsByteArray(saveByteArrayLocally);
self._init_odf_collab_editor(); self._init_odf_collab_editor();
egw.refresh('','filemanager'); egw.refresh('','filemanager');
@ -429,8 +423,12 @@ app.classes.filemanager = app.classes.filemanager.extend({
function joinSession(_sessionId) function joinSession(_sessionId)
{ {
var sid = _sessionId; var sid = _sessionId;
server.joinSession(userId, sid, function (_memberId) { server.joinSession(userId, sid, function (_memberId) {
memberId = _memberId; memberId = _memberId;
// Set server object for current session
self.collab_server = {server:server, memberid: memberId, es_id: sid};
if (Object.keys(self.editor).length == 0) { if (Object.keys(self.editor).length == 0) {
Wodo.createCollabTextEditor('filemanager-editor_odfEditor', editorOptions, onEditorCreated); Wodo.createCollabTextEditor('filemanager-editor_odfEditor', editorOptions, onEditorCreated);
} else { } else {
@ -459,6 +457,5 @@ app.classes.filemanager = app.classes.filemanager.extend({
}); });
}); });
} }
}); });

View File

@ -54,7 +54,7 @@
<buttononly statustext="Tile view" id="button[change_view]" onclick="app.filemanager.change_view" image="list_tile" background_image="true"/> <buttononly statustext="Tile view" id="button[change_view]" onclick="app.filemanager.change_view" image="list_tile" background_image="true"/>
</template> </template>
<template id="filemanager.index.header_right" template="" lang="" group="0" version="1.9.003"> <template id="filemanager.index.header_right" template="" lang="" group="0" version="1.9.003">
<buttononly statustext="Create new open document file" id="button[new_doc]" onclick="app.filemanager.list_editor_new();" image="new" background_image="true"/> <buttononly statustext="Create new open document file" id="button[new_doc]" onclick="app.filemanager.editor_new();" image="new" background_image="true"/>
<buttononly statustext="Rename, change permissions or ownership" id="button[edit]" onclick="app.filemanager.editprefs();" image="edit" background_image="true"/> <buttononly statustext="Rename, change permissions or ownership" id="button[edit]" onclick="app.filemanager.editprefs();" image="edit" background_image="true"/>
<buttononly statustext="Create directory" id="button[createdir]" onclick="app.filemanager.createdir();" image="button_createdir" ro_image="createdir_disabled" background_image="true"/> <buttononly statustext="Create directory" id="button[createdir]" onclick="app.filemanager.createdir();" image="button_createdir" ro_image="createdir_disabled" background_image="true"/>
<buttononly statustext="Create a link" id="button[symlink]" onclick="app.filemanager.symlink();" image="link" ro_image="link_disabled" background_image="true"/> <buttononly statustext="Create a link" id="button[symlink]" onclick="app.filemanager.symlink();" image="link" ro_image="link_disabled" background_image="true"/>