Add routingId to support Harness gateway + Prototype for Commit modal (#68)

* Implement file path edit input

* Implement file path edit input - cont

* Prototype for Commit modal

* Add routingId to support Harness gateway
This commit is contained in:
Tan Nhu 2022-11-10 15:17:55 -08:00 committed by GitHub
parent c8cee4d250
commit 31a58e3172
18 changed files with 483 additions and 103 deletions

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useCallback } from 'react'
import React, { useEffect, useState, useCallback, useMemo } from 'react'
import { RestfulProvider } from 'restful-react'
import { TooltipContextProvider } from '@harness/uicore'
import { ModalProvider } from '@harness/use-modal'
@ -34,6 +34,11 @@ const App: React.FC<AppProps> = React.memo(function App({
(): Partial<RequestInit> => buildResfulReactRequestOptions(hooks?.useGetToken?.() || token),
[token, hooks]
)
const queryParams = useMemo(
() => (!standalone ? { routingId: space.split('/').shift() } : {}),
[space, standalone]
)
useEffect(() => {
languageLoader(lang).then(setStrings)
@ -46,7 +51,7 @@ const App: React.FC<AppProps> = React.memo(function App({
<RestfulProvider
base={standalone ? '/' : getConfigNew('scm')}
requestOptions={getRequestOptions}
queryParams={{}}
queryParams={queryParams}
queryParamStringifyOptions={{ skipNulls: true }}
onResponse={response => {
if (!response.ok && response.status === 401) {

View File

@ -0,0 +1,58 @@
.main {
padding-left: var(--spacing-xxlarge) !important;
height: 100%;
.extendedDescription textarea {
height: 100px !important;
}
.radioGroup {
> div {
margin-bottom: var(--spacing-small) !important;
}
&.directly {
label[class*='RadioButton']:first-of-type {
background: rgba(220, 238, 255, 0.5);
mix-blend-mode: normal;
border: 1px solid var(--primary-6);
}
}
&.newBranch {
label[class*='RadioButton']:last-of-type {
background: rgba(220, 238, 255, 0.5);
mix-blend-mode: normal;
border: 1px solid var(--primary-6);
}
}
label[class*='RadioButton'] {
border-radius: var(--spacing-2);
border: 1px solid var(--bp3-intent-color, var(--grey-200));
padding: var(--spacing-small) !important;
&:first-of-type {
margin-bottom: var(--spacing-small);
}
span {
font-size: var(--form-input-font-size);
font-weight: 500;
}
}
.newBranchContainer {
align-items: center;
padding-left: var(--spacing-medium);
> div {
margin-bottom: 0 !important;
input {
width: 588px;
}
}
}
}
}

View File

@ -0,0 +1,11 @@
/* eslint-disable */
// this is an auto-generated file
declare const styles: {
readonly main: string
readonly extendedDescription: string
readonly radioGroup: string
readonly directly: string
readonly newBranch: string
readonly newBranchContainer: string
}
export default styles

View File

@ -0,0 +1,206 @@
/*
* Copyright 2021 Harness Inc. All rights reserved.
* Use of this source code is governed by the PolyForm Shield 1.0.0 license
* that can be found in the licenses directory at the root of this repository, also available at
* https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
*/
import React, { useState } from 'react'
import { Dialog, Intent } from '@blueprintjs/core'
import * as yup from 'yup'
import {
Button,
ButtonProps,
Container,
Layout,
FlexExpander,
Icon,
Formik,
FormikForm,
Heading,
useToaster,
FormInput
} from '@harness/uicore'
import cx from 'classnames'
import { FontVariation } from '@harness/design-system'
import { useMutate } from 'restful-react'
import { get } from 'lodash-es'
import { useModalHook } from '@harness/use-modal'
import { String, useStrings } from 'framework/strings'
import { DEFAULT_BRANCH_NAME, getErrorMessage, Unknown } from 'utils/Utils'
import type { TypesRepository, OpenapiCreateRepositoryRequest } from 'services/scm'
import { useAppContext } from 'AppContext'
import css from './CommitModalButton.module.scss'
enum CommitToGitRefOption {
DIRECTLY = 'directly',
NEW_BRANCH = 'new-branch'
}
interface RepoFormData {
message: string
extendedDescription: string
newBranch: string
branch: string
commitToGitRefOption: CommitToGitRefOption
}
const formInitialValues: RepoFormData = {
message: '',
extendedDescription: '',
newBranch: '',
branch: '',
commitToGitRefOption: CommitToGitRefOption.DIRECTLY
}
export interface CommitModalButtonProps extends Omit<ButtonProps, 'onClick' | 'onSubmit'> {
gitRef: string
commitMessagePlaceHolder: string
resourcePath: string
onSubmit: (data: TypesRepository) => void
}
export const CommitModalButton: React.FC<CommitModalButtonProps> = ({
gitRef,
resourcePath,
commitMessagePlaceHolder,
onSubmit,
...props
}) => {
const ModalComponent: React.FC = () => {
const { standalone } = useAppContext()
const { getString } = useStrings()
const [targetBranchOption, setTargetBranchOption] = useState(CommitToGitRefOption.DIRECTLY)
const [branchName, setBranchName] = useState(gitRef)
const { showError } = useToaster()
const { mutate: createRepo, loading: submitLoading } = useMutate<TypesRepository>({
verb: 'POST',
path: `/api/v1/repos?spacePath=${gitRef}`
})
const loading = submitLoading
console.log('Commit to', { targetBranchOption, branchName })
const handleSubmit = (formData?: Unknown): void => {
try {
createRepo({
message: branchName || get(formData, 'message', DEFAULT_BRANCH_NAME),
extendedDescription: get(formData, 'extendedDescription', '').trim(),
commitToGitRefOption: get(formData, 'commitToGitRefOption') === CommitToGitRefOption.DIRECTLY,
uid: get(formData, 'name', '').trim(),
parentId: standalone ? gitRef : 0
} as OpenapiCreateRepositoryRequest)
.then(response => {
hideModal()
onSubmit(response)
})
.catch(_error => {
showError(getErrorMessage(_error), 0, 'failedToCreateRepo')
})
} catch (exception) {
showError(getErrorMessage(exception), 0, 'failedToCreateRepo')
}
}
return (
<Dialog
isOpen
enforceFocus={false}
onClose={hideModal}
title={''}
style={{ width: 700, maxHeight: '95vh', overflow: 'auto' }}>
<Layout.Vertical className={cx(css.main)}>
<Heading level={3} font={{ variation: FontVariation.H3 }} margin={{ bottom: 'xlarge' }}>
{getString('commitChanges')}
</Heading>
<Container margin={{ right: 'xxlarge' }}>
<Formik
initialValues={formInitialValues}
formName="commitChanges"
enableReinitialize={true}
validationSchema={yup.object().shape({})}
validateOnChange
validateOnBlur
onSubmit={handleSubmit}>
<FormikForm>
<FormInput.Text
name="message"
label={getString('commitMessage')}
placeholder={commitMessagePlaceHolder}
tooltipProps={{
dataTooltipId: 'commitMessage'
}}
inputGroup={{ autoFocus: true }}
/>
<FormInput.TextArea
className={css.extendedDescription}
name="extendedDescription"
placeholder={getString('optionalExtendedDescription')}
/>
<Container
className={cx(
css.radioGroup,
targetBranchOption === CommitToGitRefOption.DIRECTLY ? css.directly : css.newBranch
)}>
<FormInput.RadioGroup
name="commitToGitRefOption"
label=""
onChange={e => {
setTargetBranchOption(get(e.target, 'defaultValue'))
}}
items={[
{
label: <String stringID="commitDirectlyTo" vars={{ gitRef }} useRichText />,
value: CommitToGitRefOption.DIRECTLY
},
{
label: <String stringID="commitToNewBranch" useRichText />,
value: CommitToGitRefOption.NEW_BRANCH
}
]}
/>
{targetBranchOption === CommitToGitRefOption.NEW_BRANCH && (
<Container>
<Layout.Horizontal spacing="medium" className={css.newBranchContainer}>
<Icon name="git-branch" />
<FormInput.Text
name="newBranch"
placeholder={getString('enterNewBranchName')}
tooltipProps={{
dataTooltipId: 'enterNewBranchName'
}}
inputGroup={{ autoFocus: true }}
/>
</Layout.Horizontal>
</Container>
)}
</Container>
<Layout.Horizontal
spacing="small"
padding={{ right: 'xxlarge', top: 'xxlarge', bottom: 'large' }}
style={{ alignItems: 'center' }}>
<Button type="submit" text={getString('commitChanges')} intent={Intent.PRIMARY} disabled={loading} />
<Button text={getString('cancel')} minimal onClick={hideModal} />
<FlexExpander />
{loading && <Icon intent={Intent.PRIMARY} name="spinner" size={16} />}
</Layout.Horizontal>
</FormikForm>
</Formik>
</Container>
</Layout.Vertical>
</Dialog>
)
}
const [openModal, hideModal] = useModalHook(ModalComponent, [
onSubmit,
gitRef,
resourcePath,
commitMessagePlaceHolder
])
return <Button onClick={openModal} {...props} />
}

View File

@ -96,16 +96,12 @@ export const NewRepoModalButton: React.FC<NewRepoModalButtonProps> = ({
data: gitignores,
loading: gitIgnoreLoading,
error: gitIgnoreError
} = useGet({
path: '/api/v1/resources/gitignore'
})
} = useGet({ path: '/api/v1/resources/gitignore' })
const {
data: licences,
loading: licenseLoading,
error: licenseError
} = useGet({
path: '/api/v1/resources/license'
})
} = useGet({ path: '/api/v1/resources/license' })
const loading = submitLoading || gitIgnoreLoading || licenseLoading
useEffect(() => {

View File

@ -11,9 +11,14 @@ export interface StringsMap {
branch: string
branches: string
cancel: string
cancelChanges: string
clone: string
cloneHTTPS: string
commit: string
commitChanges: string
commitDirectlyTo: string
commitMessage: string
commitToNewBranch: string
commits: string
commitsOn: string
content: string
@ -31,6 +36,7 @@ export interface StringsMap {
editFile: string
email: string
enterDescription: string
enterNewBranchName: string
enterRepoName: string
existingAccount: string
failedToCreateRepo: string
@ -47,6 +53,8 @@ export interface StringsMap {
noAccount: string
none: string
ok: string
optional: string
optionalExtendedDescription: string
pageNotFound: string
password: string
private: string

View File

@ -15,9 +15,11 @@ export function useGetResourceContent({
includeCommit = false
}: UseGetResourceContentParams) {
const { data, error, loading, refetch, response } = useGet<OpenapiGetContentOutput>({
path: `/api/v1/repos/${repoMetadata.path}/+/content${
resourcePath ? '/' + resourcePath : ''
}?include_commit=${String(includeCommit)}${gitRef ? `&git_ref=${gitRef}` : ''}`
path: `/api/v1/repos/${repoMetadata.path}/+/content${resourcePath ? '/' + resourcePath : ''}`,
queryParams: {
include_commit: String(includeCommit),
git_ref: gitRef
}
})
return { data, error, loading, refetch, response }

View File

@ -15,6 +15,8 @@ repositories: Repositories
files: Files
commit: Commit
commits: Commits
commitChanges: Commit Changes
cancelChanges: Cancel Changes
pullRequests: Pull Requests
settings: Settings
newFile: New File
@ -69,3 +71,9 @@ createRepoModal:
validation:
repoNamePatternIsNotValid: "Name can only contain alphanumerics, '-', '_', '.', and '$'"
gitBranchNameInvalid: Branch name is invalid.
commitMessage: Commit message
optionalExtendedDescription: Optional extended description
optional: Optional
commitDirectlyTo: 'Commit directly to the <strong style="color: var(--primary-7)">{{gitRef}}</strong> branch'
commitToNewBranch: Create a <strong>new branch</strong> for this commit and start a pull request
enterNewBranchName: New branch name

View File

@ -38,14 +38,9 @@ export default function RepositoriesListing() {
const [searchTerm, setSearchTerm] = useState<string | undefined>()
const { routes } = useAppContext()
const [pageIndex, setPageIndex] = usePageIndex()
const path = useMemo(
() =>
`/api/v1/spaces/${space}/+/repos?page=${pageIndex + 1}&per_page=${LIST_FETCHING_PER_PAGE}${
searchTerm ? `&query=${searchTerm}` : ''
}`,
[space, searchTerm, pageIndex]
)
const { data: repositories, error, loading, refetch, response } = useGet<TypesRepository[]>({ path })
const path = useMemo(() => `/api/v1/spaces/${space}/+/repos?page=${pageIndex + 1}`, [space, pageIndex])
const queryParams = useMemo(() => ({ per_page: LIST_FETCHING_PER_PAGE, query: searchTerm }), [searchTerm])
const { data: repositories, error, loading, refetch, response } = useGet<TypesRepository[]>({ path, queryParams })
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)
useEffect(() => {

View File

@ -35,12 +35,10 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
const history = useHistory()
const [query, setQuery] = useState('')
const [activeBranch, setActiveBranch] = useState(gitRef || repoMetadata.defaultBranch)
const path = useMemo(
() =>
`/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=${BRANCH_PER_PAGE}&page=1&query=${query}`,
[query, repoMetadata.path]
)
const { data, loading } = useGet<RepoBranch[]>({ path })
const { data, loading } = useGet<RepoBranch[]>({
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
queryParams: { sort: 'date', direction: 'desc', per_page: BRANCH_PER_PAGE, page: 1, query }
})
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
const defaultBranches = useMemo(
() => [repoMetadata.defaultBranch].concat(gitRef ? gitRef : []),
@ -145,7 +143,7 @@ export function ContentHeader({ repoMetadata, gitRef, resourcePath = '' }: Conte
history.push(
routes.toSCMRepositoryFileEdit({
repoPath: repoMetadata.path as string,
resourcePath: '',
resourcePath,
gitRef: gitRef || (repoMetadata.defaultBranch as string)
})
)

View File

@ -1,13 +1,11 @@
import React from 'react'
import { Container, Color, Layout, Button, FlexExpander, ButtonVariation, Heading } from '@harness/uicore'
// import { useHistory } from 'react-router-dom'
import { useHistory } from 'react-router-dom'
import { useGet } from 'restful-react'
// import { useStrings } from 'framework/strings'
import { MarkdownViewer } from 'components/SourceCodeViewer/SourceCodeViewer'
// import { useAppContext } from 'AppContext'
import { useAppContext } from 'AppContext'
import type { OpenapiContentInfo, OpenapiGetContentOutput, RepoFileContent, TypesRepository } from 'services/scm'
import { GitIcon } from 'utils/GitUtils'
import type {} from 'services/scm'
import css from './Readme.module.scss'
interface FolderContentProps {
@ -17,14 +15,15 @@ interface FolderContentProps {
}
export function Readme({ metadata, gitRef, readmeInfo }: FolderContentProps) {
// const { getString } = useStrings()
// const history = useHistory()
// const { routes } = useAppContext()
const history = useHistory()
const { routes } = useAppContext()
const { data /*error, loading, refetch, response */ } = useGet<OpenapiGetContentOutput>({
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}?include_commit=false${
gitRef ? `&git_ref=${gitRef}` : ''
}`
path: `/api/v1/repos/${metadata.path}/+/content/${readmeInfo.path}`,
queryParams: {
include_commit: false,
git_ref: gitRef
}
})
return (
@ -32,7 +31,19 @@ export function Readme({ metadata, gitRef, readmeInfo }: FolderContentProps) {
<Layout.Horizontal padding="small" className={css.heading}>
<Heading level={5}>{readmeInfo.name}</Heading>
<FlexExpander />
<Button variation={ButtonVariation.ICON} icon={GitIcon.EDIT} />
<Button
variation={ButtonVariation.ICON}
icon={GitIcon.EDIT}
onClick={() => {
history.push(
routes.toSCMRepositoryFileEdit({
repoPath: metadata.path as string,
gitRef: gitRef || (metadata.defaultBranch as string),
resourcePath: readmeInfo.path as string
})
)
}}
/>
</Layout.Horizontal>
{/* TODO: Loading and Error handling */}

View File

@ -21,9 +21,14 @@ export function RepositoryBranchesContent({ repoMetadata }: RepositoryBranchesCo
const [searchTerm, setSearchTerm] = useState('')
const [pageIndex, setPageIndex] = usePageIndex()
const { data: branches, response /*error, loading, refetch */ } = useGet<RepoBranch[]>({
path: `/api/v1/repos/${repoMetadata.path}/+/branches?per_page=${LIST_FETCHING_PER_PAGE}&page=${
pageIndex + 1
}&direction=desc&include_commit=true&query=${searchTerm}`
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
queryParams: {
per_page: LIST_FETCHING_PER_PAGE,
page: pageIndex + 1,
direction: 'desc',
include_commit: true,
query: searchTerm
}
})
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)

View File

@ -17,12 +17,16 @@ export function CommitsContentHeader({ repoMetadata, onSwitch }: CommitsContentH
const { getString } = useStrings()
const [query, setQuery] = useState('')
const [activeBranch, setActiveBranch] = useState(repoMetadata.defaultBranch)
const path = useMemo(
() =>
`/api/v1/repos/${repoMetadata.path}/+/branches?sort=date&direction=desc&per_page=${BRANCH_PER_PAGE}&page=1&query=${query}`,
[query, repoMetadata.path]
)
const { data, loading } = useGet<RepoBranch[]>({ path })
const { data, loading } = useGet<RepoBranch[]>({
path: `/api/v1/repos/${repoMetadata.path}/+/branches`,
queryParams: {
sort: 'date',
direction: 'desc',
per_page: BRANCH_PER_PAGE,
page: 1,
query
}
})
// defaultBranches is computed using repository default branch, and gitRef in URL, if it exists
const defaultBranches = useMemo(() => [repoMetadata.defaultBranch].concat([]), [repoMetadata])
const [branches, setBranches] = useState<SelectOption[]>(

View File

@ -21,9 +21,12 @@ export function RepositoryCommitsContent({ repoMetadata, commitRef }: Repository
const history = useHistory()
const [pageIndex, setPageIndex] = usePageIndex()
const { data: commits, response /*error, loading, refetch */ } = useGet<RepoCommit[]>({
path: `/api/v1/repos/${repoMetadata.path}/+/commits?per_page=${LIST_FETCHING_PER_PAGE}&page=${
pageIndex + 1
}&git_ref=${commitRef || repoMetadata.defaultBranch}`
path: `/api/v1/repos/${repoMetadata.path}/+/commits`,
queryParams: {
per_page: LIST_FETCHING_PER_PAGE,
page: pageIndex + 1,
git_ref: commitRef || repoMetadata.defaultBranch
}
})
const { totalItems, totalPages, pageSize } = useGetPaginationInfo(response)

View File

@ -11,7 +11,7 @@
// border-top-right-radius: 4px;
align-items: center;
padding: 0 var(--spacing-xlarge) !important;
height: 52px;
height: 58px;
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);
@ -26,8 +26,21 @@
input {
padding: var(--spacing-xsmall) var(--spacing-small);
height: 28px;
width: 200px;
&:focus {
width: 400px;
}
}
}
.refLink {
font-weight: 600;
border-radius: 4px;
color: var(--primary-7);
background: var(--primary-1);
padding: 2px 8px;
}
}
}
@ -36,7 +49,7 @@
overflow: hidden;
.editorContainer {
height: calc(100vh - 96px - 52px);
height: calc(100vh - 96px - 58px);
overflow: hidden;
}
}

View File

@ -5,6 +5,7 @@ declare const styles: {
readonly heading: string
readonly path: string
readonly inputContainer: string
readonly refLink: string
readonly content: string
readonly editorContainer: string
}

View File

@ -1,38 +1,56 @@
import React from 'react'
import {
Button,
ButtonSize,
ButtonVariation,
Color,
Container,
FlexExpander,
Icon,
Layout,
Text,
TextInput
} from '@harness/uicore'
import { Link } from 'react-router-dom'
import React, { ChangeEvent, useCallback, useMemo, useState } from 'react'
import { Button, ButtonVariation, Color, Container, FlexExpander, Icon, Layout, Text, TextInput } from '@harness/uicore'
import { Link, useHistory } 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'
import { GitIcon } from 'utils/GitUtils'
import { isDir } from 'utils/GitUtils'
import { useStrings } from 'framework/strings'
import { filenameToLanguage } from 'utils/Utils'
import { filenameToLanguage, FILE_SEPERATOR } from 'utils/Utils'
import { CommitModalButton } from 'components/CommitModalButton/CommitModalButton'
import css from './FileEditor.module.scss'
interface FileEditorProps {
repoMetadata: TypesRepository
gitRef: string
resourcePath?: string
resourcePath: string
contentInfo: OpenapiGetContentOutput
}
export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '' }: FileEditorProps) {
const PathSeparator = () => <Text color={Color.GREY_900}>/</Text>
function Editor({ contentInfo, repoMetadata, gitRef, resourcePath }: FileEditorProps) {
const history = useHistory()
const isNew = useMemo(() => isDir(contentInfo), [contentInfo])
const [fileName, setFileName] = useState(isNew ? '' : (contentInfo.name as string))
const [parentPath, setParentPath] = useState(
isNew ? resourcePath : resourcePath.split(FILE_SEPERATOR).slice(0, -1).join(FILE_SEPERATOR)
)
const { getString } = useStrings()
const { routes } = useAppContext()
const language = filenameToLanguage(contentInfo?.name)
const rebuildPaths = useCallback(() => {
const _tokens = fileName.split(FILE_SEPERATOR).filter(part => !!part.trim())
const _fileName = _tokens.pop() as string
const _parentPath = parentPath
.split(FILE_SEPERATOR)
.concat(_tokens)
.map(p => p.trim())
.filter(part => !!part.trim())
.join(FILE_SEPERATOR)
if (_fileName && _fileName !== fileName) {
setFileName(_fileName.trim())
}
setParentPath(_parentPath)
}, [fileName, setFileName, parentPath, setParentPath])
const fileResourcePath = useMemo(
() => [parentPath, fileName].filter(p => !!p.trim()).join(FILE_SEPERATOR),
[parentPath, fileName]
)
return (
<Container className={css.container}>
@ -40,52 +58,87 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
<Container>
<Layout.Horizontal spacing="small" className={css.path}>
<Link to={routes.toSCMRepository({ repoPath: repoMetadata.path as string, gitRef })}>
<Icon name="main-folder" />
<Icon name="main-folder" padding={{ right: 'small' }} />
<Text color={Color.GREY_900} inline>
{repoMetadata.uid}
</Text>
</Link>
<Text color={Color.GREY_900}>/</Text>
<ReactJoin separator={<Text color={Color.GREY_900}>/</Text>}>
{resourcePath.split('/').map((_path, index, paths) => {
const pathAtIndex = paths.slice(0, index + 1).join('/')
<PathSeparator />
{parentPath && (
<>
<ReactJoin separator={<PathSeparator />}>
{parentPath.split(FILE_SEPERATOR).map((_path, index, paths) => {
const pathAtIndex = paths.slice(0, index + 1).join('/')
return index < paths.length - 1 ? (
<Link
key={_path + index}
to={routes.toSCMRepository({
repoPath: repoMetadata.path as string,
gitRef,
resourcePath: pathAtIndex
})}>
<Text color={Color.GREY_900}>{_path}</Text>
</Link>
) : (
<TextInput
key={_path + index}
autoFocus
value={_path || ''}
wrapperClassName={css.inputContainer}
placeholder={getString('nameYourFile')}
/>
)
})}
</ReactJoin>
return (
<Link
key={_path + index}
to={routes.toSCMRepository({
repoPath: repoMetadata.path as string,
gitRef,
resourcePath: pathAtIndex
})}>
<Text color={Color.GREY_900}>{_path}</Text>
</Link>
)
})}
</ReactJoin>
<PathSeparator />
</>
)}
<TextInput
autoFocus
value={fileName}
wrapperClassName={css.inputContainer}
placeholder={getString('nameYourFile')}
onInput={(event: ChangeEvent<HTMLInputElement>) => {
setFileName(event.currentTarget.value)
}}
onBlur={rebuildPaths}
onFocus={event => {
const value = (parentPath ? parentPath + FILE_SEPERATOR : '') + fileName
setFileName(value)
setParentPath('')
setTimeout(() => event.target.setSelectionRange(value.length, value.length), 0)
}}
/>
<Text color={Color.GREY_900}>{getString('in')}</Text>
<Link
to={routes.toSCMRepository({
repoPath: repoMetadata.path as string,
gitRef
})}>
})}
className={css.refLink}>
{gitRef}
</Link>
</Layout.Horizontal>
</Container>
<FlexExpander />
<Button
text={getString('commit')}
icon={GitIcon.COMMIT}
iconProps={{ size: 10 }}
variation={ButtonVariation.PRIMARY}
size={ButtonSize.SMALL}
/>
<Container>
<Layout.Horizontal spacing="small">
<CommitModalButton
text={getString('commitChanges')}
variation={ButtonVariation.PRIMARY}
commitMessagePlaceHolder={'Update file...'}
gitRef={gitRef}
resourcePath={fileResourcePath}
onSubmit={data => console.log({ data })}
/>
<Button
text={getString('cancelChanges')}
variation={ButtonVariation.TERTIARY}
onClick={() => {
history.push(
routes.toSCMRepository({
repoPath: repoMetadata.path as string,
gitRef,
resourcePath
})
)
}}
/>
</Layout.Horizontal>
</Container>
</Layout.Horizontal>
<Container className={cx(css.content, language)}>
@ -99,3 +152,5 @@ export function FileEditor({ contentInfo, repoMetadata, gitRef, resourcePath = '
</Container>
)
}
export const FileEditor = React.memo(Editor)

View File

@ -14,6 +14,7 @@ export type Unknown = any // eslint-disable-line @typescript-eslint/no-explicit-
export const DEFAULT_BRANCH_NAME = 'main'
export const REGEX_VALID_REPO_NAME = /^[a-zA-Z_][0-9a-zA-Z-_.$]*$/
export const SUGGESTED_BRANCH_NAMES = [DEFAULT_BRANCH_NAME, 'master']
export const FILE_SEPERATOR = '/'
/** This utility shows a toaster without being bound to any component.
* It's useful to show cross-page/component messages */