Skip to content

Commit

Permalink
feat: enabled multiline text fields in LG Editor (microsoft#6209)
Browse files Browse the repository at this point in the history
* feat: enabled multiline text fields in LG Editor

* Extract textFieldResizeMaxCharsThreshold to variable

* add new item on enter

* Removed debounce from string array enditor

* Focus text

* minor change

* small refactor
  • Loading branch information
tdurnford authored Mar 5, 2021
1 parent 702f34a commit a558e78
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 25 deletions.
11 changes: 10 additions & 1 deletion Composer/packages/lib/code-editor/src/lg/ModalityPivot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const renderModalityEditor = ({
lgOption,
lgTemplates,
memoryVariables,
modalities,
telemetryClient,
editorSettings,
onRemoveModality,
Expand All @@ -103,6 +104,7 @@ const renderModalityEditor = ({
lgOption?: LGOption;
lgTemplates?: readonly LgTemplate[];
memoryVariables?: readonly string[];
modalities: ModalityType[];
telemetryClient: TelemetryClient;
editorSettings?: Partial<CodeEditorSettings>;
onRemoveModality: (modality: ModalityType) => void;
Expand Down Expand Up @@ -152,7 +154,13 @@ const renderModalityEditor = ({
/>
);
case 'Text':
return <TextModalityEditor {...commonProps} response={structuredResponse?.Text as TextStructuredResponseItem} />;
return (
<TextModalityEditor
{...commonProps}
focusOnMount={modalities.length === 1 && modalities[0] === 'Text'}
response={structuredResponse?.Text as TextStructuredResponseItem}
/>
);
}
};

Expand Down Expand Up @@ -391,6 +399,7 @@ export const ModalityPivot = React.memo((props: Props) => {
lgOption,
lgTemplates,
memoryVariables,
modalities,
telemetryClient,
editorSettings,
onRemoveModality,
Expand Down
10 changes: 6 additions & 4 deletions Composer/packages/lib/code-editor/src/lg/hooks/useStringArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import { LGOption } from '../../utils/types';

const getInitialItems = <T extends ArrayBasedStructuredResponseItem>(
response: T,
lgTemplates?: readonly LgTemplate[]
lgTemplates?: readonly LgTemplate[],
focusOnMount?: boolean
): string[] => {
const templateId = getTemplateId(response);
const template = lgTemplates?.find(({ name }) => name === templateId);
return response?.value && template?.body
? template?.body?.replace(/- /g, '').split(/\r?\n/g) || []
: response?.value || [];
: response?.value || (focusOnMount ? [''] : []);
};

export const useStringArray = <T extends ArrayBasedStructuredResponseItem>(
Expand All @@ -28,17 +29,18 @@ export const useStringArray = <T extends ArrayBasedStructuredResponseItem>(
onUpdateResponseTemplate: (response: PartialStructuredResponse) => void;
},
options?: {
focusOnMount?: boolean;
lgOption?: LGOption;
lgTemplates?: readonly LgTemplate[];
}
) => {
const newTemplateNameSuffix = React.useMemo(() => kind.toLowerCase(), [kind]);

const { onRemoveTemplate, onTemplateChange, onUpdateResponseTemplate } = callbacks;
const { lgOption, lgTemplates } = options || {};
const { lgOption, lgTemplates, focusOnMount } = options || {};

const [templateId, setTemplateId] = React.useState(getTemplateId(structuredResponse));
const [items, setItems] = React.useState<string[]>(getInitialItems(structuredResponse, lgTemplates));
const [items, setItems] = React.useState<string[]>(getInitialItems(structuredResponse, lgTemplates, focusOnMount));

const onChange = React.useCallback(
(newItems: string[]) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import { LgTemplate, TelemetryClient } from '@bfc/shared';
import { FluentTheme } from '@uifabric/fluent-theme';
import formatMessage from 'format-message';
import debounce from 'lodash/debounce';
import { Callout, DirectionalHint } from 'office-ui-fabric-react/lib/Callout';
import { ILinkStyles, Link } from 'office-ui-fabric-react/lib/Link';
import React, { useCallback, useEffect, useRef, useState } from 'react';
Expand Down Expand Up @@ -77,23 +76,16 @@ export const StringArrayEditor = React.memo(
}: StringArrayEditorProps) => {
const containerRef = useRef<HTMLDivElement | null>(null);

const [currentIndex, setCurrentIndex] = useState<number | null>(null);
const [currentIndex, setCurrentIndex] = useState<number | null>(items.length === 1 && items[0] === '' ? 0 : null);
const [calloutTargetElement, setCalloutTargetElement] = useState<HTMLInputElement | null>(null);

const debouncedChange = React.useCallback(
debounce((items: string[], callback: (arr: string[]) => void) => {
callback(items);
}, 300),
[]
);

const onItemChange = useCallback(
(index: number) => (_, newValue?: string) => {
const updatedItems = [...items];
updatedItems[index] = newValue ?? '';
debouncedChange(updatedItems, onChange);
onChange(updatedItems);
},
[debouncedChange, items, onChange]
[items, onChange]
);

const onItemFocus = useCallback(
Expand All @@ -107,8 +99,12 @@ export const StringArrayEditor = React.memo(
(index: number) => () => {
const newItems = items.filter((_, idx) => idx !== index);
onChange(newItems);

if (currentIndex) {
setCurrentIndex(newItems.length - 1);
}
},
[items, onChange]
[currentIndex, items, onChange]
);

const onClickAddVariation = useCallback(() => {
Expand All @@ -124,10 +120,19 @@ export const StringArrayEditor = React.memo(
const keydownHandler = (e: KeyboardEvent) => {
if (submitKeys.includes(e.key)) {
setCalloutTargetElement(null);
setCurrentIndex(null);
// Remove empty variations only if necessary
if (items.some((item) => !item)) {
onChange(items.filter(Boolean));

const filteredItems = items.filter(Boolean);

if (e.key === 'Enter') {
onChange([...filteredItems, '']);
setCurrentIndex(filteredItems.length);
} else {
setCurrentIndex(null);

// Remove empty variations only if necessary
if (items.length !== filteredItems.length) {
onChange(filteredItems);
}
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const Input = styled(TextField)({
padding: '8px 0 8px 4px',
width: '100%',
position: 'relative',
'& input': {
'& input, & textarea': {
fontSize: FluentTheme.fonts.small.fontSize,
},
'& .ms-TextField-fieldGroup::after': {
Expand Down Expand Up @@ -89,6 +89,8 @@ const textFieldStyles = {
},
};

const textFieldResizeMaxCharsThreshold = 25;

type Props = {
mode: 'edit' | 'view';
editorMode?: 'single' | 'editor';
Expand Down Expand Up @@ -155,7 +157,7 @@ type TextFieldItemProps = Omit<Props, 'onRemove' | 'mode' | 'onFocus' | 'telemet
const TextFieldItem = React.memo(({ value, onShowCallout, onChange }: TextFieldItemProps) => {
const itemRef = useRef<ITextField | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);

useEffect(() => {
itemRef.current?.focus();
Expand Down Expand Up @@ -196,6 +198,8 @@ const TextFieldItem = React.memo(({ value, onShowCallout, onChange }: TextFieldI
<Input
componentRef={(ref) => (itemRef.current = ref)}
defaultValue={value}
multiline={value.length > textFieldResizeMaxCharsThreshold}
resizable={false}
styles={textFieldStyles}
onChange={onChange}
onClick={click}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { CommonModalityEditorProps, TextStructuredResponseItem } from '../types'
import { ModalityEditorContainer } from './ModalityEditorContainer';
import { StringArrayEditor } from './StringArrayEditor';

type Props = CommonModalityEditorProps & { response: TextStructuredResponseItem };
type Props = CommonModalityEditorProps & { focusOnMount: boolean; response: TextStructuredResponseItem };

const TextModalityEditor = React.memo(
({
focusOnMount,
response,
removeModalityDisabled: disableRemoveModality,
lgOption,
Expand All @@ -33,7 +34,7 @@ const TextModalityEditor = React.memo(
onTemplateChange,
onUpdateResponseTemplate,
},
{ lgOption, lgTemplates }
{ lgOption, lgTemplates, focusOnMount }
);

return (
Expand Down

0 comments on commit a558e78

Please sign in to comment.