Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
pmelab committed Jun 14, 2024
1 parent dff7735 commit bdb49b5
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 91 deletions.
2 changes: 1 addition & 1 deletion apps/preview/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
57 changes: 55 additions & 2 deletions packages/executors/.eslintrc
Original file line number Diff line number Diff line change
@@ -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"]
}
}
61 changes: 52 additions & 9 deletions packages/executors/src/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<any>(null);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<any>(false);
const [result, setResult] = React.useState<any>(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 <LoadingIndicator />;
}

if (error) {
return <ErrorIndicator error={error} />;
}

return children(result);
};

export const withOperation: typeof withOperationType = (id, Component) => {
return function WithExecutionResult({ operationVariables, ...props }: any) {
return (
<ExecutionResult id={id} variables={operationVariables}>
{(result: any) => {
return <Component operationResult={result} {...props} />;
}}
</ExecutionResult>
);
};
};
9 changes: 9 additions & 0 deletions packages/executors/src/lib.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,12 @@ export class ExecutorRegistryError extends Error {
this.name = 'ExecutorRegistryError';
}
}

export function LoadingIndicator() {
return <div className="operation-loading">Loading...</div>;
}

export function ErrorIndicator({ error }: { error: Error }) {
console.error(error);
return <div className="operation-error">{error.message}</div>;
}
38 changes: 33 additions & 5 deletions packages/executors/src/server.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { PropsWithChildren } from 'react';

import type {
ExecuteOperation as ExecuteOperationType,
RegistryEntry,
withOperation as withOperationType,
} from '../types';
import {
ErrorIndicator,
ExecutorRegistryError,
getCandidates,
matchVariables,
Expand Down Expand Up @@ -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<string, any>;
children: (result: PropsWithChildren<any>) => JSX.Element;
}>) => {
try {
const result = await useExecutor(id, variables)(variables);
return children(result);
} catch (e: unknown) {
if (e instanceof Error) {
return <ErrorIndicator error={e} />;
}
return <ErrorIndicator error={new Error('Unknown error:' + e)} />;
}
}) 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 (
<ExecutionResult id={id} variables={operationVariables}>
{(result: any) => {
return <Component operationResult={result} {...props} />;
}}
</ExecutionResult>
);
};
};
9 changes: 4 additions & 5 deletions packages/executors/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ export function useExecutor(
variables?: Record<string, any>,
): (vars?: Record<string, any>) => any | Promise<any>;

export function ExecuteOperation(props: {
id: string;
variables?: Record<string, any>;
children: (result: { loading: boolean; result: any }) => JSX.Element;
}): JSX.Element | Promise<JSX.Element>;
export function withOperation(
id: string,
Component: (props: any) => JSX.Element | null,
): (props: PropsWithChildren) => JSX.Element;
31 changes: 17 additions & 14 deletions packages/schema/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
ExecuteOperation as UntypedExecuteOperation,
OperationExecutor as UntypedOperationExecutor,
useExecutor as untypedUseExecutor,
withOperation as untypedWithOperation,
} from '@amazeelabs/executors';
import { PropsWithChildren } from 'react';

Expand Down Expand Up @@ -48,17 +48,20 @@ export function useExecutor<OperationId extends AnyOperationId>(
return untypedUseExecutor(id, variables);
}

export function ExecuteOperation<OperationId extends AnyOperationId>(props: {
id: OperationId;
variables?: OperationVariables<OperationId>;
children: (result: {
loading: boolean;
result: OperationResult<OperationId>;
}) => JSX.Element;
}):
| OperationResult<OperationId>
| ((
variables?: OperationVariables<OperationId>,
) => Promise<OperationResult<OperationId>>) {
return UntypedExecuteOperation(props);
export function withOperation<
TOperation extends AnyOperationId,
TComponentProps extends PropsWithChildren,
>(
id: TOperation,
Component: (
props: TComponentProps & {
operationResult: OperationResult<TOperation>;
},
) => JSX.Element | null,
): (
props: TComponentProps & {
operationVariables: OperationVariables<TOperation>;
},
) => JSX.Element | null {
return untypedWithOperation(id, Component);
}
1 change: 1 addition & 0 deletions packages/ui/src/components/Organisms/PageDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client';
import { PageFragment } from '@custom/schema';
import React from 'react';

Expand Down
79 changes: 33 additions & 46 deletions packages/ui/src/components/Routes/Frame.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -35,41 +29,34 @@ function translationsMap(translatables: FrameQuery['stringTranslations']) {
);
}

export function Frame(props: PropsWithChildren<{}>) {
const locale = useLocale();
return (
<ExecuteOperation id={FrameQuery}>
{({ 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 (
<IntlProvider locale={locale} messages={messages}>
<TranslationsProvider>
<Header />
<PageTransitionWrapper>{props.children}</PageTransitionWrapper>
<Footer />
</TranslationsProvider>
</IntlProvider>
);
}}
</ExecuteOperation>
);
}
export const Frame = withOperation(
FrameQuery,
({ operationResult, children }) => {
const locale = useLocale();
const rawTranslations = operationResult.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 (
<IntlProvider locale={locale} messages={messages}>
<TranslationsProvider>
<Header />
<PageTransitionWrapper>{children}</PageTransitionWrapper>
<Footer />
</TranslationsProvider>
</IntlProvider>
);
},
);
Loading

0 comments on commit bdb49b5

Please sign in to comment.