websocket update

This commit is contained in:
Yuri Kuznetsov
2020-03-09 16:31:05 +02:00
parent 3fbdff674e
commit 4a71ab5f5d
6 changed files with 190 additions and 2 deletions
@@ -0,0 +1,70 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Hooks\Common;
use Espo\ORM\Entity;
class WebSocketSubmit extends \Espo\Core\Hooks\Base
{
public static $order = 20;
protected function init()
{
$this->addDependency('metadata');
$this->addDependency('webSocketSubmission');
}
protected function getMetadata()
{
return $this->getInjection('metadata');
}
protected function getWebSocketSubmission()
{
return $this->getInjection('webSocketSubmission');
}
public function afterSave(Entity $entity, array $options = [])
{
if ($options['silent'] ?? false) return;
if ($entity->isNew()) return;
if (!$this->getConfig()->get('useWebSocket')) return;
$scope = $entity->getEntityType();
$id = $entity->id;
if (!$this->getMetadata()->get(['scopes', $scope, 'object'])) return;
$data = (object) [];
$topic = "recordUpdate.{$scope}.{$id}";
$this->getInjection('webSocketSubmission')->submit($topic, null, $data);
}
}
@@ -1,6 +1,10 @@
{
"categories": {
"newNotification": {},
"recordUpdate": {
"paramList": ["scope", "id"],
"accessCheckCommand": "AclCheck --userId=:userId --scope=:scope --id=:id --action=read"
},
"streamUpdate": {
"paramList": ["scope", "id"],
"accessCheckCommand": "AclCheck --userId=:userId --scope=:scope --id=:id --action=stream"
+21
View File
@@ -301,6 +301,21 @@ Espo.define('views/fields/base', 'view', function (Dep) {
this.setupSearch();
}
this.on('highlight', function () {
var $cell = this.getCellElement();
$cell.addClass('highlighted');
$cell.addClass('transition');
setTimeout(function () {
$cell.removeClass('highlighted');
}, this.highlightPeriod || 3000);
setTimeout(function () {
$cell.removeClass('transition');
}, (this.highlightPeriod || 3000) + 2000);
}, this);
this.on('invalid', function () {
var $cell = this.getCellElement();
$cell.addClass('has-error');
@@ -356,6 +371,10 @@ Espo.define('views/fields/base', 'view', function (Dep) {
if (changed && !options.skipReRender) {
this.reRender();
}
if (changed && options.highlight) {
this.trigger('highlight');
}
}
}.bind(this));
@@ -595,6 +614,8 @@ Espo.define('views/fields/base', 'view', function (Dep) {
}
this.reRender(true);
this.trigger('after:inline-edit-off');
},
inlineEdit: function () {
+6 -1
View File
@@ -326,6 +326,11 @@ define('views/record/base', ['view', 'view-record-helper', 'dynamic-logic'], fun
},
resetModelChanges: function () {
if (this.updatedAttributes) {
this.attributes = this.updatedAttributes;
this.updatedAttributes = null;
}
var attributes = this.model.attributes;
for (var attr in attributes) {
if (!(attr in this.attributes)) {
@@ -333,7 +338,7 @@ define('views/record/base', ['view', 'view-record-helper', 'dynamic-logic'], fun
}
}
this.model.set(this.attributes);
this.model.set(this.attributes, {skipReRender: true});
},
setModelAttributes: function (setAttributes, options) {
+75 -1
View File
@@ -40,6 +40,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
fieldsMode: 'detail',
mode: 'detail',
gridLayout: null,
detailLayout: null,
@@ -519,6 +521,11 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
this.inlineEditModeIsOn = false;
this.setIsNotChanged();
}, this);
this.listenTo(fieldView, 'after:inline-edit-off', function () {
if (this.updatedAttributes) {
this.resetModelChanges();
}
}, this);
}
},
@@ -652,6 +659,13 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
},
resetModelChanges: function () {
var skipReRender = true;
if (this.updatedAttributes) {
this.attributes = this.updatedAttributes;
this.updatedAttributes = null;
skipReRender = false;
}
var attributes = this.model.attributes;
for (var attr in attributes) {
if (!(attr in this.attributes)) {
@@ -659,7 +673,7 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
}
}
this.model.set(this.attributes, {skipReRender: true});
this.model.set(this.attributes, {skipReRender: skipReRender});
},
delete: function () {
@@ -914,6 +928,20 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
this.$detailButtonContainer = this.$el.find('.detail-button-container');
this.$dropdownItemListButton = this.$detailButtonContainer.find('.dropdown-item-list-button');
}, this);
if (
!this.isNew &&
this.getConfig().get('useWebSocket') &&
this.getMetadata().get(['scopes', this.entityType, 'object'])
) {
this.subscribeToWebSocket();
this.once('remove', function () {
if (this.isSubscribedToWebSocked) {
this.unsubscribeFromWebSocket();
}
}.bind(this));
}
},
setupBeforeFinal: function () {
@@ -1185,10 +1213,15 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
}
this.enableButtons();
this.setIsNotChanged();
setTimeout(function () {
this.unblockUpdateWebSocket();
}.bind(this), this.blockUpdateWebSocketPeriod || 500);
},
beforeSave: function () {
this.notify('Saving...');
this.blockUpdateWebSocket();
},
beforeBeforeSave: function () {
@@ -1718,5 +1751,46 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
this.getRouter().navigate(url, {trigger: true});
},
subscribeToWebSocket: function () {
var topic = 'recordUpdate.' + this.entityType + '.' + this.model.id;
this.recordUpdateWebSocketTopic = topic;
this.isSubscribedToWebSocked = true;
this.getHelper().webSocketManager.subscribe(topic, function (t, data) {
this.handleRecordUpdate();
}.bind(this))
},
unsubscribeFromWebSocket: function () {
if (!this.isSubscribedToWebSocked) return;
this.getHelper().webSocketManager.unsubscribe(this.recordUpdateWebSocketTopic);
},
handleRecordUpdate: function () {
if (this.updateWebSocketIsBlocked) return;
if (this.inlineEditModeIsOn || this.mode == 'edit') {
var m = this.model.clone();
m.fetch().then(
function () {
if (this.inlineEditModeIsOn || this.mode == 'edit') {
this.updatedAttributes = Espo.Utils.cloneDeep(m.attributes);
}
}.bind(this)
);
} else {
this.model.fetch({highlight: true});
}
},
blockUpdateWebSocket: function () {
this.updateWebSocketIsBlocked = true;
},
unblockUpdateWebSocket: function () {
this.updateWebSocketIsBlocked = false;
},
});
});
+14
View File
@@ -770,6 +770,20 @@ table.table > thead th {
margin-bottom: 2px;
}
.cell.highlighted {
> label {
color: @state-warning-text;
}
}
.cell.transition {
> label {
-webkit-transition: color .5s ease;
-o-transition: color .5s ease;
transition: color .5s ease;
}
}
.filter .selectize-input {
min-height: 30px;
}