Compare commits
6 Commits
0204a379f7
...
pov
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64614fdd7c | ||
| e2f0f5c7e6 | |||
| fb1bd05161 | |||
| b1b15ee45d | |||
| 716ddba5ef | |||
| e4a862d142 |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "image-viewer",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "image-viewer",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React, {createContext} from 'react'
|
||||
import React, { createContext } from 'react'
|
||||
import OnePageView from './OnePageView'
|
||||
import TwoPagesView from './TwoPagesView.jsx'
|
||||
import BookComponentsController from './BookComponentsController'
|
||||
import './index.css'
|
||||
|
||||
export const BookContext = createContext({})
|
||||
|
||||
@@ -9,16 +12,22 @@ export interface IBookPage {
|
||||
thumbnail?: string
|
||||
}
|
||||
|
||||
export const bookViewModes = [
|
||||
export const getBookViewModes = (currIndex = 0, srcLength = 0) => [
|
||||
{
|
||||
text: 'One Page',
|
||||
value: 'one page',
|
||||
icon: ''
|
||||
icon: '',
|
||||
pagesToTurn: 1,
|
||||
nextDisable: currIndex >= srcLength - 1,
|
||||
prevDisable: currIndex === 0,
|
||||
},
|
||||
{
|
||||
text: 'Two Pages',
|
||||
value: 'two pages',
|
||||
icon: ''
|
||||
icon: '',
|
||||
pagesToTurn: 2,
|
||||
nextDisable: currIndex >= srcLength - 2,
|
||||
prevDisable: currIndex <= 1,
|
||||
}
|
||||
]
|
||||
|
||||
@@ -32,33 +41,51 @@ export interface IBookContext {
|
||||
src?: IBookPage[],
|
||||
flagToReverse?: boolean,
|
||||
mode?: BookViewMode,
|
||||
readonly setBookContextState: (newState: any)=> undefined
|
||||
readonly setBookContextState: (newState: any) => undefined
|
||||
}
|
||||
|
||||
|
||||
class BookComponent extends React.Component<any>{
|
||||
constructor(props: 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) {
|
||||
super(props)
|
||||
this.state = {
|
||||
currIndex: 0,
|
||||
src: [],
|
||||
flagToReverse: false,
|
||||
mode: "one page",
|
||||
setBookContextState: (newState: IBookContext)=>{
|
||||
this.setState(oldState=> ({
|
||||
setBookContextState: (newState: IBookContext) => {
|
||||
this.setState((oldState: any) => ({
|
||||
...oldState,
|
||||
...newState
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
changePage: this.changePage,
|
||||
pagesToTurn: 1,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
fetch('https://picsum.photos/v2/list')
|
||||
.then(response=>{
|
||||
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() {
|
||||
fetch('https://picsum.photos/v2/list?limit=30')
|
||||
.then(response => {
|
||||
return response.json()
|
||||
}).then(data=>{
|
||||
data = data.map((item: any)=>{
|
||||
}).then(data => {
|
||||
data = data.map((item: any) => {
|
||||
return {
|
||||
src: item.download_url,
|
||||
alt: item.author
|
||||
@@ -70,13 +97,39 @@ class BookComponent extends React.Component<any>{
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render(){
|
||||
return <BookContext.Provider value={{...this.state}}>
|
||||
|
||||
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() {
|
||||
const { mode } = this.state
|
||||
|
||||
return <BookContext.Provider value={{ ...this.state }}>
|
||||
<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>
|
||||
|
||||
@@ -1,45 +1,48 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {Button, Popover} from 'antd'
|
||||
import {Button} from 'antd'
|
||||
import ViewSwitcher from './ViewSwitcher'
|
||||
import JumpControls from './JumpControls'
|
||||
import {
|
||||
CaretLeftOutlined,
|
||||
CaretRightOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const ControlsContanter = styled.div`
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
width: 500px;
|
||||
width: 100%;
|
||||
`
|
||||
export default function BookCoponentsController(props: any) {
|
||||
const {onPrevClick, prevDisable, onNextClick, nextDisable, ...restProps} = props
|
||||
|
||||
return (
|
||||
<ControlsContanter>
|
||||
<JumpControls {...restProps}/>
|
||||
|
||||
<Button
|
||||
onClick={()=>{
|
||||
onPrevClick()
|
||||
}}
|
||||
disabled={prevDisable}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
style={{
|
||||
marginRight: 8
|
||||
}}
|
||||
icon={<CaretLeftOutlined />}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onClick={()=>{
|
||||
onNextClick()
|
||||
}}
|
||||
disabled={nextDisable}
|
||||
style={{
|
||||
marginRight: 8
|
||||
}}
|
||||
icon={<CaretRightOutlined />}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
|
||||
|
||||
<Popover
|
||||
trigger="click"
|
||||
content={<JumpControls {...restProps}/>}>
|
||||
<Button>
|
||||
Jump
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
|
||||
<ViewSwitcher {...restProps}/>
|
||||
|
||||
</ControlsContanter>
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
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 {src, currIndex, setBookContextState} = props
|
||||
const {src, currIndex, setBookContextState, pagesToTurn} = props
|
||||
const [inputValue, setInputValue] = useState(currIndex)
|
||||
|
||||
const [sliderValue, setSliderValue] = useState(currIndex)
|
||||
const onChange = useCallback(
|
||||
(value) => {
|
||||
|
||||
if(value){
|
||||
const newCurrIndex = value -1
|
||||
|
||||
if(newCurrIndex < src.length && newCurrIndex >=0){
|
||||
setBookContextState({
|
||||
currIndex: value - 1,
|
||||
@@ -17,31 +26,43 @@ const JumpControls = (props: any)=>{
|
||||
}
|
||||
}
|
||||
},
|
||||
[inputValue])
|
||||
[inputValue, src.length, setBookContextState])
|
||||
|
||||
useEffect(()=>{
|
||||
setInputValue(currIndex + 1)
|
||||
}, [currIndex])
|
||||
|
||||
return <Row>
|
||||
<Col span={12}>
|
||||
useEffect(() => {
|
||||
setSliderValue(inputValue)
|
||||
}, [inputValue])
|
||||
|
||||
|
||||
const sliderRestProps = ()=>{
|
||||
return sliderValue === inputValue? {}: {value: inputValue}
|
||||
}
|
||||
|
||||
return <Container>
|
||||
<Slider
|
||||
style={{
|
||||
flexGrow: 1
|
||||
}}
|
||||
min={1}
|
||||
max={src.length}
|
||||
onChange={onChange}
|
||||
value={typeof inputValue === 'number' ? inputValue : 0}
|
||||
onAfterChange={onChange}
|
||||
step={pagesToTurn}
|
||||
defaultValue={typeof inputValue === 'number' ? inputValue : 1}
|
||||
{...sliderRestProps()}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
|
||||
<InputNumber
|
||||
className={"jump-control"}
|
||||
min={1}
|
||||
max={src.length}
|
||||
style={{ margin: '0 8px' }}
|
||||
value={inputValue}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
}
|
||||
|
||||
export default JumpControls
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, {useState, useCallback, useEffect} from 'react'
|
||||
import React from 'react'
|
||||
import {Transition, animated} from 'react-spring/renderprops'
|
||||
import styled, { } from 'styled-components'
|
||||
import BookComponentsController from './BookComponentsController'
|
||||
|
||||
const ComponentContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -24,10 +23,9 @@ const PageContainer = styled.div`
|
||||
height: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledImage = styled(animated.div)<{src:string}>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
const StyledImage = styled(animated.div)<{src:string, width:number | string, height: number| string}>`
|
||||
width: ${props=> props.width? props.width: '100%'};
|
||||
height: ${props=> props.height? props.height: '100%'};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@@ -36,30 +34,13 @@ const StyledImage = styled(animated.div)<{src:string}>`
|
||||
`
|
||||
|
||||
const PageComponent = (props: any)=>{
|
||||
const {src} = props
|
||||
return <StyledImage src={src}/>
|
||||
const {src, width, height} = props
|
||||
return <StyledImage src={src} width={width} height={height}/>
|
||||
}
|
||||
|
||||
|
||||
function BookComponent(props: any) {
|
||||
const {src, currIndex, setBookContextState, 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])
|
||||
|
||||
const {src, currIndex, flagToReverse} = props
|
||||
return (
|
||||
<ComponentContainer>
|
||||
<PageContainer>
|
||||
@@ -77,8 +58,6 @@ function BookComponent(props: any) {
|
||||
return(
|
||||
<animated.div style={{
|
||||
...style,
|
||||
overflowX: 'hidden'
|
||||
|
||||
}}>
|
||||
{React.createElement(()=><PageComponent src={src[currIndex]? src[currIndex].src: null}/>)}
|
||||
</animated.div>
|
||||
@@ -87,17 +66,8 @@ function BookComponent(props: any) {
|
||||
}
|
||||
</Transition>
|
||||
</PageContainer>
|
||||
|
||||
<BookComponentsController
|
||||
prevDisable={prevDisable}
|
||||
onPrevClick={()=> changePage(-1, true)}
|
||||
nextDisable={nextDisable}
|
||||
onNextClick={()=>changePage(1, false)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
</ComponentContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default BookComponent
|
||||
export {ComponentContainer, PageContainer, PageComponent, BookComponent as default }
|
||||
91
src/components/TwoPagesView.jsx
Normal file
91
src/components/TwoPagesView.jsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTransition, animated } from 'react-spring';
|
||||
import {
|
||||
ComponentContainer,
|
||||
PageComponent,
|
||||
PageContainer,
|
||||
} from './OnePageView';
|
||||
|
||||
export default function TwoPagesView(props) {
|
||||
let { src, currIndex, flagToReverse } = props;
|
||||
const [pagesToshow, setPagesToShow] = useState([
|
||||
{
|
||||
src: '',
|
||||
alt: '',
|
||||
thumbnail: '',
|
||||
},
|
||||
{
|
||||
src: '',
|
||||
alt: '',
|
||||
thumbnail: '',
|
||||
},
|
||||
]);
|
||||
|
||||
const transitionFirst = useTransition(pagesToshow[0], (item) => item.src, {
|
||||
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)"},
|
||||
});
|
||||
|
||||
const transitionSecond = useTransition(pagesToshow[1], (item) => 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)"},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const shouldShowPage = (pageIndex) => {
|
||||
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>
|
||||
{transitionFirst.map(({ item, key, props }) => {
|
||||
return (
|
||||
item && (
|
||||
<animated.div style={{ ...props }} key={key}>
|
||||
<PageComponent width={'50%'} src={item.src} />
|
||||
</animated.div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
|
||||
{transitionSecond.map(({ item, key, props }) => {
|
||||
return (
|
||||
item && (
|
||||
<animated.div
|
||||
style={{
|
||||
...props,
|
||||
transformOrigin: 'left center',
|
||||
}}
|
||||
key={key}
|
||||
>
|
||||
<PageComponent width={'50%'} src={item.src} />
|
||||
</animated.div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</PageContainer>
|
||||
</ComponentContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {Select} from 'antd'
|
||||
import {IBookContext, bookViewModes} from './BookComponent'
|
||||
import {IBookContext, getBookViewModes} from './BookComponent'
|
||||
|
||||
|
||||
const {Option} = Select
|
||||
@@ -10,15 +10,16 @@ export default function ViewSwitcher(props: IBookContext) {
|
||||
|
||||
const viewModeChange = useCallback((mode) => {
|
||||
setBookContextState({
|
||||
mode
|
||||
mode,
|
||||
pagesToTurn: getBookViewModes().find(item=> item.value === mode)?.pagesToTurn || 0
|
||||
})
|
||||
},[])
|
||||
},[setBookContextState])
|
||||
|
||||
return (
|
||||
<Select value={mode} onSelect={viewModeChange}>
|
||||
{
|
||||
bookViewModes.map((item: any)=>{
|
||||
return <Option value={item.value}>{item.text}</Option>
|
||||
getBookViewModes().map((item: any)=>{
|
||||
return <Option value={item.value} key={item.value}>{item.text}</Option>
|
||||
})
|
||||
}
|
||||
</Select>
|
||||
|
||||
3
src/components/index.css
Normal file
3
src/components/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.jump-control .ant-input-number-handler-wrap{
|
||||
display: none;
|
||||
}
|
||||
Reference in New Issue
Block a user