diff --git a/components/core/FontFrame/Settings/index.js b/components/core/FontFrame/Settings/index.js index c5f70999..cde14f8a 100644 --- a/components/core/FontFrame/Settings/index.js +++ b/components/core/FontFrame/Settings/index.js @@ -19,6 +19,7 @@ const STYLES_CONTROLLER_WRAPPER = (theme) => export const Controls = ({ view, + customView, settings, defaultOptions, updateView, @@ -31,6 +32,8 @@ export const Controls = ({ getRandomLayout, resetLayout, }) => { + const isCustomView = (value) => view === "custom" && customView === value; + const arrayToSelectOptions = (arr) => arr.reduce((acc, option) => [...acc, { value: option, name: Strings.capitalize(option) }], []); @@ -120,7 +123,7 @@ export const Controls = ({ value={settings.column} onChange={(e) => updateColumn(e.target.value)} selectMinWidth="none" - disabled={view !== "paragraph"} + disabled={!(view === "paragraph" || isCustomView("paragraph"))} /> ); diff --git a/components/core/FontFrame/Views/ContentEditable.js b/components/core/FontFrame/Views/ContentEditable.js new file mode 100644 index 00000000..a82b664c --- /dev/null +++ b/components/core/FontFrame/Views/ContentEditable.js @@ -0,0 +1,51 @@ +import * as React from "react"; + +function normalizeHtml(str) { + return str && str.replace(/ |\u202F|\u00A0/g, " "); +} + +export default class ContentEditable extends React.Component { + constructor(props) { + super(props); + this.myRef = React.createRef(); + } + + shouldComponentUpdate(nextProps, nextState) { + const { html } = nextProps; + const el = this.myRef.current; + + if (normalizeHtml(el.innerHTML) !== normalizeHtml(html)) return true; + return false; + } + + componentDidUpdate() { + if (!this.myRef) return; + const { html } = this.props; + const el = this.myRef.current; + /** NOTE(Amine): because we often prevent rerendering, + * React doesn't update the Dom, so we do it manually + */ + if (normalizeHtml(el.innerHTML) !== normalizeHtml(html)) { + el.innerHTML = this.props.html; + } + } + + handleChange = () => { + const el = this.myRef.current; + if (!el) return; + this.props.onChange(el.innerHTML); + }; + + render() { + const { onChange, html, ...props } = this.props; + return ( +
+ ); + } +} diff --git a/components/core/FontFrame/Views/Paragraph.js b/components/core/FontFrame/Views/Paragraph.js index 7228a4ec..5863df58 100644 --- a/components/core/FontFrame/Views/Paragraph.js +++ b/components/core/FontFrame/Views/Paragraph.js @@ -1,36 +1,34 @@ import * as React from "react"; +import ContentEditable from "./ContentEditable"; + import { css } from "@emotion/react"; -const STYLES_PARAGRAPH = (theme) => css` - width: 100%; - margin-top: 12px; - color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack}; - padding: 0px 32px 28px; - word-break: break-word; - white-space: pre-wrap; +const STYLES_PARAGRAPH_WRAPPER = (theme) => css` + display: flex; + height: 100%; + .font_frame_paragraph { + width: 100%; + margin-top: 12px; + color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack}; + padding: 0px 32px 28px; + word-break: break-word; + white-space: pre-wrap; - &:focus { - outline: none; + &:focus { + outline: none; + } } `; const STYLES_TYPE_TO_EDIT = (isFocused) => (theme) => css` - ::after { + .font_frame_paragraph::after { content: " type to edit"; color: ${theme.fontPreviewDarkMode ? theme.system.textGrayDark : theme.system.textGrayLight}; opacity: ${isFocused ? 0 : 1}; } `; -const MemoizedChild = React.memo( - ({ children }) => { - return
{children}
; - }, - (prevProps, nextProps) => !nextProps.shouldUpdateView -); - export default function Paragraph({ - shouldUpdateView, content, valign, textAlign, @@ -51,35 +49,33 @@ export default function Paragraph({ }; return ( -
-
+ e.stopPropagation()} - onInput={(e) => { - onChange(e.currentTarget.innerText); - }} + html={content} + onChange={onChange} onFocus={handleFocus} onBlur={handleBlur} - > - {content} -
+ />
); } diff --git a/components/core/FontFrame/Views/Sentence.js b/components/core/FontFrame/Views/Sentence.js index b55f1e7f..1cbd015c 100644 --- a/components/core/FontFrame/Views/Sentence.js +++ b/components/core/FontFrame/Views/Sentence.js @@ -2,31 +2,29 @@ import * as React from "react"; import { css } from "@emotion/react"; -const STYLES_SENTENCE = (theme) => css` - width: 100%; - margin-top: 12px; - color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack}; - padding: 0px 32px 28px; - word-break: break-word; - &:focus { - outline: none; +import ContentEditable from "./ContentEditable"; + +const STYLES_SENTENCE_WRAPPER = (theme) => css` + .font_frame_sentence { + width: 100%; + margin-top: 12px; + color: ${theme.fontPreviewDarkMode ? theme.system.white : theme.system.pitchBlack}; + padding: 0px 32px 28px; + word-break: break-word; + &:focus { + outline: none; + } } `; const STYLES_TYPE_TO_EDIT = (isFocused) => (theme) => css` - ::after { + .font_frame_sentence::after { content: " type to edit"; color: ${theme.fontPreviewDarkMode ? theme.system.textGrayDark : theme.system.textGrayLight}; opacity: ${isFocused ? 0 : 1}; } `; -const MemoizedChild = React.memo( - ({ children }) => children, - (prevProps, nextProps) => !nextProps.shouldUpdateView -); - export default function Sentence({ - shouldUpdateView, content, valign, textAlign, @@ -45,29 +43,34 @@ export default function Sentence({ bottom: { marginTop: "auto" }, }; return ( -
-
+ { e.stopPropagation(); }} - onInput={(e) => { - onChange(e.currentTarget.innerHTML); - }} + onChange={onChange} onFocus={handleFocus} onBlur={handleBlur} - > - {content} -
+ html={content} + />
); } diff --git a/components/core/FontFrame/Views/index.js b/components/core/FontFrame/Views/index.js index 90f19843..525f1c21 100644 --- a/components/core/FontFrame/Views/index.js +++ b/components/core/FontFrame/Views/index.js @@ -7,7 +7,6 @@ export default function FontView({ view, customView, content: { sentence, paragraph, custom }, - shouldUpdateView, updateCustomView, }) { const isCustomView = (value) => view === "custom" && customView === value; @@ -19,7 +18,6 @@ export default function FontView({ if (view === "paragraph" || isCustomView("paragraph")) { return ( { return { ...state, view: action.value, - context: { ...state.context, shouldUpdateView: true }, + context: { ...state.context }, }; case "UPDATE_CUSTOM_VIEW": return { @@ -113,7 +112,6 @@ const reducer = (state, action) => { context: { ...state.context, customViewContent: action.payload.customViewContent, - shouldUpdateView: false, }, }; case "RESET": diff --git a/components/core/FontFrame/index.js b/components/core/FontFrame/index.js index 1638e806..2662ec96 100644 --- a/components/core/FontFrame/index.js +++ b/components/core/FontFrame/index.js @@ -86,7 +86,6 @@ export default function FontFrame({ cid, url, ...props }) { custom: currentState.context.customViewContent, }} customView={currentState.customView} - shouldUpdateView={currentState.context.shouldUpdateView} settings={currentState.context.settings} updateCustomView={updateCustomView} /> @@ -95,6 +94,7 @@ export default function FontFrame({ cid, url, ...props }) { {currentState.context.showSettings && (