@nice2dev/icons 1.3.15 → 1.3.17
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/CHANGELOG.md +26 -0
- package/README.md +1 -1
- package/dist/cjs/NiceDesktopIconExporter.js +688 -599
- package/dist/cjs/NiceFaviconGenerator.js +603 -417
- package/dist/cjs/NiceIconPicker.js +788 -508
- package/dist/cjs/NiceIconPreview.js +246 -188
- package/dist/cjs/NiceIconSetCreator.js +683 -552
- package/dist/cjs/NiceIconStacker.js +547 -171
- package/dist/cjs/NiceIconValue.js +27 -35
- package/dist/cjs/NtdIconBadge.js +384 -360
- package/dist/cjs/NtdIconMorph.js +419 -354
- package/dist/cjs/NtdStateMorph.js +150 -147
- package/dist/cjs/advancedIconSearch.js +373 -510
- package/dist/cjs/animationControls.js +518 -642
- package/dist/cjs/animationSpeed.js +69 -0
- package/dist/cjs/client.js +12 -15
- package/dist/cjs/createIcon.js +374 -252
- package/dist/cjs/design-tokens.js +324 -390
- package/dist/cjs/figmaExport.js +162 -257
- package/dist/cjs/figmaSync.js +234 -316
- package/dist/cjs/headless-ui.js +240 -368
- package/dist/cjs/iconAnalytics.js +337 -393
- package/dist/cjs/iconHtml.js +64 -86
- package/dist/cjs/iconMetadata.js +392 -472
- package/dist/cjs/iconParts.js +120 -90
- package/dist/cjs/iconStack.js +136 -155
- package/dist/cjs/iconStyles.js +1670 -509
- package/dist/cjs/iconValue.js +359 -430
- package/dist/cjs/icons/accessibility.js +1119 -134
- package/dist/cjs/icons/actions.js +1290 -330
- package/dist/cjs/icons/ai.js +1424 -144
- package/dist/cjs/icons/brand.js +613 -184
- package/dist/cjs/icons/business.js +938 -192
- package/dist/cjs/icons/devtech.js +1656 -227
- package/dist/cjs/icons/ecommerce.js +802 -111
- package/dist/cjs/icons/education.js +1345 -199
- package/dist/cjs/icons/files.js +1597 -328
- package/dist/cjs/icons/fintech.js +793 -104
- package/dist/cjs/icons/food.js +1028 -156
- package/dist/cjs/icons/fun.js +804 -109
- package/dist/cjs/icons/gaming.js +916 -170
- package/dist/cjs/icons/health.js +623 -112
- package/dist/cjs/icons/index.js +32 -42
- package/dist/cjs/icons/input.js +282 -83
- package/dist/cjs/icons/legal.js +1065 -122
- package/dist/cjs/icons/media.js +1061 -267
- package/dist/cjs/icons/navigation.js +407 -123
- package/dist/cjs/icons/omniverk.js +523 -93
- package/dist/cjs/icons/realestate.js +1520 -193
- package/dist/cjs/icons/saas.js +867 -98
- package/dist/cjs/icons/science.js +1344 -202
- package/dist/cjs/icons/shapes.js +1316 -308
- package/dist/cjs/icons/smarthome.js +786 -90
- package/dist/cjs/icons/social.js +591 -59
- package/dist/cjs/icons/sports.js +1133 -133
- package/dist/cjs/icons/sustainability.js +1211 -139
- package/dist/cjs/icons/travel.js +1470 -148
- package/dist/cjs/icons/ui.js +1508 -315
- package/dist/cjs/icons/weather.js +1160 -173
- package/dist/cjs/index.js +559 -568
- package/dist/cjs/lottieIntegration.js +261 -266
- package/dist/cjs/nicetodev-icons.css +1 -1
- package/dist/cjs/particleEffects.js +270 -251
- package/dist/cjs/resolver.js +229 -284
- package/dist/cjs/rsc.js +175 -193
- package/dist/cjs/sharpen.js +73 -130
- package/dist/cjs/smilAnimations.js +380 -165
- package/dist/cjs/stacking.js +87 -122
- package/dist/cjs/tailwind-plugin.js +280 -320
- package/dist/cjs/types.js +46 -72
- package/dist/cjs/utilities.js +163 -324
- package/dist/cjs/variantProps.js +12 -52
- package/dist/esm/NiceDesktopIconExporter.js +671 -598
- package/dist/esm/NiceFaviconGenerator.js +583 -416
- package/dist/esm/NiceIconPicker.js +789 -507
- package/dist/esm/NiceIconPreview.js +247 -187
- package/dist/esm/NiceIconSetCreator.js +685 -551
- package/dist/esm/NiceIconStacker.js +549 -170
- package/dist/esm/NiceIconValue.js +29 -34
- package/dist/esm/NtdIconBadge.js +388 -359
- package/dist/esm/NtdIconMorph.js +423 -353
- package/dist/esm/NtdStateMorph.js +152 -146
- package/dist/esm/advancedIconSearch.js +391 -509
- package/dist/esm/animationControls.js +527 -641
- package/dist/esm/animationSpeed.js +69 -0
- package/dist/esm/client.js +48 -10
- package/dist/esm/createIcon.js +375 -251
- package/dist/esm/design-tokens.js +334 -387
- package/dist/esm/figmaExport.js +170 -256
- package/dist/esm/figmaSync.js +237 -315
- package/dist/esm/headless-ui.js +254 -365
- package/dist/esm/iconAnalytics.js +342 -392
- package/dist/esm/iconHtml.js +67 -85
- package/dist/esm/iconMetadata.js +404 -471
- package/dist/esm/iconParts.js +125 -89
- package/dist/esm/iconStack.js +145 -154
- package/dist/esm/iconStyles.js +1681 -508
- package/dist/esm/iconValue.js +361 -429
- package/dist/esm/icons/accessibility.js +1133 -133
- package/dist/esm/icons/actions.js +1320 -329
- package/dist/esm/icons/ai.js +1441 -143
- package/dist/esm/icons/brand.js +616 -179
- package/dist/esm/icons/business.js +957 -191
- package/dist/esm/icons/devtech.js +1687 -226
- package/dist/esm/icons/ecommerce.js +816 -110
- package/dist/esm/icons/education.js +1364 -198
- package/dist/esm/icons/files.js +1628 -327
- package/dist/esm/icons/fintech.js +807 -103
- package/dist/esm/icons/food.js +1043 -153
- package/dist/esm/icons/fun.js +819 -108
- package/dist/esm/icons/gaming.js +933 -169
- package/dist/esm/icons/health.js +636 -111
- package/dist/esm/icons/index.js +589 -32
- package/dist/esm/icons/input.js +326 -82
- package/dist/esm/icons/legal.js +1079 -121
- package/dist/esm/icons/media.js +1086 -266
- package/dist/esm/icons/navigation.js +421 -122
- package/dist/esm/icons/omniverk.js +534 -91
- package/dist/esm/icons/realestate.js +1544 -198
- package/dist/esm/icons/saas.js +880 -97
- package/dist/esm/icons/science.js +1370 -208
- package/dist/esm/icons/shapes.js +1343 -307
- package/dist/esm/icons/smarthome.js +799 -89
- package/dist/esm/icons/social.js +599 -58
- package/dist/esm/icons/sports.js +1148 -132
- package/dist/esm/icons/sustainability.js +1226 -138
- package/dist/esm/icons/travel.js +1486 -147
- package/dist/esm/icons/ui.js +1540 -314
- package/dist/esm/icons/weather.js +1181 -172
- package/dist/esm/index.js +749 -58
- package/dist/esm/lottieIntegration.js +264 -265
- package/dist/esm/nicetodev-icons.css +1 -1
- package/dist/esm/particleEffects.js +271 -250
- package/dist/esm/resolver.js +231 -283
- package/dist/esm/rsc.js +182 -192
- package/dist/esm/sharpen.js +78 -129
- package/dist/esm/smilAnimations.js +385 -164
- package/dist/esm/stacking.js +99 -121
- package/dist/esm/tailwind-plugin.js +282 -319
- package/dist/esm/types.js +50 -71
- package/dist/esm/utilities.js +177 -321
- package/dist/esm/variantProps.js +23 -52
- package/dist/types/animationSpeed.d.ts +19 -0
- package/dist/types/createIcon.d.ts +6 -0
- package/dist/types/iconStyles.d.ts +6 -1
- package/dist/types/icons/brand.d.ts +0 -4
- package/dist/types/icons/food.d.ts +0 -3
- package/dist/types/icons/index.d.ts +3 -3
- package/dist/types/icons/omniverk.d.ts +0 -3
- package/dist/types/icons/science.d.ts +1 -1
- package/dist/types/smilAnimations.d.ts +4 -2
- package/dist/types/types.d.ts +7 -0
- package/package.json +15 -22
|
@@ -1,651 +1,724 @@
|
|
|
1
|
-
import { jsxs, jsx } from
|
|
2
|
-
import { useNiceTranslation } from
|
|
3
|
-
import { useState, useMemo, useCallback } from
|
|
4
|
-
|
|
5
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
6
|
-
Constants
|
|
7
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
8
|
-
/** Standard desktop icon sizes used across platforms */
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useNiceTranslation } from "@nice2dev/ui-core";
|
|
3
|
+
import { useState, useMemo, useCallback } from "react";
|
|
4
|
+
/* empty css */
|
|
9
5
|
const DESKTOP_ICON_SIZES = [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
{ size: 16, label: "16×16", platforms: ["windows", "linux"] },
|
|
7
|
+
{ size: 24, label: "24×24", platforms: ["linux"] },
|
|
8
|
+
{ size: 32, label: "32×32", platforms: ["windows", "linux"] },
|
|
9
|
+
{ size: 48, label: "48×48", platforms: ["windows", "linux"] },
|
|
10
|
+
{ size: 64, label: "64×64", platforms: ["windows", "linux"] },
|
|
11
|
+
{ size: 128, label: "128×128", platforms: ["windows", "macos", "linux"] },
|
|
12
|
+
{ size: 256, label: "256×256", platforms: ["windows", "macos", "linux"] },
|
|
13
|
+
{ size: 512, label: "512×512", platforms: ["macos", "linux"] },
|
|
14
|
+
{ size: 1024, label: "1024×1024", platforms: ["macos"] }
|
|
19
15
|
];
|
|
20
|
-
/** Retina/HiDPI scale factors */
|
|
21
16
|
const RETINA_VARIANTS = [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
{ scale: 1, suffix: "" },
|
|
18
|
+
{ scale: 2, suffix: "@2x" },
|
|
19
|
+
{ scale: 3, suffix: "@3x" }
|
|
25
20
|
];
|
|
26
|
-
/** Default export configuration */
|
|
27
21
|
const DEFAULT_CONFIG = {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
22
|
+
name: "icon",
|
|
23
|
+
includeWindows: true,
|
|
24
|
+
includeMacOS: true,
|
|
25
|
+
includeLinux: true,
|
|
26
|
+
includeRetina: true,
|
|
27
|
+
backgroundColor: void 0,
|
|
28
|
+
// transparent
|
|
29
|
+
padding: 0.1,
|
|
30
|
+
fillColor: "#1e1e1e",
|
|
31
|
+
sizes: [16, 32, 48, 64, 128, 256, 512]
|
|
37
32
|
};
|
|
38
|
-
/** Linux hicolor theme icon directories */
|
|
39
33
|
const LINUX_HICOLOR_DIRS = [
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
34
|
+
{ size: 16, dir: "hicolor/16x16/apps" },
|
|
35
|
+
{ size: 24, dir: "hicolor/24x24/apps" },
|
|
36
|
+
{ size: 32, dir: "hicolor/32x32/apps" },
|
|
37
|
+
{ size: 48, dir: "hicolor/48x48/apps" },
|
|
38
|
+
{ size: 64, dir: "hicolor/64x64/apps" },
|
|
39
|
+
{ size: 128, dir: "hicolor/128x128/apps" },
|
|
40
|
+
{ size: 256, dir: "hicolor/256x256/apps" },
|
|
41
|
+
{ size: 512, dir: "hicolor/512x512/apps" }
|
|
48
42
|
];
|
|
49
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
50
|
-
Utility Functions
|
|
51
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
52
|
-
/**
|
|
53
|
-
* Render an icon component to a canvas at the specified size
|
|
54
|
-
*/
|
|
55
43
|
async function renderIconToCanvas(IconComponent, size, fillColor, backgroundColor, padding = 0.1) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
root.unmount();
|
|
95
|
-
resolve(canvas);
|
|
96
|
-
}
|
|
97
|
-
}, 50);
|
|
98
|
-
});
|
|
44
|
+
const canvas = document.createElement("canvas");
|
|
45
|
+
canvas.width = size;
|
|
46
|
+
canvas.height = size;
|
|
47
|
+
const ctx = canvas.getContext("2d");
|
|
48
|
+
if (backgroundColor) {
|
|
49
|
+
ctx.fillStyle = backgroundColor;
|
|
50
|
+
ctx.fillRect(0, 0, size, size);
|
|
51
|
+
} else {
|
|
52
|
+
ctx.clearRect(0, 0, size, size);
|
|
53
|
+
}
|
|
54
|
+
const container = document.createElement("div");
|
|
55
|
+
const iconSize = Math.round(size * (1 - padding * 2));
|
|
56
|
+
const offset = Math.round(size * padding);
|
|
57
|
+
const { createRoot } = await import("react-dom/client");
|
|
58
|
+
const root = createRoot(container);
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
root.render(/* @__PURE__ */ jsx(IconComponent, { size: iconSize, fill: fillColor }));
|
|
61
|
+
setTimeout(() => {
|
|
62
|
+
const svg = container.querySelector("svg");
|
|
63
|
+
if (svg) {
|
|
64
|
+
const svgData = new XMLSerializer().serializeToString(svg);
|
|
65
|
+
const img = new Image();
|
|
66
|
+
img.onload = () => {
|
|
67
|
+
ctx.drawImage(img, offset, offset, iconSize, iconSize);
|
|
68
|
+
root.unmount();
|
|
69
|
+
resolve(canvas);
|
|
70
|
+
};
|
|
71
|
+
img.onerror = () => {
|
|
72
|
+
root.unmount();
|
|
73
|
+
resolve(canvas);
|
|
74
|
+
};
|
|
75
|
+
img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData)));
|
|
76
|
+
} else {
|
|
77
|
+
root.unmount();
|
|
78
|
+
resolve(canvas);
|
|
79
|
+
}
|
|
80
|
+
}, 50);
|
|
81
|
+
});
|
|
99
82
|
}
|
|
100
|
-
/**
|
|
101
|
-
* Convert canvas to PNG blob
|
|
102
|
-
*/
|
|
103
83
|
function canvasToBlob(canvas) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
canvas.toBlob((blob) => {
|
|
86
|
+
if (blob) {
|
|
87
|
+
resolve(blob);
|
|
88
|
+
} else {
|
|
89
|
+
reject(new Error("Failed to convert canvas to blob"));
|
|
90
|
+
}
|
|
91
|
+
}, "image/png");
|
|
92
|
+
});
|
|
114
93
|
}
|
|
115
|
-
/**
|
|
116
|
-
* Create a Windows ICO file from multiple PNG images.
|
|
117
|
-
* ICO format: https://en.wikipedia.org/wiki/ICO_(file_format)
|
|
118
|
-
*/
|
|
119
94
|
async function createIcoFile(pngBuffers) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
view.
|
|
141
|
-
view.
|
|
142
|
-
view.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
view.setUint32(entryOffset + 12, entry.offset, true); // Image data offset
|
|
155
|
-
entryOffset += entrySize;
|
|
156
|
-
}
|
|
157
|
-
// Write image data
|
|
158
|
-
for (const entry of entries) {
|
|
159
|
-
uint8.set(new Uint8Array(entry.data), entry.offset);
|
|
160
|
-
}
|
|
161
|
-
return new Blob([buffer], { type: 'image/x-icon' });
|
|
95
|
+
const sorted = [...pngBuffers].sort((a, b) => b.size - a.size);
|
|
96
|
+
const headerSize = 6;
|
|
97
|
+
const entrySize = 16;
|
|
98
|
+
const directorySize = headerSize + entrySize * sorted.length;
|
|
99
|
+
let currentOffset = directorySize;
|
|
100
|
+
const entries = [];
|
|
101
|
+
for (const png of sorted) {
|
|
102
|
+
entries.push({ size: png.size, offset: currentOffset, data: png.data });
|
|
103
|
+
currentOffset += png.data.byteLength;
|
|
104
|
+
}
|
|
105
|
+
const totalSize = currentOffset;
|
|
106
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
107
|
+
const view = new DataView(buffer);
|
|
108
|
+
const uint8 = new Uint8Array(buffer);
|
|
109
|
+
view.setUint16(0, 0, true);
|
|
110
|
+
view.setUint16(2, 1, true);
|
|
111
|
+
view.setUint16(4, sorted.length, true);
|
|
112
|
+
let entryOffset = headerSize;
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
const size = entry.size > 255 ? 0 : entry.size;
|
|
115
|
+
view.setUint8(entryOffset + 0, size);
|
|
116
|
+
view.setUint8(entryOffset + 1, size);
|
|
117
|
+
view.setUint8(entryOffset + 2, 0);
|
|
118
|
+
view.setUint8(entryOffset + 3, 0);
|
|
119
|
+
view.setUint16(entryOffset + 4, 1, true);
|
|
120
|
+
view.setUint16(entryOffset + 6, 32, true);
|
|
121
|
+
view.setUint32(entryOffset + 8, entry.data.byteLength, true);
|
|
122
|
+
view.setUint32(entryOffset + 12, entry.offset, true);
|
|
123
|
+
entryOffset += entrySize;
|
|
124
|
+
}
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
uint8.set(new Uint8Array(entry.data), entry.offset);
|
|
127
|
+
}
|
|
128
|
+
return new Blob([buffer], { type: "image/x-icon" });
|
|
162
129
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Create a ZIP file containing multiple files
|
|
165
|
-
*/
|
|
166
130
|
async function createZipFile(files) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
else if (file.data instanceof Blob) {
|
|
180
|
-
dataBytes = new Uint8Array(await file.data.arrayBuffer());
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
dataBytes = new Uint8Array(file.data);
|
|
184
|
-
}
|
|
185
|
-
const crc32 = calculateCRC32(dataBytes);
|
|
186
|
-
fileEntries.push({ name: nameBytes, data: dataBytes, crc32, offset });
|
|
187
|
-
// Local file header size: 30 + name length + data length
|
|
188
|
-
offset += 30 + nameBytes.length + dataBytes.length;
|
|
189
|
-
}
|
|
190
|
-
const centralDirectoryOffset = offset;
|
|
191
|
-
// Calculate central directory size
|
|
192
|
-
let centralDirSize = 0;
|
|
193
|
-
for (const entry of fileEntries) {
|
|
194
|
-
centralDirSize += 46 + entry.name.length;
|
|
131
|
+
const encoder = new TextEncoder();
|
|
132
|
+
const fileEntries = [];
|
|
133
|
+
let offset = 0;
|
|
134
|
+
for (const file of files) {
|
|
135
|
+
const nameBytes = encoder.encode(file.name);
|
|
136
|
+
let dataBytes;
|
|
137
|
+
if (typeof file.data === "string") {
|
|
138
|
+
dataBytes = encoder.encode(file.data);
|
|
139
|
+
} else if (file.data instanceof Blob) {
|
|
140
|
+
dataBytes = new Uint8Array(await file.data.arrayBuffer());
|
|
141
|
+
} else {
|
|
142
|
+
dataBytes = new Uint8Array(file.data);
|
|
195
143
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
pos += 4;
|
|
230
|
-
// Uncompressed size
|
|
231
|
-
view.setUint32(pos, entry.data.length, true);
|
|
232
|
-
pos += 4;
|
|
233
|
-
// File name length
|
|
234
|
-
view.setUint16(pos, entry.name.length, true);
|
|
235
|
-
pos += 2;
|
|
236
|
-
// Extra field length
|
|
237
|
-
view.setUint16(pos, 0, true);
|
|
238
|
-
pos += 2;
|
|
239
|
-
// File name
|
|
240
|
-
bytes.set(entry.name, pos);
|
|
241
|
-
pos += entry.name.length;
|
|
242
|
-
// File data
|
|
243
|
-
bytes.set(entry.data, pos);
|
|
244
|
-
pos += entry.data.length;
|
|
245
|
-
}
|
|
246
|
-
// Write central directory
|
|
247
|
-
for (const entry of fileEntries) {
|
|
248
|
-
// Central directory file header signature
|
|
249
|
-
view.setUint32(pos, 0x02014b50, true);
|
|
250
|
-
pos += 4;
|
|
251
|
-
// Version made by
|
|
252
|
-
view.setUint16(pos, 20, true);
|
|
253
|
-
pos += 2;
|
|
254
|
-
// Version needed
|
|
255
|
-
view.setUint16(pos, 20, true);
|
|
256
|
-
pos += 2;
|
|
257
|
-
// General purpose bit flag
|
|
258
|
-
view.setUint16(pos, 0, true);
|
|
259
|
-
pos += 2;
|
|
260
|
-
// Compression method
|
|
261
|
-
view.setUint16(pos, 0, true);
|
|
262
|
-
pos += 2;
|
|
263
|
-
// File time & date
|
|
264
|
-
const now = new Date();
|
|
265
|
-
const dosTime = ((now.getHours() << 11) | (now.getMinutes() << 5) | (now.getSeconds() >> 1)) & 0xffff;
|
|
266
|
-
const dosDate = (((now.getFullYear() - 1980) << 9) | ((now.getMonth() + 1) << 5) | now.getDate()) & 0xffff;
|
|
267
|
-
view.setUint16(pos, dosTime, true);
|
|
268
|
-
pos += 2;
|
|
269
|
-
view.setUint16(pos, dosDate, true);
|
|
270
|
-
pos += 2;
|
|
271
|
-
// CRC-32
|
|
272
|
-
view.setUint32(pos, entry.crc32, true);
|
|
273
|
-
pos += 4;
|
|
274
|
-
// Compressed size
|
|
275
|
-
view.setUint32(pos, entry.data.length, true);
|
|
276
|
-
pos += 4;
|
|
277
|
-
// Uncompressed size
|
|
278
|
-
view.setUint32(pos, entry.data.length, true);
|
|
279
|
-
pos += 4;
|
|
280
|
-
// File name length
|
|
281
|
-
view.setUint16(pos, entry.name.length, true);
|
|
282
|
-
pos += 2;
|
|
283
|
-
// Extra field length
|
|
284
|
-
view.setUint16(pos, 0, true);
|
|
285
|
-
pos += 2;
|
|
286
|
-
// File comment length
|
|
287
|
-
view.setUint16(pos, 0, true);
|
|
288
|
-
pos += 2;
|
|
289
|
-
// Disk number start
|
|
290
|
-
view.setUint16(pos, 0, true);
|
|
291
|
-
pos += 2;
|
|
292
|
-
// Internal file attributes
|
|
293
|
-
view.setUint16(pos, 0, true);
|
|
294
|
-
pos += 2;
|
|
295
|
-
// External file attributes
|
|
296
|
-
view.setUint32(pos, 0, true);
|
|
297
|
-
pos += 4;
|
|
298
|
-
// Relative offset of local header
|
|
299
|
-
view.setUint32(pos, entry.offset, true);
|
|
300
|
-
pos += 4;
|
|
301
|
-
// File name
|
|
302
|
-
bytes.set(entry.name, pos);
|
|
303
|
-
pos += entry.name.length;
|
|
304
|
-
}
|
|
305
|
-
// End of central directory record
|
|
306
|
-
view.setUint32(pos, 0x06054b50, true);
|
|
144
|
+
const crc32 = calculateCRC32(dataBytes);
|
|
145
|
+
fileEntries.push({ name: nameBytes, data: dataBytes, crc32, offset });
|
|
146
|
+
offset += 30 + nameBytes.length + dataBytes.length;
|
|
147
|
+
}
|
|
148
|
+
const centralDirectoryOffset = offset;
|
|
149
|
+
let centralDirSize = 0;
|
|
150
|
+
for (const entry of fileEntries) {
|
|
151
|
+
centralDirSize += 46 + entry.name.length;
|
|
152
|
+
}
|
|
153
|
+
const totalSize = offset + centralDirSize + 22;
|
|
154
|
+
const buffer = new ArrayBuffer(totalSize);
|
|
155
|
+
const view = new DataView(buffer);
|
|
156
|
+
const bytes = new Uint8Array(buffer);
|
|
157
|
+
let pos = 0;
|
|
158
|
+
for (const entry of fileEntries) {
|
|
159
|
+
view.setUint32(pos, 67324752, true);
|
|
160
|
+
pos += 4;
|
|
161
|
+
view.setUint16(pos, 20, true);
|
|
162
|
+
pos += 2;
|
|
163
|
+
view.setUint16(pos, 0, true);
|
|
164
|
+
pos += 2;
|
|
165
|
+
view.setUint16(pos, 0, true);
|
|
166
|
+
pos += 2;
|
|
167
|
+
const now = /* @__PURE__ */ new Date();
|
|
168
|
+
const dosTime = (now.getHours() << 11 | now.getMinutes() << 5 | now.getSeconds() >> 1) & 65535;
|
|
169
|
+
const dosDate = (now.getFullYear() - 1980 << 9 | now.getMonth() + 1 << 5 | now.getDate()) & 65535;
|
|
170
|
+
view.setUint16(pos, dosTime, true);
|
|
171
|
+
pos += 2;
|
|
172
|
+
view.setUint16(pos, dosDate, true);
|
|
173
|
+
pos += 2;
|
|
174
|
+
view.setUint32(pos, entry.crc32, true);
|
|
175
|
+
pos += 4;
|
|
176
|
+
view.setUint32(pos, entry.data.length, true);
|
|
307
177
|
pos += 4;
|
|
308
|
-
|
|
178
|
+
view.setUint32(pos, entry.data.length, true);
|
|
179
|
+
pos += 4;
|
|
180
|
+
view.setUint16(pos, entry.name.length, true);
|
|
181
|
+
pos += 2;
|
|
309
182
|
view.setUint16(pos, 0, true);
|
|
310
183
|
pos += 2;
|
|
311
|
-
|
|
184
|
+
bytes.set(entry.name, pos);
|
|
185
|
+
pos += entry.name.length;
|
|
186
|
+
bytes.set(entry.data, pos);
|
|
187
|
+
pos += entry.data.length;
|
|
188
|
+
}
|
|
189
|
+
for (const entry of fileEntries) {
|
|
190
|
+
view.setUint32(pos, 33639248, true);
|
|
191
|
+
pos += 4;
|
|
192
|
+
view.setUint16(pos, 20, true);
|
|
193
|
+
pos += 2;
|
|
194
|
+
view.setUint16(pos, 20, true);
|
|
195
|
+
pos += 2;
|
|
312
196
|
view.setUint16(pos, 0, true);
|
|
313
197
|
pos += 2;
|
|
314
|
-
|
|
315
|
-
|
|
198
|
+
view.setUint16(pos, 0, true);
|
|
199
|
+
pos += 2;
|
|
200
|
+
const now = /* @__PURE__ */ new Date();
|
|
201
|
+
const dosTime = (now.getHours() << 11 | now.getMinutes() << 5 | now.getSeconds() >> 1) & 65535;
|
|
202
|
+
const dosDate = (now.getFullYear() - 1980 << 9 | now.getMonth() + 1 << 5 | now.getDate()) & 65535;
|
|
203
|
+
view.setUint16(pos, dosTime, true);
|
|
316
204
|
pos += 2;
|
|
317
|
-
|
|
318
|
-
view.setUint16(pos, fileEntries.length, true);
|
|
205
|
+
view.setUint16(pos, dosDate, true);
|
|
319
206
|
pos += 2;
|
|
320
|
-
|
|
321
|
-
|
|
207
|
+
view.setUint32(pos, entry.crc32, true);
|
|
208
|
+
pos += 4;
|
|
209
|
+
view.setUint32(pos, entry.data.length, true);
|
|
322
210
|
pos += 4;
|
|
323
|
-
|
|
324
|
-
view.setUint32(pos, centralDirectoryOffset, true);
|
|
211
|
+
view.setUint32(pos, entry.data.length, true);
|
|
325
212
|
pos += 4;
|
|
326
|
-
|
|
213
|
+
view.setUint16(pos, entry.name.length, true);
|
|
214
|
+
pos += 2;
|
|
215
|
+
view.setUint16(pos, 0, true);
|
|
216
|
+
pos += 2;
|
|
217
|
+
view.setUint16(pos, 0, true);
|
|
218
|
+
pos += 2;
|
|
219
|
+
view.setUint16(pos, 0, true);
|
|
220
|
+
pos += 2;
|
|
327
221
|
view.setUint16(pos, 0, true);
|
|
328
|
-
|
|
222
|
+
pos += 2;
|
|
223
|
+
view.setUint32(pos, 0, true);
|
|
224
|
+
pos += 4;
|
|
225
|
+
view.setUint32(pos, entry.offset, true);
|
|
226
|
+
pos += 4;
|
|
227
|
+
bytes.set(entry.name, pos);
|
|
228
|
+
pos += entry.name.length;
|
|
229
|
+
}
|
|
230
|
+
view.setUint32(pos, 101010256, true);
|
|
231
|
+
pos += 4;
|
|
232
|
+
view.setUint16(pos, 0, true);
|
|
233
|
+
pos += 2;
|
|
234
|
+
view.setUint16(pos, 0, true);
|
|
235
|
+
pos += 2;
|
|
236
|
+
view.setUint16(pos, fileEntries.length, true);
|
|
237
|
+
pos += 2;
|
|
238
|
+
view.setUint16(pos, fileEntries.length, true);
|
|
239
|
+
pos += 2;
|
|
240
|
+
view.setUint32(pos, centralDirSize, true);
|
|
241
|
+
pos += 4;
|
|
242
|
+
view.setUint32(pos, centralDirectoryOffset, true);
|
|
243
|
+
pos += 4;
|
|
244
|
+
view.setUint16(pos, 0, true);
|
|
245
|
+
return new Blob([buffer], { type: "application/zip" });
|
|
329
246
|
}
|
|
330
|
-
/**
|
|
331
|
-
* Calculate CRC-32 for ZIP files
|
|
332
|
-
*/
|
|
333
247
|
function calculateCRC32(data) {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
248
|
+
let crc = 4294967295;
|
|
249
|
+
const table = getCRC32Table();
|
|
250
|
+
for (let i = 0; i < data.length; i++) {
|
|
251
|
+
crc = crc >>> 8 ^ table[(crc ^ data[i]) & 255];
|
|
252
|
+
}
|
|
253
|
+
return (crc ^ 4294967295) >>> 0;
|
|
340
254
|
}
|
|
341
255
|
let crc32Table = null;
|
|
342
256
|
function getCRC32Table() {
|
|
343
|
-
|
|
344
|
-
return crc32Table;
|
|
345
|
-
}
|
|
346
|
-
crc32Table = new Uint32Array(256);
|
|
347
|
-
for (let i = 0; i < 256; i++) {
|
|
348
|
-
let c = i;
|
|
349
|
-
for (let j = 0; j < 8; j++) {
|
|
350
|
-
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
351
|
-
}
|
|
352
|
-
crc32Table[i] = c;
|
|
353
|
-
}
|
|
257
|
+
if (crc32Table) {
|
|
354
258
|
return crc32Table;
|
|
259
|
+
}
|
|
260
|
+
crc32Table = new Uint32Array(256);
|
|
261
|
+
for (let i = 0; i < 256; i++) {
|
|
262
|
+
let c = i;
|
|
263
|
+
for (let j = 0; j < 8; j++) {
|
|
264
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
265
|
+
}
|
|
266
|
+
crc32Table[i] = c;
|
|
267
|
+
}
|
|
268
|
+
return crc32Table;
|
|
355
269
|
}
|
|
356
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
357
|
-
Hook
|
|
358
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
359
|
-
/**
|
|
360
|
-
* Hook for exporting desktop icons in multiple formats and sizes.
|
|
361
|
-
*
|
|
362
|
-
* @example
|
|
363
|
-
* ```tsx
|
|
364
|
-
* const { exportAll, downloadAll, icons, isExporting } = useDesktopIconExporter(MyIcon);
|
|
365
|
-
*
|
|
366
|
-
* // Export all icons
|
|
367
|
-
* await exportAll();
|
|
368
|
-
*
|
|
369
|
-
* // Download as ZIP
|
|
370
|
-
* await downloadAll();
|
|
371
|
-
* ```
|
|
372
|
-
*/
|
|
373
270
|
function useDesktopIconExporter(icon, initialConfig) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
catch (error) {
|
|
435
|
-
console.error('Failed to export icons:', error);
|
|
271
|
+
const [config, setConfig] = useState({
|
|
272
|
+
...DEFAULT_CONFIG,
|
|
273
|
+
...initialConfig
|
|
274
|
+
});
|
|
275
|
+
const [icons, setIcons] = useState([]);
|
|
276
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
277
|
+
const [progress, setProgress] = useState(0);
|
|
278
|
+
const totalIcons = useMemo(() => {
|
|
279
|
+
let count = 0;
|
|
280
|
+
const scales = config.includeRetina ? RETINA_VARIANTS.length : 1;
|
|
281
|
+
for (const size of config.sizes) {
|
|
282
|
+
if (config.includeWindows || config.includeLinux) {
|
|
283
|
+
count += scales;
|
|
284
|
+
}
|
|
285
|
+
if (config.includeMacOS && size >= 128) {
|
|
286
|
+
count += scales;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return count;
|
|
290
|
+
}, [config]);
|
|
291
|
+
const exportAll = useCallback(async () => {
|
|
292
|
+
setIsExporting(true);
|
|
293
|
+
setProgress(0);
|
|
294
|
+
const exported = [];
|
|
295
|
+
let completed = 0;
|
|
296
|
+
try {
|
|
297
|
+
const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
|
|
298
|
+
for (const size of config.sizes) {
|
|
299
|
+
for (const variant of scales) {
|
|
300
|
+
const actualSize = size * variant.scale;
|
|
301
|
+
const canvas = await renderIconToCanvas(
|
|
302
|
+
icon,
|
|
303
|
+
actualSize,
|
|
304
|
+
config.fillColor,
|
|
305
|
+
config.backgroundColor,
|
|
306
|
+
config.padding
|
|
307
|
+
);
|
|
308
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
309
|
+
const blob = await canvasToBlob(canvas);
|
|
310
|
+
const platforms = [];
|
|
311
|
+
if (config.includeWindows && size <= 256) {
|
|
312
|
+
platforms.push("windows");
|
|
313
|
+
}
|
|
314
|
+
if (config.includeMacOS && size >= 128) {
|
|
315
|
+
platforms.push("macos");
|
|
316
|
+
}
|
|
317
|
+
if (config.includeLinux) {
|
|
318
|
+
platforms.push("linux");
|
|
319
|
+
}
|
|
320
|
+
exported.push({
|
|
321
|
+
filename: `${config.name}-${size}x${size}${variant.suffix}.png`,
|
|
322
|
+
size,
|
|
323
|
+
scale: variant.scale,
|
|
324
|
+
platform: platforms.join(","),
|
|
325
|
+
dataUrl,
|
|
326
|
+
blob
|
|
327
|
+
});
|
|
328
|
+
completed++;
|
|
329
|
+
setProgress(completed / totalIcons * 100);
|
|
436
330
|
}
|
|
437
|
-
|
|
438
|
-
|
|
331
|
+
}
|
|
332
|
+
setIcons(exported);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error("Failed to export icons:", error);
|
|
335
|
+
} finally {
|
|
336
|
+
setIsExporting(false);
|
|
337
|
+
}
|
|
338
|
+
}, [icon, config, totalIcons]);
|
|
339
|
+
const exportWindows = useCallback(async () => {
|
|
340
|
+
const windowsSizes = config.sizes.filter((s) => s <= 256);
|
|
341
|
+
const pngBuffers = [];
|
|
342
|
+
for (const size of windowsSizes) {
|
|
343
|
+
const canvas = await renderIconToCanvas(
|
|
344
|
+
icon,
|
|
345
|
+
size,
|
|
346
|
+
config.fillColor,
|
|
347
|
+
config.backgroundColor,
|
|
348
|
+
config.padding
|
|
349
|
+
);
|
|
350
|
+
const blob = await canvasToBlob(canvas);
|
|
351
|
+
const buffer = await blob.arrayBuffer();
|
|
352
|
+
pngBuffers.push({ size, data: buffer });
|
|
353
|
+
}
|
|
354
|
+
const icoBlob = await createIcoFile(pngBuffers);
|
|
355
|
+
return URL.createObjectURL(icoBlob);
|
|
356
|
+
}, [icon, config]);
|
|
357
|
+
const exportMacOS = useCallback(async () => {
|
|
358
|
+
const macSizes = config.sizes.filter((s) => s >= 128);
|
|
359
|
+
const files = [];
|
|
360
|
+
for (const size of macSizes) {
|
|
361
|
+
const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
|
|
362
|
+
for (const variant of scales) {
|
|
363
|
+
const actualSize = size * variant.scale;
|
|
364
|
+
const canvas = await renderIconToCanvas(
|
|
365
|
+
icon,
|
|
366
|
+
actualSize,
|
|
367
|
+
config.fillColor,
|
|
368
|
+
config.backgroundColor,
|
|
369
|
+
config.padding
|
|
370
|
+
);
|
|
371
|
+
const blob = await canvasToBlob(canvas);
|
|
372
|
+
files.push({
|
|
373
|
+
name: `${config.name}.iconset/icon_${size}x${size}${variant.suffix}.png`,
|
|
374
|
+
data: blob
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
files.push({
|
|
379
|
+
name: "README.txt",
|
|
380
|
+
data: new Blob(
|
|
381
|
+
[
|
|
382
|
+
`macOS Icon Set
|
|
383
|
+
=============
|
|
384
|
+
|
|
385
|
+
To convert to .icns format, use the following command in Terminal:
|
|
386
|
+
|
|
387
|
+
iconutil -c icns ${config.name}.iconset
|
|
388
|
+
|
|
389
|
+
This will create ${config.name}.icns
|
|
390
|
+
|
|
391
|
+
Note: iconutil is only available on macOS.
|
|
392
|
+
`
|
|
393
|
+
],
|
|
394
|
+
{ type: "text/plain" }
|
|
395
|
+
)
|
|
396
|
+
});
|
|
397
|
+
const zipBlob = await createZipFile(files);
|
|
398
|
+
return URL.createObjectURL(zipBlob);
|
|
399
|
+
}, [icon, config]);
|
|
400
|
+
const exportLinux = useCallback(async () => {
|
|
401
|
+
const linuxIcons = [];
|
|
402
|
+
for (const { size, dir } of LINUX_HICOLOR_DIRS) {
|
|
403
|
+
if (!config.sizes.includes(size)) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const canvas = await renderIconToCanvas(
|
|
407
|
+
icon,
|
|
408
|
+
size,
|
|
409
|
+
config.fillColor,
|
|
410
|
+
config.backgroundColor,
|
|
411
|
+
config.padding
|
|
412
|
+
);
|
|
413
|
+
const dataUrl = canvas.toDataURL("image/png");
|
|
414
|
+
const blob = await canvasToBlob(canvas);
|
|
415
|
+
linuxIcons.push({
|
|
416
|
+
filename: `${dir}/${config.name}.png`,
|
|
417
|
+
size,
|
|
418
|
+
scale: 1,
|
|
419
|
+
platform: "linux",
|
|
420
|
+
dataUrl,
|
|
421
|
+
blob
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return linuxIcons;
|
|
425
|
+
}, [icon, config]);
|
|
426
|
+
const downloadSingle = useCallback((exportedIcon) => {
|
|
427
|
+
const link = document.createElement("a");
|
|
428
|
+
link.href = exportedIcon.dataUrl;
|
|
429
|
+
link.download = exportedIcon.filename;
|
|
430
|
+
link.click();
|
|
431
|
+
}, []);
|
|
432
|
+
const downloadAll = useCallback(async () => {
|
|
433
|
+
setIsExporting(true);
|
|
434
|
+
setProgress(0);
|
|
435
|
+
try {
|
|
436
|
+
const files = [];
|
|
437
|
+
await exportAll();
|
|
438
|
+
for (const exportedIcon of icons) {
|
|
439
|
+
if (exportedIcon.blob) {
|
|
440
|
+
files.push({ name: `png/${exportedIcon.filename}`, data: exportedIcon.blob });
|
|
439
441
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const exportWindows = useCallback(async () => {
|
|
442
|
+
}
|
|
443
|
+
if (config.includeWindows) {
|
|
443
444
|
const windowsSizes = config.sizes.filter((s) => s <= 256);
|
|
444
445
|
const pngBuffers = [];
|
|
445
446
|
for (const size of windowsSizes) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
447
|
+
const canvas = await renderIconToCanvas(
|
|
448
|
+
icon,
|
|
449
|
+
size,
|
|
450
|
+
config.fillColor,
|
|
451
|
+
config.backgroundColor,
|
|
452
|
+
config.padding
|
|
453
|
+
);
|
|
454
|
+
const blob = await canvasToBlob(canvas);
|
|
455
|
+
pngBuffers.push({ size, data: await blob.arrayBuffer() });
|
|
450
456
|
}
|
|
451
457
|
const icoBlob = await createIcoFile(pngBuffers);
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const exportMacOS = useCallback(async () => {
|
|
458
|
+
files.push({ name: `windows/${config.name}.ico`, data: icoBlob });
|
|
459
|
+
}
|
|
460
|
+
if (config.includeMacOS) {
|
|
456
461
|
const macSizes = config.sizes.filter((s) => s >= 128);
|
|
457
|
-
const
|
|
462
|
+
const scales = config.includeRetina ? RETINA_VARIANTS : [RETINA_VARIANTS[0]];
|
|
458
463
|
for (const size of macSizes) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
464
|
+
for (const variant of scales) {
|
|
465
|
+
const actualSize = size * variant.scale;
|
|
466
|
+
const canvas = await renderIconToCanvas(
|
|
467
|
+
icon,
|
|
468
|
+
actualSize,
|
|
469
|
+
config.fillColor,
|
|
470
|
+
config.backgroundColor,
|
|
471
|
+
config.padding
|
|
472
|
+
);
|
|
473
|
+
const blob = await canvasToBlob(canvas);
|
|
474
|
+
files.push({
|
|
475
|
+
name: `macos/${config.name}.iconset/icon_${size}x${size}${variant.suffix}.png`,
|
|
476
|
+
data: blob
|
|
477
|
+
});
|
|
478
|
+
}
|
|
470
479
|
}
|
|
471
|
-
// Add README with iconutil instructions
|
|
472
480
|
files.push({
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
`macOS Icon Set
|
|
476
|
-
=============
|
|
477
|
-
|
|
478
|
-
To convert to .icns format, use the following command in Terminal:
|
|
479
|
-
|
|
480
|
-
iconutil -c icns ${config.name}.iconset
|
|
481
|
-
|
|
482
|
-
This will create ${config.name}.icns
|
|
483
|
-
|
|
484
|
-
Note: iconutil is only available on macOS.
|
|
485
|
-
`,
|
|
486
|
-
], { type: 'text/plain' }),
|
|
481
|
+
name: "macos/README.txt",
|
|
482
|
+
data: `To convert to .icns: iconutil -c icns ${config.name}.iconset`
|
|
487
483
|
});
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}, [icon, config]);
|
|
491
|
-
// Export Linux PNG set
|
|
492
|
-
const exportLinux = useCallback(async () => {
|
|
493
|
-
const linuxIcons = [];
|
|
484
|
+
}
|
|
485
|
+
if (config.includeLinux) {
|
|
494
486
|
for (const { size, dir } of LINUX_HICOLOR_DIRS) {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
blob,
|
|
508
|
-
});
|
|
487
|
+
if (!config.sizes.includes(size)) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const canvas = await renderIconToCanvas(
|
|
491
|
+
icon,
|
|
492
|
+
size,
|
|
493
|
+
config.fillColor,
|
|
494
|
+
config.backgroundColor,
|
|
495
|
+
config.padding
|
|
496
|
+
);
|
|
497
|
+
const blob = await canvasToBlob(canvas);
|
|
498
|
+
files.push({ name: `linux/${dir}/${config.name}.png`, data: blob });
|
|
509
499
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
500
|
+
}
|
|
501
|
+
const zipBlob = await createZipFile(files);
|
|
502
|
+
const url = URL.createObjectURL(zipBlob);
|
|
503
|
+
const link = document.createElement("a");
|
|
504
|
+
link.href = url;
|
|
505
|
+
link.download = `${config.name}-icons.zip`;
|
|
506
|
+
link.click();
|
|
507
|
+
URL.revokeObjectURL(url);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
console.error("Failed to download icons:", error);
|
|
510
|
+
} finally {
|
|
511
|
+
setIsExporting(false);
|
|
512
|
+
}
|
|
513
|
+
}, [icon, icons, config, exportAll]);
|
|
514
|
+
return {
|
|
515
|
+
config,
|
|
516
|
+
setConfig,
|
|
517
|
+
icons,
|
|
518
|
+
isExporting,
|
|
519
|
+
progress,
|
|
520
|
+
exportAll,
|
|
521
|
+
exportWindows,
|
|
522
|
+
exportMacOS,
|
|
523
|
+
exportLinux,
|
|
524
|
+
downloadAll,
|
|
525
|
+
downloadSingle
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
const NiceDesktopIconExporter = ({
|
|
529
|
+
icon,
|
|
530
|
+
initialConfig,
|
|
531
|
+
onExport,
|
|
532
|
+
className = ""
|
|
533
|
+
}) => {
|
|
534
|
+
const { t } = useNiceTranslation();
|
|
535
|
+
const {
|
|
536
|
+
config,
|
|
537
|
+
setConfig,
|
|
538
|
+
icons,
|
|
539
|
+
isExporting,
|
|
540
|
+
progress,
|
|
541
|
+
exportAll,
|
|
542
|
+
downloadAll,
|
|
543
|
+
downloadSingle
|
|
544
|
+
} = useDesktopIconExporter(icon, initialConfig);
|
|
545
|
+
const handleExport = async () => {
|
|
546
|
+
await exportAll();
|
|
547
|
+
onExport?.(icons);
|
|
548
|
+
};
|
|
549
|
+
const IconComponent = icon;
|
|
550
|
+
return /* @__PURE__ */ jsxs("div", { className: `nice-desktop-icon-exporter ${className}`, children: [
|
|
551
|
+
/* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__preview", children: [
|
|
552
|
+
/* @__PURE__ */ jsx("h3", { children: t("desktopIconExporter.preview", "Preview") }),
|
|
553
|
+
/* @__PURE__ */ jsx("div", { className: "nice-desktop-icon-exporter__preview-grid", children: [16, 32, 48, 64, 128, 256].map((size) => /* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__preview-item", children: [
|
|
554
|
+
/* @__PURE__ */ jsx(
|
|
555
|
+
"div",
|
|
556
|
+
{
|
|
557
|
+
className: "nice-desktop-icon-exporter__preview-box",
|
|
558
|
+
style: {
|
|
559
|
+
width: Math.min(size, 128),
|
|
560
|
+
height: Math.min(size, 128),
|
|
561
|
+
backgroundColor: config.backgroundColor || "#f0f0f0"
|
|
562
|
+
},
|
|
563
|
+
children: /* @__PURE__ */ jsx(
|
|
564
|
+
IconComponent,
|
|
565
|
+
{
|
|
566
|
+
size: Math.min(size, 128) * (1 - config.padding * 2),
|
|
567
|
+
fill: config.fillColor
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
}
|
|
571
|
+
),
|
|
572
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
573
|
+
size,
|
|
574
|
+
"×",
|
|
575
|
+
size
|
|
576
|
+
] })
|
|
577
|
+
] }, size)) })
|
|
578
|
+
] }),
|
|
579
|
+
/* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__config", children: [
|
|
580
|
+
/* @__PURE__ */ jsx("h3", { children: t("desktopIconExporter.configuration", "Configuration") }),
|
|
581
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
582
|
+
t("desktopIconExporter.iconName", "Icon Name:"),
|
|
583
|
+
/* @__PURE__ */ jsx(
|
|
584
|
+
"input",
|
|
585
|
+
{
|
|
586
|
+
type: "text",
|
|
587
|
+
value: config.name,
|
|
588
|
+
onChange: (e) => setConfig((c) => ({ ...c, name: e.target.value }))
|
|
589
|
+
}
|
|
590
|
+
)
|
|
591
|
+
] }),
|
|
592
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
593
|
+
t("desktopIconExporter.fillColor", "Fill Color:"),
|
|
594
|
+
/* @__PURE__ */ jsx(
|
|
595
|
+
"input",
|
|
596
|
+
{
|
|
597
|
+
type: "color",
|
|
598
|
+
value: config.fillColor,
|
|
599
|
+
onChange: (e) => setConfig((c) => ({ ...c, fillColor: e.target.value }))
|
|
600
|
+
}
|
|
601
|
+
)
|
|
602
|
+
] }),
|
|
603
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
604
|
+
t("desktopIconExporter.background", "Background:"),
|
|
605
|
+
/* @__PURE__ */ jsx(
|
|
606
|
+
"input",
|
|
607
|
+
{
|
|
608
|
+
type: "color",
|
|
609
|
+
value: config.backgroundColor || "#ffffff",
|
|
610
|
+
onChange: (e) => setConfig((c) => ({ ...c, backgroundColor: e.target.value }))
|
|
611
|
+
}
|
|
612
|
+
),
|
|
613
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setConfig((c) => ({ ...c, backgroundColor: void 0 })), children: t("desktopIconExporter.transparent", "Transparent") })
|
|
614
|
+
] }),
|
|
615
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
616
|
+
t("desktopIconExporter.padding", "Padding: {padding}%").replace(
|
|
617
|
+
"{padding}",
|
|
618
|
+
String(Math.round(config.padding * 100))
|
|
619
|
+
),
|
|
620
|
+
/* @__PURE__ */ jsx(
|
|
621
|
+
"input",
|
|
622
|
+
{
|
|
623
|
+
type: "range",
|
|
624
|
+
min: "0",
|
|
625
|
+
max: "0.3",
|
|
626
|
+
step: "0.01",
|
|
627
|
+
value: config.padding,
|
|
628
|
+
onChange: (e) => setConfig((c) => ({ ...c, padding: parseFloat(e.target.value) }))
|
|
629
|
+
}
|
|
630
|
+
)
|
|
631
|
+
] }),
|
|
632
|
+
/* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__platforms", children: [
|
|
633
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
634
|
+
/* @__PURE__ */ jsx(
|
|
635
|
+
"input",
|
|
636
|
+
{
|
|
637
|
+
type: "checkbox",
|
|
638
|
+
checked: config.includeWindows,
|
|
639
|
+
onChange: (e) => setConfig((c) => ({ ...c, includeWindows: e.target.checked }))
|
|
532
640
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
files.push({ name: `windows/${config.name}.ico`, data: icoBlob });
|
|
641
|
+
),
|
|
642
|
+
t("desktopIconExporter.windowsIco", "Windows (.ico)")
|
|
643
|
+
] }),
|
|
644
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
645
|
+
/* @__PURE__ */ jsx(
|
|
646
|
+
"input",
|
|
647
|
+
{
|
|
648
|
+
type: "checkbox",
|
|
649
|
+
checked: config.includeMacOS,
|
|
650
|
+
onChange: (e) => setConfig((c) => ({ ...c, includeMacOS: e.target.checked }))
|
|
544
651
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
name: `macos/${config.name}.iconset/icon_${size}x${size}${variant.suffix}.png`,
|
|
556
|
-
data: blob,
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
files.push({
|
|
561
|
-
name: 'macos/README.txt',
|
|
562
|
-
data: `To convert to .icns: iconutil -c icns ${config.name}.iconset`,
|
|
563
|
-
});
|
|
652
|
+
),
|
|
653
|
+
t("desktopIconExporter.macosIconset", "macOS (.iconset)")
|
|
654
|
+
] }),
|
|
655
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
656
|
+
/* @__PURE__ */ jsx(
|
|
657
|
+
"input",
|
|
658
|
+
{
|
|
659
|
+
type: "checkbox",
|
|
660
|
+
checked: config.includeLinux,
|
|
661
|
+
onChange: (e) => setConfig((c) => ({ ...c, includeLinux: e.target.checked }))
|
|
564
662
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
663
|
+
),
|
|
664
|
+
t("desktopIconExporter.linuxHicolor", "Linux (hicolor)")
|
|
665
|
+
] }),
|
|
666
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
667
|
+
/* @__PURE__ */ jsx(
|
|
668
|
+
"input",
|
|
669
|
+
{
|
|
670
|
+
type: "checkbox",
|
|
671
|
+
checked: config.includeRetina,
|
|
672
|
+
onChange: (e) => setConfig((c) => ({ ...c, includeRetina: e.target.checked }))
|
|
575
673
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
*/
|
|
627
|
-
const NiceDesktopIconExporter = ({ icon, initialConfig, onExport, className = '', }) => {
|
|
628
|
-
const { t } = useNiceTranslation();
|
|
629
|
-
const { config, setConfig, icons, isExporting, progress, exportAll, downloadAll, downloadSingle, } = useDesktopIconExporter(icon, initialConfig);
|
|
630
|
-
const handleExport = async () => {
|
|
631
|
-
await exportAll();
|
|
632
|
-
onExport === null || onExport === void 0 ? void 0 : onExport(icons);
|
|
633
|
-
};
|
|
634
|
-
const IconComponent = icon;
|
|
635
|
-
return (jsxs("div", { className: `nice-desktop-icon-exporter ${className}`, children: [jsxs("div", { className: "nice-desktop-icon-exporter__preview", children: [jsx("h3", { children: t('desktopIconExporter.preview', 'Preview') }), jsx("div", { className: "nice-desktop-icon-exporter__preview-grid", children: [16, 32, 48, 64, 128, 256].map((size) => (jsxs("div", { className: "nice-desktop-icon-exporter__preview-item", children: [jsx("div", { className: "nice-desktop-icon-exporter__preview-box", style: {
|
|
636
|
-
width: Math.min(size, 128),
|
|
637
|
-
height: Math.min(size, 128),
|
|
638
|
-
backgroundColor: config.backgroundColor || '#f0f0f0',
|
|
639
|
-
}, children: jsx(IconComponent, { size: Math.min(size, 128) * (1 - config.padding * 2), fill: config.fillColor }) }), jsxs("span", { children: [size, "\u00D7", size] })] }, size))) })] }), jsxs("div", { className: "nice-desktop-icon-exporter__config", children: [jsx("h3", { children: t('desktopIconExporter.configuration', 'Configuration') }), jsxs("label", { children: [t('desktopIconExporter.iconName', 'Icon Name:'), jsx("input", { type: "text", value: config.name, onChange: (e) => setConfig((c) => ({ ...c, name: e.target.value })) })] }), jsxs("label", { children: [t('desktopIconExporter.fillColor', 'Fill Color:'), jsx("input", { type: "color", value: config.fillColor, onChange: (e) => setConfig((c) => ({ ...c, fillColor: e.target.value })) })] }), jsxs("label", { children: [t('desktopIconExporter.background', 'Background:'), jsx("input", { type: "color", value: config.backgroundColor || '#ffffff', onChange: (e) => setConfig((c) => ({ ...c, backgroundColor: e.target.value })) }), jsx("button", { onClick: () => setConfig((c) => ({ ...c, backgroundColor: undefined })), children: t('desktopIconExporter.transparent', 'Transparent') })] }), jsxs("label", { children: [t('desktopIconExporter.padding', 'Padding: {padding}%').replace('{padding}', String(Math.round(config.padding * 100))), jsx("input", { type: "range", min: "0", max: "0.3", step: "0.01", value: config.padding, onChange: (e) => setConfig((c) => ({ ...c, padding: parseFloat(e.target.value) })) })] }), jsxs("div", { className: "nice-desktop-icon-exporter__platforms", children: [jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.includeWindows, onChange: (e) => setConfig((c) => ({ ...c, includeWindows: e.target.checked })) }), t('desktopIconExporter.windowsIco', 'Windows (.ico)')] }), jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.includeMacOS, onChange: (e) => setConfig((c) => ({ ...c, includeMacOS: e.target.checked })) }), t('desktopIconExporter.macosIconset', 'macOS (.iconset)')] }), jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.includeLinux, onChange: (e) => setConfig((c) => ({ ...c, includeLinux: e.target.checked })) }), t('desktopIconExporter.linuxHicolor', 'Linux (hicolor)')] }), jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.includeRetina, onChange: (e) => setConfig((c) => ({ ...c, includeRetina: e.target.checked })) }), t('desktopIconExporter.hidpiRetina2x3x', 'HiDPI/Retina (@2x, @3x)')] })] }), jsxs("div", { className: "nice-desktop-icon-exporter__sizes", children: [jsx("h4", { children: t('desktopIconExporter.sizes', 'Sizes:') }), DESKTOP_ICON_SIZES.map(({ size, label }) => (jsxs("label", { children: [jsx("input", { type: "checkbox", checked: config.sizes.includes(size), onChange: (e) => {
|
|
640
|
-
setConfig((c) => ({
|
|
641
|
-
...c,
|
|
642
|
-
sizes: e.target.checked
|
|
643
|
-
? [...c.sizes, size].sort((a, b) => a - b)
|
|
644
|
-
: c.sizes.filter((s) => s !== size),
|
|
645
|
-
}));
|
|
646
|
-
} }), label] }, size)))] })] }), jsxs("div", { className: "nice-desktop-icon-exporter__actions", children: [jsx("button", { onClick: handleExport, disabled: isExporting, children: isExporting
|
|
647
|
-
? t('desktopIconExporter.exporting', 'Exporting... {progress}%').replace('{progress}', String(Math.round(progress)))
|
|
648
|
-
: t('desktopIconExporter.generateIcons', 'Generate Icons') }), jsx("button", { onClick: downloadAll, disabled: isExporting || icons.length === 0, children: t('desktopIconExporter.downloadAllZip', 'Download All (ZIP)') })] }), icons.length > 0 && (jsxs("div", { className: "nice-desktop-icon-exporter__results", children: [jsx("h3", { children: t('desktopIconExporter.generatedIcons', 'Generated Icons ({icons})').replace('{icons}', String(icons.length)) }), jsx("div", { className: "nice-desktop-icon-exporter__results-grid", children: icons.map((exportedIcon) => (jsxs("div", { className: "nice-desktop-icon-exporter__result-item", children: [jsx("img", { src: exportedIcon.dataUrl, alt: exportedIcon.filename }), jsx("span", { children: exportedIcon.filename }), jsx("button", { onClick: () => downloadSingle(exportedIcon), children: t('desktopIconExporter.download', 'Download') })] }, exportedIcon.filename))) })] }))] }));
|
|
674
|
+
),
|
|
675
|
+
t("desktopIconExporter.hidpiRetina2x3x", "HiDPI/Retina (@2x, @3x)")
|
|
676
|
+
] })
|
|
677
|
+
] }),
|
|
678
|
+
/* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__sizes", children: [
|
|
679
|
+
/* @__PURE__ */ jsx("h4", { children: t("desktopIconExporter.sizes", "Sizes:") }),
|
|
680
|
+
DESKTOP_ICON_SIZES.map(({ size, label }) => /* @__PURE__ */ jsxs("label", { children: [
|
|
681
|
+
/* @__PURE__ */ jsx(
|
|
682
|
+
"input",
|
|
683
|
+
{
|
|
684
|
+
type: "checkbox",
|
|
685
|
+
checked: config.sizes.includes(size),
|
|
686
|
+
onChange: (e) => {
|
|
687
|
+
setConfig((c) => ({
|
|
688
|
+
...c,
|
|
689
|
+
sizes: e.target.checked ? [...c.sizes, size].sort((a, b) => a - b) : c.sizes.filter((s) => s !== size)
|
|
690
|
+
}));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
),
|
|
694
|
+
label
|
|
695
|
+
] }, size))
|
|
696
|
+
] })
|
|
697
|
+
] }),
|
|
698
|
+
/* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__actions", children: [
|
|
699
|
+
/* @__PURE__ */ jsx("button", { onClick: handleExport, disabled: isExporting, children: isExporting ? t("desktopIconExporter.exporting", "Exporting... {progress}%").replace(
|
|
700
|
+
"{progress}",
|
|
701
|
+
String(Math.round(progress))
|
|
702
|
+
) : t("desktopIconExporter.generateIcons", "Generate Icons") }),
|
|
703
|
+
/* @__PURE__ */ jsx("button", { onClick: downloadAll, disabled: isExporting || icons.length === 0, children: t("desktopIconExporter.downloadAllZip", "Download All (ZIP)") })
|
|
704
|
+
] }),
|
|
705
|
+
icons.length > 0 && /* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__results", children: [
|
|
706
|
+
/* @__PURE__ */ jsx("h3", { children: t("desktopIconExporter.generatedIcons", "Generated Icons ({icons})").replace(
|
|
707
|
+
"{icons}",
|
|
708
|
+
String(icons.length)
|
|
709
|
+
) }),
|
|
710
|
+
/* @__PURE__ */ jsx("div", { className: "nice-desktop-icon-exporter__results-grid", children: icons.map((exportedIcon) => /* @__PURE__ */ jsxs("div", { className: "nice-desktop-icon-exporter__result-item", children: [
|
|
711
|
+
/* @__PURE__ */ jsx("img", { src: exportedIcon.dataUrl, alt: exportedIcon.filename }),
|
|
712
|
+
/* @__PURE__ */ jsx("span", { children: exportedIcon.filename }),
|
|
713
|
+
/* @__PURE__ */ jsx("button", { onClick: () => downloadSingle(exportedIcon), children: t("desktopIconExporter.download", "Download") })
|
|
714
|
+
] }, exportedIcon.filename)) })
|
|
715
|
+
] })
|
|
716
|
+
] });
|
|
717
|
+
};
|
|
718
|
+
export {
|
|
719
|
+
DESKTOP_ICON_SIZES,
|
|
720
|
+
LINUX_HICOLOR_DIRS,
|
|
721
|
+
NiceDesktopIconExporter,
|
|
722
|
+
RETINA_VARIANTS,
|
|
723
|
+
useDesktopIconExporter
|
|
649
724
|
};
|
|
650
|
-
|
|
651
|
-
export { DESKTOP_ICON_SIZES, LINUX_HICOLOR_DIRS, NiceDesktopIconExporter, RETINA_VARIANTS, useDesktopIconExporter };
|