/*
* Copyright 2020 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* The widget for comparing two values of any type. It displays the reference value on the left, and the value to be compared on the right.
* This widget works only if it is added into a
*/
Ext.define('Ametys.form.widget.Comparison', {
extend: 'Ametys.form.AbstractFieldsWrapper',
alias: ['widget.comparator'],
layout: {
type: 'hbox',
align: 'stretch'
},
statics: {
/**
* Compare two values and return the array of modifiers to apply on value
* Values are consider canonical since [2.1, 2.2] compared to [2.2, 2.1] will return a move of [2.2] for example (and not 1=>2 on first line and 2=>1 on the second line)
* @param {String/String[]} value The current value
* @param {String/String[]} comparisonValue The comparison value
* @param {Boolean} base Is the comparisonValue the old value (right part of the comparison widget) or the future value (left part of the comparison widget)?
* @param {Function} mappingFunction optionnal mapping function to apply to each element of the incoming values to convert them to a string
* @return {String[]} An array that have the size of the value array that respectively tell for each value its modification.is new, delete or modified
* When base=true, the array values are "none", "moved", "added".
* When base=false, the array values are "none", "moved", "deleted".
*/
compareCanonicalValues: function(value, comparisonValue, base, mappingFunction) {
mappingFunction = mappingFunction || function(v) { return v; };
let uniqueCharIndex = {};
let uniqueCharIndexCount = 0;
function _uniqueChar(item)
{
var v = uniqueCharIndex[item];
if (!v)
{
uniqueCharIndex[item] = String.fromCharCode(65 + uniqueCharIndexCount);
uniqueCharIndexCount++;
}
return uniqueCharIndex[item];
}
// Compare each array value to a single char (to avoid diff inside values)
let cValues = Ext.Array.from(value).map(mappingFunction).map(_uniqueChar);
let cCompareValues = Ext.Array.from(comparisonValue).map(mappingFunction).map(_uniqueChar);
var a;
// As diff(a,b) is not the same as diff(b,a) we have to call twice with the same order
if (base)
{
a = HtmlDiff.execute(cCompareValues.join('\n'), cValues.join('\n'));
a = a.replace(/<del [^>]*>.*?<\/del>/gs, "");
}
else
{
a = HtmlDiff.execute(cValues.join('\n'), cCompareValues.join('\n'));
a = a.replace(/<ins [^>]*>.*?<\/ins>/gs, "");
}
// Prepare reponse
let response = [];
let results = a.split('\n');
let index = 1;
let openedTag = 0;
for (let result of results)
{
if (result)
{
let anyChange;
do
{
anyChange = false;
if (result.startsWith('</del>') || result.startsWith('</ins>'))
{
openedTag--;
result = result.substring(6);
anyChange = true;
}
if (result.startsWith('<del') || result.startsWith('<ins'))
{
openedTag++;
result = result.substring(result.indexOf('>') + 1);
anyChange = true;
}
}
while (anyChange);
if (openedTag > 0)
{
let uniqueChar = result.substring(0, 1);
if (cCompareValues.indexOf(uniqueChar) != -1)
{
response.push("moved");
}
else
{
response.push(base ? "added" : "deleted");
}
}
else
{
response.push("none");
}
result = result.substring(1);
do
{
anyChange = false;
if (result.startsWith('</del>') || result.startsWith('</ins>'))
{
openedTag--;
result = result.substring(6);
anyChange = true;
}
if (result.startsWith('<del') || result.startsWith('<ins'))
{
openedTag++;
result = result.substring(result.indexOf('>') + 1);
anyChange = true;
}
}
while (anyChange);
index++;
}
}
return response;
}
},
/**
* @property {String} externalizableCls The base class for this field
* @private
*/
comparatorFieldCls: 'a-form-comparison-field',
/**
* @property {String} diffCls The CSS class when field is has difference
* @private
*/
diffCls: 'a-form-comparison-field-with-diff',
/**
* @property {String} itemWrapperCls The base class for fields' wrapper
* @private
*/
itemWrapperCls: 'a-form-comparison-item-wrapper',
/**
* @property {String} leftItemWrapperCls The base class for left field
* @private
*/
leftItemWrapperCls: 'a-form-comparison-item-wrapper-left',
/**
* @property {String} leftItemWrapperCls The base class for right field
* @private
*/
rightItemWrapperCls: 'a-form-comparison-item-wrapper-right',
/**
* @property {Boolean} isComparatorField Flag denoting that this component is a Comparator Field. Always true.
* @readonly
*/
isComparatorField: true,
/**
* @protected
* @property {Object} _leftValue The reference value (left side)
*/
/**
* @protected
* @property {Object} _rightValue The value to be compared with the reference value (right side)
*/
/**
* @cfg {String} comparator-wrapped-widget The widget to wrap.
*/
constructor: function(config)
{
this._leftField = this._createField(config);
this._rightField = this._createField(config);
config.items = [
{
// left side
xtype: 'container',
flex: 1,
layout: 'fit',
cls: [this.itemWrapperCls, this.leftItemWrapperCls],
items: [this._leftField],
style: {
padding: '3px 6px',
opacity: 0 // A field with no value should remains hidden (such as unexisting repeater entry)
}
},
{
// right side
xtype: 'container',
flex: 1,
layout: 'fit',
cls: [this.itemWrapperCls, this.rightItemWrapperCls],
items: [this._rightField],
style: {
padding: '3px 6px',
opacity: 0 // A field with no value should remains hidden (such as unexisting repeater entry)
}
}
];
var cls = Ext.Array.from(config.cls);
Ext.Array.push(cls, this.comparatorFieldCls);
config.cls = cls;
this.callParent(arguments);
},
/**
* @private
* Creates a wrapped field
* @param {String} itemId The itemId of the field
* @param {Object} comparatorConfig The configuration of this field
* @return {Ext.form.field.Field} The created field
*/
_createField: function(comparatorConfig)
{
var fieldConfig = {
hideLabel: true,
readOnly: true,
msgTarget: 'none',
preventMark: true,
cls: ['ametys']
};
fieldConfig = Ext.applyIf(fieldConfig, comparatorConfig);
delete fieldConfig.ametysDescription;
delete fieldConfig.ametysDescriptionUrl;
delete fieldConfig.fieldLabel;
delete fieldConfig.anchor;
delete fieldConfig.style;
delete fieldConfig['comparator-wrapped-widget'];
fieldConfig.resizable = false; // richtext for ex won't work, since Comparison does not watch their size
var xtype = comparatorConfig['comparator-wrapped-widget'];
var type = comparatorConfig.type;
// Let's check if there is a version of this widget that supports comparisons
if (!xtype)
{
xtype = Ametys.form.WidgetManager.getWidgetXType(null, type, fieldConfig.enumeration !== undefined, fieldConfig.multiple || false);
}
var finalXType = xtype;
var comparisonXType = xtype + "-comparison";
if (Ametys.form.WidgetManager.hasWidgetXType(comparisonXType, type, fieldConfig.enumeration !== undefined, fieldConfig.multiple || false))
{
finalXType = comparisonXType;
}
var field = Ametys.form.WidgetManager.getWidget(finalXType, type, fieldConfig);
field.getErrors = function(/* value */) { return []; };
return field;
},
/**
* Set the widget diff state
* @param {Boolean} state true when right value differs from left value
*/
setInDiffState: function(state)
{
if (state)
{
this.addCls(this.diffCls);
this.markWarning("{{i18n PLUGINS_CORE_UI_WIDGET_COMPARATOR_HAS_MODIFICATIONS}}");
}
else
{
this.removeCls(this.diffCls);
this.clearWarning();
}
},
renderActiveWarning: function()
{
// Do not render warnings
},
setValue: function(value)
{
if (this.form.baseMode === true)
{
this._leftValue = value;
this._leftField.setValue(value);
this._leftField.ownerCt.setStyle('opacity', 'unset');
if (this._rightField.canDisplayComparisons)
{
this._rightField.setComparisonValue(value, true);
}
}
else if (this.form.baseMode === false)
{
this._rightValue = value;
this._rightField.setValue(value);
this._rightField.ownerCt.setStyle('opacity', 'unset');
if (this._leftField.canDisplayComparisons)
{
this._leftField.setComparisonValue(value, false);
}
}
else // if (this.form.baseMode === null)
{
// Default values should be ignored
}
},
getValue: function(value)
{
return '';
},
getErrors: function(value)
{
return [];
}
});