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.
- package/README.md +137 -2
- package/bun.lockb +0 -0
- package/dist/image-exporter.es.js +499 -612
- package/dist/image-exporter.umd.js +506 -619
- package/package.json +18 -15
- package/src/capture/capture-element.ts +58 -0
- package/src/capture/determine-total-elements.ts +40 -0
- package/src/capture/download-images.ts +69 -0
- package/src/capture/get-image-options.ts +196 -0
- package/src/capture/handle-filenames.ts +52 -0
- package/src/capture/index.ts +99 -0
- package/src/capture/remove-hidden-elements.ts +19 -0
- package/src/config.ts +19 -0
- package/src/cors-proxy/cleanup.ts +43 -0
- package/src/cors-proxy/index.ts +6 -21
- package/src/{utils → cors-proxy}/is-valid-url.ts +5 -3
- package/src/cors-proxy/proxy-css.ts +51 -44
- package/src/cors-proxy/proxy-images.ts +21 -77
- package/src/cors-proxy/run.ts +26 -0
- package/src/index.ts +10 -4
- package/src/logger.ts +61 -0
- package/src/types.d.ts +51 -0
- package/vite.config.js +3 -7
- package/example/example.css +0 -122
- package/example/example.html +0 -152
- package/example/github.jpg +0 -0
- package/example/poll-h.svg +0 -1
- package/src/capture-images.ts +0 -129
- package/src/clean-up.ts +0 -50
- package/src/default-options.ts +0 -58
- package/src/download-images.ts +0 -52
- package/src/get-capture-element.test.html +0 -21
- package/src/get-capture-element.test.ts +0 -36
- package/src/get-capture-element.ts +0 -175
- package/src/get-options/get-input-options.test.html +0 -217
- package/src/get-options/get-input-options.test.ts +0 -109
- package/src/get-options/get-input-options.ts +0 -40
- package/src/get-options/get-item-options.ts +0 -46
- package/src/get-options/get-wrapper-options.test.html +0 -33
- package/src/get-options/get-wrapper-options.test.ts +0 -109
- package/src/get-options/get-wrapper-options.ts +0 -84
- package/src/get-options/index.ts +0 -28
- package/src/image-exporter.ts +0 -108
- package/src/types/image.ts +0 -2
- package/src/types/index.ts +0 -2
- package/src/types/options.ts +0 -69
- package/src/utils/convert-to-slug.ts +0 -15
- package/src/utils/get-attribute-values.ts +0 -68
- package/src/utils/get-date-MMDDYY.ts +0 -11
- package/src/utils/ignore-items.ts +0 -11
- package/src/utils/index.ts +0 -18
- package/src/utils/is-visible.ts +0 -12
- package/src/utils/parse-labels.ts +0 -55
- package/src/utils/push-to-window.ts +0 -3
- package/tests/index.html +0 -88
- package/tests/input-tests.html +0 -169
package/example/example.html
DELETED
|
@@ -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>
|
package/example/github.jpg
DELETED
|
Binary file
|
package/example/poll-h.svg
DELETED
|
@@ -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>
|
package/src/capture-images.ts
DELETED
|
@@ -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
|
-
}
|
package/src/default-options.ts
DELETED
|
@@ -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
|
-
};
|
package/src/download-images.ts
DELETED
|
@@ -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
|
-
}
|