Skip to content

Commit

Permalink
feat(react): add imagePreviewer property in theme and allow rendering…
Browse files Browse the repository at this point in the history
… custom components in webchat (#2030)

* feat(react): working image preview modal

* feat(react): botonic API to allow open/close modal with custom modal content

* chore(eact): add types for modals

* fix(react): correct styles for large images

* wip: zoom in/zoom out func

* wip: photoswipe working

* chore(react): remove share and full-screen

* wip

* wip(react): setting custom property for imagePreviewer

* feat(react): working previewer feature

* refactor(react): rename names to not be only bind to imagePreviewer and to be generic
  • Loading branch information
vanbasten17 authored Dec 22, 2021
1 parent d136a89 commit 967b2aa
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 4 deletions.
37 changes: 33 additions & 4 deletions packages/botonic-react/src/components/image.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { INPUT, isBrowser } from '@botonic/core'
import React from 'react'
import React, { useContext, useState } from 'react'
import styled from 'styled-components'

import { ROLES } from '../constants'
import { ROLES, WEBCHAT } from '../constants'
import { WebchatContext } from '../contexts'
import { Message } from './message'

const StyledImage = styled.img`
border-radius: 8px;
max-width: 150px;
max-height: 150px;
margin: 10px;
margin: -3px -6px;
cursor: ${({ hasPreviewer }) => (hasPreviewer ? 'pointer' : 'auto')};
`

const serialize = imageProps => {
Expand All @@ -18,7 +20,34 @@ const serialize = imageProps => {

export const Image = props => {
let content = props.children
if (isBrowser()) content = <StyledImage src={props.src} />

const [isPreviewerOpened, setIsPreviewerOpened] = useState(false)
const openPreviewer = () => setIsPreviewerOpened(true)
const closePreviewer = () => setIsPreviewerOpened(false)

const { getThemeProperty } = useContext(WebchatContext)
const imagePreviewer = getThemeProperty(
WEBCHAT.CUSTOM_PROPERTIES.imagePreviewer,
null
)
if (isBrowser()) {
content = (
<>
<StyledImage
src={props.src}
onClick={openPreviewer}
hasPreviewer={Boolean(imagePreviewer)}
/>
{imagePreviewer &&
imagePreviewer({
src: props.src,
isPreviewerOpened,
openPreviewer,
closePreviewer,
})}
</>
)
}
return (
<Message
role={ROLES.IMAGE_MESSAGE}
Expand Down
1 change: 1 addition & 0 deletions packages/botonic-react/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const WEBCHAT = {
enableAnimations: 'animations.enable',
markdownStyle: 'markdownStyle',
scrollbar: 'scrollbar',
imagePreviewer: 'imagePreviewer',
// Mobile
mobileBreakpoint: 'mobileBreakpoint',
mobileStyle: 'mobileStyle',
Expand Down
2 changes: 2 additions & 0 deletions packages/botonic-react/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ export class WebchatApp {
updateLastMessageDate(date: string): void
updateUser(user: core.SessionUser): void
updateWebchatSettings(settings: WebchatSettingsProps): void
renderCustomComponent(customComponent: React.ReactNode): void
unmountCustomComponent(): void
}

export interface WebchatContextProps {
Expand Down
8 changes: 8 additions & 0 deletions packages/botonic-react/src/webchat-app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,14 @@ export class WebchatApp {
this.webchatRef.current.closeCoverComponent()
}

renderCustomComponent(_customComponent) {
this.webchatRef.current.renderCustomComponent(_customComponent)
}

unmountCustomComponent() {
this.webchatRef.current.unmountCustomComponent()
}

toggleCoverComponent() {
this.webchatRef.current.toggleCoverComponent()
}
Expand Down
1 change: 1 addition & 0 deletions packages/botonic-react/src/webchat/actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const TOGGLE_WEBCHAT = 'toggleWebchat'
export const TOGGLE_EMOJI_PICKER = 'toggleEmojiPicker'
export const TOGGLE_PERSISTENT_MENU = 'togglePersistentMenu'
export const TOGGLE_COVER_COMPONENT = 'toggleCoverComponent'
export const DO_RENDER_CUSTOM_COMPONENT = 'doRenderCustomComponent'
export const SET_ERROR = 'setError'
export const CLEAR_MESSAGES = 'clearMessages'
export const UPDATE_LAST_MESSAGE_DATE = 'updateLastMessageDate'
Expand Down
8 changes: 8 additions & 0 deletions packages/botonic-react/src/webchat/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ADD_MESSAGE,
ADD_MESSAGE_COMPONENT,
CLEAR_MESSAGES,
DO_RENDER_CUSTOM_COMPONENT,
SET_CURRENT_ATTACHMENT,
SET_ERROR,
SET_ONLINE,
Expand Down Expand Up @@ -59,6 +60,7 @@ export const webchatInitialState = {
isEmojiPickerOpen: false,
isPersistentMenuOpen: false,
isCoverComponentOpen: false,
isCustomComponentRendered: false,
lastMessageUpdate: undefined,
currentAttachment: undefined,
jwt: null,
Expand Down Expand Up @@ -137,6 +139,11 @@ export function useWebchat() {
type: TOGGLE_COVER_COMPONENT,
payload: toggle,
})
const doRenderCustomComponent = toggle =>
webchatDispatch({
type: DO_RENDER_CUSTOM_COMPONENT,
payload: toggle,
})
const setError = error =>
webchatDispatch({
type: SET_ERROR,
Expand Down Expand Up @@ -192,6 +199,7 @@ export function useWebchat() {
toggleEmojiPicker,
togglePersistentMenu,
toggleCoverComponent,
doRenderCustomComponent,
setError,
setOnline,
clearMessages,
Expand Down
4 changes: 4 additions & 0 deletions packages/botonic-react/src/webchat/webchat-reducer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
DO_RENDER_CUSTOM_COMPONENT,
SET_CURRENT_ATTACHMENT,
SET_ERROR,
SET_ONLINE,
Expand All @@ -18,6 +19,7 @@ import {
} from './actions'
import { messagesReducer } from './messages-reducer'

// eslint-disable-next-line complexity
export function webchatReducer(state, action) {
switch (action.type) {
case UPDATE_WEBVIEW:
Expand All @@ -41,6 +43,8 @@ export function webchatReducer(state, action) {
return { ...state, isPersistentMenuOpen: action.payload }
case TOGGLE_COVER_COMPONENT:
return { ...state, isCoverComponentOpen: action.payload }
case DO_RENDER_CUSTOM_COMPONENT:
return { ...state, isCustomComponentRendered: action.payload }
case SET_ERROR:
return { ...state, error: action.payload || {} }
case SET_ONLINE:
Expand Down
17 changes: 17 additions & 0 deletions packages/botonic-react/src/webchat/webchat.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React, {
useEffect,
useImperativeHandle,
useRef,
useState,
} from 'react'
import Textarea from 'react-textarea-autosize'
import styled, { StyleSheetManager } from 'styled-components'
Expand Down Expand Up @@ -183,6 +184,7 @@ export const Webchat = forwardRef((props, ref) => {
toggleEmojiPicker,
togglePersistentMenu,
toggleCoverComponent,
doRenderCustomComponent,
setError,
setOnline,
clearMessages,
Expand All @@ -199,6 +201,7 @@ export const Webchat = forwardRef((props, ref) => {
const { initialSession, initialDevSettings, onStateChange } = props
const getThemeProperty = _getThemeProperty(theme)

const [customComponent, setCustomComponent] = useState(null)
const storage = props.storage === undefined ? localStorage : props.storage
const storageKey =
typeof props.storageKey === 'function'
Expand Down Expand Up @@ -559,6 +562,11 @@ export const Webchat = forwardRef((props, ref) => {
toggleWebchat: () => toggleWebchat(!webchatState.isWebchatOpen),
openCoverComponent: () => toggleCoverComponent(true),
closeCoverComponent: () => toggleCoverComponent(false),
renderCustomComponent: _customComponent => {
setCustomComponent(_customComponent)
doRenderCustomComponent(true)
},
unmountCustomComponent: () => doRenderCustomComponent(false),
toggleCoverComponent: () =>
toggleCoverComponent(!webchatState.isCoverComponentOpen),
openWebviewApi: component => openWebviewT(component),
Expand Down Expand Up @@ -886,6 +894,11 @@ export const Webchat = forwardRef((props, ref) => {
)
}

const _renderCustomComponent = () => {
if (!customComponent) return <></>
else return customComponent
}

const WebchatComponent = (
<WebchatContext.Provider
value={{
Expand Down Expand Up @@ -916,6 +929,7 @@ export const Webchat = forwardRef((props, ref) => {
{triggerButton()}
</div>
)}

{webchatState.isWebchatOpen && (
<StyledWebchat
// TODO: Distinguis between multiple instances of webchat, e.g. `${uniqueId}-botonic-webchat`
Expand Down Expand Up @@ -948,6 +962,9 @@ export const Webchat = forwardRef((props, ref) => {
{!webchatState.handoff && userInputArea()}
{webchatState.webview && webchatWebview()}
{webchatState.isCoverComponentOpen && coverComponent()}
{webchatState.isCustomComponentRendered &&
customComponent &&
_renderCustomComponent()}
</StyledWebchat>
)}
</WebchatContext.Provider>
Expand Down

0 comments on commit 967b2aa

Please sign in to comment.