@fto-consult/common
Version:
Un ensemble de bibliothèques et d'utilistaires communs pour le développement d'applications javascript
572 lines (556 loc) • 23.5 kB
JavaScript
import {isObj,isNonNullString,isDataURL,defaultStr,sortBy,isNumber,defaultObj,defaultFunc,isFunction,defaultVal,defaultNumber} from "$cutils";
import theme from "$theme";
import {getLoggedUserCode,getUserFullName,getUserEmail,getUserPseudo} from "$cauth/utils";
import appConfig from "$capp/config";
import textToObject from "./textToObject";
import Colors from "$theme/colors";
import DateLib from "$clib/date";
import { LOGO_WIDTH } from "../formats/fields";
import formats from "../formats/formats"
import { defaultPageFormat,defaultPageOrientation } from "../formats";
import outlineText from "./outlineText";
import pageSizes from "../formats/pageSizes/iso";
const MAX_CREATED_SIGNATURE_PRINT_SIZE = 50;
import Preloader from "$preloader";
///prepare les options d'impression
export const prepareOptions = (options)=>{
options = defaultObj(options);
const {showPreloader,hidePreloader} = options;
options.showPreloader = typeof showPreloader =='function'? showPreloader : typeof Preloader?.open =="function"? Preloader.open : typeof Preloader?.showPreloader =='function'? Preloader. showPreloader : x=>true;
options.hidePreloader = typeof hidePreloader =='function'? hidePreloader : typeof Preloader?.close =="function" ? Preloader?. close : typeof Preloader?.hidePreloader =="function" ? Preloader.hidePreloader : x=>true;
return options;
}
/*permet de créer l'entête des données de type tableau
@param {Array} tableHeader : l'entte du tableau à créer
@param {function|object} : les options supplémentaires. is function alors il s'agit de la méthode render par défaut
lorsque options est un objet, alors il est de la forme :
{
render | renderItem {function({item,index})=><>}
color {string}, les couleurs d'entête
fillColor {string}, les couleur d'arrière plan d'entête
}
*/
export const createTableHeader = (tableHeader,options)=>{
if(!isObjOrArray(tableHeader)) return [];
if(isFunction(options)){
options = {render:options};
}
options = defaultObj(options);
let {renderItem,filter,render,fillColor,color,upperCase,upper,...rest} = options;
fillColor = Colors.isValid(fillColor)? fillColor : theme.colors.background;
filter = typeof filter =="function"? filter : x=> undefined;
color = Colors.isValid(color)? color : theme.colors.text;
renderItem = isFunction(renderItem)? renderItem : isFunction(render)? render : undefined;
rest = defaultObj(rest)
let tH = [];
Object.map(tableHeader,(e,index)=>{
const item = e;
if(renderItem){
if(isObj(e)){
e = renderItem({...e,item,index})
} else {
e = renderItem({value:e,item,index})
}
}
const f = filter({value:e,item,index});
if(f === false) return null;
if(e || f === true){
tH.push(
{
text : isNonNullString(e)? (upper!== false && upperCase !== false ? e.toUpperCase():"") : !isObj(e)? e : undefined,
fillColor,
color,
bold : true,
...rest,
...(isObj(item)? item:{}),
}
)
}
});
return tH;
}
/**
@see : https://pdfmake.github.io/docs/0.1/document-definition-object/page/
* @param {function({
pageWidth,pageCount,
})}, la fonction de rappel à appeler pour passer les paramtères costomisées
* @param {If pageBreakBefore returns true, a page break will be added before the currentNode. Current node has the following information attached:} currentNode
* @param {*} followingNodesOnPage
* @param {*} nodesOnNextPage
* @param {*} previousNodesOnPage
*/
export const pageBreakBefore = (cb)=>{
const pageBreakBefore = defaultFunc(cb);
return function(currentNode, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage) {
if(isObj(currentNode.startPosition) && isNonNullString(currentNode.text)){
return pageBreakBefore({
...currentNode.startPosition,
currentNode,
followingNodesOnPage, nodesOnNextPage, previousNodesOnPage,
text : currentNode.text,
top : defaultNumber(currentNode.startPosition.top),
pageWidth : currentNode.startPosition.pageInnerWidth,
left : currentNode.startPosition.left,
pageHeight : currentNode.startPosition.pageInnerHeight
})
}
return currentNode.headlineLevel === 1 && followingNodesOnPage.length === 0;
}
};
export const createHeader = (options)=>{
options = defaultObj(options);
const {header,headerAlignment,headerFontSize,headerTextColor} = options;
if(isObj(header) && Object.size(header,true)) return header;
if(Array.isArray(header) && header.length) return header;
if(isNonNullString(header)){
return {
text : header,
fontSize : typeof headerFontSize === 'number' ? headerFontSize : 11,
margin : [10,7,10,0],
color : theme.Colors.isValid(headerTextColor)? headerTextColor : undefined,
alignment : isNonNullString(headerAlignment) && ['left','center','right','justify'].includes(headerAlignment.trim().toLowerCase())? headerAlignment.trim().toLowerCase() : "center",
}
}
return undefined;
}
/**** permet d'évaluer le contenu du pied de page
permet de créer le footer de la page à générer
* @param {
* footerCopyRight : Le contenu de pied de page
footerCopyRightColor {string}, la couleur du footerCopyRight
showPreloader {function(text)}, la fonction de rappel à utiliser pour afficher la progression en cours de la création du pdf
hidePreloader {function()}, la fonction appelée pour masquer le preloader
* }
*/
export const createFooter = (opt)=>{
const {showPreloader,hidePreloader,...opts} = prepareOptions(opt);
const margin = [10,0,10,10];
let footerCopyRight = opts.footerCopyRight === false ? "" : defaultVal(opts.footerCopyRight,isNonNullString(opts.footer) ? opts.footer:undefined,undefined)
if(isNonNullString(footerCopyRight)){
footerCopyRight = {
text : textToObject(footerCopyRight),
alignment : "center",
fontSize:10,
margin,
color : Colors.isValid(opts.footerCopyRightColor)? opts.footerCopyRightColor : undefined
}
}
let devWebsite = appConfig.devWebsite;
return function(currentPage, pageCount, pageSize) {
currentPage = defaultNumber(currentPage);
const currentPageStr = currentPage.formatNumber().toString();
pageCount = defaultNumber(pageCount);
if(currentPage === 1) hidePreloader();
showPreloader("page "+currentPage.formatNumber()+"/"+pageCount.formatNumber());
if(currentPage >= pageCount){
setTimeout(hidePreloader,50);
}
pageCount = pageCount > 2 ? {
fontSize: 9,
//width : "140",
text:[
{
text : ' Page '+currentPageStr + '/' + pageCount.formatNumber(),
bold : true,
},
{
text: ['\n[ powered by ',{text:appConfig.name,link:isValidUrl(devWebsite)? devWebsite:undefined,color:'red'}," ]"],
fontSize : 9,
italics : true,
link : isValidUrl(devWebsite)? devWebsite : undefined
}
],
margin : [5,0,10,0],
alignment: 'right'
} : null;
if(isObj(footerCopyRight) && footerCopyRight.text && pageCount){
footerCopyRight.alignment = "left";
footerCopyRight.width = "*"
}
const content = pageCount ? [
{
columns: [
footerCopyRight,
pageCount
],
columnGap: 10,
margin
}
] : footerCopyRight? [footerCopyRight] : undefined;
return content;
}
};
const getHeaderOrFooter = (options,name,fallback)=>{
options = defaultObj(options);
name = name.trim();
if(options[name] === false){
delete options[name];
return undefined;
}
if(typeof options[name] === "function") return options[name];
if(Array.isArray(options[name]) && options[name].length || isObj(options[name]) && Object.size(options[name],true)){
return options[name];
}
return fallback(options);
}
/*** permet de récupérer le footer du doc definition : @see : https://pdfmake.github.io/docs/0.1/document-definition-object/headers-footers/ */
export const getFooter = (options)=>{
return getHeaderOrFooter(options,"footer",createFooter,true);
}
export const getHeader = (options)=>{
return getHeaderOrFooter(options,"header",createHeader)
}
export const pageHeaderMargin = [ 0, 0, 0, 8 ];
/**** retourne l'espace entre les colonnes des entêtes du document, espace entre la colonnes du tiers et celle de la société */
export const getHeaderColumnGap = ()=>{
return 10;
}
/**** permet de créer l'entête de la page à générer comme pdf
* @param : Object {
* ///PropType.array, le contenu à concaténer auprès du companyHeader
* ///tableau contenant le header à afficher côte à cote ou de manière repartie au companyHeader
* pageHeader : [], le contenu à afficher du côté du companyHeader à créer
* }
*
*/
export const createPageHeader = (options)=>{
options = defaultObj(options);
const companyHeader = getCompanyHeader(options);
let hasCompany = companyHeader && companyHeader.length> 0;
const dynamicPageHeaderWidth = defaultVal(options.dynamicPageHeaderWidth,0);
let pageHeader = Array.isArray(options.pageHeader)? options.pageHeader : isObj(options.pageHeader)? [options.pageHeader] : [];
let columnGap = getHeaderColumnGap();
let hasLogo = hasCompany && isObj(companyHeader[0]) && companyHeader[0].image ? true : false;
let margin = pageHeaderMargin;
let ret = {}
let hasColumns = false;
if(dynamicPageHeaderWidth){
//le logo seule prend quatre lignes
let companyHeaderLength = hasCompany ? (hasLogo? (companyHeader.length+4):companyHeader.length) : 0;
let diff = pageHeader.length - companyHeaderLength;
if(diff > 1){
let div = Math.floor(diff/2);
for(let i = div; i >= 1;i--){
if(i >= pageHeader.length) continue;
companyHeader.push(pageHeader[pageHeader.length-i]);
delete pageHeader[pageHeader.length-i];
}
} else if(companyHeader.length > pageHeader.length){
let div = Math.floor((companyHeader.length-pageHeader.length)/2);
div += hasLogo?2:0;
for(let i = div; i >= 1;i--){
if(i >= companyHeader.length) continue;
pageHeader.push(companyHeader[companyHeader.length-i]);
delete companyHeader[companyHeader.length-i];
}
}
hasCompany = companyHeader.length;
pageHeader = pageHeader.length ? pageHeader : undefined;
}
let pageHeaderContent = [];
if(hasCompany){
if(pageHeader){
pageHeaderContent.push(companyHeader);
pageHeaderContent.push(pageHeader);
hasColumns = true;
} else pageHeaderContent = companyHeader;
} else if(pageHeader){
pageHeaderContent = pageHeader;
}
if(hasColumns){
ret.columns = pageHeaderContent;
ret.columnGap = columnGap;
ret.margin = margin;
} else {
if(isArray(pageHeaderContent)){
ret = pageHeaderContent;
ret.push({text:'',margin})
} else {
ret = {text:pageHeaderContent,margin}
}
}
return ret;
};
/****
permet de générer l'entête de la société
@param {
logo {string}, Logo au format dataUrl
displayLogo{boolean}, si le logo sera affiché
mobile1 {string}, le téléphone mobile1,
mobile2 {string},le téléphone mobile2
phone {string},le téléphone mobile
email {string}, l'email de la société
postBox {string}, la boîte postale de la société
fax {string}, le fax de la société
}
*/
export const getCompanyHeader = (options)=>{
options = defaultObj(options);
let fontSize = defaultNumber(options.fontSize,11);
let bold = true;
const headerColumn = []
if(options.displayLogo !== false && options.displayLogo !== 0 && isDataURL(options.logo)){
headerColumn.push({
image : options.logo,
width : defaultNumber(options.logoWidth,LOGO_WIDTH)
});
}
const displaySocialReason = !!defaultVal(options.displaySocialReason,1);
const companyName = displaySocialReason && defaultStr(options.companyName).trim() || '';
if(companyName){
headerColumn.push({text:companyName+"\n",fontSize:13,bold})
}
const socialReason = displaySocialReason && defaultStr(options.socialReason).trim() ||'';
if(socialReason){
headerColumn.push({text:socialReason+"\n",fontSize:13,bold:false})
}
let phone = defaultStr(options.phone);
if(isNonNullString(options.mobile)){
phone += (isNonNullString(phone)?"/":"")+options.mobile
}
if(isNonNullString(options.mobile1)){
phone += (isNonNullString(phone)?"/":"")+options.mobile1
}
if(isNonNullString(options.mobile2)){
phone += (isNonNullString(phone)?"/":"")+options.mobile2
}
if(isNonNullString(phone)){
headerColumn.push({
text : "TEL : "+phone,
fontSize,
bold
});
}
if(isNonNullString(options.fax)){
headerColumn.push({
text : "FAX : "+options.fax,
fontSize,
bold
})
}
if(isNonNullString(options.postBox)){
headerColumn.push({
text : "BP : "+options.postBox,
fontSize,
bold
})
}
if(isNonNullString(options.email)){
headerColumn.push({
text : "EMAIL : "+options.email,
fontSize,
bold
})
}
return headerColumn;
}
/*** récupère les marges à partir des options des pages
@see : https://pdfmake.github.io/docs/0.1/document-definition-object/page
// [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins
les marges sont définies de la forme :
{
leftMargin : {number},
rightMargin : {number},
topMargin : {number},
bottomMargin: {number};
}
*/
export const getPageMargins = (options)=>{
options = defaultObj(options);
const ret = [20,20,20,30];
const margins = ['left','top','right','bottom']
for(let i in margins){
let m = margins[i]+"Margin";
if(isNumber(options[m])){
ret [i]=options[m]
}
if(i == 3){
ret[i] = Math.max(ret[i],30)
}
}
return ret;
}
/***affiche le contenu rendu des ginataires du document
* @param {Array} signatories : tableau portant la liste des signataires du document, de la forme :
{
image{string|dataURL},
label|text|code {string}, le libelé à affiché avant le signataire
position {string}, la fonction occupée par l'employée en question
signature {Array,Object,string}, le contenu à remplacer par la signature image, lorsque la signature n'est pas définie,
signatureOptions {object}, les options liés à la signature :
}
@param {object} options,
*/
export const createSignatories = (signatories,options)=>{
signatories = Array.isArray(signatories)? signatories : [];
options = Object.assign({},options);
if(!signatories.length){
return {text:""};
}
const marginNumber = Math.floor(typeof options.signatoriesMargin == 'number'? options.signatoriesMargin : 2);
const opts = {
margin : [0,7,0,0]
}
const canPrintSignatoriesImages = !!options.printSignatoriesImages;
const columns = []
let marginText = '';
for(let i=0; i< marginNumber-1;i++){
marginText+="\n";
}
signatories.map((s,i)=>{
if(!isObj(s)) return null;
let image = s.image;
const sOptions = defaultObj(s.signatureOptions);
const style = {fontSize:13};
s.fontStyle = defaultStr(s.fontStyle,'italics+bold').toLowerCase();
if(s.fontStyle.contains("italics")){
style.italics = true;
}
if(s.fontStyle.contains("bold")){
style.bold = true;
}
const position = defaultStr(s.position);
const signature = Array.isArray(s.signature) || isObj(s.signature) || isNonNullString(s.signature) ? s.signature : null;
s = defaultStr(s.label,s.text,s.code);
if(isNonNullString(s)){
const text = [{text:position?position:s,...style}];
const hasImage = canPrintSignatoriesImages && isDataURL(image);
if(hasImage){
const signatureImage = {image,alignment:'center',margin:[0,0,0,0]};
const maxSignatureDim = defaultNumber(options.maxCreatedSignaturePrintSize,MAX_CREATED_SIGNATURE_PRINT_SIZE)
let width = Math.abs(defaultNumber(sOptions.width)),
height = Math.abs(defaultNumber(sOptions.height));
if(width && height){
width = Math.min(width,maxSignatureDim);
height = Math.min(height,maxSignatureDim);
} else if(width){
width = height = Math.min(width,maxSignatureDim)
} else if(height){
height = width = Math.min(height,maxSignatureDim);
} else {
width = height = maxSignatureDim
}
signatureImage.fit = [width,height];
text.push(signatureImage);
} else if(signature){
text.push(signature);
}
if(position){
//lorsque la fonction est spécifiée et que ni la signature, ni l'image n'est spécifiée, alors on laisse un intervalle de 2 lignes pour obtenir la signature concernée
text.push({text:`${!hasImage && !signature?`${marginText}`:""}${s}`,fontSize:12});
} else if(marginText && !hasImage && !signature){
text.push({text:marginText})
}
columns.push(text);
}
return '';
})
if(columns.length > 0){
const percent = (100/columns.length)
for(let i in columns){
columns[i].width = percent+"%";
}
} else {
delete opts.margin;
//return columns;
}
return {
columns,
width : '100%',
alignment : 'center',
...opts
}
}
/**** includesEmailOnPrintedUser{boolean}, si le mail */
export const getPrintedDate = (opts)=>{
opts = defaultObj(opts);
let displayPrintedDate = opts.displayPrintedDate !== undefined ? opts.displayPrintedDate : true;
if(!displayPrintedDate){
return null;
}
const uEmail = getUserEmail();
const pseudo = getUserPseudo();
const fullName = getUserFullName() || pseudo || getLoggedUserCode();
const includesEmailOnPrintedUser = opts.includesEmailOnPrintedUser !== false && opts.includesEmailOnPrintedUser !== 0;
const cCode = isNonNullString(fullName)? (includesEmailOnPrintedUser ? `${fullName}${uEmail?`[${uEmail}]`:""}`: fullName) : "";
const code = cCode.length > 60 && includesEmailOnPrintedUser ? cCode?.substring(0,60) : cCode;
const hasCode = isNonNullString(code) || typeof code =='number';
return {
text : [(`Date de tirage : ${new Date().toFormat(DateLib.defaultDateTimeFormat)} ${hasCode ? `, par : `:""}`).toUpperCase(),hasCode ? {text:code,bold:true}:{text:""}],
italics: true,
fontSize : typeof opts.printedDateFontSize =='number'? opts.printedDateFontSize : 11,
margin : [0,3,0,0]
}
}
/****
*/
export const printTags = (tags,arg)=>{
arg = defaultObj(arg);
const columns = []
Object.map(tags,(s,i)=>{
const image = outlineText(s);
if(!image){
columns.push({image})
}
return '';
})
if(columns.length <= 0) return null;
return {
columns,
alignment : 'center',
}
}
/*** retourne les dimensions de la page */
export const getPageSize = (options,force)=>{
options = defaultObj(options);
let {pageHeight,pageWidth,pageSize,pageFormat,pageOrientation} = options;
pageFormat = defaultStr(pageFormat,pageSize).trim().toUpperCase();
if(!pageFormat || !formats.includes(pageFormat)){
pageFormat = defaultPageFormat;
}
pageOrientation = defaultStr(options.pageOrientation).toLowerCase().trim();
if(!pageOrientation || !["landscape",defaultPageOrientation].includes(pageOrientation)){
pageOrientation = defaultPageOrientation;
}
const isLandScape = pageOrientation == 'landscape';
pageSize = pageFormat;
pageWidth = typeof pageWidth =='number'? pageWidth : 0;
pageHeight = typeof pageHeight =='number'? pageHeight: 0;
if(isObj(pageSizes[pageSize]) && Array.isArray(pageSizes[pageSize].pt)){
if(pageWidth <= 0){
pageWidth = pageSizes[pageSize].pt[isLandScape? 1 : 0]
}
if(pageHeight <= 0){
pageHeight = pageSizes[pageSize].pt[isLandScape? 0 : 1]
}
}
if( pageWidth > 0 && pageHeight>0){
pageSize = {
width : pageWidth,
height: pageHeight
}
pageOrientation = undefined;
}
const pageMargins = typeof options.pageMargins === 'number'? [options.pageMargins,options.pageMargins,options.pageMargins,options.pageMargins]
: Array.isArray(options.pageMargins) && (options.pageMargins.length == 2 || options.pageMargins.length ===4) && options.pageMargins || getPageMargins(options);
//console.log(pageSize,pageFormat,' page format is page siez')
let margins = {};
if(pageMargins.length === 2){ //[horizontal, vertical]
margins = {
pageMarginLeft : pageMargins[0],
pageMarginRight : pageMargins[0],
pageMarginTop : pageMargins[1],
pageMarginBottom : pageMargins[1],
}
} else {
margins = {
pageMarginLeft : pageMargins[0],
pageMarginTop : pageMargins[1],
pageMarginRight : pageMargins[2],
pageMarginBottom : pageMargins[3],
}
}
return {pageSize,pageFormat,pageOrientation,pageMargins,...margins}
}
export {textToObject,textToObject as sprintf};
export {outlineText};