Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 1 addition & 5 deletions packages/api/src/decorator.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
// Decorator general

export { default as DecoratorComposer } from './decorator/DecoratorComposer';
export {
type DecoratorMiddleware,
type DecoratorMiddlewareInit,
type DecoratorMiddlewareTypes
} from './decorator/types';
export { activityBorderMiddleware, activityGroupingMiddleware, type DecoratorMiddleware } from './decorator/types';

// ActivityBorderDecorator

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { EmptyObject } from 'type-fest';
import templateMiddleware from '../../private/templateMiddleware';
import templateMiddleware, {
type InferInit,
type InferMiddleware,
type InferProps,
type InferRequest
} from '../../private/templateMiddleware';
import { type activityBorderDecoratorTypeName } from '../types';

type Request = Readonly<{
Expand All @@ -26,19 +31,21 @@ type Request = Readonly<{

type Props = EmptyObject;

const template = templateMiddleware<typeof activityBorderDecoratorTypeName, Request, Props>(
'ActivityBorderDecoratorMiddleware'
);

const {
initMiddleware: initActivityBorderDecoratorMiddleware,
Provider: ActivityBorderDecoratorMiddlewareProvider,
Proxy: ActivityBorderDecoratorMiddlewareProxy,
// False positive, `types` is used for its typing.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
types
} = templateMiddleware<typeof activityBorderDecoratorTypeName, Request, Props>('ActivityBorderDecoratorMiddleware');
'~types': _types
Comment thread
compulim marked this conversation as resolved.
Outdated
} = template;

type ActivityBorderDecoratorMiddleware = typeof types.middleware;
type ActivityBorderDecoratorMiddlewareInit = typeof types.init;
type ActivityBorderDecoratorMiddlewareProps = typeof types.props;
type ActivityBorderDecoratorMiddlewareRequest = typeof types.request;
type ActivityBorderDecoratorMiddleware = InferMiddleware<typeof template>;
type ActivityBorderDecoratorMiddlewareInit = InferInit<typeof template>;
type ActivityBorderDecoratorMiddlewareProps = InferProps<typeof template>;
type ActivityBorderDecoratorMiddlewareRequest = InferRequest<typeof template>;

export {
ActivityBorderDecoratorMiddlewareProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { type WebChatActivity } from 'botframework-webchat-core';
import templateMiddleware from '../../private/templateMiddleware';
import templateMiddleware, {
type InferInit,
type InferMiddleware,
type InferProps,
type InferRequest
} from '../../private/templateMiddleware';
import { type activityGroupingDecoratorTypeName } from '../types';

type Request = Readonly<{
Expand All @@ -13,19 +18,21 @@ type Props = Readonly<{
activities: readonly WebChatActivity[];
}>;

const template = templateMiddleware<typeof activityGroupingDecoratorTypeName, Request, Props>(
'ActivityGroupingDecoratorMiddleware'
);

const {
initMiddleware: initActivityGroupingDecoratorMiddleware,
Provider: ActivityGroupingDecoratorMiddlewareProvider,
Proxy: ActivityGroupingDecoratorMiddlewareProxy,
// False positive, `types` is used for its typing.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
types
} = templateMiddleware<typeof activityGroupingDecoratorTypeName, Request, Props>('ActivityGroupingDecoratorMiddleware');
'~types': _types
} = template;

type ActivityGroupingDecoratorMiddleware = typeof types.middleware;
type ActivityGroupingDecoratorMiddlewareInit = typeof types.init;
type ActivityGroupingDecoratorMiddlewareProps = typeof types.props;
type ActivityGroupingDecoratorMiddlewareRequest = typeof types.request;
type ActivityGroupingDecoratorMiddleware = InferMiddleware<typeof template>;
type ActivityGroupingDecoratorMiddlewareInit = InferInit<typeof template>;
type ActivityGroupingDecoratorMiddlewareProps = InferProps<typeof template>;
type ActivityGroupingDecoratorMiddlewareRequest = InferRequest<typeof template>;

export {
ActivityGroupingDecoratorMiddlewareProvider,
Expand Down
26 changes: 7 additions & 19 deletions packages/api/src/decorator/private/templateMiddleware.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { render } from '@testing-library/react';
import React, { Fragment, type ReactNode } from 'react';

import templateMiddleware from './templateMiddleware';
import templateMiddleware, { type InferMiddleware } from './templateMiddleware';

type ButtonProps = Readonly<{ children?: ReactNode | undefined }>;
type LinkProps = Readonly<{ children?: ReactNode | undefined; href: string }>;
Expand All @@ -20,27 +20,15 @@ const InternalLinkImpl = ({ children, href }: LinkProps) => <a href={href}>{chil

// User story for using templateMiddleware as a building block for uber middleware.
test('an uber middleware', () => {
const {
initMiddleware: initButtonMiddleware,
Provider: ButtonProvider,
Proxy: Button,
// False positive, `types` is used for its typing.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
types: buttonTypes
} = templateMiddleware<'button', void, ButtonProps>('Button');
const buttonTemplate = templateMiddleware<'button', void, ButtonProps>('Button');
const { initMiddleware: initButtonMiddleware, Provider: ButtonProvider, Proxy: Button } = buttonTemplate;

type ButtonMiddleware = typeof buttonTypes.middleware;
type ButtonMiddleware = InferMiddleware<typeof buttonTemplate>;

const {
initMiddleware: initLinkMiddleware,
Provider: LinkProvider,
Proxy: Link,
// False positive, `types` is used for its typing.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
types: linkTypes
} = templateMiddleware<'link', { external: boolean }, LinkProps>('Link');
const linkTemplate = templateMiddleware<'link', { external: boolean }, LinkProps>('Link');
const { initMiddleware: initLinkMiddleware, Provider: LinkProvider, Proxy: Link } = linkTemplate;

type LinkMiddleware = typeof linkTypes.middleware;
type LinkMiddleware = InferMiddleware<typeof linkTemplate>;

const buttonMiddleware: ButtonMiddleware[] = [init => init === 'button' && (() => () => ButtonImpl)];
const linkMiddleware: LinkMiddleware[] = [
Expand Down
20 changes: 13 additions & 7 deletions packages/api/src/decorator/private/templateMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@ import { createChainOfResponsibility, type ComponentMiddleware } from 'react-cha
import { type EmptyObject } from 'type-fest';
import { any, array, function_, pipe, safeParse, type InferOutput } from 'valibot';

export type MiddlewareWithInit<M extends ComponentMiddleware<any, any, any>, I> = (init: I) => ReturnType<M> | false;
type MiddlewareWithInit<M extends ComponentMiddleware<any, any, any>, I> = (init: I) => ReturnType<M> | false;

const EMPTY_ARRAY = Object.freeze([]);

// Following @types/react to use {} for props.
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export default function templateMiddleware<Init extends string, Request = any, Props extends {} = EmptyObject>(
name: string
) {
function templateMiddleware<Init extends string, Request = any, Props extends {} = EmptyObject>(name: string) {
type Middleware = ComponentMiddleware<Request, Props>;

const middlewareSchema = array(pipe(any(), function_()));
Expand All @@ -22,8 +20,8 @@ export default function templateMiddleware<Init extends string, Request = any, P
const warnInvalid = warnOnce(`"${name}" prop is invalid`);

const initMiddleware = (
middleware: readonly MiddlewareWithInit<ComponentMiddleware<unknown, unknown>, Init>[],
init: Init
middleware: readonly MiddlewareWithInit<ComponentMiddleware<unknown, unknown>, unknown>[],
init: unknown
): readonly Middleware[] => {
if (middleware) {
if (isMiddleware(middleware)) {
Expand All @@ -50,11 +48,19 @@ export default function templateMiddleware<Init extends string, Request = any, P
initMiddleware,
Provider,
Proxy,
types: {
'~types': {
init: undefined as Init,
middleware: undefined as Middleware,
props: undefined as Props,
request: undefined as Request
}
};
Comment thread
compulim marked this conversation as resolved.
Outdated
}

type InferMiddleware<T extends { '~types': { middleware } }> = T['~types']['middleware'];
type InferInit<T extends { '~types': { init } }> = T['~types']['init'];
type InferProps<T extends { '~types': { props } }> = T['~types']['props'];
type InferRequest<T extends { '~types': { request } }> = T['~types']['request'];

export default templateMiddleware;
export { type InferInit, type InferMiddleware, type InferProps, type InferRequest, type MiddlewareWithInit };
23 changes: 14 additions & 9 deletions packages/api/src/decorator/types.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { type ActivityBorderDecoratorMiddleware } from './ActivityBorder/private/ActivityBorderDecoratorMiddleware';
import { type activityBorderDecoratorTypeName } from './ActivityBorder/types';
import { activityBorderDecoratorTypeName } from './ActivityBorder/types';
import { type ActivityGroupingDecoratorMiddleware } from './ActivityGrouping/private/ActivityGroupingDecoratorMiddleware';
import { type activityGroupingDecoratorTypeName } from './ActivityGrouping/types';
import { activityGroupingDecoratorTypeName } from './ActivityGrouping/types';

export type DecoratorMiddlewareTypes = {
[activityBorderDecoratorTypeName]: ReturnType<ActivityBorderDecoratorMiddleware>;
[activityGroupingDecoratorTypeName]: ReturnType<ActivityGroupingDecoratorMiddleware>;
};
export type DecoratorMiddleware =
| ((init: typeof activityBorderDecoratorTypeName) => ReturnType<ActivityBorderDecoratorMiddleware> | false)
| ((init: typeof activityGroupingDecoratorTypeName) => ReturnType<ActivityGroupingDecoratorMiddleware> | false);

export type DecoratorMiddlewareInit = keyof DecoratorMiddlewareTypes;
export function activityBorderMiddleware(
enhancer: ReturnType<ActivityBorderDecoratorMiddleware>
): (init: string) => ReturnType<ActivityBorderDecoratorMiddleware> | false {
return init => init === activityBorderDecoratorTypeName && enhancer;
}

export interface DecoratorMiddleware {
(init: keyof DecoratorMiddlewareTypes): DecoratorMiddlewareTypes[typeof init] | false;
export function activityGroupingMiddleware(
enhancer: ReturnType<ActivityGroupingDecoratorMiddleware>
): (init: string) => ReturnType<ActivityGroupingDecoratorMiddleware> | false {
return init => init === activityGroupingDecoratorTypeName && enhancer;
}
25 changes: 16 additions & 9 deletions packages/api/src/hooks/internal/SendBoxMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import templateMiddleware from './private/templateMiddleware';
import templateMiddleware, {
type InferInit,
type InferMiddleware,
type InferProps,
type InferRequest
} from './private/templateMiddleware';

const template = templateMiddleware<undefined, void, { className?: string | undefined }>('sendBoxMiddleware');

const {
initMiddleware: initSendBoxMiddleware,
Provider: SendBoxMiddlewareProvider,
Proxy: SendBoxMiddlewareProxy,
// False positive, `types` is used for its typing.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
types
} = templateMiddleware<undefined, void, { className?: string | undefined }>('sendBoxMiddleware');
'~types': _types
} = template;

type SendBoxMiddleware = typeof types.middleware;
type SendBoxMiddlewareProps = typeof types.props;
type SendBoxMiddlewareRequest = typeof types.request;
type SendBoxMiddleware = InferMiddleware<typeof template>;
type SendBoxMiddlewareInit = InferInit<typeof template>;
type SendBoxMiddlewareProps = InferProps<typeof template>;
type SendBoxMiddlewareRequest = InferRequest<typeof template>;

export {
initSendBoxMiddleware,
SendBoxMiddlewareProvider,
SendBoxMiddlewareProxy,
initSendBoxMiddleware,
type SendBoxMiddleware,
type SendBoxMiddlewareInit,
type SendBoxMiddlewareProps,
type SendBoxMiddlewareRequest
};
25 changes: 16 additions & 9 deletions packages/api/src/hooks/internal/SendBoxToolbarMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
import templateMiddleware from './private/templateMiddleware';
import templateMiddleware, {
type InferInit,
type InferMiddleware,
type InferProps,
type InferRequest
} from './private/templateMiddleware';

const template = templateMiddleware<undefined, void, { className?: string | undefined }>('sendBoxToolbarMiddleware');

const {
initMiddleware: initSendBoxToolbarMiddleware,
Provider: SendBoxToolbarMiddlewareProvider,
Proxy: SendBoxToolbarMiddlewareProxy,
// False positive, `types` is used for its typing.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
types
} = templateMiddleware<undefined, void, { className?: string | undefined }>('sendBoxToolbarMiddleware');
'~types': _types
} = template;

type SendBoxToolbarMiddleware = typeof types.middleware;
type SendBoxToolbarMiddlewareProps = typeof types.props;
type SendBoxToolbarMiddlewareRequest = typeof types.request;
type SendBoxToolbarMiddleware = InferMiddleware<typeof template>;
type SendBoxToolbarMiddlewareInit = InferInit<typeof template>;
type SendBoxToolbarMiddlewareProps = InferProps<typeof template>;
type SendBoxToolbarMiddlewareRequest = InferRequest<typeof template>;

export {
initSendBoxToolbarMiddleware,
SendBoxToolbarMiddlewareProvider,
SendBoxToolbarMiddlewareProxy,
initSendBoxToolbarMiddleware,
type SendBoxToolbarMiddleware,
type SendBoxToolbarMiddlewareInit,
type SendBoxToolbarMiddlewareProps,
type SendBoxToolbarMiddlewareRequest
};
10 changes: 7 additions & 3 deletions packages/api/src/hooks/internal/private/templateMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import templateMiddleware from '../../../decorator/private/templateMiddleware';
Comment thread
compulim marked this conversation as resolved.

// TODO: We should move them to a common directory.
export default templateMiddleware;
export {
default,
type InferInit,
type InferMiddleware,
type InferProps,
type InferRequest
} from '../../../decorator/private/templateMiddleware';
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import {
type DecoratorMiddleware,
type DecoratorMiddlewareInit,
type DecoratorMiddlewareTypes
} from 'botframework-webchat-api/decorator';
import { activityGroupingMiddleware, type DecoratorMiddleware } from 'botframework-webchat-api/decorator';
import RenderActivityGrouping from './ui/RenderActivityGrouping';
import SenderGrouping from './ui/SenderGrouping/SenderGrouping';
import StatusGrouping from './ui/StatusGrouping/StatusGrouping';

export default function createDefaultActivityGroupingDecoratorMiddleware(): readonly DecoratorMiddleware[] {
return Object.freeze([
(init: DecoratorMiddlewareInit) =>
init === 'activity grouping' &&
((() =>
activityGroupingMiddleware(
() =>
({ groupingName }) =>
groupingName === 'sender'
? SenderGrouping
: groupingName === 'status'
? StatusGrouping
: RenderActivityGrouping) satisfies DecoratorMiddlewareTypes['activity grouping'])
: RenderActivityGrouping
)
]);
}
14 changes: 5 additions & 9 deletions packages/fluent-theme/src/private/FluentThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { type ActivityMiddleware, type StyleOptions, type TypingIndicatorMiddleware } from 'botframework-webchat-api';
import {
activityBorderMiddleware,
DecoratorComposer,
DecoratorMiddleware,
type DecoratorMiddlewareInit,
type DecoratorMiddlewareTypes
type DecoratorMiddleware
} from 'botframework-webchat-api/decorator';
import { Components } from 'botframework-webchat-component';
import { WebChatDecorator } from 'botframework-webchat-component/decorator';
Expand Down Expand Up @@ -54,12 +53,9 @@ const activityMiddleware: readonly ActivityMiddleware[] = Object.freeze([
const sendBoxMiddleware = [() => () => () => PrimarySendBox];

const decoratorMiddleware: readonly DecoratorMiddleware[] = Object.freeze([
(init: DecoratorMiddlewareInit) =>
init === 'activity border' &&
((next => request =>
request.livestreamingState === 'preparing'
? ActivityLoader
: next(request)) satisfies DecoratorMiddlewareTypes['activity border'])
activityBorderMiddleware(
next => request => (request.livestreamingState === 'preparing' ? ActivityLoader : next(request))
)
]);

const styles = createStyles('fluent-theme');
Expand Down
Loading