@iconoma/studio 0.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 (53) hide show
  1. package/README.md +9 -0
  2. package/dist/api/actions.d.ts +7 -0
  3. package/dist/api/actions.d.ts.map +1 -0
  4. package/dist/api/actions.js +351 -0
  5. package/dist/api/db.d.ts +11 -0
  6. package/dist/api/db.d.ts.map +1 -0
  7. package/dist/api/db.js +20 -0
  8. package/dist/api/index.d.ts +4 -0
  9. package/dist/api/index.d.ts.map +1 -0
  10. package/dist/api/index.js +201 -0
  11. package/dist/api/queue.d.ts +6 -0
  12. package/dist/api/queue.d.ts.map +1 -0
  13. package/dist/api/queue.js +4 -0
  14. package/dist/api/svgo-plugin-map-colors.d.ts +11 -0
  15. package/dist/api/svgo-plugin-map-colors.d.ts.map +1 -0
  16. package/dist/api/svgo-plugin-map-colors.js +199 -0
  17. package/dist/api/target-clients/interface.d.ts +6 -0
  18. package/dist/api/target-clients/interface.d.ts.map +1 -0
  19. package/dist/api/target-clients/interface.js +1 -0
  20. package/dist/api/target-clients/react-native.d.ts +7 -0
  21. package/dist/api/target-clients/react-native.d.ts.map +1 -0
  22. package/dist/api/target-clients/react-native.js +22 -0
  23. package/dist/api/target-clients/react.d.ts +9 -0
  24. package/dist/api/target-clients/react.d.ts.map +1 -0
  25. package/dist/api/target-clients/react.js +285 -0
  26. package/dist/api/types.d.ts +57 -0
  27. package/dist/api/types.d.ts.map +1 -0
  28. package/dist/api/types.js +1 -0
  29. package/dist/api/utils.d.ts +28 -0
  30. package/dist/api/utils.d.ts.map +1 -0
  31. package/dist/api/utils.js +126 -0
  32. package/dist/client/assets/base-80a1f760-DXByDNOn.js +1 -0
  33. package/dist/client/assets/consoleHook-59e792cb-Cc-Wa9Dv.js +2 -0
  34. package/dist/client/assets/index-2gul9srt.js +810 -0
  35. package/dist/client/assets/index-599aeaf7-CuvDGLig.js +16 -0
  36. package/dist/client/assets/index-B8EuJHGY.css +1 -0
  37. package/dist/client/assets/node-C6_XYplI.js +4 -0
  38. package/dist/client/assets/runtime-BL9Nzh_-.js +1 -0
  39. package/dist/client/favicon.ico +0 -0
  40. package/dist/client/icon.png +0 -0
  41. package/dist/client/index.html +21 -0
  42. package/dist/server/assets/base-80a1f760-qtF4btYl.js +31 -0
  43. package/dist/server/assets/consoleHook-59e792cb-DzCtq2z9.js +230 -0
  44. package/dist/server/assets/index-599aeaf7-CrZ9wybV.js +7378 -0
  45. package/dist/server/assets/node-Cq9Cey_i.js +1412 -0
  46. package/dist/server/assets/runtime-Cj4RK1K3.js +8158 -0
  47. package/dist/server/entry-server.js +38533 -0
  48. package/dist/server/favicon.ico +0 -0
  49. package/dist/server/icon.png +0 -0
  50. package/dist/server.d.ts +10 -0
  51. package/dist/server.d.ts.map +1 -0
  52. package/dist/server.js +86 -0
  53. package/package.json +88 -0
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # Iconoma Studio!
2
+
3
+ This is the Iconoma Studio, if you want to use Iconoma go to [https://github.com/Theryston/iconoma](https://github.com/Theryston/iconoma). But to start Iconoma Studio programmatically:
4
+
5
+ ```ts
6
+ import { createServer } from "@iconoma/studio";
7
+
8
+ const { url, close } = await createServer({ port });
9
+ ```
@@ -0,0 +1,7 @@
1
+ import { Table } from "./db.js";
2
+ import { ActionModel } from "./types.js";
3
+ export declare function actionsWorker({ actionId, table, }: {
4
+ actionId: number;
5
+ table: Table<ActionModel>;
6
+ }): Promise<void | import("./types.js").LockFileIcon>;
7
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../api/actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAmBtC,wBAAsB,aAAa,CAAC,EAClC,QAAQ,EACR,KAAK,GACN,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;CAC3B,kDAuDA"}
@@ -0,0 +1,351 @@
1
+ import { getIconContent, getLockFile, getPwd, setLockFile, getConfig, createAction, setContent, } from "./utils.js";
2
+ import path from "node:path";
3
+ import fs from "node:fs/promises";
4
+ import crypto from "node:crypto";
5
+ import { optimize } from "svgo";
6
+ import { ReactTargetClient } from "./target-clients/react.js";
7
+ import { ReactNativeTargetClient } from "./target-clients/react-native.js";
8
+ import { mapColors } from "./svgo-plugin-map-colors.js";
9
+ export async function actionsWorker({ actionId, table, }) {
10
+ const action = table.get(actionId);
11
+ if (!action)
12
+ return;
13
+ try {
14
+ let output = null;
15
+ switch (action.type) {
16
+ case "MIGRATE_SVG_TO_LOCK":
17
+ output = await migrateSvgToLock(action.filePath, action.metadata && "iconKey" in action.metadata
18
+ ? action.metadata
19
+ : undefined);
20
+ break;
21
+ case "MIGRATE_SVG_TO_FILE":
22
+ output = await migrateSvgToFile(action.filePath, action.iconKey);
23
+ break;
24
+ case "ADD_EXTRA_TARGET":
25
+ output = await addExtraTarget(action);
26
+ break;
27
+ case "REMOVE_EXTRA_TARGET":
28
+ output = await removeExtraTarget(action);
29
+ break;
30
+ case "CREATE_ICON":
31
+ output = await createIcon(action);
32
+ break;
33
+ case "REMOVE_ICON":
34
+ output = await removeIcon(action);
35
+ break;
36
+ case "REGENERATE_ICON":
37
+ output = await regenerateIcon(action);
38
+ break;
39
+ case "REGENERATE_ALL":
40
+ output = await regenerateAll(action, actionId, table);
41
+ break;
42
+ }
43
+ table.update(actionId, {
44
+ ...action,
45
+ status: "completed",
46
+ percentage: 100,
47
+ });
48
+ return output;
49
+ }
50
+ catch (error) {
51
+ console.error(error);
52
+ table.update(actionId, {
53
+ ...action,
54
+ status: "failed",
55
+ error: error instanceof Error ? error.message : "Unknown error",
56
+ });
57
+ }
58
+ }
59
+ async function removeExtraTarget(action) {
60
+ const lockFile = await getLockFile();
61
+ if (!lockFile)
62
+ throw new Error(`Lock file not found`);
63
+ const icon = lockFile.icons[action.iconKey];
64
+ if (!icon)
65
+ throw new Error(`Icon ${action.iconKey} not found in lock file`);
66
+ let targetClient;
67
+ switch (action.targetId) {
68
+ case "react":
69
+ targetClient = new ReactTargetClient();
70
+ break;
71
+ case "react-native":
72
+ targetClient = new ReactNativeTargetClient();
73
+ break;
74
+ }
75
+ if (!targetClient)
76
+ throw new Error(`Target client not found`);
77
+ await targetClient.removeIcon(icon, action.iconKey, action.filePath);
78
+ delete icon.targets[action.targetId];
79
+ lockFile.icons[action.iconKey] = icon;
80
+ await setLockFile(lockFile);
81
+ console.log(`Removed target ${action.targetId} for ${action.iconKey}`);
82
+ }
83
+ async function addExtraTarget(action) {
84
+ const lockFile = await getLockFile();
85
+ if (!lockFile)
86
+ throw new Error(`Lock file not found`);
87
+ const icon = lockFile.icons[action.iconKey];
88
+ if (!icon)
89
+ throw new Error(`Icon ${action.iconKey} not found in lock file`);
90
+ let targetClient;
91
+ switch (action.targetId) {
92
+ case "react":
93
+ targetClient = new ReactTargetClient();
94
+ break;
95
+ case "react-native":
96
+ targetClient = new ReactNativeTargetClient();
97
+ break;
98
+ }
99
+ if (!targetClient)
100
+ throw new Error(`Target client not found`);
101
+ await targetClient.addIcon(icon, action.iconKey, action.filePath);
102
+ icon.targets[action.targetId] = {
103
+ path: action.filePath,
104
+ builtFrom: {
105
+ svgHash: icon.svg.hash,
106
+ configHash: lockFile.configHash,
107
+ },
108
+ };
109
+ lockFile.icons[action.iconKey] = icon;
110
+ await setLockFile(lockFile);
111
+ console.log(`Added target ${action.targetId} for ${action.iconKey}`);
112
+ }
113
+ async function migrateSvgToLock(filePath, metadata) {
114
+ if (!filePath || !metadata?.iconKey)
115
+ return;
116
+ const lockFile = await getLockFile();
117
+ if (!lockFile)
118
+ return;
119
+ const icon = lockFile.icons[metadata.iconKey];
120
+ if (!icon)
121
+ return;
122
+ const pwd = await getPwd();
123
+ const fullPath = path.join(pwd, filePath);
124
+ const exists = await fs
125
+ .access(fullPath)
126
+ .then(() => true)
127
+ .catch(() => false);
128
+ if (!exists)
129
+ throw new Error(`File ${fullPath} does not exist`);
130
+ const content = await getIconContent(icon);
131
+ if (icon.svg.content !== content) {
132
+ icon.svg.content = content;
133
+ icon.svg.hash = crypto.createHash("sha256").update(content).digest("hex");
134
+ lockFile.icons[metadata.iconKey] = icon;
135
+ await setLockFile(lockFile);
136
+ }
137
+ else {
138
+ console.log(`SVG for ${metadata.iconKey} is already in lock`);
139
+ }
140
+ await fs.unlink(fullPath);
141
+ const folder = path.dirname(fullPath);
142
+ const files = await fs.readdir(folder);
143
+ if (files.length === 0) {
144
+ await fs.rmdir(folder);
145
+ console.log(`Deleted folder ${folder}`);
146
+ }
147
+ console.log(`Migrated SVG to lock for ${metadata.iconKey}`);
148
+ }
149
+ async function migrateSvgToFile(filePath, iconKey) {
150
+ if (!filePath || !iconKey)
151
+ return;
152
+ const pwd = await getPwd();
153
+ const outputPath = path.join(pwd, filePath);
154
+ const exists = await fs
155
+ .access(outputPath)
156
+ .then(() => true)
157
+ .catch(() => false);
158
+ if (exists) {
159
+ console.log(`File ${outputPath} already exists`);
160
+ return;
161
+ }
162
+ const lockFile = await getLockFile();
163
+ if (!lockFile)
164
+ throw new Error(`Lock file not found`);
165
+ const icon = lockFile.icons[iconKey];
166
+ if (!icon)
167
+ throw new Error(`Icon ${iconKey} not found in lock file`);
168
+ const folder = path.dirname(outputPath);
169
+ await fs.mkdir(folder, { recursive: true });
170
+ const content = await getIconContent(icon);
171
+ await fs.writeFile(outputPath, content);
172
+ icon.svg.content = `file://${filePath}`;
173
+ icon.svg.hash = crypto.createHash("sha256").update(content).digest("hex");
174
+ lockFile.icons[iconKey] = icon;
175
+ await setLockFile(lockFile);
176
+ console.log(`Migrated SVG to file for ${iconKey}`);
177
+ }
178
+ async function removeIcon(action) {
179
+ const lockFile = await getLockFile();
180
+ if (!lockFile)
181
+ throw new Error(`Lock file not found`);
182
+ const icon = lockFile.icons[action.iconKey];
183
+ if (!icon)
184
+ throw new Error(`Icon ${action.iconKey} not found in lock file`);
185
+ for (const [targetId, target] of Object.entries(icon.targets)) {
186
+ await removeExtraTarget({
187
+ type: "REMOVE_EXTRA_TARGET",
188
+ iconKey: action.iconKey,
189
+ targetId,
190
+ filePath: target.path,
191
+ status: "pending",
192
+ percentage: 0,
193
+ });
194
+ }
195
+ if (icon.svg.content.startsWith("file://")) {
196
+ const iconPath = icon.svg.content.replace("file://", "");
197
+ const pwd = await getPwd();
198
+ const filePath = path.join(pwd, iconPath);
199
+ const exists = await fs
200
+ .access(filePath)
201
+ .then(() => true)
202
+ .catch(() => false);
203
+ if (exists) {
204
+ await fs.unlink(filePath);
205
+ const folder = path.dirname(filePath);
206
+ const files = await fs.readdir(folder).catch(() => []);
207
+ if (files.length === 0) {
208
+ await fs.rmdir(folder).catch(() => { });
209
+ }
210
+ }
211
+ }
212
+ delete lockFile.icons[action.iconKey];
213
+ await setLockFile(lockFile);
214
+ console.log(`Removed icon ${action.iconKey}`);
215
+ }
216
+ async function regenerateAll(action, actionId, table) {
217
+ const lockFile = await getLockFile();
218
+ if (!lockFile)
219
+ throw new Error(`Lock file not found`);
220
+ const iconKeys = Object.keys(lockFile.icons);
221
+ const totalIcons = iconKeys.length;
222
+ if (totalIcons === 0) {
223
+ console.log("No icons to regenerate");
224
+ return;
225
+ }
226
+ table.update(actionId, {
227
+ ...action,
228
+ status: "processing",
229
+ percentage: 0,
230
+ });
231
+ for (let i = 0; i < iconKeys.length; i++) {
232
+ const iconKey = iconKeys[i];
233
+ try {
234
+ await regenerateIcon({
235
+ type: "REGENERATE_ICON",
236
+ iconKey,
237
+ status: "pending",
238
+ percentage: 0,
239
+ });
240
+ const percentage = Math.round(((i + 1) / totalIcons) * 100);
241
+ table.update(actionId, {
242
+ ...action,
243
+ status: "processing",
244
+ percentage,
245
+ });
246
+ console.log(`Regenerated icon ${iconKey} (${i + 1}/${totalIcons})`);
247
+ }
248
+ catch (error) {
249
+ console.error(`Failed to regenerate icon ${iconKey}:`, error);
250
+ }
251
+ }
252
+ console.log(`Regenerated all ${totalIcons} icons`);
253
+ }
254
+ async function regenerateIcon(action) {
255
+ const lockFile = await getLockFile();
256
+ if (!lockFile)
257
+ throw new Error(`Lock file not found`);
258
+ const icon = lockFile.icons[action.iconKey];
259
+ if (!icon)
260
+ throw new Error(`Icon ${action.iconKey} not found in lock file`);
261
+ const svgContent = await getIconContent(icon);
262
+ const iconName = icon.name;
263
+ const tags = icon.tags;
264
+ await removeIcon({
265
+ type: "REMOVE_ICON",
266
+ iconKey: action.iconKey,
267
+ status: "pending",
268
+ percentage: 0,
269
+ });
270
+ await createIcon({
271
+ type: "CREATE_ICON",
272
+ metadata: {
273
+ name: iconName,
274
+ tags,
275
+ content: svgContent,
276
+ },
277
+ status: "pending",
278
+ percentage: 0,
279
+ });
280
+ console.log(`Regenerated icon ${action.iconKey}`);
281
+ }
282
+ async function createIcon(action) {
283
+ if (!action.metadata ||
284
+ !("name" in action.metadata) ||
285
+ !("tags" in action.metadata) ||
286
+ !("content" in action.metadata)) {
287
+ throw new Error("CREATE_ICON action requires metadata with name, tags, and content");
288
+ }
289
+ const { name: iconName, tags, content } = action.metadata;
290
+ const iconKey = iconName.toLowerCase().replace(/ /g, "-");
291
+ const config = await getConfig();
292
+ if (!config)
293
+ throw new Error("Config not found");
294
+ const colorMap = action.metadata.colorMap;
295
+ const optimizedResult = optimize(content, {
296
+ ...config.svgo,
297
+ plugins: [
298
+ ...(config.svgo?.plugins ?? []),
299
+ "convertStyleToAttrs",
300
+ ...(colorMap
301
+ ? [
302
+ mapColors({
303
+ map: colorMap,
304
+ replaceStyleElementText: true,
305
+ replaceInlineStyle: true,
306
+ }),
307
+ ]
308
+ : []),
309
+ ],
310
+ });
311
+ const optimizedContent = optimizedResult.data;
312
+ const { content: svgContent, hash: svgHash } = await setContent(config, iconKey, optimizedContent);
313
+ const lockFile = await getLockFile();
314
+ if (!lockFile)
315
+ throw new Error("Lock file not found");
316
+ let icon = lockFile.icons[iconKey];
317
+ const colorVariableKeys = Object.values(colorMap || {}).map((variable) => variable);
318
+ if (icon) {
319
+ icon.name = iconKey;
320
+ icon.tags = tags;
321
+ icon.svg.content = svgContent;
322
+ icon.svg.hash = svgHash;
323
+ icon.targets = {};
324
+ icon.colorVariableKeys = colorVariableKeys;
325
+ }
326
+ else {
327
+ icon = {
328
+ name: iconKey,
329
+ tags,
330
+ svg: {
331
+ content: svgContent,
332
+ hash: svgHash,
333
+ },
334
+ targets: {},
335
+ colorVariableKeys,
336
+ };
337
+ }
338
+ lockFile.icons[iconKey] = icon;
339
+ await setLockFile(lockFile);
340
+ for (const target of config.extraTargets) {
341
+ const filePath = target.outputPath.replace("{name}", iconKey);
342
+ createAction({
343
+ type: "ADD_EXTRA_TARGET",
344
+ targetId: target.targetId,
345
+ filePath,
346
+ iconKey,
347
+ });
348
+ }
349
+ console.log(`Created/updated icon ${iconKey}`);
350
+ return icon;
351
+ }
@@ -0,0 +1,11 @@
1
+ export declare function table<T>(tableName?: string): {
2
+ create: (value: T) => number;
3
+ get: (id: number) => T | undefined;
4
+ update: (id: number, value: T) => Map<number, any>;
5
+ delete: (id: number) => boolean;
6
+ getAll: () => (T & {
7
+ id: number;
8
+ })[];
9
+ };
10
+ export type Table<T> = ReturnType<typeof table<T>>;
11
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../api/db.ts"],"names":[],"mappings":"AAEA,wBAAgB,KAAK,CAAC,CAAC,EAAE,SAAS,GAAE,MAAkB;oBASlC,CAAC;cAMP,MAAM,KAAG,CAAC,GAAG,SAAS;iBACnB,MAAM,SAAS,CAAC;iBAChB,MAAM;kBACP,CAAC,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,EAAE;EAGrC;AAED,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
package/dist/api/db.js ADDED
@@ -0,0 +1,20 @@
1
+ const db = new Map();
2
+ export function table(tableName = "default") {
3
+ let data = db.get(tableName);
4
+ if (!data) {
5
+ data = new Map();
6
+ db.set(tableName, data);
7
+ }
8
+ return {
9
+ create: (value) => {
10
+ const size = data.size;
11
+ const id = size + 1;
12
+ data.set(id, value);
13
+ return id;
14
+ },
15
+ get: (id) => data.get(id),
16
+ update: (id, value) => data.set(id, value),
17
+ delete: (id) => data.delete(id),
18
+ getAll: () => Array.from(data.values()).map((value, id) => ({ ...value, id })),
19
+ };
20
+ }
@@ -0,0 +1,4 @@
1
+ import { Router } from "express";
2
+ declare const apiRoutes: Router;
3
+ export default apiRoutes;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../api/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAQ,MAAM,SAAS,CAAC;AAevC,QAAA,MAAM,SAAS,EAAE,MAAiB,CAAC;AAgRnC,eAAe,SAAS,CAAC"}
@@ -0,0 +1,201 @@
1
+ import { Router, json } from "express";
2
+ import { actionsTable, createAction, getConfig, getLockFile, getPwd, setConfig, toPascalFromSeparated, getIconContent, } from "./utils.js";
3
+ import path from "node:path";
4
+ import fs from "node:fs/promises";
5
+ const apiRoutes = Router();
6
+ apiRoutes.use(json());
7
+ apiRoutes.get("/pwd", async (req, res) => {
8
+ res.json({ pwd: await getPwd() });
9
+ });
10
+ apiRoutes.get("/config", async (req, res) => {
11
+ const config = await getConfig();
12
+ res.json(config);
13
+ });
14
+ apiRoutes.post("/config/changes", async (req, res) => {
15
+ const currentConfig = await getConfig();
16
+ const lockFile = await getLockFile();
17
+ const newConfig = req.body;
18
+ if (!currentConfig) {
19
+ return res.json({ changes: [] });
20
+ }
21
+ const addedExtraTargets = newConfig.extraTargets.filter((newTarget) => !currentConfig.extraTargets.some((currentTarget) => currentTarget.targetId === newTarget.targetId));
22
+ const removedExtraTargets = currentConfig.extraTargets.filter((currentTarget) => !newConfig.extraTargets.some((newTarget) => newTarget.targetId === currentTarget.targetId));
23
+ const changedExtraTargets = newConfig.extraTargets
24
+ .filter((newTarget) => currentConfig.extraTargets.some((currentTarget) => currentTarget.targetId === newTarget.targetId &&
25
+ currentTarget.outputPath !== newTarget.outputPath))
26
+ .filter((newTarget) => !addedExtraTargets.some((t) => t.targetId === newTarget.targetId) &&
27
+ !removedExtraTargets.some((t) => t.targetId === newTarget.targetId));
28
+ const changes = [];
29
+ const icons = lockFile?.icons || {};
30
+ for (const iconKey of Object.keys(icons)) {
31
+ const icon = icons[iconKey];
32
+ if (!icon)
33
+ continue;
34
+ if (newConfig.svg.folder !== currentConfig.svg.folder ||
35
+ newConfig.svg.inLock !== currentConfig.svg.inLock) {
36
+ const isFile = icon.svg.content.startsWith("file://");
37
+ if (isFile && newConfig.svg.inLock) {
38
+ changes.push({
39
+ type: "MIGRATE_SVG_TO_LOCK",
40
+ filePath: icon.svg.content.replace("file://", ""),
41
+ metadata: {
42
+ iconKey,
43
+ },
44
+ });
45
+ }
46
+ if (!isFile && !newConfig.svg.inLock) {
47
+ if (!newConfig.svg.folder)
48
+ continue;
49
+ const filePath = path.join(newConfig.svg.folder, iconKey + ".svg");
50
+ changes.push({
51
+ type: "MIGRATE_SVG_TO_FILE",
52
+ filePath,
53
+ iconKey,
54
+ });
55
+ }
56
+ }
57
+ for (const addedTarget of addedExtraTargets) {
58
+ const filePath = addedTarget.outputPath.replace("{name}", iconKey);
59
+ changes.push({
60
+ type: "ADD_EXTRA_TARGET",
61
+ targetId: addedTarget.targetId,
62
+ filePath,
63
+ iconKey,
64
+ });
65
+ }
66
+ for (const removedTarget of removedExtraTargets) {
67
+ const filePath = removedTarget.outputPath.replace("{name}", iconKey);
68
+ changes.push({
69
+ type: "REMOVE_EXTRA_TARGET",
70
+ targetId: removedTarget.targetId,
71
+ filePath,
72
+ iconKey,
73
+ });
74
+ }
75
+ for (const changedTarget of changedExtraTargets) {
76
+ const filePath = changedTarget.outputPath.replace("{name}", iconKey);
77
+ const originalFilePath = currentConfig.extraTargets
78
+ .find((t) => t.targetId === changedTarget.targetId)
79
+ ?.outputPath.replace("{name}", iconKey);
80
+ changes.push({
81
+ type: "REMOVE_EXTRA_TARGET",
82
+ targetId: changedTarget.targetId,
83
+ filePath: originalFilePath,
84
+ iconKey,
85
+ });
86
+ changes.push({
87
+ type: "ADD_EXTRA_TARGET",
88
+ targetId: changedTarget.targetId,
89
+ filePath,
90
+ iconKey,
91
+ });
92
+ }
93
+ }
94
+ if (JSON.stringify(newConfig.svgo) !== JSON.stringify(currentConfig.svgo)) {
95
+ changes.push({
96
+ type: "REGENERATE_ALL",
97
+ });
98
+ }
99
+ res.json({ changes });
100
+ });
101
+ apiRoutes.post("/config", async (req, res) => {
102
+ const body = req.body;
103
+ await setConfig(body.config);
104
+ for (const change of body.changes) {
105
+ createAction(change);
106
+ }
107
+ res.json({ success: true });
108
+ });
109
+ apiRoutes.get("/actions", async (req, res) => {
110
+ const actions = actionsTable.getAll();
111
+ res.json({ actions });
112
+ });
113
+ apiRoutes.get("/icons", async (req, res) => {
114
+ const lockFile = await getLockFile();
115
+ if (!lockFile) {
116
+ return res.json({ icons: [] });
117
+ }
118
+ const icons = Object.entries(lockFile.icons).map(async ([iconKey, icon]) => {
119
+ const svgContent = await getIconContent(icon);
120
+ return {
121
+ iconKey,
122
+ icon,
123
+ svgContent,
124
+ pascalName: toPascalFromSeparated(iconKey),
125
+ };
126
+ });
127
+ const iconsWithContent = await Promise.all(icons);
128
+ res.json({ icons: iconsWithContent });
129
+ });
130
+ apiRoutes.post("/icons/create", async (req, res) => {
131
+ const body = req.body;
132
+ const icon = await createAction({
133
+ type: "CREATE_ICON",
134
+ metadata: {
135
+ name: body.name,
136
+ tags: body.tags,
137
+ content: body.content,
138
+ colorMap: body.colorMap,
139
+ },
140
+ });
141
+ if (!icon) {
142
+ return res.status(500).json({ error: "Icon not found after creation" });
143
+ }
144
+ res.json({ icon, pascalName: toPascalFromSeparated(body.name) });
145
+ });
146
+ apiRoutes.get("/icons/:iconKey", async (req, res) => {
147
+ const iconKey = req.params.iconKey;
148
+ if (!iconKey) {
149
+ return res.status(400).json({ error: "Icon key is required" });
150
+ }
151
+ const lockFile = await getLockFile();
152
+ if (!lockFile) {
153
+ return res.status(404).json({ error: "Lock file not found" });
154
+ }
155
+ const icon = lockFile.icons[iconKey];
156
+ if (!icon) {
157
+ return res.status(404).json({ error: "Icon not found" });
158
+ }
159
+ const svgContent = await getIconContent(icon);
160
+ res.json({
161
+ icon,
162
+ pascalName: toPascalFromSeparated(iconKey),
163
+ svgContent,
164
+ });
165
+ });
166
+ apiRoutes.delete("/icons/:iconKey", async (req, res) => {
167
+ const iconKey = req.params.iconKey;
168
+ if (!iconKey) {
169
+ return res.status(400).json({ error: "Icon key is required" });
170
+ }
171
+ const lockFile = await getLockFile();
172
+ if (!lockFile) {
173
+ return res.status(404).json({ error: "Lock file not found" });
174
+ }
175
+ const icon = lockFile.icons[iconKey];
176
+ if (!icon) {
177
+ return res.status(404).json({ error: "Icon not found" });
178
+ }
179
+ await createAction({
180
+ type: "REMOVE_ICON",
181
+ iconKey,
182
+ });
183
+ res.json({ success: true });
184
+ });
185
+ apiRoutes.get("/icons/content", async (req, res) => {
186
+ const filePath = req.query.path;
187
+ if (!filePath) {
188
+ return res.status(400).json({ error: "Path is required" });
189
+ }
190
+ const pwd = await getPwd();
191
+ const fullPath = path.join(pwd, filePath);
192
+ try {
193
+ const content = await fs.readFile(fullPath, "utf-8");
194
+ res.setHeader("Content-Type", "image/svg+xml");
195
+ res.send(content);
196
+ }
197
+ catch (error) {
198
+ res.status(404).json({ error: "File not found" });
199
+ }
200
+ });
201
+ export default apiRoutes;
@@ -0,0 +1,6 @@
1
+ declare const actionsQueue: import("fastq").queueAsPromised<{
2
+ actionId: number;
3
+ table: import("./db.js").Table<import("./types.js").ActionModel>;
4
+ }, void | import("./types.js").LockFileIcon>;
5
+ export { actionsQueue };
6
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../api/queue.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,YAAY;;;yCAA0B,CAAC;AAE7C,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { promise as fastq } from "fastq";
2
+ import { actionsWorker } from "./actions.js";
3
+ const actionsQueue = fastq(actionsWorker, 1);
4
+ export { actionsQueue };
@@ -0,0 +1,11 @@
1
+ export type ColorMap = Record<string, string>;
2
+ export type MapColorsParams = {
3
+ map: ColorMap;
4
+ attributes?: string[];
5
+ replaceInlineStyle?: boolean;
6
+ replaceStyleElementText?: boolean;
7
+ };
8
+ export declare const mapColorsPluginBase: any;
9
+ export declare function mapColors(params: MapColorsParams): any;
10
+ export default mapColorsPluginBase;
11
+ //# sourceMappingURL=svgo-plugin-map-colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svgo-plugin-map-colors.d.ts","sourceRoot":"","sources":["../../api/svgo-plugin-map-colors.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE9C,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,QAAQ,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,CAAC;AA8HF,eAAO,MAAM,mBAAmB,EA2G3B,GAAG,CAAC;AAET,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,GAAG,CAStD;AAED,eAAe,mBAAmB,CAAC"}