diff --git a/Composer/.eslintrc.js b/Composer/.eslintrc.js index 0c8aedc698..9f597c4cfe 100644 --- a/Composer/.eslintrc.js +++ b/Composer/.eslintrc.js @@ -17,6 +17,7 @@ module.exports = { '@typescript-eslint/ban-ts-ignore': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', '@typescript-eslint/no-use-before-define': 'warn', // eslint rules diff --git a/Composer/packages/client/package.json b/Composer/packages/client/package.json index 827fc966fe..248094737f 100644 --- a/Composer/packages/client/package.json +++ b/Composer/packages/client/package.json @@ -39,6 +39,7 @@ "lodash.findindex": "^4.6.0", "lodash.findlastindex": "^4.6.0", "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", "lodash.once": "^4.1.1", "lodash.set": "^4.3.2", "lodash.startcase": "^4.4.0", @@ -94,7 +95,13 @@ "@emotion/babel-preset-css-prop": "^10.0.14", "@types/jest": "^24.0.16", "@types/jwt-decode": "^2.2.1", + "@types/lodash.clonedeep": "^4.5.6", + "@types/lodash.find": "^4.6.6", + "@types/lodash.findindex": "^4.6.6", + "@types/lodash.get": "^4.4.6", + "@types/lodash.isequal": "^4.5.5", "@types/lodash.once": "^4.1.6", + "@types/lodash.set": "^4.3.6", "@types/reach__router": "^1.2.4", "@types/react": "16.9.0", "@types/react-dom": "16.9.0", @@ -104,4 +111,4 @@ "jest-dom": "^3.2.2", "react-testing-library": "^6.0.1" } -} \ No newline at end of file +} diff --git a/Composer/packages/client/src/CreationFlow/index.js b/Composer/packages/client/src/CreationFlow/index.js index a00ada69ba..df1d85b80f 100644 --- a/Composer/packages/client/src/CreationFlow/index.js +++ b/Composer/packages/client/src/CreationFlow/index.js @@ -1,14 +1,12 @@ import React, { useState, useEffect, useContext } from 'react'; import { toLower } from 'lodash'; -import { CreationFlowStatus } from '../constants'; +import { CreationFlowStatus, DialogCreationCopy, Steps } from '../constants'; import { CreateOptions } from './CreateOptions/index'; import { DefineConversation } from './DefineConversation/index'; -import { Steps } from './../constants/index'; import { OpenProject } from './OpenProject'; import { StoreContext } from './../store'; -import { DialogInfo } from './../constants/index'; import { StepWizard } from './StepWizard/StepWizard'; import { navigateTo } from './../utils/navigation'; @@ -120,15 +118,15 @@ export function CreationFlow(props) { const steps = { [Steps.CREATE]: { - ...DialogInfo.CREATE_NEW_BOT, + ...DialogCreationCopy.CREATE_NEW_BOT, children: , }, [Steps.LOCATION]: { - ...DialogInfo.SELECT_LOCATION, + ...DialogCreationCopy.SELECT_LOCATION, children: , }, [Steps.DEFINE]: { - ...DialogInfo.DEFINE_CONVERSATION_OBJECTIVE, + ...DialogCreationCopy.DEFINE_CONVERSATION_OBJECTIVE, children: ( { return [VISUAL_EDITOR, FORM_EDITOR].includes(sourceWindowName); }; -const useDebouncedFunc = (fn, delay = 750) => useRef(debounce(fn, delay)).current; - const FileChangeTypes = { CREATE: 'create', UPDATE: 'update', @@ -71,7 +56,7 @@ export const ShellApi: React.FC = () => { const [, forceUpdate] = useState(); const { state, actions } = useContext(StoreContext); - const { dialogs, schemas, lgFiles, luFiles, designPageLocation, focusPath, breadcrumb } = state; + const { dialogs, schemas, lgFiles, luFiles, designPageLocation, focusPath, breadcrumb, botName } = state; const updateDialog = actions.updateDialog; const updateLuFile = actions.updateLuFile; //if debounced, error can't pass to form const updateLgFile = actions.updateLgFile; @@ -169,8 +154,13 @@ export const ShellApi: React.FC = () => { function getState(sourceWindow?: string): ShellData { const currentDialog = dialogs.find(d => d.id === dialogId); + if (!currentDialog) { + return {} as ShellData; + } + return { data: getData(sourceWindow), + botName, dialogs, focusPath, schemas, diff --git a/Composer/packages/client/src/TestController.tsx b/Composer/packages/client/src/TestController.tsx index 8a980f2152..16c5cafe02 100644 --- a/Composer/packages/client/src/TestController.tsx +++ b/Composer/packages/client/src/TestController.tsx @@ -9,6 +9,7 @@ import { Stack, } from 'office-ui-fabric-react'; import formatMessage from 'format-message'; +import { DialogInfo } from 'shared'; import settingsStorage from './utils/dialogSettingStorage'; import { StoreContext } from './store'; @@ -17,7 +18,6 @@ import { BotStatus, LuisConfig, Text } from './constants'; import { PublishLuisDialog } from './publishDialog'; import { OpenAlertModal, DialogStyle } from './components/Modal'; import { getReferredFiles } from './utils/luUtil'; -import { DialogInfo } from './store/types'; const openInEmulator = (url, authSettings: { MicrosoftAppId: string; MicrosoftAppPassword: string }) => { // this creates a temporary hidden iframe to fire off the bfemulator protocol diff --git a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx index 36d1aa7a8d..afa298354d 100644 --- a/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx +++ b/Composer/packages/client/src/components/ProjectTree/TriggerCreationModal.tsx @@ -4,6 +4,7 @@ import formatMessage from 'format-message'; import { DialogFooter, PrimaryButton, DefaultButton, Stack, IDropdownOption } from 'office-ui-fabric-react'; import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; import { get } from 'lodash'; +import { DialogInfo } from 'shared'; import { addNewTrigger, @@ -17,7 +18,6 @@ import { getActivityTypes, } from '../../utils/dialogUtil'; import { StoreContext } from '../../store'; -import { DialogInfo } from '../../store/types'; import { styles, dropdownStyles, dialogWindow } from './styles'; diff --git a/Composer/packages/client/src/components/ProjectTree/index.tsx b/Composer/packages/client/src/components/ProjectTree/index.tsx index 37926c9e13..45ddc435d3 100644 --- a/Composer/packages/client/src/components/ProjectTree/index.tsx +++ b/Composer/packages/client/src/components/ProjectTree/index.tsx @@ -11,8 +11,8 @@ import { import React, { useMemo, useRef, useState } from 'react'; import { cloneDeep } from 'lodash'; import formatMessage from 'format-message'; +import { DialogInfo, ITrigger } from 'shared'; -import { DialogInfo, ITrigger } from '../../store/types'; import { createSelectedPath, getFriendlyName } from '../../utils'; import { addButton, groupListStyle, root, searchBox } from './styles'; diff --git a/Composer/packages/client/src/constants/index.ts b/Composer/packages/client/src/constants/index.ts index bd9a8a5868..1dcd4b60b6 100644 --- a/Composer/packages/client/src/constants/index.ts +++ b/Composer/packages/client/src/constants/index.ts @@ -165,7 +165,7 @@ export const Steps = { NONE: 'NONE', }; -export const DialogInfo = { +export const DialogCreationCopy = { CREATE_NEW_BOT: { title: formatMessage('Create from scratch?'), subText: formatMessage('You can create a new bot from scratch with Designer, or start with a template.'), diff --git a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx index 1ae4faf3e1..9bb140d325 100644 --- a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx +++ b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx @@ -1,9 +1,8 @@ import React, { useState, useEffect } from 'react'; import { initializeIcons } from 'office-ui-fabric-react'; +import { LuFile, ShellData } from 'shared'; import ApiClient from '../messenger/ApiClient'; -import { ShellData } from '../ShellApi'; -import { LuFile } from '../store/types'; import getEditor from './EditorMap'; diff --git a/Composer/packages/client/src/pages/design/new-dialog-modal.js b/Composer/packages/client/src/pages/design/new-dialog-modal.js index 3a04c2cd7d..0c53a5c416 100644 --- a/Composer/packages/client/src/pages/design/new-dialog-modal.js +++ b/Composer/packages/client/src/pages/design/new-dialog-modal.js @@ -1,6 +1,6 @@ import React from 'react'; -import { DialogInfo } from '../../constants'; +import { DialogCreationCopy } from '../../constants'; import { DefineConversation } from '../../CreationFlow/DefineConversation/index'; import { DialogWrapper } from '../../components/DialogWrapper/index'; @@ -8,7 +8,7 @@ export default function NewDialogModal(props) { const { isOpen, onDismiss, onSubmit, onGetErrorMessage } = props; return ( - + { templateUsedInDialogMap[template.Name] = dialogs.reduce((result, dialog) => { - if (dialog.lgTemplates.indexOf(template.Name) !== -1) { + if (dialog.lgTemplates.includes(template.Name)) { result.push(dialog.id); } return result; diff --git a/Composer/packages/client/src/pages/language-understanding/table-view.js b/Composer/packages/client/src/pages/language-understanding/table-view.js index 7d7041904c..04600cb49c 100644 --- a/Composer/packages/client/src/pages/language-understanding/table-view.js +++ b/Composer/packages/client/src/pages/language-understanding/table-view.js @@ -48,7 +48,7 @@ export default function TableView(props) { name, phrases: [utterance.text], fileId: luFile.id, - used: luDialog.luIntents.indexOf(name) !== -1, // used by it's dialog or not + used: luDialog.luIntents.includes(name), // used by it's dialog or not state, }); } diff --git a/Composer/packages/client/src/store/action/dialog.ts b/Composer/packages/client/src/store/action/dialog.ts index 5a105e9a8a..20c1b21618 100644 --- a/Composer/packages/client/src/store/action/dialog.ts +++ b/Composer/packages/client/src/store/action/dialog.ts @@ -1,8 +1,9 @@ import axios from 'axios'; import clonedeep from 'lodash.clonedeep'; import { remove } from 'lodash'; +import { DialogInfo } from 'shared'; -import { ActionCreator, State, DialogInfo } from '../types'; +import { ActionCreator, State } from '../types'; import { undoable, Pick } from '../middlewares/undo'; import { BASEURL, ActionTypes } from './../../constants/index'; diff --git a/Composer/packages/client/src/store/index.tsx b/Composer/packages/client/src/store/index.tsx index 626a112539..f526c17bad 100644 --- a/Composer/packages/client/src/store/index.tsx +++ b/Composer/packages/client/src/store/index.tsx @@ -29,7 +29,7 @@ const initialState: State = { templateId: '', storageFileLoadingStatus: 'success', lgFiles: [], - schemas: {}, + schemas: { editor: {} }, luFiles: [], designPageLocation: { dialogId: '', diff --git a/Composer/packages/client/src/store/types.ts b/Composer/packages/client/src/store/types.ts index 1696a1f903..dc7ffac372 100644 --- a/Composer/packages/client/src/store/types.ts +++ b/Composer/packages/client/src/store/types.ts @@ -1,8 +1,7 @@ -// TODO: Extract some common types to be shared across packages (e.g. DialogInfo, LgFile, etc) // TODO: remove this once we can expand the types /* eslint-disable @typescript-eslint/no-explicit-any */ import React from 'react'; -import { PromptTab } from 'shared'; +import { PromptTab, DialogInfo, BotSchemas, LgFile, LuFile } from 'shared'; import { CreationFlowStatus, BotStatus } from '../constants'; @@ -29,12 +28,6 @@ export interface BreadcrumbItem { focused: string; } -export interface BotSchemas { - editor?: any; - sdk?: any; - diagnostics?: any[]; -} - export interface State { dialogs: DialogInfo[]; botName: string; @@ -84,60 +77,6 @@ export interface MiddlewareApi { export type MiddlewareFunc = (middlewareApi: MiddlewareApi) => (next: any) => React.Dispatch; -export interface ITrigger { - id: string; - displayName: string; - type: string; - isIntent: boolean; -} - -export interface DialogInfo { - id: string; - displayName: string; - isRoot: boolean; - content: any; - diagnostics: string[]; - luFile: string; - triggers: ITrigger[]; -} - -export interface Intent { - name: string; -} - -export interface Utterance { - intent: string; - text: string; -} - -export interface LuDiagnostic { - text: string; -} - -export interface LuFile { - id: string; - relativePath: string; - content: string; - parsedContent: { - LUISJsonStructure: { - intents: Intent[]; - utterances: Utterance[]; - }; - }; - diagnostics: LuDiagnostic[]; -} - -export interface LgFile { - id: string; - relativePath: string; - content: string; -} - -export interface LgTemplate { - Name: string; - Body: string; -} - export interface ILuisConfig { name: string; authoringKey: string; diff --git a/Composer/packages/client/src/utils/dialogUtil.ts b/Composer/packages/client/src/utils/dialogUtil.ts index 9b3c909588..50b0ca424a 100644 --- a/Composer/packages/client/src/utils/dialogUtil.ts +++ b/Composer/packages/client/src/utils/dialogUtil.ts @@ -2,8 +2,7 @@ import { ConceptLabels, DialogGroup, SDKTypes, dialogGroups, seedNewDialog } fro import { cloneDeep, get, set } from 'lodash'; import { ExpressionEngine } from 'botbuilder-expression-parser'; import { IDropdownOption } from 'office-ui-fabric-react'; - -import { DialogInfo } from '../store/types'; +import { DialogInfo } from 'shared'; import { getFocusPath } from './navigation'; import { upperCaseName } from './fileUtil'; diff --git a/Composer/packages/client/src/utils/luUtil.ts b/Composer/packages/client/src/utils/luUtil.ts index ddeb847f84..ab8916c1de 100644 --- a/Composer/packages/client/src/utils/luUtil.ts +++ b/Composer/packages/client/src/utils/luUtil.ts @@ -1,4 +1,4 @@ -import { LuFile, DialogInfo, LuDiagnostic } from '../store/types'; +import { LuFile, DialogInfo, LuDiagnostic } from 'shared'; export function getReferredFiles(luFiles: LuFile[], dialogs: DialogInfo[]) { return luFiles.filter(file => { diff --git a/Composer/packages/extensions/obiformeditor/src/Form/ObjectFieldTemplate/ObjectItem.tsx b/Composer/packages/extensions/obiformeditor/src/Form/ObjectFieldTemplate/ObjectItem.tsx index 4429c03265..3061f99a48 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/ObjectFieldTemplate/ObjectItem.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/ObjectFieldTemplate/ObjectItem.tsx @@ -3,8 +3,7 @@ import { IContextualMenuItem, ContextualMenuItemType, IconButton } from 'office- import formatMessage from 'format-message'; import { NeutralColors, FontSizes } from '@uifabric/fluent-theme'; import classnames from 'classnames'; -import { JSONSchema6 } from 'json-schema'; -import { FIELDS_TO_HIDE } from 'shared'; +import { FIELDS_TO_HIDE, OBISchema } from 'shared'; import './styles.css'; @@ -14,7 +13,7 @@ interface ObjectItemProps { onDropPropertyClick: (name: string) => (e) => void; onEdit: (e) => void; onAdd: (e) => void; - schema: JSONSchema6; + schema: OBISchema; } export default function ObjectItem(props: ObjectItemProps) { diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/CasesField.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/CasesField.tsx index c335b2f644..d319161913 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/CasesField.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/CasesField.tsx @@ -1,207 +1,172 @@ +/** @jsx jsx */ +import { jsx } from '@emotion/core'; import React, { useState } from 'react'; import formatMessage from 'format-message'; -import { FieldProps } from '@bfcomposer/react-jsonschema-form'; -import { DefaultButton, TextField, DirectionalHint, IContextualMenuItem, IconButton } from 'office-ui-fabric-react'; -import get from 'lodash.get'; +import { IContextualMenuItem, IconButton, TextField } from 'office-ui-fabric-react'; import { NeutralColors, FontSizes } from '@uifabric/fluent-theme'; -import { createStepMenu, DialogGroup } from 'shared'; - -import Modal from '../../Modal'; -import { swap } from '../utils'; - -import { TableField } from './TableField'; - -import './styles.css'; - -interface CaseFormData { - oldValue?: string; - newValue?: string; +import { CaseCondition } from 'shared'; +import cloneDeep from 'lodash.clonedeep'; + +import { swap, remove } from '../utils'; +import { BFDFieldProps } from '../types'; +import { ExpressionWidget } from '../widgets/ExpressionWidget'; + +import { arrayItem, arrayItemValue, field } from './styles'; +import { EditableField } from './EditableField'; +interface CaseItemProps { + index: number; + value: string; + hasMoveUp: boolean; + hasMoveDown: boolean; + onReorder: (a: number, b: number) => void; + onDelete: (idx: number) => void; + onEdit: (idx: number, value?: string) => void; } -function CaseConditionActions(props) { - const { item, index, onEdit, onRemove, onMove, canMoveUp, canMoveDown } = props; +const CaseItem: React.FC = props => { + const { value, hasMoveDown, hasMoveUp, onReorder, onDelete, index, onEdit } = props; + const [key, setKey] = useState(value); - const menuItems: IContextualMenuItem[] = [ - { - key: 'edit', - text: formatMessage('Edit'), - iconProps: { iconName: 'Edit' }, - onClick: () => onEdit(item.value), - }, + // This needs to return true to dismiss the menu after a click. + const fabricMenuItemClickHandler = fn => e => { + fn(e); + return true; + }; + + const contextItems: IContextualMenuItem[] = [ { key: 'moveUp', - text: formatMessage('Move Up'), + text: 'Move Up', iconProps: { iconName: 'CaretSolidUp' }, - disabled: !canMoveUp, - onClick: () => { - onMove(item.value, index - 1); - }, + disabled: !hasMoveUp, + onClick: fabricMenuItemClickHandler(() => onReorder(index, index - 1)), }, { key: 'moveDown', - text: formatMessage('Move Down'), + text: 'Move Down', iconProps: { iconName: 'CaretSolidDown' }, - disabled: !canMoveDown, - onClick: () => { - onMove(item.value, index + 1); - }, + disabled: !hasMoveDown, + onClick: fabricMenuItemClickHandler(() => onReorder(index, index + 1)), }, { key: 'remove', - text: formatMessage('Remove'), + text: 'Remove', iconProps: { iconName: 'Cancel' }, - onClick: () => onRemove(item.value), + onClick: fabricMenuItemClickHandler(() => onDelete(index)), }, ]; + const handleEdit = (_e: any, newVal?: string) => { + onEdit(index, newVal); + }; + + const handleBlur = () => { + setKey(value); + if (!value) { + onDelete(index); + } + }; + return ( - +
+
+ +
+ +
); -} +}; -export const CasesField: React.FC> = props => { - const { formData, schema } = props; - const [showModal, setShowModal] = useState(false); - const [caseFormData, setCaseFormData] = useState({}); - const items = formData; - const newLabel = formatMessage('Add New Case'); - - const handleCaseUpdate = e => { - e.preventDefault(); - const { oldValue, newValue } = caseFormData; - - if (newValue) { - const existingCase = items.find(i => i.value === oldValue); - - if (existingCase) { - props.onChange( - items.map(i => { - if (i.value === oldValue) { - return { - ...i, - value: newValue, - }; - } - return i; - }) - ); - } else { - props.onChange([...(items || []), { value: newValue }]); - } +export const CasesField: React.FC> = props => { + const { id, formData, schema, formContext } = props; + const [newBranch, setNewBranch] = useState(''); - setShowModal(false); - setCaseFormData({}); - } + const handleChange = (_e: any, newValue?: string) => { + setNewBranch(newValue || ''); }; - const handleStepsUpdate = (caseName: string) => (caseSteps: MicrosoftIDialog[]) => { - const updatedCases = items.map(i => { - if (i.value === caseName) { - return { - ...i, - actions: caseSteps, - }; - } - - return i; - }); + const submitNewBranch = (e: React.KeyboardEvent) => { + if (e.key.toLowerCase() === 'enter') { + e.preventDefault(); - props.onChange(updatedCases); + if (newBranch) { + props.onChange([...props.formData, { value: newBranch }]); + setNewBranch(''); + } + } }; - const handleItemEdit = (caseName: string) => { - setCaseFormData({ oldValue: caseName, newValue: caseName }); - setShowModal(true); + const handleReorder = (aIdx: number, bIdx: number) => { + props.onChange(swap(props.formData, aIdx, bIdx)); }; - const handleRemoveItem = (caseName: string) => { - props.onChange(items.filter(i => i.value !== caseName)); + const handleDelete = (idx: number) => { + props.onChange(remove(props.formData, idx)); }; - const handleMoveItem = (caseName: string, newPos: number) => { - const caseIdx = items.findIndex(i => i.value === caseName); - props.onChange(swap(items, caseIdx, newPos)); + const handleEdit = (idx, val) => { + const casesCopy = cloneDeep(props.formData) || []; + casesCopy[idx].value = val; + props.onChange(casesCopy); }; return ( -
- {items.map((item: CaseCondition, itemIdx: number) => ( -
- - {...props} - title={`Branch: ${item.value}`} - formData={item.actions} - navPrefix={`cases[${itemIdx}].actions`} - onChange={handleStepsUpdate(item.value)} - > - {({ createNewItemAtIndex }) => ( - - {formatMessage('Add New Action for { caseName }', { caseName: item.value })} - - )} - -
- setShowModal(true)} - index={itemIdx} - canMoveUp={itemIdx > 0} - canMoveDown={itemIdx < items.length - 1} - /> -
-
- ))} - setShowModal(true)}> - {newLabel} - - {showModal && ( - setShowModal(false)}> -
+
+
+ +
+
+ {formData.map((v, i) => ( + + ))} +
+
setCaseFormData({ ...caseFormData, newValue: val })} - componentRef={el => { - if (el) { - el.focus(); - } + styles={{ + root: { margin: '5px 0 7px -9px', cursor: 'default' }, + fieldGroup: { + borderColor: 'transparent', + transition: 'border-color 0.1s linear', + selectors: { + ':hover': { + borderColor: 'transparent', + }, + }, + }, }} + value={formatMessage('Default')} + onChange={() => {}} + autoComplete="off" + readOnly /> - - {newLabel} - - - - )} +
+
+
); }; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/BotAsks.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/BotAsks.tsx index 1d1704fdc7..ffc668d955 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/BotAsks.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/BotAsks.tsx @@ -1,5 +1,6 @@ import React from 'react'; import formatMessage from 'format-message'; +import { MicrosoftInputDialog } from 'shared'; import { LgEditorWidget } from '../../widgets/LgEditorWidget'; import { WidgetLabel } from '../../widgets/WidgetLabel'; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/ChoiceOptions.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/ChoiceOptions.tsx index c760eaec5b..acfbcdd59b 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/ChoiceOptions.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/ChoiceOptions.tsx @@ -1,8 +1,8 @@ import React from 'react'; import formatMessage from 'format-message'; import { IdSchema } from '@bfcomposer/react-jsonschema-form'; -import { JSONSchema6 } from 'json-schema'; import get from 'lodash.get'; +import { IChoiceOption, OBISchema } from 'shared'; import { field } from '../styles'; import { TextWidget, CheckboxWidget } from '../../../widgets'; @@ -10,7 +10,7 @@ import { FormContext } from '../../../types'; interface ChoiceOptionsProps { idSchema: IdSchema; - schema: JSONSchema6; + schema: OBISchema; formData: IChoiceOption; onChange: (field: keyof IChoiceOption) => (data: any) => void; formContext: FormContext; @@ -19,7 +19,7 @@ interface ChoiceOptionsProps { export const ChoiceOptions: React.FC = props => { const { schema, formData, onChange, idSchema, formContext } = props; - const optionSchema = (field: keyof IChoiceOption): JSONSchema6 => { + const optionSchema = (field: keyof IChoiceOption): OBISchema => { return get(schema, ['properties', field]); }; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/Choices.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/Choices.tsx index a08f768910..12f6417e2c 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/Choices.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/Choices.tsx @@ -5,6 +5,7 @@ import { JSONSchema6 } from 'json-schema'; import formatMessage from 'format-message'; import { TextField, IconButton, IContextualMenuItem } from 'office-ui-fabric-react'; import { NeutralColors, FontSizes } from '@uifabric/fluent-theme'; +import { IChoice } from 'shared'; import { field, choiceItemContainer, choiceItemValue, choiceItemSynonyms } from '../styles'; import { swap, remove } from '../../../utils'; @@ -32,6 +33,7 @@ interface ChoicesProps { const ChoiceItem: React.FC = props => { const { choice, index, onEdit, hasMoveUp, hasMoveDown, onReorder, onDelete } = props; + const [key, setKey] = useState(`${choice.value}:${choice.synonyms ? choice.synonyms.join() : ''}`); const contextItems: IContextualMenuItem[] = [ { @@ -64,8 +66,15 @@ const ChoiceItem: React.FC = props => { } }; + const handleBlur = () => { + setKey(`${choice.value}:${choice.synonyms ? choice.synonyms.join() : ''}`); + if (!choice.value && (!choice.synonyms || !choice.synonyms.length)) { + onDelete(index); + } + }; + return ( -
+
= props => { styleOverrides={{ root: { margin: '5px 0 7px 0' }, }} + onBlur={handleBlur} />
@@ -83,6 +93,7 @@ const ChoiceItem: React.FC = props => { styleOverrides={{ root: { margin: '5px 0 7px 0' }, }} + onBlur={handleBlur} />
@@ -184,8 +195,8 @@ export const Choices: React.FC = props => { onEdit={handleEdit} onReorder={handleReorder} onDelete={handleDelete} - hasMoveDown={i > 0} - hasMoveUp={i !== formData.length - 1} + hasMoveDown={i !== formData.length - 1} + hasMoveUp={i !== 0} /> ))}
diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/index.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/index.tsx index 34daec23ba..63b6c94c62 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/index.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ChoiceInput/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { FieldProps, IdSchema } from '@bfcomposer/react-jsonschema-form'; import formatMessage from 'format-message'; +import { ChoiceInput, IChoiceOption } from 'shared'; import { PromptFieldChangeHandler, GetSchema } from '../types'; import { CheckboxWidget } from '../../../widgets'; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ConfirmInput.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ConfirmInput.tsx index c2d8819dde..7bc66aa805 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ConfirmInput.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/ConfirmInput.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { FieldProps, IdSchema } from '@bfcomposer/react-jsonschema-form'; import formatMessage from 'format-message'; +import { ConfirmInput, IChoiceOption } from 'shared'; import { PromptFieldChangeHandler, GetSchema } from './types'; import { Choices } from './ChoiceInput/Choices'; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Exceptions.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Exceptions.tsx index d7b3f71f94..9d946e1d18 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Exceptions.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Exceptions.tsx @@ -3,6 +3,7 @@ import { jsx } from '@emotion/core'; import React from 'react'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import formatMessage from 'format-message'; +import { MicrosoftInputDialog } from 'shared'; import { WidgetLabel } from '../../widgets/WidgetLabel'; import { LgEditorWidget } from '../../widgets/LgEditorWidget'; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/PromptSettings.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/PromptSettings.tsx index db9d382554..fa395507a1 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/PromptSettings.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/PromptSettings.tsx @@ -2,6 +2,7 @@ import { jsx } from '@emotion/core'; import formatMessage from 'format-message'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; +import { MicrosoftInputDialog } from 'shared'; import { TextWidget, CheckboxWidget } from '../../widgets'; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserAnswers.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserAnswers.tsx index 84348a6528..ef6c43e9ff 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserAnswers.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/UserAnswers.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import formatMessage from 'format-message'; import { JSONSchema6 } from 'json-schema'; -import { SDKTypes } from 'shared'; +import { SDKTypes, MicrosoftInputDialog, ChoiceInput, ConfirmInput } from 'shared'; import { TextWidget, SelectWidget } from '../../widgets'; @@ -60,7 +60,7 @@ export const UserAnswers: React.FC = props => { onChange={onChange('defaultLocale')} schema={getSchema('defaultLocale')} id={idSchema.defaultLocale.__id} - value={(formData as ChoiceInput).defaultLocale} + value={((formData as unknown) as ChoiceInput).defaultLocale} label={formatMessage('Default locale')} formContext={props.formContext} rawErrors={errorSchema.defaultLocale && errorSchema.defaultLocale.__errors} @@ -73,7 +73,7 @@ export const UserAnswers: React.FC = props => { onChange={onChange('style')} schema={getSchema('style')} id={idSchema.style.__id} - value={(formData as ChoiceInput).style} + value={((formData as unknown) as ChoiceInput).style} label={formatMessage('List style')} formContext={props.formContext} rawErrors={errorSchema.style && errorSchema.style.__errors} @@ -81,9 +81,11 @@ export const UserAnswers: React.FC = props => { />
)} - {formData.$type === SDKTypes.ChoiceInput && } + {formData.$type === SDKTypes.ChoiceInput && ( + + )} {formData.$type === SDKTypes.ConfirmInput && ( - + )} ); diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Validations.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Validations.tsx index 939bc6fc41..73800d8f63 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Validations.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/PromptField/Validations.tsx @@ -26,6 +26,7 @@ interface ValidationItemProps { const ValidationItem: React.FC = props => { const { value, hasMoveDown, hasMoveUp, onReorder, onDelete, index, formContext, onEdit, schema } = props; + const [key, setKey] = useState(value); // This needs to return true to dismiss the menu after a click. const fabricMenuItemClickHandler = fn => e => { @@ -61,6 +62,7 @@ const ValidationItem: React.FC = props => { }; const handleBlur = () => { + setKey(value); if (!value) { onDelete(index); } @@ -70,6 +72,7 @@ const ValidationItem: React.FC = props => {
(data: any) => void; -export type GetSchema = (field: InputDialogKeys) => JSONSchema6; +export type GetSchema = (field: InputDialogKeys) => OBISchema; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx index 39f8922c4b..1b4c344911 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/InlineLuEditor.tsx @@ -1,7 +1,6 @@ import React, { useState } from 'react'; import { LuEditor } from 'code-editor'; - -import { LuFile } from '../../../types'; +import { LuFile } from 'shared'; interface InlineLuEditorProps { file: LuFile; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/RegexEditor.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/RegexEditor.tsx index e0d4fa0a7c..d61550727e 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/RegexEditor.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/RegexEditor.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { toIdSchema } from '@bfcomposer/react-jsonschema-form/lib/utils'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import { JSONSchema6 } from 'json-schema'; +import { MicrosoftIRecognizer } from 'shared'; export default function RegexEditor(props: FieldProps) { if (!props.schema.oneOf) { diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx index 9545dcfbd7..a614d4f305 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/RecognizerField/index.tsx @@ -2,9 +2,9 @@ import React, { useState, ReactElement } from 'react'; import formatMessage from 'format-message'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import { Dropdown, ResponsiveMode, IDropdownOption, Spinner, SpinnerSize } from 'office-ui-fabric-react'; +import { MicrosoftIRecognizer, LuFile } from 'shared'; import { BaseField } from '../BaseField'; -import { LuFile } from '../../../types'; import ToggleEditor from './ToggleEditor'; import RegexEditor from './RegexEditor'; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/RulesField.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/RulesField.tsx index b1e5c6f46a..650aef1061 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/RulesField.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/RulesField.tsx @@ -3,7 +3,7 @@ import formatMessage from 'format-message'; import { DirectionalHint, DefaultButton } from 'office-ui-fabric-react'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import get from 'lodash.get'; -import { createStepMenu, DialogGroup } from 'shared'; +import { createStepMenu, DialogGroup, ITriggerCondition, OnIntent } from 'shared'; import { setOverridesOnField } from '../utils'; @@ -16,7 +16,7 @@ const renderTitle = (item: ITriggerCondition) => { return friendlyName; } - const intentName = (item as OnIntent).intent; + const intentName = ((item as unknown) as OnIntent).intent; if (intentName) { return intentName; } diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/StepsField.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/StepsField.tsx index bc7125f0d2..d61ae853f8 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/StepsField.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/StepsField.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { DialogGroup, createStepMenu } from 'shared'; +import { DialogGroup, createStepMenu, MicrosoftIDialog } from 'shared'; import formatMessage from 'format-message'; import { DefaultButton, DirectionalHint } from 'office-ui-fabric-react'; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/TableField.tsx b/Composer/packages/extensions/obiformeditor/src/Form/fields/TableField.tsx index ae09167430..28268a4e9c 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/fields/TableField.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/TableField.tsx @@ -14,7 +14,7 @@ import { DirectionalHint } from 'office-ui-fabric-react'; import get from 'lodash.get'; import { FieldProps } from '@bfcomposer/react-jsonschema-form'; import { NeutralColors, FontSizes } from '@uifabric/fluent-theme'; -import { COMPOUND_TYPES } from 'shared'; +import { COMPOUND_TYPES, MicrosoftIDialog } from 'shared'; import { buildDialogOptions, swap, remove, insertAt, DialogOptionsOpts } from '../utils'; import { FormContext } from '../types'; @@ -162,12 +162,15 @@ export function TableField(props: const createNewItemAtIndex = (idx: number = items.length) => (_: any, item: IContextualMenuItem) => { onChange(insertAt(items, item.data, idx)); - // @ts-ignore - IDialog could potentially be a string, so TS complains about $type - if (COMPOUND_TYPES.includes(item.$type)) { - formContext.shellApi.onFocusEvent(`${navPrefix}[${idx}]`); - } - - formContext.shellApi.onFocusSteps([`${navPrefix}[${idx}]`]); + // wait until change can propogate before navigating + setTimeout(() => { + // @ts-ignore - IDialog could potentially be a string, so TS complains about $type + if (COMPOUND_TYPES.includes(item.$type)) { + formContext.shellApi.onFocusEvent(`${navPrefix}[${idx}]`); + } + + formContext.shellApi.onFocusSteps([`${navPrefix}[${idx}]`]); + }, 500); return true; }; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/fields/styles.ts b/Composer/packages/extensions/obiformeditor/src/Form/fields/styles.ts new file mode 100644 index 0000000000..d4ecceb789 --- /dev/null +++ b/Composer/packages/extensions/obiformeditor/src/Form/fields/styles.ts @@ -0,0 +1,23 @@ +import { css } from '@emotion/core'; + +export const arrayItem = css` + display: flex; + align-items: center; + padding-left: 10px; + + & + & { + margin-top: 10px; + } +`; + +export const arrayItemValue = css` + flex: 1; +`; + +export const arrayItemDefault = css` + font-size: 14px; +`; + +export const field = css` + margin: 10px 0; +`; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/types.ts b/Composer/packages/extensions/obiformeditor/src/Form/types.ts index 943b67b39a..9df144e6c9 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/types.ts +++ b/Composer/packages/extensions/obiformeditor/src/Form/types.ts @@ -1,21 +1,14 @@ import { WidgetProps, FieldProps, ObjectFieldTemplateProps } from '@bfcomposer/react-jsonschema-form'; -import { JSONSchema6 } from 'json-schema'; +import { ShellData, EditorSchema, ShellApi, OBISchema } from 'shared'; -import { ShellApi, LuFile, LgFile, DialogInfo } from '../types'; - -export interface FormContext { - editorSchema: any; +export interface FormContext + extends Pick { + editorSchema: EditorSchema; shellApi: ShellApi; rootId: string; - luFiles: LuFile[]; - lgFiles: LgFile[]; dialogOptions: { value: string; label: string }[]; - currentDialog: DialogInfo; dialogId?: string; isRoot: boolean; - focusedEvent: string; - focusedSteps: string[]; - focusedTab?: string; } interface EnumOption { @@ -34,7 +27,7 @@ export interface BFDFieldProps extends FieldProps { export interface BFDWidgetProps extends Partial { id: string; - schema: JSONSchema6; + schema: OBISchema; onChange: (data: any) => void; formContext: FormContext; options?: { diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx index 36c2d41352..2e8082a360 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/IntentWidget.tsx @@ -2,8 +2,8 @@ import React from 'react'; import { Dropdown, ResponsiveMode, IDropdownOption } from 'office-ui-fabric-react'; import get from 'lodash.get'; import formatMessage from 'format-message'; +import { LuFile, DialogInfo, RegexRecognizer } from 'shared'; -import { LuFile, DialogInfo } from '../../types'; import { BFDWidgetProps, FormContext } from '../types'; import { WidgetLabel } from './WidgetLabel'; diff --git a/Composer/packages/extensions/obiformeditor/src/FormEditor.tsx b/Composer/packages/extensions/obiformeditor/src/FormEditor.tsx index 8cef137af9..7825f09e9a 100644 --- a/Composer/packages/extensions/obiformeditor/src/FormEditor.tsx +++ b/Composer/packages/extensions/obiformeditor/src/FormEditor.tsx @@ -4,32 +4,21 @@ import { JSONSchema6Definition, JSONSchema6 } from 'json-schema'; import merge from 'lodash.merge'; import get from 'lodash.get'; import isEqual from 'lodash.isequal'; -import { appschema } from 'shared'; +import { appschema, ShellData, ShellApi } from 'shared'; import Form from './Form'; import { uiSchema } from './schema/uischema'; import { getMemoryOptions } from './Form/utils'; -import { DialogInfo, FormMemory, FormData, ShellApi, EditorSchema, LuFile, LgFile } from './types'; +import { FormMemory, FormData } from './types'; const getType = (data: FormData): string | undefined => { return data.$type; }; -export interface FormEditorProps { - data: FormData; - currentDialog: DialogInfo; - dialogs: DialogInfo[]; - focusPath: string; - focusedEvent: string; - focusedSteps: string[]; - focusedTab?: string; - isRoot: boolean; - lgFiles: LgFile[]; - luFiles: LuFile[]; +export interface FormEditorProps extends ShellData { memory: FormMemory; onBlur?: () => void; onChange: (newData: object, updatePath?: string) => void; - schemas: EditorSchema; shellApi: ShellApi; } diff --git a/Composer/packages/extensions/obiformeditor/src/ObiFormEditor.tsx b/Composer/packages/extensions/obiformeditor/src/ObiFormEditor.tsx index 898042f11f..b691ac57ec 100644 --- a/Composer/packages/extensions/obiformeditor/src/ObiFormEditor.tsx +++ b/Composer/packages/extensions/obiformeditor/src/ObiFormEditor.tsx @@ -40,12 +40,12 @@ const ObiFormEditor: React.FC = props => { }; // only need to debounce the change handler when focusedSteps change - const debouncedOnChange = useMemo(() => debounce(onChange, 750), [props.focusedSteps[0]]); + const debouncedOnChange = useMemo(() => debounce(onChange, 500), [props.focusedSteps[0]]); const key = get(props.data, '$designer.id', props.focusPath); return ( - + diff --git a/Composer/packages/extensions/obiformeditor/src/schema/uischema.ts b/Composer/packages/extensions/obiformeditor/src/schema/uischema.ts index 6547910d85..7e65d2e36d 100644 --- a/Composer/packages/extensions/obiformeditor/src/schema/uischema.ts +++ b/Composer/packages/extensions/obiformeditor/src/schema/uischema.ts @@ -11,11 +11,8 @@ const promptFieldsSchemas = PROMPT_TYPES.reduce((schemas, type) => { }, {}); const triggerUiSchema = { - actions: { - 'ui:field': 'StepsField', - }, - 'ui:order': ['condition', '*', 'actions'], - 'ui:hidden': [...globalHidden], + 'ui:order': ['condition', '*'], + 'ui:hidden': ['actions', ...globalHidden], }; export const uiSchema: { [key in SDKTypes]?: UiSchema } = { @@ -23,14 +20,8 @@ export const uiSchema: { [key in SDKTypes]?: UiSchema } = { recognizer: { 'ui:field': 'RecognizerField', }, - triggers: { - 'ui:field': 'RulesField', - }, - actions: { - 'ui:field': 'StepsField', - }, 'ui:order': ['recognizer', 'triggers', '*'], - 'ui:hidden': ['autoEndDialog', 'generator', ...globalHidden], + 'ui:hidden': ['triggers', 'autoEndDialog', 'generator', ...globalHidden], }, [SDKTypes.BeginDialog]: { dialog: { @@ -53,16 +44,12 @@ export const uiSchema: { [key in SDKTypes]?: UiSchema } = { }, }, [SDKTypes.Foreach]: { - actions: { - 'ui:field': 'StepsField', - }, 'ui:order': ['itemsProperty', 'actions', '*'], + 'ui:hidden': ['actions'], }, [SDKTypes.ForeachPage]: { - actions: { - 'ui:field': 'StepsField', - }, 'ui:order': ['itemsProperty', 'pageSize', 'actions', '*'], + 'ui:hidden': ['actions'], }, [SDKTypes.HttpRequest]: { body: { @@ -71,13 +58,7 @@ export const uiSchema: { [key in SDKTypes]?: UiSchema } = { 'ui:order': ['method', 'url', 'body', 'property', 'responseTypes', 'headers', '*'], }, [SDKTypes.IfCondition]: { - actions: { - 'ui:field': 'StepsField', - }, - elseActions: { - 'ui:field': 'StepsField', - }, - 'ui:hidden': [...globalHidden], + 'ui:hidden': ['actions', 'elseActions', ...globalHidden], }, [SDKTypes.OnActivity]: { ...triggerUiSchema, @@ -110,11 +91,8 @@ export const uiSchema: { [key in SDKTypes]?: UiSchema } = { intent: { 'ui:widget': 'IntentWidget', }, - actions: { - 'ui:field': 'StepsField', - }, 'ui:order': ['intent', 'condition', 'entities', '*'], - 'ui:hidden': [...globalHidden], + 'ui:hidden': ['actions', ...globalHidden], }, [SDKTypes.OnInvokeActivity]: { ...triggerUiSchema, @@ -165,10 +143,7 @@ export const uiSchema: { [key in SDKTypes]?: UiSchema } = { cases: { 'ui:field': 'CasesField', }, - default: { - 'ui:field': 'StepsField', - }, - 'ui:hidden': [...globalHidden], + 'ui:hidden': ['default', ...globalHidden], }, [SDKTypes.SendActivity]: { activity: { diff --git a/Composer/packages/extensions/obiformeditor/src/types.ts b/Composer/packages/extensions/obiformeditor/src/types.ts index 19d0875ebe..eb3321050d 100644 --- a/Composer/packages/extensions/obiformeditor/src/types.ts +++ b/Composer/packages/extensions/obiformeditor/src/types.ts @@ -15,103 +15,7 @@ export interface FormMemory { [MemoryScope.turn]: MemoryBag; } -export interface DialogInfo { - id: string; - isRoot: boolean; - displayName: string; - content: MicrosoftAdaptiveDialog; - diagnostics: string[]; - referredDialogs: string[]; - lgFile: string; - luFile: string; - luIntents: string[]; - lgTemplates: string[]; - relativePath: string; -} - -export interface Intent { - name: string; -} - -export interface Utterance { - intent: string; - text: string; -} - -export interface LuFile { - id: string; - relativePath: string; - content: string; - parsedContent: { - LUISJsonStructure: { - intents: Intent[]; - utterances: Utterance[]; - }; - }; -} - -export interface CodeRange { - startLineNumber: number; - endLineNumber: number; -} - -export interface LgFile { - id: string; - relativePath: string; - content: string; - templates: [LgTemplate]; -} -export interface LgTemplate { - Name: string; - Body: string; - Parameters: string; - Range: CodeRange; -} - export interface FormData { $type?: string; [key: string]: any; } - -export interface ShellApi { - getState: () => Promise; - getDialogs: () => Promise; - saveData: (newData: T, updatePath: string) => Promise; - navTo: (path: string) => Promise; - onFocusSteps: (stepIds: string[], focusedTab?: string) => Promise; - onFocusEvent: (eventId: string) => Promise; - createLuFile: (id: string) => Promise; - updateLuFile: (id: string, content: string) => Promise; - updateLgFile: (id: string, content: string) => Promise; - getLgTemplates: (id: string) => Promise; - createLgTemplate: (id: string, template: LgTemplate, position: number) => Promise; - updateLgTemplate: (id: string, templateName: string, templateStr: string) => Promise; - removeLgTemplate: (id: string, templateName: string) => Promise; - createDialog: () => Promise; - validateExpression: (expression?: string) => Promise; -} -export interface EditorSchema { - editor: { - content?: { - fieldTemplateOverrides?: any; - SDKOverrides?: any; - }; - }; -} - -declare module 'json-schema' { - interface OBISchema { - $role?: string; - $type?: string; - $copy?: string; - $id?: string; - $designer?: { - [key: string]: any; - }; - } - - interface JSONSchema6 extends OBISchema { - title?: string; - __additional_property?: boolean; - } -} diff --git a/Composer/packages/lib/shared/src/types/index.ts b/Composer/packages/lib/shared/src/types/index.ts new file mode 100644 index 0000000000..353ecb5a30 --- /dev/null +++ b/Composer/packages/lib/shared/src/types/index.ts @@ -0,0 +1,3 @@ +export * from './schema'; +export * from './sdk'; +export * from './shell'; diff --git a/Composer/packages/lib/shared/src/types.ts b/Composer/packages/lib/shared/src/types/schema.ts similarity index 100% rename from Composer/packages/lib/shared/src/types.ts rename to Composer/packages/lib/shared/src/types/schema.ts index c2c25ba691..e6b5ef2d0c 100644 --- a/Composer/packages/lib/shared/src/types.ts +++ b/Composer/packages/lib/shared/src/types/schema.ts @@ -1,21 +1,5 @@ import { JSONSchema6 } from 'json-schema'; -export interface OBISchema extends JSONSchema6 { - $schema?: string; - $role?: string; - $type?: SDKTypes; - $copy?: string; - $id?: string; - $designer?: { - id: string; - [key: string]: any; - }; - description?: string; - definitions?: any; - title?: string; - __additional_property?: boolean; -} - // All of the known SDK types. Update this list when we take a schema update. // To get this list copy the output of the following commands in a node repl from the project root: @@ -113,3 +97,19 @@ export enum SDKTypes { TrueSelector = 'Microsoft.TrueSelector', UrlEntityRecognizer = 'Microsoft.UrlEntityRecognizer', } + +export interface OBISchema extends JSONSchema6 { + $schema?: string; + $role?: string; + $type?: SDKTypes; + $copy?: string; + $id?: string; + $designer?: { + id: string; + [key: string]: any; + }; + description?: string; + definitions?: any; + title?: string; + __additional_property?: boolean; +} diff --git a/Composer/packages/extensions/obiformeditor/src/schema/types.d.ts b/Composer/packages/lib/shared/src/types/sdk.ts similarity index 82% rename from Composer/packages/extensions/obiformeditor/src/schema/types.d.ts rename to Composer/packages/lib/shared/src/types/sdk.ts index 5abf2be24e..c650ee86e6 100644 --- a/Composer/packages/extensions/obiformeditor/src/schema/types.d.ts +++ b/Composer/packages/lib/shared/src/types/sdk.ts @@ -16,21 +16,11 @@ type MicrosoftIActivityTemplate = string; type MicrosoftIExpression = string; -interface IBaseDialog extends BaseSchema { - /** This is that will be passed in as InputProperty and also set as the OutputProperty */ - property?: string; - /** This defines properties which be passed as arguments to this dialog */ - inputProperties?: { [x: string]: string }; - /** This is the property which the EndDialog(result) will be set to when EndDialog() is called */ - outputProperty?: string; -} - interface OpenObject { [x: string]: T; } -type CardAction = OpenObject; -interface IChoice { +export interface IChoice { /** the value to return when selected. */ value?: string; /** Card action for the choice */ @@ -41,7 +31,7 @@ interface IChoice { type IListStyle = 'None' | 'Auto' | 'Inline' | 'List' | 'SuggestedAction' | 'HeroCard'; -interface IChoiceOption { +export interface IChoiceOption { /** Character used to separate individual choices when there are more than 2 choices */ inlineSeparator?: string; /** Separator inserted between the choices when their are only 2 choices */ @@ -52,7 +42,7 @@ interface IChoiceOption { includeNumbers?: boolean; } -interface IConfirmChoice { +export interface IConfirmChoice { /** the value to return when selected. */ value?: string; /** Card action for the choice */ @@ -61,7 +51,7 @@ interface IConfirmChoice { synonyms?: string[]; } -interface IRecognizerOption { +export interface IRecognizerOption { /** If true, the choices value field will NOT be search over */ noValue?: boolean; } @@ -70,7 +60,7 @@ interface IRecognizerOption { * Inputs */ -interface InputDialog extends BaseSchema { +export interface InputDialog extends BaseSchema { /** (Optional) id for the dialog */ id: string; /** The message to send to as prompt for this input. */ @@ -96,13 +86,13 @@ interface InputDialog extends BaseSchema { } /** This represents a dialog which gathers an attachment such as image or music */ -interface AttachmentInput extends Partial { +export interface AttachmentInput extends Partial { /** The attachment output format. */ outputFormat?: 'all' | 'first'; } /** This represents a dialog which gathers a choice responses */ -interface ChoiceInput extends Partial { +export interface ChoiceInput extends Partial { /** The output format. */ outputFormat?: 'value' | 'index'; choices?: IChoice[]; @@ -117,7 +107,7 @@ interface ChoiceInput extends Partial { } /** This represents a dialog which gathers a yes/no style responses */ -interface ConfirmInput extends Partial { +export interface ConfirmInput extends Partial { outputFormat: undefined; /** The prompts default locale that should be recognized. */ defaultLocale?: string; @@ -127,12 +117,12 @@ interface ConfirmInput extends Partial { confirmChoices?: IConfirmChoice[]; } -interface DateTimeInput extends Partial { +export interface DateTimeInput extends Partial { outputFormat: undefined; /** The prompts default locale that should be recognized. */ defaultLocale?: string; } -interface NumberInput extends Partial { +export interface NumberInput extends Partial { /** The output format. */ outputFormat?: 'float' | 'integer'; /** The prompts default locale that should be recognized. */ @@ -140,18 +130,24 @@ interface NumberInput extends Partial { } /** This represents a dialog which gathers a text from the user */ -interface TextInput extends Partial { +export interface TextInput extends Partial { /** The output format. */ outputFormat?: 'none' | 'trim' | 'lowercase' | 'uppercase'; } -type MicrosoftInputDialog = AttachmentInput | ChoiceInput | ConfirmInput | DateTimeInput | NumberInput | TextInput; +export type MicrosoftInputDialog = + | AttachmentInput + | ChoiceInput + | ConfirmInput + | DateTimeInput + | NumberInput + | TextInput; /** * Recognizers */ -interface LuisRecognizer extends BaseSchema { +export interface LuisRecognizer extends BaseSchema { applicationId: string; endpoint: string; endpointKey: string; @@ -163,18 +159,18 @@ interface IntentPattern { pattern: string; } -interface RegexRecognizer extends BaseSchema { +export interface RegexRecognizer extends BaseSchema { /** Pattern->Intents mappings */ intents: IntentPattern[]; } -type MicrosoftIRecognizer = LuisRecognizer | RegexRecognizer | string; +export type MicrosoftIRecognizer = LuisRecognizer | RegexRecognizer | string; /** * Rules */ -declare enum DialogEvent { +export enum DialogEvent { beginDialog = 'beginDialog', consultDialog = 'consultDialog', cancelDialog = 'cancelDialog', @@ -195,7 +191,7 @@ interface RuleBase extends BaseSchema { } /** This defines the steps to take when an Intent is recognized (and optionally entities) */ -interface OnIntent extends RuleBase { +export interface OnIntent extends RuleBase { /** Intent name to trigger on */ intent: string; /** The entities required to trigger this rule */ @@ -203,15 +199,15 @@ interface OnIntent extends RuleBase { } /** Defines a sequence of steps to take if there is no other trigger or plan operating */ -interface OnUnknownIntent extends RuleBase {} +export interface OnUnknownIntent extends RuleBase {} -type ITriggerCondition = OnIntent | OnUnknownIntent; +export type ITriggerCondition = OnIntent | OnUnknownIntent; /** * Conversational Flow and Dialog Management */ -interface CaseCondition { +export interface CaseCondition { /** Value which must match the condition property */ value: string; /** Steps to execute if case is equal to condition */ @@ -219,7 +215,7 @@ interface CaseCondition { } /** Step which conditionally decides which step to execute next. */ -interface SwitchCondition extends BaseSchema { +export interface SwitchCondition extends BaseSchema { /** Expression to evaluate to switch on. */ condition?: string; /** Cases to evaluate against condition */ @@ -229,7 +225,7 @@ interface SwitchCondition extends BaseSchema { } /** Flexible, data driven dialog that can adapt to the conversation. */ -interface MicrosoftAdaptiveDialog extends BaseSchema { +export interface MicrosoftAdaptiveDialog extends BaseSchema { /** Optional dialog ID. */ id?: string; /** If this is true the dialog will automatically end when there are no more steps to run. If this is false it is the responsbility of the author to call EndDialog at an appropriate time. */ @@ -243,7 +239,7 @@ interface MicrosoftAdaptiveDialog extends BaseSchema { } /* Union of components which implement the IDialog interface */ -type MicrosoftIDialog = +export type MicrosoftIDialog = | ChoiceInput | ConfirmInput | MicrosoftIRecognizer diff --git a/Composer/packages/lib/shared/src/types/shell.ts b/Composer/packages/lib/shared/src/types/shell.ts new file mode 100644 index 0000000000..6ae47b835f --- /dev/null +++ b/Composer/packages/lib/shared/src/types/shell.ts @@ -0,0 +1,117 @@ +import { MicrosoftAdaptiveDialog } from './sdk'; + +export interface ITrigger { + id: string; + displayName: string; + type: string; + isIntent: boolean; +} + +export interface DialogInfo { + content: MicrosoftAdaptiveDialog; + diagnostics: string[]; + displayName: string; + id: string; + isRoot: boolean; + lgFile: string; + lgTemplates: string[]; + luFile: string; + luIntents: string[]; + referredDialogs: string[]; + relativePath: string; + triggers: ITrigger[]; +} + +export interface EditorSchema { + content?: { + fieldTemplateOverrides?: any; + SDKOverrides?: any; + }; +} + +export interface BotSchemas { + editor: EditorSchema; + sdk?: any; + diagnostics?: any[]; +} + +export interface Intent { + name: string; +} + +export interface Utterance { + intent: string; + text: string; +} + +export interface LuDiagnostic { + text: string; +} + +export interface LuFile { + id: string; + relativePath: string; + content: string; + parsedContent: { + LUISJsonStructure: { + intents: Intent[]; + utterances: Utterance[]; + }; + }; + diagnostics: LuDiagnostic[]; +} + +export interface LgFile { + id: string; + relativePath: string; + content: string; + templates: [LgTemplate]; +} + +export interface CodeRange { + startLineNumber: number; + endLineNumber: number; +} + +export interface LgTemplate { + Name: string; + Body: string; + Parameters: string; + Range: CodeRange; +} + +export interface ShellData { + botName: string; + currentDialog: DialogInfo; + data: { + $type: string; + [key: string]: any; + }; + dialogId: string; + dialogs: DialogInfo[]; + focusedEvent: string; + focusedSteps: string[]; + focusedTab?: string; + focusPath: string; + lgFiles: LgFile[]; + luFiles: LuFile[]; + schemas: BotSchemas; +} + +export interface ShellApi { + getState: () => Promise; + getDialogs: () => Promise; + saveData: (newData: T, updatePath: string) => Promise; + navTo: (path: string) => Promise; + onFocusSteps: (stepIds: string[], focusedTab?: string) => Promise; + onFocusEvent: (eventId: string) => Promise; + createLuFile: (id: string) => Promise; + updateLuFile: (id: string, content: string) => Promise; + updateLgFile: (id: string, content: string) => Promise; + getLgTemplates: (id: string) => Promise; + createLgTemplate: (id: string, template: LgTemplate, position: number) => Promise; + updateLgTemplate: (id: string, templateName: string, templateStr: string) => Promise; + removeLgTemplate: (id: string, templateName: string) => Promise; + createDialog: () => Promise; + validateExpression: (expression?: string) => Promise; +} diff --git a/Composer/yarn.lock b/Composer/yarn.lock index dc786d6d67..765beb7573 100644 --- a/Composer/yarn.lock +++ b/Composer/yarn.lock @@ -2253,6 +2253,13 @@ resolved "https://registry.yarnpkg.com/@types/jwt-decode/-/jwt-decode-2.2.1.tgz#afdf5c527fcfccbd4009b5fd02d1e18241f2d2f2" integrity sha512-aWw2YTtAdT7CskFyxEX2K21/zSDStuf/ikI3yBqmwpwJF0pS+/IX5DWv+1UFffZIbruP6cnT9/LAJV1gFwAT1A== +"@types/lodash.clonedeep@^4.5.6": + version "4.5.6" + resolved "https://registry.yarnpkg.com/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz#3b6c40a0affe0799a2ce823b440a6cf33571d32b" + integrity sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA== + dependencies: + "@types/lodash" "*" + "@types/lodash.debounce@^4.0.6": version "4.0.6" resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60" @@ -2262,15 +2269,22 @@ "@types/lodash.find@^4.6.6": version "4.6.6" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@types/lodash.find/-/@types/lodash.find-4.6.6.tgz#9b298092ff15642ddf0c6b04d9e0a2f3c49ac845" - integrity sha1-mymAkv8VZC3fDGsE2eCi88SayEU= + resolved "https://registry.yarnpkg.com/@types/lodash.find/-/lodash.find-4.6.6.tgz#9b298092ff15642ddf0c6b04d9e0a2f3c49ac845" + integrity sha512-rpfXWzKWaw12XMcdQYA7f0xVmkXwJkhPPON69pGVFNYF6/66CduGyLiYnoZk1xBOvGwMnmyrCZ/yJewPO4OMeg== + dependencies: + "@types/lodash" "*" + +"@types/lodash.findindex@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.findindex/-/lodash.findindex-4.6.6.tgz#36079decaaab8bcdcfb86cd5f3c0f20aca908691" + integrity sha512-quPh7tw70yhryaubH6wBvgIQgeU1PFjdoT4eaW6WCKzjIlxgImLKIv4bvJhMTUlRkMgf5VAfECKKXKuB8cexgw== dependencies: "@types/lodash" "*" "@types/lodash.get@^4.4.6": version "4.4.6" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@types/lodash.get/-/@types/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240" - integrity sha1-DHrFYkPa4Pnwmrb3WylHHi53ckA= + resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240" + integrity sha512-E6zzjR3GtNig8UJG/yodBeJeIOtgPkMgsLjDU3CbgCAPC++vJ0eCMnJhVpRZb/ENqEFlov1+3K9TKtY4UdWKtQ== dependencies: "@types/lodash" "*" @@ -2283,8 +2297,8 @@ "@types/lodash.isequal@^4.5.5": version "4.5.5" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/@types/lodash.isequal/-/@types/lodash.isequal-4.5.5.tgz#4fed1b1b00bef79e305de0352d797e9bb816c8ff" - integrity sha1-T+0bGwC+954wXeA1LXl+m7gWyP8= + resolved "https://registry.yarnpkg.com/@types/lodash.isequal/-/lodash.isequal-4.5.5.tgz#4fed1b1b00bef79e305de0352d797e9bb816c8ff" + integrity sha512-4IKbinG7MGP131wRfceK6W4E/Qt3qssEFLF30LnJbjYiSfHGGRU/Io8YxXrZX109ir+iDETC8hw8QsDijukUVg== dependencies: "@types/lodash" "*" @@ -2316,6 +2330,13 @@ dependencies: "@types/lodash" "*" +"@types/lodash.set@^4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@types/lodash.set/-/lodash.set-4.3.6.tgz#33e635c2323f855359225df6a5c8c6f1f1908264" + integrity sha512-ZeGDDlnRYTvS31Laij0RsSaguIUSBTYIlJFKL3vm3T2OAZAQj2YpSvVWJc0WiG4jqg9fGX6PAPGvDqBcHfSgFg== + dependencies: + "@types/lodash" "*" + "@types/lodash.startcase@^4.4.6": version "4.4.6" resolved "https://registry.yarnpkg.com/@types/lodash.startcase/-/lodash.startcase-4.4.6.tgz#0e91c90004c87f738ae495a24e7b858cdecf0e2f" @@ -5089,7 +5110,6 @@ botbuilder-expression-parser@^4.5.9: "botbuilder-lg@https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.12.tgz": version "4.6.12" - uid "9be19953ecbc5eca84f7dacfbda55567d7d197d8" resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/botbuilder-lg/-/4.6.12.tgz#9be19953ecbc5eca84f7dacfbda55567d7d197d8" dependencies: antlr4ts "0.5.0-alpha.1" @@ -12598,7 +12618,7 @@ lodash.isboolean@^3.0.3: lodash.isequal@^4.5.0: version "4.5.0" - resolved "https://botbuilder.myget.org/F/botbuilder-declarative/npm/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= lodash.isinteger@^4.0.4: