From 9b1bd53427f1cded809adcfad341ab5fee484c53 Mon Sep 17 00:00:00 2001 From: MAX-786 Date: Tue, 2 Jul 2024 21:58:52 +0530 Subject: [PATCH 1/8] add field to select/enter url --- .../components/manage/Form/Form.jsx | 2 +- .../Preferences/PersonalPreferences.jsx | 195 ++++++++++++++++++ 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx diff --git a/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx b/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx index 00b06a0..ce60d07 100644 --- a/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx +++ b/packages/volto-hydra/src/customizations/components/manage/Form/Form.jsx @@ -324,7 +324,7 @@ class Form extends Component { ) { this.setState(() => ({ sidebarMetadataIsAvailable: true })); } - if (this.props.location.pathname !== prevProps.location.pathname) { + if (this.props?.location?.pathname !== prevProps?.location?.pathname) { this.setState({ formData: this.props.formData }); } } diff --git a/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx b/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx new file mode 100644 index 0000000..86506c3 --- /dev/null +++ b/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx @@ -0,0 +1,195 @@ +/** + * Personal preferences component. + * @module components/manage/Preferences/PersonalPreferences + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { map, keys } from 'lodash'; +import { withCookies } from 'react-cookie'; +import { defineMessages, injectIntl } from 'react-intl'; +import { toast } from 'react-toastify'; + +import { Toast } from '@plone/volto/components'; +import { Form } from '@plone/volto/components/manage/Form'; +import languages from '@plone/volto/constants/Languages'; +import { changeLanguage } from '@plone/volto/actions'; +import { toGettextLang } from '@plone/volto/helpers'; +import config from '@plone/volto/registry'; + +const urls = { + URL1: 'http://localhost:3000', + URL2: 'http://localhost:3001', + URL3: 'http://localhost:3002', +}; + +const messages = defineMessages({ + personalPreferences: { + id: 'Personal Preferences', + defaultMessage: 'Personal Preferences', + }, + default: { + id: 'Default', + defaultMessage: 'Default', + }, + language: { + id: 'Language', + defaultMessage: 'Language', + }, + languageDescription: { + id: 'Your preferred language', + defaultMessage: 'Your preferred language', + }, + saved: { + id: 'Changes saved', + defaultMessage: 'Changes saved', + }, + back: { + id: 'Back', + defaultMessage: 'Back', + }, + success: { + id: 'Success', + defaultMessage: 'Success', + }, + frontendUrls: { + id: 'Select Frontend URL', + defaultMessage: 'Select Frontend URL', + }, + frontendUrl: { + id: 'Enter Frontend URL', + defaultMessage: 'Enter Frontend URL', + }, + urlsDescription: { + id: `Select your Frontend's base URL`, + defaultMessage: `Select your Frontend's base URL`, + }, + urlDescription: { + id: `OR Enter your Frontend's base URL`, + defaultMessage: `OR Enter your Frontend's base URL`, + }, +}); + +/** + * PersonalPreferences class. + * @class PersonalPreferences + * @extends Component + */ +class PersonalPreferences extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + changeLanguage: PropTypes.func.isRequired, + closeMenu: PropTypes.func.isRequired, + }; + + /** + * Constructor + * @method constructor + * @param {Object} props Component properties + * @constructs PersonalPreferences + */ + constructor(props) { + super(props); + this.onCancel = this.onCancel.bind(this); + this.onSubmit = this.onSubmit.bind(this); + } + + /** + * Submit handler + * @method onSubmit + * @param {object} data Form data. + * @returns {undefined} + */ + onSubmit(data) { + let language = data.language || 'en'; + if (config.settings.supportedLanguages.includes(language)) { + const langFileName = toGettextLang(language); + import('@root/../locales/' + langFileName + '.json').then((locale) => { + this.props.changeLanguage(language, locale.default); + }); + } + toast.success( + , + ); + this.props.closeMenu(); + } + + /** + * Cancel handler + * @method onCancel + * @returns {undefined} + */ + onCancel() { + this.props.closeMenu(); + } + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { + const { cookies } = this.props; + return ( +
[lang, languages[lang]]), + }, + urls: { + description: this.props.intl.formatMessage( + messages.urlsDescription, + ), + title: this.props.intl.formatMessage(messages.frontendUrls), + type: 'string', + choices: map(keys(urls), (url) => [url, urls[url]]), + }, + url: { + description: this.props.intl.formatMessage( + messages.urlDescription, + ), + title: this.props.intl.formatMessage(messages.frontendUrl), + type: 'string', + }, + }, + required: [], + }} + onSubmit={this.onSubmit} + onCancel={this.onCancel} + onChangeFormData={(newFormData) => { + console.log(newFormData); + return; + }} + /> + ); + } +} + +export default compose( + injectIntl, + withCookies, + connect(null, { changeLanguage }), +)(PersonalPreferences); From 3512b76457e0b956ee7e8b5acd429d6264103822 Mon Sep 17 00:00:00 2001 From: MAX-786 Date: Tue, 2 Jul 2024 22:52:11 +0530 Subject: [PATCH 2/8] change ui: show/hide enter url input when it is checked or not --- .../Preferences/PersonalPreferences.jsx | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx b/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx index 86506c3..9dcf829 100644 --- a/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx +++ b/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx @@ -98,6 +98,9 @@ class PersonalPreferences extends Component { super(props); this.onCancel = this.onCancel.bind(this); this.onSubmit = this.onSubmit.bind(this); + this.state = { + hidden: true, + }; } /** @@ -127,6 +130,7 @@ class PersonalPreferences extends Component { */ onCancel() { this.props.closeMenu(); + toast.error(); } /** @@ -147,7 +151,12 @@ class PersonalPreferences extends Component { { id: 'default', title: this.props.intl.formatMessage(messages.default), - fields: ['language', 'urls', 'url'], + fields: ['language'], + }, + { + id: 'frontend', + title: 'Frontend', + fields: ['urls', 'urlCheck', 'url'], }, ], properties: { @@ -166,13 +175,19 @@ class PersonalPreferences extends Component { title: this.props.intl.formatMessage(messages.frontendUrls), type: 'string', choices: map(keys(urls), (url) => [url, urls[url]]), + mode: !this.state.hidden ? 'hidden' : '', }, - url: { + urlCheck: { description: this.props.intl.formatMessage( messages.urlDescription, ), + title: this.props.intl.formatMessage(messages.frontendUrl), + type: 'boolean', + }, + url: { title: this.props.intl.formatMessage(messages.frontendUrl), type: 'string', + mode: this.state.hidden ? 'hidden' : '', }, }, required: [], @@ -180,7 +195,11 @@ class PersonalPreferences extends Component { onSubmit={this.onSubmit} onCancel={this.onCancel} onChangeFormData={(newFormData) => { - console.log(newFormData); + if (newFormData.urlCheck) { + this.setState({ hidden: false }); + } else { + this.setState({ hidden: true }); + } return; }} /> From 03b46599b656fd5a0b29a6bc09ad2272bf937386 Mon Sep 17 00:00:00 2001 From: MAX-786 Date: Wed, 3 Jul 2024 00:09:10 +0530 Subject: [PATCH 3/8] Add URL validation and saving functionality to PersonalPreferences component --- .../Preferences/PersonalPreferences.jsx | 40 ++++++++++++++----- .../volto-hydra/src/utils/getSavedURLs.js | 33 +++++++++++++++ packages/volto-hydra/src/utils/isValidUrl.js | 9 ++++- 3 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 packages/volto-hydra/src/utils/getSavedURLs.js diff --git a/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx b/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx index 9dcf829..d30a06c 100644 --- a/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx +++ b/packages/volto-hydra/src/customizations/components/manage/Preferences/PersonalPreferences.jsx @@ -18,12 +18,8 @@ import languages from '@plone/volto/constants/Languages'; import { changeLanguage } from '@plone/volto/actions'; import { toGettextLang } from '@plone/volto/helpers'; import config from '@plone/volto/registry'; - -const urls = { - URL1: 'http://localhost:3000', - URL2: 'http://localhost:3001', - URL3: 'http://localhost:3002', -}; +import getSavedURLs from '../../../../utils/getSavedURLs'; +import isValidUrl from '../../../../utils/isValidUrl'; const messages = defineMessages({ personalPreferences: { @@ -98,6 +94,7 @@ class PersonalPreferences extends Component { super(props); this.onCancel = this.onCancel.bind(this); this.onSubmit = this.onSubmit.bind(this); + this.urls = getSavedURLs(); this.state = { hidden: true, }; @@ -118,8 +115,31 @@ class PersonalPreferences extends Component { }); } toast.success( - , + , ); + if (data.urlCheck) { + if (!isValidUrl(data.url)) { + toast.error( + , + ); + return; + } + const urlList = [...new Set([this.urls, data.url])]; + this.props.cookies.set('saved_urls', urlList.join(','), { + expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 Days + }); + console.log('data.url', data.url); + } else { + console.log('data.urls', data.urls); + } this.props.closeMenu(); } @@ -130,7 +150,6 @@ class PersonalPreferences extends Component { */ onCancel() { this.props.closeMenu(); - toast.error(); } /** @@ -140,6 +159,7 @@ class PersonalPreferences extends Component { */ render() { const { cookies } = this.props; + const urls = this.urls; return ( [url, urls[url]]), + choices: map(urls, (url) => [url, url]), mode: !this.state.hidden ? 'hidden' : '', }, urlCheck: { diff --git a/packages/volto-hydra/src/utils/getSavedURLs.js b/packages/volto-hydra/src/utils/getSavedURLs.js new file mode 100644 index 0000000..230a466 --- /dev/null +++ b/packages/volto-hydra/src/utils/getSavedURLs.js @@ -0,0 +1,33 @@ +import Cookies from 'js-cookie'; +import isValidUrl from './isValidUrl'; + +/** + * Get the default URL(s) from the environment + * @returns {Array} URL(s) from the environment + */ +export const getURlsFromEnv = () => { + const presetUrlsString = + process.env['RAZZLE_DEFAULT_IFRAME_URL'] || + (typeof window !== 'undefined' && + window.env['RAZZLE_DEFAULT_IFRAME_URL']) || + 'http://localhost:3002'; // fallback if env is not set + + const presetUrls = presetUrlsString.split(','); + return presetUrls; +}; + +/** + * Get the saved URLs from the cookies + * @returns {Array} Saved URLs + */ +const getSavedURLs = () => { + const urls = Cookies.get('saved_urls') + ? Cookies.get('saved_urls').split(',') + : []; + const savedUrls = [ + ...new Set([...urls, ...getURlsFromEnv()]), // Merge saved URLs with default URLs make sure they are unique + ]; + return savedUrls.filter(isValidUrl); +}; + +export default getSavedURLs; diff --git a/packages/volto-hydra/src/utils/isValidUrl.js b/packages/volto-hydra/src/utils/isValidUrl.js index 386957f..2659a61 100644 --- a/packages/volto-hydra/src/utils/isValidUrl.js +++ b/packages/volto-hydra/src/utils/isValidUrl.js @@ -1,6 +1,11 @@ -export default function isValidUrl(string) { +/** + * Check if the URL is valid + * @param {URL} string + * @returns bool + */ +export default function isValidUrl(url) { try { - new URL(string); + new URL(url); return true; } catch (error) { return false; From fc833cd498f79c6e6b2618a22163dcc1c0195b08 Mon Sep 17 00:00:00 2001 From: MAX-786 Date: Fri, 5 Jul 2024 10:00:56 +0530 Subject: [PATCH 4/8] Update iframe Src handling - Replace SET_SELECTED_BLOCK constant with SET_FRONTEND_PREVIEW_URL in actions.js and reducers.js - Adjust height calculation in styles.css for Iframe component - Remove unused code in utils/getSavedURLs.js - Update config.addonReducers in index.js to use frontendPreviewUrl reducer - Comment out Breadcrumbs component in App.jsx --- packages/volto-hydra/src/actions.js | 8 +- .../src/components/Iframe/View.jsx | 76 ++-- .../src/components/Iframe/styles.css | 2 +- packages/volto-hydra/src/constants.js | 2 +- .../components/manage/Form/Form.jsx | 328 +++++++++--------- .../Preferences/PersonalPreferences.jsx | 30 +- .../components/theme/App/App.jsx | 2 +- .../components/theme/Login/Login.original.jsx | 259 -------------- packages/volto-hydra/src/index.js | 6 +- packages/volto-hydra/src/reducers.js | 10 +- .../volto-hydra/src/utils/getSavedURLs.js | 2 +- .../volto-hydra/src/utils/usePresetsUrls.js | 31 -- .../volto-hydra/src/utils/usePreseturls.js | 31 -- 13 files changed, 226 insertions(+), 561 deletions(-) delete mode 100644 packages/volto-hydra/src/customizations/components/theme/Login/Login.original.jsx delete mode 100644 packages/volto-hydra/src/utils/usePresetsUrls.js delete mode 100644 packages/volto-hydra/src/utils/usePreseturls.js diff --git a/packages/volto-hydra/src/actions.js b/packages/volto-hydra/src/actions.js index 4360841..2f39910 100644 --- a/packages/volto-hydra/src/actions.js +++ b/packages/volto-hydra/src/actions.js @@ -1,8 +1,8 @@ -import { SET_SELECTED_BLOCK } from './constants'; +import { SET_FRONTEND_PREVIEW_URL } from './constants'; -export function setSelectedBlock(uid) { +export function setFrontendPreviewUrl(url) { return { - type: SET_SELECTED_BLOCK, - uid: uid, + type: SET_FRONTEND_PREVIEW_URL, + url: url, }; } diff --git a/packages/volto-hydra/src/components/Iframe/View.jsx b/packages/volto-hydra/src/components/Iframe/View.jsx index 262cf98..ed0b419 100644 --- a/packages/volto-hydra/src/components/Iframe/View.jsx +++ b/packages/volto-hydra/src/components/Iframe/View.jsx @@ -12,18 +12,19 @@ import { import './styles.css'; import { useIntl } from 'react-intl'; import config from '@plone/volto/registry'; -import usePresetUrls from '../../utils/usePreseturls'; import isValidUrl from '../../utils/isValidUrl'; import { BlockChooser } from '@plone/volto/components'; import { createPortal } from 'react-dom'; import { usePopper } from 'react-popper'; -import UrlInput from '../UrlInput'; +import { useSelector, useDispatch } from 'react-redux'; +import { getURlsFromEnv } from '../../utils/getSavedURLs'; +import { setSidebarTab } from '@plone/volto/actions'; /** - * Format the URL for the Iframe with location, token and enabling edit mode - * @param {*} url - * @param {*} token - * @returns {string} URL with the admin params + * Format the URL for the Iframe with location, token and edit mode + * @param {URL} url + * @param {String} token + * @returns {URL} URL with the admin params */ const getUrlWithAdminParams = (url, token) => { return typeof window !== 'undefined' @@ -49,10 +50,8 @@ const Iframe = (props) => { type: contentType, selectedBlock, } = props; - // const [ready, setReady] = useState(false); - // useEffect(() => { - // setReady(true); - // }, []); + + const dispatch = useDispatch(); const [addNewBlockOpened, setAddNewBlockOpened] = useState(false); const [popperElement, setPopperElement] = useState(null); const [referenceElement, setReferenceElement] = useState(null); @@ -64,7 +63,7 @@ const Iframe = (props) => { { name: 'offset', options: { - offset: [0, -250], + offset: [0, -350], }, }, { @@ -77,16 +76,18 @@ const Iframe = (props) => { }); //------------------------- - const [url, setUrl] = useState(''); - const [src, setSrc] = useState(''); - const history = useHistory(); + const [iframeSrc, setIframeSrc] = useState(null); + const urlFromEnv = getURlsFromEnv(); + const u = + useSelector((state) => state.frontendPreviewUrl.url) || + Cookies.get('iframe_url') || + urlFromEnv[0]; - const presetUrls = usePresetUrls(); - const defaultUrl = presetUrls[0]; - const savedUrl = Cookies.get('iframe_url'); - const initialUrl = savedUrl - ? getUrlWithAdminParams(savedUrl, token) - : getUrlWithAdminParams(defaultUrl, token); + useEffect(() => { + setIframeSrc(getUrlWithAdminParams(u, token)); + u && Cookies.set('iframe_url', u, { expires: 7 }); + }, [token, u]); + const history = useHistory(); //-----Experimental----- const intl = useIntl(); @@ -121,26 +122,19 @@ const Iframe = (props) => { const handleNavigateToUrl = useCallback( (givenUrl = null) => { - if (!isValidUrl(givenUrl) && !isValidUrl(url)) { + if (!isValidUrl(givenUrl)) { return; } // Update adminUI URL with the new URL - const formattedUrl = givenUrl ? new URL(givenUrl) : new URL(url); + const formattedUrl = new URL(givenUrl); const newOrigin = formattedUrl.origin; Cookies.set('iframe_url', newOrigin, { expires: 7 }); history.push(`${formattedUrl.pathname}`); }, - [history, url], + [history], ); - useEffect(() => { - setUrl( - `${savedUrl || defaultUrl}${window.location.pathname.replace('/edit', '')}`, - ); - setSrc(initialUrl); - }, [savedUrl, defaultUrl, initialUrl]); - useEffect(() => { //----------------Experimental---------------- const onDeleteBlock = (id, selectPrev) => { @@ -149,7 +143,7 @@ const Iframe = (props) => { onChangeFormData(newFormData); onSelectBlock(selectPrev ? previous : null); - const origin = new URL(src).origin; + const origin = new URL(iframeSrc).origin; document .getElementById('previewIframe') .contentWindow.postMessage( @@ -158,7 +152,7 @@ const Iframe = (props) => { ); }; //---------------------------------------------- - const initialUrlOrigin = initialUrl ? new URL(initialUrl).origin : ''; + const initialUrlOrigin = iframeSrc && new URL(iframeSrc).origin; const messageHandler = (event) => { if (event.origin !== initialUrlOrigin) { return; @@ -173,6 +167,7 @@ const Iframe = (props) => { if (history.location.pathname.endsWith('/edit')) { onSelectBlock(event.data.uid); setAddNewBlockOpened(false); + dispatch(setSidebarTab(1)); } break; @@ -198,31 +193,28 @@ const Iframe = (props) => { window.removeEventListener('message', messageHandler); }; }, [ + dispatch, handleNavigateToUrl, history.location.pathname, - initialUrl, + iframeSrc, onChangeFormData, onSelectBlock, properties, - src, token, ]); useEffect(() => { - if (form && Object.keys(form).length > 0 && isValidUrl(src)) { + if (form && Object.keys(form).length > 0 && isValidUrl(iframeSrc)) { // Send the form data to the iframe - const origin = new URL(src).origin; + const origin = new URL(iframeSrc).origin; document .getElementById('previewIframe') .contentWindow.postMessage({ type: 'FORM_DATA', data: form }, origin); } - }, [form, initialUrl, src]); + }, [form, iframeSrc]); return (
-
- -
{addNewBlockOpened && createPortal(
{ ? (id, value) => { setAddNewBlockOpened(false); const newId = onInsertBlock(id, value); - const origin = new URL(src).origin; + const origin = new URL(iframeSrc).origin; document .getElementById('previewIframe') .contentWindow.postMessage( @@ -272,7 +264,7 @@ const Iframe = (props) => {