Skip to content

Commit

Permalink
feat(bubbles): ✨ Add image bubble
Browse files Browse the repository at this point in the history
  • Loading branch information
baptisteArno committed Jan 20, 2022
1 parent c43fd1d commit 2d17897
Show file tree
Hide file tree
Showing 33 changed files with 847 additions and 141 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ FACEBOOK_CLIENT_SECRET=
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# (Optional) Used for GIF search
NEXT_PUBLIC_GIPHY_API_KEY=
22 changes: 22 additions & 0 deletions apps/builder/assets/logos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,25 @@ export const GoogleAnalyticsLogo = (props: IconProps) => (
</defs>
</Icon>
)

export const GiphyLogo = (props: IconProps) => (
<Icon viewBox="0 0 163.79999999999998 35" {...props}>
<g fill="none" fillRule="evenodd">
<path d="M4 4h20v27H4z" fill="#000" />
<g fillRule="nonzero">
<path d="M0 3h4v29H0z" fill="#04ff8e" />
<path d="M24 11h4v21h-4z" fill="#8e2eff" />
<path d="M0 31h28v4H0z" fill="#00c5ff" />
<path d="M0 0h16v4H0z" fill="#fff152" />
<path d="M24 8V4h-4V0h-4v12h12V8" fill="#ff5b5b" />
<path d="M24 16v-4h4" fill="#551c99" />
</g>
<path d="M16 0v4h-4" fill="#999131" />
<path
d="M59.1 12c-2-1.9-4.4-2.4-6.2-2.4-4.4 0-7.3 2.6-7.3 8 0 3.5 1.8 7.8 7.3 7.8 1.4 0 3.7-.3 5.2-1.4v-3.5h-6.9v-6h13.3v12.1c-1.7 3.5-6.4 5.3-11.7 5.3-10.7 0-14.8-7.2-14.8-14.3S42.7 3.2 52.9 3.2c3.8 0 7.1.8 10.7 4.4zm9.1 19.2V4h7.6v27.2zm20.1-7.4v7.3h-7.7V4h13.2c7.3 0 10.9 4.6 10.9 9.9 0 5.6-3.6 9.9-10.9 9.9zm0-6.5h5.5c2.1 0 3.2-1.6 3.2-3.3 0-1.8-1.1-3.4-3.2-3.4h-5.5zM125 31.2V20.9h-9.8v10.3h-7.7V4h7.7v10.3h9.8V4h7.6v27.2zm24.2-17.9l5.9-9.3h8.7v.3l-10.8 16v10.8h-7.7V20.3L135 4.3V4h8.7z"
fill="#000"
fillRule="nonzero"
/>
</g>
</Icon>
)
11 changes: 3 additions & 8 deletions apps/builder/components/account/PersonalInfoForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { UploadIcon } from 'assets/icons'
import { UploadButton } from 'components/shared/buttons/UploadButton'
import { useUser } from 'contexts/UserContext'
import React, { ChangeEvent, useState } from 'react'
import { uploadFile } from 'services/utils'

