From 73981f9407db1985536bac01da29b0e870a93548 Mon Sep 17 00:00:00 2001 From: blam Date: Fri, 10 Feb 2023 15:42:47 +0100 Subject: [PATCH] chore: removed some unneeded code, just use the one that exists Signed-off-by: blam --- .../DirectoryEditorContext.tsx | 243 ------------------ .../TemplateEditorPage/DryRunContext.test.tsx | 44 ---- .../next/TemplateEditorPage/DryRunContext.tsx | 179 ------------- .../DryRunResults/DryRunResults.test.tsx | 118 --------- .../DryRunResults/DryRunResults.tsx | 91 ------- .../DryRunResults/DryRunResultsList.test.tsx | 95 ------- .../DryRunResults/DryRunResultsList.tsx | 83 ------ .../DryRunResults/DryRunResultsSplitView.tsx | 55 ---- .../DryRunResults/DryRunResultsView.test.tsx | 99 ------- .../DryRunResults/DryRunResultsView.tsx | 195 -------------- .../TemplateEditorPage/DryRunResults/index.ts | 17 -- .../TemplateEditorPage/TemplateEditor.tsx | 10 +- .../TemplateEditorBrowser.test.tsx | 55 ---- .../TemplateEditorBrowser.tsx | 100 ------- .../TemplateEditorPage/TemplateEditorForm.tsx | 5 +- .../TemplateEditorPage.test.tsx | 58 ----- .../TemplateEditorTextArea.tsx | 153 ----------- .../TemplateFormPreviewer.tsx | 2 +- 18 files changed, 8 insertions(+), 1594 deletions(-) delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DirectoryEditorContext.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.test.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsSplitView.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/index.ts delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.test.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.test.tsx delete mode 100644 plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DirectoryEditorContext.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DirectoryEditorContext.tsx deleted file mode 100644 index 0edf390bfe..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DirectoryEditorContext.tsx +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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 { ErrorPanel, Progress } from '@backstage/core-components'; -import { useAsync, useRerender } from '@react-hookz/web'; -import React, { createContext, ReactNode, useContext, useEffect } from 'react'; -import { - TemplateDirectoryAccess, - TemplateFileAccess, -} from '../../lib/filesystem'; - -const MAX_SIZE = 1024 * 1024; -const MAX_SIZE_MESSAGE = 'This file is too large to be displayed'; - -interface DirectoryEditorFile { - /** The path of the file relative to the root directory */ - path: string; - /** The staged content of the file */ - content: string; - /** Whether the staged content matches what is on disk */ - dirty: boolean; - - /** Update the staged content of the file without saving */ - updateContent(content: string): void; - /** Save the staged content of the file to disk */ - save(): Promise; - /** Reload the staged content of the file from disk */ - reload(): Promise; -} - -interface DirectoryEditor { - /** A list of all files in the edited directory */ - files: Array; - - /** The currently selected file */ - selectedFile: DirectoryEditorFile | undefined; - /** Switch the selected file */ - setSelectedFile(path: string | undefined): void; - - /** Save all files to disk */ - save(): Promise; - /** Reload all files from disk */ - reload(): Promise; - - subscribe(listener: () => void): () => void; -} - -class DirectoryEditorFileManager implements DirectoryEditorFile { - readonly #access: TemplateFileAccess; - readonly #signalUpdate: () => void; - - #content?: string; - #savedContent?: string; - - constructor(access: TemplateFileAccess, signalUpdate: () => void) { - this.#access = access; - this.#signalUpdate = signalUpdate; - } - - get path() { - return this.#access.path; - } - - get content() { - return this.#content ?? MAX_SIZE_MESSAGE; - } - - updateContent(content: string): void { - if (this.#content === undefined) { - return; - } - this.#content = content; - this.#signalUpdate(); - } - - get dirty() { - return this.#content !== this.#savedContent; - } - - async save(): Promise { - if (this.#content !== undefined) { - await this.#access.save(this.#content); - this.#savedContent = this.#content; - this.#signalUpdate(); - } - } - - async reload(): Promise { - const file = await this.#access.file(); - if (file.size > MAX_SIZE) { - if (this.#content !== undefined) { - this.#content = undefined; - this.#savedContent = undefined; - this.#signalUpdate(); - } - return; - } - - const content = await file.text(); - if (this.#content !== content) { - this.#content = content; - this.#savedContent = content; - this.#signalUpdate(); - } - } -} - -class DirectoryEditorManager implements DirectoryEditor { - readonly #access: TemplateDirectoryAccess; - readonly #listeners = new Set<() => void>(); - - #files: DirectoryEditorFile[] = []; - #selectedFile: DirectoryEditorFile | undefined; - - constructor(access: TemplateDirectoryAccess) { - this.#access = access; - } - - get files() { - return this.#files; - } - - get selectedFile() { - return this.#selectedFile; - } - - setSelectedFile = (path: string | undefined): void => { - const prev = this.#selectedFile; - const next = this.#files.find(file => file.path === path); - if (prev !== next) { - this.#selectedFile = next; - this.#signalUpdate(); - } - }; - - get dirty() { - return this.#files.some(file => file.dirty); - } - - async save(): Promise { - await Promise.all(this.#files.map(file => file.save())); - } - - async reload(): Promise { - const selectedPath = this.#selectedFile?.path; - - const files = await this.#access.listFiles(); - const fileManagers = await Promise.all( - files.map(async file => { - const manager = new DirectoryEditorFileManager( - file, - this.#signalUpdate, - ); - await manager.reload(); - return manager; - }), - ); - this.#files.length = 0; - this.#files.push(...fileManagers); - - this.setSelectedFile(selectedPath); - this.#signalUpdate(); - } - - subscribe(listener: () => void): () => void { - this.#listeners.add(listener); - return () => { - this.#listeners.delete(listener); - }; - } - - #signalUpdate = () => { - this.#listeners.forEach(listener => listener()); - }; -} - -const DirectoryEditorContext = createContext( - undefined, -); - -export function useDirectoryEditor(): DirectoryEditor { - const value = useContext(DirectoryEditorContext); - const rerender = useRerender(); - - useEffect(() => value?.subscribe(rerender), [value, rerender]); - - if (!value) { - throw new Error('must be used within a DirectoryEditorProvider'); - } - return value; -} - -interface DirectoryEditorProviderProps { - directory: TemplateDirectoryAccess; - children?: ReactNode; -} - -export function DirectoryEditorProvider(props: DirectoryEditorProviderProps) { - const { directory } = props; - - const [{ result, error }, { execute }] = useAsync( - async (dir: TemplateDirectoryAccess) => { - const manager = new DirectoryEditorManager(dir); - await manager.reload(); - - const firstYaml = manager.files.find(file => file.path.match(/\.ya?ml$/)); - if (firstYaml) { - manager.setSelectedFile(firstYaml.path); - } - - return manager; - }, - ); - - useEffect(() => { - execute(directory); - }, [execute, directory]); - - if (error) { - return ; - } else if (!result) { - return ; - } - - return ( - - {props.children} - - ); -} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.test.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.test.tsx deleted file mode 100644 index 36a62f1b8a..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 { base64EncodeContent } from './DryRunContext'; - -// eslint-disable-next-line no-restricted-imports -import { TextEncoder } from 'util'; - -window.TextEncoder = TextEncoder; - -describe('base64EncodeContent', () => { - it('encodes text files', () => { - expect(base64EncodeContent('abc')).toBe('YWJj'); - expect(base64EncodeContent('abc'.repeat(1000000))).toBe( - window.btoa(''), - ); - }); - - it('encodes binary files', () => { - expect(base64EncodeContent('\x00\x01\x02')).toBe('AAEC'); - expect(base64EncodeContent('😅')).toBe('8J+YhQ=='); - // Triggers chunking - expect(base64EncodeContent('😅'.repeat(18000))).toBe( - '8J+YhfCfmIXwn5iF8J+YhfCfmIXwn5iF'.repeat(3000), - ); - // Triggers size check - expect(base64EncodeContent('😅'.repeat(1000000))).toBe( - window.btoa(''), - ); - }); -}); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.tsx deleted file mode 100644 index a617eebe1e..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunContext.tsx +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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 yaml from 'yaml'; -import { useApi } from '@backstage/core-plugin-api'; -import { JsonObject } from '@backstage/types'; -import React, { - createContext, - ReactNode, - useCallback, - useContext, - useMemo, - useRef, - useState, -} from 'react'; -import { - scaffolderApiRef, - ScaffolderDryRunResponse, -} from '@backstage/plugin-scaffolder-react'; - -const MAX_CONTENT_SIZE = 64 * 1024; -const CHUNK_SIZE = 32 * 1024; - -interface DryRunOptions { - templateContent: string; - values: JsonObject; - files: Array<{ path: string; content: string }>; -} - -interface DryRunResult extends ScaffolderDryRunResponse { - id: number; -} - -interface DryRun { - results: DryRunResult[]; - selectedResult: DryRunResult | undefined; - - selectResult(id: number): void; - deleteResult(id: number): void; - execute(options: DryRunOptions): Promise; -} - -const DryRunContext = createContext(undefined); - -interface DryRunProviderProps { - children: ReactNode; -} - -export function base64EncodeContent(content: string): string { - if (content.length > MAX_CONTENT_SIZE) { - return window.btoa(''); - } - - try { - return window.btoa(content); - } catch { - const decoder = new TextEncoder(); - const buffer = decoder.encode(content); - - const chunks = new Array(); - for (let offset = 0; offset < buffer.length; offset += CHUNK_SIZE) { - chunks.push( - String.fromCharCode(...buffer.slice(offset, offset + CHUNK_SIZE)), - ); - } - return window.btoa(chunks.join('')); - } -} - -export function DryRunProvider(props: DryRunProviderProps) { - const scaffolderApi = useApi(scaffolderApiRef); - - const [state, setState] = useState< - Pick - >({ - results: [], - selectedResult: undefined, - }); - const idRef = useRef(1); - - const selectResult = useCallback((id: number) => { - setState(prevState => { - const result = prevState.results.find(r => r.id === id); - if (result === prevState.selectedResult) { - return prevState; - } - return { - results: prevState.results, - selectedResult: result, - }; - }); - }, []); - - const deleteResult = useCallback((id: number) => { - setState(prevState => { - const index = prevState.results.findIndex(r => r.id === id); - if (index === -1) { - return prevState; - } - const newResults = prevState.results.slice(); - const [deleted] = newResults.splice(index, 1); - return { - results: newResults, - selectedResult: - prevState.selectedResult?.id === deleted.id - ? newResults[0] - : prevState.selectedResult, - }; - }); - }, []); - - const execute = useCallback( - async (options: DryRunOptions) => { - if (!scaffolderApi.dryRun) { - throw new Error('Scaffolder API does not support dry-run'); - } - - const parsed = yaml.parse(options.templateContent); - - const response = await scaffolderApi.dryRun({ - template: parsed, - values: options.values, - secrets: {}, - directoryContents: options.files.map(file => ({ - path: file.path, - base64Content: base64EncodeContent(file.content), - })), - }); - - const result = { - ...response, - id: idRef.current++, - }; - - setState(prevState => ({ - results: [...prevState.results, result], - selectedResult: prevState.selectedResult ?? result, - })); - }, - [scaffolderApi], - ); - - const dryRun = useMemo( - () => ({ - ...state, - selectResult, - deleteResult, - execute, - }), - [state, selectResult, deleteResult, execute], - ); - - return ( - - {props.children} - - ); -} - -export function useDryRun(): DryRun { - const value = useContext(DryRunContext); - if (!value) { - throw new Error('must be used within a DryRunProvider'); - } - return value; -} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx deleted file mode 100644 index 0267093ee6..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { act, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React, { useEffect } from 'react'; -import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; -import { DryRunProvider, useDryRun } from '../DryRunContext'; -import { DryRunResults } from './DryRunResults'; - -function DryRunRemote({ - execute, - remove, -}: { - execute?: boolean; - remove?: boolean; -}) { - const dryRun = useDryRun(); - - useEffect(() => { - if (execute) { - dryRun.execute({ - templateContent: '', - values: {}, - files: [], - }); - } - if (remove) { - if (dryRun.selectedResult) { - dryRun.deleteResult(dryRun.selectedResult.id); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [execute, remove]); - - return null; -} - -const mockScaffolderApi = { - dryRun: async () => ({ - directoryContents: [], - log: [], - output: {}, - steps: [], - }), -}; - -describe('DryRunResults', () => { - it('renders without exploding', async () => { - await renderInTestApp( - - - - - , - ); - expect(screen.getByText('Dry-run results')).toBeInTheDocument(); - }); - - it('expands when dry-run result is added and toggles on click, and disappears when results are gone', async () => { - const { rerender } = await renderInTestApp( - - - - - - , - ); - - expect(screen.getByText('Files')).not.toBeVisible(); - - await act(async () => { - rerender( - - - - - - , - ); - }); - - expect(screen.getByText('Files')).toBeVisible(); - - await userEvent.click(screen.getByText('Dry-run results')); - await waitFor(() => expect(screen.getByText('Files')).not.toBeVisible()); - - await userEvent.click(screen.getByText('Dry-run results')); - expect(screen.getByText('Files')).toBeVisible(); - - await act(async () => { - rerender( - - - - - - , - ); - }); - - await waitFor(() => expect(screen.getByText('Files')).not.toBeVisible()); - }); -}); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.tsx deleted file mode 100644 index ae9374438f..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResults.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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 { BackstageTheme } from '@backstage/theme'; -import Accordion from '@material-ui/core/Accordion'; -import AccordionDetails from '@material-ui/core/AccordionDetails'; -import AccordionSummary from '@material-ui/core/AccordionSummary'; -import Divider from '@material-ui/core/Divider'; -import { makeStyles } from '@material-ui/core/styles'; -import Typography from '@material-ui/core/Typography'; -import ExpandMoreIcon from '@material-ui/icons/ExpandLess'; -import { usePrevious } from '@react-hookz/web'; -import React, { useEffect, useState } from 'react'; -import { useDryRun } from '../DryRunContext'; -import { DryRunResultsList } from './DryRunResultsList'; -import { DryRunResultsView } from './DryRunResultsView'; - -const useStyles = makeStyles((theme: BackstageTheme) => ({ - header: { - height: 48, - minHeight: 0, - '&.Mui-expanded': { - height: 48, - minHeight: 0, - }, - }, - content: { - display: 'grid', - background: theme.palette.background.default, - gridTemplateColumns: '180px auto 1fr', - gridTemplateRows: '1fr', - padding: 0, - height: 400, - }, -})); - -export function DryRunResults() { - const classes = useStyles(); - const dryRun = useDryRun(); - const [expanded, setExpanded] = useState(false); - const [hidden, setHidden] = useState(true); - - const resultsLength = dryRun.results.length; - const prevResultsLength = usePrevious(resultsLength); - useEffect(() => { - if (prevResultsLength === 0 && resultsLength === 1) { - setHidden(false); - setExpanded(true); - } else if (prevResultsLength === 1 && resultsLength === 0) { - setExpanded(false); - } - }, [prevResultsLength, resultsLength]); - - return ( - <> - - - ); -} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx deleted file mode 100644 index 706f284db1..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { act, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React, { useEffect } from 'react'; -import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; -import { DryRunProvider, useDryRun } from '../DryRunContext'; -import { DryRunResultsList } from './DryRunResultsList'; - -function DryRunRemote({ execute }: { execute?: number }) { - const dryRun = useDryRun(); - - useEffect(() => { - if (execute) { - dryRun.execute({ - templateContent: '', - values: {}, - files: [], - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [execute]); - - return null; -} - -const mockScaffolderApi = { - dryRun: async () => ({ - directoryContents: [], - log: [], - output: {}, - steps: [], - }), -}; - -describe('DryRunResultsList', () => { - it('renders without exploding', async () => { - const rendered = await renderInTestApp( - - - - - , - ); - expect(rendered.baseElement.querySelector('ul')).toBeEmptyDOMElement(); - }); - - it('adds new result items and deletes them', async () => { - const { rerender } = await renderInTestApp( - - - - - - , - ); - - expect(screen.getByText('Result 1')).toBeInTheDocument(); - expect(screen.queryByText('Result 2')).not.toBeInTheDocument(); - - await act(async () => { - rerender( - - - - - - , - ); - }); - - expect(screen.getByText('Result 1')).toBeInTheDocument(); - expect(screen.getByText('Result 2')).toBeInTheDocument(); - - await userEvent.click(screen.getAllByLabelText('delete')[0]); - - expect(screen.queryByText('Result 1')).not.toBeInTheDocument(); - expect(screen.getByText('Result 2')).toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx deleted file mode 100644 index 462c3b58fd..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 { BackstageTheme } from '@backstage/theme'; -import IconButton from '@material-ui/core/IconButton'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import ListItemText from '@material-ui/core/ListItemText'; -import { makeStyles } from '@material-ui/core/styles'; -import CancelIcon from '@material-ui/icons/Cancel'; -import CheckIcon from '@material-ui/icons/Check'; -import DeleteIcon from '@material-ui/icons/Delete'; -import React from 'react'; -import { useDryRun } from '../DryRunContext'; - -const useStyles = makeStyles((theme: BackstageTheme) => ({ - root: { - overflowY: 'auto', - background: theme.palette.background.default, - }, - iconSuccess: { - minWidth: 0, - marginRight: theme.spacing(1), - color: theme.palette.status.ok, - }, - iconFailure: { - minWidth: 0, - marginRight: theme.spacing(1), - color: theme.palette.status.error, - }, -})); - -export function DryRunResultsList() { - const classes = useStyles(); - const dryRun = useDryRun(); - - return ( - - {dryRun.results.map(result => { - const failed = result.log.some(l => l.body.status === 'failed'); - return ( - dryRun.selectResult(result.id)} - > - - {failed ? : } - - - - dryRun.deleteResult(result.id)} - > - - - - - ); - })} - - ); -} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsSplitView.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsSplitView.tsx deleted file mode 100644 index b554a1c0c0..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsSplitView.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { makeStyles } from '@material-ui/core/styles'; -import Divider from '@material-ui/core/Divider'; -import React, { Children, ReactNode } from 'react'; -import classNames from 'classnames'; - -const useStyles = makeStyles(theme => ({ - root: { - display: 'grid', - gridTemplateColumns: '280px auto 3fr', - gridTemplateRows: '1fr', - }, - child: { - overflowY: 'auto', - height: '100%', - minHeight: 0, - }, - firstChild: { - background: theme.palette.background.paper, - }, -})); - -export function DryRunResultsSplitView(props: { children: ReactNode }) { - const classes = useStyles(); - const childArray = Children.toArray(props.children); - - if (childArray.length !== 2) { - throw new Error('must have exactly 2 children'); - } - - return ( -
-
- {childArray[0]} -
- -
{childArray[1]}
-
- ); -} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx deleted file mode 100644 index c2d8d3a2a4..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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 { entityRouteRef } from '@backstage/plugin-catalog-react'; -import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React, { ReactNode, useEffect } from 'react'; -import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; -import { DryRunProvider, useDryRun } from '../DryRunContext'; -import { DryRunResultsView } from './DryRunResultsView'; - -// The inside needs mocking to render in jsdom -jest.mock('react-virtualized-auto-sizer', () => ({ - __esModule: true, - default: (props: { - children: (size: { width: number; height: number }) => ReactNode; - }) => <>{props.children({ width: 400, height: 200 })}, -})); - -function DryRunRemote() { - const dryRun = useDryRun(); - - useEffect(() => { - dryRun.execute({ - templateContent: '', - values: {}, - files: [], - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return null; -} - -describe('DryRunResultsView', () => { - it('renders a result', async () => { - await renderInTestApp( - ({ - directoryContents: [ - { - path: 'foo.txt', - base64Content: window.btoa('Foo Content'), - executable: false, - }, - ], - log: [{ body: { message: 'Foo Message', stepId: 'foo' } }], - output: { - links: [{ title: 'Foo Link', url: 'http://example.com' }], - }, - steps: [{ id: 'foo', action: 'foo', name: 'Foo' }], - }), - }, - ], - ]} - > - - - - - , - { - mountedRoutes: { - '/catalog/:kind/:namespace/:name': entityRouteRef, - }, - }, - ); - - expect(screen.getByText('foo.txt')).toBeInTheDocument(); - expect(screen.getByText('Foo Content')).toBeInTheDocument(); - - expect(screen.queryByText('Foo Message')).not.toBeInTheDocument(); - expect(screen.queryByText('Foo Link')).not.toBeInTheDocument(); - - await userEvent.click(screen.getByText('Log')); - expect(screen.getByText('Foo Message')).toBeInTheDocument(); - - await userEvent.click(screen.getByText('Output')); - expect(screen.getByText('Foo Link')).toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx deleted file mode 100644 index 852ec05a82..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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 { LogViewer } from '@backstage/core-components'; -import { StreamLanguage } from '@codemirror/language'; -import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import Box from '@material-ui/core/Box'; -import Divider from '@material-ui/core/Divider'; -import { makeStyles } from '@material-ui/core/styles'; -import Tab from '@material-ui/core/Tab'; -import Tabs from '@material-ui/core/Tabs'; -import CodeMirror from '@uiw/react-codemirror'; -import React, { useEffect, useMemo, useState } from 'react'; -import { FileBrowser } from '../../../components/FileBrowser'; -import { TaskStatusStepper } from '../../../components/TaskPage/TaskPage'; -import { TaskPageLinks } from '../../../components/TaskPage/TaskPageLinks'; -import { useDryRun } from '../DryRunContext'; -import { DryRunResultsSplitView } from './DryRunResultsSplitView'; - -const useStyles = makeStyles({ - root: { - display: 'flex', - flexFlow: 'column nowrap', - }, - contentWrapper: { - flex: 1, - position: 'relative', - }, - content: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - - display: 'flex', - '& > *': { - flex: 1, - }, - }, - codeMirror: { - height: '100%', - overflowY: 'auto', - }, -}); - -function FilesContent() { - const classes = useStyles(); - const { selectedResult } = useDryRun(); - const [selectedPath, setSelectedPath] = useState(''); - const selectedFile = selectedResult?.directoryContents.find( - f => f.path === selectedPath, - ); - - useEffect(() => { - if (selectedResult) { - const [firstFile] = selectedResult.directoryContents; - if (firstFile) { - setSelectedPath(firstFile.path); - } else { - setSelectedPath(''); - } - } - return undefined; - }, [selectedResult]); - - if (!selectedResult) { - return null; - } - return ( - - file.path)} - /> - - - ); -} -function LogContent() { - const { selectedResult } = useDryRun(); - const [currentStepId, setUserSelectedStepId] = useState(); - - const steps = useMemo(() => { - if (!selectedResult) { - return []; - } - return ( - selectedResult.steps.map(step => { - const stepLog = selectedResult.log.filter( - l => l.body.stepId === step.id, - ); - return { - id: step.id, - name: step.name, - logString: stepLog.map(l => l.body.message).join('\n'), - status: stepLog[stepLog.length - 1]?.body.status ?? 'completed', - }; - }) ?? [] - ); - }, [selectedResult]); - - if (!selectedResult) { - return null; - } - - const selectedStep = steps.find(s => s.id === currentStepId) ?? steps[0]; - - return ( - - - - - ); -} - -function OutputContent() { - const classes = useStyles(); - const { selectedResult } = useDryRun(); - - if (!selectedResult) { - return null; - } - - return ( - - - {selectedResult.output?.links?.length && ( - - )} - - - - ); -} - -export function DryRunResultsView() { - const classes = useStyles(); - const [selectedTab, setSelectedTab] = useState<'files' | 'log' | 'output'>( - 'files', - ); - - return ( -
- setSelectedTab(v)}> - - - - - - -
-
- {selectedTab === 'files' && } - {selectedTab === 'log' && } - {selectedTab === 'output' && } -
-
-
- ); -} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/index.ts b/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/index.ts deleted file mode 100644 index af8ef5214f..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/DryRunResults/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * 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. - */ - -export { DryRunResults } from './DryRunResults'; diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditor.tsx index c5556c3b06..4cdd383f83 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditor.tsx @@ -20,12 +20,12 @@ import type { NextFieldExtensionOptions, } from '@backstage/plugin-scaffolder-react'; import { TemplateDirectoryAccess } from '../../lib/filesystem'; -import { DirectoryEditorProvider } from './DirectoryEditorContext'; -import { DryRunProvider } from './DryRunContext'; -import { DryRunResults } from './DryRunResults'; -import { TemplateEditorBrowser } from './TemplateEditorBrowser'; +import { DirectoryEditorProvider } from '../../components/TemplateEditorPage/DirectoryEditorContext'; +import { DryRunProvider } from '../../components/TemplateEditorPage/DryRunContext'; +import { DryRunResults } from '../../components/TemplateEditorPage/DryRunResults'; +import { TemplateEditorBrowser } from '../../components/TemplateEditorPage/TemplateEditorBrowser'; import { TemplateEditorForm } from './TemplateEditorForm'; -import { TemplateEditorTextArea } from './TemplateEditorTextArea'; +import { TemplateEditorTextArea } from '../../components/TemplateEditorPage/TemplateEditorTextArea'; const useStyles = makeStyles({ // Reset and fix sizing to make sure scrolling behaves correctly diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.test.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.test.tsx deleted file mode 100644 index cec311a5cd..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.test.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 { renderInTestApp } from '@backstage/test-utils'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { MockFileSystemAccess } from '../../lib/filesystem/MockFileSystemAccess'; -import { DirectoryEditorProvider } from './DirectoryEditorContext'; -import { TemplateEditorBrowser } from './TemplateEditorBrowser'; - -Blob.prototype.text = async function text() { - return new Promise(resolve => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result as string); - reader.readAsText(this); - }); -}; - -describe('TemplateEditorBrowser', () => { - it('should render files and expand dirs without exploding', async () => { - await renderInTestApp( - - - , - ); - - await expect(screen.findByText('foo.txt')).resolves.toBeInTheDocument(); - expect(screen.getByText('dir')).toBeInTheDocument(); - expect(screen.queryByText('bar.txt')).not.toBeInTheDocument(); - expect(screen.queryByText('baz.txt')).not.toBeInTheDocument(); - - await userEvent.click(screen.getByText('dir')); - expect(screen.getByText('bar.txt')).toBeInTheDocument(); - expect(screen.getByText('baz.txt')).toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx deleted file mode 100644 index a668aa1868..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorBrowser.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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 { 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 from 'react'; -import { FileBrowser } from '../../components/FileBrowser'; - -import { useDirectoryEditor } from './DirectoryEditorContext'; - -const useStyles = makeStyles(theme => ({ - button: { - padding: theme.spacing(1), - }, - buttons: { - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - justifyContent: 'flex-start', - }, - buttonsGap: { - flex: '1 1 auto', - }, - buttonsDivider: { - marginBottom: theme.spacing(1), - }, -})); - -/** The local file browser for the template editor */ -export function TemplateEditorBrowser(props: { onClose?: () => void }) { - const classes = useStyles(); - const directoryEditor = useDirectoryEditor(); - const changedFiles = directoryEditor.files.filter(file => file.dirty); - - const handleClose = () => { - if (!props.onClose) { - return; - } - if (changedFiles.length > 0) { - // eslint-disable-next-line no-alert - const accepted = window.confirm( - 'Are you sure? Unsaved changes will be lost', - ); - if (!accepted) { - return; - } - } - props.onClose(); - }; - - return ( - <> -
- - !file.dirty)} - onClick={() => directoryEditor.save()} - > - - - - - directoryEditor.reload()} - > - - - -
- - - - - -
- - file.path)} - /> - - ); -} diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx index 0ac604c75d..5dc841cf20 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorForm.tsx @@ -25,9 +25,8 @@ import { Stepper, TemplateParameterSchema, } from '@backstage/plugin-scaffolder-react'; - -import { useDirectoryEditor } from './DirectoryEditorContext'; -import { useDryRun } from './DryRunContext'; +import { useDryRun } from '../../components/TemplateEditorPage/DryRunContext'; +import { useDirectoryEditor } from '../../components/TemplateEditorPage/DirectoryEditorContext'; const useStyles = makeStyles({ containerWrapper: { diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.test.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.test.tsx deleted file mode 100644 index bf9cd8c464..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorPage.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; -import { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React from 'react'; -import { TemplateEditorPage } from './TemplateEditorPage'; - -describe('TemplateEditorPage', () => { - it('renders without exploding', async () => { - await renderInTestApp(); - - expect(screen.getByText('Load Template Directory')).toBeInTheDocument(); - expect(screen.getByText('Edit Template Form')).toBeInTheDocument(); - }); - - it('template directory loading should not be supported in Jest', async () => { - await renderInTestApp(); - - expect( - screen.getByRole('button', { name: /Load Template Directory/ }), - ).toBeDisabled(); - }); - - it('should be able to continue to form preview', async () => { - await renderInTestApp( - - - , - ); - - await userEvent.click(screen.getByText('Edit Template Form')); - - expect(screen.getByLabelText('Load Existing Template')).toBeInTheDocument(); - }); -}); diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx deleted file mode 100644 index 588b0cd4ba..0000000000 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateEditorTextArea.tsx +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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 ( -
- - {(props.onSave || props.onReload) && ( -
- - {props.onSave && ( - - props.onSave?.()} - > - - - - )} - {props.onReload && ( - - props.onReload?.()} - > - - - - )} - -
- )} -
- ); -} - -/** 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(), - } - : { - onReload: () => directoryEditor.reload(), - }; - - return ( - directoryEditor.selectedFile?.updateContent(content)} - {...actions} - /> - ); -} - -TemplateEditorTextArea.DirectoryEditor = TemplateEditorDirectoryEditorTextArea; diff --git a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx index c5f84a4b66..1476cda2d0 100644 --- a/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/next/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -37,7 +37,7 @@ import { type LayoutOptions, } from '@backstage/plugin-scaffolder-react'; import { TemplateEditorForm } from './TemplateEditorForm'; -import { TemplateEditorTextArea } from './TemplateEditorTextArea'; +import { TemplateEditorTextArea } from '../../components/TemplateEditorPage/TemplateEditorTextArea'; const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI parameters: