@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
242 lines (240 loc) • 8.84 kB
JavaScript
/*
* 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 GlobalAppToolbar from '../GlobalAppToolbar';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import React, { useEffect, useRef, useState } from 'react';
import { Box } from '@mui/material';
import { fetchConfigurationXML, writeConfiguration } from '../../services/configuration';
import AceEditor from '../AceEditor/AceEditor';
import useStyles from './styles';
import SecondaryButton from '../SecondaryButton';
import PrimaryButton from '../PrimaryButton';
import { forkJoin } from 'rxjs';
import { ConditionalLoadingState } from '../LoadingState/LoadingState';
import ConfigurationSamplePreviewDialog from '../ConfigurationSamplePreviewDialog';
import ConfirmDropdown from '../ConfirmDropdown';
import { useDispatch } from 'react-redux';
import { showSystemNotification } from '../../state/actions/system';
import { useHistory } from 'react-router';
import ConfirmDialog from '../ConfirmDialog/ConfirmDialog';
import { useMount } from '../../hooks/useMount';
import Paper from '@mui/material/Paper';
const translations = defineMessages({
configSaved: {
id: 'globalConfig.configSaved',
defaultMessage: 'Configuration saved successfully.'
},
documentError: {
id: 'globalConfig.documentError',
defaultMessage: 'The document contains errors. Check for error markers on side of the editor.'
}
});
export function GlobalConfigManagement() {
const [content, setContent] = useState('');
const [sample, setSample] = useState('');
const [lastSavedContent, setLastSavedContent] = useState('');
const [enable, setEnable] = useState(true);
const [viewSample, setViewSample] = useState(false);
const [hasChanges, setHasChanges] = useState(false);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [nextRoute, setNextRoute] = useState();
const history = useHistory();
const { classes } = useStyles();
const aceEditorRef = useRef();
const dispatch = useDispatch();
const { formatMessage } = useIntl();
useEffect(() => {
const historyBlock = history.block;
history.block((props) => {
if (hasChanges) {
history.goBack();
setNextRoute(props.pathname);
setShowConfirmDialog(true);
return false;
}
});
return () => {
history.block = historyBlock;
};
}, [hasChanges, history]);
useMount(() => {
const requests = [
fetchConfigurationXML('studio_root', '/configuration/samples/sample-studio-config-override.yaml', 'studio'),
fetchConfigurationXML('studio_root', '/configuration/studio-config-override.yaml', 'studio')
];
forkJoin(requests).subscribe(([sample, content]) => {
setLastSavedContent(content);
setContent(content);
setSample(sample);
setEnable(false);
});
});
const onUseSampleClick = (type) => {
if (type === 'replace') {
setContent(sample);
} else {
const currentContent = aceEditorRef.current.getValue();
setContent(currentContent + sample);
}
setViewSample(false);
};
const onResetClick = () => {
aceEditorRef.current.setValue(lastSavedContent, -1); // sets cursor in position 0, avoiding all editor content selection
aceEditorRef.current.focus();
};
const onSaveClick = () => {
const errors = aceEditorRef.current
.getSession()
.getAnnotations()
.filter((annotation) => {
return annotation.type === 'error';
});
if (errors.length) {
dispatch(
showSystemNotification({
message: formatMessage(translations.documentError),
options: {
variant: 'error'
}
})
);
} else {
const value = aceEditorRef.current.getValue();
writeConfiguration('studio_root', '/configuration/studio-config-override.yaml', 'studio', value).subscribe(
() => {
setLastSavedContent(value);
setHasChanges(false);
dispatch(
showSystemNotification({
message: formatMessage(translations.configSaved)
})
);
},
({ response: { response } }) => {
dispatch(
showSystemNotification({
message: response.message,
options: {
variant: 'error'
}
})
);
}
);
setHasChanges(false);
}
};
const onChange = (e) => {
const hasChanges = lastSavedContent !== aceEditorRef.current.getValue();
setHasChanges(hasChanges);
};
const onConfirmOk = () => {
setHasChanges(false);
setShowConfirmDialog(false);
// timeout needed to avoid running the useEffect on line:64 with hasChanges on true
setTimeout(() => {
history.push(nextRoute);
});
};
return React.createElement(
Paper,
{ elevation: 0 },
React.createElement(GlobalAppToolbar, {
title: React.createElement(FormattedMessage, {
id: 'globalMenu.globalConfigEntryLabel',
defaultMessage: 'Global Config'
})
}),
React.createElement(
ConditionalLoadingState,
{ isLoading: enable },
React.createElement(
'section',
{ className: classes.paper },
React.createElement(AceEditor, {
ref: aceEditorRef,
onChange: onChange,
classes: { editorRoot: classes.root },
value: content,
mode: 'ace/mode/yaml',
theme: 'ace/theme/textmate',
autoFocus: true,
readOnly: enable
}),
React.createElement(
Box,
{ p: '10px', display: 'flex', justifyContent: 'space-between' },
React.createElement(
SecondaryButton,
{ onClick: () => setViewSample(true) },
React.createElement(FormattedMessage, { id: 'globalConfig.viewSample', defaultMessage: 'View Sample' })
),
React.createElement(ConfirmDropdown, {
disabled: !hasChanges,
classes: { button: classes.marginLeftAuto },
text: React.createElement(FormattedMessage, { id: 'words.reset', defaultMessage: 'Reset' }),
cancelText: React.createElement(FormattedMessage, { id: 'words.cancel', defaultMessage: 'Cancel' }),
confirmText: React.createElement(FormattedMessage, { id: 'words.ok', defaultMessage: 'Ok' }),
confirmHelperText: React.createElement(FormattedMessage, {
id: 'globalConfig.confirmHelper',
defaultMessage: 'Discard unsaved changes?'
}),
onConfirm: onResetClick
}),
React.createElement(
PrimaryButton,
{ disabled: !hasChanges, onClick: onSaveClick },
React.createElement(FormattedMessage, { id: 'words.save', defaultMessage: 'Save' })
)
)
)
),
React.createElement(ConfigurationSamplePreviewDialog, {
onUseSampleClick: onUseSampleClick,
open: viewSample,
onClose: () => setViewSample(false),
onClosed: () => aceEditorRef.current.focus(),
content: sample
}),
React.createElement(ConfirmDialog, {
open: showConfirmDialog,
title: React.createElement(FormattedMessage, {
id: 'globalConfigManagement.pendingChanges',
defaultMessage: 'You have unsaved changes. Discard changes?'
}),
onOk: onConfirmOk,
onCancel: () => {
setShowConfirmDialog(false);
}
})
);
}
export default GlobalConfigManagement;