89-poster-defaults (#96)
* print defaults in printproducts * Add default to printproduct mutation * added insertDefaults when changing productgroup * fixed bug for square in square * cleanup, and tests for calculations * small fixes, added return values for update
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,245 @@
|
||||
import { Maybe } from 'graphql/jsutils/Maybe';
|
||||
import {
|
||||
BorderType,
|
||||
PrintProductDefaults,
|
||||
} from '../types/printproduct-defaults-types';
|
||||
import { BaseSQLDataSource } from './BaseSQLDataSource';
|
||||
|
||||
type TPosterDefaultPositioning = {
|
||||
width_mm: number;
|
||||
height_mm: number;
|
||||
crop_x: number;
|
||||
crop_y: number;
|
||||
};
|
||||
export const calculateDefaultPositioning = (
|
||||
productWidth: number,
|
||||
productHeight: number,
|
||||
focusXpoint2: number,
|
||||
focusYpoint2: number,
|
||||
): TPosterDefaultPositioning => {
|
||||
// Because we dont want to be dependent on different environments
|
||||
// number of supported decimals to round to 10 decimal places.
|
||||
// And I know 70/50 is 1.4 so its not needed but added for clarity.
|
||||
const size70x50ratio = parseFloat((70 / 50).toFixed(10));
|
||||
const size50x70ratio = parseFloat((50 / 70).toFixed(10));
|
||||
const width_height_ratio = productWidth / productHeight;
|
||||
|
||||
// If focuspoints are null we set to 50 to make calculations
|
||||
// below a bit clearer.
|
||||
const fpX = focusXpoint2 === null ? 50 : focusXpoint2;
|
||||
const fpY = focusYpoint2 === null ? 50 : focusYpoint2;
|
||||
|
||||
const width_mm = width_height_ratio >= size70x50ratio ? 700 : 500;
|
||||
const height_mm = width_height_ratio <= size50x70ratio ? 700 : 500;
|
||||
|
||||
// Calculate where to put the crop_x percent value
|
||||
// We place the center of the crop area as close to
|
||||
// the focusXpoint2 and focusYpoint2 values. We do
|
||||
// different calculations depending on if the poster size
|
||||
// orientation (landscape, portrait or square) is inside
|
||||
// the motif orientation.
|
||||
//
|
||||
// The goal for the calculations is to get the smallest amount of area
|
||||
// that we crop away.
|
||||
const crop_x = (() => {
|
||||
if (width_height_ratio >= size70x50ratio) {
|
||||
const centerOfRatioArea = (productHeight * 1.4) / 2;
|
||||
let x = Math.max(centerOfRatioArea, (fpX / 100) * productWidth); // Place center close to focuspointX but not outside the left edge
|
||||
x = Math.min(productWidth - centerOfRatioArea, x); // Dont place outside the right edge
|
||||
x = x - centerOfRatioArea; // Convert center to left edge
|
||||
x = x / productWidth; // Convert to percent;
|
||||
return x;
|
||||
} else if (
|
||||
// Square in landscape
|
||||
width_height_ratio > 1 &&
|
||||
width_height_ratio < size70x50ratio
|
||||
) {
|
||||
const centerOfRatioArea = productHeight / 2;
|
||||
let x = Math.max(centerOfRatioArea, (fpX / 100) * productWidth); // Place center close to focuspointX but not outside the left side
|
||||
x = Math.min(productWidth - centerOfRatioArea, x); // Dont place outside the right edge
|
||||
x = x - centerOfRatioArea; // Convert center to left edge
|
||||
x = x / productWidth; // Convert to percent
|
||||
return x;
|
||||
} else {
|
||||
// If the scenario is, square in portrait, portrait in portrait or square
|
||||
// in square then we are placing vertical, therefor x will be 0.
|
||||
return 0;
|
||||
}
|
||||
})();
|
||||
|
||||
// Read comment for crop_x above.
|
||||
const crop_y = (() => {
|
||||
if (width_height_ratio < 1 && width_height_ratio > size50x70ratio) {
|
||||
// Square in portrait
|
||||
const centerOfRatioArea = productWidth / 2;
|
||||
let y = Math.max(centerOfRatioArea, (fpY / 100) * productHeight); // Place center close to focuspointY but not ouside top edge
|
||||
y = Math.min(productHeight - centerOfRatioArea, y); // Dont place outside bottom edge
|
||||
y = y - centerOfRatioArea; // Convert center to top edge
|
||||
y = y / productHeight; // Convert to percent
|
||||
return y;
|
||||
} else if (width_height_ratio <= size50x70ratio) {
|
||||
// Portrait in portrait
|
||||
const centerOfRatioArea = productWidth / size50x70ratio / 2;
|
||||
let y = Math.max(centerOfRatioArea, (fpY / 100) * productHeight); // Place center close to focuspointY but not ouside top edge
|
||||
y = Math.min(productHeight - centerOfRatioArea, y); // Dont place outside bottom edge
|
||||
y = y - centerOfRatioArea; // Convert center to top edge
|
||||
y = y / productHeight; // Convert to percent
|
||||
return y;
|
||||
} else {
|
||||
// If the scenario is landscape in landscape, square in landscape or square in square
|
||||
// the we are placing horizontal and therefor y will be 0.
|
||||
return 0;
|
||||
}
|
||||
})();
|
||||
|
||||
return { width_mm, height_mm, crop_x, crop_y };
|
||||
};
|
||||
|
||||
export class PrintProductDefaultsAPI extends BaseSQLDataSource {
|
||||
constructor(config) {
|
||||
super(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @function getBorderType
|
||||
* @description Will convert lowercase none | white | black to
|
||||
* uppercase and define it as a BorderType
|
||||
*/
|
||||
getBorderType(dbValue: string): string {
|
||||
switch (dbValue) {
|
||||
case 'white':
|
||||
return BorderType[BorderType.WHITE];
|
||||
case 'black':
|
||||
return BorderType[BorderType.BLACK];
|
||||
default:
|
||||
return BorderType[BorderType.NONE];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @function getDefaults
|
||||
* @description This function will fetch the default values from printproducts_defaults table
|
||||
* with the printId as a key. Border is defined in lowercase in DB but returned in uppercase
|
||||
* to keep consistency in both DB and graphql.
|
||||
*/
|
||||
async getDefaults(printId: number): Promise<Maybe<PrintProductDefaults>> {
|
||||
return this.knex
|
||||
.column(
|
||||
{ printId: 'print_id' },
|
||||
{ widthMm: 'width_mm' },
|
||||
{ heightMm: 'height_mm' },
|
||||
{ cropX: 'crop_x' },
|
||||
{ cropY: 'crop_y' },
|
||||
'border',
|
||||
)
|
||||
.select()
|
||||
.from('printproducts_defaults')
|
||||
.where('print_id', printId)
|
||||
.first()
|
||||
.then((row) => {
|
||||
if (row) {
|
||||
return Object.assign(
|
||||
{
|
||||
...row,
|
||||
},
|
||||
{ border: this.getBorderType(row.border) },
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @function insertDefaults
|
||||
* @description Inserts a new printproduct into printproducts_defaults, sets the default
|
||||
* values the same way we did when settings default values for all older printproducts.
|
||||
*/
|
||||
async insertDefaults(printId: number): Promise<0 | 1> {
|
||||
const product = await this.knex.raw(
|
||||
`
|
||||
SELECT width, height, "focusXpoint2", "focusYpoint2" FROM
|
||||
"product-printproducts" ppp
|
||||
JOIN v_product vp ON ppp.productid = vp.id
|
||||
WHERE printid = ?`,
|
||||
[printId],
|
||||
);
|
||||
|
||||
const { width, height, focusXpoint2, focusYpoint2 } = product.rows[0];
|
||||
|
||||
// GUARDS
|
||||
if (!width || !height)
|
||||
throw new Error(
|
||||
'PrintProductDefaultsAPI.insertDefaults: width and height must be defined for a motif to be able to set defaults',
|
||||
);
|
||||
|
||||
const { width_mm, height_mm, crop_x, crop_y } = calculateDefaultPositioning(
|
||||
width,
|
||||
height,
|
||||
focusXpoint2,
|
||||
focusYpoint2,
|
||||
);
|
||||
|
||||
return this.knex
|
||||
.raw(
|
||||
`
|
||||
INSERT INTO printproducts_defaults (print_id, width_mm, height_mm, crop_x, crop_y, border)
|
||||
VALUES (?, ?, ?, ?, ?, 'none');
|
||||
`,
|
||||
[printId, width_mm, height_mm, crop_x, crop_y],
|
||||
)
|
||||
.catch((e) => {
|
||||
throw new Error(e);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// MUTATIONS
|
||||
// ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @function updateDefaults
|
||||
* @description Updates or inserts default values for a printproduct in the
|
||||
* table printproducts_defaults.
|
||||
*/
|
||||
async updateDefaults(
|
||||
printId: number,
|
||||
widthMm: number,
|
||||
heightMm: number,
|
||||
cropX: number,
|
||||
cropY: number,
|
||||
border: 'none' | 'white' | 'black',
|
||||
): Promise<PrintProductDefaults> {
|
||||
return this.knex
|
||||
.table('printproducts_defaults')
|
||||
.update(
|
||||
{
|
||||
width_mm: widthMm,
|
||||
height_mm: heightMm,
|
||||
crop_x: cropX,
|
||||
crop_y: cropY,
|
||||
border: border.toLocaleLowerCase(),
|
||||
},
|
||||
['print_id', 'width_mm', 'height_mm', 'crop_x', 'crop_y', 'border'],
|
||||
)
|
||||
.where({ print_id: printId })
|
||||
.then((row) => {
|
||||
if (row) {
|
||||
const first = row[0];
|
||||
return Object.assign(
|
||||
{
|
||||
...first,
|
||||
},
|
||||
{ border: this.getBorderType(first.border) },
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
throw new Error(
|
||||
`Something went wrong when trying to update with printid [${printId}][${e.message}]`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,18 @@ import {
|
||||
import { UserInputError } from 'apollo-server';
|
||||
import { sendPathChangedCmd } from '../bernard/client';
|
||||
import { TextsAPI } from './texts-api';
|
||||
import { PrintProductDefaultsAPI } from './printproduct-defaults-api';
|
||||
|
||||
const MINUTE = 60;
|
||||
|
||||
export class ProductAPI extends BaseSQLDataSource {
|
||||
textsApi: TextsAPI;
|
||||
constructor(config, textsApi) {
|
||||
printProductDefaultsApi: PrintProductDefaultsAPI;
|
||||
|
||||
constructor(config, textsApi, printProductDefaultsApi) {
|
||||
super(config);
|
||||
this.textsApi = textsApi;
|
||||
this.printProductDefaultsApi = printProductDefaultsApi;
|
||||
}
|
||||
|
||||
// async getMaterial(printId: number): Promise<any> {
|
||||
@@ -607,12 +611,20 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
// Delete from product-printproducts_materials
|
||||
const printProductIds = shouldDelete.map((item) => item.printid);
|
||||
|
||||
// Delete from materials first, must be in sequence since we have constraint in materials to printproducts
|
||||
// Delete from materials first, must be in sequence
|
||||
// since we have constraint in materials to printproducts
|
||||
await this.knex
|
||||
.from('product-printproducts_materials')
|
||||
.whereIn('printid', printProductIds)
|
||||
.del();
|
||||
|
||||
// Delete from printproducts_defaults, same here run
|
||||
// this first because of constraint.
|
||||
await this.knex
|
||||
.from('printproducts_defaults')
|
||||
.whereIn('printId', printProductIds)
|
||||
.del();
|
||||
|
||||
// Delete from product-printproducts
|
||||
await this.knex
|
||||
.from('product-printproducts')
|
||||
@@ -648,6 +660,21 @@ export class ProductAPI extends BaseSQLDataSource {
|
||||
.where('productid', productId)
|
||||
.whereIn('groupid', shouldAdd);
|
||||
|
||||
// Add printproduct-defaults for all new printIds
|
||||
const insertDefaults = [];
|
||||
newPrintIds.forEach(({ printid, groupid }) => {
|
||||
// Possible to add for canvas and framed-print later
|
||||
if (groupid === ProductGroup.POSTER) {
|
||||
// Add printproduct defaults if poster
|
||||
insertDefaults.push(
|
||||
this.printProductDefaultsApi.insertDefaults(printid),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Run parallell
|
||||
await Promise.all(insertDefaults);
|
||||
|
||||
// Insert printIds into product-printproducts_materials
|
||||
const insertMaterials = [];
|
||||
const insertMaterialQuery = `INSERT INTO "product-printproducts_materials" (printid, materialid) VALUES (?, ?)`;
|
||||
|
||||
Reference in New Issue
Block a user