From 6c8c07451f5035c727ab2a31bafa4eab403a63b9 Mon Sep 17 00:00:00 2001 From: Anu-Ujin Bat-Ulzii Date: Wed, 29 Mar 2023 18:41:30 +0800 Subject: [PATCH] perf(webbuilder): update webbuilder (#4331) --- .../core-ui/public/images/previews/blank.png | Bin 0 -> 11627 bytes .../src/components/WebBuilder.tsx | 8 +- .../src/components/contentTypes/List.tsx | 3 +- .../src/components/contentTypes/styles.tsx | 1 + .../src/components/pages/List.tsx | 65 ++-- .../src/components/pages/PageForm.tsx | 14 +- .../src/components/pages/styles.tsx | 33 +- .../src/components/sites/Detail.tsx | 338 ++++++++++++++++++ .../src/components/sites/List.tsx | 45 ++- .../src/components/sites/SiteForm.tsx | 308 ++-------------- .../src/components/sites/styles.tsx | 40 ++- .../src/components/templates/List.tsx | 84 ++--- .../src/components/templates/TemplateForm.tsx | 92 +++++ packages/plugin-webbuilder-ui/src/configs.js | 10 +- .../src/containers/Webbuilder.tsx | 2 +- .../src/containers/contentTypes/List.tsx | 23 +- .../src/containers/pages/PageDetail.tsx | 172 +++++++++ .../src/containers/sites/Detail.tsx | 170 +++++++++ .../src/containers/sites/List.tsx | 2 +- .../src/containers/sites/SiteForm.tsx | 133 +------ .../src/containers/templates/List.tsx | 52 +-- .../src/containers/templates/TemplateForm.tsx | 91 +++++ packages/plugin-webbuilder-ui/src/routes.tsx | 26 +- packages/plugin-webbuilder-ui/src/types.ts | 7 +- .../src/boards/components/RightMenu.tsx | 146 ++++---- 25 files changed, 1188 insertions(+), 677 deletions(-) create mode 100644 packages/core-ui/public/images/previews/blank.png create mode 100644 packages/plugin-webbuilder-ui/src/components/sites/Detail.tsx create mode 100644 packages/plugin-webbuilder-ui/src/components/templates/TemplateForm.tsx create mode 100644 packages/plugin-webbuilder-ui/src/containers/pages/PageDetail.tsx create mode 100644 packages/plugin-webbuilder-ui/src/containers/sites/Detail.tsx create mode 100644 packages/plugin-webbuilder-ui/src/containers/templates/TemplateForm.tsx diff --git a/packages/core-ui/public/images/previews/blank.png b/packages/core-ui/public/images/previews/blank.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb46794c55460ee59fcb454134a624f81760615 GIT binary patch literal 11627 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 Wh@g%MQ{~^&YL_m!pC_HYk@`O#y}*tD literal 0 HcmV?d00001 diff --git a/packages/plugin-webbuilder-ui/src/components/WebBuilder.tsx b/packages/plugin-webbuilder-ui/src/components/WebBuilder.tsx index d6a3c2c63d1..9080cc977df 100644 --- a/packages/plugin-webbuilder-ui/src/components/WebBuilder.tsx +++ b/packages/plugin-webbuilder-ui/src/components/WebBuilder.tsx @@ -51,8 +51,8 @@ function WebBuilder(props: Props) { onChange={search} value={searchValue} /> - - @@ -64,8 +64,8 @@ function WebBuilder(props: Props) { } actionBar={ diff --git a/packages/plugin-webbuilder-ui/src/components/contentTypes/List.tsx b/packages/plugin-webbuilder-ui/src/components/contentTypes/List.tsx index 7dc59b153cf..5466f69b07e 100644 --- a/packages/plugin-webbuilder-ui/src/components/contentTypes/List.tsx +++ b/packages/plugin-webbuilder-ui/src/components/contentTypes/List.tsx @@ -42,6 +42,7 @@ class ContentTypesList extends React.Component { ))}
  • handleItemSettings( { displayName: '', code: '', fields: [] }, @@ -49,7 +50,7 @@ class ContentTypesList extends React.Component { ) } > -
    +
      {__('Create content type')}
    diff --git a/packages/plugin-webbuilder-ui/src/components/contentTypes/styles.tsx b/packages/plugin-webbuilder-ui/src/components/contentTypes/styles.tsx index d060e45fa24..641b72a76c4 100644 --- a/packages/plugin-webbuilder-ui/src/components/contentTypes/styles.tsx +++ b/packages/plugin-webbuilder-ui/src/components/contentTypes/styles.tsx @@ -18,6 +18,7 @@ export const TypeFormContainer = styled.div` export const ContentTypeItem = styled.div` margin-left: ${dimensions.unitSpacing - 5}px; + flex: 1; i { color: ${colors.colorCoreGray}; diff --git a/packages/plugin-webbuilder-ui/src/components/pages/List.tsx b/packages/plugin-webbuilder-ui/src/components/pages/List.tsx index 9a549c52902..f8200c23762 100644 --- a/packages/plugin-webbuilder-ui/src/components/pages/List.tsx +++ b/packages/plugin-webbuilder-ui/src/components/pages/List.tsx @@ -2,6 +2,7 @@ import 'grapesjs/dist/css/grapes.min.css'; import { IPageDoc } from '../../types'; import Icon from '@erxes/ui/src/components/Icon'; +import { Link } from 'react-router-dom'; import { List } from './styles'; import React from 'react'; import { __ } from '@erxes/ui/src/utils/core'; @@ -9,41 +10,59 @@ import { __ } from '@erxes/ui/src/utils/core'; type Props = { siteId?: string; pages: IPageDoc[]; - queryParams: any; + pageId: string; + showDarkMode: boolean; + onLoad: (isLoading?: boolean) => void; handleItemSettings: (item: any, type: string) => void; }; class PageList extends React.Component { render() { - const { pages, handleItemSettings, siteId = '', queryParams } = this.props; + const { + pages, + handleItemSettings, + siteId = '', + pageId, + onLoad, + showDarkMode + } = this.props; return ( - - {pages.map(page => ( -
  • - - - {page.name} - - handleItemSettings(page, 'page')} - /> -
  • - ))} + + {pages.map(page => { + const isActive = pageId === page._id; + + const onClick = () => { + onLoad(true); + handleItemSettings({}, ''); + }; + + return ( +
  • + + + {page.name} + + {isActive && ( + handleItemSettings(page, 'page')} + /> + )} +
  • + ); + })}
  • handleItemSettings({ name: '', description: '' }, 'page') } > -
    +
      {__('Create page')}
    diff --git a/packages/plugin-webbuilder-ui/src/components/pages/PageForm.tsx b/packages/plugin-webbuilder-ui/src/components/pages/PageForm.tsx index ff773f8faa7..05bb9eb1bb3 100755 --- a/packages/plugin-webbuilder-ui/src/components/pages/PageForm.tsx +++ b/packages/plugin-webbuilder-ui/src/components/pages/PageForm.tsx @@ -1,5 +1,3 @@ -import 'grapesjs/dist/css/grapes.min.css'; - import Button from '@erxes/ui/src/components/Button'; import ControlLabel from '@erxes/ui/src/components/form/Label'; import { FlexPad } from '@erxes/ui/src/components/step/styles'; @@ -65,7 +63,6 @@ class PageForm extends React.Component { @@ -106,8 +97,7 @@ class PageForm extends React.Component { return ( - {__('Page Settings')} - {this.renderButtons()} + {__('Page Settings')} {this.renderButtons()} diff --git a/packages/plugin-webbuilder-ui/src/components/pages/styles.tsx b/packages/plugin-webbuilder-ui/src/components/pages/styles.tsx index 73dea242218..96d9d302b7c 100644 --- a/packages/plugin-webbuilder-ui/src/components/pages/styles.tsx +++ b/packages/plugin-webbuilder-ui/src/components/pages/styles.tsx @@ -1,8 +1,10 @@ import { colors, dimensions } from '@erxes/ui/src/styles'; +import { FlexItem } from '@erxes/ui/src/components/step/styles'; import styled from 'styled-components'; +import styledTS from 'styled-components-ts'; -export const List = styled.ul` +export const List = styledTS<{ showDarkMode: boolean }>(styled.ul)` margin: 0; padding: 0; list-style: none; @@ -11,11 +13,13 @@ export const List = styled.ul` display: flex; align-items: center; justify-content: space-between; - padding: 5px 0; font-size: 12px; text-transform: capitalize; line-height: 15px; transition: all ease 0.3s; + cursor: pointer; + border-bottom: 1px solid ${props => + props.showDarkMode ? '#666' : colors.borderPrimary}; > i { visibility: hidden; @@ -25,7 +29,12 @@ export const List = styled.ul` > a { display: flex; color: inherit; - cursor: pointer; + padding: 8px 0; + flex: 1; + + > div { + flex: 1; + } &.active { color: ${colors.colorSecondary}; @@ -36,14 +45,25 @@ export const List = styled.ul` } } - .link, - .link > i:before { + &.link, + &.link > i:before { font-weight: 600; cursor: pointer; + padding: 8px 0; + } + + &:last-child { + border: 0; } &: hover { - color: ${colors.colorSecondary}; + color: ${colors.textPrimary}; + background: ${colors.bgActive}; + + &.link { + background: none; + color: ${props => props.showDarkMode && colors.colorShadowGray}; + } > i { visibility: visible; @@ -63,4 +83,5 @@ export const PageHeader = styled.div` export const PageFormContainer = styled.div` width: 500px; height: 100%; + position: relative; `; diff --git a/packages/plugin-webbuilder-ui/src/components/sites/Detail.tsx b/packages/plugin-webbuilder-ui/src/components/sites/Detail.tsx new file mode 100644 index 00000000000..d2837901fe6 --- /dev/null +++ b/packages/plugin-webbuilder-ui/src/components/sites/Detail.tsx @@ -0,0 +1,338 @@ +import 'grapesjs/dist/css/grapes.min.css'; + +import { + CustomButtonWrapper, + ItemDetailContainer, + Loader, + SettingsContent +} from './styles'; +import { IContentTypeDoc, IPageDoc } from '../../types'; +import { __, uploadHandler } from '@erxes/ui/src/utils'; + +import Alert from '@erxes/ui/src/utils/Alert'; +import Button from '@erxes/ui/src/components/Button'; +import ContentTypeForm from '../../containers/contentTypes/ContentTypeForm'; +import EntryList from '../../containers/entries/List'; +import { FlexItem } from '@erxes/ui/src/components/step/styles'; +import GrapesJS from 'grapesjs'; +import PageForm from '../pages/PageForm'; +import React from 'react'; +import Spinner from '@erxes/ui/src/components/Spinner'; +import customPlugins from '../customPlugins'; +import gjsPresetWebpage from 'grapesjs-preset-webpage'; +import { readFile } from '@erxes/ui/src/utils/core'; + +type Props = { + page: IPageDoc; + pageSave: ( + name: string, + description: string, + siteId: string, + html: string, + css: string, + pageId?: string, + afterSave?: any + ) => void; + pageRemove: (_id: string, afterSave?: any) => void; + onLoad: (isLoading?: boolean) => void; + handleItemSettings: (item: any, type: string) => void; + contentTypes: IContentTypeDoc[]; + settingsObject: any; + showDarkMode: boolean; + pages: IPageDoc[]; + loading: boolean; + queryParams: any; + type: string; + _id: string; +}; + +type State = { + name: string; + description: string; +}; + +class SiteDetail extends React.Component { + grapes; + + constructor(props) { + super(props); + + const page = props.page ? props.page : props.pages[0] || ({} as IPageDoc); + + this.state = { + name: page.name || '', + description: page.description || '' + }; + } + + fetchPage = () => { + const { contentTypes, onLoad } = this.props; + let { pages } = this.props; + + const page = this.props.page ? this.props.page : pages[0]; + pages = pages.filter(p => p._id !== page?._id); + + this.grapes = GrapesJS.init({ + protectedCss: '', + container: `#editor`, + fromElement: true, + plugins: [gjsPresetWebpage, customPlugins], + pluginsOpts: { + [customPlugins as any]: { + pages, + contentTypes, + open: false + } + }, + storageManager: false, + assetManager: { + uploadFile: e => { + const files = e.dataTransfer ? e.dataTransfer.files : e.target.files; + + uploadHandler({ + files, + + beforeUpload: () => { + Alert.warning( + 'Upload in progress. Please wait until response shows.' + ); + }, + + afterUpload: ({ status, response, fileInfo }) => { + if (status !== 'ok') { + Alert.error(response.statusText); + } + + Alert.info('Success'); + + editor.AssetManager.add({ + type: fileInfo.type, + src: readFile(response), + height: 350, + width: 250, + name: fileInfo.name + }); + } + }); + } + } + }); + + const editor = this.grapes; + + const pfx = editor.getConfig().stylePrefix; + const modal = editor.Modal; + const cmdm = editor.Commands; + const htmlCodeViewer = editor.CodeManager.getViewer('CodeMirror').clone(); + const cssCodeViewer = editor.CodeManager.getViewer('CodeMirror').clone(); + const pnm = editor.Panels; + const container = document.createElement('div'); + const btnEdit = document.createElement('button'); + + const codeViewerOptions = { + theme: 'hopscotch', + autoBeautify: true, + autoCloseTags: true, + autoCloseBrackets: true, + lineWrapping: true, + styleActiveLine: true, + smartIndent: true, + indentWithTabs: true, + readOnly: 0 + }; + + htmlCodeViewer.set({ + codeName: 'htmlmixed', + ...codeViewerOptions + }); + + cssCodeViewer.set({ + codeName: 'css', + ...codeViewerOptions + }); + + editor.getConfig().allowScripts = 1; + btnEdit.innerHTML = 'Edit'; + btnEdit.className = pfx + 'btn-prim ' + pfx + 'btn-import'; + btnEdit.onclick = () => { + const html = htmlCodeViewer.editor.getValue(); + const css = cssCodeViewer.editor.getValue(); + + editor.DomComponents.getWrapper().set('content', ''); + + editor.setComponents(html.trim()); + editor.setStyle(css.trim()); + + modal.close(); + }; + + // don't move this block + if (page && page.html) { + const { html, css } = page; + + editor.setComponents(html); + editor.setStyle(css.trim()); + } + + cmdm.add('html-edit', { + run: (editr, sender) => { + // tslint:disable-next-line:no-unused-expression + sender && sender.set('active', 0); + let htmlViewer = htmlCodeViewer.editor; + let cssViewer = cssCodeViewer.editor; + + modal.setTitle('Edit code'); + + if (!htmlViewer && !cssViewer) { + const htmlArea = document.createElement('textarea'); + const htmlLabel = document.createElement('p'); + htmlLabel.innerHTML = 'Html'; + + const cssArea = document.createElement('textarea'); + const cssLabel = document.createElement('p'); + cssLabel.innerHTML = 'Css'; + + container.appendChild(htmlLabel); + container.appendChild(htmlArea); + + container.appendChild(cssLabel); + container.appendChild(cssArea); + + container.appendChild(btnEdit); + + htmlCodeViewer.init(htmlArea); + cssCodeViewer.init(cssArea); + + htmlViewer = htmlCodeViewer.editor; + cssViewer = cssCodeViewer.editor; + } + + const InnerHtml = editr.getHtml(); + const Css = editr.getCss({ keepUnusedStyles: true }); + + modal.setContent(''); + modal.setContent(container); + + htmlCodeViewer.setContent(InnerHtml); + cssCodeViewer.setContent(Css); + + modal.open(); + + htmlViewer.refresh(); + cssViewer.refresh(); + } + }); + + pnm.addButton('options', [ + { + id: 'edit', + className: 'fa fa-edit', + command: 'html-edit', + attributes: { + title: 'Edit' + } + } + ]); + + onLoad(false); + }; + + componentDidUpdate(prevProps, prevState) { + if (prevProps.page !== this.props.page) { + setTimeout(() => { + this.fetchPage(); + }, 1000); + } + } + + componentDidMount() { + this.fetchPage(); + } + + pageSave = (pageName: string, pageDescription: string, pageId: string) => { + const e = this.grapes; + + this.props.pageSave( + pageName, + pageDescription, + this.props._id, + pageId ? e.getHtml() : '', + pageId ? e.getCss({ keepUnusedStyles: true }) : '', + pageId, + this.props.handleItemSettings(null, '') + ); + }; + + renderItemSettings() { + const { settingsObject, type } = this.props; + + switch (type) { + case 'page': + return ( + + ); + case 'contenttype': + return ( + + ); + case 'entries': + return ( + + ); + default: + return null; + } + } + + render() { + const { name, description } = this.state; + const { page, pages, settingsObject, showDarkMode, loading } = this.props; + + const pageId = page ? page._id : (pages[0] || ({} as IPageDoc))._id; + + return ( + <> + {settingsObject && ( + + + {this.renderItemSettings()} + + + )} + + + + + {loading && ( + + + + )} +
    + + + ); + } +} + +export default SiteDetail; diff --git a/packages/plugin-webbuilder-ui/src/components/sites/List.tsx b/packages/plugin-webbuilder-ui/src/components/sites/List.tsx index 3b9e56b1daf..58e695f10fa 100644 --- a/packages/plugin-webbuilder-ui/src/components/sites/List.tsx +++ b/packages/plugin-webbuilder-ui/src/components/sites/List.tsx @@ -5,14 +5,16 @@ import { SiteBox, SitePreview } from './styles'; +import { __, readFile } from '@erxes/ui/src/utils'; import Button from '@erxes/ui/src/components/Button'; import Dropdown from 'react-bootstrap/Dropdown'; import DropdownToggle from '@erxes/ui/src/components/DropdownToggle'; import { ISiteDoc } from '../../types'; import Icon from '@erxes/ui/src/components/Icon'; +import ModalTrigger from '@erxes/ui/src/components/ModalTrigger'; import React from 'react'; -import { readFile, __ } from '@erxes/ui/src/utils'; +import TemplateForm from '../../containers/templates/TemplateForm'; import { getEnv } from '@erxes/ui/src/utils/core'; type Props = { @@ -25,20 +27,52 @@ type Props = { queryParams: any; }; -class SiteList extends React.Component { +type State = { + currentSite: any; +}; + +class SiteList extends React.Component { + constructor(props) { + super(props); + + this.state = { + currentSite: null + }; + } + showSite = (site: ISiteDoc) => { const { REACT_APP_API_URL } = getEnv(); - const url = `${REACT_APP_API_URL}/pl:webbuilder/${site.name}`; + const url = `${REACT_APP_API_URL}/pl:xbuilder/${site.name}`; window.open(`${url}`, '_blank'); }; + renderEditAction = (site: ISiteDoc) => { + const trigger = ( + + ); + + const content = ({ closeModal }) => ( + + ); + + return ( + + ); + }; + renderList(site: ISiteDoc) { const { remove, duplicate } = this.props; return ( - + { > {__('View site')} + {this.renderEditAction(site)} @@ -67,7 +102,7 @@ class SiteList extends React.Component { - +
  • {__('Editor')}
  • diff --git a/packages/plugin-webbuilder-ui/src/components/sites/SiteForm.tsx b/packages/plugin-webbuilder-ui/src/components/sites/SiteForm.tsx index 40c3341fb30..7b4b9acef46 100755 --- a/packages/plugin-webbuilder-ui/src/components/sites/SiteForm.tsx +++ b/packages/plugin-webbuilder-ui/src/components/sites/SiteForm.tsx @@ -2,44 +2,23 @@ import 'grapesjs/dist/css/grapes.min.css'; import { CollapseLeftMenu, - ItemDetailContainer, LeftSidebar, LeftSidebarContent, - SettingsContent, SiteFormContainer, SubTitle } from './styles'; -import { IContentTypeDoc, IPageDoc } from '../../types'; -import { __, uploadHandler } from '@erxes/ui/src/utils'; -import Alert from '@erxes/ui/src/utils/Alert'; -import ContentTypeForm from '../../containers/contentTypes/ContentTypeForm'; import ContentTypeList from '../../containers/contentTypes/List'; -import EntryList from '../../containers/entries/List'; +import Detail from '../../containers/sites/Detail'; import { FlexItem } from '@erxes/ui/src/components/step/styles'; -import GrapesJS from 'grapesjs'; +import { IPageDoc } from '../../types'; import Icon from '@erxes/ui/src/components/Icon'; -import PageForm from '../pages/PageForm'; import PageList from '../pages/List'; import React from 'react'; import Wrapper from '@erxes/ui/src/layout/components/Wrapper'; -import customPlugins from '../customPlugins'; -import gjsPresetWebpage from 'grapesjs-preset-webpage'; -import { readFile } from '@erxes/ui/src/utils/core'; +import { __ } from '@erxes/ui/src/utils'; type Props = { - page?: IPageDoc; - pageSave: ( - name: string, - description: string, - siteId: string, - html: string, - css: string, - pageId?: string, - afterSave?: any - ) => void; - pageRemove: (_id: string, afterSave?: any) => void; - contentTypes: IContentTypeDoc[]; pages: IPageDoc[]; queryParams: any; _id: string; @@ -52,208 +31,30 @@ type State = { settingsObject: any; showDarkMode: boolean; showPage: boolean; + loading: boolean; showContentType: boolean; type?: string; }; class SiteForm extends React.Component { - grapes; - constructor(props) { super(props); - const page = props.page ? props.page : props.pages[0]; + const page = props.pages[0] || ({} as IPageDoc); this.state = { - name: page.name, - description: page.description, + name: page.name || '', + description: page.description || '', siteId: page.siteId, settingsObject: null, showPage: false, showContentType: false, + loading: false, showDarkMode: localStorage.getItem('showDarkMode') === 'true' ? true : false || false }; } - componentDidMount() { - const { contentTypes } = this.props; - let { pages } = this.props; - - const page = this.props.page ? this.props.page : pages[0]; - pages = pages.filter(p => p._id !== page?._id); - - this.grapes = GrapesJS.init({ - protectedCss: '', - container: `#editor`, - fromElement: true, - plugins: [gjsPresetWebpage, customPlugins], - pluginsOpts: { - [customPlugins as any]: { - pages, - contentTypes, - open: false - } - }, - storageManager: false, - assetManager: { - uploadFile: e => { - const files = e.dataTransfer ? e.dataTransfer.files : e.target.files; - - uploadHandler({ - files, - - beforeUpload: () => { - Alert.warning( - 'Upload in progress. Please wait until response shows.' - ); - }, - - afterUpload: ({ status, response, fileInfo }) => { - if (status !== 'ok') { - Alert.error(response.statusText); - } - - Alert.info('Success'); - - editor.AssetManager.add({ - type: fileInfo.type, - src: readFile(response), - height: 350, - width: 250, - name: fileInfo.name - }); - } - }); - } - } - }); - - const editor = this.grapes; - - const pfx = editor.getConfig().stylePrefix; - const modal = editor.Modal; - const cmdm = editor.Commands; - const htmlCodeViewer = editor.CodeManager.getViewer('CodeMirror').clone(); - const cssCodeViewer = editor.CodeManager.getViewer('CodeMirror').clone(); - const pnm = editor.Panels; - const container = document.createElement('div'); - const btnEdit = document.createElement('button'); - - const codeViewerOptions = { - theme: 'hopscotch', - autoBeautify: true, - autoCloseTags: true, - autoCloseBrackets: true, - lineWrapping: true, - styleActiveLine: true, - smartIndent: true, - indentWithTabs: true, - readOnly: 0 - }; - - htmlCodeViewer.set({ - codeName: 'htmlmixed', - ...codeViewerOptions - }); - - cssCodeViewer.set({ - codeName: 'css', - ...codeViewerOptions - }); - - editor.getConfig().allowScripts = 1; - btnEdit.innerHTML = 'Edit'; - btnEdit.className = pfx + 'btn-prim ' + pfx + 'btn-import'; - btnEdit.onclick = () => { - const html = htmlCodeViewer.editor.getValue(); - const css = cssCodeViewer.editor.getValue(); - - editor.DomComponents.getWrapper().set('content', ''); - - editor.setComponents(html.trim()); - editor.setStyle(css.trim()); - - modal.close(); - }; - - // don't move this block - if (page && page.html) { - const { html, css } = page; - - editor.setComponents(html); - editor.setStyle(css.trim()); - } - - cmdm.add('html-edit', { - run: (editr, sender) => { - // tslint:disable-next-line:no-unused-expression - sender && sender.set('active', 0); - let htmlViewer = htmlCodeViewer.editor; - let cssViewer = cssCodeViewer.editor; - - modal.setTitle('Edit code'); - - if (!htmlViewer && !cssViewer) { - const htmlArea = document.createElement('textarea'); - const htmlLabel = document.createElement('p'); - htmlLabel.innerHTML = 'Html'; - - const cssArea = document.createElement('textarea'); - const cssLabel = document.createElement('p'); - cssLabel.innerHTML = 'Css'; - - container.appendChild(htmlLabel); - container.appendChild(htmlArea); - - container.appendChild(cssLabel); - container.appendChild(cssArea); - - container.appendChild(btnEdit); - - htmlCodeViewer.init(htmlArea); - cssCodeViewer.init(cssArea); - - htmlViewer = htmlCodeViewer.editor; - cssViewer = cssCodeViewer.editor; - } - - const InnerHtml = editr.getHtml(); - const Css = editr.getCss({ keepUnusedStyles: true }); - - modal.setContent(''); - modal.setContent(container); - - htmlCodeViewer.setContent(InnerHtml); - cssCodeViewer.setContent(Css); - - modal.open(); - - htmlViewer.refresh(); - cssViewer.refresh(); - } - }); - - pnm.addButton('options', [ - { - id: 'edit', - className: 'fa fa-edit', - command: 'html-edit', - attributes: { - title: 'Edit' - } - } - ]); - } - - onChange = (type: string, value: any) => { - this.setState({ [type]: value } as any); - }; - - onSelectSite = (value: any) => { - this.setState({ siteId: value }); - }; - toggleSubTitle = (key: string, value: boolean) => { this.setState({ [key]: value } as any); }; @@ -268,9 +69,17 @@ class SiteForm extends React.Component { }); }; + onLoad = (loading?: boolean) => { + this.setState({ loading: loading ? loading : false }); + }; + renderLeftSidebar() { const { pages = [], _id, queryParams } = this.props; const { showDarkMode, showContentType, showPage } = this.state; + const currentPageId = + Object.keys(queryParams).length !== 0 + ? queryParams.pageId + : (pages[0] || ({} as IPageDoc))._id; return ( { @@ -333,65 +144,10 @@ class SiteForm extends React.Component { ); } - pageSave = ( - pageName: string, - pageDescription: string, - pageId: string, - pageSlug?: string - ) => { - const e = this.grapes; - - this.props.pageSave( - pageName, - pageDescription, - this.props._id, - pageId ? e.getHtml() : '', - pageId ? e.getCss({ keepUnusedStyles: true }) : '', - pageId, - this.handleItemSettings(null, '') - ); - }; - - renderItemSettings() { - const { settingsObject, type } = this.state; - - switch (type) { - case 'page': - return ( - - ); - case 'contenttype': - return ( - - ); - case 'entries': - return ( - - ); - default: - return null; - } - } - render() { - const { name, settingsObject, showDarkMode } = this.state; - - const breadcrumb = [ - { title: 'Sites', link: '/webbuilder' }, - { title: name } - ]; + const { _id, queryParams, pages } = this.props; + const { name, settingsObject, showDarkMode, type, loading } = this.state; + const breadcrumb = [{ title: 'Sites', link: '/xbuilder' }, { title: name }]; return ( <> @@ -400,16 +156,16 @@ class SiteForm extends React.Component { {this.renderLeftSidebar()} - {settingsObject && ( - - - {this.renderItemSettings()} - - - )} - -
    - + diff --git a/packages/plugin-webbuilder-ui/src/components/sites/styles.tsx b/packages/plugin-webbuilder-ui/src/components/sites/styles.tsx index a540b59236c..2fa2e9992df 100644 --- a/packages/plugin-webbuilder-ui/src/components/sites/styles.tsx +++ b/packages/plugin-webbuilder-ui/src/components/sites/styles.tsx @@ -214,7 +214,7 @@ export const Labels = styledTS<{ filteredCategories?: boolean }>(styled.div)` export const LeftSidebar = styledTS<{ width?: number }>(styled.div)` overflow: auto; height: 100%; - width: 200px; + width: 250px; border-right: 1px solid ${colors.colorShadowGray}; &.darkmode { @@ -239,9 +239,8 @@ export const CollapseLeftMenu = styled.div` export const SubTitle = styledTS<{ flexBetween?: boolean }>(styled.div)` margin: 0; letter-spacing: 0.5px; - font-size: 12px; background-color: rgba(0, 0, 0, 0.1); - padding: 9px ${dimensions.unitSpacing}px; + padding: ${dimensions.unitSpacing}px; border-bottom: 1px solid rgba(0, 0, 0, 0.25); border-top: 1px solid rgba(0, 0, 0, 0.25); @@ -347,12 +346,43 @@ export const SiteFormContainer = styledTS<{ showDarkMode?: boolean }>( background-color: #3d3d3d !important; } `} + + .right-section { + position: relative; + } `; export const SettingsContent = styled.div` height: 100%; position: absolute; - left: 199px; - width: calc(100% - 200px); + left: 249px; + width: calc(100% - 250px); z-index: 10; `; + +export const Editor = styled.div` + position: relative; +`; + +export const CustomButtonWrapper = styled.div` + position: absolute; + z-index: 5; + left: 130px; + top: 7px; +`; + +export const Loader = styledTS<{ showDarkMode?: boolean }>(styled.div)` + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; + opacity: .7; + background: ${props => + props.showDarkMode ? colors.colorCoreDarkGray : colors.colorWhite}; + z-index: 9; + + > div { + height: 100%; + } +`; diff --git a/packages/plugin-webbuilder-ui/src/components/templates/List.tsx b/packages/plugin-webbuilder-ui/src/components/templates/List.tsx index ececd639451..ab5fe53506f 100644 --- a/packages/plugin-webbuilder-ui/src/components/templates/List.tsx +++ b/packages/plugin-webbuilder-ui/src/components/templates/List.tsx @@ -1,4 +1,3 @@ -import { HeaderContent } from './styles'; import { Content, FilterContainer, @@ -16,24 +15,25 @@ import { __, getEnv, router } from '@erxes/ui/src/utils/core'; import { BarItems } from '@erxes/ui/src/layout/styles'; import Button from '@erxes/ui/src/components/Button'; import { CATEGORIES } from '../../constants'; -import ControlLabel from '@erxes/ui/src/components/form/Label'; import DataWithLoader from '@erxes/ui/src/components/DataWithLoader'; import FormControl from '@erxes/ui/src/components/form/Control'; -import FormGroup from '@erxes/ui/src/components/form/Group'; +import { HeaderContent } from './styles'; +import { IAttachment } from '@erxes/ui/src/types'; +import { IRouterProps } from '@erxes/ui/src/types'; import { ITemplateDoc } from '../../types'; import Icon from '@erxes/ui/src/components/Icon'; +import { Label } from '@erxes/ui/src/components/form/styles'; import ModalTrigger from '@erxes/ui/src/components/ModalTrigger'; import Pagination from '@erxes/ui/src/components/pagination/Pagination'; -import Wrapper from '@erxes/ui/src/layout/components/Wrapper'; +import TemplateForm from '../../containers/templates/TemplateForm'; import Uploader from '@erxes/ui/src/components/Uploader'; -import { IAttachment } from '@erxes/ui/src/types'; +import Wrapper from '@erxes/ui/src/layout/components/Wrapper'; import { withRouter } from 'react-router-dom'; -import { IRouterProps } from '@erxes/ui/src/types'; type Props = { templates: ITemplateDoc[]; templatesCount: number; - use: (_id: string, name: string, coverImage: any) => void; + // use: (_id: string, name: string, coverImage: any) => void; queryParams: any; } & IRouterProps; @@ -53,7 +53,7 @@ function List(props: Props) { const renderDemoAction = (template: ITemplateDoc) => { const { REACT_APP_API_URL } = getEnv(); - const url = `${REACT_APP_API_URL}/pl:webbuilder/demo/${template._id}`; + const url = `${REACT_APP_API_URL}/pl:xbuilder/demo/${template._id}`; const onClick = () => window.open(`${url}`, '_blank'); @@ -103,51 +103,14 @@ function List(props: Props) { const renderUseAction = template => { const trigger = ; + const site = localStorage.getItem('xbuilderSiteId') || ''; const content = ({ closeModal }) => ( - <> - - Your WebSite Name - - setName(e.target.value)} - /> - - - - Cover image - - - - - - - - - - + ); return ( @@ -172,8 +135,9 @@ function List(props: Props) {
    {template.name} - Business + {__('Business')}
    +
    ); @@ -221,9 +185,9 @@ function List(props: Props) { @@ -248,6 +212,18 @@ function List(props: Props) { + {renderRow( + { + _id: '0', + name: 'Blank Site', + html: '', + image: '/images/previews/blank.png', + categories: '' + }, + 0 + )} + {/* {templates.map((template, index) => + renderRow(template, index + 1) */} {filterTemplates().map((template, index) => renderRow(template, index) )} diff --git a/packages/plugin-webbuilder-ui/src/components/templates/TemplateForm.tsx b/packages/plugin-webbuilder-ui/src/components/templates/TemplateForm.tsx new file mode 100644 index 00000000000..8859b77cac1 --- /dev/null +++ b/packages/plugin-webbuilder-ui/src/components/templates/TemplateForm.tsx @@ -0,0 +1,92 @@ +import 'grapesjs/dist/css/grapes.min.css'; + +import { ISite, ISiteDoc } from '../../types'; + +import Button from '@erxes/ui/src/components/Button'; +import ControlLabel from '@erxes/ui/src/components/form/Label'; +import FormControl from '@erxes/ui/src/components/form/Control'; +import FormGroup from '@erxes/ui/src/components/form/Group'; +import { ModalFooter } from '@erxes/ui/src/styles/main'; +import React from 'react'; +import { __ } from '@erxes/ui/src/utils'; + +type Props = { + closeModal: () => void; + useTemplate: (_id: string, name: string) => void; + saveSite: (_id: string, args: ISite) => void; + currentTemplateId?: string; + selectedSite: ISiteDoc; +}; + +type State = { + name: string; +}; + +class TemplateForm extends React.Component { + constructor(props) { + super(props); + + this.state = { + name: props.selectedSite.name + }; + } + + onClick = () => { + const { name } = this.state; + const { + useTemplate, + saveSite, + selectedSite, + currentTemplateId + } = this.props; + + if (selectedSite._id) { + return saveSite(selectedSite._id, { + name, + domain: selectedSite.domain || '' + }); + } + + return useTemplate(currentTemplateId || '', name); + }; + + render() { + return ( + <> + + Your WebSite Name + + this.setState({ name: e.target.value })} + /> + + + + + + + + + ); + } +} + +export default TemplateForm; diff --git a/packages/plugin-webbuilder-ui/src/configs.js b/packages/plugin-webbuilder-ui/src/configs.js index 90be641d301..db7493e2c1d 100644 --- a/packages/plugin-webbuilder-ui/src/configs.js +++ b/packages/plugin-webbuilder-ui/src/configs.js @@ -1,21 +1,21 @@ module.exports = { - name: 'webbuilder', + name: 'xbuilder', port: 3027, exposes: { './routes': './src/routes.tsx' }, routes: { url: 'http://localhost:3027/remoteEntry.js', - scope: 'webbuilder', + scope: 'xbuilder', module: './routes' }, menus: [ { - text: 'Web builder', - url: '/webbuilder', + text: 'X Builder', + url: '/xbuilder', icon: 'icon-window-grid', location: 'mainNavigation', - permission: 'showWebbuilder' + permission: 'showXbuilder' } ] }; \ No newline at end of file diff --git a/packages/plugin-webbuilder-ui/src/containers/Webbuilder.tsx b/packages/plugin-webbuilder-ui/src/containers/Webbuilder.tsx index 7dc14799a6a..4283868ae93 100644 --- a/packages/plugin-webbuilder-ui/src/containers/Webbuilder.tsx +++ b/packages/plugin-webbuilder-ui/src/containers/Webbuilder.tsx @@ -13,7 +13,7 @@ type Props = { }; type FinalProps = { - sitesTotalCountQuery: any; + sitesTotalCountQuery: SitesTotalCountQueryResponse; } & Props; function WebBuilderContainer(props: FinalProps) { diff --git a/packages/plugin-webbuilder-ui/src/containers/contentTypes/List.tsx b/packages/plugin-webbuilder-ui/src/containers/contentTypes/List.tsx index 7c3012641ad..204bbb65c56 100644 --- a/packages/plugin-webbuilder-ui/src/containers/contentTypes/List.tsx +++ b/packages/plugin-webbuilder-ui/src/containers/contentTypes/List.tsx @@ -4,13 +4,12 @@ import { TypesMainQueryResponse, TypesRemoveMutationResponse } from '../../types'; -import { mutations, queries } from '../../graphql'; import List from '../../components/contentTypes/List'; import React from 'react'; -import Spinner from '@erxes/ui/src/components/Spinner'; import gql from 'graphql-tag'; import { graphql } from 'react-apollo'; +import { queries } from '../../graphql'; type Props = { siteId: string; @@ -19,14 +18,13 @@ type Props = { type FinalProps = { typesMainQuery: TypesMainQueryResponse; -} & Props & - TypesRemoveMutationResponse; +} & Props; function ContentTypesContainer(props: FinalProps) { - const { typesRemoveMutation, typesMainQuery } = props; + const { typesMainQuery } = props; if (typesMainQuery.loading) { - return ; + return null; } const { list = [], totalCount } = @@ -50,18 +48,5 @@ export default compose( }, fetchPolicy: 'network-only' }) - }), - graphql(gql(mutations.typesRemove), { - name: 'typesRemoveMutation', - options: ({ siteId }) => ({ - refetchQueries: [ - { - query: gql(queries.contentTypes), - variables: { - siteId - } - } - ] - }) }) )(ContentTypesContainer); diff --git a/packages/plugin-webbuilder-ui/src/containers/pages/PageDetail.tsx b/packages/plugin-webbuilder-ui/src/containers/pages/PageDetail.tsx new file mode 100644 index 00000000000..66bf90c5c22 --- /dev/null +++ b/packages/plugin-webbuilder-ui/src/containers/pages/PageDetail.tsx @@ -0,0 +1,172 @@ +import * as compose from 'lodash.flowright'; + +import { Alert, confirm } from '@erxes/ui/src/utils'; +import { + PageDetailQueryResponse, + PagesAddMutationResponse, + PagesEditMutationResponse, + PagesMainQueryResponse, + PagesRemoveMutationResponse, + TypesQueryResponse +} from '../../types'; +import { mutations, queries } from '../../graphql'; + +import { IRouterProps } from '@erxes/ui/src/types'; +import React from 'react'; +import SiteForm from '../../components/sites/SiteForm'; +import { generatePaginationParams } from '@erxes/ui/src/utils/router'; +import gql from 'graphql-tag'; +import { graphql } from 'react-apollo'; +import { withRouter } from 'react-router-dom'; + +type Props = { + _id: string; + queryParams: any; +} & IRouterProps; + +type FinalProps = Props & { + pageDetailQuery?: PageDetailQueryResponse; + pagesMainQuery: PagesMainQueryResponse; + typesQuery: TypesQueryResponse; +} & PagesAddMutationResponse & + PagesEditMutationResponse & + PagesRemoveMutationResponse; + +const PageDetailContainer = (props: FinalProps) => { + const { pageDetailQuery, history, pagesMainQuery, typesQuery } = props; + + if ( + (pageDetailQuery && pageDetailQuery.loading) || + pagesMainQuery.loading || + typesQuery.loading + ) { + return null; + } + + const pageSave = ( + name: string, + description: string, + siteId: string, + html: string, + css: string, + pageId?: string, + afterSave?: any + ) => { + let method: any = props.pagesAdd; + + const variables: any = { + name, + description, + siteId, + html, + css + }; + + if (pageId) { + method = props.pagesEdit; + variables._id = pageId; + } + + method({ variables }) + .then(() => { + Alert.success(`Success`); + + if (afterSave) { + afterSave(); + } + + pagesMainQuery.refetch(); + }) + .catch(error => { + Alert.error(error.message); + }); + }; + + const pageRemove = (_id: string, afterSave?: any) => { + confirm().then(() => { + props + .pagesRemoveMutation({ variables: { _id } }) + .then(() => { + Alert.success('You successfully deleted a page.'); + + if (afterSave) { + afterSave(); + } + + pagesMainQuery.refetch(); + }) + .catch(e => { + Alert.error(e.message); + }); + }); + }; + + let page; + + if (pageDetailQuery) { + page = pageDetailQuery.webbuilderPageDetail; + } + + const pagesMain = pagesMainQuery.webbuilderPagesMain || {}; + const contentTypes = typesQuery.webbuilderContentTypes || []; + + const updatedProps = { + ...props, + pageSave, + pageRemove, + page, + contentTypes, + pages: pagesMain.list || [] + }; + + return ; +}; + +const refetchPageQueries = () => [{ query: gql(queries.pagesMain) }]; + +export default compose( + graphql<{}, PagesAddMutationResponse>(gql(mutations.add), { + name: 'pagesAdd', + options: () => ({ + refetchQueries: refetchPageQueries() + }) + }), + graphql<{}, PagesRemoveMutationResponse>(gql(mutations.remove), { + name: 'pagesRemoveMutation' + }), + graphql(gql(mutations.edit), { + name: 'pagesEdit', + options: ({ queryParams }) => ({ + refetchQueries: [ + ...refetchPageQueries(), + { + query: gql(queries.pageDetail), + variables: { _id: queryParams.pageId || '' } + } + ] + }) + }), + + graphql( + gql(queries.pageDetail), + { + name: 'pageDetailQuery', + skip: ({ queryParams }) => !queryParams.pageId, + options: ({ queryParams }) => ({ + variables: { _id: queryParams.pageId || '' } + }) + } + ), + graphql<{}, TypesQueryResponse>(gql(queries.contentTypes), { + name: 'typesQuery' + }), + graphql(gql(queries.pagesMain), { + name: 'pagesMainQuery', + options: ({ _id, queryParams }) => ({ + variables: { + ...generatePaginationParams(queryParams), + siteId: _id || '' + } + }) + }) +)(withRouter(PageDetailContainer)); diff --git a/packages/plugin-webbuilder-ui/src/containers/sites/Detail.tsx b/packages/plugin-webbuilder-ui/src/containers/sites/Detail.tsx new file mode 100644 index 00000000000..169ceb9442a --- /dev/null +++ b/packages/plugin-webbuilder-ui/src/containers/sites/Detail.tsx @@ -0,0 +1,170 @@ +import * as compose from 'lodash.flowright'; + +import { Alert, confirm } from '@erxes/ui/src/utils'; +import { + IPageDoc, + PageDetailQueryResponse, + PagesAddMutationResponse, + PagesEditMutationResponse, + PagesRemoveMutationResponse, + TypesQueryResponse +} from '../../types'; +import { mutations, queries } from '../../graphql'; + +import Detail from '../../components/sites/Detail'; +import { IRouterProps } from '@erxes/ui/src/types'; +import React from 'react'; +import Spinner from '@erxes/ui/src/components/Spinner'; +import { generatePaginationParams } from '@erxes/ui/src/utils/router'; +import gql from 'graphql-tag'; +import { graphql } from 'react-apollo'; +import { withRouter } from 'react-router-dom'; + +type Props = { + _id: string; + queryParams: any; + onLoad: (isLoading?: boolean) => void; + pages: IPageDoc[]; + loading: boolean; + settingsObject: any; + type: string; + showDarkMode: boolean; + handleItemSettings: (item: any, type: string) => void; +} & IRouterProps; + +type FinalProps = Props & { + pageDetailQuery?: PageDetailQueryResponse; + typesQuery: TypesQueryResponse; +} & PagesAddMutationResponse & + PagesEditMutationResponse & + PagesRemoveMutationResponse; + +const SitesDetailContainer = (props: FinalProps) => { + const { pageDetailQuery, typesQuery } = props; + + if ((pageDetailQuery && pageDetailQuery.loading) || typesQuery.loading) { + return ; + } + + const pageSave = ( + name: string, + description: string, + siteId: string, + html: string, + css: string, + pageId?: string, + afterSave?: any + ) => { + let method: any = props.pagesAdd; + + const variables: any = { + name, + description, + siteId, + html, + css + }; + + if (pageId) { + method = props.pagesEdit; + variables._id = pageId; + } + + method({ variables }) + .then(() => { + Alert.success(`Success`); + + if (afterSave) { + afterSave(); + } + }) + .catch(error => { + Alert.error(error.message); + }); + }; + + const pageRemove = (_id: string, afterSave?: any) => { + confirm().then(() => { + props + .pagesRemoveMutation({ variables: { _id } }) + .then(() => { + Alert.success('You successfully deleted a page.'); + + if (afterSave) { + afterSave(); + } + }) + .catch(e => { + Alert.error(e.message); + }); + }); + }; + + let page; + + if (pageDetailQuery) { + page = pageDetailQuery.webbuilderPageDetail; + } + + const contentTypes = typesQuery.webbuilderContentTypes || []; + + const updatedProps = { + ...props, + contentTypes, + page, + pageSave, + pageRemove + }; + + return ; +}; + +const refetchPageQueries = ({ _id, queryParams }) => [ + { + query: gql(queries.pagesMain), + variables: { + ...generatePaginationParams(queryParams), + siteId: _id || '' + } + } +]; + +export default compose( + graphql( + gql(queries.pageDetail), + { + name: 'pageDetailQuery', + skip: ({ queryParams }) => !queryParams.pageId, + options: ({ queryParams }) => ({ + variables: { _id: queryParams.pageId || '' } + }) + } + ), + graphql(gql(mutations.add), { + name: 'pagesAdd', + options: ({ _id, queryParams }) => ({ + refetchQueries: refetchPageQueries({ _id, queryParams }) + }) + }), + graphql(gql(mutations.edit), { + name: 'pagesEdit', + options: ({ queryParams, _id }) => ({ + refetchQueries: [ + ...refetchPageQueries({ _id, queryParams }), + { + query: gql(queries.pageDetail), + variables: { _id: queryParams.pageId || '' } + } + ] + }) + }), + graphql(gql(mutations.remove), { + name: 'pagesRemoveMutation', + options: ({ _id, queryParams }) => ({ + refetchQueries: refetchPageQueries({ _id, queryParams }) + }) + }), + graphql<{}, TypesQueryResponse>(gql(queries.contentTypes), { + name: 'typesQuery' + }) +)(withRouter(SitesDetailContainer)); diff --git a/packages/plugin-webbuilder-ui/src/containers/sites/List.tsx b/packages/plugin-webbuilder-ui/src/containers/sites/List.tsx index cc505be31ae..9a97b10692f 100644 --- a/packages/plugin-webbuilder-ui/src/containers/sites/List.tsx +++ b/packages/plugin-webbuilder-ui/src/containers/sites/List.tsx @@ -47,7 +47,7 @@ function SitesContainer(props: FinalProps) { const remove = (_id: string) => { if (_id === selectedSite) { - localStorage.removeItem('webbuilderSiteId'); + localStorage.removeItem('xbuilderSiteId'); } const message = `This will permanently delete the current site. Are you absolutely sure?`; diff --git a/packages/plugin-webbuilder-ui/src/containers/sites/SiteForm.tsx b/packages/plugin-webbuilder-ui/src/containers/sites/SiteForm.tsx index 0122afeaf89..e06e6b435ff 100644 --- a/packages/plugin-webbuilder-ui/src/containers/sites/SiteForm.tsx +++ b/packages/plugin-webbuilder-ui/src/containers/sites/SiteForm.tsx @@ -1,22 +1,13 @@ import * as compose from 'lodash.flowright'; -import { Alert, confirm } from '@erxes/ui/src/utils'; -import { - PageDetailQueryResponse, - PagesAddMutationResponse, - PagesEditMutationResponse, - PagesMainQueryResponse, - PagesRemoveMutationResponse, - TypesQueryResponse -} from '../../types'; -import { mutations, queries } from '../../graphql'; - import { IRouterProps } from '@erxes/ui/src/types'; +import { PagesMainQueryResponse } from '../../types'; import React from 'react'; import SiteForm from '../../components/sites/SiteForm'; import { generatePaginationParams } from '@erxes/ui/src/utils/router'; import gql from 'graphql-tag'; import { graphql } from 'react-apollo'; +import { queries } from '../../graphql'; import { withRouter } from 'react-router-dom'; type Props = { @@ -25,141 +16,27 @@ type Props = { } & IRouterProps; type FinalProps = Props & { - pageDetailQuery?: PageDetailQueryResponse; pagesMainQuery: PagesMainQueryResponse; - typesQuery: TypesQueryResponse; -} & PagesAddMutationResponse & - PagesEditMutationResponse & - PagesRemoveMutationResponse; +}; const FormContainer = (props: FinalProps) => { - const { pageDetailQuery, history, pagesMainQuery, typesQuery } = props; + const { pagesMainQuery } = props; - if ( - (pageDetailQuery && pageDetailQuery.loading) || - pagesMainQuery.loading || - typesQuery.loading - ) { + if (pagesMainQuery.loading) { return null; } - const pageSave = ( - name: string, - description: string, - siteId: string, - html: string, - css: string, - pageId?: string, - afterSave?: any - ) => { - let method: any = props.pagesAdd; - - const variables: any = { - name, - description, - siteId, - html, - css - }; - - if (pageId) { - method = props.pagesEdit; - variables._id = pageId; - } - - method({ variables }) - .then(() => { - Alert.success(`Success`); - - if (afterSave) { - afterSave(); - } - - pagesMainQuery.refetch(); - }) - .catch(error => { - Alert.error(error.message); - }); - }; - - const pageRemove = (_id: string, afterSave?: any) => { - confirm().then(() => { - props - .pagesRemoveMutation({ variables: { _id } }) - .then(() => { - Alert.success('You successfully deleted a page.'); - - if (afterSave) { - afterSave(); - } - - pagesMainQuery.refetch(); - }) - .catch(e => { - Alert.error(e.message); - }); - }); - }; - - let page; - - if (pageDetailQuery) { - page = pageDetailQuery.webbuilderPageDetail; - } - const pagesMain = pagesMainQuery.webbuilderPagesMain || {}; - const contentTypes = typesQuery.webbuilderContentTypes || []; const updatedProps = { ...props, - pageSave, - pageRemove, - page, - contentTypes, pages: pagesMain.list || [] }; return ; }; -const refetchPageQueries = () => [{ query: gql(queries.pagesMain) }]; - export default compose( - graphql<{}, PagesAddMutationResponse>(gql(mutations.add), { - name: 'pagesAdd', - options: () => ({ - refetchQueries: refetchPageQueries() - }) - }), - graphql<{}, PagesRemoveMutationResponse>(gql(mutations.remove), { - name: 'pagesRemoveMutation' - }), - graphql(gql(mutations.edit), { - name: 'pagesEdit', - options: ({ queryParams }) => ({ - refetchQueries: [ - ...refetchPageQueries(), - { - query: gql(queries.pageDetail), - variables: { _id: queryParams.pageId || '' } - } - ] - }) - }), - - graphql( - gql(queries.pageDetail), - { - name: 'pageDetailQuery', - skip: ({ queryParams }) => !queryParams.pageId, - options: ({ queryParams }) => ({ - variables: { _id: queryParams.pageId || '' } - }) - } - ), - graphql<{}, TypesQueryResponse>(gql(queries.contentTypes), { - name: 'typesQuery' - }), graphql(gql(queries.pagesMain), { name: 'pagesMainQuery', options: ({ _id, queryParams }) => ({ diff --git a/packages/plugin-webbuilder-ui/src/containers/templates/List.tsx b/packages/plugin-webbuilder-ui/src/containers/templates/List.tsx index be08ecc889d..291abf45a63 100644 --- a/packages/plugin-webbuilder-ui/src/containers/templates/List.tsx +++ b/packages/plugin-webbuilder-ui/src/containers/templates/List.tsx @@ -2,88 +2,46 @@ import * as compose from 'lodash.flowright'; import { TemplatesQueryResponse, - TemplatesTotalCountQueryResponse, - TemplatesUseMutationResponse + TemplatesTotalCountQueryResponse } from '../../types'; -import { mutations, queries } from '../../graphql'; -import { Alert } from '@erxes/ui/src/utils'; import List from '../../components/templates/List'; import React from 'react'; import Spinner from '@erxes/ui/src/components/Spinner'; import { generatePaginationParams } from '@erxes/ui/src/utils/router'; import gql from 'graphql-tag'; import { graphql } from 'react-apollo'; +import { queries } from '../../graphql'; type Props = { queryParams: any; - selectedSite: string; }; type FinalProps = { templatesQuery: TemplatesQueryResponse; templatesCountQuery: TemplatesTotalCountQueryResponse; -} & Props & - TemplatesUseMutationResponse; +} & Props; function ListContainer(props: FinalProps) { - const { templatesQuery, templatesCountQuery, templatesUse } = props; + const { templatesQuery, templatesCountQuery } = props; if (templatesQuery.loading || templatesCountQuery.loading) { return ; } - const use = (_id: string, name: string, coverImage: any) => { - templatesUse({ - variables: { - _id, - name, - coverImage - } - }) - .then(res => { - const { - data: { webbuilderTemplatesUse } - } = res; - - Alert.success('Successfully created a website'); - - window.location.href = `/webbuilder/sites/edit/${webbuilderTemplatesUse}`; - }) - .catch(e => { - Alert.error(e.message); - }); - }; - const templates = templatesQuery.webbuilderTemplates || []; const templatesCount = templatesCountQuery.webbuilderTemplatesTotalCount || 0; const updatedProps = { ...props, templates, - templatesCount, - use + templatesCount }; return ; } export default compose( - graphql(gql(mutations.templatesUse), { - name: 'templatesUse', - options: ({ selectedSite }) => ({ - refetchQueries: [ - { query: gql(queries.sites), variables: { fromSelect: true } }, - { query: gql(queries.sitesTotalCount) }, - { - query: gql(queries.contentTypes), - variables: { - siteId: selectedSite - } - } - ] - }) - }), graphql(gql(queries.templates), { name: 'templatesQuery', options: ({ queryParams }) => ({ diff --git a/packages/plugin-webbuilder-ui/src/containers/templates/TemplateForm.tsx b/packages/plugin-webbuilder-ui/src/containers/templates/TemplateForm.tsx new file mode 100644 index 00000000000..b9eabb707c6 --- /dev/null +++ b/packages/plugin-webbuilder-ui/src/containers/templates/TemplateForm.tsx @@ -0,0 +1,91 @@ +import * as compose from 'lodash.flowright'; + +import { + ISite, + ISiteDoc, + SitesEditMutationResponse, + TemplatesUseMutationResponse +} from '../../types'; +import { mutations, queries } from '../../graphql'; + +import { Alert } from '@erxes/ui/src/utils'; +import React from 'react'; +import TemplateForm from '../../components/templates/TemplateForm'; +import gql from 'graphql-tag'; +import { graphql } from 'react-apollo'; + +type Props = { + currentTemplateId?: string; + closeModal: () => void; + selectedSite: ISiteDoc; +}; + +type FinalProps = {} & Props & + TemplatesUseMutationResponse & + SitesEditMutationResponse; + +function TemplateFormContainer(props: FinalProps) { + const { templatesUse, sitesEditMutation, closeModal } = props; + + const useTemplate = (_id: string, name: string) => { + templatesUse({ variables: { _id, name } }) + .then(res => { + const { + data: { webbuilderTemplatesUse } + } = res; + + Alert.success('Successfully created a website'); + + window.location.href = `/xbuilder/sites/edit/${webbuilderTemplatesUse}`; + }) + .catch(e => { + Alert.error(e.message); + }); + }; + + const saveSite = (_id: string, args: ISite) => { + sitesEditMutation({ variables: { _id, ...args } }) + .then(res => { + Alert.success('Successfully saved a website'); + closeModal(); + }) + .catch(e => { + Alert.error(e.message); + }); + }; + + const updatedProps = { + ...props, + useTemplate, + saveSite + }; + + return ; +} + +export default compose( + graphql(gql(mutations.templatesUse), { + name: 'templatesUse', + options: ({ selectedSite }) => ({ + refetchQueries: [ + { query: gql(queries.sites), variables: { fromSelect: true } }, + { query: gql(queries.sitesTotalCount) }, + { + query: gql(queries.contentTypes), + variables: { + siteId: selectedSite + } + } + ] + }) + }), + graphql(gql(mutations.sitesEdit), { + name: 'sitesEditMutation', + options: () => ({ + refetchQueries: [ + { query: gql(queries.sites), variables: { fromSelect: true } }, + { query: gql(queries.sitesTotalCount) } + ] + }) + }) +)(TemplateFormContainer); diff --git a/packages/plugin-webbuilder-ui/src/routes.tsx b/packages/plugin-webbuilder-ui/src/routes.tsx index ff13ba1796a..08ecaa67575 100755 --- a/packages/plugin-webbuilder-ui/src/routes.tsx +++ b/packages/plugin-webbuilder-ui/src/routes.tsx @@ -1,5 +1,6 @@ +import { Route, Switch } from 'react-router-dom'; + import React from 'react'; -import { Route } from 'react-router-dom'; import asyncComponent from '@erxes/ui/src/components/AsyncComponent'; import queryString from 'query-string'; @@ -11,13 +12,13 @@ const SitesListContainer = asyncComponent(() => const SiteForm = asyncComponent(() => import( - /* webpackChunkName: "SiteForm - WebBuilders" */ './containers/sites/SiteForm' + /* webpackChunkName: "SiteForm - XBuilders" */ './containers/sites/SiteForm' ) ); const WebBuilderContainer = asyncComponent(() => import( - /* webpackChunkName: "PageForm - WebBuilderContainer" */ './containers/Webbuilder' + /* webpackChunkName: "PageForm - XBuilderContainer" */ './containers/Webbuilder' ) ); @@ -35,17 +36,10 @@ const webBuilderSitesCreate = history => { const { location, match } = history; const queryParams = queryString.parse(location.search); - const site = localStorage.getItem('webbuilderSiteId') || ''; const { step } = match.params; - return ( - - ); + return ; }; const webBuilderSitesEdit = ({ match, location }) => { @@ -57,25 +51,25 @@ const webBuilderSitesEdit = ({ match, location }) => { const routes = () => { return ( - <> + - + ); }; diff --git a/packages/plugin-webbuilder-ui/src/types.ts b/packages/plugin-webbuilder-ui/src/types.ts index 6db75ab3e6a..7414375b5e1 100644 --- a/packages/plugin-webbuilder-ui/src/types.ts +++ b/packages/plugin-webbuilder-ui/src/types.ts @@ -1,5 +1,7 @@ -import { IUser } from '@erxes/ui/src/auth/types'; import { IAttachment, QueryResponse } from '@erxes/ui/src/types'; + +import { IUser } from '@erxes/ui/src/auth/types'; + export interface IPage { name: string; description: string; @@ -62,6 +64,7 @@ export interface ITemplateDoc extends ITemplate { export interface ISite { name: string; domain: string; + templateImage?: string; coverImage: IAttachment; } @@ -174,7 +177,7 @@ export type EntriesRemoveMutationResponse = { // template export type TemplatesUseMutationResponse = { templatesUse: (doc: { - variables: { _id: string; name: string; coverImage: string }; + variables: { _id: string; name: string; coverImage?: string }; }) => Promise; }; diff --git a/packages/ui-cards/src/boards/components/RightMenu.tsx b/packages/ui-cards/src/boards/components/RightMenu.tsx index 9eb38d235a7..742200bd79e 100644 --- a/packages/ui-cards/src/boards/components/RightMenu.tsx +++ b/packages/ui-cards/src/boards/components/RightMenu.tsx @@ -1,32 +1,32 @@ -import Button from "@erxes/ui/src/components/Button"; -import FormControl from "@erxes/ui/src/components/form/Control"; -import ControlLabel from "@erxes/ui/src/components/form/Label"; -import DateControl from "@erxes/ui/src/components/form/DateControl"; -import Icon from "@erxes/ui/src/components/Icon"; -import { Tabs, TabTitle } from "@erxes/ui/src/components/tabs"; -import { IOption } from "@erxes/ui/src/types"; -import { __ } from "coreui/utils"; -import SelectTeamMembers from "@erxes/ui/src/team/containers/SelectTeamMembers"; -import React from "react"; -import Select from "react-select-plus"; -import RTG from "react-transition-group"; -import { PRIORITIES } from "../constants"; -import SegmentFilter from "../containers/SegmentFilter"; -import dayjs from "dayjs"; +import Button from '@erxes/ui/src/components/Button'; +import FormControl from '@erxes/ui/src/components/form/Control'; +import ControlLabel from '@erxes/ui/src/components/form/Label'; +import DateControl from '@erxes/ui/src/components/form/DateControl'; +import Icon from '@erxes/ui/src/components/Icon'; +import { Tabs, TabTitle } from '@erxes/ui/src/components/tabs'; +import { IOption } from '@erxes/ui/src/types'; +import { __ } from 'coreui/utils'; +import SelectTeamMembers from '@erxes/ui/src/team/containers/SelectTeamMembers'; +import React from 'react'; +import Select from 'react-select-plus'; +import RTG from 'react-transition-group'; +import { PRIORITIES } from '../constants'; +import SegmentFilter from '../containers/SegmentFilter'; +import dayjs from 'dayjs'; import { CustomRangeContainer, FilterBox, FilterButton, MenuFooter, RightMenuContainer, - TabContent, -} from "../styles/rightMenu"; -import { IOptions } from "../types"; -import Archive from "./Archive"; -import SelectLabel from "./label/SelectLabel"; -import { isEnabled } from "@erxes/ui/src/utils/core"; -import SelectBranches from "@erxes/ui/src/team/containers/SelectBranches"; -import SelectDepartments from "@erxes/ui/src/team/containers/SelectDepartments"; + TabContent +} from '../styles/rightMenu'; +import { IOptions } from '../types'; +import Archive from './Archive'; +import SelectLabel from './label/SelectLabel'; +import { isEnabled } from '@erxes/ui/src/utils/core'; +import SelectBranches from '@erxes/ui/src/team/containers/SelectBranches'; +import SelectDepartments from '@erxes/ui/src/team/containers/SelectDepartments'; type Props = { onSearch: (search: string) => void; @@ -54,8 +54,8 @@ export default class RightMenu extends React.Component { super(props); this.state = { - currentTab: "Filter", - showMenu: false, + currentTab: 'Filter', + showMenu: false }; this.setWrapperRef = this.setWrapperRef.bind(this); @@ -67,18 +67,18 @@ export default class RightMenu extends React.Component { } componentDidMount() { - document.addEventListener("click", this.handleClickOutside, true); + document.addEventListener('click', this.handleClickOutside, true); } componentWillUnmount() { - document.removeEventListener("click", this.handleClickOutside, true); + document.removeEventListener('click', this.handleClickOutside, true); } - handleClickOutside = (event) => { + handleClickOutside = event => { if ( this.wrapperRef && !this.wrapperRef.contains(event.target) && - this.state.currentTab === "Filter" + this.state.currentTab === 'Filter' ) { this.setState({ showMenu: false }); } @@ -89,9 +89,9 @@ export default class RightMenu extends React.Component { }; onSearch = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { const target = e.currentTarget as HTMLInputElement; - this.props.onSearch(target.value || ""); + this.props.onSearch(target.value || ''); } }; @@ -104,7 +104,7 @@ export default class RightMenu extends React.Component { const selected = queryParams[key] === value; - const onClick = (_e) => { + const onClick = _e => { onSelect(value, key); }; @@ -117,7 +117,7 @@ export default class RightMenu extends React.Component { } onChangeRangeFilter = (kind: string, date) => { - const formattedDate = date ? dayjs(date).format("YYYY-MM-DD") : ""; + const formattedDate = date ? dayjs(date).format('YYYY-MM-DD') : ''; const { queryParams, onSelect } = this.props; @@ -129,18 +129,18 @@ export default class RightMenu extends React.Component { renderDates() { const { link } = this.props; - if (link.includes("calendar")) { + if (link.includes('calendar')) { return null; } return ( <> - {this.renderLink("Assigned to me", "assignedToMe", "true")} - {this.renderLink("Due tomorrow", "closeDateType", "nextDay")} - {this.renderLink("Due next week", "closeDateType", "nextWeek")} - {this.renderLink("Due next month", "closeDateType", "nextMonth")} - {this.renderLink("Has no close date", "closeDateType", "noCloseDate")} - {this.renderLink("Overdue", "overdue", "closeDateType")} + {this.renderLink('Assigned to me', 'assignedToMe', 'true')} + {this.renderLink('Due tomorrow', 'closeDateType', 'nextDay')} + {this.renderLink('Due next week', 'closeDateType', 'nextWeek')} + {this.renderLink('Due next month', 'closeDateType', 'nextMonth')} + {this.renderLink('Has no close date', 'closeDateType', 'noCloseDate')} + {this.renderLink('Overdue', 'overdue', 'closeDateType')} ); } @@ -148,20 +148,20 @@ export default class RightMenu extends React.Component { renderFilter() { const { queryParams, onSelect, extraFilter, options } = this.props; - const priorityValues = PRIORITIES.map((p) => ({ label: p, value: p })); + const priorityValues = PRIORITIES.map(p => ({ label: p, value: p })); const priorities = queryParams ? queryParams.priority : []; const onPrioritySelect = (ops: IOption[]) => onSelect( - ops.map((option) => option.value), - "priority" + ops.map(option => option.value), + 'priority' ); return ( @@ -185,13 +185,13 @@ export default class RightMenu extends React.Component { onSelect={onSelect} />