Added new HTML editor with some breaking changes.

This commit is contained in:
Patrick Fic
2020-08-14 15:50:36 -07:00
parent 57cbc6961c
commit 873e81556d
20 changed files with 574 additions and 358 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file

View File

@@ -36,6 +36,7 @@
"react-big-calendar": "^0.26.0", "react-big-calendar": "^0.26.0",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-drag-listview": "^0.1.7", "react-drag-listview": "^0.1.7",
"react-email-editor": "^1.1.1",
"react-ga": "^3.1.2", "react-ga": "^3.1.2",
"react-grid-gallery": "^0.5.5", "react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.0.0", "react-grid-layout": "^1.0.0",

View File

@@ -45,7 +45,7 @@ export default function FormsFieldChanged({ form }) {
/> />
</div> </div>
); );
return null; return <div style={{ display: "none" }}></div>;
}} }}
</Form.Item> </Form.Item>
); );

View File

@@ -6,13 +6,23 @@ import Icon, {
FileFilled, FileFilled,
GlobalOutlined, GlobalOutlined,
HomeFilled, HomeFilled,
ImportOutlined,
LineChartOutlined,
ScheduleOutlined,
TeamOutlined, TeamOutlined,
UnorderedListOutlined,
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Avatar, Col, Layout, Menu, Row } from "antd"; import { Avatar, Layout, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { FaCalendarAlt, FaCarCrash, FaCreditCard } from "react-icons/fa"; import { BsKanban } from "react-icons/bs";
import {
FaCalendarAlt,
FaCarCrash,
FaCreditCard,
FaFileInvoiceDollar,
} from "react-icons/fa";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -42,30 +52,6 @@ const mapDispatchToProps = (dispatch) => ({
signOutStart: () => dispatch(signOutStart()), signOutStart: () => dispatch(signOutStart()),
}); });
const logoSpan = {
xs: {
span: 0,
},
sm: { span: 0 },
md: {
span: 0,
},
lg: {
span: 2,
},
};
const menuSpan = {
md: {
span: 24,
//offset: 1,
},
lg: {
span: 21,
offset: 1,
},
};
function Header({ function Header({
bodyshop, bodyshop,
handleMenuClick, handleMenuClick,
@@ -81,19 +67,6 @@ function Header({
return ( return (
<Header> <Header>
<Row>
<Col {...logoSpan}>
<img
className="header-shop-logo"
alt={bodyshop ? bodyshop.shopname : "ImEX Online Logo"}
src={
bodyshop && bodyshop.logo_img_path
? bodyshop.logo_img_path
: "./logo192.png"
}
/>
</Col>
<Col {...menuSpan}>
<Menu <Menu
mode="horizontal" mode="horizontal"
theme="dark" theme="dark"
@@ -107,6 +80,12 @@ function Header({
{t("menus.header.home")} {t("menus.header.home")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="schedule">
<Link to="/manage/schedule">
<Icon component={FaCalendarAlt} />
{t("menus.header.schedule")}
</Link>
</Menu.Item>
<Menu.SubMenu <Menu.SubMenu
title={ title={
<span> <span>
@@ -115,41 +94,49 @@ function Header({
</span> </span>
} }
> >
<Menu.Item key="schedule"> <Menu.Item key="activejobs">
<Link to="/manage/schedule"> <FileFilled />
<Icon component={FaCalendarAlt} /> <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
{t("menus.header.schedule")} </Menu.Item>
<Menu.Item key="availablejobs">
<Link to="/manage/available">
<ImportOutlined /> {t("menus.header.availablejobs")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Divider />
<Menu.Item key="alljobs">
<UnorderedListOutlined />
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item>
<Menu.Divider />
<Menu.Item key="productionlist"> <Menu.Item key="productionlist">
<Link to="/manage/production/list"> <Link to="/manage/production/list">
<Icon component={FaCalendarAlt} /> <ScheduleOutlined />
{t("menus.header.productionlist")} {t("menus.header.productionlist")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="productionboard"> <Menu.Item key="productionboard">
<Link to="/manage/production/board"> <Link to="/manage/production/board">
<Icon component={BsKanban} />
{t("menus.header.productionboard")} {t("menus.header.productionboard")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Divider />
<Menu.Item key="scoreboard"> <Menu.Item key="scoreboard">
<Link to="/manage/scoreboard"> <LineChartOutlined />
{t("menus.header.scoreboard")} <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
</Link>
</Menu.Item>
<Menu.Item key="activejobs">
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
</Menu.Item>
<Menu.Item key="alljobs">
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item>
<Menu.Item key="availablejobs">
<Link to="/manage/available">
{t("menus.header.availablejobs")}
</Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.customers")}> <Menu.SubMenu
title={
<span>
<UserOutlined />
<span>{t("menus.header.customers")}</span>
</span>
}
>
<Menu.Item key="owners"> <Menu.Item key="owners">
<Link to="/manage/owners"> <Link to="/manage/owners">
<TeamOutlined /> <TeamOutlined />
@@ -198,6 +185,25 @@ function Header({
</span> </span>
} }
> >
<Menu.Item key="invoices">
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link>
</Menu.Item>
<Menu.Item
key="enterinvoices"
onClick={() => {
setInvoiceEnterContext({
actions: {},
context: {},
});
}}
>
<Icon component={FaFileInvoiceDollar} />
{t("menus.header.enterinvoices")}
</Menu.Item>
<Menu.Divider />
<Menu.Item key="allpayments">
<Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
</Menu.Item>
<Menu.Item <Menu.Item
key="enterpayments" key="enterpayments"
onClick={() => { onClick={() => {
@@ -210,21 +216,8 @@ function Header({
<Icon component={FaCreditCard} /> <Icon component={FaCreditCard} />
{t("menus.header.enterpayment")} {t("menus.header.enterpayment")}
</Menu.Item> </Menu.Item>
<Menu.Divider />
<Menu.Item
key="enterinvoices"
onClick={() => {
setInvoiceEnterContext({
actions: {},
context: {},
});
}}
>
{t("menus.header.enterinvoices")}
</Menu.Item>
<Menu.Item key="invoices">
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link>
</Menu.Item>
<Menu.Item key="timetickets"> <Menu.Item key="timetickets">
<Link to="/manage/timetickets"> <Link to="/manage/timetickets">
{t("menus.header.timetickets")} {t("menus.header.timetickets")}
@@ -241,6 +234,7 @@ function Header({
> >
{t("menus.header.entertimeticket")} {t("menus.header.entertimeticket")}
</Menu.Item> </Menu.Item>
<Menu.Divider />
<Menu.SubMenu title={t("menus.header.export")}> <Menu.SubMenu title={t("menus.header.export")}>
<Menu.Item key="receivables"> <Menu.Item key="receivables">
@@ -259,12 +253,6 @@ function Header({
</Link> </Link>
</Menu.Item> </Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.Item key="allpayments">
<Link to="/manage/payments">
{t("menus.header.allpayments")}
</Link>
</Menu.Item>
</Menu.SubMenu> </Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.shop")}> <Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key="shop"> <Menu.Item key="shop">
@@ -324,14 +312,10 @@ function Header({
{t("user.actions.signout")} {t("user.actions.signout")}
</Menu.Item> </Menu.Item>
<Menu.Item key="shiftclock"> <Menu.Item key="shiftclock">
<Link to="/manage/shiftclock"> <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
{t("menus.header.shiftclock")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.Item> <Menu.Item>
<Link to="/manage/profile"> <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
{t("menus.currentuser.profile")}
</Link>
</Menu.Item> </Menu.Item>
<Menu.SubMenu <Menu.SubMenu
title={ title={
@@ -353,8 +337,6 @@ function Header({
</Menu.SubMenu> </Menu.SubMenu>
</Menu.SubMenu> </Menu.SubMenu>
</Menu> </Menu>
</Col>
</Row>
</Header> </Header>
); );
} }

View File

@@ -5,21 +5,33 @@ import { useTranslation } from "react-i18next";
import { UPDATE_TEMPLATE } from "../../graphql/templates.queries"; import { UPDATE_TEMPLATE } from "../../graphql/templates.queries";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
export default function ShopTemplateSaveButton({ templateId, html, gql }) { export default function ShopTemplateSaveButton({
templateId,
gql,
emailEditorRef,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateTemplate] = useMutation(UPDATE_TEMPLATE); const [updateTemplate] = useMutation(UPDATE_TEMPLATE);
const handleSave = async () => { const handleSave = async () => {
logImEXEvent("shop_template_update"); logImEXEvent("shop_template_update");
emailEditorRef.current.exportHtml(async (data) => {
console.log("handleSave -> html", data);
const result = await updateTemplate({ const result = await updateTemplate({
variables: { variables: {
templateId: templateId, templateId: templateId,
template: { query: gql, html }, template: {
query: gql,
html: data.html,
jsontemplate: data.design,
},
}, },
}); });
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ message: t("templates.successes.updated") }); notification["success"]({
message: t("templates.successes.updated"),
});
} else { } else {
notification["error"]({ notification["error"]({
message: t("templates.errors.updating", { message: t("templates.errors.updating", {
@@ -27,6 +39,7 @@ export default function ShopTemplateSaveButton({ templateId, html, gql }) {
}), }),
}); });
} }
});
}; };
return ( return (

View File

@@ -1,20 +1,22 @@
import { Editor } from "@tinymce/tinymce-react";
import React, { useEffect } from "react";
import ShopTemplateEditorSaveButton from "../shop-template-editor-save-button/shop-template-editor-save-button.component";
import { Input } from "antd"; import { Input } from "antd";
import React, { useEffect, useRef } from "react";
import EmailEditor from "react-email-editor";
import ShopTemplateEditorSaveButton from "../shop-template-editor-save-button/shop-template-editor-save-button.component";
export default function ShopTemplateEditorComponent({ export default function ShopTemplateEditorComponent({
templateId, templateId,
html, html,
gql, gql,
json,
editorState, editorState,
}) { }) {
const [editorContent, seteditorContent] = editorState; const [editorContent, seteditorContent] = editorState;
const emailEditorRef = useRef(null);
useEffect(() => { useEffect(() => {
seteditorContent((prevstate) => { if (json && Object.keys(json).length > 0)
return { ...prevstate, html: html }; emailEditorRef.current.loadDesign(json);
}); }, [json, emailEditorRef]);
}, [html, seteditorContent]);
useEffect(() => { useEffect(() => {
seteditorContent((prevstate) => { seteditorContent((prevstate) => {
@@ -22,34 +24,29 @@ export default function ShopTemplateEditorComponent({
}); });
}, [gql, seteditorContent]); }, [gql, seteditorContent]);
const exportJson = (props) => {
emailEditorRef.current.saveDesign((js) => {
seteditorContent({ ...editorContent, JSON: js });
console.log(js);
});
};
const exportHtml = (props) => {
emailEditorRef.current.exportHtml((js) => {
console.log(js);
});
};
return ( return (
<div> <div>
<ShopTemplateEditorSaveButton <ShopTemplateEditorSaveButton
templateId={templateId} templateId={templateId}
html={editorContent.html}
gql={editorContent.gql} gql={editorContent.gql}
emailEditorRef={emailEditorRef}
/> />
<Editor <button onClick={exportJson}>json</button>
value={editorContent.html} <button onClick={exportHtml}>html</button>
apiKey='f3s2mjsd77ya5qvqkee9vgh612cm6h41e85efqakn2d0kknk' //TODO Pull this into app var <EmailEditor ref={emailEditorRef} />
init={{
height: 500,
//menubar: false,
encoding: "raw",
extended_valid_elements: "span",
//entity_encoding: "raw",
plugins: [
"advlist autolink lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table paste code help wordcount",
],
toolbar:
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
}}
onEditorChange={(text) =>
seteditorContent({ ...editorContent, html: text })
}
/>
QUERY QUERY
<Input.TextArea <Input.TextArea
value={editorContent.gql} value={editorContent.gql}

View File

@@ -19,14 +19,14 @@ export default function ShopTemplateEditorContainer() {
}); });
if (!!!search.customTemplateId) return <span>No selection.</span>; if (!!!search.customTemplateId) return <span>No selection.</span>;
if (error) return <AlertComponent message={error.message} type='error' />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<LoadingSpinner loading={loading}> <LoadingSpinner loading={loading}>
{data && data.templates_by_pk ? data.templates_by_pk.name : ""} {data && data.templates_by_pk ? data.templates_by_pk.name : ""}
<ShopTemplateEditorComponent <ShopTemplateEditorComponent
templateId={search.customTemplateId} templateId={search.customTemplateId}
html={data && data.templates_by_pk ? data.templates_by_pk.html : ""} json={data && data.templates_by_pk && data.templates_by_pk.jsontemplate}
gql={data && data.templates_by_pk ? data.templates_by_pk.query : ""} gql={data && data.templates_by_pk ? data.templates_by_pk.query : ""}
editorState={editorState} editorState={editorState}
/> />

View File

@@ -4,9 +4,9 @@ export const QUERY_TEMPLATES_BY_NAME = gql`
query QUERY_TEMPLATES_BY_NAME($name: String!) { query QUERY_TEMPLATES_BY_NAME($name: String!) {
templates(where: { name: { _eq: $name } }) { templates(where: { name: { _eq: $name } }) {
id id
html
name name
query query
html
bodyshopid bodyshopid
} }
} }
@@ -28,6 +28,7 @@ export const QUERY_TEMPLATE_BY_PK = gql`
name name
query query
html html
jsontemplate
} }
} }
`; `;

View File

@@ -32,10 +32,10 @@ export function ShopTemplatesContainer({ setBreadcrumbs, bodyshop }) {
return ( return (
<RbacWrapper action="shop:templates"> <RbacWrapper action="shop:templates">
<Row> <Row>
<Col span={6}> <Col span={4}>
<ShopTemplatesListContainer /> <ShopTemplatesListContainer />
</Col> </Col>
<Col span={18}> <Col span={20}>
<div> <div>
<ShopTemplateEditor /> <ShopTemplateEditor />
</div> </div>

View File

@@ -938,8 +938,8 @@
"invoices": "Invoices", "invoices": "Invoices",
"jobs": "Jobs", "jobs": "Jobs",
"owners": "Owners", "owners": "Owners",
"productionboard": "Production Board", "productionboard": "Production Board - Visual",
"productionlist": "Production - List", "productionlist": "Production Board - List",
"recent": "Recent Items", "recent": "Recent Items",
"schedule": "Schedule", "schedule": "Schedule",
"scoreboard": "Scoreboard", "scoreboard": "Scoreboard",
@@ -983,7 +983,7 @@
"joblookup": "Job Lookup", "joblookup": "Job Lookup",
"login": "Login", "login": "Login",
"logout": "Logout", "logout": "Logout",
"productionboard": "Production Board", "productionboard": "Production Board - Visual",
"productionlist": "Production List" "productionlist": "Production List"
} }
}, },
@@ -1301,8 +1301,8 @@
"owner-detail": "{{name}}", "owner-detail": "{{name}}",
"owners": "Owners", "owners": "Owners",
"payments-all": "All Payments", "payments-all": "All Payments",
"productionboard": "Production Board", "productionboard": "Production Board - Visual",
"productionlist": "Production - List", "productionlist": "Production Board - List",
"schedule": "Schedule", "schedule": "Schedule",
"scoreboard": "Scoreboard", "scoreboard": "Scoreboard",
"shop": "Manage my Shop ({{shopname}})", "shop": "Manage my Shop ({{shopname}})",
@@ -1332,7 +1332,7 @@
"owners-detail": "{{name}} | $t(titles.app)", "owners-detail": "{{name}} | $t(titles.app)",
"payments-all": "Payments | $t(titles.app)", "payments-all": "Payments | $t(titles.app)",
"productionboard": "Production - Board", "productionboard": "Production - Board",
"productionlist": "Production - List View | $t(titles.app)", "productionlist": "Production Board - List | $t(titles.app)",
"profile": "My Profile | $t(titles.app)", "profile": "My Profile | $t(titles.app)",
"resetpassword": "Reset Password", "resetpassword": "Reset Password",
"resetpasswordvalidate": "Enter New Password", "resetpasswordvalidate": "Enter New Password",

View File

@@ -10876,6 +10876,11 @@ react-draggable@^4.0.0, react-draggable@^4.0.3:
classnames "^2.2.5" classnames "^2.2.5"
prop-types "^15.6.0" prop-types "^15.6.0"
react-email-editor@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/react-email-editor/-/react-email-editor-1.1.1.tgz#5a809c5588cf0a2042cf144771ef340cded36f92"
integrity sha512-8V4Iba3dSw0eTmNQlmhisPv2EsiWAwEBVvKsWFiW/BcQ1x42vchbWCNCX7ss0njMQ9r7uReSptrcy10f4MtVEA==
react-error-overlay@^6.0.7: react-error-overlay@^6.0.7:
version "6.0.7" version "6.0.7"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.7.tgz#1dcfb459ab671d53f660a991513cb2f0a0553108"

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."templates" DROP COLUMN "jsontemplate";
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."templates" ADD COLUMN "jsontemplate" jsonb NULL DEFAULT
jsonb_build_object();
type: run_sql

View File

@@ -0,0 +1,31 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_insert_permission
- args:
permission:
check:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- bodyshopid
- html
- name
- query
set: {}
role: user
table:
name: templates
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,32 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_insert_permission
- args:
permission:
check:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- bodyshopid
- html
- jsontemplate
- name
- query
set: {}
role: user
table:
name: templates
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,35 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- html
- name
- query
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: templates
schema: public
type: create_select_permission

View File

@@ -0,0 +1,36 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- bodyshopid
- created_at
- html
- id
- jsontemplate
- name
- query
- updated_at
computed_fields: []
filter:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: templates
schema: public
type: create_select_permission

View File

@@ -0,0 +1,34 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_update_permission
- args:
permission:
columns:
- html
- name
- query
- created_at
- updated_at
- bodyshopid
- id
filter:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: templates
schema: public
type: create_update_permission

View File

@@ -0,0 +1,35 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_update_permission
- args:
permission:
columns:
- bodyshopid
- created_at
- html
- id
- jsontemplate
- name
- query
- updated_at
filter:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: templates
schema: public
type: create_update_permission

View File

@@ -3569,19 +3569,21 @@ tables:
columns: columns:
- bodyshopid - bodyshopid
- html - html
- jsontemplate
- name - name
- query - query
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- bodyshopid
- created_at
- html - html
- id
- jsontemplate
- name - name
- query - query
- created_at
- updated_at - updated_at
- bodyshopid
- id
filter: filter:
_or: _or:
- bodyshopid: - bodyshopid:
@@ -3598,13 +3600,14 @@ tables:
- role: user - role: user
permission: permission:
columns: columns:
- bodyshopid
- created_at
- html - html
- id
- jsontemplate
- name - name
- query - query
- created_at
- updated_at - updated_at
- bodyshopid
- id
filter: filter:
_or: _or:
- bodyshopid: - bodyshopid: