diff --git a/web/config/webpack.common.js b/web/config/webpack.common.js index ffd01a146..b63b998a2 100644 --- a/web/config/webpack.common.js +++ b/web/config/webpack.common.js @@ -1,17 +1,17 @@ -const path = require('path'); +const path = require('path') const webpack = require('webpack') const { container: { ModuleFederationPlugin }, DefinePlugin -} = require('webpack'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); +} = require('webpack') +const MiniCssExtractPlugin = require('mini-css-extract-plugin') +const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin') const GenerateStringTypesPlugin = require('../scripts/webpack/GenerateStringTypesPlugin').GenerateStringTypesPlugin const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin') - -const moduleFederationConfig = require('./moduleFederation.config'); -const CONTEXT = process.cwd(); +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') +const moduleFederationConfig = require('./moduleFederation.config') +const CONTEXT = process.cwd() const DEV = process.env.NODE_ENV === 'development' const ON_PREM = `${process.env.ON_PREM}` === 'true' @@ -150,8 +150,7 @@ module.exports = { }, resolve: { extensions: ['.mjs', '.js', '.ts', '.tsx', '.json', '.ttf', '.scss'], - plugins: [ - new TsconfigPathsPlugin()] + plugins: [new TsconfigPathsPlugin()] }, plugins: [ new ModuleFederationPlugin(moduleFederationConfig), @@ -164,5 +163,71 @@ module.exports = { new RetryChunkLoadPlugin({ maxRetries: 2 }), + new MonacoWebpackPlugin({ + // available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options + languages: [ + 'abap', + 'apex', + 'azcli', + 'bat', + 'cameligo', + 'clojure', + 'coffee', + 'cpp', + 'csharp', + 'csp', + 'css', + 'dockerfile', + 'fsharp', + 'go', + 'graphql', + 'handlebars', + 'html', + 'ini', + 'java', + 'javascript', + 'json', + 'kotlin', + 'less', + 'lua', + 'markdown', + 'mips', + 'msdax', + 'mysql', + 'objective-c', + 'pascal', + 'pascaligo', + 'perl', + 'pgsql', + 'php', + 'postiats', + 'powerquery', + 'powershell', + 'pug', + 'python', + 'r', + 'razor', + 'redis', + 'redshift', + 'restructuredtext', + 'ruby', + 'rust', + 'sb', + 'scheme', + 'scss', + 'shell', + 'solidity', + 'sophia', + 'sql', + 'st', + 'swift', + 'tcl', + 'twig', + 'typescript', + 'vb', + 'xml', + 'yaml' + ] + }) ] -}; \ No newline at end of file +} diff --git a/web/config/webpack.dev.js b/web/config/webpack.dev.js index 5be0658a4..95dc73a75 100644 --- a/web/config/webpack.dev.js +++ b/web/config/webpack.dev.js @@ -12,7 +12,6 @@ const { WatchIgnorePlugin, container: { ModuleFederationPlugin } } = require('webpack') -const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') const commonConfig = require('./webpack.common') const baseUrl = process.env.BASE_URL ?? 'https://qa.harness.io/gateway' @@ -61,72 +60,6 @@ const devConfig = { new DefinePlugin({ 'process.env': '{}', // required for @blueprintjs/core __DEV__: DEV - }), - new MonacoWebpackPlugin({ - // available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options - languages: [ - 'abap', - 'apex', - 'azcli', - 'bat', - 'cameligo', - 'clojure', - 'coffee', - 'cpp', - 'csharp', - 'csp', - 'css', - 'dockerfile', - 'fsharp', - 'go', - 'graphql', - 'handlebars', - 'html', - 'ini', - 'java', - 'javascript', - 'json', - 'kotlin', - 'less', - 'lua', - 'markdown', - 'mips', - 'msdax', - 'mysql', - 'objective-c', - 'pascal', - 'pascaligo', - 'perl', - 'pgsql', - 'php', - 'postiats', - 'powerquery', - 'powershell', - 'pug', - 'python', - 'r', - 'razor', - 'redis', - 'redshift', - 'restructuredtext', - 'ruby', - 'rust', - 'sb', - 'scheme', - 'scss', - 'shell', - 'solidity', - 'sophia', - 'sql', - 'st', - 'swift', - 'tcl', - 'twig', - 'typescript', - 'vb', - 'xml', - 'yaml' - ] }) // new ForkTsCheckerWebpackPlugin() // new WatchIgnorePlugin({ diff --git a/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx b/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx new file mode 100644 index 000000000..504b6c5d6 --- /dev/null +++ b/web/src/components/SourceCodeEditor/MonacoSourceCodeEditor.tsx @@ -0,0 +1,102 @@ +import React, { useEffect } from 'react' +import { Container } from '@harness/uicore' +import type monacoEditor from 'monaco-editor/esm/vs/editor/editor.api' +import MonacoEditor from 'react-monaco-editor' +import { noop } from 'lodash-es' +import type { SourceCodeEditorProps } from 'utils/Utils' + +export const MonacoEditorOptions = { + ignoreTrimWhitespace: true, + minimap: { enabled: false }, + codeLens: false, + scrollBeyondLastLine: false, + smartSelect: false, + tabSize: 2, + insertSpaces: true, + overviewRulerBorder: false, + automaticLayout: true +} + +const diagnosticsOptions = { + noSemanticValidation: true, + noSyntaxValidation: true +} + +const compilerOptions = { + jsx: 'react', + noLib: true, + allowNonTsExtensions: true +} + +function autoAdjustEditorHeight(editor: monacoEditor.editor.IStandaloneCodeEditor) { + // Adjust editor height based on its content + // https://github.com/microsoft/monaco-editor/issues/794#issuecomment-427092969 + const LINE_HEIGHT = 18 + const CONTAINER_GUTTER = 10 + const editorNode = editor.getDomNode() as HTMLElement + const codeContainer = editorNode.getElementsByClassName('view-lines')[0] + let prevLineCount = 0 + const adjustHeight = (): void => { + const _height = + codeContainer.childElementCount > prevLineCount + ? (codeContainer as HTMLElement).offsetHeight // unfold + : codeContainer.childElementCount * LINE_HEIGHT + CONTAINER_GUTTER // fold + prevLineCount = codeContainer.childElementCount + + editorNode.style.height = Math.max(_height, 100) + 'px' + editor.layout() + } + + setTimeout(adjustHeight, 0) + editor.onDidChangeModelDecorations(() => setTimeout(adjustHeight, 0)) +} + +const toOnOff = (flag: boolean) => (flag ? 'on' : 'off') + +export default function MonacoSourceCodeEditor({ + source, + language = 'plaintext', + lineNumbers = true, + readOnly = false, + className, + height, + autoHeight, + wordWrap = true, + onChange = noop +}: SourceCodeEditorProps) { + const scrollbar = autoHeight ? 'hidden' : 'auto' + + useEffect(() => { + monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions) + monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions?.(diagnosticsOptions) + monaco.languages.typescript.typescriptDefaults.setCompilerOptions(compilerOptions) + }, []) + + return ( + + { + if (autoHeight) { + autoAdjustEditorHeight(editor) + } + }} + onChange={onChange} + /> + + ) +} diff --git a/web/src/components/SourceCodeEditor/SourceCodeEditor.tsx b/web/src/components/SourceCodeEditor/SourceCodeEditor.tsx index 3a9e2b1be..0a384e817 100644 --- a/web/src/components/SourceCodeEditor/SourceCodeEditor.tsx +++ b/web/src/components/SourceCodeEditor/SourceCodeEditor.tsx @@ -1,79 +1,17 @@ -import React, { useRef } from 'react' -import { Container } from '@harness/uicore' -import MonacoEditor from 'react-monaco-editor' -import { MonacoEditorOptions } from 'utils/Utils' +import React, { lazy, Suspense } from 'react' +import { Text } from '@harness/uicore' +import type { SourceCodeEditorProps } from 'utils/Utils' +import { useStrings } from 'framework/strings' -export interface SourceCodeEditorProps { - source: string - language?: string - lineNumbers?: boolean - readOnly?: boolean - highlightLines?: string // i.e: {1,3-4}, TODO: not yet supported - className?: string - height?: number | string - autoHeight?: boolean -} +function Editor(props: SourceCodeEditorProps) { + const { getString } = useStrings() + const MonacoSourceCodeEditor = lazy(() => import('./MonacoSourceCodeEditor')) -function MonacoSourceCodeEditor({ - source, - language = 'plaintext', - lineNumbers = true, - readOnly = false, - className, - height, - autoHeight -}: SourceCodeEditorProps) { - const inputContainerRef = useRef(null) - const scrollbar = autoHeight ? 'hidden' : 'auto' - - // return return ( - - { - if (autoHeight) { - // Aadjust editor height based on content - // https://github.com/microsoft/monaco-editor/issues/794#issuecomment-427092969 - const LINE_HEIGHT = 18 - const CONTAINER_GUTTER = 10 - const editorNode = editor.getDomNode() as HTMLElement - const codeContainer = editorNode.getElementsByClassName('view-lines')[0] - let prevLineCount = 0 - const adjustHeight = (): void => { - const _height = - codeContainer.childElementCount > prevLineCount - ? (codeContainer as HTMLElement).offsetHeight // unfold - : codeContainer.childElementCount * LINE_HEIGHT + CONTAINER_GUTTER // fold - prevLineCount = codeContainer.childElementCount - - editorNode.style.height = Math.max(_height, 100) + 'px' - editor.layout() - } - - setTimeout(adjustHeight, 0) - editor.onDidChangeModelDecorations(() => setTimeout(adjustHeight, 0)) - } - }} - /> - + {getString('loading')}}> + + ) } -export const SourceCodeEditor = React.memo(MonacoSourceCodeEditor) +export const SourceCodeEditor = React.memo(Editor) diff --git a/web/src/framework/strings/stringTypes.ts b/web/src/framework/strings/stringTypes.ts index 706d0d15d..6d9e8f8ee 100644 --- a/web/src/framework/strings/stringTypes.ts +++ b/web/src/framework/strings/stringTypes.ts @@ -36,9 +36,12 @@ export interface StringsMap { failedToCreateRepo: string files: string history: string + identifier: string + in: string inactiveBranches: string loading: string name: string + nameYourFile: string newFile: string newFolder: string newRepo: string diff --git a/web/src/i18n/strings.en.yaml b/web/src/i18n/strings.en.yaml index 6c62cc3e0..fc211748f 100644 --- a/web/src/i18n/strings.en.yaml +++ b/web/src/i18n/strings.en.yaml @@ -32,6 +32,7 @@ create: Create clone: Clone copy: Copy defaultBranch: Default +in: in ok: OK loading: Loading... addGitIgnore: Add a .gitignore @@ -51,6 +52,7 @@ createBranch: + Create Branch searchBranches: Search branches updated: Updated cloneHTTPS: Clone with HTTPS +nameYourFile: Name your file... repos: name: Repo Name data: Repo Data diff --git a/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx b/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx index fcfc863d7..1011c6a7b 100644 --- a/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx +++ b/web/src/pages/Repository/RepositoryContent/ContentHeader/ContentHeader.tsx @@ -129,9 +129,7 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte variation={ButtonVariation.SECONDARY} icon="main-clone" iconProps={{ size: 10 }} - tooltip={ - - } + tooltip={} tooltipProps={{ interactionKind: 'click', minimal: true, diff --git a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss index 99553fd62..8cfe44be8 100644 --- a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss +++ b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss @@ -7,13 +7,28 @@ overflow: hidden; .heading { - border-top-left-radius: 4px; - border-top-right-radius: 4px; + // border-top-left-radius: 4px; + // border-top-right-radius: 4px; align-items: center; padding: 0 var(--spacing-xlarge) !important; height: 52px; background-color: var(--grey-100); box-shadow: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16); + border-bottom: 1px solid var(--grey-200); + + .path { + align-items: center; + + .inputContainer { + margin-left: var(--spacing-small) !important; + margin-bottom: 0; + + input { + padding: var(--spacing-xsmall) var(--spacing-small); + height: 28px; + } + } + } } .content { @@ -21,8 +36,7 @@ overflow: hidden; .editorContainer { - height: calc(100vh - 95px - 52px); - background-color: red; + height: calc(100vh - 96px - 52px); overflow: hidden; } } diff --git a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts index 0481e443b..0ba005f4d 100644 --- a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts +++ b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.module.scss.d.ts @@ -3,6 +3,8 @@ declare const styles: { readonly container: string readonly heading: string + readonly path: string + readonly inputContainer: string readonly content: string readonly editorContainer: string } diff --git a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx index 5a5e24b43..1ae465322 100644 --- a/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx +++ b/web/src/pages/RepositoryFileEdit/FileEditor/FileEditor.tsx @@ -8,10 +8,12 @@ import { FlexExpander, Icon, Layout, - Text + Text, + TextInput } from '@harness/uicore' import { Link } from 'react-router-dom' import ReactJoin from 'react-join' +import cx from 'classnames' import { SourceCodeEditor } from 'components/SourceCodeEditor/SourceCodeEditor' import type { OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm' import { useAppContext } from 'AppContext' @@ -22,7 +24,7 @@ import css from './FileEditor.module.scss' interface FileEditorProps { repoMetadata: TypesRepository - gitRef?: string + gitRef: string resourcePath?: string contentInfo: OpenapiGetContentOutput } @@ -30,12 +32,13 @@ interface FileEditorProps { export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '' }: FileEditorProps) { const { getString } = useStrings() const { routes } = useAppContext() + const language = filenameToLanguage(contentInfo?.name) return ( - + @@ -44,7 +47,7 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = ' {resourcePath.split('/').map((_path, index, paths) => { const pathAtIndex = paths.slice(0, index + 1).join('/') - return ( + return index < paths.length - 1 ? ( ) })} + {getString('in')} + + {gitRef} + @@ -69,16 +88,14 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = ' /> - {(contentInfo?.content as RepoFileContent)?.data && ( - - - - )} + + + ) } diff --git a/web/src/pages/RepositoryFileEdit/RepositoryFileEditContent/RepositoryFileEditContent.tsx b/web/src/pages/RepositoryFileEdit/RepositoryFileEditContent/RepositoryFileEditContent.tsx index fd08c4197..c46f8243d 100644 --- a/web/src/pages/RepositoryFileEdit/RepositoryFileEditContent/RepositoryFileEditContent.tsx +++ b/web/src/pages/RepositoryFileEdit/RepositoryFileEditContent/RepositoryFileEditContent.tsx @@ -1,7 +1,6 @@ import React from 'react' import { Container } from '@harness/uicore' import type { TypesRepository } from 'services/scm' -import { isFile } from 'utils/GitUtils' import { useGetResourceContent } from 'hooks/useGetResourceContent' import { FileEditor } from '../FileEditor/FileEditor' import css from './RepositoryFileEditContent.module.scss' @@ -19,10 +18,10 @@ export function RepositoryFileEditContent({ repoMetadata, gitRef, resourcePath } return ( - {data && isFile(data) && ( + {data && ( diff --git a/web/src/utils/Utils.ts b/web/src/utils/Utils.ts index db70b8710..ff8f5634a 100644 --- a/web/src/utils/Utils.ts +++ b/web/src/utils/Utils.ts @@ -26,15 +26,17 @@ export function showToaster(message: string, props?: Partial): IToa export const getErrorMessage = (error: Unknown): string => get(error, 'data.error', get(error, 'data.message', error?.message)) -export const MonacoEditorOptions = { - ignoreTrimWhitespace: true, - minimap: { enabled: false }, - codeLens: false, - scrollBeyondLastLine: false, - smartSelect: false, - tabSize: 2, - insertSpaces: true, - overviewRulerBorder: false +export interface SourceCodeEditorProps { + source: string + language?: string + lineNumbers?: boolean + readOnly?: boolean + highlightLines?: string // i.e: {1,3-4}, TODO: not yet supported + className?: string + height?: number | string + autoHeight?: boolean + wordWrap?: boolean + onChange?: (value: string) => void } // Monaco editor has a bug where when its value is set, the value @@ -157,10 +159,16 @@ const MONACO_SUPPORTED_LANGUAGES = [ 'yaml' ] +const EXTENSION_TO_LANG: Record = { + tsx: 'typescript', + jsx: 'typescript' +} + export const filenameToLanguage = (name?: string): string | undefined => { - const map = langMap.languages(name?.split('.').pop() || '') + const extension = name?.split('.').pop() || '' + const map = langMap.languages(extension) if (map?.length) { - return MONACO_SUPPORTED_LANGUAGES.find(lang => map.includes(lang)) + return MONACO_SUPPORTED_LANGUAGES.find(lang => map.includes(lang)) || EXTENSION_TO_LANG[extension] } }