Skip to content

Commit

Permalink
feat: yield Action fly-out menu from uischema (#3721)
Browse files Browse the repository at this point in the history
* define default menu schema

* update menu type

* hoists Extension context to DesignPage

* generate action menu from uischema

* remove comments

* typed menuTree with MenuTree

* rename the filter

* use defaultMenuOrder to sort schema menu

* remove duplicated def of OAuthInput

* fix UT EdgeMenu: migrate to new interface

* merge with 'Custom Actions' if exists

* remove duplicated Extension context in VisualEditor

* apply `useFormConfig` hook to PropertyEditor

* apply `useMenuConfig` to Action flyout menu

* move comments position

Co-authored-by: Chris Whitten <[email protected]>
Co-authored-by: Andy Brown <[email protected]>
  • Loading branch information
3 people authored Aug 3, 2020
1 parent 84b305d commit e157b0f
Show file tree
Hide file tree
Showing 14 changed files with 379 additions and 103 deletions.
53 changes: 31 additions & 22 deletions Composer/packages/client/src/pages/design/DesignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import get from 'lodash/get';
import { DialogFactory, SDKKinds, DialogInfo, PromptTab, LuIntentSection, getEditorAPI } from '@bfc/shared';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { JsonEditor } from '@bfc/code-editor';
import { useTriggerApi } from '@bfc/extension';
import Extension, { useTriggerApi, PluginConfig } from '@bfc/extension';
import { useRecoilValue } from 'recoil';

import { LoadingSpinner } from '../../components/LoadingSpinner';
Expand Down Expand Up @@ -45,6 +45,7 @@ import {
luFilesState,
localeState,
} from '../../recoilModel';
import plugins, { mergePluginConfigs } from '../../plugins';
import { useElectronFeatures } from '../../hooks/useElectronFeatures';

import {
Expand Down Expand Up @@ -512,6 +513,12 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
onboardingAddCoachMarkRef({ addNew });
}, []);

const pluginConfig: PluginConfig = useMemo(() => {
const sdkUISchema = schemas?.ui?.content ?? {};
const userUISchema = schemas?.uiOverrides?.content ?? {};
return mergePluginConfigs({ uiSchema: sdkUISchema }, plugins, { uiSchema: userUISchema });
}, [schemas?.ui?.content, schemas?.uiOverrides?.content]);

if (!dialogId) {
return <LoadingSpinner />;
}
Expand All @@ -536,28 +543,30 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
/>
<Toolbar toolbarItems={toolbarItems} />
</div>
<Conversation css={editorContainer}>
<div css={editorWrapper}>
<div aria-label={formatMessage('Authoring canvas')} css={visualPanel} role="region">
{breadcrumbItems}
{dialogJsonVisible ? (
<JsonEditor
key="dialogjson"
editorSettings={userSettings.codeEditor}
id={currentDialog.id}
schema={schemas.sdk.content}
value={currentDialog.content || undefined}
onChange={(data) => {
updateDialog({ id: currentDialog.id, content: data });
}}
/>
) : (
<VisualEditor openNewTriggerModal={openNewTriggerModal} />
)}
<Extension plugins={pluginConfig} shell={shell.api} shellData={shell.data}>
<Conversation css={editorContainer}>
<div css={editorWrapper}>
<div aria-label={formatMessage('Authoring canvas')} css={visualPanel} role="region">
{breadcrumbItems}
{dialogJsonVisible ? (
<JsonEditor
key="dialogjson"
editorSettings={userSettings.codeEditor}
id={currentDialog.id}
schema={schemas.sdk.content}
value={currentDialog.content || undefined}
onChange={(data) => {
updateDialog({ id: currentDialog.id, content: data });
}}
/>
) : (
<VisualEditor openNewTriggerModal={openNewTriggerModal} />
)}
</div>
<PropertyEditor key={focusPath} />
</div>
<PropertyEditor key={focusPath} />
</div>
</Conversation>
</Conversation>
</Extension>
</div>
</div>
<Suspense fallback={<LoadingSpinner />}>
Expand Down
33 changes: 12 additions & 21 deletions Composer/packages/client/src/pages/design/PropertyEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
import { jsx } from '@emotion/core';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import AdaptiveForm, { resolveRef, getUIOptions } from '@bfc/adaptive-form';
import Extension, { FormErrors, JSONSchema7, PluginConfig } from '@bfc/extension';
import { FormErrors, JSONSchema7, useFormConfig } from '@bfc/extension';
import formatMessage from 'format-message';
import isEqual from 'lodash/isEqual';
import debounce from 'lodash/debounce';
import mapValues from 'lodash/mapValues';
import { Resizable, ResizeCallback } from 're-resizable';
import { MicrosoftAdaptiveDialog } from '@bfc/shared';

import { useShell } from '../../shell';
import plugins, { mergePluginConfigs } from '../../plugins';

import { formEditor } from './styles';

Expand Down Expand Up @@ -57,22 +55,17 @@ const PropertyEditor: React.FC = () => {
};
}, [formData]);

const formUIOptions = useFormConfig();

const $schema = useMemo(() => {
if (schemas?.sdk?.content && localData) {
return resolveBaseSchema(schemas.sdk.content, localData.$kind);
}
}, [schemas?.sdk?.content, localData.$kind]);

