image-exporter 0.0.1 → 1.0.0

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 +499 -612
  4. package/dist/image-exporter.umd.js +506 -619
  5. package/package.json +18 -15
  6. package/src/capture/capture-element.ts +58 -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,152 +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>image-exporter Example</title>
7
- <link rel="stylesheet" href="example.css" />
8
- </head>
9
-
10
- <body>
11
- <div class="gf_loader">
12
- <div class="gf_loader-message">Loading...</div>
13
- </div>
14
- <div class="wrapper">
15
- <div>
16
- <form action="" autocomplete="off" class="input-wrapper">
17
- <!--You can set settings via user input like this-->
18
- <label for="format" class="input-label">File Format</label>
19
- <select title="format" gf-format-input class="example-input">
20
- <option value="png">PNG</option>
21
- <option value="jpg">JPG</option>
22
- </select>
23
-
24
- <label for="scale" class="input-label">Scale</label>
25
- <select title="scale" gf-scale-input class="example-input">
26
- <option value="1">@1x</option>
27
- <option value="2">@2x</option>
28
- <option value="3">@3x</option>
29
- <option value="4">@4x</option>
30
- </select>
31
-
32
- <label for="zipname" class="input-label">Zip Name</label>
33
- <input
34
- title="zipname"
35
- type="text"
36
- value="images"
37
- gf-zip-label-input
38
- class="example-input"
39
- />
40
-
41
- <label for="zip-label-date" class="input-label">Zip Label Date</label>
42
- <select title="zip-label-date" gf-zip-label-date-input class="example-input">
43
- <option value="true">Enabled</option>
44
- <option value="false">Disabled</option>
45
- </select>
46
-
47
- <label for="zip-label-scale" class="input-label">Zip Label Scale</label>
48
- <select title="zip-label-scale" gf-zip-label-scale-input class="example-input">
49
- <option value="true">Enabled</option>
50
- <option value="false">Disabled</option>
51
- </select>
52
-
53
- <div gf="trigger" class="example-button">Generate</div>
54
- </form>
55
- </div>
56
- <div class="example-wrapper" gf="wrapper" gf-zip-label="test">
57
- <div class="example-1" gf="capture" gf-scale="1,4">
58
- <div class="example-text-1" gf="slug">Example image 1</div>
59
- </div>
60
- <div class="example-2" gf="capture">
61
- <div class="example-text-2" gf="slug">Example image 2</div>
62
- </div>
63
- </div>
64
- </div>
65
- <script src="../dist/image-exporter.umd.js" type="text/javascript"></script>
66
- <script>
67
- let options = {
68
- corsProxyBaseUrl: "http://localhost:8010/",
69
- selectors: {
70
- wrapper: "[gf='wrapper']",
71
- capture: "[gf='capture']",
72
- trigger: "[gf='trigger']",
73
- slug: "[gf='slug']",
74
- ignore: "[gf='ignore']",
75
- },
76
- image: {
77
- scale: {
78
- value: 1,
79
- attributeSelector: "gf-scale",
80
- inputSelector: "gf-scale-input",
81
- },
82
- quality: {
83
- value: 1,
84
- attributeSelector: "gf-quality",
85
- inputSelector: "gf-quality-input",
86
- },
87
- format: {
88
- value: "jpg",
89
- attributeSelector: "gf-format",
90
- inputSelector: "gf-format-input",
91
- },
92
- dateInLabel: {
93
- value: true,
94
- attributeSelector: "gf-img-label-date",
95
- inputSelector: "gf-img-label-date-input",
96
- },
97
- scaleInLabel: {
98
- value: true,
99
- attributeSelector: "gf-img-label-scale",
100
- inputSelector: "gf-img-label-scale-input",
101
- },
102
- },
103
- zip: {
104
- label: {
105
- value: "images",
106
- attributeSelector: "gf-zip-label",
107
- inputSelector: "gf-zip-label-input",
108
- },
109
- dateInLabel: {
110
- value: true,
111
- attributeSelector: "gf-zip-label-date",
112
- inputSelector: "gf-zip-label-date-input",
113
- },
114
- scaleInLabel: {
115
- value: true,
116
- attributeSelector: "gf-zip-label-scale",
117
- inputSelector: "gf-zip-label-scale-input",
118
- },
119
- },
120
- };
121
-
122
- let imageExporter = new ImageExporter({ options });
123
- imageExporter.addTrigger(document.querySelector("[gf=trigger]"));
124
- // imageExporter.addTrigger(
125
- // document.querySelector("[gf=test-trigger]"),
126
- // document.querySelector("[test=test]")
127
- // );
128
- </script>
129
- <script>
130
- let optionsx = {
131
- inputPrefix: "gf",
132
- attributes: {
133
- slugSelector: "[gf='slug']",
134
- wrapperSelector: '[gf="wrapper"]',
135
- captureSelector: '[gf="capture"]',
136
- triggerSelector: '[gf="trigger"]',
137
- slugSelector: '[gf="slug"]',
138
- ignoreSelector: '[gf="ignore"]',
139
- scale: "gf-scale",
140
- quality: "gf-quality",
141
- format: "gf-format",
142
- zipLabel: "gf-zip-label",
143
- zipLabelDate: "gf-zip-label-date",
144
- zipLabelScale: "gf-zip-label-scale",
145
- imgLabelDate: "gf-img-label-date",
146
- imgLabelScale: "gf-img-label-scale",
147
- corsProxyBaseUrl: "gf-cors-proxy-base-url",
148
- },
149
- };
150
- </script>
151
- </body>
152
- </html>
Binary file
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M448 432V80c0-26.5-21.5-48-48-48H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48zM112 192c-8.84 0-16-7.16-16-16v-32c0-8.84 7.16-16 16-16h128c8.84 0 16 7.16 16 16v32c0 8.84-7.16 16-16 16H112zm0 96c-8.84 0-16-7.16-16-16v-32c0-8.84 7.16-16 16-16h224c8.84 0 16 7.16 16 16v32c0 8.84-7.16 16-16 16H112zm0 96c-8.84 0-16-7.16-16-16v-32c0-8.84 7.16-16 16-16h64c8.84 0 16 7.16 16 16v32c0 8.84-7.16 16-16 16h-64z"/></svg>
@@ -1,129 +0,0 @@
1
- import * as types from "./types";
2
- import * as htmlToImage from "html-to-image";
3
- import { runCorsProxy } from "./cors-proxy";
4
- import { ignoreFilter } from "./utils/ignore-items";
5
- import { getItemOptions } from "./get-options";
6
-
7
- /**
8
- * Asynchronously captures images from a set of DOM elements using specified options.
9
- *
10
- *
11
- * This function is designed to capture images from a collection of elements. It first initializes
12
- * a CORS proxy and prepares nodes that should be ignored during the image capture process. Then, for
13
- * each element in the `captureElements` array, it captures an image using the `captureImage` function
14
- * with options tailored to each element. The function handles these operations asynchronously and
15
- * collects all the captured images in an array.
16
- *
17
- * @param {Object} options - An object containing global options for image capture.
18
- * @param {Array<HTMLElement>} captureElements - An array of DOM elements from which images will be captured.
19
- * @returns {Promise<Array>} A promise that resolves to an array of captured images.
20
- *
21
- */
22
- export async function captureImages(
23
- options: types.Options,
24
- captureElements: HTMLElement[]
25
- ): Promise<types.Image[]> {
26
- try {
27
- // When enabled, replaces urls with proxied ones to bypass CORS errors.
28
- await runCorsProxy(options);
29
-
30
- // Gets array of tuples representing images, see captureImage() documentation for more info
31
- const images = await Promise.all(
32
- captureElements.map((element, index) =>
33
- captureImage(
34
- element,
35
- getItemOptions(element, options, index + 1),
36
- ignoreFilter(options)
37
- )
38
- )
39
- );
40
- console.log(images);
41
- return images;
42
- } catch (e) {
43
- console.error("ImageExporter: Error in captureImages", e);
44
- return [];
45
- }
46
- }
47
-
48
- /**
49
- * Asynchronously captures an image from a DOM element with specific options.
50
- *
51
- * This function is responsible for capturing an image of the provided DOM element. It updates a
52
- * loading message based on the 'slug' property in the options. The image capture is then performed
53
- * using the 'htmlToImage' library, with settings tailored according to the provided options. The function
54
- * supports different image formats and handles quality and scaling. The result is an image encoded
55
- * as a data URL along with its filename.
56
- *
57
- * @param {HTMLElement} element - The DOM element from which the image is to be captured.
58
- * @param {Object} itemOptions - An object containing options for the capture process. It includes properties
59
- * like 'slug', 'format', 'quality', 'scale', and 'loaderEnabled'.
60
- * @returns {Promise<[string, string]>} A promise that resolves to a tuple: [dataURL, fileName].
61
- * 'dataURL' is the base64 encoded image, and 'fileName' is the name of the image file.
62
- *
63
- */
64
-
65
- export async function captureImage(
66
- element,
67
- itemOptions: types.ItemOptions,
68
- ignoreFilter
69
- ): Promise<types.Image> {
70
- try {
71
- itemOptions.slug = ensureUniqueSlug(itemOptions.slug);
72
-
73
- let dataURL = "";
74
- // Final settings for capturing images.
75
- let htmlToImageOptions = {
76
- // Ensure quality is a number
77
- quality: itemOptions.image.quality.value,
78
- // Ensure scale is a number
79
- pixelRatio: itemOptions.image.scale.value,
80
- // Function that returns false if the element should be ignored
81
- // filter: ignoreFilter,
82
- };
83
-
84
- // Captures image based on format
85
- switch (itemOptions.image.format.value.toLowerCase()) {
86
- case "jpg":
87
- dataURL = await htmlToImage.toJpeg(element, htmlToImageOptions);
88
- itemOptions.fileName = `${itemOptions.slug}.jpg`;
89
- console.log("Captured image as jpg", itemOptions.fileName);
90
- break;
91
- case "png":
92
- default:
93
- dataURL = await htmlToImage.toPng(element, htmlToImageOptions);
94
- itemOptions.fileName = `${itemOptions.slug}.png`;
95
- console.log("Captured image as png", itemOptions.fileName);
96
- break;
97
- }
98
- // const image: types.Image = [dataURL, itemOptions.fileName];
99
-
100
- // returns image stored in tuple. [dataURL, fileName]
101
- return [dataURL, itemOptions.fileName];
102
- } catch (e) {
103
- console.error("ImageExporter: Error in captureImage", e);
104
- return ["", ""];
105
- }
106
- }
107
-
108
- let usedSlugs: any = [];
109
-
110
- function ensureUniqueSlug(slug: string): string {
111
- try {
112
- if (usedSlugs.includes(slug)) {
113
- let counter = 1;
114
- let newSlug = `${slug}-${counter}`;
115
- while (usedSlugs.includes(newSlug)) {
116
- counter++;
117
- newSlug = `${slug}-${counter}`;
118
- }
119
- usedSlugs.push(newSlug);
120
- return newSlug;
121
- } else {
122
- usedSlugs.push(slug);
123
- return slug;
124
- }
125
- } catch (e) {
126
- console.error("ImageExporter: Error in ensureUniqueSlug", e);
127
- return slug;
128
- }
129
- }
package/src/clean-up.ts DELETED
@@ -1,50 +0,0 @@
1
- import * as types from "./types";
2
-
3
- /**
4
- * This function cleans up after the `findMultiScaleElements` feature.
5
- * It removes cloned elements and sets the `ie-scale` attribute back to the csv value.
6
- *
7
- * Eventually this may become a subfunction, with this function having more tasks.
8
- *
9
- * @param options
10
- * @param captureElements
11
- */
12
- export function cleanUp(options: types.Options, captureElements: Element[]) {
13
- // Find 'ie-clone-source', if exists...
14
- const sourceElements = document.querySelectorAll("[ie-clone-source]");
15
- if (sourceElements) {
16
- console.log("ping");
17
- sourceElements.forEach((sourceElement) => {
18
- // Set attribute options.attributes.scale.attributeSelector to the value of 'ie-clone-source'
19
- const attributeValue = sourceElement.getAttribute("ie-clone-source");
20
- sourceElement.removeAttribute("ie-clone-source");
21
- if (attributeValue) {
22
- console.log("pong");
23
- sourceElement.setAttribute(options.image.scale.attributeSelector, attributeValue);
24
- }
25
-
26
- // Climb DOM to find last parent with 'ie-clone' attribute
27
- let parentElement = sourceElement.parentElement;
28
- let lastCloneParent: any = null;
29
- while (parentElement) {
30
- if (parentElement.hasAttribute("ie-clone")) {
31
- lastCloneParent = parentElement;
32
- }
33
- parentElement = parentElement.parentElement;
34
- }
35
-
36
- if (lastCloneParent) {
37
- // Move sourceElement to next sibling of the last parent with 'ie-clone' attribute
38
- const nextSibling = lastCloneParent.nextElementSibling;
39
- if (nextSibling) {
40
- nextSibling.parentNode.insertBefore(sourceElement, nextSibling);
41
- } else {
42
- lastCloneParent.parentNode.appendChild(sourceElement);
43
- }
44
-
45
- // Remove the last parent with 'ie-clone' attribute
46
- lastCloneParent.remove();
47
- }
48
- });
49
- }
50
- }
@@ -1,58 +0,0 @@
1
- import * as types from "./types";
2
-
3
- export const defaultOptions: types.Options = {
4
- corsProxyBaseUrl: "",
5
- downloadImages: true,
6
- selectors: {
7
- wrapper: "[ie='wrapper']",
8
- capture: "[ie='capture']",
9
- trigger: "[ie='trigger']",
10
- slug: "[ie='slug']",
11
- ignore: "[ie='ignore']",
12
- },
13
- image: {
14
- scale: {
15
- value: 1,
16
- attributeSelector: "ie-scale",
17
- inputSelector: "ie-scale-input",
18
- },
19
- quality: {
20
- value: 1,
21
- attributeSelector: "ie-quality",
22
- inputSelector: "ie-quality-input",
23
- },
24
- format: {
25
- value: "jpg",
26
- attributeSelector: "ie-format",
27
- inputSelector: "ie-format-input",
28
- },
29
- dateInLabel: {
30
- value: true,
31
- attributeSelector: "ie-img-label-date",
32
- inputSelector: "ie-img-label-date-input",
33
- },
34
- scaleInLabel: {
35
- value: true,
36
- attributeSelector: "ie-img-label-scale",
37
- inputSelector: "ie-img-label-scale-input",
38
- },
39
- },
40
- zip: {
41
- label: {
42
- value: "images",
43
- attributeSelector: "ie-zip-label",
44
- inputSelector: "ie-zip-label-input",
45
- },
46
- dateInLabel: {
47
- value: true,
48
- attributeSelector: "ie-zip-label-date",
49
- inputSelector: "ie-zip-label-date-input",
50
- },
51
- scaleInLabel: {
52
- value: true,
53
- attributeSelector: "ie-zip-label-scale",
54
- inputSelector: "ie-zip-label-scale-input",
55
- },
56
- },
57
- debug: false,
58
- };
@@ -1,52 +0,0 @@
1
- import * as types from "./types";
2
- import download from "downloadjs";
3
- import JSZip from "jszip";
4
- import { parseZipLabel } from "./utils";
5
-
6
- /**
7
- * Downloads images based on the provided array of images and options.
8
- * If there is only one image, it is downloaded individually.
9
- * If there are multiple images, they are zipped and downloaded as a single file.
10
- *
11
- * @param images - An array of images to be downloaded. Each image should be represented as a tuple with the data URL and the file name.
12
- * @param options - Additional options for downloading the images.
13
- * @returns A promise that resolves when all the images are downloaded.
14
- */
15
- export async function downloadImages(images: types.Image[], options: types.Options) {
16
- if (images.length === 1) {
17
- const [dataURL, fileName] = images[0];
18
-
19
- await download(dataURL, fileName);
20
- } else if (images.length > 1) {
21
- const zipName = parseZipLabel(options);
22
- const zipBlob = await zipUpImages(images);
23
-
24
- await download(zipBlob, zipName);
25
- }
26
- }
27
-
28
- /**
29
- * Zips up the given images into a single ZIP file.
30
- *
31
- * @param images - An array of image tuples, where each tuple contains the data URL and the filename.
32
- * @returns A Promise that resolves to the generated ZIP file as a Blob.
33
- * @throws If there is an error creating the ZIP file.
34
- */
35
- async function zipUpImages(images: types.Image[]) {
36
- const zip = new JSZip();
37
- // Loop through each image tuple and add to the zip
38
- images.forEach(([dataURL, filename]) => {
39
- // Extract the content from the data URL
40
- const content = dataURL.split(",")[1]; // Assumes base64 encoding
41
- zip.file(filename, content, { base64: true });
42
- });
43
-
44
- try {
45
- // Generate the ZIP file
46
- const zipBlob = await zip.generateAsync({ type: "blob" });
47
- return zipBlob;
48
- } catch (error) {
49
- console.error("Error creating ZIP:", error);
50
- throw error;
51
- }
52
- }
@@ -1,21 +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
- </head>
8
- <body>
9
- <div ie="wrapper">
10
- <div ie="capture" ie-scale="1,2,3"></div>
11
- <div ie="capture"></div>
12
- <div ie="capture"></div>
13
- <div ie="capture"></div>
14
- </div>
15
- <script src="../../dist/image-exporter.umd.js"></script>
16
- <script>
17
- let imageExporter = new ImageExporter({ downloadImages: false, debug: true });
18
- imageExporter.captureAll();
19
- </script>
20
- </body>
21
- </html>
@@ -1,36 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
- import puppeteer from "puppeteer";
3
- import path from "path";
4
- import * as types from "./types";
5
- import { defaultOptions } from "./default-options";
6
-
7
- describe("get-capture-element", () => {
8
- let browser;
9
- let page;
10
-
11
- beforeAll(async () => {
12
- // Setup for puppeteer with Vitest
13
- browser = await puppeteer.launch();
14
- page = await browser.newPage();
15
- await page.goto(`file://${path.join(__dirname, "get-capture-element.test.html")}`);
16
- });
17
-
18
- afterAll(async () => {
19
- // Teardown for puppeteer with Vitest
20
- await browser.close();
21
- });
22
-
23
- it("encapsulate multi-scale", async () => {
24
- const result = await page.evaluate(() => {
25
- const js = `const options = { downloadImages: false, debug: true };
26
- const imageExporter = new ImageExporter(options);
27
- const images = imageExporter.captureAll();`;
28
- //insert at the end of the body
29
- const script = document.createElement("script");
30
- script.text = js;
31
- document.body.appendChild(script);
32
-
33
- return (window as any).getInputOptionsDebug;
34
- });
35
- });
36
- });
@@ -1,175 +0,0 @@
1
- import * as types from "./types";
2
- import { isVisible } from "./utils/is-visible";
3
- import { pushToWindow } from "./utils/push-to-window";
4
-
5
- // Finds all elements to be captured and returns them in an array
6
- export function getCaptureElements(options: types.Options): HTMLElement[] {
7
- try {
8
- if (!document.querySelector(options.selectors.capture)) {
9
- console.error("ImageExporter: No capture items found in the wrapper.");
10
- return [];
11
- }
12
-
13
- // If CSV values found in scale attribute, encapsulate elements until all scales are accounted for
14
- findMultiScaleElements(options);
15
-
16
- // Find all elements to be captured (which includes the multi-scale elements)
17
- const elements = Array.from(
18
- document.querySelectorAll(
19
- `${options.selectors.wrapper} ${options.selectors.capture}`
20
- ) as NodeListOf<HTMLElement>
21
- );
22
-
23
- // Filter out elements that are not visible
24
- const visibleElements = elements.filter((element) => isVisible(element));
25
-
26
- return visibleElements;
27
- } catch (e) {
28
- console.error("ImageExporter: Error in getCaptureElements", e);
29
- return [];
30
- }
31
- }
32
-
33
- // If CSV values found in ${prefix}-scale, encapsulate elements until all scales are accounted for
34
- export function findMultiScaleElements(options: types.Options) {
35
- try {
36
- const elements = Array.from(
37
- document.querySelectorAll(
38
- `${options.selectors.wrapper} ${options.selectors.capture}`
39
- ) as NodeListOf<HTMLElement>
40
- );
41
- if (elements) {
42
- const elementsWithScaleAttribute = elements.filter((element) =>
43
- element.hasAttribute(options.image.scale.attributeSelector)
44
- );
45
-
46
- // Check attribute value. It will be a string.
47
- // If string successfully converts to a number, do nothing.
48
- // If string is comma-separated, convert to array of numbers.
49
- elementsWithScaleAttribute.forEach((element) => {
50
- const scaleValue: any = element.getAttribute(
51
- options.image.scale.attributeSelector
52
- );
53
-
54
- if (scaleValue.includes(",")) {
55
- console.log("Multi-scale element found:", scaleValue);
56
- // If scaleValue is an array...
57
- const scaleArray: Array<Number> = scaleValue.split(",").map(Number);
58
-
59
- encapsulateMultiScaleElements(options, element, scaleArray, scaleValue);
60
- if (options.debug)
61
- pushToWindow("findMultiScaleElementsTest", element.outerHTML);
62
- }
63
- });
64
- return true;
65
- } else {
66
- return false;
67
- }
68
- } catch (e) {
69
- console.error("ImageExporter: Error in findMultiScaleElements", e);
70
- return;
71
- }
72
- }
73
-
74
- function encapsulateMultiScaleElements(
75
- options: types.Options,
76
- element: Element,
77
- scaleArray: Array<Number>,
78
- scaleValue: string
79
- ) {
80
- try {
81
- // Set scale attribute
82
- element.setAttribute(options.image.scale.attributeSelector, scaleArray[0].toString());
83
- // Force include scale img attribute
84
- element.setAttribute(options.image.scaleInLabel.attributeSelector, "true");
85
- element.setAttribute("ie-clone-source", scaleValue);
86
-
87
- // iterate through array and wrap the element in a new element for each scale
88
- for (let i = 1; i < scaleArray.length; i++) {
89
- const newElement = cloneElementAttributes(options, element);
90
- // Set scale attribute
91
- newElement.setAttribute(
92
- options.image.scale.attributeSelector,
93
- scaleArray[i].toString()
94
- );
95
- // Force include scale img attribute
96
- newElement.setAttribute(options.image.scaleInLabel.attributeSelector, "true");
97
- // Insert element before the original element, then move the original element inside the new element, deleting the original element
98
- if (element.parentNode) {
99
- element.parentNode.insertBefore(newElement, element);
100
- newElement.appendChild(element);
101
- console.log("Encapsulated element", element, "with scale", scaleArray[i]);
102
- }
103
- }
104
- } catch (e) {
105
- console.error("ImageExporter: Error in encapsulateMultiScaleElements", e);
106
- return;
107
- }
108
- }
109
-
110
- // only clones attributes that are in the options.image[key].attributeSelector
111
- function cloneElementAttributes(
112
- options: types.Options,
113
- originalElement: Element
114
- ): Element {
115
- try {
116
- // Create a new div element
117
- const clonedElement = document.createElement("div");
118
-
119
- const arrayOfPossibleAttributes = Object.keys(options.image).map(
120
- (key) => options.image[key].attributeSelector
121
- );
122
-
123
- // Adds capture attribute to cloned element
124
- const { prefix, value } = parseStringAttribute(options.selectors.capture);
125
- clonedElement.setAttribute(prefix, value);
126
- clonedElement.setAttribute("ie-clone", "true");
127
- setExplicitDimensions(originalElement, clonedElement);
128
-
129
- // Iterate over all attributes of the original element
130
- Array.from(originalElement.attributes).forEach((attr: any) => {
131
- // Check if the attribute name exists in types.Attributes
132
- if (attr.name in arrayOfPossibleAttributes) {
133
- // Copy the attribute to the cloned element
134
- clonedElement.setAttribute(attr.name, attr.value);
135
- }
136
- });
137
-
138
- return clonedElement;
139
- } catch (e) {
140
- console.error("ImageExporter: Error in cloneElementAttributes", e);
141
- return originalElement;
142
- }
143
- }
144
-
145
- function parseStringAttribute(attributeValue: string) {
146
- if (!attributeValue.includes("=")) {
147
- throw new Error("Invalid attribute format. Expected format: [prefix=value]");
148
- }
149
-
150
- const attributeArray = attributeValue.split("=");
151
- if (attributeArray.length !== 2) {
152
- throw new Error("Invalid attribute format. Expected format: [prefix=value]");
153
- }
154
-
155
- const prefix = attributeArray[0].trim().replace(/^\[|\]$/g, "");
156
- const value = attributeArray[1]
157
- .trim()
158
- .replace(/^\[|\]$/g, "")
159
- .replace(/^'|'$/g, "");
160
-
161
- if (!prefix || !value) {
162
- throw new Error("Invalid attribute format. Prefix or value is missing.");
163
- }
164
-
165
- return { prefix, value };
166
- }
167
-
168
- function setExplicitDimensions(originalElement: any, clonedElement: any) {
169
- const originalElementStyle = window.getComputedStyle(originalElement);
170
- const originalElementWidth = originalElementStyle.getPropertyValue("width");
171
- const originalElementHeight = originalElementStyle.getPropertyValue("height");
172
-
173
- clonedElement.style.width = originalElementWidth;
174
- clonedElement.style.height = originalElementHeight;
175
- }