Skip to content

Commit 80cb878

Browse files
committed
Refactor command system
1 parent 4ef9cea commit 80cb878

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+964
-790
lines changed

.changeset/chubby-insects-shake.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pulse-editor/shared-utils": patch
3+
"@pulse-editor/react-api": patch
4+
---
5+
6+
Update types

.changeset/free-ears-swim.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pulse-editor/shared-utils": patch
3+
"@pulse-editor/react-api": patch
4+
---
5+
6+
Add lib version in AppConfig

.changeset/polite-rules-switch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pulse-editor/shared-utils": patch
3+
"@pulse-editor/react-api": patch
4+
---
5+
6+
Update utils and types

.changeset/pre.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"beige-pandas-rhyme",
1818
"calm-rivers-march",
1919
"chatty-trains-beam",
20+
"chubby-insects-shake",
2021
"clean-mangos-swim",
2122
"cold-shrimps-give",
2223
"cruel-waves-double",
@@ -27,6 +28,7 @@
2728
"early-pumas-listen",
2829
"few-wasps-beam",
2930
"fluffy-poems-cover",
31+
"free-ears-swim",
3032
"fruity-goats-look",
3133
"full-beans-stop",
3234
"hot-symbols-fry",
@@ -36,6 +38,7 @@
3638
"mighty-ghosts-crash",
3739
"petite-memes-fix",
3840
"polite-lines-dance",
41+
"polite-rules-switch",
3942
"polite-worms-fix",
4043
"real-knives-rest",
4144
"rude-ducks-design",

npm-packages/react-api/CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# @pulse-editor/react-api
22

3+
## 0.1.1-alpha.39
4+
5+
### Patch Changes
6+
7+
- Add lib version in AppConfig
8+
- Updated dependencies
9+
- @pulse-editor/shared-utils@0.1.1-alpha.39
10+
11+
## 0.1.1-alpha.38
12+
13+
### Patch Changes
14+
15+
- Update utils and types
16+
- Updated dependencies
17+
- @pulse-editor/shared-utils@0.1.1-alpha.38
18+
19+
## 0.1.1-alpha.37
20+
21+
### Patch Changes
22+
23+
- Update types
24+
- Updated dependencies
25+
- @pulse-editor/shared-utils@0.1.1-alpha.37
26+
327
## 0.1.1-alpha.36
428

529
### Patch Changes

npm-packages/react-api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pulse-editor/react-api",
3-
"version": "0.1.1-alpha.36",
3+
"version": "0.1.1-alpha.39",
44
"main": "dist/main.js",
55
"files": [
66
"dist"
@@ -38,7 +38,7 @@
3838
"typescript-eslint": "^8.30.1"
3939
},
4040
"peerDependencies": {
41-
"@pulse-editor/shared-utils": "0.1.1-alpha.36",
41+
"@pulse-editor/shared-utils": "0.1.1-alpha.39",
4242
"react": "^19.0.0",
4343
"react-dom": "^19.0.0"
4444
}

npm-packages/react-api/src/hooks/editor/use-command.ts

Lines changed: 0 additions & 115 deletions
This file was deleted.

