diff --git a/Composer/packages/client/src/ShellApi.ts b/Composer/packages/client/src/ShellApi.ts index 45cd42ad89..60e8e8e0d0 100644 --- a/Composer/packages/client/src/ShellApi.ts +++ b/Composer/packages/client/src/ShellApi.ts @@ -75,7 +75,7 @@ export const ShellApi: React.FC = () => { const updateDialog = actions.updateDialog; const updateLuFile = actions.updateLuFile; //if debounced, error can't pass to form const updateLgFile = actions.updateLgFile; - const updateLgTemplate = useDebouncedFunc(actions.updateLgTemplate); + const updateLgTemplate = actions.updateLgTemplate; const createLuFile = actions.createLuFile; const createLgFile = actions.createLgFile; @@ -102,6 +102,7 @@ export const ShellApi: React.FC = () => { apiClient.registerApi('createLuFile', ({ id, content }, event) => fileHandler(LU, CREATE, { id, content }, event)); apiClient.registerApi('createLgFile', ({ id, content }, event) => fileHandler(LU, CREATE, { id, content }, event)); apiClient.registerApi('updateLgTemplate', updateLgTemplateHandler); + apiClient.registerApi('removeLgTemplate', removeLgTemplateHandler); apiClient.registerApi('getLgTemplates', ({ id }, event) => getLgTemplates({ id }, event)); apiClient.registerApi('navTo', navTo); apiClient.registerApi('onFocusEvent', focusEvent); @@ -244,7 +245,7 @@ export const ShellApi: React.FC = () => { * * @param {*} event */ - async function updateLgTemplateHandler({ id, templateName, template }, event) { + function updateLgTemplateHandler({ id, templateName, template }, event) { if (isEventSourceValid(event) === false) return false; const file = lgFiles.find(file => file.id === id); if (!file) throw new Error(`lg file ${id} not found`); @@ -255,13 +256,25 @@ export const ShellApi: React.FC = () => { const content = updateTemplateInContent({ content: file.content, templateName, template }); checkLgContent(content); - await updateLgTemplate({ + return updateLgTemplate({ file, templateName, template, }); } + function removeLgTemplateHandler({ id, templateName }, event) { + if (isEventSourceValid(event) === false) return false; + const file = lgFiles.find(file => file.id === id); + if (!file) throw new Error(`lg file ${id} not found`); + if (!templateName) throw new Error(`templateName is missing or empty`); + + return actions.removeLgTemplate({ + file, + templateName, + }); + } + async function fileHandler(fileTargetType, fileChangeType, { id, content }, event) { if (isEventSourceValid(event) === false) return false; diff --git a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx index 9bcfdff186..c76f10cf05 100644 --- a/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx +++ b/Composer/packages/extensions/obiformeditor/src/Form/widgets/LgEditorWidget.tsx @@ -1,5 +1,4 @@ -import React from 'react'; -import { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { LgEditor } from 'code-editor'; import debounce from 'lodash.debounce'; @@ -30,41 +29,57 @@ interface LgEditorWidgetProps { export const LgEditorWidget: React.FC = props => { const { formContext, name, value, height = 250 } = props; - const lgId = `bfd${name}-${formContext.dialogId}`; const [errorMsg, setErrorMsg] = useState(''); - + const lgId = `bfd${name}-${formContext.dialogId}`; const lgFileId = formContext.currentDialog.lgFile || 'common'; const lgFile = formContext.lgFiles.find(file => file.id === lgFileId); - const template = lgFile - ? lgFile.templates.find(template => { - return template.Name === lgId; - }) - : undefined; + + const updateLgTemplate = useMemo( + () => + debounce((body: string) => { + formContext.shellApi + .updateLgTemplate(lgFileId, lgId, body) + .then(() => setErrorMsg('')) + .catch(error => setErrorMsg(error)); + }, 500), + [lgId, lgFileId] + ); + + const template = (lgFile && + lgFile.templates.find(template => { + return template.Name === lgId; + })) || { + Name: lgId, + Body: getInitialTemplate(name, value), + Parameters: '', + Range: { + startLineNumber: 1, + endLineNumber: 1, + }, + }; // template body code range - const codeRange = template - ? { - startLineNumber: template.Range.startLineNumber + 1, // cut template name - endLineNumber: template.Range.endLineNumber, - } - : -1; + const codeRange = { + startLineNumber: 2, + endLineNumber: template.Body.split('\n').length + 1, + }; - let content = lgFile ? lgFile.content : ''; - if (!template) { - const newTemplateBody = getInitialTemplate(name, value || '-'); - content += ['\n', '# ' + lgId, newTemplateBody].join('\n'); - } + const [localContent, setLocalContent] = useState(template.Body); + const content = `#${template.Name}\n${localContent}`; - const onChange = debounce((data): void => { - // hit the lg api and replace it's Body with data + const onChange = (newTemplate: string) => { + const body = newTemplate.slice(newTemplate.indexOf('\n') + 1); if (formContext.dialogId) { - formContext.shellApi - .updateLgFile(lgFileId, data) - .then(() => setErrorMsg('')) - .catch(error => setErrorMsg(error)); + if (body) { + updateLgTemplate(body); + } else { + updateLgTemplate.flush(); + formContext.shellApi.removeLgTemplate(lgFileId, lgId); + } props.onChange(`[${lgId}]`); } - }, 200); + setLocalContent(body); + }; return ( here.', @@ -45,15 +46,28 @@ export function RichEditor(props: RichEditorProps) { return `${height}px`; }; + let borderColor = NeutralColors.gray120; + + if (hovered) { + borderColor = NeutralColors.gray160; + } + + if (isInvalid) { + borderColor = SharedColors.red20; + } + return (
setHovered(true)} + onMouseLeave={() => setHovered(false)} > {props.codeRange ? memoEditor : baseEditor}