Skip to content

Commit

Permalink
Refactor useExecuteCode to leverage atomCallback (#814)
Browse files Browse the repository at this point in the history
Co-authored-by: anserwaseem <[email protected]>
  • Loading branch information
evshi and anserwaseem authored Sep 24, 2024
1 parent f466659 commit b42978c
Show file tree
Hide file tree
Showing 17 changed files with 687 additions and 202 deletions.
7 changes: 7 additions & 0 deletions .changeset/chatty-icons-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@ensembleui/react-framework": patch
"@ensembleui/react-kitchen-sink": patch
"@ensembleui/react-runtime": patch
---

Optimize dependencies for useExecuteCode
8 changes: 5 additions & 3 deletions apps/kitchen-sink/src/ensemble/screens/forms.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ View:
ensemble.storage.set('inputVal', 'sagar');
ensemble.storage.set('mockApiStatusCode', 200);
ensemble.storage.set('mockApiReasonPhrase', 'Success!');
ensemble.storage.set('mockApiName', 'testMockResponse');
ensemble.storage.set('dummyData', [
{ value: "val 1", label: "lab 1" },
{ value: "val 2", label: "lab 2" },
Expand Down Expand Up @@ -365,6 +366,7 @@ View:
initialValue: false
onChange:
executeCode: |
debugger;
app.setUseMockResponse(!app.useMockResponse);
- Checkbox:
id: throwMockApiErrorCheckbox
Expand All @@ -386,7 +388,7 @@ View:
label: Call API!
onTap:
invokeAPI:
name: testMockResponse
name: ${ensemble.storage.get('mockApiName')}
onResponse:
executeCode: console.log("Mock API called ", response);
- Button:
Expand All @@ -395,7 +397,7 @@ View:
label: Call API from executeCode!
onTap:
executeCode: |
ensemble.invokeAPI("testMockResponse");
ensemble.invokeAPI(ensemble.storage.get('mockApiName'));
- Spacer:
styles:
size: 20
Expand Down Expand Up @@ -455,7 +457,7 @@ API:
uri: https://dummyjson.com/users/1
mockResponse:
statusCode: "${ensemble.storage.get('mockApiStatusCode')}"
reasonPhrase: "${ensemble.storage.get('mockApiReasonPhrase')}"
reasonPhrase: "${ensemble.env.randomId}"
body:
- id: 0
name: Harry Potter
Expand Down
21 changes: 13 additions & 8 deletions apps/kitchen-sink/src/ensemble/widgets/StyledText.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ Widget:
console.log('StyledText Widget loaded');
console.log(inputText);
body:
Text:
text: Styled Text
styles:
fontSize: 24px
fontWeight: ${typography.fontWeight['bold']}
color: blue
backgroundColor: ${colors.dark['300']}
padding: 8px
Column:
onTap:
executeCode: |
console.log(inputText)
children:
- Text:
text: Styled Text
styles:
fontSize: 24px
fontWeight: ${typography.fontWeight['bold']}
color: blue
backgroundColor: ${colors.dark['300']}
padding: 8px
154 changes: 154 additions & 0 deletions packages/framework/src/api/data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { has, set } from "lodash-es";
import type { Setter } from "jotai";
import { DataFetcher, type WebSocketConnection, type Response } from "../data";
import { error } from "../shared";
import type {
EnsembleActionHookResult,
EnsembleMockResponse,
EnsembleSocketModel,
} from "../shared";
import { screenDataAtom, type ScreenContextDefinition } from "../state";
import { isUsingMockResponse } from "../appConfig";
import { mockResponse } from "../evaluate/mock";

export const invokeAPI = async (
apiName: string,
screenContext: ScreenContextDefinition,
apiInputs?: { [key: string]: unknown },
context?: { [key: string]: unknown },
evaluatedMockResponse?: string | EnsembleMockResponse,
setter?: Setter,
): Promise<Response | undefined> => {
const api = screenContext.model?.apis?.find(
(model) => model.name === apiName,
);

if (!api) {
error(`Unable to find API with name ${apiName}`);
return;
}

const update = {};
if (setter) {
// Now, because the API exists, set its state to loading
set(update, api.name, {
isLoading: true,
isError: false,
isSuccess: false,
});
setter(screenDataAtom, update);
}

// If mock resposne does not exist, fetch the data directly from the API
const useMockResponse =
has(api, "mockResponse") && isUsingMockResponse(screenContext.app?.id);
const res = await DataFetcher.fetch(
api,
{ ...apiInputs, ...context },
{
mockResponse: mockResponse(
evaluatedMockResponse ?? api.mockResponse,
useMockResponse,
),
useMockResponse,
},
);

if (setter) {
set(update, api.name, res);
setter(screenDataAtom, { ...update });
}
return res;
};

export const handleConnectSocket = (
socketName: string,
screenContext: ScreenContextDefinition,
onOpen?: EnsembleActionHookResult,
onMessage?: EnsembleActionHookResult,
onClose?: EnsembleActionHookResult,
setter?: Setter,
): WebSocketConnection | undefined => {
const socket = findSocket(socketName, screenContext);
if (!socket) {
error(`Unable to find socket ${socketName}`);
return;
}

// check the socket is already connected
const prevSocketConnection = screenContext.data[socket.name] as
| WebSocketConnection
| undefined;

if (prevSocketConnection?.isConnected) {
return prevSocketConnection;
}

const ws = new WebSocket(socket.uri);

if (onOpen?.callback) {
ws.onopen = (): unknown => onOpen.callback();
}

if (onMessage?.callback) {
ws.onmessage = (e: MessageEvent): unknown =>
onMessage.callback({ data: e.data as unknown });
}

if (onClose?.callback) {
ws.onclose = (): unknown => onClose.callback();
}

if (setter) {
const update = {};
set(update, socket.name, { socket: ws, isConnected: true });
setter(screenDataAtom, update);
}

return { socket: ws, isConnected: true };
};

export const handleMessageSocket = (
socketName: string,
message: { [key: string]: unknown },
screenContext: ScreenContextDefinition,
): void => {
const socket = findSocket(socketName, screenContext);
if (!socket) {
error(`Unable to find socket ${socketName}`);
return;
}

const socketInstance = screenContext.data[socket.name] as WebSocketConnection;
if (socketInstance.isConnected) {
socketInstance.socket?.send(JSON.stringify(message));
}
};

export const handleDisconnectSocket = (
socketName: string,
screenContext: ScreenContextDefinition,
setter?: Setter,
): void => {
const socket = findSocket(socketName, screenContext);
if (!socket) {
error(`Unable to find socket ${socketName}`);
return;
}

const socketInstance = screenContext.data[socket.name] as WebSocketConnection;
if (socketInstance.isConnected) {
socketInstance.socket?.close();
if (setter) {
const update = {};
set(update, socket.name, { isConnected: false });
setter(screenDataAtom, update);
}
}
};

const findSocket = (
socketName: string,
screenContext: ScreenContextDefinition,
): EnsembleSocketModel | undefined =>
screenContext.model?.sockets?.find((model) => model.name === socketName);
3 changes: 3 additions & 0 deletions packages/framework/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./data";
export * from "./navigate";
export * from "./modal";
87 changes: 87 additions & 0 deletions packages/framework/src/api/modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { cloneDeep, isObject } from "lodash-es";
import type { ReactNode } from "react";
import { unwrapWidget } from "../parser";
import type {
ShowDialogAction,
EnsembleWidget,
ShowDialogOptions,
} from "../shared";

export interface ModalProps {
title?: string | React.ReactNode;
maskClosable?: boolean;
mask?: boolean;
hideCloseIcon?: boolean;
hideFullScreenIcon?: boolean;
onClose?: () => void;
position?: "top" | "right" | "bottom" | "left" | "center";
height?: string;
width?: string | number;
margin?: string;
padding?: string;
backgroundColor?: string;
horizontalOffset?: number;
verticalOffset?: number;
showShadow?: boolean;
}

export interface ModalContext {
openModal: (
content: React.ReactNode,
options: ModalProps,
isDialog?: boolean,
context?: { [key: string]: unknown },
) => void;
closeAllModals: () => void;
navigateBack: () => void;
}

export interface ShowDialogApiProps {
action?: ShowDialogAction;
openModal?: (...args: any[]) => void;
render?: (widgets: EnsembleWidget[]) => ReactNode[];
}

export const showDialog = (props?: ShowDialogApiProps): void => {
const { action, openModal, render } = props ?? {};
if (!action || !openModal || (!action.widget && !action.body)) {
return;
}

const widget = action.widget ?? action.body;

const content = widget?.name
? (cloneDeep(widget) as unknown as EnsembleWidget)
: unwrapWidget(cloneDeep(widget!));

openModal(
render?.([content]),
getShowDialogOptions(action.options),
true,
screen || undefined,
);
};

export const getShowDialogOptions = (
options?: ShowDialogOptions,
onClose?: () => void,
): ShowDialogOptions => {
const noneStyleOption = {
backgroundColor: "transparent",
showShadow: false,
};

const dialogOptions = {
maskClosable: true,
hideCloseIcon: true,
hideFullScreenIcon: true,
verticalOffset: options?.verticalOffset,
horizontalOffset: options?.horizontalOffset,
padding: "12px",
onClose,
...(options?.style === "none" ? noneStyleOption : {}),
...(isObject(options) ? options : {}),
};

return dialogOptions;
};
Loading

0 comments on commit b42978c

Please sign in to comment.