npm-packages/react-api/src/hooks/editor/use-loading.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function useLoading() {
1313

1414
useEffect(() => {
1515
if (isReady) {
16-
imc?.sendMessage(IMCMessageTypeEnum.EditorLoadingExt, {
16+
imc?.sendMessage(IMCMessageTypeEnum.EditorLoadingApp, {
1717
isLoading,
1818
});
1919
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import {
2+
Action,
3+
IMCMessage,
4+
IMCMessageTypeEnum,
5+
ReceiverHandler,
6+
TypedVariable,
7+
} from "@pulse-editor/shared-utils";
8+
import { useEffect, useRef, useState } from "react";
9+
import useIMC from "../../lib/use-imc";
10+
11+
/**
12+
* Register an app action to listen to IMC messages from the core,
13+
* and pass to the action to handle.
14+
*
15+
* @param name Name of the command.
16+
* @param description Description of the command.
17+
* @param parameters Parameters of the command.
18+
* @param returns Return values of the command.
19+
* @param callbackHandler Callback handler function to handle the command.
20+
* @param isExtReady Whether the extension is ready to receive commands.
21+
*
22+
*/
23+
export default function useRegisterAction(
24+
name: string,
25+
description: string,
26+
parameters: Record<string, TypedVariable>,
27+
returns: Record<string, TypedVariable>,
28+
callbackHandler?: (args: any) => Promise<string | void>,
29+
isExtReady: boolean = true
30+
) {
31+
const { isReady, imc } = useIMC(getReceiverHandlerMap());
32+
33+
// Queue to hold commands until extension is ready
34+
const commandQueue = useRef<{ args: any; resolve: (v: any) => void }[]>([]);
35+
36+
const [action, setAction] = useState<Action>({
37+
name,
38+
description,
39+
parameters,
40+
returns,
41+
handler: callbackHandler,
42+
});
43+
44+
// Flush queued commands when isExtReady becomes true
45+
useEffect(() => {
46+
if (isExtReady && commandQueue.current.length > 0) {
47+
const pending = [...commandQueue.current];
48+
commandQueue.current = [];
49+
pending.forEach(async ({ args, resolve }) => {
50+
const res = await executeAction(args);
51+
resolve(res);
52+
});
53+
}
54+
}, [isExtReady]);
55+
56+
useEffect(() => {
57+
async function updateAction() {
58+
// Register or update action.
59+
// This will only pass signature info to the editor.
60+
// The actual handler is stored in this hook,
61+
// so the execution happens inside the extension app.
62+
await imc?.sendMessage(IMCMessageTypeEnum.EditorRegisterAction, {
63+
name: action.name,
64+
description: action.description,
65+
parameters: action.parameters,
66+
returns: action.returns,
67+
});
68+
69+
// Update receiver
70+
imc?.updateReceiverHandlerMap(getReceiverHandlerMap());
71+
}
72+
73+
if (isExtReady) {
74+
updateAction();
75+
}
76+
}, [action, imc, isExtReady]);
77+
78+
useEffect(() => {
79+
setAction((prev) => ({ ...prev, name, description, parameters, returns }));
80+
}, [callbackHandler]);
81+
82+
async function executeAction(args: any) {
83+
if (!action.handler) return;
84+
85+
const res = await action.handler(args);
86+
return res;
87+
}
88+
89+
function getReceiverHandlerMap() {
90+
const receiverHandlerMap = new Map<IMCMessageTypeEnum, ReceiverHandler>([
91+
[
92+
IMCMessageTypeEnum.EditorRunAppAction,
93+
async (_senderWindow: Window, message: IMCMessage) => {
94+
const { name: requestedName, args }: { name: string; args: any } =
95+
message.payload;
96+
97+
if (name === requestedName) {
98+
// Validate parameters
99+
const actionParams = parameters;
100+
if (Object.keys(args).length !== Object.keys(actionParams).length) {
101+
throw new Error(
102+
`Invalid number of parameters: expected ${
103+
Object.keys(actionParams).length
104+
}, got ${Object.keys(args).length}`
105+
);
106+
}
107+
108+
for (const [key, value] of Object.entries(args)) {
109+
if (parameters[key] === undefined) {
110+
throw new Error(`Invalid parameter: ${key}`);
111+
}
112+
if (typeof value !== parameters[key].type) {
113+
throw new Error(
114+
`Invalid type for parameter ${key}: expected ${
115+
parameters[key].type
116+
}, got ${typeof value}. Value received: ${value}`
117+
);
118+
}
119+
}
120+
121+
// If extension is ready, execute immediately
122+
if (isExtReady) {
123+
return await executeAction(args);
124+
}
125+
126+
// Otherwise, queue the command and return when executed
127+
return new Promise((resolve) => {
128+
commandQueue.current.push({ args, resolve });
129+
});
130+
}
131+
},
132+
],
133+
]);
134+
return receiverHandlerMap;
135+
}
136+
137+
return {
138+
isReady,
139+
};
140+
}

npm-packages/react-api/src/lib/use-imc.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export default function useIMC(handlerMap: ReceiverHandlerMap) {
3535
await newImc.initOtherWindow(targetWindow);
3636
setImc(newImc);
3737

38-
newImc.sendMessage(IMCMessageTypeEnum.ExtReady).then(() => {
38+
newImc.sendMessage(IMCMessageTypeEnum.AppReady).then(() => {
3939
setIsReady(true);
4040
});
4141
}

0 commit comments

Comments
 (0)