META-INF.resources.js.fragment-editor.FragmentEditor.js Maven / Gradle / Ivy
/**
* SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
* SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
*/
import ClayForm from '@clayui/form';
import ClayIcon from '@clayui/icon';
import ClayTabs from '@clayui/tabs';
import {useIsMounted, usePrevious} from '@liferay/frontend-js-react-web';
import {
cancelDebounce,
debounce,
fetch,
navigate,
openToast,
sub,
} from 'frontend-js-web';
import React, {useCallback, useEffect, useState} from 'react';
import CodeMirrorEditor from './CodeMirrorEditor';
import {FieldTypeSelector} from './FieldTypeSelector';
import FragmentPreview from './FragmentPreview';
import createFile from './createFile';
const CHANGES_STATUS = {
saved: Liferay.Language.get('changes-saved'),
saving: Liferay.Language.get('saving-changes'),
unsaved: Liferay.Language.get('unsaved-changes'),
};
const FragmentEditor = ({
context: {namespace},
props: {
allowedStatus = {
approved: false,
draft: false,
},
autocompleteTags,
dataAttributes,
fieldTypes: availableFieldTypes,
fragmentCollectionId,
fragmentConfigurationURL,
fragmentEntryId,
htmlEditorCustomEntities,
initialCSS,
initialConfiguration,
initialFieldTypes,
initialHTML,
initialJS,
name,
propagationEnabled,
readOnly,
showFieldTypes,
status,
urls,
},
}) => {
const [activeTabKeyValue, setActiveTabKeyValue] = useState(0);
const [changesStatus, setChangesStatus] = useState(null);
const [configuration, setConfiguration] = useState(initialConfiguration);
const [css, setCss] = useState(initialCSS);
const [html, setHtml] = useState(initialHTML);
const [js, setJs] = useState(initialJS);
const [fieldTypes, setFieldTypes] = useState(initialFieldTypes);
const previousConfiguration =
usePrevious(configuration) || initialConfiguration;
const previousCss = usePrevious(css) || initialCSS;
const previousFieldTypes = usePrevious(fieldTypes) || initialFieldTypes;
const previousHtml = usePrevious(html) || initialHTML;
const previousJs = usePrevious(js) || initialJS;
const [previewData, setPreviewData] = useState({
configuration: initialConfiguration,
css: initialCSS,
html: initialHTML,
js: initialJS,
});
const isMounted = useIsMounted();
const contentHasChanged = useCallback(() => {
return (
previousConfiguration !== configuration ||
previousCss !== css ||
previousFieldTypes.length !== fieldTypes.length ||
previousHtml !== html ||
previousJs !== js
);
}, [
configuration,
css,
fieldTypes,
html,
previousCss,
previousConfiguration,
previousFieldTypes,
previousHtml,
previousJs,
js,
]);
const publish = () => {
const formData = new FormData();
formData.append(`${namespace}fragmentEntryId`, fragmentEntryId);
fetch(urls.publish, {
body: formData,
method: 'POST',
})
.then((response) => response.json())
.then((response) => {
if (response.error) {
throw response.error;
}
return response;
})
.then((response) => {
const redirectURL = response.redirect || urls.redirect;
navigate(redirectURL);
})
.catch((error) => {
if (isMounted()) {
setChangesStatus(CHANGES_STATUS.unsaved);
}
const message =
typeof error === 'string'
? error
: Liferay.Language.get('error');
openToast({
message,
type: 'danger',
});
});
};
/* eslint-disable-next-line react-hooks/exhaustive-deps */
const saveDraft = useCallback(
debounce(() => {
setChangesStatus(CHANGES_STATUS.saving);
const formData = new FormData();
formData.append(`${namespace}configurationContent`, configuration);
formData.append(
`${namespace}cssContent`,
createFile('cssContent', css)
);
formData.append(`${namespace}fieldTypes`, fieldTypes);
formData.append(
`${namespace}fragmentCollectionId`,
fragmentCollectionId
);
formData.append(`${namespace}fragmentEntryId`, fragmentEntryId);
formData.append(
`${namespace}htmlContent`,
createFile('htmlContent', html)
);
formData.append(
`${namespace}jsContent`,
createFile('jsContent', js)
);
formData.append(`${namespace}name`, name);
formData.append(`${namespace}status`, allowedStatus.draft);
fetch(urls.edit, {
body: formData,
method: 'POST',
})
.then((response) => response.json())
.then((response) => {
if (response.error) {
throw response.error;
}
return response;
})
.then(() => {
setPreviewData({configuration, css, html, js});
setChangesStatus(CHANGES_STATUS.saved);
})
.catch((error) => {
if (isMounted()) {
setChangesStatus(CHANGES_STATUS.unsaved);
}
const message =
typeof error === 'string'
? error
: Liferay.Language.get('error');
openToast({
message,
type: 'danger',
});
});
}, 500),
[configuration, css, fieldTypes, html, js]
);
const previousSaveDraft = usePrevious(saveDraft);
useEffect(() => {
if (previousSaveDraft && previousSaveDraft !== saveDraft) {
cancelDebounce(previousSaveDraft);
}
}, [previousSaveDraft, saveDraft]);
useEffect(() => {
if (contentHasChanged()) {
setChangesStatus(CHANGES_STATUS.unsaved);
saveDraft();
}
}, [contentHasChanged, saveDraft]);
return (
setActiveTabKeyValue(0)}
>
{Liferay.Language.get('code')}
setActiveTabKeyValue(1)}
>
{Liferay.Language.get('configuration')}
{readOnly ? (
{Liferay.Language.get('read-only-view')}
) : (
<>
{propagationEnabled && (
{Liferay.Language.get(
'automatic-propagation-enabled'
)}
)}
{changesStatus}
>
)}
',
'const fragmentElement = ...;',
'const configuration = ...;',
]}
content={initialJS}
mode="javascript"
onChange={setJs}
readOnly={readOnly}
/>
{showFieldTypes && (
<>
>
)}
json
{!readOnly && (
{Liferay.Language.get(
'add-the-json-configuration'
)}
)}
);
};
export default FragmentEditor;