lemail2
Version:
Strapi Email designer plugin.
269 lines (237 loc) • 8.35 kB
JavaScript
/*
*
* Designer
*
*/
import React, { useState, useEffect, memo, useRef } from 'react';
import { Button, Textarea } from '@buffetjs/core';
import { Prompt, useHistory, useParams } from 'react-router-dom';
import { BackHeader, InputText, useGlobalContext, request } from 'strapi-helper-plugin';
import { merge } from 'lodash';
import EmailEditor from 'react-email-editor';
import styled from 'styled-components';
import pluginId from '../../pluginId';
import getTrad from '../../utils/getTrad';
import TabsNav from '../../components/Tabs';
import MediaLibrary from '../../components/MediaLibrary';
const DesignerContainer = styled.div`
padding: 18px 30px;
min-height: calc(100vh - 6rem);
display: flex;
flex-direction: column;
`;
const Bar = styled.div`
flex: 1;
/* background-color: #dedede; */
color: #000;
padding: 0 0 20px 0;
display: flex;
max-height: 60px;
justify-content: space-between;
h1 {
flex: 1;
font-size: 16px;
text-align: left;
}
`;
const EmailDesigner = () => {
const history = useHistory();
const { templateId } = useParams();
const emailEditorRef = useRef(null);
const [templateData, setTemplateData] = useState();
const [enablePrompt, togglePrompt] = useState(false);
const [bodyText, setBodyText] = useState('');
const [isMediaLibraryOpen, setIsMediaLibraryOpen] = useState(false);
const [mode, setMode] = useState('html');
const { formatMessage } = useGlobalContext();
const [filesToUpload, setFilesToUpload] = useState({});
const [configLoaded, setConfigLoaded] = useState(false);
const defaultEditorTools = {
image: {
properties: {
src: {
value: {
url: `https://picsum.photos/600/350`,
},
},
},
},
}
const [editorTools, setEditorTools] = useState({ ...defaultEditorTools });
const [editorOptions, setEditorOptions] = useState({});
const isMounted = useRef(true);
useEffect(() => {
(async () => {
const editorConfig = (await request(`/${pluginId}/config`, { method: 'GET' })).config.editor;
if (isMounted.current && editorConfig) {
if (editorConfig.tools) {
setEditorTools(merge(defaultEditorTools, editorConfig.tools))
}
if (editorConfig.options) {
setEditorOptions(editorConfig.options)
}
}
setConfigLoaded(true)
})();
}, []);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
if (!emailEditorRef.current || templateId === '' || templateId === 'new') return;
(async () => {
const _templateData = await request(`/${pluginId}/templates/${templateId}`, { method: 'GET' });
setTemplateData(_templateData);
setBodyText(_templateData.bodyText);
emailEditorRef.current?.editor?.loadDesign(_templateData.design);
})();
}, [configLoaded, templateId]);
const saveDesign = () => {
emailEditorRef.current.editor.exportHtml(async (data) => {
const { design, html } = data;
try {
// strapi.lockAppWithOverlay();
const response = await request(`/${pluginId}/templates/${templateId}`, {
method: 'POST',
body: {
name: templateData?.name || formatMessage({ id: getTrad('noName') }),
subject: templateData?.subject || '',
design,
bodyText,
bodyHtml: html,
},
});
strapi.notification.toggle({
type: 'success',
message: { id: getTrad('notification.success.submit') },
});
togglePrompt(false);
if (templateId === 'new' && templateId !== response.id)
history.replace(`/plugins/${pluginId}/design/${response.id}`);
} catch (err) {
console.error(err);
strapi.notification.toggle({
type: 'warning',
message: { id: 'notification.error' },
});
}
});
};
const onDesignLoad = () => {
// eslint-disable-next-line no-unused-vars
emailEditorRef.current.editor.addEventListener('design:updated', (data) => {
/*
let { type, item, changes } = data;
console.log("design:updated", type, item, changes);
*/
togglePrompt(true);
});
};
const onLoadHandler = () => {
// ⬇︎ workaround to avoid firing onLoad api before setting the editor ref
setTimeout(() => {
emailEditorRef.current?.editor?.addEventListener('onDesignLoad', onDesignLoad);
emailEditorRef.current?.editor?.registerCallback('image', onImageImageHandler);
if (templateData) emailEditorRef.current.editor.loadDesign(templateData.design);
}, 500);
};
// Custom media uploads
const [imageUploadDoneCallback, setImageUploadDoneCallback] = useState(undefined);
const onImageImageHandler = (data, done) => {
const filesObject = Object.assign({}, data.accepted);
setFilesToUpload(filesObject);
setImageUploadDoneCallback(() => done);
setIsMediaLibraryOpen(true);
};
const handleMediaLibraryChange = (data) => {
if (imageUploadDoneCallback) {
imageUploadDoneCallback({ progress: 100, url: data.url })
setImageUploadDoneCallback(undefined)
} else console.log(imageUploadDoneCallback)
};
const handleToggleMediaLibrary = () => {
setIsMediaLibraryOpen((prev) => !prev)
};
return (
<>
<BackHeader onClick={history.goBack} />
<DesignerContainer className="container-fluid">
<Prompt message={formatMessage({ id: getTrad('prompt.unsaved') })} when={enablePrompt} />
<>
<Bar>
<InputText
// error={formErrors[input.name]}
name="name"
onChange={({ target: { value } }) => {
setTemplateData((state) => ({ ...state, name: value }));
}}
placeholder={getTrad('templateNameInputFieldPlaceholder')}
type="text"
value={templateData?.name || ''}
style={{ marginTop: 0, width: '40%', marginRight: 10 }}
/>
<InputText
// error={formErrors[input.name]}
name="subject"
onChange={({ target: { value } }) => {
setTemplateData((state) => ({ ...state, subject: value }));
}}
placeholder={getTrad('templateSubjectInputFieldPlaceholder')}
type="text"
value={templateData?.subject || ''}
style={{ marginTop: 0, width: '60%', marginRight: 10 }}
/>
<Button onClick={saveDesign} color="success">
{formatMessage({ id: getTrad('designer.action.saveTemplate') })}
</Button>
</Bar>
<TabsNav
links={[
{
isActive: mode === 'html',
name: getTrad('designer.version.html'),
onClick: () => setMode('html'),
},
{
isActive: mode === 'text',
name: getTrad('designer.version.text'),
onClick: () => setMode('text'),
},
]}
style={{ marginTop: '0.4rem', height: '5rem' }}
/>
{configLoaded && <div style={{ height: '100%', display: mode === 'html' ? 'flex' : 'none' }}>
<React.StrictMode>
<EmailEditor
ref={emailEditorRef}
onLoad={onLoadHandler}
style={{
border: '1px solid #dedede',
}}
appearance={{
minWidth: '100%',
theme: 'light',
}}
locale={strapi.currentLanguage}
tools={editorTools}
options={editorOptions}
/>
</React.StrictMode>
</div>}
<div style={{ display: mode === 'text' ? 'block' : 'none' }}>
<Textarea name="textarea" onChange={({ target: { value } }) => setBodyText(value)} value={bodyText} />
</div>
</>
</DesignerContainer>
<MediaLibrary
onToggle={handleToggleMediaLibrary}
isOpen={isMediaLibraryOpen}
onChange={handleMediaLibraryChange}
filesToUpload={filesToUpload}
/>
</>
);
};
export default memo(EmailDesigner);