blob: aa4638194e1aabebb9f9c900cc5c1d66c3c5e9b8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
import _ from 'lodash';
/**
* @param {ng.IWindowService} $window
* @param {ReturnType<typeof import('./Focus.service').default>} Focus
* @param {ng.IRootScopeService} $rootScope
*/
export default function service($window, Focus, $rootScope) {
function ensureActivePanel(ui, pnl, focusId) {
if (ui && ui.loadPanel) {
const collapses = $('[bs-collapse-target]');
ui.loadPanel(pnl);
const idx = _.findIndex(collapses, function(collapse) {
return collapse.id === pnl;
});
if (idx >= 0) {
const activePanels = ui.activePanels;
if (!_.includes(ui.topPanels, idx)) {
ui.expanded = true;
const customExpanded = ui[pnl];
if (customExpanded)
ui[customExpanded] = true;
}
if (!activePanels || activePanels.length < 1)
ui.activePanels = [idx];
else if (!_.includes(activePanels, idx)) {
const newActivePanels = _.cloneDeep(activePanels);
newActivePanels.push(idx);
ui.activePanels = newActivePanels;
}
}
if (!_.isNil(focusId))
Focus.move(focusId);
}
}
/** @type {CanvasRenderingContext2D} */
let context = null;
/**
* Calculate width of specified text in body's font.
*
* @param {string} text Text to calculate width.
* @returns {Number} Width of text in pixels.
*/
function measureText(text) {
if (!context) {
const canvas = document.createElement('canvas');
context = canvas.getContext('2d');
const style = window.getComputedStyle(document.getElementsByTagName('body')[0]);
context.font = style.fontSize + ' ' + style.fontFamily;
}
return context.measureText(text).width;
}
/**
* Compact java full class name by max number of characters.
*
* @param names Array of class names to compact.
* @param nameLength Max available width in characters for simple name.
* @returns {*} Array of compacted class names.
*/
function compactByMaxCharts(names, nameLength) {
for (let nameIx = 0; nameIx < names.length; nameIx++) {
const s = names[nameIx];
if (s.length > nameLength) {
let totalLength = s.length;
const packages = s.split('.');
const packageCnt = packages.length - 1;
for (let i = 0; i < packageCnt && totalLength > nameLength; i++) {
if (packages[i].length > 0) {
totalLength -= packages[i].length - 1;
packages[i] = packages[i][0];
}
}
if (totalLength > nameLength) {
const className = packages[packageCnt];
const classNameLen = className.length;
let remains = Math.min(nameLength - totalLength + classNameLen, classNameLen);
if (remains < 3)
remains = Math.min(3, classNameLen);
packages[packageCnt] = className.substring(0, remains) + '...';
}
let result = packages[0];
for (let i = 1; i < packages.length; i++)
result += '.' + packages[i];
names[nameIx] = result;
}
}
return names;
}
/**
* Compact java full class name by max number of pixels.
*
* @param names Array of class names to compact.
* @param nameLength Max available width in characters for simple name. Used for calculation optimization.
* @param nameWidth Maximum available width in pixels for simple name.
* @returns {*} Array of compacted class names.
*/
function compactByMaxPixels(names, nameLength, nameWidth) {
if (nameWidth <= 0)
return names;
const fitted = [];
const widthByName = [];
const len = names.length;
let divideTo = len;
for (let nameIx = 0; nameIx < len; nameIx++) {
fitted[nameIx] = false;
widthByName[nameIx] = nameWidth;
}
// Try to distribute space from short class names to long class names.
let remains = 0;
do {
for (let nameIx = 0; nameIx < len; nameIx++) {
if (!fitted[nameIx]) {
const curNameWidth = measureText(names[nameIx]);
if (widthByName[nameIx] > curNameWidth) {
fitted[nameIx] = true;
remains += widthByName[nameIx] - curNameWidth;
divideTo -= 1;
widthByName[nameIx] = curNameWidth;
}
}
}
const remainsByName = remains / divideTo;
for (let nameIx = 0; nameIx < len; nameIx++) {
if (!fitted[nameIx])
widthByName[nameIx] += remainsByName;
}
}
while (remains > 0);
// Compact class names to available for each space.
for (let nameIx = 0; nameIx < len; nameIx++) {
const s = names[nameIx];
if (s.length > (nameLength / 2 | 0)) {
let totalWidth = measureText(s);
if (totalWidth > widthByName[nameIx]) {
const packages = s.split('.');
const packageCnt = packages.length - 1;
for (let i = 0; i < packageCnt && totalWidth > widthByName[nameIx]; i++) {
if (packages[i].length > 1) {
totalWidth -= measureText(packages[i].substring(1, packages[i].length));
packages[i] = packages[i][0];
}
}
let shortPackage = '';
for (let i = 0; i < packageCnt; i++)
shortPackage += packages[i] + '.';
const className = packages[packageCnt];
const classLen = className.length;
let minLen = Math.min(classLen, 3);
totalWidth = measureText(shortPackage + className);
// Compact class name if shorten package path is very long.
if (totalWidth > widthByName[nameIx]) {
let maxLen = classLen;
let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
while (middleLen !== minLen && middleLen !== maxLen) {
const middleLenPx = measureText(shortPackage + className.substr(0, middleLen) + '...');
if (middleLenPx > widthByName[nameIx])
maxLen = middleLen;
else
minLen = middleLen;
middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
}
names[nameIx] = shortPackage + className.substring(0, middleLen) + '...';
}
else
names[nameIx] = shortPackage + className;
}
}
}
return names;
}
/**
* Compact any string by max number of pixels.
*
* @param label String to compact.
* @param nameWidth Maximum available width in pixels for simple name.
* @returns {*} Compacted string.
*/
function compactLabelByPixels(label, nameWidth) {
if (nameWidth <= 0)
return label;
const totalWidth = measureText(label);
if (totalWidth > nameWidth) {
let maxLen = label.length;
let minLen = Math.min(maxLen, 3);
let middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
while (middleLen !== minLen && middleLen !== maxLen) {
const middleLenPx = measureText(label.substr(0, middleLen) + '...');
if (middleLenPx > nameWidth)
maxLen = middleLen;
else
minLen = middleLen;
middleLen = (minLen + (maxLen - minLen) / 2 ) | 0;
}
return label.substring(0, middleLen) + '...';
}
return label;
}
/**
* Calculate available width for text in link to edit element.
*
* @param index Showed index of element for calculation of maximum width in pixels.
* @param id Id of contains link table.
* @returns {*[]} First element is length of class for single value, second element is length for pair vlaue.
*/
function availableWidth(index, id) {
const idElem = $('#' + id);
let width = 0;
switch (idElem.prop('tagName')) {
// Detection of available width in presentation table row.
case 'TABLE':
const cont = $(idElem.find('tr')[index - 1]).find('td')[0];
width = cont.clientWidth;
if (width > 0) {
const children = $(cont).children(':not("a")');
_.forEach(children, function(child) {
if ('offsetWidth' in child)
width -= $(child).outerWidth(true);
});
}
break;
// Detection of available width in dropdown row.
case 'A':
width = idElem.width();
$(idElem).children(':not("span")').each(function(ix, child) {
if ('offsetWidth' in child)
width -= child.offsetWidth;
});
break;
default:
}
return width | 0;
}
// TODO: move somewhere else
function triggerValidation(form) {
const fe = (m) => Object.keys(m.$error)[0];
const em = (e) => (m) => {
if (!e)
return;
const walk = (m) => {
if (!m || !m.$error[e])
return;
if (m.$error[e] === true)
return m;
return walk(m.$error[e][0]);
};
return walk(m);
};
$rootScope.$broadcast('$showValidationError', em(fe(form))(form));
}
return {
/**
* Cut class name by width in pixel or width in symbol count.
*
* @param id Id of parent table.
* @param index Row number in table.
* @param maxLength Maximum length in symbols for all names.
* @param names Array of class names to compact.
* @param divider String to visualy divide items.
* @returns {*} Array of compacted class names.
*/
compactJavaName(id, index, maxLength, names, divider) {
divider = ' ' + divider + ' ';
const prefix = index + ') ';
const nameCnt = names.length;
const nameLength = ((maxLength - 3 * (nameCnt - 1)) / nameCnt) | 0;
try {
const nameWidth = (availableWidth(index, id) - measureText(prefix) - (nameCnt - 1) * measureText(divider)) /
nameCnt | 0;
// HTML5 calculation of showed message width.
names = compactByMaxPixels(names, nameLength, nameWidth);
}
catch (err) {
names = compactByMaxCharts(names, nameLength);
}
let result = prefix + names[0];
for (let nameIx = 1; nameIx < names.length; nameIx++)
result += divider + names[nameIx];
return result;
},
/**
* Compact text by width in pixels or symbols count.
*
* @param id Id of parent table.
* @param index Row number in table.
* @param maxLength Maximum length in symbols for all names.
* @param label Text to compact.
* @returns Compacted label text.
*/
compactTableLabel(id, index, maxLength, label) {
label = index + ') ' + label;
try {
const nameWidth = availableWidth(index, id) | 0;
// HTML5 calculation of showed message width.
label = compactLabelByPixels(label, nameWidth);
}
catch (err) {
const nameLength = maxLength - 3 | 0;
label = label.length > maxLength ? label.substr(0, nameLength) + '...' : label;
}
return label;
},
widthIsSufficient(id, index, text) {
try {
const available = availableWidth(index, id);
const required = measureText(text);
return !available || available >= Math.floor(required);
}
catch (err) {
return true;
}
},
ensureActivePanel(panels, id, focusId) {
ensureActivePanel(panels, id, focusId);
},
saveBtnTipText(dirty, objectName) {
if (dirty)
return 'Save ' + objectName;
return 'Nothing to save';
},
formUI() {
return {
ready: false,
expanded: false,
loadedPanels: [],
loadPanel(pnl) {
if (!_.includes(this.loadedPanels, pnl))
this.loadedPanels.push(pnl);
},
isPanelLoaded(pnl) {
return _.includes(this.loadedPanels, pnl);
}
};
},
markPristineInvalidAsDirty(ngModelCtrl) {
if (ngModelCtrl && ngModelCtrl.$invalid && ngModelCtrl.$pristine)
ngModelCtrl.$setDirty();
},
triggerValidation
};
}
service.$inject = ['$window', 'IgniteFocus', '$rootScope'];