From ae2bcd8e706bb465500a552ad18fcb29cebb0719 Mon Sep 17 00:00:00 2001 From: Alexander Zakharov Date: Fri, 12 Jul 2024 15:14:23 +0200 Subject: [PATCH] feat: #203 allow users to specify maximum upload size --- src/components/Tool/index.tsx | 15 +++++++---- src/components/UploadDropzone/index.tsx | 24 +++++++++++++++-- src/contexts/ToolOptionsContext.tsx | 34 +++++++++++++++++++++++++ src/index.ts | 21 ++++++++------- src/types/index.ts | 4 +++ 5 files changed, 82 insertions(+), 16 deletions(-) create mode 100644 src/contexts/ToolOptionsContext.tsx diff --git a/src/components/Tool/index.tsx b/src/components/Tool/index.tsx index 91adb7e6..942aaa74 100644 --- a/src/components/Tool/index.tsx +++ b/src/components/Tool/index.tsx @@ -1,12 +1,17 @@ import {Flex} from '@sanity/ui' -import React from 'react' +import React, {ComponentProps} from 'react' import Browser from '../Browser' +import {ToolOptionsProvider} from '../../contexts/ToolOptionsContext' +import {Tool as SanityTool} from 'sanity' +import {MediaToolOptions} from '@types' -const Tool = () => { +const Tool = ({tool: {options}}: ComponentProps['component']>) => { return ( - - - + + + + + ) } diff --git a/src/components/UploadDropzone/index.tsx b/src/components/UploadDropzone/index.tsx index f7ca4336..ef873f7d 100644 --- a/src/components/UploadDropzone/index.tsx +++ b/src/components/UploadDropzone/index.tsx @@ -1,7 +1,7 @@ import {white} from '@sanity/color' import {Flex, Text} from '@sanity/ui' import React, {ReactNode} from 'react' -import {DropEvent, useDropzone} from 'react-dropzone' +import {DropEvent, DropzoneOptions, useDropzone} from 'react-dropzone' import {useDispatch} from 'react-redux' import styled from 'styled-components' import {useAssetSourceActions} from '../../contexts/AssetSourceDispatchContext' @@ -9,6 +9,7 @@ import {DropzoneDispatchProvider} from '../../contexts/DropzoneDispatchContext' import useTypedSelector from '../../hooks/useTypedSelector' import {notificationsActions} from '../../modules/notifications' import {uploadsActions} from '../../modules/uploads' +import {useToolOptions} from '../../contexts/ToolOptionsContext' type Props = { children: ReactNode @@ -62,6 +63,10 @@ async function filterFiles(fileList: FileList) { const UploadDropzone = (props: Props) => { const {children} = props + const { + dropzone: {maxSize} + } = useToolOptions() + const {onSelect} = useAssetSourceActions() // Redux @@ -82,6 +87,19 @@ const UploadDropzone = (props: Props) => { ) } + const handleDropRejected: DropzoneOptions['onDropRejected'] = rejections => { + const errorCodes = rejections.flatMap(({errors}) => errors.map(({code}) => code)) + + if (errorCodes.includes('file-too-large')) { + dispatch( + notificationsActions.add({ + status: 'error', + title: 'One or more files exceed the maximum upload size.' + }) + ) + } + } + // Use custom file selector to obtain files on file drop + change events (excluding folders and packages) const handleFileGetter = async (event: DropEvent) => { let fileList: FileList | undefined @@ -123,7 +141,9 @@ const UploadDropzone = (props: Props) => { // HACK: Disable drag and drop functionality when in a selecting context // (This is currently due to Sanity's native image input taking precedence with drag and drop) noDrag: !!onSelect, - onDrop: handleDrop + onDrop: handleDrop, + maxSize, + onDropRejected: handleDropRejected }) return ( diff --git a/src/contexts/ToolOptionsContext.tsx b/src/contexts/ToolOptionsContext.tsx new file mode 100644 index 00000000..69f3379e --- /dev/null +++ b/src/contexts/ToolOptionsContext.tsx @@ -0,0 +1,34 @@ +import {MediaToolOptions} from '@types' +import React, {PropsWithChildren, createContext, useContext, useMemo} from 'react' +import {DropzoneOptions} from 'react-dropzone' + +type ContextProps = { + dropzone: Pick +} + +const ToolOptionsContext = createContext(null) + +type Props = { + options?: MediaToolOptions +} + +export const ToolOptionsProvider = ({options, children}: PropsWithChildren) => { + const value = useMemo( + () => ({dropzone: {maxSize: options?.maximumUploadSize}}), + [options?.maximumUploadSize] + ) + + return {children} +} + +export const useToolOptions = () => { + const context = useContext(ToolOptionsContext) + + if (!context) { + throw new Error('useToolOptions must be used within an ToolOptionsProvider') + } + + return context +} + +export default ToolOptionsContext diff --git a/src/index.ts b/src/index.ts index 8e3554d6..e9414345 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ -import {definePlugin, Tool as SanityTool} from 'sanity' +import {definePlugin} from 'sanity' import {ImageIcon} from '@sanity/icons' import type {AssetSource} from 'sanity' import FormBuilderTool from './components/FormBuilderTool' import Tool from './components/Tool' import mediaTag from './schemas/tag' +import {MediaToolOptions} from '@types' const plugin = { icon: ImageIcon, @@ -16,12 +17,7 @@ export const mediaAssetSource: AssetSource = { component: FormBuilderTool } -const tool = { - ...plugin, - component: Tool -} as SanityTool - -export const media = definePlugin({ +export const media = definePlugin(options => ({ name: 'media', form: { file: { @@ -39,6 +35,13 @@ export const media = definePlugin({ types: [mediaTag] }, tools: prev => { - return [...prev, tool] + return [ + ...prev, + { + ...plugin, + options, + component: Tool + } + ] } -}) +})) diff --git a/src/types/index.ts b/src/types/index.ts index 9f36243f..604ba6e1 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -10,6 +10,10 @@ import * as z from 'zod' import {assetFormSchema, tagFormSchema, tagOptionSchema} from '../formSchema' import {RootReducerState} from '../modules/types' +export type MediaToolOptions = { + maximumUploadSize?: number +} + type CustomFields = { altText?: string description?: string