@animaapp/anima-sdk-react 0.1.0 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@animaapp/anima-sdk-react",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Anima's JavaScript utilities library",
6
6
  "author": "Anima App, Inc.",
@@ -16,8 +16,7 @@
16
16
  "url": "git+https://github.com/AnimaApp/anima-sdk.git"
17
17
  },
18
18
  "publishConfig": {
19
- "access": "public",
20
- "registry": "https://registry.npmjs.org/"
19
+ "access": "public"
21
20
  },
22
21
  "scripts": {
23
22
  "build": "vite build",
@@ -41,4 +40,4 @@
41
40
  "vite-tsconfig-paths": "^5.1.4",
42
41
  "vitest": "^3.0.5"
43
42
  }
44
- }
43
+ }
@@ -1,3 +1,4 @@
1
+ import { arrayBufferToBase64 } from "./utils";
1
2
  import type {
2
3
  AnimaSDKResult,
3
4
  GetCodeParams,
@@ -7,6 +8,14 @@ import { convertCodegenFilesToAnimaFiles } from "@animaapp/anima-sdk";
7
8
  import { EventSource } from "eventsource";
8
9
  import { useImmer } from "use-immer";
9
10
 
11
+ type LocalAssetsStorage =
12
+ | { strategy: "local"; path: string }
13
+ | { strategy: "local"; filePath: string; referencePath: string };
14
+
15
+ export type UseAnimaParams = Omit<GetCodeParams, "assetsStorage"> & {
16
+ assetsStorage?: GetCodeParams["assetsStorage"] | LocalAssetsStorage;
17
+ };
18
+
10
19
  type Status = "idle" | "pending" | "success" | "aborted" | "error";
11
20
 
12
21
  type TaskStatus = "pending" | "running" | "finished";
@@ -33,7 +42,30 @@ const defaultProgress: CodegenStatus = {
33
42
  },
34
43
  };
35
44
 
