From e32df0a20545b350d1bffede3c4c18d8eecc0052 Mon Sep 17 00:00:00 2001 From: allardy Date: Thu, 5 Sep 2019 15:37:18 -0400 Subject: [PATCH] feat(qna): added support for content elements --- modules/qna/package.json | 18 +- modules/qna/src/backend/api.ts | 36 +- modules/qna/src/backend/parsers.ts | 58 ---- modules/qna/src/backend/setup.ts | 19 +- modules/qna/src/backend/storage.ts | 8 +- modules/qna/src/backend/transfer.ts | 58 +++- modules/qna/src/backend/validation.ts | 5 + modules/qna/src/views/FormModal.jsx | 307 ------------------ modules/qna/src/views/full/ImportModal.tsx | 225 +++++++++++++ .../src/views/full/{index.jsx => index.tsx} | 275 ++++------------ modules/qna/src/views/full/style.scss | 6 + modules/qna/src/views/full/style.scss.d.ts | 1 + modules/qna/src/views/full/toaster.tsx | 24 ++ modules/qna/src/views/tsconfig.json | 17 + modules/qna/src/views/tslint.json | 10 + modules/qna/yarn.lock | 290 +++++++++-------- .../components/Content/CreateOrEditModal.js | 2 +- .../web/components/Content/Select/Widget.tsx | 38 +-- .../Content/Select/{index.js => index.tsx} | 46 ++- .../web/components/Content/Select/style.scss | 6 + .../components/Content/Select/style.scss.d.ts | 1 + .../components/Shared/ElementsList/index.jsx | 62 ---- .../components/Shared/ElementsList/index.tsx | 120 +++++++ .../components/Shared/ElementsList/style.scss | 35 +- .../Shared/ElementsList/style.scss.d.ts | 3 + 25 files changed, 840 insertions(+), 830 deletions(-) delete mode 100644 modules/qna/src/backend/parsers.ts delete mode 100644 modules/qna/src/views/FormModal.jsx create mode 100644 modules/qna/src/views/full/ImportModal.tsx rename modules/qna/src/views/full/{index.jsx => index.tsx} (63%) create mode 100644 modules/qna/src/views/full/toaster.tsx create mode 100644 modules/qna/src/views/tsconfig.json create mode 100644 modules/qna/src/views/tslint.json rename src/bp/ui-studio/src/web/components/Content/Select/{index.js => index.tsx} (92%) mode change 100755 => 100644 delete mode 100644 src/bp/ui-studio/src/web/components/Shared/ElementsList/index.jsx create mode 100644 src/bp/ui-studio/src/web/components/Shared/ElementsList/index.tsx diff --git a/modules/qna/package.json b/modules/qna/package.json index a2f075a8313..f7b176c2bf3 100644 --- a/modules/qna/package.json +++ b/modules/qna/package.json @@ -15,28 +15,26 @@ "react": "React", "react-dom": "ReactDOM", "react-bootstrap": "ReactBootstrap", - "botpress/elements-list": "ElementsList" + "botpress/elements-list": "ElementsList", + "botpress/utils": "BotpressUtils" } }, "devDependencies": { - "@babel/helpers": "^7.4.3", + "@blueprintjs/core": "^3.15.1", + "@types/react-bootstrap": "^0.32.19", + "@types/node": "^10.11.3", + "@types/react": "^16.8.17", + "@types/react-dom": "^16.8.4", "module-builder": "../../build/module-builder" }, "dependencies": { "axios": "^0.18.1", "bluebird": "^3.5.3", "classnames": "^2.2.6", - "csv-parse": "^2.5.0", - "iconv-lite": "^0.4.23", "joi": "^13.6.0", - "json2csv": "^4.3.5", "lodash": "^4.17.11", "moment": "^2.24.0", - "ms": "^2.1.1", - "multer": "^1.4.1", "nanoid": "^2.0.1", - "react-icons": "^3.7.0", - "react-select": "^1.2.1", - "yn": "^2.0.0" + "react-select": "^1.2.1" } } diff --git a/modules/qna/src/backend/api.ts b/modules/qna/src/backend/api.ts index fa9e1eb34f4..62bd3edbdb9 100644 --- a/modules/qna/src/backend/api.ts +++ b/modules/qna/src/backend/api.ts @@ -2,14 +2,12 @@ import * as sdk from 'botpress/sdk' import { validate } from 'joi' import _ from 'lodash' import moment from 'moment' -import multer from 'multer' import nanoid from 'nanoid' -import yn from 'yn' -import { QnaEntry, QnaItem } from './qna' +import { QnaEntry } from './qna' import Storage from './storage' -import { importQuestions, prepareExport } from './transfer' -import { QnaDefSchema, QnaItemArraySchema } from './validation' +import { importQuestions, prepareExport, prepareImport } from './transfer' +import { QnaDefSchema } from './validation' export default async (bp: typeof sdk, botScopedStorage: Map) => { const jsonUploadStatuses = {} @@ -93,20 +91,33 @@ export default async (bp: typeof sdk, botScopedStorage: Map) => router.get('/export', async (req, res) => { const storage = botScopedStorage.get(req.params.botId) - const data: string = await prepareExport(storage) + const data: string = await prepareExport(storage, bp) + res.setHeader('Content-Type', 'application/json') res.setHeader('Content-disposition', `attachment; filename=qna_${moment().format('DD-MM-YYYY')}.json`) res.end(data) }) - const upload = multer() - router.post('/import', upload.single('json'), async (req, res) => { + router.post('/import/summary', async (req, res) => { const storage = botScopedStorage.get(req.params.botId) + const cmsIds = await storage.getAllContentElementIds() + const importData = await prepareImport(JSON.parse(req.body.fileContent)) + + res.send({ + qnaCount: await storage.count(), + cmsCount: (cmsIds && cmsIds.length) || 0, + fileQnaCount: (importData.questions && importData.questions.length) || 0, + fileCmsCount: (importData.content && importData.content.length) || 0 + }) + }) + router.post('/import', async (req, res) => { const uploadStatusId = nanoid() - res.end(uploadStatusId) + res.send(uploadStatusId) + + const storage = botScopedStorage.get(req.params.botId) - if (yn(req.body.isReplace)) { + if (req.body.importAction === 'clear_insert') { updateUploadStatus(uploadStatusId, 'Deleting existing questions') const questions = await storage.fetchQNAs() @@ -115,10 +126,9 @@ export default async (bp: typeof sdk, botScopedStorage: Map) => } try { - const parsedJson: any = JSON.parse(req.file.buffer) - const questions = (await validate(parsedJson, QnaItemArraySchema)) as QnaItem[] + const importData = await prepareImport(JSON.parse(req.body.fileContent)) - await importQuestions(questions, storage, updateUploadStatus, uploadStatusId) + await importQuestions(importData, storage, bp, updateUploadStatus, uploadStatusId) updateUploadStatus(uploadStatusId, 'Completed') } catch (e) { bp.logger.attachError(e).error('JSON Import Failure') diff --git a/modules/qna/src/backend/parsers.ts b/modules/qna/src/backend/parsers.ts deleted file mode 100644 index 7380aecfc2e..00000000000 --- a/modules/qna/src/backend/parsers.ts +++ /dev/null @@ -1,58 +0,0 @@ -import parseCsvToJson from 'csv-parse/lib/sync' -import get from 'lodash/get' - -const parseFlow = str => { - const [redirectFlow, redirectNode = ''] = str.split('#') - return { redirectFlow, redirectNode } -} - -export const jsonParse = (jsonContent, options) => - jsonContent.map(({ questions, answer: instruction, answer2, action, category }, i) => { - if (!['text', 'redirect', 'text_redirect'].includes(action)) { - throw new Error( - `Failed to process CSV-row ${i + 1}: action should be either "text", "redirect" or "text_redirect"` - ) - } - - let redirectInstruction = undefined - let textAnswer = '' - const { hasCategory } = options - - if (action === 'text') { - textAnswer = instruction - } else if (action === 'redirect') { - redirectInstruction = instruction - } else if (action === 'text_redirect') { - textAnswer = instruction - redirectInstruction = answer2 - } - - const flowParams = redirectInstruction ? parseFlow(redirectInstruction) : { redirectFlow: '', redirectNode: '' } - const categoryWrapper = hasCategory ? { category } : {} - return { questions, action, answer: textAnswer, ...flowParams, ...categoryWrapper } - }) - -export const csvParse = (csvContent, options) => { - const { hasCategory } = options - - const mergeRows = (acc, { question, answer, answer2, category, action }) => { - const [prevRow] = acc.slice(-1) - const isSameAnswer = prevRow && (prevRow.answer === answer && (!answer2 || answer2 === prevRow.answer2)) - if (isSameAnswer) { - return [...acc.slice(0, acc.length - 1), { ...prevRow, questions: [...prevRow.questions, question] }] - } - const categoryWrapper = hasCategory ? { category } : {} - return [...acc, { answer, answer2, action, ...categoryWrapper, questions: [question] }] - } - const categoryWrapper = hasCategory ? ['category'] : [] - const rows = parseCsvToJson(csvContent, { - columns: ['question', 'action', 'answer', 'answer2', ...categoryWrapper] - }).reduce(mergeRows, []) - - // We trim the header if detected in the first row - if (get(rows, '0.action') === 'action') { - rows.splice(0, 1) - } - - return jsonParse(rows, options) -} diff --git a/modules/qna/src/backend/setup.ts b/modules/qna/src/backend/setup.ts index b264d6dc5ab..a131113fe77 100644 --- a/modules/qna/src/backend/setup.ts +++ b/modules/qna/src/backend/setup.ts @@ -30,7 +30,7 @@ export const initModule = async (bp: typeof sdk, botScopedStorage: Map { + const getAlternativeAnswer = (qnaEntry: QnaEntry, lang: string): string => { const randomIndex = Math.floor(Math.random() * qnaEntry.answers[lang].length) return qnaEntry.answers[lang][randomIndex] } @@ -50,12 +50,21 @@ export const initModule = async (bp: typeof sdk, botScopedStorage: Map export default class Storage { private bp: typeof sdk private config - private botId: string + public botId: string private categories: string[] constructor(bp: typeof sdk, config, botId) { @@ -268,6 +268,12 @@ export default class Storage { return { items, count } } + async getAllContentElementIds(list?: QnaItem[]): Promise { + const qnas = list || (await this.fetchQNAs()) + const allAnswers = _.flatMap(qnas, qna => _.flatMap(Object.keys(qna.data.answers), lang => qna.data.answers[lang])) + return _.uniq(_.filter(allAnswers, x => _.isString(x) && x.startsWith('#!'))) + } + async count() { const questions = await this.fetchQNAs() return questions.length diff --git a/modules/qna/src/backend/transfer.ts b/modules/qna/src/backend/transfer.ts index da0f2e072d8..6fbf121c482 100644 --- a/modules/qna/src/backend/transfer.ts +++ b/modules/qna/src/backend/transfer.ts @@ -1,9 +1,52 @@ -import { QnaItem, QnaEntry } from './qna' +import * as sdk from 'botpress/sdk' +import { validate } from 'joi' +import _ from 'lodash' + +import { QnaEntry, QnaItem } from './qna' import Storage from './storage' +import { QnaItemArraySchema, QnaItemCmsArraySchema } from './validation' + +const debug = DEBUG('qna:import') + +type ContentData = Pick + +interface ImportData { + questions?: QnaItem[] + content?: ContentData[] +} + +export const prepareImport = async (parsedJson: any): Promise => { + try { + const result = (await validate(parsedJson, QnaItemCmsArraySchema)) as { cms: ContentData[]; qnas: QnaItem[] } + return { questions: result.qnas, content: result.cms } + } catch (err) { + debug(`New format doesn't match provided file: ${err.message}`) + } + + try { + const result = (await validate(parsedJson, QnaItemArraySchema)) as QnaItem[] + return { questions: result, content: undefined } + } catch (err) { + debug(`Old format doesn't match provided file: ${err.message}`) + } -export const importQuestions = async (questions: QnaItem[], storage, statusCallback, uploadStatusId) => { + return {} +} + +export const importQuestions = async (data: ImportData, storage, bp, statusCallback, uploadStatusId) => { statusCallback(uploadStatusId, 'Calculating diff with existing questions') + const { questions, content } = data + if (!questions) { + return + } + + if (content) { + for (const element of content) { + await bp.cms.createOrUpdateContentElement(storage.botId, element.contentType, element.formData, element.id) + } + } + const existingQuestionItems = (await (storage as Storage).fetchQNAs()).map(item => item.id) const itemsToSave = questions.filter(item => !existingQuestionItems.includes(item.id)) const entriesToSave = itemsToSave.map(q => q.data) @@ -16,7 +59,14 @@ export const importQuestions = async (questions: QnaItem[], storage, statusCallb }) } -export const prepareExport = async (storage: Storage) => { +export const prepareExport = async (storage: Storage, bp: typeof sdk) => { const qnas = await storage.fetchQNAs() - return JSON.stringify(qnas, undefined, 2) + const contentElementIds = await storage.getAllContentElementIds() + + const cms = await Promise.mapSeries(contentElementIds, async id => { + const data = await bp.cms.getContentElement(storage.botId, id.replace('#!', '')) + return _.pick(data, ['id', 'contentType', 'formData', 'previews']) + }) + + return JSON.stringify({ qnas, cms }, undefined, 2) } diff --git a/modules/qna/src/backend/validation.ts b/modules/qna/src/backend/validation.ts index 96e15203878..9cde6aa34d2 100644 --- a/modules/qna/src/backend/validation.ts +++ b/modules/qna/src/backend/validation.ts @@ -24,3 +24,8 @@ const QnaItemSchema = Joi.object().keys({ }) export const QnaItemArraySchema = Joi.array().items(QnaItemSchema) + +export const QnaItemCmsArraySchema = Joi.object().keys({ + qnas: Joi.array().items(QnaItemSchema), + cms: Joi.array().items(Joi.object()) +}) diff --git a/modules/qna/src/views/FormModal.jsx b/modules/qna/src/views/FormModal.jsx deleted file mode 100644 index 6883b1e13af..00000000000 --- a/modules/qna/src/views/FormModal.jsx +++ /dev/null @@ -1,307 +0,0 @@ -import React, { Component } from 'react' -import { FormControl, Button, Modal, Alert } from 'react-bootstrap' -import classnames from 'classnames' -import some from 'lodash/some' -import ElementsList from 'botpress/elements-list' -import _ from 'lodash' -import Select from 'react-select' -import style from './style.scss' - -const ACTIONS = { - TEXT: 'text', - REDIRECT: 'redirect', - TEXT_REDIRECT: 'text_redirect' -} - -export default class FormModal extends Component { - defaultState = { - item: { - answers: [], - questions: [], - redirectFlow: '', - redirectNode: '', - action: ACTIONS.TEXT, - category: 'global', - enabled: true - }, - invalidFields: { - category: false, - questions: false, - answer: false, - checkbox: false, - redirectFlow: false, - redirectNode: false - }, - errorMessage: undefined, - isText: true, - isRedirect: false - } - - state = this.defaultState - - componentDidUpdate(prevProps) { - const { id } = this.props - if (prevProps.id === id) { - return - } - if (!id) { - return this.setState(this.defaultState) - } - this.props.bp.axios.get(`/mod/qna/questions/${id}`).then(({ data: { data: item } }) => { - this.setState({ - item, - isRedirect: [ACTIONS.REDIRECT, ACTIONS.TEXT_REDIRECT].includes(item.action), - isText: [ACTIONS.TEXT, ACTIONS.TEXT_REDIRECT].includes(item.action) - }) - }) - } - - closeAndClear = () => { - this.props.closeQnAModal() - this.setState(this.defaultState) - } - - changeItemProperty = (key, value) => { - const { item } = this.state - - this.setState({ item: { ...item, [key]: value } }) - } - - handleSelect = key => selectedOption => - this.changeItemProperty(key, selectedOption ? selectedOption.value : selectedOption) - - changeItemAction = actionType => () => { - this.setState({ [actionType]: !this.state[actionType] }, () => { - const { isText, isRedirect } = this.state - const action = isText && isRedirect ? ACTIONS.TEXT_REDIRECT : isRedirect ? ACTIONS.REDIRECT : ACTIONS.TEXT - - this.changeItemProperty('action', action) - }) - } - - validateForm() { - const { item, isText, isRedirect } = this.state - const invalidFields = { - questions: !item.questions.length || !item.questions[0].length, - answer: isText && (!item.answers.length || !item.answers[0].length), - checkbox: !(isText || isRedirect), - redirectFlow: isRedirect && !item.redirectFlow, - redirectNode: isRedirect && !item.redirectNode - } - - this.setState({ invalidFields, errorMessage: undefined }) - return some(invalidFields) - } - - trimItemQuestions = questions => { - return _.uniq(questions.map(q => q.trim()).filter(q => q !== '')) - } - - onCreate = async questions => { - try { - await this.props.bp.axios.post('/mod/qna/questions', { ...this.state.item, questions }) - - this.props.fetchData() - this.closeAndClear() - } catch (error) { - this.setState({ errorMessage: _.get(error, 'response.data.full', error.message) }) - } - } - - onEdit = async questions => { - const { - page, - filters: { question, categories } - } = this.props - - try { - const { data } = await this.props.bp.axios.put( - `/mod/qna/questions/${this.props.id}`, - { ...this.state.item, questions }, - { - params: { ...page, question, categories: categories.map(({ value }) => value) } - } - ) - - this.props.updateQuestion(data) - this.closeAndClear() - } catch (error) { - this.setState({ errorMessage: _.get(error, 'response.data.full', error.message) }) - } - } - - alertMessage() { - const hasInvalidInputs = Object.values(this.state.invalidFields).find(Boolean) - - return ( -
- {this.state.invalidFields.checkbox && Action checkbox is required} - {hasInvalidInputs && Inputs are required.} - {this.state.errorMessage && {this.state.errorMessage}} -
- ) - } - - handleSubmit = event => { - event.preventDefault() - if (this.validateForm()) { - return - } - - const item = this.state.item - const questions = this.trimItemQuestions(item.questions) - - this.props.modalType === 'edit' ? this.onEdit(questions) : this.onCreate(questions) - } - - createAnswer = answer => { - const answers = [...this.state.item.answers, answer] - this.changeItemProperty('answers', answers) - } - - updateAnswer = (answer, index) => { - const answers = this.state.item.answers - if (answers[index]) { - answers[index] = answer - this.changeItemProperty('answers', answers) - } - } - - deleteAnswer = index => { - const answers = this.state.item.answers - if (answers[index]) { - answers.splice(index, 1) - this.changeItemProperty('answers', answers) - } - } - - render() { - const { - item: { redirectFlow }, - invalidFields - } = this.state - const { flows, flowsList, showQnAModal, categories, modalType } = this.props - const currentFlow = flows ? flows.find(({ name }) => name === redirectFlow) || { nodes: [] } : { nodes: [] } - const nodeList = currentFlow.nodes.map(({ name }) => ({ label: name, value: name })) - const isEdit = modalType === 'edit' - - return ( - -
- - {!isEdit ? 'Create a new' : 'Edit'} Q&A - - - - {this.alertMessage()} - {categories.length ? ( -
- Category - - - - - -
- -
-
-
and / or
-
-
-
-
- - - - - -
-
-
- - - - - - - - - ) - } -} diff --git a/modules/qna/src/views/full/ImportModal.tsx b/modules/qna/src/views/full/ImportModal.tsx new file mode 100644 index 00000000000..b9348620cd0 --- /dev/null +++ b/modules/qna/src/views/full/ImportModal.tsx @@ -0,0 +1,225 @@ +import { Button, Callout, Classes, Dialog, FileInput, FormGroup, Intent, Radio, RadioGroup } from '@blueprintjs/core' +import 'bluebird-global' +import _ from 'lodash' +import React, { FC, Fragment, useEffect, useState } from 'react' + +import { toastFailure, toastSuccess } from './toaster' + +const JSON_STATUS_POLL_INTERVAL = 1000 + +interface Props { + axios: any + onImportCompleted: () => void +} + +interface Summary { + qnaCount: number + cmsCount: number + fileQnaCount: number + fileCmsCount: number +} + +export const ImportModal: FC = props => { + const [filePath, setFilePath] = useState() + const [fileContent, setFileContent] = useState() + const [isLoading, setIsLoading] = useState(false) + const [isDialogOpen, setDialogOpen] = useState(false) + const [importAction, setImportAction] = useState('insert') + const [summary, setSummary] = useState() + const [statusId, setStatusId] = useState() + const [uploadStatus, setUploadStatus] = useState() + const [hasError, setHasError] = useState(false) + + useEffect(() => { + if (statusId) { + const interval = setInterval(async () => { + await updateUploadStatus() + }, JSON_STATUS_POLL_INTERVAL) + return () => clearInterval(interval) + } + }, [statusId]) + + const queryImportSummary = async () => { + setIsLoading(true) + try { + const { data } = await props.axios.post('/mod/qna/import/summary', { fileContent }) + + if (!data.fileQnaCount && !data.fileCmsCount) { + setUploadStatus(`We were not able to extract any data from your file. Please validate the format`) + setHasError(true) + } + + setSummary(data) + } catch (err) { + toastFailure(err) + } finally { + setIsLoading(false) + } + } + + const submitChanges = async () => { + setIsLoading(true) + + try { + const { data } = await props.axios.post('/mod/qna/import', { fileContent, importAction }) + setStatusId(data) + } catch (err) { + clearStatus() + setHasError(true) + toastFailure(err) + } + } + + const updateUploadStatus = async () => { + const { data: status } = await props.axios.get(`/mod/qna/json-upload-status/${statusId}`) + setUploadStatus(status) + + if (status === 'Completed') { + clearStatus() + closeDialog() + toastSuccess('Upload successful') + props.onImportCompleted() + } else if (status.startsWith('Error')) { + clearStatus() + setHasError(true) + } + } + + const readFile = event => { + const files = (event.target as HTMLInputElement).files + if (!files) { + return + } + + const fr = new FileReader() + fr.readAsBinaryString(files[0]) + fr.onload = loadedEvent => { + setFileContent(_.get(loadedEvent, 'target.result')) + } + setFilePath(files[0].name) + } + + const clearStatus = () => { + setStatusId(undefined) + setIsLoading(false) + } + + const closeDialog = () => { + clearState() + setDialogOpen(false) + } + + const clearState = () => { + setFilePath(undefined) + setFileContent(undefined) + setUploadStatus(undefined) + setStatusId(undefined) + setSummary(undefined) + setHasError(false) + } + + const renderUpload = () => { + return ( + +
+ Select your JSON file} + labelFor="input-archive" + helperText={ + + Select a JSON file exported from the module QNA. You will see a summary of modifications when clicking + on Next + + } + > + + +
+
+
+
+
+
+ ) + } + + const renderSummary = () => { + const { qnaCount, cmsCount, fileQnaCount, fileCmsCount } = summary + + return ( + +
+ {uploadStatus && ( + + {uploadStatus} + + )} + + {!uploadStatus && ( +
+

+ Your file contains {fileQnaCount} questions and {fileCmsCount} content + elements. +
+
+ The bot contains {qnaCount} questions and {cmsCount} content elements. +

+ +

+ setImportAction(e.target['value'])} + selectedValue={importAction} + > + + + +

+
+ )} +
+
+
+
+
+
+ ) + } + + return ( + + - - - ) - } - renderQnAHeader = () => ( -
- {this.renderSearch()} - {this.renderImportModal()} -
+
{this.renderSearch()}
- - + + + /> ) - renderVariationsOverlay = variations => { - return ( - -
    - {variations.map(variation => ( -
  • {variation}
  • - ))} -
-
- ) - } - - renderVariationsOverlayTrigger = elements => { + renderVariationsOverlay = elements => { return ( - elements.length > 1 && ( - - + !!elements.length && ( + + {elements.map(variation => ( +
  • + {variation.startsWith('#!') ? : variation} +
  • + ))} + + } + > +   ({elements.length}) -
    + ) ) } - renderMissingTranslationsOverlay = () => { - return ( - - Missing translation - - } - > - - - ) - } - renderRedirectInfo(redirectFlow, redirectNode) { if (!redirectFlow || !redirectNode) { return null @@ -430,7 +282,10 @@ export default class QnaAdmin extends Component { {!questions.length && ( )} {answers[0] && (
    A:
    {answers[0]}
    - {this.renderVariationsOverlayTrigger(answers)} + {this.renderVariationsOverlay(answers)}
    )} -
    +
    {this.renderRedirectInfo(item.redirectFlow, item.redirectNode)}
    {item.category ? ( @@ -524,7 +379,7 @@ export default class QnaAdmin extends Component { return ( ) @@ -550,13 +405,9 @@ export default class QnaAdmin extends Component { render() { return ( +
    - {this.renderQnAHeader()} {this.renderPagination()} diff --git a/modules/qna/src/views/full/style.scss b/modules/qna/src/views/full/style.scss index ad18aaf9b8e..cbe1e615ca2 100644 --- a/modules/qna/src/views/full/style.scss +++ b/modules/qna/src/views/full/style.scss @@ -461,3 +461,9 @@ $elementHeight: 40px; .variationDelete { display: none; } + +.tooltip { + li { + margin-left: -20px; + } +} diff --git a/modules/qna/src/views/full/style.scss.d.ts b/modules/qna/src/views/full/style.scss.d.ts index 3d035b69130..634548882a7 100644 --- a/modules/qna/src/views/full/style.scss.d.ts +++ b/modules/qna/src/views/full/style.scss.d.ts @@ -67,6 +67,7 @@ interface CssExports { 'searchField': string; 'strong': string; 'toggleButton': string; + 'tooltip': string; 'variation': string; 'variationDelete': string; } diff --git a/modules/qna/src/views/full/toaster.tsx b/modules/qna/src/views/full/toaster.tsx new file mode 100644 index 00000000000..ff52343e9b1 --- /dev/null +++ b/modules/qna/src/views/full/toaster.tsx @@ -0,0 +1,24 @@ +import { Intent, Position, Toaster } from '@blueprintjs/core' +import _ from 'lodash' +import React from 'react' + +export const toastSuccess = message => + Toaster.create({ className: 'recipe-toaster', position: Position.TOP_RIGHT }).show({ + message, + intent: Intent.SUCCESS, + timeout: 1000 + }) + +export const toastFailure = message => + Toaster.create({ className: 'recipe-toaster', position: Position.TOP_RIGHT }).show({ + message, + intent: Intent.DANGER, + timeout: 3000 + }) + +export const toastInfo = message => + Toaster.create({ className: 'recipe-toaster', position: Position.TOP_RIGHT }).show({ + message, + intent: Intent.PRIMARY, + timeout: 1000 + }) diff --git a/modules/qna/src/views/tsconfig.json b/modules/qna/src/views/tsconfig.json new file mode 100644 index 00000000000..5ffce4de4c9 --- /dev/null +++ b/modules/qna/src/views/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "esModuleInterop": true, + "allowJs": true, + "target": "es5", + "lib": ["es7", "dom", "esnext"], + "jsx": "react", + "typeRoots": ["../../node_modules/@types"], + "baseUrl": "./", + "paths": { + "botpress/ui": ["../../../../src/bp/ui-studio/src/web/components/Shared/Interface/typings.d.ts"], + "botpress/utils": ["../../../../src/bp/ui-studio/src/web/components/Shared/Utils/typings.d.ts"], + "botpress/sdk": ["../botpress.d.ts"] + } + } +} diff --git a/modules/qna/src/views/tslint.json b/modules/qna/src/views/tslint.json new file mode 100644 index 00000000000..3df0b7d7201 --- /dev/null +++ b/modules/qna/src/views/tslint.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../../tslint.json", + "rules": { + "quotemark": [false, "single", "avoid-escape"], + "no-null-keyword": false + }, + "linterOptions": { + "exclude": ["**/*.json"] + } +} diff --git a/modules/qna/yarn.lock b/modules/qna/yarn.lock index 052750441d3..85422f67087 100644 --- a/modules/qna/yarn.lock +++ b/modules/qna/yarn.lock @@ -232,7 +232,7 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" -"@babel/helpers@^7.4.3", "@babel/helpers@^7.4.4": +"@babel/helpers@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5" integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A== @@ -734,6 +734,13 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript" "^7.3.2" +"@babel/runtime@^7.1.2": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" + integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.1.0", "@babel/template@^7.4.4": version "7.4.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.4.4.tgz#f4b88d1225689a08f5bc3a17483545be9e4ed237" @@ -767,6 +774,67 @@ lodash "^4.17.11" to-fast-properties "^2.0.0" +"@blueprintjs/core@^3.15.1": + version "3.18.0" + resolved "https://registry.yarnpkg.com/@blueprintjs/core/-/core-3.18.0.tgz#8835ead10460e6535076465865d2ad7600ae1a07" + integrity sha512-dr3A6uhpAAWmf5muY6PFQp5EgEzinRLZa/TGQMA05q1P2xrOn/LYlsqJWJBUJ3j9tmh1RP8VPeu1HmosQb3J7w== + dependencies: + "@blueprintjs/icons" "^3.10.0" + "@types/dom4" "^2.0.1" + classnames "^2.2" + dom4 "^2.1.5" + normalize.css "^8.0.1" + popper.js "^1.15.0" + react-popper "^1.3.3" + react-transition-group "^2.9.0" + resize-observer-polyfill "^1.5.1" + tslib "~1.9.0" + +"@blueprintjs/icons@^3.10.0": + version "3.10.0" + resolved "https://registry.yarnpkg.com/@blueprintjs/icons/-/icons-3.10.0.tgz#45cdb3ca62110e74bcf9e741237a6b4e2cf2c3a4" + integrity sha512-lyAUpkr3qEStPcJpMnxRKuVAPvaRNSce1ySPbkE58zPmD4WBya2gNrWex41xoqRYM0GsiBSwH9CnpY8t6fZKUA== + dependencies: + classnames "^2.2" + tslib "~1.9.0" + +"@types/dom4@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.1.tgz#506d5781b9bcab81bd9a878b198aec7dee2a6033" + integrity sha512-kSkVAvWmMZiCYtvqjqQEwOmvKwcH+V4uiv3qPQ8pAh1Xl39xggGEo8gHUqV4waYGHezdFw0rKBR8Jt0CrQSDZA== + +"@types/node@^10.11.3": + version "10.14.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.17.tgz#b96d4dd3e427382482848948041d3754d40fd5ce" + integrity sha512-p/sGgiPaathCfOtqu2fx5Mu1bcjuP8ALFg4xpGgNkcin7LwRyzUKniEHBKdcE1RPsenq5JVPIpMTJSygLboygQ== + +"@types/prop-types@*": + version "15.7.2" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.2.tgz#0e58ae66773d7fd7c372a493aff740878ec9ceaa" + integrity sha512-f8JzJNWVhKtc9dg/dyDNfliTKNOJSLa7Oht/ElZdF/UbMUmAH3rLmAk3ODNjw0mZajDEgatA03tRjB4+Dp/tzA== + +"@types/react-bootstrap@^0.32.19": + version "0.32.19" + resolved "https://registry.yarnpkg.com/@types/react-bootstrap/-/react-bootstrap-0.32.19.tgz#1bdf1536cabcc4e0be5a2011e33d6766685a7e2a" + integrity sha512-WT108qFXs462RALsnA51e/Q5UJMtCCwIubhgv1U/YtVh2Rh18ZLALkNfAJZO9iroj7fk5RHcfMg7cZxirZp47w== + dependencies: + "@types/react" "*" + +"@types/react-dom@^16.8.4": + version "16.9.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.0.tgz#ba6ddb00bf5de700b0eb91daa452081ffccbfdea" + integrity sha512-OL2lk7LYGjxn4b0efW3Pvf2KBVP0y1v3wip1Bp7nA79NkOpElH98q3WdCEdDj93b2b0zaeBG9DvriuKjIK5xDA== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^16.8.17": + version "16.9.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.2.tgz#6d1765431a1ad1877979013906731aae373de268" + integrity sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@webassemblyjs/ast@1.8.5": version "1.8.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" @@ -1003,11 +1071,6 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -append-field@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" - integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= - aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -1368,14 +1431,6 @@ builtin-status-codes@^3.0.0: resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -busboy@^0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" - integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= - dependencies: - dicer "0.2.5" - readable-stream "1.1.x" - cacache@^10.0.4: version "10.0.4" resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" @@ -1532,7 +1587,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.4, classnames@^2.2.6: +classnames@^2.2, classnames@^2.2.4, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -1602,7 +1657,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.15.1, commander@^2.19.0, commander@^2.8.1: +commander@^2.19.0, commander@^2.8.1: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== @@ -1622,7 +1677,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.5.2: +concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -1737,6 +1792,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-context@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.3.0.tgz#546dede9dc422def0d3fc2fe03afe0bc0f4f7d8c" + integrity sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw== + dependencies: + gud "^1.0.0" + warning "^4.0.3" + cross-env@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.2.0.tgz#6ecd4c015d5773e614039ee529076669b9d126f2" @@ -1827,10 +1890,10 @@ cssesc@^0.1.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" integrity sha1-yBSQPkViM3GgR3tAEJqq++6t27Q= -csv-parse@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-2.5.0.tgz#65748997ecc3719c594622db1b9ea0e2eb7d56bb" - integrity sha512-4OcjOJQByI0YDU5COYw9HAqjo8/MOLLmT9EKyMCXUzgvh30vS1SlMK+Ho84IH5exN44cSnrYecw/7Zpu2m4lkA== +csstype@^2.2.0: + version "2.6.6" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.6.tgz#c34f8226a94bbb10c32cc0d714afdf942291fc41" + integrity sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg== currently-unhandled@^0.4.1: version "0.4.1" @@ -1944,14 +2007,6 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= -dicer@0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" - integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= - dependencies: - readable-stream "1.1.x" - streamsearch "0.1.2" - diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -1968,6 +2023,18 @@ dir-glob@^2.0.0: dependencies: path-type "^3.0.0" +dom-helpers@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" + integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA== + dependencies: + "@babel/runtime" "^7.1.2" + +dom4@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/dom4/-/dom4-2.1.5.tgz#f98a94eb67b340f0fa5b42b0ee9c38cda035428e" + integrity sha512-gJbnVGq5zaBUY0lUh0LUEVGYrtN75Ks8ZwpwOYvnVFrKy/qzXK4R/1WuLIFExWj/tBxbRAkTzZUGJHXmqsBNjQ== + domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -1991,11 +2058,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - electron-to-chromium@^1.3.133: version "1.3.137" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.137.tgz#ba7c88024984c038a5c5c434529aabcea7b42944" @@ -2478,6 +2540,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -2593,7 +2660,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -iconv-lite@^0.4.23, iconv-lite@^0.4.4: +iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -2873,11 +2940,6 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -2997,15 +3059,6 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json2csv@^4.3.5: - version "4.5.1" - resolved "https://registry.yarnpkg.com/json2csv/-/json2csv-4.5.1.tgz#d16d1fa7bea3a8cdddeb500a4eca5d7d2888ecc1" - integrity sha512-o90Xa1ziGk3i7AJEO79Jac4+7SEUk58/DxS5mDPW6GF7poX0y7Y0pm1FbWrkz9VzKE4MpUW9aKBOCpJ0U1Ua8A== - dependencies: - commander "^2.15.1" - jsonparse "^1.3.1" - lodash.get "^4.4.2" - json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -3032,11 +3085,6 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonparse@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -3138,11 +3186,6 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= - lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -3236,11 +3279,6 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - mem@^4.0.0: version "4.3.0" resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" @@ -3314,7 +3352,7 @@ mime-db@1.40.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== -mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: +mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== @@ -3485,20 +3523,6 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -multer@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.1.tgz#24b12a416a22fec2ade810539184bf138720159e" - integrity sha512-zzOLNRxzszwd+61JFuAo0fxdQfvku12aNJgnla0AQ+hHxFmfc/B7jBVuPr5Rmvu46Jze/iJrFpSOsD7afO8SDw== - dependencies: - append-field "^1.0.0" - busboy "^0.2.11" - concat-stream "^1.5.2" - mkdirp "^0.5.1" - object-assign "^4.1.1" - on-finished "^2.3.0" - type-is "^1.6.4" - xtend "^4.0.0" - nan@^2.12.1, nan@^2.13.2: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -3675,6 +3699,11 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize.css@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3" + integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg== + npm-bundled@^1.0.1: version "1.0.6" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" @@ -3743,13 +3772,6 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -on-finished@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -4011,6 +4033,11 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" +popper.js@^1.14.4, popper.js@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2" + integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA== + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -4081,7 +4108,7 @@ promise-inflight@^1.0.1: resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= -prop-types@^15.5.8: +prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -4202,13 +4229,6 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-icons@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-3.7.0.tgz#64fe46231fabfeea27895edeae6c3b78114b8c8f" - integrity sha512-7MyPwjIhuyW0D2N3s4DEd0hGPGFf0sK+IIRKhc1FvSpZNVmnUoGvHbmAwzGJU+3my+fvihVWgwU5SDtlAri56Q== - dependencies: - camelcase "^5.0.0" - react-input-autosize@^2.1.2: version "2.2.1" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8" @@ -4221,6 +4241,23 @@ react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-popper@^1.3.3: + version "1.3.4" + resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.4.tgz#f0cd3b0d30378e1f663b0d79bcc8614221652ced" + integrity sha512-9AcQB29V+WrBKk6X7p0eojd1f25/oJajVdMZkywIoAV6Ag7hzE1Mhyeup2Q1QnvFRtGQFQvtqfhlEoDAPfKAVA== + dependencies: + "@babel/runtime" "^7.1.2" + create-react-context "^0.3.0" + popper.js "^1.14.4" + prop-types "^15.6.1" + typed-styles "^0.0.7" + warning "^4.0.2" + react-select@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.3.0.tgz#1828ad5bf7f3e42a835c7e2d8cb13b5c20714876" @@ -4230,6 +4267,16 @@ react-select@^1.2.1: prop-types "^15.5.8" react-input-autosize "^2.1.2" +react-transition-group@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" + integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg== + dependencies: + dom-helpers "^3.4.0" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-lifecycles-compat "^3.0.4" + read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -4260,16 +4307,6 @@ read-pkg@^1.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@1.1.x: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -4299,6 +4336,11 @@ regenerate@^1.2.1, regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== +regenerator-runtime@^0.13.2: + version "0.13.3" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz#7cf6a77d8f5c6f60eb73c5fc1955b2ceb01e6bf5" + integrity sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw== + regenerator-transform@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.0.tgz#2ca9aaf7a2c239dd32e4761218425b8c7a86ecaf" @@ -4422,6 +4464,11 @@ require-main-filename@^1.0.1: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -4821,11 +4868,6 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= -streamsearch@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" - integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= - string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4850,11 +4892,6 @@ string_decoder@^1.0.0: dependencies: safe-buffer "~5.1.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -5072,7 +5109,7 @@ ts-loader@^6.0.0: micromatch "^4.0.0" semver "^6.0.0" -tslib@^1.9.0: +tslib@^1.9.0, tslib@~1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== @@ -5094,13 +5131,10 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -type-is@^1.6.4: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" +typed-styles@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" + integrity sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q== typedarray@^0.0.6: version "0.0.6" @@ -5260,6 +5294,13 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" +warning@^4.0.2, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" @@ -5436,8 +5477,3 @@ yargs@^7.0.0: which-module "^1.0.0" y18n "^3.2.1" yargs-parser "^5.0.0" - -yn@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" - integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo= diff --git a/src/bp/ui-studio/src/web/components/Content/CreateOrEditModal.js b/src/bp/ui-studio/src/web/components/Content/CreateOrEditModal.js index ac1b73bc436..bc59e3e9cc6 100755 --- a/src/bp/ui-studio/src/web/components/Content/CreateOrEditModal.js +++ b/src/bp/ui-studio/src/web/components/Content/CreateOrEditModal.js @@ -78,7 +78,7 @@ class CreateOrEditModal extends React.Component { show={this.props.show} onHide={this.props.handleClose} backdrop={'static'} - style={{ zIndex: 1051 }} + style={{ zIndex: 1052 }} > {this.state.mustChangeLang ? this.renderSwitchLang() : this.renderForm()} diff --git a/src/bp/ui-studio/src/web/components/Content/Select/Widget.tsx b/src/bp/ui-studio/src/web/components/Content/Select/Widget.tsx index a6769d2bd9f..8ba71d92ae0 100644 --- a/src/bp/ui-studio/src/web/components/Content/Select/Widget.tsx +++ b/src/bp/ui-studio/src/web/components/Content/Select/Widget.tsx @@ -1,7 +1,6 @@ +import { Button, Classes, ControlGroup, InputGroup } from '@blueprintjs/core' import _ from 'lodash' import React, { Component } from 'react' -import { FormControl, FormGroup, InputGroup } from 'react-bootstrap' -import { IoIosFolderOpen, IoMdCreate } from 'react-icons/io' import { connect } from 'react-redux' import { fetchContentItem, upsertContentItem } from '~/actions' import store from '~/store' @@ -108,25 +107,22 @@ class ContentPickerWidget extends Component { } return ( - - - - - {contentItem && ( -
    - -
    - )} -
    window.botpress.pickContent({ contentType }, this.onChange)} - > - -
    -
    - {this.renderModal()} -
    -
    + + + {contentItem &&
    +
    + +
    +
    + {this.props.elements && this.props.elements.map((element, index) => this.renderElement(element, index))} +
    + ) + } +} diff --git a/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss b/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss index c904cbbc1af..03e2bcd950c 100644 --- a/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss +++ b/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss @@ -10,8 +10,10 @@ .listElement { display: flex !important; + flex-direction: row; width: 100%; - align-items: stretch; + justify-content: space-between; + padding: 2px 5px !important; &:hover { .listElementIcon { @@ -31,4 +33,35 @@ .listElementIcon { display: none !important; padding-left: 10px; + height: 10px !important; +} + +.elementListInput { + display: flex; + justify-content: space-between; + margin-top: 5px; + + @media (max-width: 475px) { + flex-wrap: wrap; + } +} + +.contentButton { + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1 1 17%; + max-width: 17%; +} + +.textField { + display: flex; + flex-direction: column; + justify-content: space-between; + flex: 1 1 82%; + max-width: 82%; +} + +.textField textarea { + margin-top: 0px; } diff --git a/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss.d.ts b/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss.d.ts index aa09343a9ec..f0e7173912e 100644 --- a/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss.d.ts +++ b/src/bp/ui-studio/src/web/components/Shared/ElementsList/style.scss.d.ts @@ -1,11 +1,14 @@ // This file is automatically generated. // Please do not change this file! interface CssExports { + 'contentButton': string; + 'elementListInput': string; 'inputArea': string; 'inputError': string; 'listElement': string; 'listElementIcon': string; 'listElementValue': string; + 'textField': string; } declare var cssExports: CssExports; export = cssExports;