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:
Arwid Thornström
2021-12-08 11:00:15 +01:00
committed by GitHub
parent b556bb8df2
commit 6741e82479
9 changed files with 3669 additions and 20 deletions
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}]`,
);
});
}
}
+29 -2
View File
@@ -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 (?, ?)`;