Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor useExecuteCode to leverage atomCallback #814

Merged
merged 9 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading