UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

870 lines (868 loc) 34.9 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import React, { useContext, useEffect, useRef, useState } from 'react'; import { fetchActiveEnvironment } from '../../services/environment'; import { fetchConfigurationXML, fetchSiteConfigurationFiles, writeConfiguration } from '../../services/configuration'; import Box from '@mui/material/Box'; import List from '@mui/material/List'; import ListItem from '@mui/material/ListItem'; import ListItemText from '@mui/material/ListItemText'; import useStyles from './styles'; import ListSubheader from '@mui/material/ListSubheader'; import { FormattedMessage, useIntl } from 'react-intl'; import Skeleton from '@mui/material/Skeleton'; import EmptyState from '../EmptyState/EmptyState'; import { translations } from './translations'; import { getTranslation } from '../../utils/i18n'; import { LoadingState } from '../LoadingState/LoadingState'; import AceEditor from '../AceEditor/AceEditor'; import GlobalAppToolbar from '../GlobalAppToolbar'; import ResizeableDrawer from '../ResizeableDrawer/ResizeableDrawer'; import IconButton from '@mui/material/IconButton'; import Tooltip from '@mui/material/Tooltip'; import MenuOpenRoundedIcon from '@mui/icons-material/MenuOpenRounded'; import MenuRoundedIcon from '@mui/icons-material/MenuRounded'; import PrimaryButton from '../PrimaryButton'; import DialogFooter from '../DialogFooter/DialogFooter'; import SecondaryButton from '../SecondaryButton'; import HelpOutlineRoundedIcon from '@mui/icons-material/HelpOutlineRounded'; import Button from '@mui/material/Button'; import ButtonGroup from '@mui/material/ButtonGroup'; import ConfirmDialog from '../ConfirmDialog/ConfirmDialog'; import informationGraphicUrl from '../../assets/information.svg'; import Typography from '@mui/material/Typography'; import { useDispatch } from 'react-redux'; import { fetchItemVersions } from '../../state/actions/versions'; import { fetchItemByPath } from '../../services/content'; import SearchBar from '../SearchBar/SearchBar'; import Alert from '@mui/material/Alert'; import { closeConfirmDialog, showConfirmDialog, showHistoryDialog } from '../../state/actions/dialogs'; import { batchActions, dispatchDOMEvent } from '../../state/actions/misc'; import { capitalize, stripCData } from '../../utils/string'; import { itemReverted, showSystemNotification } from '../../state/actions/system'; import { getHostToHostBus } from '../../utils/subjects'; import { filter, map } from 'rxjs/operators'; import { parseValidateDocument, serialize } from '../../utils/xml'; import { forkJoin } from 'rxjs'; import { encrypt } from '../../services/security'; import { showErrorDialog } from '../../state/reducers/dialogs/error'; import ResizeBar from '../ResizeBar'; import { useSelection } from '../../hooks/useSelection'; import { useActiveSiteId } from '../../hooks/useActiveSiteId'; import { useMount } from '../../hooks/useMount'; import { findPendingEncryption } from './utils'; import useUpdateRefs from '../../hooks/useUpdateRefs'; import { MAX_CONFIG_SIZE, UNDEFINED } from '../../utils/constants'; import { ApiResponseErrorState } from '../ApiResponseErrorState'; import { nnou } from '../../utils/object'; import { MaxLengthCircularProgress } from '../MaxLengthCircularProgress'; import useUnmount from '../../hooks/useUnmount'; import useActiveUser from '../../hooks/useActiveUser'; import { createCustomDocumentEventListener } from '../../utils/dom'; import { ProjectToolsRoutes } from '../../env/routes'; import { SiteToolsContext } from '../SiteTools/siteToolsContext'; export function SiteConfigurationManagement(props) { const { embedded, showAppsButton, onSubmittingAndOrPendingChange, isSubmitting } = props; const site = useActiveSiteId(); const { username } = useActiveUser(); const sessionStorageKey = `craftercms.${username}.projectToolsConfigurationData.${site}`; const baseUrl = useSelection((state) => state.env.authoringBase); const { classes, cx: clsx } = useStyles(); const { formatMessage } = useIntl(); const [environment, setEnvironment] = useState(); const [files, setFiles] = useState(); const [selectedConfigFile, setSelectedConfigFile] = useState( () => JSON.parse(sessionStorage.getItem(sessionStorageKey))?.selectedConfigFile ?? null ); const [changesRecoveredOnMount] = useState(() => selectedConfigFile !== null); const ignoreEnv = selectedConfigFile?.path === 'site-policy-config.xml'; const [selectedConfigFileXml, setSelectedConfigFileXml] = useState(null); const [configError, setConfigError] = useState(null); const [selectedSampleConfigFileXml, setSelectedSampleConfigFileXml] = useState(null); const [loadingXml, setLoadingXml] = useState(true); const [encrypting, setEncrypting] = useState(false); const [loadingSampleXml, setLoadingSampleXml] = useState(false); const [sampleError, setSampleError] = useState(null); const [showSampleEditor, setShowSampleEditor] = useState(false); const [width, setWidth] = useState(240); const [openDrawer, setOpenDrawer] = useState(true); const [leftEditorWidth, setLeftEditorWidth] = useState(null); const [disabledSaveButton, setDisabledSaveButton] = useState(true); const [confirmDialogProps, setConfirmDialogProps] = useState(null); const [keyword, setKeyword] = useState(''); const dispatch = useDispatch(); const setTool = useContext(SiteToolsContext)?.setTool; const refs = useUpdateRefs({ disabledSaveButton, selectedConfigFile, setTool }); const functionRefs = useUpdateRefs({ onSubmittingAndOrPendingChange }); const editorRef = useRef({ container: null }); const [contentSize, setContentSize] = useState(0); useMount(() => { if (changesRecoveredOnMount) { dispatch( showSystemNotification({ message: formatMessage({ defaultMessage: 'Unsaved changes were restored on to the editor.' }), options: { variant: 'info' } }) ); } fetchActiveEnvironment().subscribe({ next(env) { setEnvironment(env); }, error({ response }) { dispatch(showErrorDialog({ error: response })); } }); }); useUnmount(() => { if (!refs.current.disabledSaveButton) { sessionStorage.setItem( sessionStorageKey, JSON.stringify({ content: editorRef.current.getValue(), selectedConfigFile: refs.current.selectedConfigFile }) ); const eventId = 'unsavedConfigurationChangesConfirmation'; const title = getTranslation(refs.current.selectedConfigFile.title, translations, formatMessage); if (refs.current.setTool) { dispatch( showConfirmDialog({ body: formatMessage({ defaultMessage: 'You left unsaved changes on "{title}"' }, { title }), onCancel: batchActions([closeConfirmDialog(), dispatchDOMEvent({ id: eventId, button: 'cancel' })]), onOk: batchActions([closeConfirmDialog(), dispatchDOMEvent({ id: eventId, button: 'ok' })]), okButtonText: React.createElement(FormattedMessage, { defaultMessage: 'Go back and recover changes' }), cancelButtonText: React.createElement(FormattedMessage, { defaultMessage: 'Discard changes' }) }) ); createCustomDocumentEventListener(eventId, ({ button }) => { if (button === 'ok') { refs.current.setTool(ProjectToolsRoutes.Configuration); } else { sessionStorage.removeItem(sessionStorageKey); } }); } else { dispatch( showConfirmDialog({ body: formatMessage( { defaultMessage: 'You left unsaved changes on "{title}". You may go back to configuration now if you wish to recover or ignore to discard changes.' }, { title } ) }) ); } } }); useEffect(() => { if (site && environment !== UNDEFINED) { fetchSiteConfigurationFiles(site, environment).subscribe({ next(files) { setFiles(files.map((file) => ({ ...file, id: `${file.module}/${file.path}` }))); }, error({ response }) { dispatch(showErrorDialog({ error: response.response })); } }); } }, [environment, site, dispatch]); useEffect(() => { if (selectedConfigFile && environment) { setConfigError(null); const sessionData = JSON.parse(sessionStorage.getItem(sessionStorageKey)); if (sessionData?.content) { setSelectedConfigFileXml(sessionData.content); setContentSize(sessionData.content.length); setDisabledSaveButton(false); sessionStorage.removeItem(sessionStorageKey); setLoadingXml(false); } else { fetchConfigurationXML( site, selectedConfigFile.path, selectedConfigFile.module, ignoreEnv ? null : environment ).subscribe({ next(xml) { setSelectedConfigFileXml(xml ?? ''); setContentSize(xml?.length ?? 0); setLoadingXml(false); }, error({ response }) { if (response.response.code === 7000) { setSelectedConfigFileXml(''); } else { setConfigError(response.response); } setLoadingXml(false); } }); } } }, [selectedConfigFile, environment, site, ignoreEnv, refs, sessionStorageKey]); // Item Revert Propagation useEffect(() => { const hostToHost$ = getHostToHostBus(); const subscription = hostToHost$ .pipe(filter((e) => itemReverted.type === e.type)) .subscribe(({ type, payload }) => { if (payload.target.endsWith(selectedConfigFile.path)) { setSelectedConfigFile({ ...selectedConfigFile }); } }); return () => { subscription.unsubscribe(); }; }, [dispatch, selectedConfigFile]); const onToggleDrawer = () => { setOpenDrawer(!openDrawer); }; const showXmlParseError = (error) => { dispatch( showSystemNotification({ message: formatMessage(translations.xmlContainsErrors, { errors: error }), options: { variant: 'error' } }) ); }; const onEncryptClick = () => { const content = editorRef.current.getValue(); const doc = parseValidateDocument(content); if (typeof doc === 'string') { showXmlParseError(doc); return; } const tags = doc.querySelectorAll('[encrypted]'); const items = findPendingEncryption(tags); if (items.length) { setEncrypting(true); forkJoin( items.map(({ tag, text }) => encrypt(stripCData(text), site).pipe(map((text) => ({ tag, text })))) ).subscribe({ next(encrypted) { encrypted.forEach(({ text, tag }) => { tag.innerHTML = `\${enc:${text}}`; tag.setAttribute('encrypted', 'true'); }); editorRef.current.setValue(serialize(doc), -1); setEncrypting(false); }, error({ response: { response } }) { dispatch(showErrorDialog({ error: response })); } }); } else { setConfirmDialogProps({ open: true, imageUrl: informationGraphicUrl, title: tags.length ? formatMessage(translations.allEncrypted) : formatMessage(translations.noEncryptItems), onOk: onConfirmDialogClose, onClose: onConfirmDialogClose, onClosed: onConfirmDialogClosed }); } }; const onEncryptHelpClick = () => { setConfirmDialogProps({ open: true, maxWidth: 'sm', onOk: onConfirmDialogClose, onClose: onConfirmDialogClose, onClosed: onConfirmDialogClosed, imageUrl: informationGraphicUrl, children: React.createElement( 'section', { className: classes.confirmDialogBody }, React.createElement( Typography, { className: classes.textMargin, variant: 'subtitle1' }, formatMessage(translations.encryptMarked) ), React.createElement( Typography, { className: classes.textMargin, variant: 'body2' }, formatMessage(translations.encryptHintPt1) ), React.createElement(Typography, { variant: 'body2' }, formatMessage(translations.encryptHintPt2, bold)), React.createElement( Typography, { className: classes.textMargin, variant: 'body2' }, formatMessage(translations.encryptHintPt3, tags) ), React.createElement(Typography, { variant: 'body2' }, formatMessage(translations.encryptHintPt4, bold)), React.createElement( Typography, { className: classes.textMargin, variant: 'body2' }, formatMessage(translations.encryptHintPt5, tagsAndCurls) ), React.createElement( Typography, { className: classes.textMargin, variant: 'body2' }, formatMessage(translations.encryptHintPt6) ), React.createElement( 'ul', null, React.createElement( 'li', null, React.createElement(Typography, { variant: 'body2' }, formatMessage(translations.encryptHintPt7)) ), React.createElement( 'li', null, React.createElement(Typography, { variant: 'body2' }, formatMessage(translations.encryptHintPt8)) ), React.createElement( 'li', null, React.createElement(Typography, { variant: 'body2' }, formatMessage(translations.encryptHintPt9)) ) ) ) }); }; const onViewSampleClick = () => { if (showSampleEditor) { setLeftEditorWidth(null); } setLoadingSampleXml(true); setShowSampleEditor(!showSampleEditor); if (showSampleEditor === false) { setSampleError(null); fetchConfigurationXML( 'studio_root', `/configuration/samples/${selectedConfigFile.samplePath}`, selectedConfigFile.module, environment ).subscribe({ next(xml) { setSelectedSampleConfigFileXml(xml); setLoadingSampleXml(false); }, error({ response }) { setSampleError(response.response); setLoadingSampleXml(false); } }); } }; const onClean = () => { if (showSampleEditor) { setShowSampleEditor(false); } if (leftEditorWidth !== null) { setLeftEditorWidth(null); } if (encrypting !== null) { setEncrypting(false); } if (!disabledSaveButton) { setDisabledSaveButton(true); } functionRefs.current.onSubmittingAndOrPendingChange?.({ hasPendingChanges: false, isSubmitting: false }); }; const onListItemClick = (file) => { if (file.id !== selectedConfigFile?.id) { setLoadingXml(true); setSelectedConfigFile(file); } onClean(); }; const onUnsavedChangesOk = (file) => { setConfirmDialogProps({ ...confirmDialogProps, open: false }); onListItemClick(file); }; const onConfirmDialogClose = () => { setConfirmDialogProps({ ...confirmDialogProps, open: false }); }; const onConfirmDialogClosed = () => { setConfirmDialogProps(null); }; const onEditorResize = (width) => { if (width > 240) { setLeftEditorWidth(width); } }; const onEditorChanges = () => { const currentEditorValue = editorRef.current.getValue(); if (selectedConfigFileXml !== currentEditorValue) { setDisabledSaveButton(false); functionRefs.current.onSubmittingAndOrPendingChange?.({ hasPendingChanges: true }); } else { setDisabledSaveButton(true); functionRefs.current.onSubmittingAndOrPendingChange?.({ hasPendingChanges: false }); } setContentSize(currentEditorValue.length); }; const onShowHistory = () => { fetchItemByPath(site, `/config/${selectedConfigFile.module}/${selectedConfigFile.path}`).subscribe((item) => { dispatch( batchActions([ fetchItemVersions({ isConfig: true, environment: environment, module: selectedConfigFile.module, item }), showHistoryDialog({}) ]) ); }); }; const onCancel = () => { onClean(); setSelectedConfigFile(null); }; const showUnsavedChangesConfirm = (file) => { setConfirmDialogProps({ open: true, title: React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.unsavedChangesTitle', defaultMessage: 'Unsaved changes' }), body: React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.unsavedChangesSubtitle', defaultMessage: 'You have unsaved changes, do you want to leave?' }), onClosed: onConfirmDialogClosed, onOk: () => onUnsavedChangesOk(file), onCancel: onConfirmDialogClose }); }; const onSave = () => { const content = editorRef.current.getValue(); const doc = parseValidateDocument(content); if (typeof doc === 'string') { showXmlParseError(doc); return; } const unencryptedItems = findPendingEncryption(doc.querySelectorAll('[encrypted]')); const errors = editorRef.current .getSession() .getAnnotations() .filter((annotation) => { return annotation.type === 'error'; }); if (errors.length) { dispatch( showSystemNotification({ message: formatMessage(translations.documentError), options: { variant: 'error' } }) ); } else { if (unencryptedItems.length === 0) { functionRefs.current.onSubmittingAndOrPendingChange?.({ isSubmitting: true }); writeConfiguration( site, selectedConfigFile.path, selectedConfigFile.module, content, ignoreEnv ? null : environment ).subscribe({ next: () => { functionRefs.current.onSubmittingAndOrPendingChange?.({ isSubmitting: false, hasPendingChanges: false }); dispatch( showSystemNotification({ message: formatMessage(translations.configSaved) }) ); setDisabledSaveButton(true); setSelectedConfigFileXml(content); }, error: ({ response: { response } }) => { functionRefs.current.onSubmittingAndOrPendingChange?.({ isSubmitting: false }); dispatch(showErrorDialog({ error: response })); } }); } else { let tags; if (unencryptedItems.length > 1) { tags = unencryptedItems.map((item) => { return formatMessage(translations.encryptionSingleDetail, { name: item.tag.tagName, value: item.text, br: React.createElement('br', { key: item.text }) }); }); } else { tags = formatMessage(translations.encryptionSingleDetail, { name: unencryptedItems[0].tag.tagName, value: unencryptedItems[0].text, br: null }); } setConfirmDialogProps({ open: true, imageUrl: informationGraphicUrl, title: formatMessage(translations.pendingEncryption, { itemCount: unencryptedItems.length, tags, br: unencryptedItems.length ? React.createElement('br', { key: unencryptedItems.length }) : null }), onOk: onConfirmDialogClose, onClose: onConfirmDialogClose, onClosed: onConfirmDialogClosed }); } } }; const bold = { bold: (msg) => React.createElement('strong', { key: msg, className: 'bold' }, msg) }; const tags = { lt: '<', gt: '>' }; const tagsAndCurls = Object.assign({ lc: '{', rc: '}' }, tags); const onAceInit = (editor) => { editor.commands.addCommand({ name: 'saveToCrafter', bindKey: { win: 'Ctrl-S', mac: 'Command-S' }, exec: () => onSave(), readOnly: false }); }; return React.createElement( 'section', { className: classes.root }, !embedded && React.createElement(GlobalAppToolbar, { title: React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.title', defaultMessage: 'Configuration' }), showAppsButton: showAppsButton }), React.createElement( ResizeableDrawer, { belowToolbar: true, open: openDrawer, width: width, classes: { drawerPaper: clsx(classes.drawerPaper, embedded && 'embedded') }, onWidthChange: setWidth }, React.createElement( List, { className: classes.list, component: 'nav', dense: true, subheader: React.createElement( ListSubheader, { className: classes.listSubheader, component: 'div' }, environment ? React.createElement( React.Fragment, null, React.createElement( Tooltip, { placement: 'top', title: React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.environment', defaultMessage: 'The active environment is "{environment}"', values: { environment } }) }, React.createElement( Alert, { severity: 'info', className: classes.alert, classes: { message: classes.ellipsis } }, React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.activeEnvironment', defaultMessage: '{environment} Environment', values: { environment: capitalize(environment) } }) ) ), React.createElement(SearchBar, { classes: { root: classes.searchBarRoot }, keyword: keyword, onChange: setKeyword, showActionButton: Boolean(keyword), autoFocus: true }) ) : React.createElement( 'section', { className: classes.listSubheaderSkeleton }, React.createElement(Skeleton, { height: 34, width: '100%' }), React.createElement(Skeleton, { height: 34, width: '100%' }) ) ) }, files ? files .filter( (file) => file.path.toLowerCase().includes(keyword) || getTranslation(file.title, translations, formatMessage).toLowerCase().includes(keyword) || getTranslation(file.description, translations, formatMessage).toLowerCase().includes(keyword) ) .map((file, i) => React.createElement( ListItem, { selected: file.id === selectedConfigFile?.id, onClick: () => { if (!disabledSaveButton && file.id !== selectedConfigFile?.id) { showUnsavedChangesConfirm(file); } else { onListItemClick(file); } }, button: true, key: i, dense: true, divider: i < files.length - 1 }, React.createElement(ListItemText, { classes: { primary: classes.ellipsis, secondary: classes.ellipsis }, primaryTypographyProps: { title: getTranslation(file.title, translations, formatMessage) }, secondaryTypographyProps: { title: getTranslation(file.description, translations, formatMessage) }, primary: getTranslation(file.title, translations, formatMessage), secondary: getTranslation(file.description, translations, formatMessage) }) ) ) : Array(15) .fill(null) .map((x, i) => React.createElement( ListItem, { button: true, key: i, dense: true, divider: i < Array.length - 1 }, React.createElement(ListItemText, { primary: React.createElement(Skeleton, { height: 15, width: '80%' }), secondary: React.createElement(Skeleton, { height: 15, width: '60%' }), primaryTypographyProps: { className: classes.itemSkeletonText }, secondaryTypographyProps: { className: classes.itemSkeletonText } }) ) ) ) ), selectedConfigFile ? React.createElement( Box, { display: 'flex', flexGrow: 1, flexDirection: loadingXml ? 'row' : 'column', paddingLeft: openDrawer ? `${width}px` : 0 }, configError ? React.createElement(ApiResponseErrorState, { error: configError, classes: { root: classes.errorState } }) : loadingXml ? React.createElement(LoadingState, null) : nnou(selectedConfigFileXml) ? React.createElement( React.Fragment, null, React.createElement(GlobalAppToolbar, { classes: { appBar: classes.appBar }, styles: { toolbar: { '& > section': {} } }, showHamburgerMenuButton: false, showAppsButton: false, startContent: React.createElement( IconButton, { onClick: onToggleDrawer, size: 'large' }, openDrawer ? React.createElement(MenuOpenRoundedIcon, null) : React.createElement(MenuRoundedIcon, null) ), title: getTranslation(selectedConfigFile.title, translations, formatMessage), subtitle: getTranslation(selectedConfigFile.description, translations, formatMessage), rightContent: React.createElement( React.Fragment, null, React.createElement( ButtonGroup, { variant: 'outlined', className: classes.buttonGroup }, React.createElement( SecondaryButton, { disabled: encrypting, onClick: onEncryptClick, loading: encrypting }, formatMessage(translations.encryptMarked) ), React.createElement( Button, { size: 'small', onClick: onEncryptHelpClick }, React.createElement(HelpOutlineRoundedIcon, null) ) ), React.createElement( SecondaryButton, { onClick: onViewSampleClick }, showSampleEditor ? React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.hideSample', defaultMessage: 'Hide Sample' }) : React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.viewSample', defaultMessage: 'View Sample' }) ) ) }), React.createElement( Box, { display: 'flex', flexGrow: 1 }, React.createElement(AceEditor, { ref: editorRef, styles: { root: { display: 'flex', width: leftEditorWidth ? `${leftEditorWidth}px` : 'auto', flexGrow: leftEditorWidth ? 0 : 1 }, editorRoot: { margin: 0, opacity: encrypting ? 0.5 : 1, border: '0', borderRadius: '0' } }, mode: 'ace/mode/xml', theme: 'ace/theme/textmate', readOnly: encrypting, autoFocus: true, onChange: onEditorChanges, value: selectedConfigFileXml, onInit: onAceInit }), showSampleEditor && React.createElement( React.Fragment, null, React.createElement(ResizeBar, { onWidthChange: onEditorResize, element: editorRef.current.container }), sampleError ? React.createElement(ApiResponseErrorState, { error: sampleError, classes: { root: classes.sampleErrorState } }) : loadingSampleXml ? React.createElement(LoadingState, null) : nnou(selectedSampleConfigFileXml) ? React.createElement(AceEditor, { classes: { root: classes.rootEditor, editorRoot: classes.editorRoot }, mode: 'ace/mode/xml', theme: 'ace/theme/textmate', autoFocus: false, readOnly: true, value: selectedSampleConfigFileXml }) : React.createElement(React.Fragment, null) ) ), React.createElement( DialogFooter, null, React.createElement( SecondaryButton, { disabled: encrypting, className: classes.historyButton, onClick: onShowHistory }, React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.history', defaultMessage: 'History' }) ), React.createElement( SecondaryButton, { disabled: encrypting, onClick: onCancel }, React.createElement(FormattedMessage, { id: 'words.cancel', defaultMessage: 'Cancel' }) ), React.createElement( PrimaryButton, { disabled: disabledSaveButton || encrypting || isSubmitting || contentSize > MAX_CONFIG_SIZE, onClick: onSave }, React.createElement(FormattedMessage, { id: 'words.save', defaultMessage: 'Save' }) ), React.createElement(MaxLengthCircularProgress, { sxs: { circularProgress: { width: '35px !important', height: '35px !important' } }, max: MAX_CONFIG_SIZE, current: contentSize, renderThresholdPercentage: 90 }) ) ) : React.createElement(React.Fragment, null) ) : React.createElement( Box, { display: 'flex', alignItems: 'center', flexGrow: 1, justifyContent: 'center', paddingLeft: openDrawer && `${width}px` }, React.createElement(EmptyState, { title: React.createElement(FormattedMessage, { id: 'siteConfigurationManagement.selectConfigFile', defaultMessage: 'Please choose a config file from the left.' }), image: `${baseUrl}/static-assets/images/choose_option.svg` }) ), React.createElement(ConfirmDialog, { open: false, ...confirmDialogProps }) ); } export default SiteConfigurationManagement;