export const PersonalInfoForm = () => {
const {
Expand All @@ -27,14 +26,10 @@ export const PersonalInfoForm = () => {
isOAuthProvider,
} = useUser()
const [reloadParam, setReloadParam] = useState('')
const [isUploading, setIsUploading] = useState(false)

const handleFileChange = async (file: File) => {
setIsUploading(true)
const { url } = await uploadFile(file, `${user?.id}/avatar`)
const handleFileUploaded = async (url: string) => {
setReloadParam(Date.now().toString())
updateUser({ image: url })
setIsUploading(false)
}

const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
Expand All @@ -60,9 +55,9 @@ export const PersonalInfoForm = () => {
<Stack>
<UploadButton
size="sm"
filePath={`users/${user?.id}/avatar`}
leftIcon={<UploadIcon />}
isLoading={isUploading}
onUploadChange={handleFileChange}
onFileUploaded={handleFileUploaded}
>
Change photo
</UploadButton>
Expand Down
3 changes: 3 additions & 0 deletions apps/builder/components/board/StepTypesList/StepIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
FilterIcon,
FlagIcon,
GlobeIcon,
ImageIcon,
NumberIcon,
PhoneIcon,
TextIcon,
Expand All @@ -29,6 +30,8 @@ export const StepIcon = ({ type, ...props }: StepIconProps) => {
switch (type) {
case BubbleStepType.TEXT:
return <ChatIcon {...props} />
case BubbleStepType.IMAGE:
return <ImageIcon {...props} />
case InputStepType.TEXT:
return <TextIcon {...props} />
case InputStepType.NUMBER:
Expand Down
2 changes: 2 additions & 0 deletions apps/builder/components/board/StepTypesList/StepTypeLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const StepTypeLabel = ({ type }: Props) => {
case InputStepType.TEXT: {
return <Text>Text</Text>
}
case BubbleStepType.IMAGE:
return <Text>Image</Text>
case InputStepType.NUMBER: {
return <Text>Number</Text>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export const StepTypesList = () => {
const x = e.clientX - rect.left
const y = e.clientY - rect.top
setRelativeCoordinates({ x, y })
console.log({ x: rect.left, y: rect.top })
setDraggedStepType(type)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
Portal,
PopoverContent,
PopoverArrow,
PopoverBody,
} from '@chakra-ui/react'
import { ImagePopoverContent } from 'components/shared/ImageUploadContent'
import { useTypebot } from 'contexts/TypebotContext'
import {
BubbleStep,
BubbleStepType,
ImageBubbleContent,
ImageBubbleStep,
TextBubbleStep,
} from 'models'
import { useRef } from 'react'

type Props = {
step: Exclude<BubbleStep, TextBubbleStep>
}

export const ContentPopover = ({ step }: Props) => {
const ref = useRef<HTMLDivElement | null>(null)
const handleMouseDown = (e: React.MouseEvent) => e.stopPropagation()

return (
<Portal>
<PopoverContent onMouseDown={handleMouseDown} w="500px">
<PopoverArrow />
<PopoverBody ref={ref} shadow="lg">
<StepContent step={step} />
</PopoverBody>
</PopoverContent>
</Portal>
)
}

export const StepContent = ({ step }: Props) => {
const { updateStep } = useTypebot()
const handleContentChange = (content: ImageBubbleContent) =>
updateStep(step.id, { content } as Partial<ImageBubbleStep>)

const handleNewImageSubmit = (url: string) => handleContentChange({ url })
switch (step.type) {
case BubbleStepType.IMAGE: {
return (
<ImagePopoverContent
url={step.content?.url}
onSubmit={handleNewImageSubmit}
/>
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ContentPopover } from './ContentPopover'
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
LogicStepType,
Step,
StepOptions,
TextBubbleStep,
} from 'models'
import { useRef } from 'react'
import {
Expand All @@ -33,7 +34,7 @@ import { RedirectSettings } from './bodies/RedirectSettings'
import { SetVariableSettingsBody } from './bodies/SetVariableSettingsBody'

type Props = {
step: Step
step: Exclude<Step, TextBubbleStep>
onExpandClick: () => void
}

Expand Down
24 changes: 13 additions & 11 deletions apps/builder/components/board/graph/BlockNode/StepNode/StepNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,10 @@ import {
useEventListener,
} from '@chakra-ui/react'
import React, { useEffect, useState } from 'react'
import { DraggableStep, Step } from 'models'
import { BubbleStep, DraggableStep, Step, TextBubbleStep } from 'models'
import { useGraph } from 'contexts/GraphContext'
import { StepIcon } from 'components/board/StepTypesList/StepIcon'
import {
isInputStep,
isLogicStep,
isTextBubbleStep,
isIntegrationStep,
} from 'utils'
import { isBubbleStep, isTextBubbleStep } from 'utils'
import { Coordinates } from '@dnd-kit/core/dist/types'
import { TextEditor } from './TextEditor/TextEditor'
import { StepNodeContent } from './StepNodeContent'
Expand All @@ -29,6 +24,7 @@ import { TargetEndpoint } from './TargetEndpoint'
import { useRouter } from 'next/router'
import { SettingsModal } from './SettingsPopoverContent/SettingsModal'
import { StepSettings } from './SettingsPopoverContent/SettingsPopoverContent'
import { ContentPopover } from './ContentPopover'

export const StepNode = ({
step,
Expand Down Expand Up @@ -185,15 +181,16 @@ export const StepNode = ({
}}
pos="absolute"
right="15px"
top="18px"
bottom="18px"
/>
)}
</HStack>
</Flex>
</PopoverTrigger>
{hasPopover(step) && (
{hasSettingsPopover(step) && (
<SettingsPopoverContent step={step} onExpandClick={onModalOpen} />
)}
{hasContentPopover(step) && <ContentPopover step={step} />}
<SettingsModal isOpen={isModalOpen} onClose={onModalClose}>
<StepSettings step={step} />
</SettingsModal>
Expand All @@ -203,5 +200,10 @@ export const StepNode = ({
)
}

const hasPopover = (step: Step) =>
isInputStep(step) || isLogicStep(step) || isIntegrationStep(step)
const hasSettingsPopover = (step: Step): step is Exclude<Step, BubbleStep> =>
!isBubbleStep(step)

const hasContentPopover = (
step: Step
): step is Exclude<BubbleStep, TextBubbleStep> =>
isBubbleStep(step) && !isTextBubbleStep(step)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Flex, HStack, Stack, Tag, Text } from '@chakra-ui/react'
import { Box, Flex, HStack, Image, Stack, Tag, Text } from '@chakra-ui/react'
import { useTypebot } from 'contexts/TypebotContext'
import {
Step,
Expand Down Expand Up @@ -34,6 +34,20 @@ export const StepNodeContent = ({ step }: Props) => {
/>
)
}
case BubbleStepType.IMAGE: {
return !step.content?.url ? (
<Text color={'gray.500'}>Click to edit...</Text>
) : (
<Box w="full">
<Image
src={step.content?.url}
alt="Step image"
rounded="md"
objectFit="cover"
/>
</Box>
)
}
case InputStepType.TEXT: {
return (
<Text color={'gray.500'}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useTypebot } from 'contexts/TypebotContext/TypebotContext'
import { BaseSelection, createEditor, Transforms } from 'slate'
import { ToolBar } from './ToolBar'
import { parseHtmlStringToPlainText } from 'services/utils'
import { TextStep, Variable } from 'models'
import { TextBubbleStep, Variable } from 'models'
import { VariableSearchInput } from 'components/shared/VariableSearchInput'
import { ReactEditor } from 'slate-react'

Expand Down Expand Up @@ -87,7 +87,7 @@ export const TextEditor = ({
richText: value,
plainText: parseHtmlStringToPlainText(html),
},
} as TextStep)
} as TextBubbleStep)
}

const handleMouseDown = (e: React.MouseEvent) => {
Expand Down
61 changes: 61 additions & 0 deletions apps/builder/components/shared/ImageUploadContent/GiphySearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Flex, Input, Stack } from '@chakra-ui/react'
import { GiphyFetch } from '@giphy/js-fetch-api'
import { Grid, SearchContext } from '@giphy/react-components'
import { GiphyLogo } from 'assets/logos'
import React, { useContext, useState, useEffect } from 'react'
import { useDebounce } from 'use-debounce'

type GiphySearchProps = {
onSubmit: (url: string) => void
}

const giphyFetch = new GiphyFetch(
process.env.NEXT_PUBLIC_GIPHY_API_KEY as string
)

export const GiphySearch = ({ onSubmit }: GiphySearchProps) => {
const { fetchGifs, searchKey, setSearch } = useContext(SearchContext)
const fetchGifsTrending = (offset: number) =>
giphyFetch.trending({ offset, limit: 10 })

const [inputValue, setInputValue] = useState('')
const [debouncedInputValue] = useDebounce(inputValue, 300)

useEffect(() => {
if (debouncedInputValue === '') return
setSearch(debouncedInputValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedInputValue])

const updateUrl = (url: string) => {
onSubmit(url)
}

return (
<Stack>
<Flex align="center">
<Input
flex="1"
autoFocus
placeholder="Search..."
onChange={(e) => setInputValue(e.target.value)}
value={inputValue}
/>
<GiphyLogo w="100px" />
</Flex>
<Flex overflowY="scroll" maxH="400px">
<Grid
onGifClick={(gif, e) => {
e.preventDefault()
updateUrl(gif.images.downsized.url)
}}
key={searchKey}
fetchGifs={searchKey === '' ? fetchGifsTrending : fetchGifs}
width={475}
columns={3}
className="my-4"
/>
</Flex>
</Stack>
)
}
Loading

0 comments on commit 2d17897

Please sign in to comment.