[PDF Form] Add Send for signing panel

This commit is contained in:
Alexey Koshelev
2026-03-24 12:07:08 +03:00
parent 260511ad50
commit db38a20908
38 changed files with 919 additions and 10 deletions

View File

@ -403,7 +403,8 @@
_config.editorConfig.canRequestSelectSpreadsheet = _config.events && !!_config.events.onRequestSelectSpreadsheet;
_config.editorConfig.canRequestReferenceSource = _config.events && !!_config.events.onRequestReferenceSource;
_config.editorConfig.canSaveDocumentToBinary = _config.events && !!_config.events.onSaveDocument;
_config.editorConfig.canStartFilling = _config.events && !!_config.events.onRequestStartFilling;
_config.editorConfig.canRequestStartFilling = _config.events && !!_config.events.onRequestStartFilling;
_config.editorConfig.canStartFilling = _config.events && !!_config.events.onStartFilling;
_config.editorConfig.canRequestRefreshFile = _config.events && !!_config.events.onRequestRefreshFile;
_config.editorConfig.canUpdateVersion = _config.events && !!_config.events.onOutdatedVersion;
_config.editorConfig.canRequestFillingStatus = _config.events && !!_config.events.onRequestFillingStatus;

View File

@ -413,8 +413,11 @@ if (window.Common === undefined) {
_postMessage({event:'onSwitchEditorType', data: {type: value, restart: restart}});
},
startFilling: function () {
_postMessage({event:'onStartFilling'});
startFilling: function (roles) {
_postMessage({
event:'onStartFilling',
data: roles
});
},
requestFillingStatus: function (role) {

View File

@ -118,7 +118,8 @@ define([], function () {
Pivot: 10,
Cell: 11,
Slicer: 12,
Form: 13
Form: 13,
SendForSigning: 15
},
importTextType = {
DRM: 0,

View File

@ -402,7 +402,11 @@ define([
}
me.btnStartFill && me.btnStartFill.on('click', function (e) {
Common.NotificationCenter.trigger('forms:request-fill');
if(appConfig.canRequestStartFilling) {
Common.NotificationCenter.trigger('forms:request-fill');
} else {
Common.NotificationCenter.trigger('forms:show-send-for-signing');
}
});
if (me.btnFillStatus) {
@ -1011,7 +1015,7 @@ define([
} else
$html.find('#slot-btn-edit-mode').hide();
if (config.canStartFilling) {
if (config.showStartFillingButton) {
me.btnStartFill = new Common.UI.Button({
cls: 'btn-text-default auto yellow',
caption: config.customization && config.customization.startFillingForm && config.customization.startFillingForm.text ? config.customization.startFillingForm.text : me.textStartFill,

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
<path fill="#000" d="m18.207 12.5-6.5 6.5H9v-2.707l6.5-6.5zM10 16.707V18h1.293l3.66-3.66-1.293-1.293zm4.367-4.367 1.293 1.293 1.133-1.133-1.293-1.293zM16 6.293V9h-1V7h-4V3H5v13h2v1H4V2h7.707zM12 6h2.293L12 3.707z"/>
<path fill="#000" d="M10 13H7v-1h3zm3-3H7V9h6z"/>
</svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20">
<path fill="#000" d="M12.5 12a3.5 3.5 0 0 1 3.5 3.5V17H4v-1.5A3.5 3.5 0 0 1 7.5 12zm-5 1A2.5 2.5 0 0 0 5 15.5v.5h10v-.5a2.5 2.5 0 0 0-2.5-2.5zM10 3a4 4 0 1 1 0 8 4 4 0 0 1 0-8m0 1a3 3 0 1 0 0 6 3 3 0 0 0 0-6"/>
</svg>

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="none">
<path d="m17.5 8.5-4-4m4 4h-3.9a.1.1 0 0 1-.1-.1V4.5m4 4v2m-4-6h-6a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h1m9.16-2.16 1.133-1.133a1 1 0 0 0 0-1.414l-.586-.586a1 1 0 0 0-1.414 0L15.66 14.34m2 2L13.5 20.5h-2v-2l4.16-4.16m2 2-2-2M9.5 11.5h5m-5 3h2" class="icon-stroke-gray-primary"/>
</svg>

After

Width:  |  Height:  |  Size: 449 B

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" fill="none">
<path d="M16.5 18.5h-9a1 1 0 0 1-1-1 3 3 0 0 1 3-3h5a3 3 0 0 1 3 3 1 1 0 0 1-1 1Z" class="icon-stroke-gray-primary"/>
<path d="M15.5 9a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z" class="icon-stroke-gray-primary"/>
</svg>

After

Width:  |  Height:  |  Size: 388 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

View File

@ -90,6 +90,7 @@ define([
Common.NotificationCenter.on('forms:close-help', _.bind(this.closeHelpTip, this));
Common.NotificationCenter.on('forms:show-help', _.bind(this.showHelpTip, this));
Common.NotificationCenter.on('forms:request-fill', _.bind(this.requestStartFilling, this));
Common.NotificationCenter.on('forms:show-send-for-signing', _.bind(this.showSendForSigning, this));
Common.NotificationCenter.on('document:ready', _.bind(this.onDocumentReady, this));
return this;
},
@ -724,6 +725,11 @@ define([
Common.Gateway.requestStartFilling(arr);
},
showSendForSigning: function() {
const rightmenuController = this.getApplication().getController('RightMenu');
rightmenuController.openSendForSigning();
},
onActiveTab: function(tab) {
(tab !== 'forms') && this.onTabCollapse();
},

View File

@ -1776,7 +1776,9 @@ define([
this.appOptions.canSwitchMode = this.appOptions.isEdit;
this.appOptions.canSubmitForms = this.appOptions.isRestrictedEdit && this.appOptions.canFillForms && this.appOptions.canLicense && !this.appOptions.isOffline && (typeof (this.editorConfig.customization) == 'object') &&
!!this.editorConfig.customization.submitForm && (typeof this.editorConfig.customization.submitForm !== 'object' || this.editorConfig.customization.submitForm.visible!==false);
this.appOptions.canStartFilling = this.editorConfig.canStartFilling && this.appOptions.isEdit && this.appOptions.isPDFForm; // show Start Filling button in the header
this.appOptions.canStartFilling = this.editorConfig.canStartFilling && this.appOptions.isEdit && this.appOptions.isPDFForm;
this.appOptions.canRequestStartFilling = this.editorConfig.canRequestStartFilling && this.appOptions.isEdit && this.appOptions.isPDFForm;
this.appOptions.showStartFillingButton = this.appOptions.canStartFilling || this.appOptions.canRequestStartFilling; // show Start Filling button in the header
this.appOptions.compactHeader = this.appOptions.customization && (typeof (this.appOptions.customization) == 'object') && !!this.appOptions.customization.compactHeader;
this.appOptions.twoLevelHeader = this.appOptions.isEdit || this.appOptions.isPDFForm && this.appOptions.canFillForms && this.appOptions.isRestrictedEdit; // when compactHeader=true some buttons move to toolbar
@ -1983,8 +1985,9 @@ define([
(!inViewMode || force) && Common.NotificationCenter.trigger('doc:mode-changed', mode);
},
onStartFilling: function(disconnect) {
onStartFilling: function(disconnect, roles) {
this._isFillInitiator = true;
this._rolesForFilling = roles;
this.api.asc_CompletePreparingOForm(!!disconnect);
!disconnect && this.onDisconnectEveryone(); // disable editing only for current user
},
@ -1999,7 +2002,7 @@ define([
},
onCompletePreparingOForm: function() {
Common.Gateway.startFilling();
Common.Gateway.startFilling(this._rolesForFilling);
},
applyModeCommonElements: function() {

View File

@ -100,6 +100,8 @@ define([
this._settings[Common.Utils.documentSettingsType.MailMerge] = {panelId: "id-mail-merge-settings", panel: rightMenu.mergeSettings, btn: rightMenu.btnMailMerge, hidden: 1, props: {}, locked: false};
this._settings[Common.Utils.documentSettingsType.Signature] = {panelId: "id-signature-settings", panel: rightMenu.signatureSettings, btn: rightMenu.btnSignature, hidden: 1, props: {}, locked: false};
this._settings[Common.Utils.documentSettingsType.Form] = {panelId: "id-form-settings", panel: rightMenu.formSettings, btn: rightMenu.btnForm, hidden: 1, props: {}, locked: false};
// this._settings[Common.Utils.documentSettingsType.FillingStatus] = {panelId: "id-filling-status-settings", panel: rightMenu.fillingStatusSettings, btn: rightMenu.btnFillingStatus, hidden: 1, props: {}, locked: false};
this._settings[Common.Utils.documentSettingsType.SendForSigning] = {panelId: "id-send-for-signing-settings", panel: rightMenu.sendForSigningSettings, btn: rightMenu.btnSendForSigning, hidden: 1, props: {}, locked: false};
},
setApi: function(api) {
@ -359,6 +361,8 @@ define([
this._settings[Common.Utils.documentSettingsType.Image].needShow = false;
this._settings[Common.Utils.documentSettingsType.Chart].needShow = false;
this.rightmenu.btnSendForSigning && this.rightmenu.btnSendForSigning.setDisabled(false);
},
onCoAuthoringDisconnect: function() {
@ -504,6 +508,8 @@ define([
disabled && this.rightmenu.btnMailMerge.setDisabled(disabled);
}
this.rightmenu.chartSettings.disableControls(disabled);
this.rightmenu.fillingStatusSettings && this.rightmenu.fillingStatusSettings.disableControls(disabled);
this.rightmenu.sendForSigningSettings && this.rightmenu.sendForSigningSettings.disableControls(disabled);
if (this.rightmenu.signatureSettings) {
!allowSignature && this.rightmenu.btnSignature.setDisabled(disabled);
@ -519,6 +525,8 @@ define([
this.rightmenu.btnTextArt.setDisabled(disabled);
this.rightmenu.btnChart.setDisabled(disabled);
this.rightmenu.btnForm && this.rightmenu.btnForm.setDisabled(disabled);
this.rightmenu.btnFillingStatus && this.rightmenu.btnFillingStatus.setDisabled(disabled);
this.rightmenu.btnSendForSigning && this.rightmenu.btnSendForSigning.setDisabled(disabled);
this.rightmenu.setDisabledAllMoreMenuItems(disabled);
} else {
var selectedElements = this.api.getSelectedElements();
@ -610,6 +618,20 @@ define([
this.rightmenu.fireEvent('editcomplete', this.rightmenu);
},
openSendForSigning: function() {
const btnSendForSigning = this.rightmenu.btnSendForSigning;
if(!btnSendForSigning.isActive()) {
btnSendForSigning.click();
}
},
closeSendForSigning: function() {
const btnSendForSigning = this.rightmenu.btnSendForSigning;
if(btnSendForSigning.isActive()) {
btnSendForSigning.click();
}
},
onHidePlugins: function() {
Common.NotificationCenter.trigger('layout:changed', 'rightmenu');
},

View File

@ -0,0 +1,48 @@
<table cols="2" role="presentation">
<tr>
<td>
<label class="header padding-small"><%= scope.txtTitle %></label>
</td>
</tr>
<tr>
<td class="progress-list">
<% _.each(items, function(item, index) { %>
<div class="progress-item <%= item.status %>">
<div class="progress-item-left">
<div class="progress-item-number"><%= index + 1 %></div>
<div class="progress-item-dashes"></div>
</div>
<div class="progress-item-content">
<div class="progress-item-header">
<div class="progress-item-label"><%= item.name %></div>
<% if (item.status == 'done') { %>
<div class="progress-item-check"></div>
<% } %>
</div>
<div class="progress-item-description"><%= item.email %></div>
<% if (item.status == 'done') { %>
<div class="progress-item-footer">
<div class="progress-item-status">The document is signed</div>
<% if (item.date) { %>
<div class="progress-item-date"><%= item.date %></div>
<% } %>
</div>
<% } %>
</div>
</div>
<% }) %>
<div class="progress-item complete <%= isAllDone ? 'all-done' : '' %>">
<div class="progress-item-left">
<div class="progress-item-number">
<div class="progress-item-check"></div>
</div>
</div>
<div class="progress-item-content">
<div class="progress-item-header">
<div class="progress-item-label">Complete</div>
</div>
</div>
</div>
</td>
</tr>
</table>

View File

@ -10,6 +10,8 @@
<div id="id-textart-settings" class="settings-panel"></div>
<div id="id-signature-settings" class="settings-panel"></div>
<div id="id-form-settings" class="settings-panel"></div>
<div id="id-filling-status-settings" class="settings-panel"></div>
<div id="id-send-for-signing-settings" class="settings-panel"></div>
</div>
</div>
<div class="tool-menu-btns" role="menubar" aria-label="<%= scope.ariaRightMenu %>" aria-orientation="vertical">
@ -23,6 +25,8 @@
<button id="id-right-menu-mail-merge" class="btn btn-category arrow-left hidden" data-hint="0" data-hint-direction="left" data-hint-offset="big" content-target="id-mail-merge-settings"><i class="icon toolbar__icon btn-mailmerge">&nbsp;</i></button>
<button id="id-right-menu-signature" class="btn btn-category arrow-left hidden" data-hint="0" data-hint-direction="left" data-hint-offset="big" content-target="id-signature-settings"><i class="icon toolbar__icon btn-menu-signature">&nbsp;</i></button>
<button id="id-right-menu-form" class="btn btn-category arrow-left hidden" data-hint="0" data-hint-direction="left" data-hint-offset="big" content-target="id-form-settings"><i class="icon toolbar__icon btn-field">&nbsp;</i></button>
<button id="id-right-menu-filling-status" class="btn btn-category arrow-left hidden" data-hint="0" data-hint-direction="left" data-hint-offset="big" content-target="id-filling-status-settings"><i class="icon toolbar__icon btn-field">&nbsp;</i></button>
<button id="id-right-menu-send-for-signing" class="btn btn-category arrow-left hidden" data-hint="0" data-hint-direction="left" data-hint-offset="big" content-target="id-send-for-signing-settings"><i class="icon toolbar__icon btn-menu-send-to-sign">&nbsp;</i></button>
<div id="slot-right-menu-more"></div>
</div>
</div>

View File

@ -0,0 +1,22 @@
<table cols="2" role="presentation">
<tr>
<td class="inner">
<div class="content">
<div class="content-padding">
<label class="header padding-small"><%= scope.txtTitle %></label>
<label class="description padding-large"><%= scope.txtDescription %></label>
</div>
<div class="roles-list-wrapper">
<div id="id-send-for-signing-settings-roles-list" class="content-padding"></div>
</div>
</div>
<div>
<div class="separator horizontal"></div>
<div class="footer">
<button id="id-send-for-signing-settings-send-btn" class="btn normal dlg-btn primary" data-hint="1" data-hint-direction="bottom" data-hint-offset="big"><%=scope.txtSend%></button>
<button id="id-send-for-signing-settings-cancel-btn" class="btn normal dlg-btn" data-hint="1" data-hint-direction="bottom" data-hint-offset="big"><%=scope.txtCancel%></button>
</div>
</div>
</td>
</tr>
</table>

View File

@ -0,0 +1,107 @@
/*
* (c) Copyright Ascensio System SIA 2010-2024
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
/**
* FillingStatusSettings.js
*
* Created on 10.03.2026
*
*/
define([
'text!documenteditor/main/app/template/FillingStatusSettings.template',
'jquery',
'underscore',
'backbone',
'common/main/lib/component/Button'
], function (menuTemplate, $, _, Backbone) {
'use strict';
DE.Views.FillingStatusSettings = Backbone.View.extend(_.extend({
el: '#id-filling-status-settings',
// Compile our stats template
template: _.template(menuTemplate),
// Delegated events for creating new items, and clearing completed ones.
events: {
},
options: {
alias: 'FillingStatusSettings'
},
initialize: function () {
this._state = {};
this._locked = false;
this.render();
},
render: function () {
const items = [
{ email: 'user333@gmail.com', name: 'Anyone', date: '1/20/2026 9:00 PM', status: 'done' },
{ email: 'test@yandex.ru', name: 'User1', date: null, status: 'done' },
{ email: 'test@yandex.ru', name: 'User2', date: null, status: 'done' }
];
const el = this.$el || $(this.el);
el.html(this.template({
scope: this,
items: items,
isAllDone: items.every(function(item) { return item.status == 'done'})
}));
},
setApi: function(api) {
this.api = api;
return this;
},
ChangeSettings: function(props) {
},
setLocked: function (locked) {
this._locked = locked;
},
setMode: function(mode) {
this.mode = mode;
},
disableControls: function(disable) {
},
txtTitle: 'Filling status',
}, DE.Views.FillingStatusSettings || {}));
});

View File

@ -39,6 +39,7 @@
var SCALE_MIN = 40;
var MENU_SCALE_PART = 260;
var MENU_BASE_WIDTH = 220;
// var MENU_BASE_WIDTH = 360;
define([
'text!documenteditor/main/app/template/RightMenu.template',
@ -59,6 +60,8 @@ define([
'documenteditor/main/app/view/TextArtSettings',
'documenteditor/main/app/view/SignatureSettings',
'documenteditor/main/app/view/FormSettings',
'documenteditor/main/app/view/FillingStatusSettings',
'documenteditor/main/app/view/SendForSigningSettings',
'common/main/lib/component/Scroller',
'common/main/lib/component/ListView',
], function (menuTemplate, $, _, Backbone) {
@ -253,6 +256,39 @@ define([
this.formSettings = new DE.Views.FormSettings();
}
if (false) {
this.btnFillingStatus = new Common.UI.Button({
hint: this.txtFormSettings,
asctype: Common.Utils.documentSettingsType.FillingStatus,
enableToggle: true,
disabled: true,
iconCls: 'btn-field',
toggleGroup: 'tabpanelbtnsGroup',
allowMouseEventsOnDisabled: true
});
this._settings[Common.Utils.documentSettingsType.FillingStatus] = {panel: "id-filling-status-settings", btn: this.btnFillingStatus};
this.btnFillingStatus.setElement($markup.findById('#id-right-menu-filling-status'), false);
this.btnFillingStatus.render().setVisible(true);
this.btnFillingStatus.on('click', this.onBtnMenuClick.bind(this));
this.fillingStatusSettings = new DE.Views.FillingStatusSettings();
}
if (!mode.canRequestStartFilling) {
this.btnSendForSigning = new Common.UI.Button({
hint: this.txtSendForSigning,
asctype: Common.Utils.documentSettingsType.SendForSigning,
enableToggle: true,
disabled: false,
iconCls: 'btn-field',
toggleGroup: 'tabpanelbtnsGroup',
allowMouseEventsOnDisabled: true
});
this._settings[Common.Utils.documentSettingsType.SendForSigning] = {panel: "id-send-for-signing-settings", btn: this.btnSendForSigning};
this.btnSendForSigning.setElement($markup.findById('#id-right-menu-send-for-signing'), false);
this.btnSendForSigning.render().setVisible(true);
this.btnSendForSigning.on('click', this.onBtnMenuClick.bind(this));
this.sendForSigningSettings = new DE.Views.SendForSigningSettings();
}
if (_.isUndefined(this.scroller)) {
this.scroller = new Common.UI.Scroller({
@ -289,6 +325,8 @@ define([
if (this.mergeSettings) this.mergeSettings.setApi(api).on('editcomplete', _fire_editcomplete);
if (this.signatureSettings) this.signatureSettings.setApi(api).on('editcomplete', _fire_editcomplete);
if (this.formSettings) this.formSettings.setApi(api).on('editcomplete', _fire_editcomplete).on('updatescroller', _updateScroller);
if (this.fillingStatusSettings) this.fillingStatusSettings.setApi(api).on('editcomplete', _fire_editcomplete).on('updatescroller', _updateScroller);
if (this.sendForSigningSettings) this.sendForSigningSettings.setApi(api).on('editcomplete', _fire_editcomplete).on('updatescroller', _updateScroller);
},
setMode: function(mode) {
@ -300,6 +338,8 @@ define([
this.chartSettings && this.chartSettings.setMode(mode);
// this.headerSettings && this.headerSettings.setMode(mode);
this.signatureSettings && this.signatureSettings.setMode(mode);
this.fillingStatusSettings && this.fillingStatusSettings.setMode(mode);
this.sendForSigningSettings && this.sendForSigningSettings.setMode(mode);
},
onBtnMenuClick: function(btn, e) {
@ -402,7 +442,7 @@ define([
setButtons: function () {
var allButtons = [this.btnText, this.btnTable, this.btnImage, this.btnShape, this.btnChart, this.btnTextArt,
this.btnMailMerge, this.btnSignature, this.btnForm];
this.btnMailMerge, this.btnSignature, this.btnForm, this.btnFillingStatus, this.btnSendForSigning];
Common.UI.SideMenu.prototype.setButtons.apply(this, [allButtons]);
},
@ -434,6 +474,7 @@ define([
txtMailMergeSettings: 'Mail Merge Settings',
txtSignatureSettings: 'Signature Settings',
txtFormSettings: 'Form Settings',
txtSendForSigning: 'Send for signing',
ariaRightMenu: 'Right menu'
}, DE.Views.RightMenu || {}));
});

View File

@ -0,0 +1,302 @@
/*
* (c) Copyright Ascensio System SIA 2010-2024
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
/**
* SendForSigningSettings.js
*
* Created on 10.03.2026
*
*/
define([
'text!documenteditor/main/app/template/SendForSigningSettings.template',
'jquery',
'underscore',
'backbone',
'common/main/lib/component/Button'
], function (menuTemplate, $, _, Backbone) {
'use strict';
DE.Views.SendForSigningSettings = Backbone.View.extend(_.extend({
el: '#id-send-for-signing-settings',
// Compile our stats template
template: _.template(menuTemplate),
// Delegated events for creating new items, and clearing completed ones.
events: {
},
options: {
alias: 'SendForSigningSettings'
},
initialize: function (options) {
this._initSettings = true;
this._state = {
isUsersListLoading: false
};
this._locked = false;
this.users = [];
this.render();
// TODO: if (config.canRequestUsers)???
this._state.isUsersListLoading = true;
Common.UI.ExternalUsers.get('filler');
Common.NotificationCenter.on('mentions:setusers', _.bind(this.onUsersListLoad, this));
},
render: function () {
const el = this.$el || $(this.el);
el.html(this.template({
scope: this
}));
},
createDelayedElements: function() {
this._initSettings = false;
const me = this;
const $markup = this.$el || $(this.el);
this._updateRolesListHeight();
this.scrollerOptions = {
el: $markup.find('#id-send-for-signing-settings-roles-list'),
wheelSpeed: 8,
alwaysVisibleY: true
};
this.scroller = new Common.UI.Scroller(this.scrollerOptions);
this.sendBtn = new Common.UI.Button({
el: $markup.find('#id-send-for-signing-settings-send-btn'),
disabled: true
});
this.sendBtn.on('click', _.bind(this.onSend, this));
this.cancelBtn = $markup.find('#id-send-for-signing-settings-cancel-btn');
this.cancelBtn.on('click', _.bind(this.onCancel, this));
const oForm = this.api.asc_GetOForm();
this.rolesCollection = new Backbone.Collection();
this.rolesCollection.on('reset', function(newCollection, details) {
me._renderRolesList();
}, this);
this.setRoles(oForm ? oForm.asc_getAllRoles() : []);
$(window).on('resize', _.bind(this.onWindowResize, this));
},
setApi: function(api) {
this.api = api;
if (this.api) {
this.api.asc_registerCallback('asc_onUpdateOFormRoles', _.bind(this.onRefreshRolesList, this));
}
return this;
},
ChangeSettings: function(props) {
if (this._initSettings) {
this.createDelayedElements();
}
},
setLocked: function (locked) {
this._locked = locked;
},
setMode: function(mode) {
this.mode = mode;
},
disableControls: function(disable) {
},
updateDisableSendButton: function() {
const hasEmptyUser = this.rolesCollection.some(function(role) {
return !role.get('user');
});
this.sendBtn.setDisabled(hasEmptyUser);
},
setRoles: function(roles) {
const oldArray = this.rolesCollection.toJSON();
const oldMap = {};
const newArray = (roles).map(function(role) {
role = role.asc_getSettings();
let color = role.asc_getColor();
color && (color = Common.Utils.ThemeColor.getHexColor(color.get_r(), color.get_g(), color.get_b()));
return {
name: role.asc_getName(),
color: color,
user: null
}
});
oldArray.forEach(function(item) {
oldMap[item.name] = item;
});
// Arrays are merged by 'name', only the `user` field is carried over from the old array
const resultArray = newArray.map(function(item) {
if (oldMap.hasOwnProperty(item.name)) {
item.user = oldMap[item.name].user;
}
return item;
});
this.rolesCollection.reset(resultArray);
},
onUsersListLoad: function(type, users, isPaginated) {
if (this._state.isUsersListLoading && type!=='filler') return;
this._state.isUsersListLoading = false;
this.users = (users || []).map(function(user) {
return {
id: user.id,
name: user.name,
email: user.email,
image: user.image,
initials: Common.Utils.getUserInitials(user.name)
}
});
},
onSend: function() {
const roles = this.rolesCollection.map(function(role) {
return {
name: role.get('name'),
color: role.get('color'),
user: role.get('user')
}
});
DE.getController('Main').onStartFilling(true, roles);
},
onCancel: function() {
// TODO: Move this out of the component by emitting an event
const rightmenuController = DE.getController('RightMenu');
rightmenuController.closeSendForSigning();
},
onRefreshRolesList: function(roles) {
if (this._initSettings) return;
this.setRoles(roles);
},
onWindowResize: function() {
this._updateRolesListHeight();
this.scroller.update(this.scrollerOptions);
},
_renderRolesList: function() {
const me = this;
const userCmbOptions = this.users.map(function(user) {
return Object.assign({value: user.id, displayValue: user.name || user.email}, user);
});
this.$el.find('#id-send-for-signing-settings-roles-list').empty();
this.rolesCollection.each(function(role, index) {
const $item = $(
'<div id="<%= id %>" class="role-item ' + (index == me.rolesCollection.length -1 ? 'last' : '') + '">' +
'<div class="role-item-left">' +
'<div class="role-item-number" style="background: #' + role.get('color') + ';">' +
(index + 1) +
'</div>' +
'<div class="role-item-dashes"></div>' +
'</div>' +
'<div class="role-item-content">' +
'<div class="role-item-label">' + role.get('name') + '</div>' +
'<div class="role-item-user-cmb"></div>' +
'</div>' +
'</div>'
);
const user = role.get('user');
const cmb = new Common.UI.ComboBox({
el: $item.find('.role-item-user-cmb'),
cls: 'input-group-nr',
menuCls: 'role-item-user-cmb-menu',
restoreMenuHeightAndTop: true,
editable: false,
data: userCmbOptions,
placeHolder: me.txtSelectUser,
itemTemplate: _.template(
'<li id="<%= id %>" class="item">' +
'<a tabindex="-1" type="menuitem" role="menuitemcheckbox" aria-checked="false">' +
'<% if(image) { %>' +
'<div class="avatar" style="background-image: url(<%= image %>); background-color: transparent;"></div>' +
'<% } else { %>' +
'<div class="avatar">' +
'<i class="icon toolbar__icon btn-user">&nbsp;</i>' +
'</div>' +
'<% } %>' +
'<div class="content">' +
'<div><%= name %></div>' +
'<div class="email"><%= email %></div>' +
'</div>' +
'</a>' +
'</li>'
)
});
(user && user.id) && cmb.setValue(user.id);
cmb.on('selected', function(combo, record) {
role.set('user', {
id: record.id,
name: record.name,
email: record.email,
image: record.image
});
me.updateDisableSendButton();
});
me.$el.find('#id-send-for-signing-settings-roles-list').append($item);
});
this.updateDisableSendButton();
this.scroller.update(this.scrollerOptions);
},
_updateRolesListHeight: function() {
const $markup = this.$el || $(this.el);
const $listWrapper = $markup.find('.roles-list-wrapper');
const height = $listWrapper.height();
$markup.find('#id-send-for-signing-settings-roles-list').height(height);
},
txtTitle: 'Send for signing',
txtDescription: 'Please check the order of filling before sending.',
txtSelectUser: 'Select user',
txtSend: 'Send',
txtCancel: 'Cancel',
}, DE.Views.SendForSigningSettings || {}));
});

View File

@ -228,6 +228,337 @@
.margin-right-2();
}
}
&#id-filling-status-settings {
.progress-item {
display: flex;
margin-bottom: 4px;
.progress-item-left {
display: flex;
flex-direction: column;
align-items: center;
.margin-right(12px);
.progress-item-number {
opacity: 0.4;
min-width: 24px;
min-height: 24px;
margin-bottom: 4px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
border: @scaled-one-px-value-ie solid @text-normal-ie;
border: @scaled-one-px-value solid @text-normal;
color: @text-normal-ie;
color: @text-normal;
}
.progress-item-dashes {
opacity: 0.4;
width: @scaled-one-px-value-ie;
width: @scaled-one-px-value;
height: 100%;
background: repeating-linear-gradient(
to bottom,
@text-normal-ie 0px,
@text-normal-ie 3px,
transparent 3px,
transparent 6px
);
background: repeating-linear-gradient(
to bottom,
@text-normal 0px,
@text-normal 3px,
transparent 3px,
transparent 6px
);
}
}
.progress-item-content {
flex: 1;
color: @text-normal-ie;
color: @text-normal;
min-height: 43px;
.progress-item-header {
display: flex;
align-items: center;
.progress-item-label {
line-height: 24px;
font-weight: 600;
.margin-right(4px);
}
}
}
.progress-item-footer {
display: flex;
align-items: center;
justify-content: space-between;
.progress-item-date {
color: @text-secondary-ie;
color: @text-secondary;
}
}
.progress-item-check {
display: block;
position: relative;
width: 24px;
height: 24px;
&::before,
&::after {
content: "";
position: absolute;
background: #009900;
// border-radius: 2px;
}
&::before {
width: 4px;
height: 2px;
left: 7px;
top: 12px;
transform: rotate(45deg);
}
&::after {
width: 10px;
height: 2px;
left: 9px;
top: 11px;
transform: rotate(-45deg);
}
}
&.active {
.progress-item-number {
opacity: 1 !important;
border-color: @background-accent-button-ie !important;
border-color: @background-accent-button !important;
}
.progress-item-label {
color: @border-fill-input-focused-ie !important;
color: @border-fill-input-focused !important;
}
}
&.complete {
&:not(.all-done) {
opacity: 0.4;
}
.progress-item-number {
opacity: 1 !important;
border-color: #009900 !important;
}
.progress-item-content {
min-height: auto !important;
}
}
}
}
&#id-send-for-signing-settings {
padding: 0;
&, table {
height: 100%;
}
.content-padding {
padding: @layout-padding-right-panel-content-ie;
padding: @layout-padding-right-panel-content;
.rtl & {
padding: @layout-padding-right-panel-content-rtl-ie;
padding: @layout-padding-right-panel-content-rtl;
}
}
.inner {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
flex: 1;
.content {
display: flex;
flex-direction: column;
flex: 1;
}
.footer {
padding: 12px 0;
display: flex;
justify-content: center;
#id-send-for-signing-settings-send-btn {
.margin-right(10px);
}
}
.description {
color: @text-secondary-ie;
color: @text-secondary;
}
.roles-list-wrapper {
flex: 1;
position: relative;
}
#id-send-for-signing-settings-roles-list {
width: 100%;
position: absolute;
overflow: hidden;
padding-top: 0;
.role-item {
display: flex;
margin-bottom: 4px;
.role-item-left {
display: flex;
flex-direction: column;
align-items: center;
.margin-right(12px);
.role-item-number {
min-width: 24px;
min-height: 24px;
margin-bottom: 4px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100%;
border: @scaled-one-px-value-ie solid rgba(0, 0, 0, 0.15);
border: @scaled-one-px-value solid rgba(0, 0, 0, 0.15);
color: #000000CC;
}
.role-item-dashes {
opacity: 0.4;
width: @scaled-one-px-value-ie;
width: @scaled-one-px-value;
height: 100%;
background: repeating-linear-gradient(
to bottom,
@text-normal-ie 0px,
@text-normal-ie 3px,
transparent 3px,
transparent 6px
);
background: repeating-linear-gradient(
to bottom,
@text-normal 0px,
@text-normal 3px,
transparent 3px,
transparent 6px
);
}
}
.role-item-content {
.role-item-user-cmb {
margin-bottom: 8px;
}
.role-item-user-cmb-menu {
width: 196px;
left: -36px;
.rtl & {
left: unset;
right: -36px;
}
.item {
a {
display: flex;
padding: 5px 7px;
.avatar {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 24px;
height: 24px;
border-radius: 24px;
background-color: @border-divider-ie;
background-color: @border-divider;
// color: @toolbar-header-text-on-background-ie;
// color: @toolbar-header-text-on-background;
vertical-align: middle;
background-size: cover;
background-position: center;
font-size: 10px;
line-height: 24px;
overflow: hidden;
text-align: center;
.margin-right(4px);
.icon {
display: block;
width: @x-small-btn-icon-size-ie;
width: @x-small-btn-icon-size;
height: @x-small-btn-icon-size-ie;
height: @x-small-btn-icon-size;
}
}
.content {
min-width: 0;
& > * {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.email {
color: @text-secondary-ie;
color: @text-secondary;
}
}
}
}
}
}
&.last {
.role-item-dashes {
display: none;
}
}
.role-item-content {
flex: 1;
color: @text-normal-ie;
color: @text-normal;
min-width: 0;
.role-item-label {
line-height: 24px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.role-item-user-cmb {
height: 24px;
}
}
}
}
}
}
}
.ie {