mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-24 20:36:40 +07:00
Merge branch 'feature/9-tiny-slider' into develop
This commit is contained in:
commit
0362854543
13 changed files with 238 additions and 25 deletions
|
@ -110,6 +110,8 @@
|
|||
"scrypt": "^6.0.3",
|
||||
"sticky-sidebar": "^3.3.1",
|
||||
"throttle-debounce": "^2.1.0",
|
||||
"tiny-slider-react": "^0.5.3",
|
||||
"tiny-slider": "2.9.2",
|
||||
"tinycolor": "^0.0.1",
|
||||
"tslint": "^5.20.0",
|
||||
"tslint-config-airbnb": "^5.11.2",
|
||||
|
|
52
src/components/containers/FullWidth/index.tsx
Normal file
52
src/components/containers/FullWidth/index.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import ResizeSensor from 'resize-sensor';
|
||||
|
||||
interface IProps {
|
||||
onRefresh?: (width: number) => void;
|
||||
}
|
||||
|
||||
const FullWidth: FC<IProps> = ({ children, onRefresh }) => {
|
||||
const sample = useRef<HTMLDivElement>(null);
|
||||
const [clientWidth, setClientWidth] = useState(document.documentElement.clientWidth);
|
||||
|
||||
const style = useMemo(() => {
|
||||
if (!sample.current) return { display: 'none' };
|
||||
|
||||
const { width } = sample.current.getBoundingClientRect();
|
||||
const { clientWidth } = document.documentElement;
|
||||
|
||||
onRefresh(clientWidth);
|
||||
|
||||
return {
|
||||
width: clientWidth,
|
||||
transform: `translate(-${(clientWidth - width) / 2}px, 0)`,
|
||||
};
|
||||
}, [sample.current, clientWidth, onRefresh]);
|
||||
|
||||
const onResize = useCallback(() => setClientWidth(document.documentElement.clientWidth), []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sample.current) return;
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
new ResizeSensor(document.body, onResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
ResizeSensor.detach(document.body, onResize);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.slider} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<div className={styles.sample} ref={sample} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { FullWidth };
|
10
src/components/containers/FullWidth/styles.module.scss
Normal file
10
src/components/containers/FullWidth/styles.module.scss
Normal file
|
@ -0,0 +1,10 @@
|
|||
.sample {
|
||||
width: 100%;
|
||||
display: block;
|
||||
background: green;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
display: block;
|
||||
}
|
|
@ -8,6 +8,7 @@ import { PRESETS } from '~/constants/urls';
|
|||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||
import { throttle } from 'throttle-debounce';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { useArrows } from '~/utils/hooks/keys';
|
||||
|
||||
interface IProps extends INodeComponentProps {}
|
||||
|
||||
|
@ -239,29 +240,7 @@ const NodeImageSlideBlock: FC<IProps> = ({
|
|||
images,
|
||||
]);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
event => {
|
||||
if (
|
||||
(event.target.tagName && ['TEXTAREA', 'INPUT'].includes(event.target.tagName)) ||
|
||||
is_modal_shown
|
||||
)
|
||||
return;
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
return onPrev();
|
||||
case 'ArrowRight':
|
||||
return onNext();
|
||||
}
|
||||
},
|
||||
[onNext, onPrev, is_modal_shown]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, [onKeyDown]);
|
||||
useArrows(onNext, onPrev, is_modal_shown);
|
||||
|
||||
useEffect(() => {
|
||||
setOffset(0);
|
||||
|
|
93
src/components/node/NodeImageTinySlider/index.tsx
Normal file
93
src/components/node/NodeImageTinySlider/index.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
import React, { FC, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { INodeComponentProps } from '~/redux/node/constants';
|
||||
import { FullWidth } from '~/components/containers/FullWidth';
|
||||
import { useNodeImages } from '~/utils/node';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { PRESETS } from '~/constants/urls';
|
||||
import TinySlider from 'tiny-slider-react';
|
||||
import styles from './styles.module.scss';
|
||||
import { TinySliderInstance, TinySliderSettings } from 'tiny-slider';
|
||||
import { useArrows } from '~/utils/hooks/keys';
|
||||
|
||||
const settings: TinySliderSettings & { center: boolean } = {
|
||||
nav: false,
|
||||
mouseDrag: true,
|
||||
gutter: 10,
|
||||
center: true,
|
||||
lazyload: true,
|
||||
items: 1,
|
||||
edgePadding: 150,
|
||||
loop: false,
|
||||
arrowKeys: false,
|
||||
// prevButton: false,
|
||||
// nextButton: false,
|
||||
autoHeight: true,
|
||||
swipeAngle: 45,
|
||||
responsive: {
|
||||
0: {
|
||||
edgePadding: 10,
|
||||
gutter: 40,
|
||||
},
|
||||
768: {
|
||||
edgePadding: 50,
|
||||
},
|
||||
1024: {
|
||||
edgePadding: 150,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const NodeImageTinySlider: FC<INodeComponentProps> = ({ node }) => {
|
||||
const ref = useRef(null);
|
||||
const slides = useRef<HTMLDivElement[]>([]);
|
||||
const images = useNodeImages(node);
|
||||
const [current, setCurrent] = useState(0);
|
||||
const [height, setHeight] = useState(images[0]?.metadata?.height || 0);
|
||||
|
||||
const onResize = useCallback(() => {
|
||||
if (!ref.current) return;
|
||||
ref.current.slider.refresh();
|
||||
const el = slides.current[current];
|
||||
if (!el) return;
|
||||
const { height } = el.getBoundingClientRect();
|
||||
setHeight(height);
|
||||
}, [ref.current, slides.current, current]);
|
||||
|
||||
const onIndexChanged = useCallback(({ index }) => {
|
||||
setCurrent(index || 0);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrent(0);
|
||||
}, [node.id]);
|
||||
|
||||
useEffect(onResize, [slides, current]);
|
||||
|
||||
const onNext = useCallback(() => {
|
||||
if (!ref.current || images.length <= 1 || current === images.length - 1) return;
|
||||
ref.current.slider.goTo(current + 1);
|
||||
}, [ref.current, current, images]);
|
||||
|
||||
const onPrev = useCallback(() => {
|
||||
if (!ref.current || images.length <= 1 || current === 0) return;
|
||||
ref.current.slider.goTo(current - 1);
|
||||
}, [ref.current, current, images]);
|
||||
|
||||
useArrows(onNext, onPrev, false);
|
||||
|
||||
return (
|
||||
<FullWidth onRefresh={onResize}>
|
||||
<div className={styles.slider} style={{ height }}>
|
||||
<TinySlider settings={settings} ref={ref} onTransitionEnd={onIndexChanged}>
|
||||
{images.map((image, i) => (
|
||||
<div className={styles.slide} key={image.id} ref={el => (slides.current[i] = el)}>
|
||||
<img src={getURL(image, PRESETS['1600'])} key={image.url} onLoad={onResize} />
|
||||
</div>
|
||||
))}
|
||||
</TinySlider>
|
||||
</div>
|
||||
</FullWidth>
|
||||
);
|
||||
};
|
||||
|
||||
export { NodeImageTinySlider };
|
22
src/components/node/NodeImageTinySlider/styles.module.scss
Normal file
22
src/components/node/NodeImageTinySlider/styles.module.scss
Normal file
|
@ -0,0 +1,22 @@
|
|||
.slider {
|
||||
padding-bottom: 15px;
|
||||
overflow: hidden;
|
||||
transition: height 0.25s;
|
||||
|
||||
:global(.tns-controls) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.slide {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
max-height: calc(100vh - 140px);
|
||||
max-width: 100%;
|
||||
border-radius: $radius;
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ const Component: FC<IProps> = ({ modal: { is_shown } }) => {
|
|||
<div>
|
||||
<BlurWrapper is_blurred={is_shown}>
|
||||
<PageCover />
|
||||
|
||||
<MainLayout>
|
||||
<Modal />
|
||||
<Sprites />
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Filler } from '~/components/containers/Filler';
|
|||
import { modalShowPhotoswipe } from '../modal/actions';
|
||||
import { IEditorComponentProps } from '~/redux/node/types';
|
||||
import { EditorFiller } from '~/components/editors/EditorFiller';
|
||||
import { NodeImageTinySlider } from '~/components/node/NodeImageTinySlider';
|
||||
|
||||
const prefix = 'NODE.';
|
||||
export const NODE_ACTIONS = {
|
||||
|
@ -90,7 +91,8 @@ export type INodeComponentProps = {
|
|||
export type INodeComponents = Record<ValueOf<typeof NODE_TYPES>, FC<INodeComponentProps>>;
|
||||
|
||||
export const NODE_HEADS: INodeComponents = {
|
||||
[NODE_TYPES.IMAGE]: NodeImageSlideBlock,
|
||||
// [NODE_TYPES.IMAGE]: NodeImageSlideBlock,
|
||||
[NODE_TYPES.IMAGE]: NodeImageTinySlider,
|
||||
};
|
||||
|
||||
export const NODE_COMPONENTS: INodeComponents = {
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
html {
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
23
src/utils/hooks/keys.ts
Normal file
23
src/utils/hooks/keys.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export const useArrows = (onNext: () => void, onPrev: () => void, locked) => {
|
||||
const onKeyDown = useCallback(
|
||||
event => {
|
||||
if ((event.target.tagName && ['TEXTAREA', 'INPUT'].includes(event.target.tagName)) || locked)
|
||||
return;
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowLeft':
|
||||
return onPrev();
|
||||
case 'ArrowRight':
|
||||
return onNext();
|
||||
}
|
||||
},
|
||||
[onNext, onPrev, locked]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, [onKeyDown]);
|
||||
};
|
|
@ -1,8 +1,10 @@
|
|||
import { USER_ROLES } from '~/redux/auth/constants';
|
||||
import { INode, IComment, ICommentGroup } from '~/redux/types';
|
||||
import { INode, IComment, ICommentGroup, IFile } from '~/redux/types';
|
||||
import { IUser } from '~/redux/auth/types';
|
||||
import path from 'ramda/es/path';
|
||||
import { NODE_TYPES } from '~/redux/node/constants';
|
||||
import { useMemo } from 'react';
|
||||
import { UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||
|
||||
export const canEditNode = (node: Partial<INode>, user: Partial<IUser>): boolean =>
|
||||
path(['role'], user) === USER_ROLES.ADMIN ||
|
||||
|
@ -19,3 +21,11 @@ export const canStarNode = (node: Partial<INode>, user: Partial<IUser>): boolean
|
|||
node.type === NODE_TYPES.IMAGE &&
|
||||
path(['role'], user) &&
|
||||
path(['role'], user) === USER_ROLES.ADMIN;
|
||||
|
||||
export const useNodeImages = (node: INode): IFile[] => {
|
||||
return useMemo(
|
||||
() =>
|
||||
(node && node.files && node.files.filter(({ type }) => type === UPLOAD_TYPES.IMAGE)) || [],
|
||||
[node.files]
|
||||
);
|
||||
};
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -9564,6 +9564,23 @@ tiny-invariant@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73"
|
||||
integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==
|
||||
|
||||
tiny-slider-react@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-slider-react/-/tiny-slider-react-0.5.3.tgz#0e96f6b9a6cdafaac7b1bc29cfc9cb9a356760f5"
|
||||
integrity sha512-miTPlaWgwfg2U7WBDxdR40LFhAncIS2fF03tuNE5nqVIF5tuvjVFHGz1V0LSJWoNeOtXgoWs94JB2/hdxrCWqA==
|
||||
dependencies:
|
||||
tiny-slider "^2.9.2"
|
||||
|
||||
tiny-slider@2.9.2:
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/tiny-slider/-/tiny-slider-2.9.2.tgz#dcd70ac79054a4d170bc2cfde3efbdaa2cc0c75f"
|
||||
integrity sha512-2sgEJpVbpIbbgiYM/xGa0HMvvtUZSJvXeZJmLWBux6VgFqh/MQG8LXBR59ZLYpa/1OtwM0E6/ic55oLOJN9Mnw==
|
||||
|
||||
tiny-slider@^2.9.2:
|
||||
version "2.9.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-slider/-/tiny-slider-2.9.3.tgz#94d8158f704f3192fef1634c0ae6779fb14ea04e"
|
||||
integrity sha512-KZY45m+t3fb3Kwlqsic0PIos1lgTNXBEC5N/AhI3aNEcryrd0nXohZMbVPMkcNYdbLjY1IUJAXWYAO6/RGJnKw==
|
||||
|
||||
tiny-warning@^1.0.0, tiny-warning@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue