Compare commits

..

5 Commits

8 changed files with 221 additions and 79 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "image-viewer", "name": "image-viewer",
"version": "0.1.2", "version": "0.1.3",
"private": false, "private": false,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",

View File

@ -1,5 +1,8 @@
import React, {createContext} from 'react' import React, {createContext} from 'react'
import OnePageView from './OnePageView' import OnePageView from './OnePageView'
import TwoPagesView from './TwoPagesView'
import BookComponentsController from './BookComponentsController'
import './index.css'
export const BookContext = createContext({}) export const BookContext = createContext({})
@ -9,16 +12,22 @@ export interface IBookPage {
thumbnail?: string thumbnail?: string
} }
export const bookViewModes = [ export const getBookViewModes = (currIndex = 0, srcLength = 0)=>[
{ {
text: 'One Page', text: 'One Page',
value: 'one page', value: 'one page',
icon: '' icon: '',
pagesToTurn: 1,
nextDisable: currIndex >= srcLength - 1,
prevDisable: currIndex === 0,
}, },
{ {
text: 'Two Pages', text: 'Two Pages',
value: 'two pages', value: 'two pages',
icon: '' icon: '',
pagesToTurn: 2,
nextDisable: currIndex >= srcLength - 2,
prevDisable: currIndex <= 1,
} }
] ]
@ -36,7 +45,12 @@ export interface IBookContext {
} }
class BookComponent extends React.Component<any>{ const getPagesCountToTurn = (mode: string)=>{
let pagesToTurn = getBookViewModes().find(item=> item.value === mode)?.pagesToTurn
return pagesToTurn || 0
}
class BookComponent extends React.Component<any, any>{
constructor(props: any){ constructor(props: any){
super(props) super(props)
this.state = { this.state = {
@ -45,13 +59,26 @@ class BookComponent extends React.Component<any>{
flagToReverse: false, flagToReverse: false,
mode: "one page", mode: "one page",
setBookContextState: (newState: IBookContext)=>{ setBookContextState: (newState: IBookContext)=>{
this.setState(oldState=> ({ this.setState((oldState: any)=> ({
...oldState, ...oldState,
...newState ...newState
})) }))
} },
changePage: this.changePage,
pagesToTurn: 1,
} }
} }
changePage = (goToPrevious: boolean, indexToGo?: number)=>{
const {mode, currIndex} = this.state
const pagesCountToTurn = indexToGo || getPagesCountToTurn(mode)
const prefixPageCount = goToPrevious? -1 : 1
this.setState({
currIndex: (pagesCountToTurn * prefixPageCount) + currIndex,
flagToReverse: goToPrevious
})
}
componentDidMount(){ componentDidMount(){
fetch('https://picsum.photos/v2/list') fetch('https://picsum.photos/v2/list')
@ -70,13 +97,39 @@ class BookComponent extends React.Component<any>{
}) })
}) })
} }
getControlsDisable = ()=>{
const {mode, currIndex, src} = this.state
const {nextDisable, prevDisable} = getBookViewModes(currIndex, src.length).find(item=> item.value === mode) || {
nextDisable: true,
prevDisable: true
}
return {
nextDisable,
prevDisable
}
}
render(){ render(){
const {mode} = this.state
return <BookContext.Provider value={{...this.state}}> return <BookContext.Provider value={{...this.state}}>
<BookContext.Consumer> <BookContext.Consumer>
{state=>( {state=>(
<> <>
<OnePageView {...state}/> {mode === "one page" && <OnePageView {...state}/>}
{mode === "two pages" && <TwoPagesView {...state}/>}
<BookComponentsController
onPrevClick={()=>{
this.changePage(true)
}}
onNextClick={()=>{
this.changePage(false)
}}
{...this.getControlsDisable()}
{...this.state}
/>
</> </>
)} )}
</BookContext.Consumer> </BookContext.Consumer>

View File

@ -1,45 +1,48 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import {Button, Popover} from 'antd' import {Button} from 'antd'
import ViewSwitcher from './ViewSwitcher' import ViewSwitcher from './ViewSwitcher'
import JumpControls from './JumpControls' import JumpControls from './JumpControls'
import {
CaretLeftOutlined,
CaretRightOutlined,
} from '@ant-design/icons';
const ControlsContanter = styled.div` const ControlsContanter = styled.div`
display: inline-flex; display: inline-flex;
justify-content: space-between; justify-content: space-between;
width: 500px; width: 100%;
` `
export default function BookCoponentsController(props: any) { export default function BookCoponentsController(props: any) {
const {onPrevClick, prevDisable, onNextClick, nextDisable, ...restProps} = props const {onPrevClick, prevDisable, onNextClick, nextDisable, ...restProps} = props
return ( return (
<ControlsContanter> <ControlsContanter>
<JumpControls {...restProps}/>
<Button <Button
onClick={()=>{ onClick={()=>{
onPrevClick() onPrevClick()
}} }}
disabled={prevDisable} disabled={prevDisable}
> style={{
Previous marginRight: 8
</Button> }}
icon={<CaretLeftOutlined />}
/>
<Button <Button
onClick={()=>{ onClick={()=>{
onNextClick() onNextClick()
}} }}
disabled={nextDisable} disabled={nextDisable}
style={{
marginRight: 8
}}
icon={<CaretRightOutlined />}
> >
Next
</Button> </Button>
<Popover
trigger="click"
content={<JumpControls {...restProps}/>}>
<Button>
Jump
</Button>
</Popover>
<ViewSwitcher {...restProps}/> <ViewSwitcher {...restProps}/>
</ControlsContanter> </ControlsContanter>

View File

@ -1,14 +1,23 @@
import React, {useState, useCallback, useEffect} from 'react' import React, {useState, useCallback, useEffect} from 'react'
import {Row, Col, Slider, InputNumber} from 'antd' import {Slider, InputNumber} from 'antd'
import styled from 'styled-components'
const Container = styled.div`
width: 100%;
display: inline-flex;
`
const JumpControls = (props: any)=>{ const JumpControls = (props: any)=>{
const {src, currIndex, setBookContextState} = props const {src, currIndex, setBookContextState, pagesToTurn} = props
const [inputValue, setInputValue] = useState(currIndex) const [inputValue, setInputValue] = useState(currIndex)
const [sliderValue, setSliderValue] = useState(currIndex)
const onChange = useCallback( const onChange = useCallback(
(value) => { (value) => {
if(value){ if(value){
const newCurrIndex = value -1 const newCurrIndex = value -1
if(newCurrIndex < src.length && newCurrIndex >=0){ if(newCurrIndex < src.length && newCurrIndex >=0){
setBookContextState({ setBookContextState({
currIndex: value - 1, currIndex: value - 1,
@ -17,31 +26,43 @@ const JumpControls = (props: any)=>{
} }
} }
}, },
[inputValue]) [inputValue, src.length, setBookContextState])
useEffect(()=>{ useEffect(()=>{
setInputValue(currIndex + 1) setInputValue(currIndex + 1)
}, [currIndex]) }, [currIndex])
return <Row> useEffect(() => {
<Col span={12}> setSliderValue(inputValue)
}, [inputValue])
const sliderRestProps = ()=>{
return sliderValue === inputValue? {}: {value: inputValue}
}
return <Container>
<Slider <Slider
style={{
flexGrow: 1
}}
min={1} min={1}
max={src.length} max={src.length}
onChange={onChange} onAfterChange={onChange}
value={typeof inputValue === 'number' ? inputValue : 0} step={pagesToTurn}
defaultValue={typeof inputValue === 'number' ? inputValue : 1}
{...sliderRestProps()}
/> />
</Col>
<Col span={4}>
<InputNumber <InputNumber
className={"jump-control"}
min={1} min={1}
max={src.length} max={src.length}
style={{ margin: '0 8px' }} style={{ margin: '0 8px' }}
value={inputValue} value={inputValue}
onChange={onChange} onChange={onChange}
/> />
</Col> </Container>
</Row>
} }
export default JumpControls export default JumpControls

View File

@ -1,7 +1,6 @@
import React, {useState, useCallback, useEffect} from 'react' import React from 'react'
import {Transition, animated} from 'react-spring/renderprops' import {Transition, animated} from 'react-spring/renderprops'
import styled, { } from 'styled-components' import styled, { } from 'styled-components'
import BookComponentsController from './BookComponentsController'
const ComponentContainer = styled.div` const ComponentContainer = styled.div`
display: flex; display: flex;
@ -24,10 +23,9 @@ const PageContainer = styled.div`
height: 100%; height: 100%;
} }
` `
const StyledImage = styled(animated.div)<{src:string, width:number | string, height: number| string}>`
const StyledImage = styled(animated.div)<{src:string}>` width: ${props=> props.width? props.width: '100%'};
width: 100%; height: ${props=> props.height? props.height: '100%'};
height: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -36,30 +34,13 @@ const StyledImage = styled(animated.div)<{src:string}>`
` `
const PageComponent = (props: any)=>{ const PageComponent = (props: any)=>{
const {src} = props const {src, width, height} = props
return <StyledImage src={src}/> return <StyledImage src={src} width={width} height={height}/>
} }
function BookComponent(props: any) { function BookComponent(props: any) {
const {src, currIndex, setBookContextState, flagToReverse} = props const {src, currIndex, flagToReverse} = props
const [prevDisable, setPrevDisable] = useState(true)
const [nextDisable, setNextDisable] = useState(false)
const changePage = useCallback((indexToGo, goToPrevious)=>{
const newCurrIndex = currIndex + indexToGo
setBookContextState({
currIndex: newCurrIndex,
flagToReverse: goToPrevious
})
}, [currIndex])
useEffect(() => {
setNextDisable(currIndex >= src.length - 1)
setPrevDisable(currIndex <= 0)
}, [currIndex, src])
return ( return (
<ComponentContainer> <ComponentContainer>
<PageContainer> <PageContainer>
@ -77,8 +58,6 @@ function BookComponent(props: any) {
return( return(
<animated.div style={{ <animated.div style={{
...style, ...style,
overflowX: 'hidden'
}}> }}>
{React.createElement(()=><PageComponent src={src[currIndex]? src[currIndex].src: null}/>)} {React.createElement(()=><PageComponent src={src[currIndex]? src[currIndex].src: null}/>)}
</animated.div> </animated.div>
@ -87,17 +66,8 @@ function BookComponent(props: any) {
} }
</Transition> </Transition>
</PageContainer> </PageContainer>
<BookComponentsController
prevDisable={prevDisable}
onPrevClick={()=> changePage(-1, true)}
nextDisable={nextDisable}
onNextClick={()=>changePage(1, false)}
{...props}
/>
</ComponentContainer> </ComponentContainer>
) )
} }
export default BookComponent export {ComponentContainer, PageContainer, PageComponent, BookComponent as default }

View File

@ -0,0 +1,91 @@
import React, {useState, useEffect} from 'react'
import {Transition, animated} from 'react-spring/renderprops'
import {ComponentContainer, PageComponent, PageContainer} from './OnePageView'
import { IBookContext } from './BookComponent'
export default function TwoPagesView(props: any) {
let {src, currIndex, flagToReverse} = props
const [pagesToshow, setPagesToShow] = useState<IBookContext[]>([])
useEffect(()=>{
const shouldShowPage = (pageIndex: number)=>{
return src.length > 0 && pageIndex <= src.length - 1
}
const newCurrIndex = currIndex % 2 === 0 ? currIndex: currIndex - 1
const shouldShowFirstPage = shouldShowPage(newCurrIndex)
if(shouldShowFirstPage){
const shouldShowSecondPage = shouldShowPage(newCurrIndex + 1)
setPagesToShow([
src[newCurrIndex],
shouldShowSecondPage? src[newCurrIndex + 1]: {
src: '',
alt: '',
thumbnail: '',
}
])
}
}, [currIndex, src])
return (
<ComponentContainer>
<PageContainer>
<Transition
reset
unique
items={pagesToshow}
keys={(item: any)=> item.src}
initial={{}}
from={{opacity: 1, transform: flagToReverse? "translate3d(-50%, 0, 0)": "translate3d(100%, 0, 0)"}}
enter={{ opacity: 1, transform: "translate3d(0%, 0, 0)" }}
leave={{ opacity: 0, transform: flagToReverse? "translate3d(100%, 0, 0)": "translate3d(-50%, 0, 0)"}}
>
{
currIndex=> style=>{
return(
<animated.div style={{
...style,
}}>
{React.createElement(()=>{
return <PageComponent width={"50%"} src={pagesToshow[0]? pagesToshow[0].src: null}/>
})}
</animated.div>
)
}
}
</Transition>
<Transition
reset
unique
items={pagesToshow}
keys={(item: any)=> item.src}
from={{opacity: 1, transform: flagToReverse? "translate3d(0%, 0, 0)": "translate3d(150%, 0, 0)"}}
enter={{ opacity: 1, transform: "translate3d(50%, 0, 0)" }}
leave={{ opacity: 0, transform: flagToReverse? "translate3d(150%, 0, 0)": "translate3d(0%, 0, 0)"}}
initial={{}}
>
{
currIndex=> style=>{
return(
<animated.div style={{
...style,
}}>
{React.createElement(()=>{
return <PageComponent width={"50%"} src={pagesToshow[1]? pagesToshow[1].src: null}/>
})}
</animated.div>
)
}
}
</Transition>
</PageContainer>
</ComponentContainer>
)
}
interface Book {
name: string
}

View File

@ -1,6 +1,6 @@
import React, {useCallback} from 'react' import React, {useCallback} from 'react'
import {Select} from 'antd' import {Select} from 'antd'
import {IBookContext, bookViewModes} from './BookComponent' import {IBookContext, getBookViewModes} from './BookComponent'
const {Option} = Select const {Option} = Select
@ -10,15 +10,16 @@ export default function ViewSwitcher(props: IBookContext) {
const viewModeChange = useCallback((mode) => { const viewModeChange = useCallback((mode) => {
setBookContextState({ setBookContextState({
mode mode,
pagesToTurn: getBookViewModes().find(item=> item.value === mode)?.pagesToTurn || 0
}) })
},[]) },[setBookContextState])
return ( return (
<Select value={mode} onSelect={viewModeChange}> <Select value={mode} onSelect={viewModeChange}>
{ {
bookViewModes.map((item: any)=>{ getBookViewModes().map((item: any)=>{
return <Option value={item.value}>{item.text}</Option> return <Option value={item.value} key={item.value}>{item.text}</Option>
}) })
} }
</Select> </Select>

3
src/components/index.css Normal file
View File

@ -0,0 +1,3 @@
.jump-control .ant-input-number-handler-wrap{
display: none;
}