mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1394 from omnivore-app/feat/dragndrop
Feat/dragndrop
This commit is contained in:
commit
6afea97975
7 changed files with 467 additions and 153 deletions
|
|
@ -1,4 +1,6 @@
|
|||
import { Box, HStack, VStack } from './../../elements/LayoutPrimitives'
|
||||
import { Box, HStack, SpanBox, VStack } from './../../elements/LayoutPrimitives'
|
||||
import Dropzone from 'react-dropzone'
|
||||
import * as Progress from '@radix-ui/react-progress'
|
||||
import type {
|
||||
LibraryItem,
|
||||
LibraryItemsQueryInput,
|
||||
|
|
@ -13,7 +15,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { LibrarySearchBar } from './LibrarySearchBar'
|
||||
import { StyledText } from '../../elements/StyledText'
|
||||
import { AddLinkModal } from './AddLinkModal'
|
||||
import { styled } from '../../tokens/stitches.config'
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
import { ListLayoutIcon } from '../../elements/images/ListLayoutIcon'
|
||||
import { GridLayoutIcon } from '../../elements/images/GridLayoutIcon'
|
||||
import {
|
||||
|
|
@ -50,6 +52,8 @@ import {
|
|||
TypeaheadSearchItemsData,
|
||||
typeaheadSearchQuery,
|
||||
} from '../../../lib/networking/queries/typeaheadSearch'
|
||||
import axios from 'axios'
|
||||
import { uploadFileRequestMutation } from '../../../lib/networking/mutations/uploadFileMutation'
|
||||
|
||||
export type LayoutType = 'LIST_LAYOUT' | 'GRID_LAYOUT'
|
||||
|
||||
|
|
@ -125,8 +129,14 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
})
|
||||
)
|
||||
|
||||
const { itemsPages, size, setSize, isValidating, performActionOnItem } =
|
||||
useGetLibraryItemsQuery(queryInputs)
|
||||
const {
|
||||
itemsPages,
|
||||
size,
|
||||
setSize,
|
||||
isValidating,
|
||||
performActionOnItem,
|
||||
mutate,
|
||||
} = useGetLibraryItemsQuery(queryInputs)
|
||||
|
||||
useEffect(() => {
|
||||
if (queryValue.startsWith('#')) {
|
||||
|
|
@ -558,6 +568,7 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
<HomeFeedGrid
|
||||
items={libraryItems}
|
||||
actionHandler={handleCardAction}
|
||||
reloadItems={mutate}
|
||||
searchTerm={queryInputs.searchQuery}
|
||||
gridContainerRef={gridContainerRef}
|
||||
applySearchQuery={(searchQuery: string) => {
|
||||
|
|
@ -611,6 +622,7 @@ export function HomeFeedContainer(): JSX.Element {
|
|||
type HomeFeedContentProps = {
|
||||
items: LibraryItem[]
|
||||
searchTerm?: string
|
||||
reloadItems: () => void
|
||||
gridContainerRef: React.RefObject<HTMLDivElement>
|
||||
applySearchQuery: (searchQuery: string) => void
|
||||
hasMore: boolean
|
||||
|
|
@ -683,6 +695,20 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
},
|
||||
})
|
||||
|
||||
const DragnDropStyle = styled('div', {
|
||||
border: '3px dashed gray',
|
||||
backgroundColor: 'aliceblue',
|
||||
borderRadius: '5px',
|
||||
width: '95%',
|
||||
height: '80%',
|
||||
position: 'absolute',
|
||||
opacity: '0.9',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: '1',
|
||||
})
|
||||
|
||||
const removeItem = () => {
|
||||
if (!props.linkToRemove) {
|
||||
return
|
||||
|
|
@ -702,6 +728,51 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
setShowUnsubscribeConfirmation(false)
|
||||
}
|
||||
|
||||
const [uploadingFiles, setUploadingFiles] = useState([])
|
||||
const [inDragOperation, setInDragOperation] = useState(false)
|
||||
const [uploadProgress, setUploadProgress] = useState(0)
|
||||
|
||||
const handleDrop = async (acceptedFiles: any) => {
|
||||
setInDragOperation(false)
|
||||
setUploadingFiles(acceptedFiles.map((file: { name: any }) => file.name))
|
||||
|
||||
for (const file of acceptedFiles) {
|
||||
try {
|
||||
const request = await uploadFileRequestMutation({
|
||||
// This will tell the backend not to save the URL
|
||||
// and give it the local filename as the title.
|
||||
url: `file://local/${file.path}`,
|
||||
contentType: file.type,
|
||||
createPageEntry: true,
|
||||
})
|
||||
if (!request?.uploadSignedUrl) {
|
||||
throw 'No upload URL available'
|
||||
}
|
||||
|
||||
const uploadResult = await axios.request({
|
||||
method: 'PUT',
|
||||
url: request?.uploadSignedUrl,
|
||||
data: file,
|
||||
withCredentials: false,
|
||||
headers: {
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
onUploadProgress: (p) => {
|
||||
console.log('upload progress: ', (p.loaded / p.total) * 100)
|
||||
setUploadProgress((p.loaded / p.total) * 100)
|
||||
},
|
||||
})
|
||||
|
||||
console.log('result of uploading: ', uploadResult)
|
||||
} catch (error) {
|
||||
console.log('ERROR', error)
|
||||
}
|
||||
}
|
||||
|
||||
setUploadingFiles([])
|
||||
props.reloadItems()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack
|
||||
|
|
@ -715,6 +786,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
}}
|
||||
>
|
||||
<Toaster />
|
||||
|
||||
{props.isValidating && props.items.length == 0 && <TopBarProgress />}
|
||||
<HStack alignment="center" distribution="start" css={{ width: '100%' }}>
|
||||
<StyledText
|
||||
|
|
@ -766,6 +838,7 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
searchTerm={props.searchTerm}
|
||||
applySearchQuery={props.applySearchQuery}
|
||||
/>
|
||||
|
||||
{viewerData?.me && (
|
||||
<Box
|
||||
css={{
|
||||
|
|
@ -813,112 +886,185 @@ function HomeFeedGrid(props: HomeFeedContentProps): JSX.Element {
|
|||
})}
|
||||
</Box>
|
||||
)}
|
||||
{!props.isValidating && props.items.length == 0 ? (
|
||||
<EmptyLibrary
|
||||
onAddLinkClicked={() => {
|
||||
props.setShowAddLinkModal(true)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
ref={props.gridContainerRef}
|
||||
css={{
|
||||
py: '$3',
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
gridAutoRows: 'auto',
|
||||
borderRadius: '8px',
|
||||
gridGap: layout == 'LIST_LAYOUT' ? '0' : '$3',
|
||||
marginTop: layout == 'LIST_LAYOUT' ? '21px' : '0',
|
||||
marginBottom: '0px',
|
||||
paddingTop: layout == 'LIST_LAYOUT' ? '0' : '21px',
|
||||
paddingBottom: layout == 'LIST_LAYOUT' ? '0px' : '21px',
|
||||
overflow: 'hidden',
|
||||
'@smDown': {
|
||||
border: 'unset',
|
||||
width: layout == 'LIST_LAYOUT' ? '100vw' : undefined,
|
||||
margin: layout == 'LIST_LAYOUT' ? '16px -16px' : undefined,
|
||||
borderRadius: layout == 'LIST_LAYOUT' ? 0 : undefined,
|
||||
},
|
||||
'@md': {
|
||||
gridTemplateColumns:
|
||||
layout == 'LIST_LAYOUT' ? 'none' : '1fr 1fr',
|
||||
},
|
||||
'@lg': {
|
||||
gridTemplateColumns:
|
||||
layout == 'LIST_LAYOUT' ? 'none' : 'repeat(3, 1fr)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.items.map((linkedItem) => (
|
||||
<Box
|
||||
className="linkedItemCard"
|
||||
data-testid="linkedItemCard"
|
||||
id={linkedItem.node.id}
|
||||
tabIndex={0}
|
||||
key={linkedItem.node.id}
|
||||
css={{
|
||||
width: '100%',
|
||||
'&> div': {
|
||||
bg: '$grayBg',
|
||||
},
|
||||
'&:focus': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
'&:hover': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{viewerData?.me && (
|
||||
<LinkedItemCard
|
||||
layout={layout}
|
||||
item={linkedItem.node}
|
||||
viewer={viewerData.me}
|
||||
handleAction={(action: LinkedItemCardAction) => {
|
||||
if (action === 'delete') {
|
||||
setShowRemoveLinkConfirmation(true)
|
||||
props.setLinkToRemove(linkedItem)
|
||||
} else if (action === 'editTitle') {
|
||||
props.setShowEditTitleModal(true)
|
||||
props.setLinkToEdit(linkedItem)
|
||||
} else if (action == 'unsubscribe') {
|
||||
setShowUnsubscribeConfirmation(true)
|
||||
props.setLinkToUnsubscribe(linkedItem)
|
||||
} else {
|
||||
props.actionHandler(action, linkedItem)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<HStack
|
||||
distribution="center"
|
||||
css={{ width: '100%', mt: '$2', mb: '$4' }}
|
||||
<Dropzone
|
||||
onDrop={handleDrop}
|
||||
onDragEnter={() => {
|
||||
setInDragOperation(true)
|
||||
}}
|
||||
onDragLeave={() => {
|
||||
setInDragOperation(false)
|
||||
}}
|
||||
preventDropOnDocument={true}
|
||||
noClick={true}
|
||||
>
|
||||
{props.hasMore ? (
|
||||
<Button
|
||||
style="ctaGray"
|
||||
css={{
|
||||
cursor: props.isValidating ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
onClick={props.loadMore}
|
||||
disabled={props.isValidating}
|
||||
>
|
||||
{props.isValidating ? 'Loading' : 'Load More'}
|
||||
</Button>
|
||||
) : (
|
||||
<StyledText style="caption"></StyledText>
|
||||
{({ getRootProps, getInputProps, acceptedFiles, fileRejections }) => (
|
||||
<div {...getRootProps({ className: 'dropzone' })}>
|
||||
{inDragOperation && uploadingFiles.length < 1 && (
|
||||
<DragnDropStyle>
|
||||
<Box
|
||||
css={{
|
||||
color: '$utilityTextDefault',
|
||||
fontWeight: '800',
|
||||
fontSize: '$4',
|
||||
}}
|
||||
>
|
||||
Drop PDF document here to add to your library
|
||||
</Box>
|
||||
</DragnDropStyle>
|
||||
)}
|
||||
{uploadingFiles.length > 0 && (
|
||||
<DragnDropStyle>
|
||||
<Box
|
||||
css={{
|
||||
color: '$utilityTextDefault',
|
||||
fontWeight: '800',
|
||||
fontSize: '$4',
|
||||
width: '80%',
|
||||
}}
|
||||
>
|
||||
<Progress.Root
|
||||
className="ProgressRoot"
|
||||
value={uploadProgress}
|
||||
>
|
||||
<Progress.Indicator
|
||||
className="ProgressIndicator"
|
||||
style={{
|
||||
transform: `translateX(-${100 - uploadProgress}%)`,
|
||||
}}
|
||||
/>
|
||||
</Progress.Root>
|
||||
<StyledText
|
||||
style="boldText"
|
||||
css={{
|
||||
color: theme.colors.omnivoreGray.toString(),
|
||||
}}
|
||||
>
|
||||
Uploading file
|
||||
</StyledText>
|
||||
</Box>
|
||||
</DragnDropStyle>
|
||||
)}
|
||||
<input {...getInputProps()} />
|
||||
{!props.isValidating && props.items.length == 0 ? (
|
||||
<EmptyLibrary
|
||||
onAddLinkClicked={() => {
|
||||
props.setShowAddLinkModal(true)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
ref={props.gridContainerRef}
|
||||
css={{
|
||||
py: '$3',
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
gridAutoRows: 'auto',
|
||||
borderRadius: '8px',
|
||||
gridGap: layout == 'LIST_LAYOUT' ? '0' : '$3',
|
||||
marginTop: layout == 'LIST_LAYOUT' ? '21px' : '0',
|
||||
marginBottom: '0px',
|
||||
paddingTop: layout == 'LIST_LAYOUT' ? '0' : '21px',
|
||||
paddingBottom: layout == 'LIST_LAYOUT' ? '0px' : '21px',
|
||||
overflow: 'hidden',
|
||||
'@smDown': {
|
||||
border: 'unset',
|
||||
width: layout == 'LIST_LAYOUT' ? '100vw' : undefined,
|
||||
margin:
|
||||
layout == 'LIST_LAYOUT' ? '16px -16px' : undefined,
|
||||
borderRadius: layout == 'LIST_LAYOUT' ? 0 : undefined,
|
||||
},
|
||||
'@md': {
|
||||
gridTemplateColumns:
|
||||
layout == 'LIST_LAYOUT' ? 'none' : '1fr 1fr',
|
||||
},
|
||||
'@lg': {
|
||||
gridTemplateColumns:
|
||||
layout == 'LIST_LAYOUT' ? 'none' : 'repeat(3, 1fr)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{props.items.map((linkedItem) => (
|
||||
<Box
|
||||
className="linkedItemCard"
|
||||
data-testid="linkedItemCard"
|
||||
id={linkedItem.node.id}
|
||||
tabIndex={0}
|
||||
key={linkedItem.node.id}
|
||||
css={{
|
||||
width: '100%',
|
||||
'&> div': {
|
||||
bg: '$grayBg',
|
||||
},
|
||||
'&:focus': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
'&:hover': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{viewerData?.me && (
|
||||
<LinkedItemCard
|
||||
layout={layout}
|
||||
item={linkedItem.node}
|
||||
viewer={viewerData.me}
|
||||
handleAction={(action: LinkedItemCardAction) => {
|
||||
if (action === 'delete') {
|
||||
setShowRemoveLinkConfirmation(true)
|
||||
props.setLinkToRemove(linkedItem)
|
||||
} else if (action === 'editTitle') {
|
||||
props.setShowEditTitleModal(true)
|
||||
props.setLinkToEdit(linkedItem)
|
||||
} else if (action == 'unsubscribe') {
|
||||
setShowUnsubscribeConfirmation(true)
|
||||
props.setLinkToUnsubscribe(linkedItem)
|
||||
} else {
|
||||
props.actionHandler(action, linkedItem)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
<HStack
|
||||
distribution="center"
|
||||
css={{ width: '100%', mt: '$2', mb: '$4' }}
|
||||
>
|
||||
{props.hasMore ? (
|
||||
<Button
|
||||
style="ctaGray"
|
||||
css={{
|
||||
cursor: props.isValidating ? 'not-allowed' : 'pointer',
|
||||
}}
|
||||
onClick={props.loadMore}
|
||||
disabled={props.isValidating}
|
||||
>
|
||||
{props.isValidating ? 'Loading' : 'Load More'}
|
||||
</Button>
|
||||
) : (
|
||||
<StyledText style="caption"></StyledText>
|
||||
)}
|
||||
</HStack>
|
||||
</div>
|
||||
)}
|
||||
</HStack>
|
||||
</Dropzone>
|
||||
</VStack>
|
||||
{/* Temporary code */}
|
||||
{/* <div>
|
||||
<strong>Files:</strong>
|
||||
<ul>
|
||||
{uploadingFiles.map((fileName) => (
|
||||
<li key={fileName}>{fileName}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div> */}
|
||||
{/* Temporary code */}
|
||||
{props.showAddLinkModal && (
|
||||
<AddLinkModal onOpenChange={() => props.setShowAddLinkModal(false)} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { useMemo, useState } from 'react'
|
||||
import Dropzone from 'react-dropzone'
|
||||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
|
||||
import { useGetUserPreferences } from '../../../lib/networking/queries/useGetUserPreferences'
|
||||
import { useMemo } from 'react'
|
||||
import { useGetLibraryItemsQuery } from '../../../lib/networking/queries/useGetLibraryItemsQuery'
|
||||
import { LinkedItemCardAction } from '../../patterns/LibraryCards/CardTypes'
|
||||
import { LibraryGridCard } from '../../patterns/LibraryCards/LibraryGridCard'
|
||||
|
|
@ -9,7 +10,6 @@ import { LayoutCoordinator } from './LibraryContainer'
|
|||
import { EmptyLibrary } from '../homeFeed/EmptyLibrary'
|
||||
import Masonry from 'react-masonry-css'
|
||||
|
||||
|
||||
export type LibraryListProps = {
|
||||
layoutCoordinator: LayoutCoordinator
|
||||
}
|
||||
|
|
@ -28,6 +28,15 @@ export function LibraryList(props: LibraryListProps): JSX.Element {
|
|||
const { itemsPages, size, setSize, isValidating, performActionOnItem } =
|
||||
useGetLibraryItemsQuery(defaultQuery)
|
||||
|
||||
const [fileNames, setFileNames] = useState([])
|
||||
const [inDragOperation, setInDragOperation] = useState(false)
|
||||
const [uploadingFiles, setUploadingFiles] = useState([])
|
||||
|
||||
const handleDrop = (acceptedFiles: any) => {
|
||||
setFileNames(acceptedFiles.map((file: { name: any }) => file.name))
|
||||
setUploadingFiles(acceptedFiles.map((file: { name: any }) => file.name))
|
||||
}
|
||||
|
||||
const libraryItems = useMemo(() => {
|
||||
const items =
|
||||
itemsPages?.flatMap((ad) => {
|
||||
|
|
@ -45,57 +54,116 @@ export function LibraryList(props: LibraryListProps): JSX.Element {
|
|||
/>
|
||||
)
|
||||
}
|
||||
console.log(fileNames)
|
||||
|
||||
return (
|
||||
<Box css={{ overflowY: 'scroll' }}>
|
||||
<Masonry
|
||||
breakpointCols={props.layoutCoordinator.layout == 'LIST_LAYOUT' ? 1 : {
|
||||
default: 3,
|
||||
1200: 2,
|
||||
992: 1
|
||||
}}
|
||||
className="omnivore-masonry-grid"
|
||||
columnClassName="omnivore-masonry-grid_column"
|
||||
>
|
||||
{libraryItems.map((linkedItem) => (
|
||||
{inDragOperation && uploadingFiles.length < 1 && (
|
||||
<Box
|
||||
css={{
|
||||
border: '3px dashed gray',
|
||||
backgroundColor: 'aliceblue',
|
||||
borderRadius: '5px',
|
||||
width: '75%',
|
||||
height: '70%',
|
||||
position: 'absolute',
|
||||
opacity: '0.8',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="linkedItemCard"
|
||||
data-testid="linkedItemCard"
|
||||
id={linkedItem.node.id}
|
||||
tabIndex={0}
|
||||
key={linkedItem.node.id}
|
||||
css={{
|
||||
width: '100%',
|
||||
'&> div': {
|
||||
bg: '$libraryBackground',
|
||||
},
|
||||
'&:focus': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
'&:hover': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
color: '$utilityTextDefault',
|
||||
fontWeight: '800',
|
||||
fontSize: '$4',
|
||||
}}
|
||||
>
|
||||
{viewerData?.me && (
|
||||
<LibraryGridCard
|
||||
layout={props.layoutCoordinator.layout}
|
||||
item={linkedItem.node}
|
||||
viewer={viewerData.me}
|
||||
handleAction={(action: LinkedItemCardAction) => {
|
||||
console.log('card clicked')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
Drag n drop files here
|
||||
</Box>
|
||||
))}
|
||||
</Masonry>
|
||||
</Box>
|
||||
)}
|
||||
<Dropzone
|
||||
onDrop={handleDrop}
|
||||
preventDropOnDocument={true}
|
||||
onDragEnter={() => {
|
||||
setInDragOperation(true)
|
||||
}}
|
||||
onDragLeave={() => {
|
||||
setInDragOperation(false)
|
||||
}}
|
||||
noClick={true}
|
||||
noDragEventsBubbling={true}
|
||||
>
|
||||
{({ getRootProps, getInputProps, acceptedFiles, fileRejections }) => (
|
||||
<Box {...getRootProps({ className: 'dropzone' })}>
|
||||
<input {...getInputProps()} />
|
||||
<Masonry
|
||||
breakpointCols={
|
||||
props.layoutCoordinator.layout == 'LIST_LAYOUT'
|
||||
? 1
|
||||
: {
|
||||
default: 3,
|
||||
1200: 2,
|
||||
992: 1,
|
||||
}
|
||||
}
|
||||
className="omnivore-masonry-grid"
|
||||
columnClassName="omnivore-masonry-grid_column"
|
||||
>
|
||||
{libraryItems.map((linkedItem) => (
|
||||
<Box
|
||||
className="linkedItemCard"
|
||||
data-testid="linkedItemCard"
|
||||
id={linkedItem.node.id}
|
||||
tabIndex={0}
|
||||
key={linkedItem.node.id}
|
||||
css={{
|
||||
width: '100%',
|
||||
'&> div': {
|
||||
bg: '$libraryBackground',
|
||||
},
|
||||
'&:focus': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
'&:hover': {
|
||||
'> div': {
|
||||
bg: '$grayBgActive',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{viewerData?.me && (
|
||||
<LibraryGridCard
|
||||
layout={props.layoutCoordinator.layout}
|
||||
item={linkedItem.node}
|
||||
viewer={viewerData.me}
|
||||
handleAction={(action: LinkedItemCardAction) => {
|
||||
console.log('card clicked')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Masonry>
|
||||
</Box>
|
||||
)}
|
||||
</Dropzone>
|
||||
{/* Temporary code */}
|
||||
<div>
|
||||
<strong>Files:</strong>
|
||||
<ul>
|
||||
{fileNames.map((fileName) => (
|
||||
<li key={fileName}>{fileName}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>{' '}
|
||||
{/* Temporary code */}
|
||||
{/* Extra padding at bottom to give space for scrolling */}
|
||||
<Box css={{ width: '100%', height: '200px' }} />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
55
packages/web/lib/networking/mutations/uploadFileMutation.ts
Normal file
55
packages/web/lib/networking/mutations/uploadFileMutation.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import { gqlFetcher } from '../networkHelpers'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
|
||||
type UploadFileInput = {
|
||||
url: string
|
||||
contentType: string
|
||||
createPageEntry?: boolean
|
||||
clientRequestId?: string
|
||||
}
|
||||
|
||||
type UploadFileOutput = {
|
||||
jobId?: string
|
||||
url?: string
|
||||
clientRequestId?: string
|
||||
}
|
||||
|
||||
type UploadFileResponseData = {
|
||||
uploadFileRequest?: UploadFileData
|
||||
errorCodes?: unknown[]
|
||||
}
|
||||
|
||||
type UploadFileData = {
|
||||
id: string
|
||||
uploadSignedUrl: string
|
||||
}
|
||||
|
||||
export async function uploadFileRequestMutation(
|
||||
input: UploadFileInput
|
||||
): Promise<UploadFileData | undefined> {
|
||||
const mutation = `
|
||||
mutation UploadFileRequest($input: UploadFileRequestInput!) {
|
||||
uploadFileRequest(input:$input) {
|
||||
... on UploadFileRequestError {
|
||||
errorCodes
|
||||
}
|
||||
... on UploadFileRequestSuccess {
|
||||
id
|
||||
uploadSignedUrl
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
if (!input.clientRequestId) {
|
||||
input.clientRequestId = uuidv4()
|
||||
}
|
||||
|
||||
const data = await gqlFetcher(mutation, { input })
|
||||
const output = data as UploadFileResponseData | undefined
|
||||
const error = output?.errorCodes?.find(() => true)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
return output?.uploadFileRequest
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ type LibraryItemsQueryResponse = {
|
|||
size: number | ((_size: number) => number)
|
||||
) => Promise<unknown[] | undefined>
|
||||
performActionOnItem: (action: LibraryItemAction, item: LibraryItem) => void
|
||||
mutate: () => void
|
||||
}
|
||||
|
||||
type LibraryItemAction =
|
||||
|
|
@ -352,5 +353,6 @@ export function useGetLibraryItemsQuery({
|
|||
performActionOnItem,
|
||||
size,
|
||||
setSize,
|
||||
mutate
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||
"@radix-ui/react-id": "^0.1.1",
|
||||
"@radix-ui/react-popover": "^0.1.1",
|
||||
"@radix-ui/react-progress": "^1.0.1",
|
||||
"@radix-ui/react-separator": "^0.1.0",
|
||||
"@radix-ui/react-tooltip": "^0.1.7",
|
||||
"@segment/analytics-next": "^1.33.5",
|
||||
|
|
@ -43,6 +44,7 @@
|
|||
"react-apple-login": "^1.1.3",
|
||||
"react-colorful": "^5.5.1",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hot-toast": "^2.1.1",
|
||||
"react-masonry-css": "^1.0.16",
|
||||
"react-pro-sidebar": "^0.7.1",
|
||||
|
|
|
|||
|
|
@ -376,4 +376,19 @@ button {
|
|||
/* .omnivore-masonry-grid_column > div {
|
||||
background: grey;
|
||||
margin-bottom: 16px;
|
||||
} */
|
||||
} */
|
||||
|
||||
.ProgressRoot {
|
||||
overflow: hidden;
|
||||
background: var(--colors-omnivoreGray);
|
||||
border-radius: 99999px;
|
||||
height: 25px;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.ProgressIndicator {
|
||||
background-color: var(--colors-omnivoreCtaYellow) ;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 660ms cubic-bezier(0.65, 0, 0.35, 1);
|
||||
}
|
||||
28
yarn.lock
28
yarn.lock
|
|
@ -5172,6 +5172,11 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@ramonak/react-progress-bar@^5.0.3":
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@ramonak/react-progress-bar/-/react-progress-bar-5.0.3.tgz#a3518fb19e6650e593a208dd429dca7cb6b63d52"
|
||||
integrity sha512-VxXGKN74q94jYoeYuFNJm3xvWhVz9dy+alFZ8S4ZmTTr/05CCq9PjwthT8JB27UdAvn8pHvKBmemV8JU2cZi6A==
|
||||
|
||||
"@reach/observe-rect@^1.1.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
|
||||
|
|
@ -9907,6 +9912,11 @@ atob@^2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
|
||||
|
||||
attr-accept@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b"
|
||||
integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==
|
||||
|
||||
auto-bind@~4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb"
|
||||
|
|
@ -14178,6 +14188,13 @@ file-loader@^6.2.0:
|
|||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
|
||||
file-selector@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc"
|
||||
integrity sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
file-system-cache@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f"
|
||||
|
|
@ -21650,7 +21667,7 @@ promzard@^0.3.0:
|
|||
dependencies:
|
||||
read "1"
|
||||
|
||||
prop-types@^15.0.0, prop-types@^15.6.0:
|
||||
prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
|
@ -22206,6 +22223,15 @@ react-draggable@^4.4.3:
|
|||
clsx "^1.1.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-dropzone@^14.2.3:
|
||||
version "14.2.3"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.3.tgz#0acab68308fda2d54d1273a1e626264e13d4e84b"
|
||||
integrity sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==
|
||||
dependencies:
|
||||
attr-accept "^2.2.2"
|
||||
file-selector "^0.6.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
react-element-to-jsx-string@^14.3.4:
|
||||
version "14.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz#709125bc72f06800b68f9f4db485f2c7d31218a8"
|
||||
|
|
|
|||
Loading…
Reference in a new issue