pixeli 0.1.9 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -88
- package/dist/cli/commands/collage/index.d.ts +2 -0
- package/dist/cli/commands/collage/index.js +125 -0
- package/dist/cli/commands/grid/index.d.ts +2 -0
- package/dist/cli/commands/grid/index.js +127 -0
- package/dist/cli/commands/masonry/index.d.ts +2 -0
- package/dist/cli/commands/masonry/index.js +129 -0
- package/dist/cli/commands/template/index.d.ts +2 -0
- package/dist/cli/commands/template/index.js +123 -0
- package/dist/cli/commands/template/presets/artGallery.d.ts +15 -0
- package/dist/cli/commands/template/presets/artGallery.js +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.d.ts +15 -0
- package/dist/cli/commands/template/presets/dashboardShot.js +16 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/horizontalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets/instagramGrid.d.ts +15 -0
- package/dist/cli/commands/template/presets/instagramGrid.js +16 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.d.ts +15 -0
- package/dist/cli/commands/template/presets/verticalBookSpread.js +13 -0
- package/dist/cli/commands/template/presets.d.ts +73 -0
- package/{lib/merges/collage-merge → dist/cli/commands/template}/presets.js +6 -8
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +24 -0
- package/dist/cli/modules/loadImages.d.ts +15 -0
- package/dist/cli/modules/loadImages.js +74 -0
- package/dist/cli/modules/progressBar.d.ts +10 -0
- package/dist/cli/modules/progressBar.js +34 -0
- package/dist/cli/schemas/collage.d.ts +29 -0
- package/dist/cli/schemas/collage.js +38 -0
- package/dist/cli/schemas/grid.d.ts +34 -0
- package/dist/cli/schemas/grid.js +38 -0
- package/dist/cli/schemas/masonry.d.ts +62 -0
- package/dist/cli/schemas/masonry.js +62 -0
- package/dist/cli/schemas/template.d.ts +31 -0
- package/dist/cli/schemas/template.js +49 -0
- package/dist/cli/utils/buildCommandFromSchema.d.ts +8 -0
- package/dist/cli/utils/buildCommandFromSchema.js +55 -0
- package/dist/cli/utils/configureCommandErrors.d.ts +2 -0
- package/dist/cli/utils/configureCommandErrors.js +22 -0
- package/dist/cli/utils/stringFormatter.d.ts +1 -0
- package/dist/cli/utils/stringFormatter.js +3 -0
- package/dist/cli/utils/toErrorMessage.d.ts +4 -0
- package/dist/cli/utils/toErrorMessage.js +22 -0
- package/dist/core/helpers.d.ts +10 -0
- package/dist/core/helpers.js +42 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1 -0
- package/dist/core/mergeError.d.ts +9 -0
- package/dist/core/mergeError.js +10 -0
- package/dist/core/merges/collage/index.d.ts +9 -0
- package/dist/core/merges/collage/index.js +32 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.d.ts +12 -0
- package/dist/core/merges/collage/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/collage/steps/createComposites.d.ts +8 -0
- package/dist/core/merges/collage/steps/createComposites.js +58 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.d.ts +12 -0
- package/dist/core/merges/collage/steps/resizeAndBorderImages.js +26 -0
- package/dist/core/merges/collage/steps/rotateImages.d.ts +7 -0
- package/dist/core/merges/collage/steps/rotateImages.js +9 -0
- package/dist/core/merges/grid/index.d.ts +12 -0
- package/dist/core/merges/grid/index.js +36 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateCanvasDimensions.js +18 -0
- package/dist/core/merges/grid/steps/calculateFontSize.d.ts +7 -0
- package/dist/core/merges/grid/steps/calculateFontSize.js +19 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.d.ts +8 -0
- package/dist/core/merges/grid/steps/calculateImageDimensions.js +18 -0
- package/dist/core/merges/grid/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/grid/steps/createComposites.js +63 -0
- package/dist/core/merges/grid/steps/prepareImages.d.ts +10 -0
- package/dist/core/merges/grid/steps/prepareImages.js +29 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.d.ts +7 -0
- package/dist/core/merges/grid/steps/shuffleImagesAndCaptions.js +17 -0
- package/dist/core/merges/index.d.ts +3 -0
- package/dist/core/merges/index.js +3 -0
- package/dist/core/merges/masonry/index.d.ts +10 -0
- package/dist/core/merges/masonry/index.js +32 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.d.ts +15 -0
- package/dist/core/merges/masonry/steps/calculateCanvasDimensions.js +17 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.d.ts +9 -0
- package/dist/core/merges/masonry/steps/calculateLaneSize.js +27 -0
- package/dist/core/merges/masonry/steps/createComposites.d.ts +25 -0
- package/dist/core/merges/masonry/steps/createComposites.js +108 -0
- package/dist/core/merges/masonry/steps/resizeImages.d.ts +7 -0
- package/dist/core/merges/masonry/steps/resizeImages.js +14 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.d.ts +17 -0
- package/dist/core/merges/masonry/steps/splitIntoLanes.js +78 -0
- package/dist/core/merges/shared-steps/applyComposites.d.ts +2 -0
- package/dist/core/merges/shared-steps/applyComposites.js +16 -0
- package/dist/core/merges/shared-steps/createCanvas.d.ts +11 -0
- package/dist/core/merges/shared-steps/createCanvas.js +17 -0
- package/dist/core/merges/shared-steps/exportCanvas.d.ts +7 -0
- package/dist/core/merges/shared-steps/exportCanvas.js +25 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.d.ts +2 -0
- package/dist/core/merges/shared-steps/finalizeImagePipelines.js +9 -0
- package/dist/core/merges/shared-steps/loadImages.d.ts +2 -0
- package/dist/core/merges/shared-steps/loadImages.js +26 -0
- package/dist/core/merges/shared-steps/validateCaptions.d.ts +10 -0
- package/dist/core/merges/shared-steps/validateCaptions.js +17 -0
- package/dist/core/merges/template/index.d.ts +10 -0
- package/dist/core/merges/template/index.js +28 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.d.ts +9 -0
- package/dist/core/merges/template/steps/calculateSlotDimensions.js +12 -0
- package/dist/core/merges/template/steps/createComposites.d.ts +10 -0
- package/dist/core/merges/template/steps/createComposites.js +25 -0
- package/dist/core/merges/template/steps/getBlocks.d.ts +13 -0
- package/dist/core/merges/template/steps/getBlocks.js +28 -0
- package/dist/core/merges/template/types.d.ts +21 -0
- package/dist/core/merges/template/types.js +1 -0
- package/dist/core/merges/types.d.ts +102 -0
- package/dist/core/merges/types.js +1 -0
- package/dist/core/modules/messages.d.ts +32 -0
- package/dist/core/modules/messages.js +54 -0
- package/dist/core/pipeline/guards.d.ts +4 -0
- package/dist/core/pipeline/guards.js +23 -0
- package/dist/core/pipeline/mergePipeline.d.ts +60 -0
- package/dist/core/pipeline/mergePipeline.js +122 -0
- package/dist/core/schemas/collage.d.ts +26 -0
- package/dist/core/schemas/collage.js +17 -0
- package/dist/core/schemas/grid.d.ts +32 -0
- package/dist/core/schemas/grid.js +29 -0
- package/dist/core/schemas/masonry.d.ts +56 -0
- package/dist/core/schemas/masonry.js +43 -0
- package/dist/core/schemas/template.d.ts +34 -0
- package/dist/core/schemas/template.js +88 -0
- package/dist/core/utils/colors/hexToRgba.d.ts +2 -0
- package/dist/core/utils/colors/hexToRgba.js +25 -0
- package/dist/core/utils/colors/rgbaToHex.d.ts +2 -0
- package/dist/core/utils/colors/rgbaToHex.js +15 -0
- package/dist/core/utils/colors/types.d.ts +7 -0
- package/dist/core/utils/colors/types.js +1 -0
- package/dist/core/utils/fonts/getFontSize.d.ts +9 -0
- package/dist/core/utils/fonts/getFontSize.js +40 -0
- package/dist/core/utils/images/addImageBorder.d.ts +13 -0
- package/dist/core/utils/images/addImageBorder.js +33 -0
- package/dist/core/utils/images/getImageHeights.d.ts +2 -0
- package/dist/core/utils/images/getImageHeights.js +9 -0
- package/dist/core/utils/images/getImageWidths.d.ts +2 -0
- package/dist/core/utils/images/getImageWidths.js +9 -0
- package/dist/core/utils/images/getSmallestImageDimensions.d.ts +5 -0
- package/dist/core/utils/images/getSmallestImageDimensions.js +9 -0
- package/dist/core/utils/images/handleImageEdges.d.ts +13 -0
- package/dist/core/utils/images/handleImageEdges.js +39 -0
- package/dist/core/utils/images/isActualImage.d.ts +5 -0
- package/dist/core/utils/images/isActualImage.js +26 -0
- package/dist/core/utils/images/parseAspectRatio.d.ts +1 -0
- package/dist/core/utils/images/parseAspectRatio.js +22 -0
- package/dist/core/utils/images/roundImage.d.ts +9 -0
- package/dist/core/utils/images/roundImage.js +27 -0
- package/dist/core/utils/images/roundImages.d.ts +8 -0
- package/dist/core/utils/images/roundImages.js +19 -0
- package/dist/core/utils/images/scaleImage.d.ts +8 -0
- package/dist/core/utils/images/scaleImage.js +36 -0
- package/dist/core/utils/images/scaleImages.d.ts +8 -0
- package/dist/core/utils/images/scaleImages.js +38 -0
- package/dist/core/utils/math/median.d.ts +1 -0
- package/dist/core/utils/math/median.js +12 -0
- package/dist/core/utils/math/randint.d.ts +6 -0
- package/dist/core/utils/math/randint.js +11 -0
- package/dist/core/utils/math/trimmedMedian.d.ts +1 -0
- package/dist/core/utils/math/trimmedMedian.js +12 -0
- package/dist/core/utils/svg/createSvgTextBuffer.d.ts +9 -0
- package/dist/core/utils/svg/createSvgTextBuffer.js +21 -0
- package/dist/validators/aspectRatio.d.ts +2 -0
- package/dist/validators/aspectRatio.js +15 -0
- package/dist/validators/coercion.d.ts +2 -0
- package/dist/validators/coercion.js +12 -0
- package/dist/validators/format.d.ts +2 -0
- package/dist/validators/format.js +15 -0
- package/dist/validators/hexColor.d.ts +7 -0
- package/dist/validators/hexColor.js +27 -0
- package/dist/validators/index.d.ts +95 -0
- package/dist/validators/index.js +64 -0
- package/dist/validators/outputFile.d.ts +2 -0
- package/dist/validators/outputFile.js +7 -0
- package/dist/validators/path.d.ts +3 -0
- package/dist/validators/path.js +18 -0
- package/dist/validators/sharpImageInput.d.ts +3 -0
- package/dist/validators/sharpImageInput.js +6 -0
- package/dist/validators/template.d.ts +15 -0
- package/dist/validators/template.js +41 -0
- package/package.json +26 -9
- package/bin/pixeli.js +0 -26
- package/commands/merge/collage.js +0 -83
- package/commands/merge/grid.js +0 -71
- package/commands/merge/helpers/utils.js +0 -11
- package/commands/merge/helpers/validations.js +0 -269
- package/commands/merge/index.js +0 -12
- package/commands/merge/masonry.js +0 -72
- package/lib/helpers/loadImages.js +0 -94
- package/lib/helpers/progressBar.js +0 -20
- package/lib/helpers/templateValidator.js +0 -139
- package/lib/helpers/utils.js +0 -208
- package/lib/merges/collage-merge/index.js +0 -110
- package/lib/merges/collage-merge/presets/artGallery.js +0 -17
- package/lib/merges/collage-merge/presets/dashboardShot.js +0 -18
- package/lib/merges/collage-merge/presets/horizontalBookSpread.js +0 -15
- package/lib/merges/collage-merge/presets/instagramGrid.js +0 -18
- package/lib/merges/collage-merge/presets/verticalBookSpread.js +0 -15
- package/lib/merges/grid-merge/index.js +0 -152
- package/lib/merges/masonry-merge/horizontal.js +0 -157
- package/lib/merges/masonry-merge/index.js +0 -57
- package/lib/merges/masonry-merge/vertical.js +0 -152
- package/lib/merges/merge-utils.js +0 -176
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
4
|
-
cliConfirm,
|
|
5
|
-
displayInfoMessage,
|
|
6
|
-
displaySuccessMessage,
|
|
7
|
-
displayWarningMessage,
|
|
8
|
-
handleError,
|
|
9
|
-
writeImage,
|
|
10
|
-
} from '../../lib/helpers/utils.js';
|
|
11
|
-
import { validateCollageOptions, validateSharedOptions } from './helpers/validations.js';
|
|
12
|
-
import { loadImages } from '../../lib/helpers/loadImages.js';
|
|
13
|
-
import { addSharedOptions } from './helpers/utils.js';
|
|
14
|
-
import { validateTemplate } from '../../lib/helpers/templateValidator.js';
|
|
15
|
-
import { collageMerge } from '../../lib/merges/collage-merge/index.js';
|
|
16
|
-
|
|
17
|
-
const collageCommand = new Command('collage');
|
|
18
|
-
|
|
19
|
-
collageCommand
|
|
20
|
-
.description('Use JSON layouts to build custom collages.')
|
|
21
|
-
.option('-t, --template <path>', 'The path to the JSON file describing the collage template', null)
|
|
22
|
-
.option('-m, --mapping <json>', 'Inline JSON template override straight from the command line', null)
|
|
23
|
-
.option('-p, --preset <preset-id>', 'Collage preset ID to use. Available collage IDs: ', null)
|
|
24
|
-
.action(async (files, opts) => {
|
|
25
|
-
await main(files, opts);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const main = async (files, opts) => {
|
|
29
|
-
// Collect and validate parameters
|
|
30
|
-
try {
|
|
31
|
-
const params = { files, ...opts };
|
|
32
|
-
const sharedOptions = await validateSharedOptions(params);
|
|
33
|
-
const collageOptions = await validateCollageOptions(opts);
|
|
34
|
-
const validatedParams = { ...sharedOptions, ...collageOptions };
|
|
35
|
-
|
|
36
|
-
// Load images, create collage, and write on disk
|
|
37
|
-
await generateAndSaveCollage(validatedParams);
|
|
38
|
-
|
|
39
|
-
// Output success message
|
|
40
|
-
} catch (e) {
|
|
41
|
-
handleError(e);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const generateAndSaveCollage = async (validatedParams) => {
|
|
46
|
-
// Replace gap and background if not provided with command line values
|
|
47
|
-
if (validatedParams.template?.canvas?.gap === undefined) {
|
|
48
|
-
validatedParams.template.canvas.gap = validatedParams.gap;
|
|
49
|
-
}
|
|
50
|
-
if (validatedParams.template?.canvas?.background === undefined) {
|
|
51
|
-
validatedParams.template.canvas.background = validatedParams.canvasColor;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Validate the template and update it
|
|
55
|
-
const validatedTemplate = validateTemplate(validatedParams.template);
|
|
56
|
-
validatedParams.template = validatedTemplate;
|
|
57
|
-
|
|
58
|
-
// Load images from file
|
|
59
|
-
const { images, ignoredFiles } = await loadImages({ ...validatedParams, count: validatedParams.template.slots.length });
|
|
60
|
-
|
|
61
|
-
// Display warnings if needed
|
|
62
|
-
if (ignoredFiles.length) {
|
|
63
|
-
displayWarningMessage('\nThese files will be ignored due to unsupported formats:');
|
|
64
|
-
for (const file of ignoredFiles) {
|
|
65
|
-
displayInfoMessage(file);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const confirmation = await cliConfirm('\nAre you sure you want to continue?');
|
|
69
|
-
if (!confirmation) return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const collage = await collageMerge(images, validatedParams);
|
|
73
|
-
|
|
74
|
-
const success = await writeImage(collage, validatedParams.output);
|
|
75
|
-
|
|
76
|
-
// Display success message
|
|
77
|
-
if (success) {
|
|
78
|
-
displaySuccessMessage(`\nImage has been created successfully: ${chalk.bold(validatedParams.output)}\n`);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
addSharedOptions(collageCommand);
|
|
83
|
-
export default collageCommand;
|
package/commands/merge/grid.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
4
|
-
cliConfirm,
|
|
5
|
-
displayInfoMessage,
|
|
6
|
-
displaySuccessMessage,
|
|
7
|
-
displayWarningMessage,
|
|
8
|
-
handleError,
|
|
9
|
-
writeImage,
|
|
10
|
-
} from '../../lib/helpers/utils.js';
|
|
11
|
-
import { addSharedOptions } from './helpers/utils.js';
|
|
12
|
-
import { validateGridOptions, validateSharedOptions } from './helpers/validations.js';
|
|
13
|
-
import { loadImages } from '../../lib/helpers/loadImages.js';
|
|
14
|
-
import { gridMerge } from '../../lib/merges/grid-merge/index.js';
|
|
15
|
-
|
|
16
|
-
const gridCommand = new Command('grid');
|
|
17
|
-
|
|
18
|
-
gridCommand
|
|
19
|
-
.description('Arranges images in an organized grid.')
|
|
20
|
-
.option('--ar, --aspect-ratio <width/height|number>', 'The aspect ratio of all the images (examples: 16/9, 4:3, 1.777)', '1:1')
|
|
21
|
-
.option('-w, --image-width <px>', 'The width of each image, defaults to the smallest image', null)
|
|
22
|
-
.option('-c, --columns <n>', 'The number of columns', 4)
|
|
23
|
-
.option('--ca, --caption', 'Whether to caption each image', false)
|
|
24
|
-
.option('--cc, --caption-color <hex>', 'Image Caption color', '#000000')
|
|
25
|
-
.option('--mcs, --max-caption-size <pt>', 'The maximum allowed caption size', 100)
|
|
26
|
-
.action(async (files, opts) => {
|
|
27
|
-
await main(files, opts);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const main = async (files, opts) => {
|
|
31
|
-
// Collect and validate parameters
|
|
32
|
-
try {
|
|
33
|
-
const params = { files, ...opts };
|
|
34
|
-
const sharedOptions = await validateSharedOptions(params);
|
|
35
|
-
const gridOptions = validateGridOptions(sharedOptions, params);
|
|
36
|
-
const validatedParams = { ...sharedOptions, ...gridOptions };
|
|
37
|
-
|
|
38
|
-
// Load images, create grid, and write grid on disk
|
|
39
|
-
await generateAndSaveGrid(validatedParams);
|
|
40
|
-
|
|
41
|
-
// Output success message
|
|
42
|
-
} catch (e) {
|
|
43
|
-
handleError(e);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const generateAndSaveGrid = async (validatedParams) => {
|
|
48
|
-
const { files, images, ignoredFiles } = await loadImages(validatedParams);
|
|
49
|
-
|
|
50
|
-
// Display warnings if needed
|
|
51
|
-
if (ignoredFiles.length) {
|
|
52
|
-
displayWarningMessage('\nThese files will be ignored due to unsupported formats:');
|
|
53
|
-
for (const file of ignoredFiles) {
|
|
54
|
-
displayInfoMessage(file);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const confirmation = await cliConfirm('\nAre you sure you want to continue?');
|
|
58
|
-
if (!confirmation) return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const grid = await gridMerge(files, images, validatedParams);
|
|
62
|
-
const success = await writeImage(grid, validatedParams.output);
|
|
63
|
-
|
|
64
|
-
// Display success message
|
|
65
|
-
if (success) {
|
|
66
|
-
displaySuccessMessage(`\nImage has been created successfully: ${chalk.bold(validatedParams.output)}\n`);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
addSharedOptions(gridCommand);
|
|
71
|
-
export default gridCommand;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export const addSharedOptions = (cmd) => {
|
|
2
|
-
return cmd
|
|
3
|
-
.argument('[files...]', 'Image filepaths to merge (use --dir for directories)')
|
|
4
|
-
.option('-d, --dir <path>', 'Directory of images to merge')
|
|
5
|
-
.option('-r, --recursive', 'Recursively include subdirectories', false)
|
|
6
|
-
.option('--sh, --shuffle', 'Shuffle up images to randomize order in the grid', false)
|
|
7
|
-
.option('--cr, --corner-radius <px>', 'How much to round the corners of each image', 0)
|
|
8
|
-
.option('-g, --gap <px>', 'Gap between images', 50)
|
|
9
|
-
.option('--bg, --canvas-color <hex|transparent>', 'Background color for canvas', '#ffffff')
|
|
10
|
-
.option('-o, --output <file>', 'Output file path', './pixeli.png');
|
|
11
|
-
};
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
displayWarningMessage,
|
|
5
|
-
handleError,
|
|
6
|
-
isSupportedOutputImage,
|
|
7
|
-
isValidHexadecimal,
|
|
8
|
-
parseAspectRatio,
|
|
9
|
-
SUPPORTED_OUTPUT_FORMATS,
|
|
10
|
-
} from '../../../lib/helpers/utils.js';
|
|
11
|
-
import { isValidPreset, PRESETS } from '../../../lib/merges/collage-merge/presets.js';
|
|
12
|
-
|
|
13
|
-
export const validateSharedOptions = async (sharedOptions) => {
|
|
14
|
-
// Extract params
|
|
15
|
-
const { files, dir, recursive, shuffle, cornerRadius, gap, canvasColor, output } = sharedOptions;
|
|
16
|
-
|
|
17
|
-
// Conduct validations
|
|
18
|
-
if ((!files || !files.length) && !dir) {
|
|
19
|
-
throw new Error('You must specify either [files...] or --dir.');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Ensure dir is a valid dir path
|
|
23
|
-
if (dir.length) {
|
|
24
|
-
let stats;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
stats = await fs.stat(dir);
|
|
28
|
-
} catch (e) {
|
|
29
|
-
throw new Error('Path does not exist.');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (!stats.isDirectory()) {
|
|
33
|
-
throw new Error('Path is not a directory.');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (isNaN(gap) || !Number.isInteger(Number(gap)) || gap < 0) {
|
|
38
|
-
throw new Error('--gap must be a positive integer.');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (isNaN(cornerRadius) || !Number.isInteger(Number(cornerRadius)) || cornerRadius < 0) {
|
|
42
|
-
throw new Error('--corner-radius must be a positive integer.');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (canvasColor !== 'transparent' && !isValidHexadecimal(canvasColor)) {
|
|
46
|
-
throw new Error('--canvas-color must be a valid hexadecimal value.');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (!isSupportedOutputImage(output)) {
|
|
50
|
-
throw new Error('Invalid output format. Choose one of the following: ' + SUPPORTED_OUTPUT_FORMATS.join(', '));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const formattedParams = {
|
|
54
|
-
files: files || [],
|
|
55
|
-
dir,
|
|
56
|
-
recursive,
|
|
57
|
-
shuffle,
|
|
58
|
-
cornerRadius: Number(cornerRadius),
|
|
59
|
-
gap: Number(gap),
|
|
60
|
-
canvasColor: canvasColor === 'transparent' ? { r: 0, g: 0, b: 0, alpha: 0 } : canvasColor,
|
|
61
|
-
output,
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
return formattedParams;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export const validateMasonryOptions = (sharedOptions, masonryOptions) => {
|
|
68
|
-
// Extract params
|
|
69
|
-
const { gap } = sharedOptions;
|
|
70
|
-
const { rowHeight, columnWidth, canvasWidth, canvasHeight, flow, hAlign, vAlign } = masonryOptions;
|
|
71
|
-
|
|
72
|
-
// Define orientations and alignments for validation
|
|
73
|
-
const FLOWS = ['horizontal', 'vertical'];
|
|
74
|
-
const HORIZONTAL_ALIGNMENTS = ['left', 'center', 'right', 'justified'];
|
|
75
|
-
const VERTICAL_ALIGNMENTS = ['top', 'middle', 'bottom', 'justified'];
|
|
76
|
-
|
|
77
|
-
// Define orientation dependent options which are ignored if defined for the wrong orientation
|
|
78
|
-
const IGNORED_FLOW_DEPENDENT_OPTIONS = {
|
|
79
|
-
horizontal: [
|
|
80
|
-
{
|
|
81
|
-
option: '--v-align',
|
|
82
|
-
value: vAlign,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
option: '--canvas-height',
|
|
86
|
-
value: canvasHeight,
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
option: '--column-width',
|
|
90
|
-
value: columnWidth,
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
vertical: [
|
|
94
|
-
{
|
|
95
|
-
option: '--h-align',
|
|
96
|
-
value: hAlign,
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
option: '--canvas-width',
|
|
100
|
-
value: canvasWidth,
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
option: '--row-height',
|
|
104
|
-
value: rowHeight,
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
// Validate orientations and alignments
|
|
110
|
-
if (!FLOWS.includes(flow)) {
|
|
111
|
-
throw new Error('Invalid orientation. Choose one of the following: ' + FLOWS.join(', '));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (hAlign && !HORIZONTAL_ALIGNMENTS.includes(hAlign)) {
|
|
115
|
-
throw new Error('Invalid horizontal alignment. Choose one of the following: ' + HORIZONTAL_ALIGNMENTS.join(', '));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (vAlign && !VERTICAL_ALIGNMENTS.includes(vAlign)) {
|
|
119
|
-
throw new Error('Invalid vertical orientation. Choose one of the following: ' + VERTICAL_ALIGNMENTS.join(', '));
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Ensure numeric values are positive integers
|
|
123
|
-
if (rowHeight && (isNaN(rowHeight) || !Number.isInteger(Number(rowHeight)) || Number(rowHeight) < 1)) {
|
|
124
|
-
throw new Error('--row-height must be a positive integer.');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (columnWidth && (isNaN(columnWidth) || !Number.isInteger(Number(columnWidth)) || Number(columnWidth) < 1)) {
|
|
128
|
-
throw new Error('--column-width must be a positive integer.');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Ensure canvas width is given
|
|
132
|
-
if (flow === 'horizontal' && !canvasWidth) {
|
|
133
|
-
throw new Error('--canvas-width must be given.');
|
|
134
|
-
}
|
|
135
|
-
// and is a positive integer
|
|
136
|
-
else if (flow === 'horizontal' && (isNaN(canvasWidth) || !Number.isInteger(Number(canvasWidth)) || Number(canvasWidth) < 1)) {
|
|
137
|
-
throw new Error('--canvas-width must be a positive integer.');
|
|
138
|
-
}
|
|
139
|
-
// and it accomodates for the minimum width needed
|
|
140
|
-
else if (flow === 'horizontal' && canvasWidth <= gap * 2) {
|
|
141
|
-
throw new Error(`--canvas-width must be greater than 2 gaps or ${gap * 2}px.`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Ensure canvas height is given
|
|
145
|
-
if (flow === 'vertical' && !canvasHeight) {
|
|
146
|
-
throw new Error('--canvas-height must be given.');
|
|
147
|
-
}
|
|
148
|
-
// and is a positive integer
|
|
149
|
-
else if (flow === 'vertical' && (isNaN(canvasHeight) || !Number.isInteger(Number(canvasHeight)) || Number(canvasHeight) < 1)) {
|
|
150
|
-
throw new Error('--canvas-height must be a positive integer.');
|
|
151
|
-
}
|
|
152
|
-
// and it accomodates for the minimum height needed
|
|
153
|
-
else if (flow === 'vertical' && canvasHeight <= gap * 2) {
|
|
154
|
-
throw new Error(`--canvas-height must be greater than 2 gaps or ${gap * 2}px.`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Validate dependent options by showing warnings when incorrect parameters
|
|
158
|
-
// are used with incorrect orientation
|
|
159
|
-
const ignoredFlowOptions = IGNORED_FLOW_DEPENDENT_OPTIONS[flow];
|
|
160
|
-
for (const { option, value } of ignoredFlowOptions) {
|
|
161
|
-
if (value) {
|
|
162
|
-
displayWarningMessage(`"${option}" option is ignored due to ${flow} flow.`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const params = {
|
|
167
|
-
rowHeight: Number(rowHeight) || null,
|
|
168
|
-
columnWidth: Number(columnWidth) || null,
|
|
169
|
-
canvasHeight: Number(canvasHeight) || null,
|
|
170
|
-
canvasWidth: Number(canvasWidth) || null,
|
|
171
|
-
flow,
|
|
172
|
-
hAlign,
|
|
173
|
-
vAlign,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
return params;
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
export const validateGridOptions = (sharedOptions, gridOptions) => {
|
|
180
|
-
// Extract params
|
|
181
|
-
const { aspectRatio, imageWidth, columns, caption, captionColor, maxCaptionSize } = gridOptions;
|
|
182
|
-
|
|
183
|
-
// Ensure aspect ratio is valid
|
|
184
|
-
const parsedAspectRatio = parseAspectRatio(aspectRatio);
|
|
185
|
-
if (!parsedAspectRatio) {
|
|
186
|
-
throw new Error('--aspect-ratio must be a valid ratio. Examples: 16/9, 2:3, 1x2, 1.77');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if (imageWidth && (isNaN(imageWidth) || !Number.isInteger(Number(imageWidth)) || Number(imageWidth) < 1)) {
|
|
190
|
-
throw new Error('--image-width must be a positive integer.');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (isNaN(columns) || !Number.isInteger(Number(columns)) || Number(columns) < 1) {
|
|
194
|
-
throw new Error('--columns must be a positive integer.');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (isNaN(maxCaptionSize) || !Number.isInteger(Number(maxCaptionSize)) || Number(maxCaptionSize) < 2) {
|
|
198
|
-
throw new Error('--max-caption-size must be a positive integer >= 2 (minimum caption size).');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (!isValidHexadecimal(captionColor)) {
|
|
202
|
-
throw new Error('--caption-color must be a valid hexadecimal value.');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const formattedParams = {
|
|
206
|
-
aspectRatio: parsedAspectRatio,
|
|
207
|
-
imageWidth: Number(imageWidth) || null,
|
|
208
|
-
columns: Number(columns),
|
|
209
|
-
caption,
|
|
210
|
-
captionColor,
|
|
211
|
-
maxCaptionSize,
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
return formattedParams;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
export const validateCollageOptions = async (collageOptions) => {
|
|
218
|
-
const { template, mapping, preset } = collageOptions;
|
|
219
|
-
|
|
220
|
-
// If a valid preset is given, use it
|
|
221
|
-
if (preset && isValidPreset(preset)) {
|
|
222
|
-
return { template: PRESETS[preset] };
|
|
223
|
-
}
|
|
224
|
-
// If preset is invalid, throw error
|
|
225
|
-
else if (preset && !isValidPreset(preset)) {
|
|
226
|
-
const validPresets = Object.keys(PRESETS).join(', ');
|
|
227
|
-
throw new Error(`"${preset}" is not a valid preset ID. Choose one of the following: \n${validPresets}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Ensure only one of the two are given
|
|
231
|
-
if (!template && !mapping) {
|
|
232
|
-
throw new Error('Either --template, --mapping, or --preset need to be provided.');
|
|
233
|
-
} else if (template && mapping) {
|
|
234
|
-
throw new Error('Either use --template or --mapping, not both.');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Ensure mapping is actually a json string
|
|
238
|
-
if (!template && mapping) {
|
|
239
|
-
try {
|
|
240
|
-
JSON.parse(mapping);
|
|
241
|
-
} catch {
|
|
242
|
-
throw new Error('--mapping should be a valid JSON string.');
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Ensure template is a valid dir path
|
|
247
|
-
if (template && !mapping) {
|
|
248
|
-
let stats;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
stats = await fs.stat(template);
|
|
252
|
-
} catch (e) {
|
|
253
|
-
throw new Error('Template path does not exist.');
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Parse and return respective JSON data
|
|
258
|
-
try {
|
|
259
|
-
if (template) {
|
|
260
|
-
const jsonStr = await fs.readFile(template, 'utf8');
|
|
261
|
-
return { template: JSON.parse(jsonStr) };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// mapping is already a JSON string
|
|
265
|
-
return { template: JSON.parse(mapping) };
|
|
266
|
-
} catch (err) {
|
|
267
|
-
throw new Error('Could not read or parse the provided template or mapping JSON.');
|
|
268
|
-
}
|
|
269
|
-
};
|
package/commands/merge/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import masonryCommand from './masonry.js';
|
|
3
|
-
import gridCommand from './grid.js';
|
|
4
|
-
import collageCommand from './collage.js';
|
|
5
|
-
|
|
6
|
-
const mergeCommand = new Command('merge').description('Merge images into a grid layout.');
|
|
7
|
-
|
|
8
|
-
mergeCommand.addCommand(masonryCommand);
|
|
9
|
-
mergeCommand.addCommand(gridCommand);
|
|
10
|
-
mergeCommand.addCommand(collageCommand);
|
|
11
|
-
|
|
12
|
-
export default mergeCommand;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
4
|
-
cliConfirm,
|
|
5
|
-
displayInfoMessage,
|
|
6
|
-
displaySuccessMessage,
|
|
7
|
-
displayWarningMessage,
|
|
8
|
-
handleError,
|
|
9
|
-
writeImage,
|
|
10
|
-
} from '../../lib/helpers/utils.js';
|
|
11
|
-
import { addSharedOptions } from './helpers/utils.js';
|
|
12
|
-
import { validateMasonryOptions, validateSharedOptions } from './helpers/validations.js';
|
|
13
|
-
import { loadImages } from '../../lib/helpers/loadImages.js';
|
|
14
|
-
import { masonryMerge } from '../../lib/merges/masonry-merge/index.js';
|
|
15
|
-
|
|
16
|
-
const masonryCommand = new Command('masonry');
|
|
17
|
-
|
|
18
|
-
masonryCommand
|
|
19
|
-
.description("Use a ragged-grid layout, preserves images' aspect ratios")
|
|
20
|
-
.option('--rh, --row-height <px>', 'The height of each row, defaults to the smallest image height', null)
|
|
21
|
-
.option('--cw, --column-width <px>', 'The width of each column, defaults to the smallest image width', null)
|
|
22
|
-
.option('--cvw, --canvas-width <px>', 'The width of the canvas', null)
|
|
23
|
-
.option('--cvh, --canvas-height <px>', 'The height of the canvas', null)
|
|
24
|
-
.option('-f, --flow <horizontal|vertical>', 'The flow of the masonry layout', 'horizontal')
|
|
25
|
-
.option('--ha, --h-align <left|center|right|justified>', 'Horizontal alignment of the grid (for horizontal flows)', null)
|
|
26
|
-
.option('--va, --v-align <top|middle|bottom|justified>', 'Vertical alignment of the grid (for vertical flows)', null)
|
|
27
|
-
.action((files, opts) => {
|
|
28
|
-
main(files, opts);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const main = async (files, opts) => {
|
|
32
|
-
try {
|
|
33
|
-
// Collect and validate parameters
|
|
34
|
-
const params = { files, ...opts };
|
|
35
|
-
const sharedOptions = await validateSharedOptions(params);
|
|
36
|
-
const masonryOptions = validateMasonryOptions(sharedOptions, opts);
|
|
37
|
-
const validatedParams = { ...sharedOptions, ...masonryOptions };
|
|
38
|
-
|
|
39
|
-
// Load images, create grid, and write grid on disk
|
|
40
|
-
generateAndSaveGrid(validatedParams);
|
|
41
|
-
|
|
42
|
-
// Output success message
|
|
43
|
-
} catch (e) {
|
|
44
|
-
handleError(e);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const generateAndSaveGrid = async (validatedParams) => {
|
|
49
|
-
const { images, ignoredFiles } = await loadImages(validatedParams);
|
|
50
|
-
|
|
51
|
-
// Display warnings if needed
|
|
52
|
-
if (ignoredFiles.length) {
|
|
53
|
-
displayWarningMessage('\nThese files will be ignored due to unsupported formats:');
|
|
54
|
-
for (const file of ignoredFiles) {
|
|
55
|
-
displayInfoMessage(file);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const confirmation = await cliConfirm('\nAre you sure you want to continue?');
|
|
59
|
-
if (!confirmation) return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const grid = await masonryMerge(images, validatedParams);
|
|
63
|
-
const success = await writeImage(grid, validatedParams.output);
|
|
64
|
-
|
|
65
|
-
// Display success message
|
|
66
|
-
if (success) {
|
|
67
|
-
displaySuccessMessage(`\nImage has been created successfully: ${chalk.bold(validatedParams.output)}\n`);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
addSharedOptions(masonryCommand);
|
|
72
|
-
export default masonryCommand;
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import sharp from 'sharp';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { isSupportedInputImage, shuffleTogether } from './utils.js';
|
|
5
|
-
|
|
6
|
-
const MAX_RECURSION_DEPTH = 10;
|
|
7
|
-
|
|
8
|
-
export const loadImages = async ({ files, dir, recursive, shuffle, count }) => {
|
|
9
|
-
let ignoredFiles = [];
|
|
10
|
-
let filepaths = files;
|
|
11
|
-
let images = [];
|
|
12
|
-
|
|
13
|
-
if (files && files.length) {
|
|
14
|
-
// Load directly from provided file list
|
|
15
|
-
images = await loadFromFiles(files, count);
|
|
16
|
-
} else {
|
|
17
|
-
// Get all files from directory
|
|
18
|
-
const { skippedFiles, paths } = await getFilesFromDirectory(dir, recursive);
|
|
19
|
-
filepaths = paths;
|
|
20
|
-
ignoredFiles = skippedFiles;
|
|
21
|
-
images = await loadFromFiles(filepaths, count);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Ensure filepaths and images match
|
|
25
|
-
if (images.length !== filepaths.length) {
|
|
26
|
-
filepaths = filepaths.slice(0, images.length);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Optional: shuffle filepaths and images together
|
|
30
|
-
if (shuffle) {
|
|
31
|
-
[filepaths, images] = shuffleTogether(filepaths, images);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return { images, files: filepaths, ignoredFiles };
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const loadFromFiles = async (files, count) => {
|
|
38
|
-
const images = [];
|
|
39
|
-
const total = count || files.length;
|
|
40
|
-
for (let i = 0; i < total; i++) {
|
|
41
|
-
// End the loop if count is higher than number of available files
|
|
42
|
-
if (i >= files.length) break;
|
|
43
|
-
|
|
44
|
-
// Load images
|
|
45
|
-
const filepath = files[i];
|
|
46
|
-
let image;
|
|
47
|
-
|
|
48
|
-
if (filepath.endsWith('.svg')) {
|
|
49
|
-
const svgBuffer = await fs.readFile(filepath);
|
|
50
|
-
image = sharp(svgBuffer);
|
|
51
|
-
} else {
|
|
52
|
-
image = sharp(filepath);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
images.push(image);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return images;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const getFilesFromDirectory = async (dir, recursive, depth = 0) => {
|
|
62
|
-
// Use to collect warnings
|
|
63
|
-
const skippedFiles = [];
|
|
64
|
-
|
|
65
|
-
// Ensure recursiveness ends at the max recursion depth
|
|
66
|
-
if (depth >= MAX_RECURSION_DEPTH) return { paths: [], skippedFiles: [] };
|
|
67
|
-
|
|
68
|
-
// Get entries
|
|
69
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
70
|
-
const files = [];
|
|
71
|
-
|
|
72
|
-
for (const entry of entries) {
|
|
73
|
-
const file = path.join(entry.parentPath, entry.name);
|
|
74
|
-
|
|
75
|
-
// If the entry is a valid image file, add it to the list
|
|
76
|
-
if (entry.isFile() && isSupportedInputImage(entry.name)) {
|
|
77
|
-
files.push(file);
|
|
78
|
-
}
|
|
79
|
-
// If it is an invalid file format, add to skipped files
|
|
80
|
-
else if (entry.isFile() && !isSupportedInputImage(entry.name) && entry.name !== '.DS_Store') {
|
|
81
|
-
skippedFiles.push(entry.name);
|
|
82
|
-
}
|
|
83
|
-
// If it's a directory AND the recursive option is true,
|
|
84
|
-
// recursively get all the files
|
|
85
|
-
else if (recursive && entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
86
|
-
const dirpath = path.join(entry.parentPath, entry.name);
|
|
87
|
-
const dirObj = await getFilesFromDirectory(dirpath, recursive, depth + 1);
|
|
88
|
-
files.push(...dirObj?.paths);
|
|
89
|
-
skippedFiles.push(...dirObj.skippedFiles);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { paths: files, skippedFiles };
|
|
94
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { SingleBar } from 'cli-progress';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
|
|
4
|
-
const title = chalk.gray('Creating Image:');
|
|
5
|
-
const bar = chalk.blue('{bar}');
|
|
6
|
-
const percentage = chalk.yellow('{percentage}%');
|
|
7
|
-
const eta = chalk.blue('ETA: ') + chalk.yellow('{eta_formatted}');
|
|
8
|
-
const stage = chalk.gray('{stage}...');
|
|
9
|
-
const divider = chalk.blue('|');
|
|
10
|
-
|
|
11
|
-
export const WRITING_TO_FILE_PERCENTAGE = 0.05;
|
|
12
|
-
|
|
13
|
-
export const progressBar = new SingleBar({
|
|
14
|
-
format: `${title} ${divider}${bar}${divider} ${percentage} ${divider} ${eta} ${divider} ${stage} `,
|
|
15
|
-
barCompleteChar: '\u2588',
|
|
16
|
-
barIncompleteChar: '\u2591',
|
|
17
|
-
stopOnComplete: true,
|
|
18
|
-
barsize: 40,
|
|
19
|
-
etaBuffer: 50,
|
|
20
|
-
});
|