From bdb49b556c8a4845db124776aca13d415a51fbc9 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Fri, 14 Jun 2024 11:06:10 +0200 Subject: [PATCH] WIP --- apps/preview/src/App.tsx | 2 +- packages/executors/.eslintrc | 57 ++++++++++++- packages/executors/src/client.tsx | 61 +++++++++++--- packages/executors/src/lib.tsx | 9 +++ packages/executors/src/server.tsx | 38 +++++++-- packages/executors/types.d.ts | 9 +-- packages/schema/src/index.ts | 31 ++++---- .../src/components/Organisms/PageDisplay.tsx | 1 + packages/ui/src/components/Routes/Frame.tsx | 79 ++++++++----------- .../ui/src/components/Routes/HomePage.tsx | 16 ++-- packages/ui/src/components/Routes/Preview.tsx | 1 + 11 files changed, 213 insertions(+), 91 deletions(-) diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index 981244d51..e61d4746e 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -24,7 +24,7 @@ const updates$ = webSocket({ function App() { const refresh = usePreviewRefresh(); useEffect(() => { - const sub = updates$.subscribe(refresh); + const sub = updates$.subscribe(() => refresh({})); return sub.unsubscribe; }, [refresh]); return ( diff --git a/packages/executors/.eslintrc b/packages/executors/.eslintrc index 0319dd9a1..2b8bf76f4 100644 --- a/packages/executors/.eslintrc +++ b/packages/executors/.eslintrc @@ -1,4 +1,57 @@ { - "extends": ["@amazeelabs/eslint-config"], - "root": true + "$schema": "https://json.schemastore.org/eslintrc.json", + "root": true, + "settings": { + "react": { + "version": "18" + } + }, + "env": { + "browser": true, + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:promise/recommended", + "plugin:react/recommended", + "prettier" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "plugins": [ + "@typescript-eslint", + "promise", + "simple-import-sort", + "import", + "no-only-tests", + "react", + "react-hooks" + ], + "rules": { + "no-unused-vars": ["off"], + "@typescript-eslint/no-unused-vars": ["error"], + "simple-import-sort/imports": "error", + "sort-imports": "off", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-duplicates": "error", + "no-only-tests/no-only-tests": "error", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "react/prop-types": ["off"], + "react/prefer-stateless-function": ["error"], + "react/react-in-jsx-scope": ["off"] + } } diff --git a/packages/executors/src/client.tsx b/packages/executors/src/client.tsx index e0bd810a8..b588f75f2 100644 --- a/packages/executors/src/client.tsx +++ b/packages/executors/src/client.tsx @@ -6,12 +6,14 @@ import React, { } from 'react'; import type { - ExecuteOperation as ExecuteOperationType, + withOperation as withOperationType, RegistryEntry, } from '../types'; import { + ErrorIndicator, ExecutorRegistryError, getCandidates, + LoadingIndicator, matchVariables, mergeExecutors, } from './lib'; @@ -54,28 +56,69 @@ export function OperationExecutor({ ); } -export const ExecuteOperation: typeof ExecuteOperationType = ({ +const ErrorClass = Error; + +const ExecutionResult = ({ id, variables, children, +}: { + id: string; + variables: any; + children: (result: any) => JSX.Element; }) => { - const [loading, setLoading] = React.useState(false); - const [result, setResult] = React.useState(null); + const [loading, setLoading] = React.useState(true); + const [error, setError] = React.useState(false); + const [result, setResult] = React.useState(undefined); const { executors } = useContext(ExecutorsContext); + useEffect(() => { - setLoading(true); const op = getCandidates(id, executors) .filter((entry) => matchVariables(entry.variables, variables)) .pop(); if (op) { if (typeof op.executor === 'function') { - setResult(op.executor(id, variables)); + (async () => { + try { + const result = await op.executor(id, variables); + setResult(result); + setLoading(false); + } catch (e: unknown) { + setLoading(false); + if (e instanceof ErrorClass) { + setError(e); + } else { + setError(new ErrorClass('Unknown error:' + e)); + } + } + })(); + } else { + setResult(op.executor); + setLoading(false); } - setResult(op.executor); } - setLoading(false); }, [setLoading, setResult, id, variables, executors]); - return children({ loading, result }); + if (loading) { + return ; + } + + if (error) { + return ; + } + + return children(result); +}; + +export const withOperation: typeof withOperationType = (id, Component) => { + return function WithExecutionResult({ operationVariables, ...props }: any) { + return ( + + {(result: any) => { + return ; + }} + + ); + }; }; diff --git a/packages/executors/src/lib.tsx b/packages/executors/src/lib.tsx index 3f2844e56..4be11220a 100644 --- a/packages/executors/src/lib.tsx +++ b/packages/executors/src/lib.tsx @@ -70,3 +70,12 @@ export class ExecutorRegistryError extends Error { this.name = 'ExecutorRegistryError'; } } + +export function LoadingIndicator() { + return
Loading...
; +} + +export function ErrorIndicator({ error }: { error: Error }) { + console.error(error); + return
{error.message}
; +} diff --git a/packages/executors/src/server.tsx b/packages/executors/src/server.tsx index c2c2797a4..9d29ca441 100644 --- a/packages/executors/src/server.tsx +++ b/packages/executors/src/server.tsx @@ -1,10 +1,11 @@ import { PropsWithChildren } from 'react'; import type { - ExecuteOperation as ExecuteOperationType, RegistryEntry, + withOperation as withOperationType, } from '../types'; import { + ErrorIndicator, ExecutorRegistryError, getCandidates, matchVariables, @@ -34,11 +35,38 @@ export function OperationExecutor({ return children; } -export const ExecuteOperation: typeof ExecuteOperationType = async ({ +export const ExecutionResult = (async ({ id, variables, children, -}) => { - const result = await useExecutor(id, variables)(variables); - return children({ loading: false, result }); +}: PropsWithChildren<{ + id: string; + variables?: Record; + children: (result: PropsWithChildren) => JSX.Element; +}>) => { + try { + const result = await useExecutor(id, variables)(variables); + return children(result); + } catch (e: unknown) { + if (e instanceof Error) { + return ; + } + return ; + } +}) as unknown as React.FC<{ + children: (result: any) => JSX.Element; + id: string; + variables: any; +}>; + +export const withOperation: typeof withOperationType = (id, Component) => { + return function WithExecutionResult({ operationVariables, ...props }: any) { + return ( + + {(result: any) => { + return ; + }} + + ); + }; }; diff --git a/packages/executors/types.d.ts b/packages/executors/types.d.ts index ffb892962..b38c19554 100644 --- a/packages/executors/types.d.ts +++ b/packages/executors/types.d.ts @@ -23,8 +23,7 @@ export function useExecutor( variables?: Record, ): (vars?: Record) => any | Promise; -export function ExecuteOperation(props: { - id: string; - variables?: Record; - children: (result: { loading: boolean; result: any }) => JSX.Element; -}): JSX.Element | Promise; +export function withOperation( + id: string, + Component: (props: any) => JSX.Element | null, +): (props: PropsWithChildren) => JSX.Element; diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index aa1975531..885cc197c 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -1,7 +1,7 @@ import { - ExecuteOperation as UntypedExecuteOperation, OperationExecutor as UntypedOperationExecutor, useExecutor as untypedUseExecutor, + withOperation as untypedWithOperation, } from '@amazeelabs/executors'; import { PropsWithChildren } from 'react'; @@ -48,17 +48,20 @@ export function useExecutor( return untypedUseExecutor(id, variables); } -export function ExecuteOperation(props: { - id: OperationId; - variables?: OperationVariables; - children: (result: { - loading: boolean; - result: OperationResult; - }) => JSX.Element; -}): - | OperationResult - | (( - variables?: OperationVariables, - ) => Promise>) { - return UntypedExecuteOperation(props); +export function withOperation< + TOperation extends AnyOperationId, + TComponentProps extends PropsWithChildren, +>( + id: TOperation, + Component: ( + props: TComponentProps & { + operationResult: OperationResult; + }, + ) => JSX.Element | null, +): ( + props: TComponentProps & { + operationVariables: OperationVariables; + }, +) => JSX.Element | null { + return untypedWithOperation(id, Component); } diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 438d43893..c5999faf9 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -1,3 +1,4 @@ +'use client'; import { PageFragment } from '@custom/schema'; import React from 'react'; diff --git a/packages/ui/src/components/Routes/Frame.tsx b/packages/ui/src/components/Routes/Frame.tsx index 67e19a404..308199b83 100644 --- a/packages/ui/src/components/Routes/Frame.tsx +++ b/packages/ui/src/components/Routes/Frame.tsx @@ -1,12 +1,6 @@ import { IntlProvider } from '@amazeelabs/react-intl'; -import { - ExecuteOperation, - FrameQuery, - Locale, - useLocale, - useLocation, -} from '@custom/schema'; -import React, { PropsWithChildren } from 'react'; +import { FrameQuery, Locale, useLocale, withOperation } from '@custom/schema'; +import React from 'react'; import translationSources from '../../../build/translatables.json'; import { TranslationsProvider } from '../../utils/translations'; @@ -35,41 +29,34 @@ function translationsMap(translatables: FrameQuery['stringTranslations']) { ); } -export function Frame(props: PropsWithChildren<{}>) { - const locale = useLocale(); - return ( - - {({ result }) => { - const rawTranslations = result.stringTranslations || []; - const translations = { - ...translationsMap( - rawTranslations?.filter(filterByLocale('en')) || [], - ), - ...translationsMap( - rawTranslations?.filter(filterByLocale(locale)) || [], - ), - }; - const messages = Object.fromEntries( - Object.keys(translationSources).map((key) => [ - key, - translations[ - translationSources[key as keyof typeof translationSources] - .defaultMessage - ] || - translationSources[key as keyof typeof translationSources] - .defaultMessage, - ]), - ); - return ( - - -
- {props.children} -