36
- type StreamMessageByType<T extends StreamCodgenMessage['type']> = Extract<StreamCodgenMessage, { type: T }>;
45
+ type StreamMessageByType<T extends StreamCodgenMessage["type"]> = Extract<
46
+ StreamCodgenMessage,
47
+ { type: T }
48
+ >;
49
+
50
+ const getAssetsLocalStrategyParams = (
51
+ localAssetsStorage: LocalAssetsStorage
52
+ ) => {
53
+ if ("path" in localAssetsStorage) {
54
+ return {
55
+ filePath: localAssetsStorage.path.replace(/^\//, ""),
56
+ referencePath:
57
+ localAssetsStorage.path === "/" ? "" : localAssetsStorage.path, // Workaround to avoid duplicated slashes in the URL. Ideally, the fix should be done in Codegen.
58
+ };
59
+ }
60
+
61
+ return {
62
+ filePath: localAssetsStorage.filePath.replace(/^\//, ""),
63
+ referencePath:
64
+ localAssetsStorage.referencePath === "/"
65
+ ? ""
66
+ : localAssetsStorage.referencePath,
67
+ };
68
+ };
37
69
 
38
70
  export const useAnimaCodegen = ({
39
71
  url,
@@ -44,7 +76,9 @@ export const useAnimaCodegen = ({
44
76
  }) => {
45
77
  const [status, updateStatus] = useImmer<CodegenStatus>(defaultProgress);
46
78
 
47
- const getCode = async <T = GetCodeParams>(params: T) => {
79
+ const getCode = async <T extends UseAnimaParams = UseAnimaParams>(
80
+ params: T
81
+ ) => {
48
82
  updateStatus((draft) => {
49
83
  draft.status = "pending";
50
84
  draft.error = null;
@@ -52,6 +86,19 @@ export const useAnimaCodegen = ({
52
86
  draft.tasks = defaultProgress.tasks;
53
87
  });
54
88
 
89
+ const initialParams = structuredClone(params);
90
+
91
+ if (params.assetsStorage?.strategy === "local") {
92
+ const { referencePath } = getAssetsLocalStrategyParams(
93
+ params.assetsStorage
94
+ );
95
+
96
+ params.assetsStorage = {
97
+ strategy: "external",
98
+ url: referencePath,
99
+ };
100
+ }
101
+
55
102
  const es = new EventSource(url, {
56
103
  fetch: (url, init) =>
57
104
  fetch(url, {
@@ -65,12 +112,11 @@ export const useAnimaCodegen = ({
65
112
  result: AnimaSDKResult | null;
66
113
  error: Error | null;
67
114
  }>((resolve) => {
68
-
69
115
  const result: Partial<AnimaSDKResult> = {};
70
116
 
71
117
  // Add specific event listeners
72
- es.addEventListener('start', (event) => {
73
- const message = JSON.parse(event.data) as StreamMessageByType<'start'>;
118
+ es.addEventListener("start", (event) => {
119
+ const message = JSON.parse(event.data) as StreamMessageByType<"start">;
74
120
  result.sessionId = message.sessionId;
75
121
 
76
122
  updateStatus((draft) => {
@@ -78,8 +124,10 @@ export const useAnimaCodegen = ({
78
124
  });
79
125
  });
80
126
 
81
- es.addEventListener('pre_codegen', (event) => {
82
- const message = JSON.parse(event.data) as StreamMessageByType<'pre_codegen'>;
127
+ es.addEventListener("pre_codegen", (event) => {
128
+ const message = JSON.parse(
129
+ event.data
130
+ ) as StreamMessageByType<"pre_codegen">;
83
131
  if (message.message === "Anima model built") {
84
132
  updateStatus((draft) => {
85
133
  draft.tasks.fetchDesign.status = "finished";
@@ -89,13 +137,15 @@ export const useAnimaCodegen = ({
89
137
  }
90
138
  });
91
139
 
92
- es.addEventListener('figma_metadata', (e) => {
93
- const message = JSON.parse(e.data) as StreamMessageByType<'figma_metadata'>;
140
+ es.addEventListener("figma_metadata", (e) => {
141
+ const message = JSON.parse(
142
+ e.data
143
+ ) as StreamMessageByType<"figma_metadata">;
94
144
  result.figmaFileName = message.figmaFileName;
95
145
  result.figmaSelectedFrameName = message.figmaSelectedFrameName;
96
146
  });
97
147
 
98
- es.addEventListener('aborted', (e) => {
148
+ es.addEventListener("aborted", () => {
99
149
  updateStatus((draft) => {
100
150
  draft.status = "aborted";
101
151
  });
@@ -105,8 +155,10 @@ export const useAnimaCodegen = ({
105
155
  });
106
156
  });
107
157
 
108
- es.addEventListener('generating_code', (e) => {
109
- const message = JSON.parse(e.data) as StreamMessageByType<'generating_code'>;
158
+ es.addEventListener("generating_code", (event) => {
159
+ const message = JSON.parse(
160
+ event.data
161
+ ) as StreamMessageByType<"generating_code">;
110
162
  if (message.payload.status === "success") {
111
163
  const codegenFiles = message.payload.files as Record<
112
164
  string,
@@ -121,22 +173,33 @@ export const useAnimaCodegen = ({
121
173
  });
122
174
  });
123
175
 
124
- es.addEventListener('codegen_completed', (e) => {
176
+ es.addEventListener("codegen_completed", () => {
125
177
  updateStatus((draft) => {
126
178
  draft.tasks.codeGeneration.status = "finished";
127
179
  });
128
180
  });
129
181
 
130
- es.addEventListener('assets_uploaded', (e) => {
182
+ es.addEventListener("assets_uploaded", () => {
131
183
  updateStatus((draft) => {
132
184
  draft.tasks.uploadAssets.status = "finished";
133
185
  });
134
186
  });
135
187
 
136
- es.addEventListener('error', (e: ErrorEvent | MessageEvent) => {
188
+ es.addEventListener("assets_list", (event) => {
189
+ const message = JSON.parse(
190
+ event.data
191
+ ) as StreamMessageByType<"assets_list">;
192
+
193
+ result.assets = message.payload.assets;
194
+ });
195
+
196
+ // TODO: For some reason, we receive errors even after the `done` event is triggered.
197
+ es.addEventListener("error", (error: ErrorEvent | MessageEvent) => {
137
198
  // Differentiate between an error message from the server and an error event from the EventSource
138
- if (e instanceof MessageEvent) {
139
- const message = JSON.parse(e.data) as StreamMessageByType<'error'>;
199
+ if (error instanceof MessageEvent) {
200
+ const message = JSON.parse(
201
+ error.data
202
+ ) as StreamMessageByType<"error">;
140
203
  updateStatus((draft) => {
141
204
  draft.status = "error";
142
205
  draft.error = new Error(message.payload.message);
@@ -148,21 +211,21 @@ export const useAnimaCodegen = ({
148
211
  });
149
212
  } else {
150
213
  // It's an EventSource error (e.g. HTTP error)
151
- console.error('EventSource error:', e);
214
+ console.error("EventSource error:", error);
152
215
 
153
216
  updateStatus((draft) => {
154
217
  draft.status = "error";
155
- draft.error = new Error("HTTP error: " + e.message);
218
+ draft.error = new Error("HTTP error: " + error.message);
156
219
  });
157
220
 
158
221
  resolve({
159
222
  result: null,
160
- error: new Error("HTTP error: " + e.message),
223
+ error: new Error("HTTP error: " + error.message),
161
224
  });
162
225
  }
163
226
  });
164
227
 
165
- es.addEventListener('done', (event) => {
228
+ es.addEventListener("done", () => {
166
229
  updateStatus((draft) => {
167
230
  draft.status = "success";
168
231
  draft.result = result as AnimaSDKResult;
@@ -173,7 +236,45 @@ export const useAnimaCodegen = ({
173
236
  });
174
237
 
175
238
  try {
176
- const { result, error } = await promise;
239
+ const { result: r, error } = await promise;
240
+
241
+ const result = structuredClone(r);
242
+
243
+ // Ideally, we should download the assets within the `assets_uploaded` event handler, since it'll improve the performance.
244
+ // But for some reason, it doesn't work. So, we download the assets here.
245
+ if (
246
+ initialParams.assetsStorage?.strategy === "local" &&
247
+ result?.assets?.length
248
+ ) {
249
+ const { filePath } = getAssetsLocalStrategyParams(
250
+ initialParams.assetsStorage
251
+ );
252
+
253
+ const downloadAssetsPromises = result.assets.map(async (asset) => {
254
+ const response = await fetch(asset.url);
255
+ const buffer = await response.arrayBuffer();
256
+ return {
257
+ assetName: asset.name,
258
+ base64: arrayBufferToBase64(buffer),
259
+ };
260
+ });
261
+
262
+ const assets = await Promise.allSettled(downloadAssetsPromises);
263
+ for (const assetPromise of assets) {
264
+ const assetsList: Record<string, string> = {};
265
+ if (assetPromise.status === "fulfilled") {
266
+ const { assetName, base64 } = assetPromise.value;
267
+
268
+ assetsList[assetName] = base64;
269
+
270
+ const assetPath = filePath ? `${filePath}/${assetName}` : assetName;
271
+ result.files[assetPath] = {
272
+ content: base64,
273
+ isBinary: true,
274
+ };
275
+ }
276
+ }
277
+ }
177
278
 
178
279
  if (error) {
179
280
  return { result: null, error };
package/src/utils.ts ADDED
@@ -0,0 +1,10 @@
1
+ export const arrayBufferToBase64 = (buffer: ArrayBuffer) => {
2
+ var binary = "";
3
+ var bytes = new Uint8Array(buffer);
4
+ var len = bytes.byteLength;
5
+ for (var i = 0; i < len; i++) {
6
+ binary += String.fromCharCode(bytes[i]);
7
+ }
8
+
9
+ return btoa(binary);
10
+ };