From edf30b0224bd725fedbe9b313abe7efcc37cd85d Mon Sep 17 00:00:00 2001 From: Maksim Chervonnyi Date: Mon, 10 Oct 2022 11:47:17 +0300 Subject: [PATCH] Package create file (#3127) --- .../app/components/FileEditor/CreateFile.tsx | 78 +++++++++++++++++++ catalog/app/components/FileEditor/index.ts | 3 +- catalog/app/containers/Bucket/Dir.tsx | 33 +------- .../Bucket/PackageTree/PackageTree.tsx | 4 + .../Bucket/PackageTree/RevisionMenu.tsx | 11 ++- docs/CHANGELOG.md | 1 + 6 files changed, 96 insertions(+), 34 deletions(-) create mode 100644 catalog/app/components/FileEditor/CreateFile.tsx diff --git a/catalog/app/components/FileEditor/CreateFile.tsx b/catalog/app/components/FileEditor/CreateFile.tsx new file mode 100644 index 00000000000..5bb664fe370 --- /dev/null +++ b/catalog/app/components/FileEditor/CreateFile.tsx @@ -0,0 +1,78 @@ +import { join, extname } from 'path' + +import * as React from 'react' +import * as RRDom from 'react-router-dom' + +import * as Dialog from 'components/Dialog' +import * as NamedRoutes from 'utils/NamedRoutes' +import type { PackageHandle } from 'utils/packageHandle' + +import { isSupportedFileType } from './loader' + +function validateFileName(value: string) { + if (!value) { + return new Error('File name is required') + } + if (!isSupportedFileType(value) || extname(value) === '.' || !extname(value)) { + // TODO: get list of supported extensions from FileEditor + return new Error('Supported file formats are JSON, Markdown, YAML and text') + } +} + +export function useCreateFileInBucket(bucket: string, path: string) { + const { urls } = NamedRoutes.use() + const history = RRDom.useHistory() + + const toFile = React.useCallback( + (name: string) => urls.bucketFile(bucket, join(path, name), { edit: true }), + [bucket, path, urls], + ) + + const createFile = React.useCallback( + (name: string) => { + if (!name) return + history.push(toFile(name)) + }, + [history, toFile], + ) + + return Dialog.usePrompt({ + onSubmit: createFile, + initialValue: 'README.md', + title: 'Enter file name', + validate: validateFileName, + }) +} + +export function useCreateFileInPackage({ bucket, name }: PackageHandle) { + const { urls } = NamedRoutes.use() + const history = RRDom.useHistory() + + const toFile = React.useCallback( + (fileName: string) => { + const next = urls.bucketPackageDetail(bucket, name, { action: 'revisePackage' }) + const key = join(name, fileName) + return urls.bucketFile(bucket, key, { + add: fileName, + edit: true, + next, + }) + }, + [bucket, name, urls], + ) + + const createFile = React.useCallback( + (fileName: string) => { + if (!fileName) return + history.push(toFile(fileName)) + }, + [history, toFile], + ) + + return Dialog.usePrompt({ + onSubmit: createFile, + initialValue: 'README.md', + title: 'Enter file name', + validate: validateFileName, + }) +} diff --git a/catalog/app/components/FileEditor/index.ts b/catalog/app/components/FileEditor/index.ts index 7a53cbcfcec..364667a7094 100644 --- a/catalog/app/components/FileEditor/index.ts +++ b/catalog/app/components/FileEditor/index.ts @@ -1,2 +1,3 @@ -export * from './FileEditor' export * from './Controls' +export * from './CreateFile' +export * from './FileEditor' diff --git a/catalog/app/containers/Bucket/Dir.tsx b/catalog/app/containers/Bucket/Dir.tsx index 7137c107a14..27be4caa74a 100644 --- a/catalog/app/containers/Bucket/Dir.tsx +++ b/catalog/app/containers/Bucket/Dir.tsx @@ -1,4 +1,4 @@ -import { basename, join, extname } from 'path' +import { basename, join } from 'path' import dedent from 'dedent' import * as R from 'ramda' @@ -8,7 +8,6 @@ import * as M from '@material-ui/core' import { Crumb, copyWithoutSpaces, render as renderCrumbs } from 'components/BreadCrumbs' import type * as DG from 'components/DataGrid' -import * as Dialog from 'components/Dialog' import * as FileEditor from 'components/FileEditor' import * as Bookmarks from 'containers/Bookmarks' import AsyncResult from 'utils/AsyncResult' @@ -39,35 +38,7 @@ interface DirectoryMenuProps { } function DirectoryMenu({ bucket, path, className }: DirectoryMenuProps) { - const { urls } = NamedRoutes.use() - const history = RRDom.useHistory() - - const createFile = React.useCallback( - (name: string) => { - if (!name) return - history.push(urls.bucketFile(bucket, join(path, name), { edit: true })) - }, - [bucket, history, path, urls], - ) - const validateFileName = React.useCallback((value: string) => { - if (!value) { - return new Error('File name is required') - } - if ( - !FileEditor.isSupportedFileType(value) || - extname(value) === '.' || - !extname(value) - ) { - // TODO: get list of supported extensions from FileEditor - return new Error('Supported file formats are JSON, Markdown, YAML and text') - } - }, []) - const prompt = Dialog.usePrompt({ - onSubmit: createFile, - initialValue: 'README.md', - title: 'Enter file name', - validate: validateFileName, - }) + const prompt = FileEditor.useCreateFileInBucket(bucket, path) const menuItems = React.useMemo( () => [ { diff --git a/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx b/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx index 181ceb1f932..fbf29408041 100644 --- a/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx +++ b/catalog/app/containers/Bucket/PackageTree/PackageTree.tsx @@ -298,6 +298,8 @@ function DirDisplay({ const openInDesktopState = OpenInDesktop.use(packageHandle, size) + const prompt = FileEditor.useCreateFileInPackage(packageHandle) + return ( <> + {prompt.render()} {hasReviseButton && ( {preferences?.ui?.blocks?.code && ( diff --git a/catalog/app/containers/Bucket/PackageTree/RevisionMenu.tsx b/catalog/app/containers/Bucket/PackageTree/RevisionMenu.tsx index bf98fb0ced1..a6fc7533452 100644 --- a/catalog/app/containers/Bucket/PackageTree/RevisionMenu.tsx +++ b/catalog/app/containers/Bucket/PackageTree/RevisionMenu.tsx @@ -7,12 +7,14 @@ import Menu from '../Menu' interface RevisionMenuProps { className: string + onCreateFile: () => void onDelete: () => void onDesktop: () => void } export default function RevisionMenu({ className, + onCreateFile, onDelete, onDesktop, }: RevisionMenuProps) { @@ -20,7 +22,12 @@ export default function RevisionMenu({ const { desktop }: { desktop: boolean } = Config.use() const items = React.useMemo(() => { - const menu = [] + const menu = [ + { + onClick: onCreateFile, + title: 'Create file', + }, + ] if (preferences?.ui?.actions?.deleteRevision) { menu.push({ onClick: onDelete, @@ -34,7 +41,7 @@ export default function RevisionMenu({ }) } return menu - }, [desktop, onDelete, onDesktop, preferences]) + }, [desktop, onCreateFile, onDelete, onDesktop, preferences]) if (!items.length) return null diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5c6d6cabb9d..76cf79e2e23 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -34,6 +34,7 @@ * [Added] Edit button for text files in packages ([#3070](https://github.com/quiltdata/quilt/pull/3070)) * [Added] Add execution context for Athena query execution ([#3062](https://github.com/quiltdata/quilt/pull/3062)) * [Added] Add confirmation if now every row is valid for creating package from Athena results ([#3073](https://github.com/quiltdata/quilt/pull/3073)) +* [Added] Create file menu item for package ([#3127](https://github.com/quiltdata/quilt/pull/3127)) * [Fixed] Fix package creation in S3 buckets with SSE-KMS enabled ([#2754](https://github.com/quiltdata/quilt/pull/2754)) * [Fixed] Fix creation of packages with large (4+ GiB) files ([#2933](https://github.com/quiltdata/quilt/pull/2933)) * [Fixed] Fix pre-popullation of default dates when using "dateformat" + {"format": "date"} ([#3082](https://github.com/quiltdata/quilt/pull/3082))