image-exporter 0.0.1 → 1.0.1

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.
Files changed (56) hide show
  1. package/README.md +137 -2
  2. package/bun.lockb +0 -0
  3. package/dist/image-exporter.es.js +511 -612
  4. package/dist/image-exporter.umd.js +518 -619
  5. package/package.json +18 -15
  6. package/src/capture/capture-element.ts +78 -0
  7. package/src/capture/determine-total-elements.ts +40 -0
  8. package/src/capture/download-images.ts +69 -0
  9. package/src/capture/get-image-options.ts +196 -0
  10. package/src/capture/handle-filenames.ts +52 -0
  11. package/src/capture/index.ts +99 -0
  12. package/src/capture/remove-hidden-elements.ts +19 -0
  13. package/src/config.ts +19 -0
  14. package/src/cors-proxy/cleanup.ts +43 -0
  15. package/src/cors-proxy/index.ts +6 -21
  16. package/src/{utils → cors-proxy}/is-valid-url.ts +5 -3
  17. package/src/cors-proxy/proxy-css.ts +51 -44
  18. package/src/cors-proxy/proxy-images.ts +21 -77
  19. package/src/cors-proxy/run.ts +26 -0
  20. package/src/index.ts +10 -4
  21. package/src/logger.ts +61 -0
  22. package/src/types.d.ts +51 -0
  23. package/vite.config.js +3 -7
  24. package/example/example.css +0 -122
  25. package/example/example.html +0 -152
  26. package/example/github.jpg +0 -0
  27. package/example/poll-h.svg +0 -1
  28. package/src/capture-images.ts +0 -129
  29. package/src/clean-up.ts +0 -50
  30. package/src/default-options.ts +0 -58
  31. package/src/download-images.ts +0 -52
  32. package/src/get-capture-element.test.html +0 -21
  33. package/src/get-capture-element.test.ts +0 -36
  34. package/src/get-capture-element.ts +0 -175
  35. package/src/get-options/get-input-options.test.html +0 -217
  36. package/src/get-options/get-input-options.test.ts +0 -109
  37. package/src/get-options/get-input-options.ts +0 -40
  38. package/src/get-options/get-item-options.ts +0 -46
  39. package/src/get-options/get-wrapper-options.test.html +0 -33
  40. package/src/get-options/get-wrapper-options.test.ts +0 -109
  41. package/src/get-options/get-wrapper-options.ts +0 -84
  42. package/src/get-options/index.ts +0 -28
  43. package/src/image-exporter.ts +0 -108
  44. package/src/types/image.ts +0 -2
  45. package/src/types/index.ts +0 -2
  46. package/src/types/options.ts +0 -69
  47. package/src/utils/convert-to-slug.ts +0 -15
  48. package/src/utils/get-attribute-values.ts +0 -68
  49. package/src/utils/get-date-MMDDYY.ts +0 -11
  50. package/src/utils/ignore-items.ts +0 -11
  51. package/src/utils/index.ts +0 -18
  52. package/src/utils/is-visible.ts +0 -12
  53. package/src/utils/parse-labels.ts +0 -55
  54. package/src/utils/push-to-window.ts +0 -3
  55. package/tests/index.html +0 -88
  56. package/tests/input-tests.html +0 -169