const pluginConfig: PluginConfig = useMemo(() => {
const sdkUISchema = schemas?.ui?.content ?? {};
const userUISchema = schemas?.uiOverrides?.content ?? {};

return mergePluginConfigs({ uiSchema: sdkUISchema }, plugins, { uiSchema: userUISchema });
}, [schemas?.ui?.content, schemas?.uiOverrides?.content]);

const $uiOptions = useMemo(() => {
return getUIOptions($schema, mapValues(pluginConfig.uiSchema, 'form'));
}, [$schema, pluginConfig]);
return getUIOptions($schema, formUIOptions);
}, [$schema, formUIOptions]);

const errors = useMemo(() => {
const diagnostics = currentDialog?.diagnostics;
Expand Down Expand Up @@ -133,15 +126,13 @@ const PropertyEditor: React.FC = () => {
onResizeStop={handleResize}
>
<div aria-label={formatMessage('form editor')} css={formEditor} data-testid="PropertyEditor" role="region">
<Extension plugins={pluginConfig} shell={shellApi} shellData={shellData}>
<AdaptiveForm
errors={errors}
formData={localData}
schema={$schema}
uiOptions={$uiOptions}
onChange={handleDataChange}
/>
</Extension>
<AdaptiveForm
errors={errors}
formData={localData}
schema={$schema}
uiOptions={$uiOptions}
onChange={handleDataChange}
/>
</div>
</Resizable>
);
Expand Down
8 changes: 1 addition & 7 deletions Composer/packages/client/src/pages/design/VisualEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ import formatMessage from 'format-message';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import get from 'lodash/get';
import VisualDesigner from '@bfc/adaptive-flow';
import Extension from '@bfc/extension';
import { useRecoilValue } from 'recoil';

import grayComposerIcon from '../../images/grayComposerIcon.svg';
import { useShell } from '../../shell';
import plugins from '../../plugins';
import { schemasState, dialogsState, designPageLocationState, dispatcherState } from '../../recoilModel';

import { middleTriggerContainer, middleTriggerElements, triggerButton, visualEditor } from './styles';
Expand Down Expand Up @@ -55,7 +52,6 @@ interface VisualEditorProps {
}

const VisualEditor: React.FC<VisualEditorProps> = (props) => {
const { api: shellApi, data: shellData } = useShell('VisualEditor');
const { openNewTriggerModal } = props;
const [triggerButtonVisible, setTriggerButtonVisibility] = useState(false);
const designPageLocation = useRecoilValue(designPageLocationState);
Expand All @@ -80,9 +76,7 @@ const VisualEditor: React.FC<VisualEditorProps> = (props) => {
css={visualEditor(triggerButtonVisible || !selected)}
data-testid="VisualEditor"
>
<Extension plugins={plugins} shell={shellApi} shellData={shellData}>
<VisualDesigner schema={schemas.sdk?.content} />
</Extension>
<VisualDesigner schema={schemas.sdk?.content} />
</div>
{!selected && onRenderBlankVisual(triggerButtonVisible, openNewTriggerModal)}
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,24 @@ describe('createActionMenu()', () => {
});

it('should show custom actions as last item.', () => {
const menuItemsWithoutCustomActions = createActionMenu(() => null, { isSelfHosted: false, enablePaste: false }, []);
const menuItemsWithoutCustomActions = createActionMenu(
() => null,
{ isSelfHosted: false, enablePaste: false },
{},
[]
);
expect(menuItemsWithoutCustomActions.findIndex((x) => x.key === 'Custom Actions')).toEqual(-1);

const customActions = [
[{ title: 'Custom1', description: 'Custom1', $ref: 'Group1.Custom1' }],
[{ title: 'Custom2', description: 'Custom2', $ref: 'Group2.Custom2' }],
];
const withCustomActions = createActionMenu(() => null, { isSelfHosted: false, enablePaste: false }, customActions);
const withCustomActions = createActionMenu(
() => null,
{ isSelfHosted: false, enablePaste: false },
{},
customActions
);
expect(withCustomActions.findIndex((x) => x.key === 'Custom Actions')).toEqual(withCustomActions.length - 1);
expect(withCustomActions[withCustomActions.length - 1].subMenuProps?.items.length).toEqual(3); // 2 action labels + 1 sep line
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useContext, useState } from 'react';
import formatMessage from 'format-message';
import { DefinitionSummary } from '@bfc/shared';
import { TooltipHost, DirectionalHint } from 'office-ui-fabric-react/lib/Tooltip';
import { useMenuConfig, MenuUISchema } from '@bfc/extension';

// TODO: leak of visual-sdk domain (EdgeAddButtonSize)
import { EdgeAddButtonSize } from '../../../adaptive-flow-renderer/constants/ElementSizes';
Expand Down Expand Up @@ -50,6 +51,7 @@ export const EdgeMenu: React.FC<EdgeMenuProps> = ({ id, onClick }) => {
setMenuSelected(menuSelected);
};

const menuSchema: MenuUISchema = useMenuConfig();
const menuItems = createActionMenu(
(item) => {
if (!item) return;
Expand All @@ -59,6 +61,7 @@ export const EdgeMenu: React.FC<EdgeMenuProps> = ({ id, onClick }) => {
isSelfHosted: selfHosted,
enablePaste: Array.isArray(clipboardActions) && !!clipboardActions.length,
},
menuSchema,
// Custom Action 'oneOf' arrays from schema file
customSchemas.map((x) => x.oneOf).filter((oneOf) => Array.isArray(oneOf) && oneOf.length) as DefinitionSummary[][]
);
Expand Down
Loading

0 comments on commit e157b0f

Please sign in to comment.