scaffolder: extract TemplateEditorTextArea
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
+10
-100
@@ -13,6 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useApiHolder } from '@backstage/core-plugin-api';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { Divider, IconButton, makeStyles, Tooltip } from '@material-ui/core';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import React, {
|
||||
Component,
|
||||
ReactNode,
|
||||
@@ -20,37 +26,21 @@ import React, {
|
||||
useReducer,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useKeyboardEvent } from '@react-hookz/web';
|
||||
import useDebounce from 'react-use/lib/useDebounce';
|
||||
import { useApiHolder } from '@backstage/core-plugin-api';
|
||||
import { JsonObject, JsonValue } from '@backstage/types';
|
||||
import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml';
|
||||
import { showPanel } from '@codemirror/view';
|
||||
import { StreamLanguage } from '@codemirror/language';
|
||||
import {
|
||||
IconButton,
|
||||
makeStyles,
|
||||
Divider,
|
||||
Tooltip,
|
||||
Paper,
|
||||
} from '@material-ui/core';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import yaml from 'yaml';
|
||||
import { FieldExtensionOptions } from '../../../extensions';
|
||||
import { TemplateDirectoryAccess } from '../../../lib/filesystem';
|
||||
import { TemplateParameterSchema } from '../../../types';
|
||||
import { FileBrowser } from '../../FileBrowser';
|
||||
import { MultistepJsonForm } from '../../MultistepJsonForm';
|
||||
import { createValidator } from '../../TemplatePage';
|
||||
import { TemplateDirectoryAccess } from '../../../lib/filesystem';
|
||||
import { FileBrowser } from '../../FileBrowser';
|
||||
import {
|
||||
DirectoryEditorProvider,
|
||||
useDirectoryEditor,
|
||||
} from './DirectoryEditorContext';
|
||||
import { DryRunProvider, useDryRun } from './DryRunContext';
|
||||
import { DryRunResults } from './DryRunResults';
|
||||
import { TemplateEditorTextArea } from './TemplateEditorTextArea';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
// Reset and fix sizing to make sure scrolling behaves correctly
|
||||
@@ -94,27 +84,9 @@ const useStyles = makeStyles(theme => ({
|
||||
marginBottom: theme.spacing(1),
|
||||
},
|
||||
editor: {
|
||||
position: 'relative',
|
||||
gridArea: 'editor',
|
||||
overflow: 'auto',
|
||||
},
|
||||
editorCodeMirror: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
editorErrorPanel: {
|
||||
color: theme.palette.error.main,
|
||||
lineHeight: 2,
|
||||
margin: theme.spacing(0, 1),
|
||||
},
|
||||
editorFloatingButtons: {
|
||||
position: 'absolute',
|
||||
top: theme.spacing(1),
|
||||
right: theme.spacing(3),
|
||||
},
|
||||
preview: {
|
||||
gridArea: 'preview',
|
||||
overflow: 'auto',
|
||||
@@ -142,7 +114,7 @@ export const TemplateEditor = (props: {
|
||||
<TemplateEditorBrowser onClose={props.onClose} />
|
||||
</section>
|
||||
<section className={classes.editor}>
|
||||
<TemplateEditorTextArea errorText={errorText} />
|
||||
<TemplateEditorTextArea.DirectoryEditor errorText={errorText} />
|
||||
</section>
|
||||
<section className={classes.preview}>
|
||||
<TemplateEditorForm
|
||||
@@ -217,68 +189,6 @@ function TemplateEditorBrowser(props: { onClose?: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
function TemplateEditorTextArea(props: { errorText?: string }) {
|
||||
const { errorText } = props;
|
||||
const classes = useStyles();
|
||||
const directoryEditor = useDirectoryEditor();
|
||||
|
||||
const panelExtension = useMemo(() => {
|
||||
if (!errorText) {
|
||||
return showPanel.of(null);
|
||||
}
|
||||
|
||||
const dom = document.createElement('div');
|
||||
dom.classList.add(classes.editorErrorPanel);
|
||||
dom.textContent = errorText;
|
||||
return showPanel.of(() => ({ dom, bottom: true }));
|
||||
}, [classes, errorText]);
|
||||
|
||||
useKeyboardEvent(
|
||||
e => e.key === 's' && (e.ctrlKey || e.metaKey),
|
||||
e => {
|
||||
e.preventDefault();
|
||||
directoryEditor.save();
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CodeMirror
|
||||
className={classes.editorCodeMirror}
|
||||
theme="dark"
|
||||
height="100%"
|
||||
extensions={[StreamLanguage.define(yamlSupport), panelExtension]}
|
||||
value={directoryEditor.selectedFile?.content}
|
||||
onChange={content =>
|
||||
directoryEditor.selectedFile?.updateContent(content)
|
||||
}
|
||||
/>
|
||||
{directoryEditor.selectedFile?.dirty && (
|
||||
<div className={classes.editorFloatingButtons}>
|
||||
<Paper>
|
||||
<Tooltip title="Save file">
|
||||
<IconButton
|
||||
className={classes.browserButton}
|
||||
onClick={() => directoryEditor.save()}
|
||||
>
|
||||
<SaveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Reload file">
|
||||
<IconButton
|
||||
className={classes.browserButton}
|
||||
onClick={() => directoryEditor.reload()}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Paper>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
generation: number;
|
||||
setErrorText(errorText: string | undefined): void;
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2022 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { StreamLanguage } from '@codemirror/language';
|
||||
import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml';
|
||||
import { showPanel } from '@codemirror/view';
|
||||
import { IconButton, makeStyles, Paper, Tooltip } from '@material-ui/core';
|
||||
import RefreshIcon from '@material-ui/icons/Refresh';
|
||||
import SaveIcon from '@material-ui/icons/Save';
|
||||
import { useKeyboardEvent } from '@react-hookz/web';
|
||||
import CodeMirror from '@uiw/react-codemirror';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useDirectoryEditor } from './DirectoryEditorContext';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
container: {
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
},
|
||||
codeMirror: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
},
|
||||
errorPanel: {
|
||||
color: theme.palette.error.main,
|
||||
lineHeight: 2,
|
||||
margin: theme.spacing(0, 1),
|
||||
},
|
||||
floatingButtons: {
|
||||
position: 'absolute',
|
||||
top: theme.spacing(1),
|
||||
right: theme.spacing(3),
|
||||
},
|
||||
floatingButton: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
}));
|
||||
|
||||
/** A wrapper around CodeMirror with an error panel and extra actions available */
|
||||
export function TemplateEditorTextArea(props: {
|
||||
content?: string;
|
||||
onUpdate?: (content: string) => void;
|
||||
errorText?: string;
|
||||
onSave?: () => void;
|
||||
onReload?: () => void;
|
||||
}) {
|
||||
const { errorText } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
const panelExtension = useMemo(() => {
|
||||
if (!errorText) {
|
||||
return showPanel.of(null);
|
||||
}
|
||||
|
||||
const dom = document.createElement('div');
|
||||
dom.classList.add(classes.errorPanel);
|
||||
dom.textContent = errorText;
|
||||
return showPanel.of(() => ({ dom, bottom: true }));
|
||||
}, [classes, errorText]);
|
||||
|
||||
useKeyboardEvent(
|
||||
e => e.key === 's' && (e.ctrlKey || e.metaKey),
|
||||
e => {
|
||||
e.preventDefault();
|
||||
if (props.onSave) {
|
||||
props.onSave();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
<CodeMirror
|
||||
className={classes.codeMirror}
|
||||
theme="dark"
|
||||
height="100%"
|
||||
extensions={[StreamLanguage.define(yamlSupport), panelExtension]}
|
||||
value={props.content}
|
||||
onChange={props.onUpdate}
|
||||
/>
|
||||
{(props.onSave || props.onReload) && (
|
||||
<div className={classes.floatingButtons}>
|
||||
<Paper>
|
||||
{props.onSave && (
|
||||
<Tooltip title="Save file">
|
||||
<IconButton
|
||||
className={classes.floatingButton}
|
||||
onClick={() => props.onSave?.()}
|
||||
>
|
||||
<SaveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{props.onReload && (
|
||||
<Tooltip title="Reload file">
|
||||
<IconButton
|
||||
className={classes.floatingButton}
|
||||
onClick={() => props.onReload?.()}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Paper>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** A version of the TemplateEditorTextArea that is connected to the DirectoryEditor context */
|
||||
export function TemplateEditorDirectoryEditorTextArea(props: {
|
||||
errorText?: string;
|
||||
}) {
|
||||
const directoryEditor = useDirectoryEditor();
|
||||
|
||||
const actions = directoryEditor.selectedFile?.dirty
|
||||
? {
|
||||
onSave: () => directoryEditor.save(),
|
||||
onReload: () => directoryEditor.reload(),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<TemplateEditorTextArea
|
||||
errorText={props.errorText}
|
||||
content={directoryEditor.selectedFile?.content}
|
||||
onUpdate={content => directoryEditor.selectedFile?.updateContent(content)}
|
||||
{...actions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
TemplateEditorTextArea.DirectoryEditor = TemplateEditorDirectoryEditorTextArea;
|
||||
Reference in New Issue
Block a user