@@ -1,108 +0,0 @@
1
- import * as types from "./types";
2
- import { determineOptions, getItemOptions } from "./get-options";
3
- import { findMultiScaleElements, getCaptureElements } from "./get-capture-element";
4
- import { captureImage, captureImages } from "./capture-images";
5
- import { downloadImages } from "./download-images";
6
- import { runCorsProxy } from "./cors-proxy";
7
- import { ignoreFilter } from "./utils/ignore-items";
8
- import { defaultOptions } from "./default-options";
9
- import { cleanUp } from "./clean-up";
10
-
11
- // TODO: ignored nodes not working
12
-
13
- export class ImageExporter {
14
- options: types.Options;
15
-
16
- constructor(userOptions = {}) {
17
- this.options = { ...defaultOptions, ...userOptions };
18
- }
19
-
20
- /**
21
- * Captures images from all elements specified in the options.
22
- * If downloadImages is set to true, the images will be downloaded.
23
- *
24
- * @returns {types.Image[]} An array of captured images.
25
- */
26
- async captureAll(): Promise<types.Image[]> {
27
- console.log(this.options);
28
- this.options = determineOptions(this.options);
29
-
30
- const captureElements = getCaptureElements(this.options);
31
-
32
- const images = await captureImages(this.options, captureElements);
33
-
34
- if (this.options.downloadImages) downloadImages(images, this.options);
35
-
36
- cleanUp(this.options, captureElements);
37
-
38
- return images;
39
- }
40
-
41
- /**
42
- * Captures an image from a single element.
43
- * If downloadImages is set to true, the image will be downloaded.
44
- *
45
- * If multiscale elements are found,
46
- * the element will be cloned and captured at each scale.
47
- */
48
- async captureElement(element: HTMLElement): Promise<types.Image | types.Image[]> {
49
- this.options = determineOptions(this.options);
50
-
51
- await runCorsProxy(this.options);
52
-
53
- const ignoredNodes = ignoreFilter(this.options);
54
-
55
- const multiScale = findMultiScaleElements(this.options);
56
-
57
- if (multiScale) {
58
- const elements = Array.from(
59
- document.querySelectorAll(
60
- "[ie-clone], [ie-clone-source]"
61
- ) as NodeListOf<HTMLElement>
62
- );
63
- const images = await captureImages(this.options, elements);
64
-
65
- if (this.options.downloadImages) downloadImages(images, this.options);
66
-
67
- cleanUp(this.options, elements);
68
-
69
- return images;
70
- }
71
-
72
- const image = await captureImage(
73
- element,
74
- getItemOptions(element, this.options, 1),
75
- ignoredNodes
76
- );
77
-
78
- if (this.options.downloadImages) downloadImages([image], this.options);
79
-
80
- cleanUp(this.options, [element]);
81
-
82
- return image;
83
- }
84
-
85
- /**
86
- * Adds a click event listener to the trigger element.
87
- * If no element is provided, the captureAll method will be run on click.
88
- * If an element is provided, provided element will be captured on click.
89
- */
90
- addTrigger(triggerSelector: string, element: HTMLElement | null = null) {
91
- const triggerElement = document.querySelector(triggerSelector);
92
- if (!triggerElement) throw new Error("Trigger element not found");
93
-
94
- if (!element) {
95
- // add event listener to trigger element. on click, run captureAll
96
- triggerElement.addEventListener("click", () => {
97
- this.captureAll();
98
- });
99
- console.log("Listener added to trigger element");
100
- } else {
101
- // add event listener to trigger element. on click, run captureElement
102
- triggerElement.addEventListener("click", () => {
103
- this.captureElement(element);
104
- });
105
- console.log("Listener added to trigger element");
106
- }
107
- }
108
- }
@@ -1,2 +0,0 @@
1
- // First is dataURL, second is fileName
2
- export type Image = [string, string];
@@ -1,2 +0,0 @@
1
- export * from "./options";
2
- export * from "./image";
@@ -1,69 +0,0 @@
1
- export interface Options {
2
- corsProxyBaseUrl: string;
3
- downloadImages: boolean;
4
- selectors: Selectors;
5
- image: ImageOptions;
6
- zip: ZipOptions;
7
- debug: boolean;
8
- }
9
-
10
- export interface Selectors {
11
- wrapper: string;
12
- capture: string;
13
- trigger: string;
14
- slug: string;
15
- ignore: string;
16
- }
17
-
18
- export interface ImageOptions {
19
- scale: NumberSetting;
20
- quality: NumberSetting;
21
- format: ImageSetting;
22
- dateInLabel: BooleanSetting;
23
- scaleInLabel: BooleanSetting;
24
- }
25
-
26
- export interface ZipOptions {
27
- label: {
28
- value: string;
29
- attributeSelector: string;
30
- inputSelector: string;
31
- };
32
- dateInLabel: {
33
- value: boolean;
34
- attributeSelector: string;
35
- inputSelector: string;
36
- };
37
- scaleInLabel: {
38
- value: boolean;
39
- attributeSelector: string;
40
- inputSelector: string;
41
- };
42
- }
43
-
44
- export interface ItemOptions extends Options {
45
- // Options +
46
- id: number;
47
- userSlug: string;
48
- slug: string;
49
- fileName: string;
50
- }
51
-
52
- export interface Setting {
53
- attributeSelector: string;
54
- inputSelector: string;
55
- value: string | number | boolean | "jpg" | "png";
56
- }
57
-
58
- export interface StringSetting extends Setting {
59
- value: string;
60
- }
61
- export interface NumberSetting extends Setting {
62
- value: number;
63
- }
64
- export interface BooleanSetting extends Setting {
65
- value: boolean;
66
- }
67
- export interface ImageSetting extends Setting {
68
- value: "jpg" | "png";
69
- }
@@ -1,15 +0,0 @@
1
- /**
2
- * Converts a given string to a URL-friendly slug.
3
- *
4
- * @param input - The string to be converted to a slug.
5
- * @returns The input string transformed into a slug format.
6
- */
7
- export function convertToSlug(input: string) {
8
- if (!input) {
9
- return null;
10
- }
11
- input = input.toLowerCase();
12
- input = input.replace(/[^a-z0-9_@ -]/g, "");
13
- input = input.replace(/\s+/g, "-");
14
- return input;
15
- }
@@ -1,68 +0,0 @@
1
- import * as types from "../types";
2
-
3
- /**
4
- * Option determination order:
5
- * 1. User input
6
- * 2. Item attribute
7
- * 3. Wrapper attribute
8
- * 4. Default
9
- *
10
- * Meaning wrapper overwrites default, item overwrites wrapper, and user input overwrites all.
11
- */
12
-
13
- /**
14
- * Checks if the user provided a value via attribute on an input element.
15
- * If the user provided a value, it returns the value. Otherwise, it returns null.
16
- */
17
- export function getUserInputValue(imageSetting: types.Setting): string | null {
18
- try {
19
- const inputElement = document.querySelector(
20
- imageSetting.attributeSelector
21
- ) as HTMLInputElement | null;
22
-
23
- if (inputElement) {
24
- return inputElement.value;
25
- } else return null;
26
- } catch (e) {
27
- console.error("ImageExporter: Error in getUserInputValue", e);
28
- return null;
29
- }
30
- }
31
-
32
- /**
33
- * Checks if the user provided a value via attribute on the wrapper element.
34
- * If the user provided a value, it returns the value. Otherwise, it returns null.
35
- */
36
- export function getWrapperValue(
37
- options: types.Options,
38
- attribute: string,
39
- wrapperSelector: string
40
- ): string | null {
41
- try {
42
- console.log("📣 - wrapperSelector:", wrapperSelector);
43
- const wrapperElement = document.querySelector(wrapperSelector);
44
- console.log(wrapperElement ? wrapperElement.getAttribute(attribute) : null);
45
- return wrapperElement ? wrapperElement.getAttribute(attribute) : null;
46
- } catch (e) {
47
- console.error("ImageExporter: Error in getWrapperValue", e);
48
- return null;
49
- }
50
- }
51
-
52
- /**
53
- * Checks if the user provided a value via attribute on an item element.
54
- * If the user provided a value, it returns the value. Otherwise, it returns null.
55
- */
56
- export function getItemValue(options: types.Options, attribute, element): string | null {
57
- try {
58
- const itemValue = element.querySelector(attribute);
59
- if (itemValue) {
60
- return itemValue;
61
- } else {
62
- return null;
63
- }
64
- } catch (e) {
65
- console.error("ImageExporter: Error in getItemValue", e);
66
- return null;
67
- }
68
- }
@@ -1,11 +0,0 @@
1
- /**
2
- *
3
- * @returns {string} Current date formatted as MMDDYY
4
- */
5
- export function getDateMMDDYY() {
6
- return (
7
- String(new Date().getMonth() + 1).padStart(2, "0") +
8
- String(new Date().getDate()).padStart(2, "0") +
9
- new Date().getFullYear().toString().slice(-2)
10
- );
11
- }
@@ -1,11 +0,0 @@
1
- import * as types from "../types";
2
-
3
- // Return a function that checks if a node does not match the ignore selector
4
- export function ignoreFilter(options: types.Options) {
5
- return (node: HTMLElement) => {
6
- if (!(node instanceof HTMLElement)) {
7
- throw new Error("The provided node is not an HTMLElement");
8
- }
9
- return !node.matches(options.selectors.ignore);
10
- };
11
- }
@@ -1,18 +0,0 @@
1
- import { getItemValue, getUserInputValue, getWrapperValue } from "./get-attribute-values";
2
- import { convertToSlug } from "./convert-to-slug";
3
- import { isVisible } from "./is-visible";
4
- import { getDateMMDDYY } from "./get-date-MMDDYY";
5
- import { parseImageLabel, parseZipLabel } from "./parse-labels";
6
- import { isValidUrl } from "./is-valid-url";
7
-
8
- export {
9
- isValidUrl,
10
- getItemValue,
11
- getWrapperValue,
12
- getUserInputValue as getUserValue,
13
- isVisible,
14
- convertToSlug,
15
- getDateMMDDYY,
16
- parseZipLabel,
17
- parseImageLabel,
18
- };
@@ -1,12 +0,0 @@
1
- /**
2
- * Determines if a given element is currently visible on the page.
3
- *
4
- * @param element - The DOM element to check for visibility.
5
- * @returns True if the element is visible, false otherwise.
6
- */
7
- export function isVisible(element: Element): boolean {
8
- if (!(element instanceof HTMLElement)) return false;
9
- if (element.offsetParent === null) return false;
10
- const rect = element.getBoundingClientRect();
11
- return rect.width > 0 && rect.height > 0;
12
- }
@@ -1,55 +0,0 @@
1
- import * as types from "../types";
2
- import { convertToSlug } from "./convert-to-slug";
3
- import { getDateMMDDYY } from "./get-date-MMDDYY";
4
-
5
- /**
6
- * Generates a zip file label based on user settings.
7
- *
8
- * This function creates a label for a zip file using various parameters from the user settings.
9
- * It includes optional elements such as a date stamp and scale factor in the filename, depending on the user's preferences.
10
- *
11
- * @param {Object} options - The settings provided by the user.
12
- * It is expected to have the following properties:
13
- * - includeScaleZip: {boolean} - Determines if scale factor should be included in the label.
14
- * - fileScale: {number} - The scale factor to be used if includeScaleZip is true.
15
- * - includeDateZip: {boolean} - Determines if the current date should be included in the label.
16
- * - zipName: {string} - The base name of the zip file.
17
- * @returns {string|null} The formatted zip file label, or null if userSettings is not provided.
18
- *
19
- */
20
-
21
- export function parseZipLabel(options: types.Options): string {
22
- const date = getDateMMDDYY();
23
- const zipScale = convertStringToBoolean(options.zip.scaleInLabel.value)
24
- ? `_@${options.image.scale.value}x`
25
- : "";
26
- const zipDate = convertStringToBoolean(options.zip.dateInLabel.value) ? `_${date}` : "";
27
- const zipNameSlug = convertToSlug(options.zip.label.value);
28
- const zipLabel = `${zipNameSlug}${zipDate}${zipScale}.zip`;
29
-
30
- return zipLabel;
31
- }
32
-
33
- export function parseImageLabel(itemOptions: types.ItemOptions): string {
34
- const date = getDateMMDDYY();
35
- const imgScale = itemOptions.image.scaleInLabel.value
36
- ? `_@${itemOptions.image.scale.value}x`
37
- : "";
38
- const imgDate = itemOptions.image.dateInLabel.value ? `_${date}` : "";
39
- const imgNameSlug = convertToSlug(itemOptions.userSlug) || `img-${itemOptions.id}`;
40
- const imgLabel = `${imgNameSlug}${imgDate}${imgScale}`;
41
-
42
- return imgLabel;
43
- }
44
-
45
- function convertStringToBoolean(str: boolean | string): boolean | string {
46
- // Convert "true" or "false" to boolean
47
- if (str === "true") {
48
- return true;
49
- } else if (str === "false") {
50
- return false;
51
- }
52
-
53
- // Return the original string if no conversion is possible
54
- return str;
55
- }
@@ -1,3 +0,0 @@
1
- export function pushToWindow(propertyName: string, value: any): void {
2
- (window as any)[propertyName] = value;
3
- }
package/tests/index.html DELETED
@@ -1,88 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Tests</title>
7
- <link
8
- rel="stylesheet"
9
- href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
10
- />
11
- <script src="/dist/image-exporter.umd.js"></script>
12
-
13
- <style>
14
- slug {
15
- display: none;
16
- }
17
- artboard {
18
- border: 1px solid #2d3138;
19
- }
20
- section {
21
- justify-content: flex-start;
22
- align-items: flex-start;
23
- flex-direction: column;
24
- padding: 1rem 0;
25
- }
26
- .test-1 {
27
- padding: 1rem;
28
- background-color: #f0f0f0;
29
- color: #333;
30
- width: 300px;
31
- height: 300px;
32
- font-weight: 800;
33
- display: flex;
34
- justify-content: center;
35
- align-items: center;
36
- background: linear-gradient(45deg, #01aaff, #0172ad);
37
- color: white;
38
- margin: 0rem;
39
- }
40
- </style>
41
- </head>
42
- <body>
43
- <header>
44
- <h1>Tests</h1>
45
- <p>Exports a series of artboards simulating common scenarios.</p>
46
- <p>Check each export on build.</p>
47
- <button gf="trigger">Run</button>
48
- <label for="format" class="input-label">File Format</label>
49
- <select title="format" ie-format-input class="example-input">
50
- <option value="png">PNG</option>
51
- <option value="jpg">JPG</option>
52
- </select>
53
-
54
- <label for="scale" class="input-label">Scale</label>
55
- <select title="scale" ie-scale-input class="example-input">
56
- <option value="1">@1x</option>
57
- <option value="2">@2x</option>
58
- <option value="3">@3x</option>
59
- <option value="4">@4x</option>
60
- </select>
61
- </header>
62
- <main ie="wrapper" ie-scale="4">
63
- <section>
64
- <h2 gf="test-trigger">Test 1</h2>
65
- <artboard ie="capture" class="test-1">
66
- <slug ie="slug">ping</slug>
67
- <div>This is the headline</div>
68
- </artboard>
69
- <artboard ie="capture" class="test-1" ie-scale="1,2,3,4" test="test">
70
- <slug ie="slug">pong</slug>
71
- <div>This is the headline</div>
72
- </artboard>
73
- </section>
74
- </main>
75
- <script>
76
- let options = {
77
- corsProxyBaseUrl: "http://localhost:8010/",
78
- };
79
-
80
- let imageExporter = new ImageExporter({ options });
81
- imageExporter.addTrigger(document.querySelector("[gf=trigger]"));
82
- imageExporter.addTrigger(
83
- document.querySelector("[gf=test-trigger]"),
84
- document.querySelector("[test=test]")
85
- );
86
- </script>
87
- </body>
88
- </html>
@@ -1,169 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Tests</title>
7
- <link
8
- rel="stylesheet"
9
- href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
10
- />
11
- <script src="/dist/image-exporter.umd.js"></script>
12
-
13
- <style>
14
- slug {
15
- display: none;
16
- }
17
- artboard {
18
- border: 1px solid #2d3138;
19
- }
20
- section {
21
- justify-content: flex-start;
22
- align-items: flex-start;
23
- flex-direction: column;
24
- padding: 1rem 0;
25
- }
26
- .artboard-1 {
27
- padding: 1rem;
28
- background-color: #f0f0f0;
29
- color: #333;
30
- width: 300px;
31
- height: 300px;
32
- font-weight: 800;
33
- display: flex;
34
- justify-content: center;
35
- align-items: center;
36
- background: linear-gradient(45deg, #01aaff, #0172ad);
37
- color: white;
38
- margin: 0rem;
39
- }
40
- </style>
41
- </head>
42
- <body>
43
- <header>
44
- <h1>Input Tests</h1>
45
- <hr />
46
-
47
- <h2>Inputs</h2>
48
- <div>
49
- <!-- Scale -->
50
- <label for="scale"><strong>Scale</strong></label>
51
- <select title="scale" ie-scale-input>
52
- <option value="1">@1x</option>
53
- <option value="2">@2x</option>
54
- <option value="3">@3x</option>
55
- <option value="4">@4x</option>
56
- </select>
57
- </div>
58
- <div>
59
- <!-- Quality -->
60
- <label for="quality"><strong>Quality</strong></label>
61
- <input
62
- type="range"
63
- id="quality"
64
- name="quality"
65
- min="0.00"
66
- max="1.00"
67
- value="1"
68
- step="0.01"
69
- ie-quality-input
70
- />
71
- </div>
72
- <div>
73
- <!-- Format -->
74
- <label for="format"><strong>Format</strong></label>
75
- <select title="format" ie-format-input>
76
- <option value="png">PNG</option>
77
- <option value="jpg">JPG</option>
78
- </select>
79
- </div>
80
- <div>
81
- <fieldset>
82
- <legend><strong>Image label</strong></legend>
83
- <!-- dateInLabel -->
84
- <label for="dateInLabel">
85
- <input
86
- type="checkbox"
87
- id="dateInLabel"
88
- name="dateInLabel"
89
- name="dateInLabel"
90
- role="switch"
91
- checked=""
92
- ie-img-label-date-input
93
- />
94
- Date in label
95
- </label>
96
- <!--scaleInLabel-->
97
- <label for="scaleInLabel">
98
- <input
99
- type="checkbox"
100
- id="scaleInLabel"
101
- name="scaleInLabel"
102
- name="scaleInLabel"
103
- role="switch"
104
- checked=""
105
- ie-img-label-scale-input
106
- />
107
- Scale in label
108
- </label>
109
- </fieldset>
110
- </div>
111
- <fieldset>
112
- <legend><strong>Zip label</strong></legend>
113
- <!-- zipLabel -->
114
- <label for="zipLabel">Label</label>
115
- <input type="text" id="zipLabel" name="zipLabel" ie-zip-label-input />
116
- <!-- zipDateInLabel -->
117
- <label for="zipDateInLabel">
118
- <input
119
- type="checkbox"
120
- id="zipDateInLabel"
121
- name="zipDateInLabel"
122
- name="zipDateInLabel"
123
- role="switch"
124
- checked=""
125
- ie-zip-label-date-input
126
- />
127
- Date in label
128
- </label>
129
- <!--zipScaleInLabel-->
130
- <label for="zipScaleInLabel">
131
- <input
132
- type="checkbox"
133
- id="zipScaleInLabel"
134
- name="zipScaleInLabel"
135
- name="zipScaleInLabel"
136
- role="switch"
137
- checked=""
138
- ie-zip-label-scale-input
139
- />
140
- Scale in label
141
- </label>
142
- </fieldset>
143
-
144
- <button ie-trigger>Run</button>
145
- </header>
146
- <hr />
147
- <main ie="wrapper">
148
- <section>
149
- <h2>Artboards</h2>
150
- <artboard ie="capture" class="artboard-1">
151
- <slug ie="slug">ping</slug>
152
- <div>This is the headline</div>
153
- </artboard>
154
- <artboard ie="capture" class="artboard-1">
155
- <slug ie="slug">pong</slug>
156
- <div>This is the headline</div>
157
- </artboard>
158
- </section>
159
- </main>
160
- <script>
161
- const testOptions = {
162
- corsProxyBaseUrl: "http://localhost:8010/",
163
- };
164
-
165
- let imageExporter = new ImageExporter({ testOptions });
166
- imageExporter.addTrigger("[ie-trigger]");
167
- </script>
168
- </body>
169
- </html>