@mirrormedia/lilith-draft-renderer
Version:
## Introduction
375 lines (327 loc) • 14.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SlideshowBlock = SlideshowBlock;
exports.SlideshowBlockV2 = SlideshowBlockV2;
var _react = _interopRequireWildcard(require("react"));
var _styledComponents = _interopRequireDefault(require("styled-components"));
var _sharedStyle = require("../shared-style");
var _reactImage = _interopRequireDefault(require("@readr-media/react-image"));
var _ampSlideshowBlock = _interopRequireDefault(require("./amp/amp-slideshow-block"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
const defaultImage = "https://unpkg.com/@mirrormedia/lilith-draft-renderer@1.4.0/lib/public/a3f3e41061aaaa6e12b4d1e5a07f280c.png";
const loadingImage = "https://unpkg.com/@mirrormedia/lilith-draft-renderer@1.4.0/lib/public/845924188760371aa28efbb3dea99d01.gif";
const Image = _styledComponents.default.img`
width: 100%;
`;
const SlideshowCount = _styledComponents.default.div`
position: absolute;
top: 50%;
left: 50%;
border-radius: 100%;
border: black 1px solid;
transform: translate(-50%, -50%);
background-color: white;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
aspect-ratio: 1;
min-height: 66px;
padding: 10px;
`;
const Figure = _styledComponents.default.figure`
position: relative;
margin-block: unset;
margin-inline: unset;
margin: 0 10px;
`;
const sliderWidth = '100%';
const slidesOffset = 2;
const Wrapper = _styledComponents.default.figure`
${_sharedStyle.defaultMarginTop}
${_sharedStyle.defaultMarginBottom}
`;
const SlideshowV2 = _styledComponents.default.figure`
touch-action: pan-y;
overflow: hidden;
position: relative;
width: ${sliderWidth};
z-index: 1;
`;
const SlidesBox = _styledComponents.default.div`
display: flex;
position: relative;
top: 0;
width: ${sliderWidth};
${({
isShifting
}) => isShifting ? 'transition: transform 0.3s ease-out' : null};
.readr-media-react-image {
object-position: center center;
background-color: transparent;
max-width: ${sliderWidth};
min-width: ${sliderWidth};
max-height: 58.75vw;
min-height: 58.75vw;
${({
theme
}) => theme.breakpoint.md} {
min-width: 100%;
min-height: 428px;
max-width: 100%;
max-height: 428px;
}
}
`;
const ClickButton = _styledComponents.default.button`
position: absolute;
top: 50%;
z-index: 1;
transform: translateY(-50%);
color: white;
height: 100%;
width: 40px;
&:focus {
border: none;
outline: none;
}
&::before {
content: '';
width: 16px;
height: 16px;
transform: rotate(45deg) translateY(-50%);
cursor: pointer;
display: block;
position: absolute;
filter: drop-shadow(0 0 1.5px #000);
}
`;
const ClickButtonPrev = (0, _styledComponents.default)(ClickButton)`
left: 0;
&::before {
border-left: 2px solid #fff;
border-bottom: 2px solid #fff;
left: 9px;
transform: rotate(45deg) translate(0, -50%);
}
`;
const ClickButtonNext = (0, _styledComponents.default)(ClickButton)`
right: 0;
&::before {
content: ' ';
border-right: 2px solid #fff;
border-top: 2px solid #fff;
left: unset;
transform: rotate(45deg) translate(-50%, 0);
right: 9px;
}
`;
const Desc = _styledComponents.default.figcaption`
font-size: 14px;
line-height: 1.8;
font-weight: 400;
color: rgba(0, 0, 0, 0.5);
margin-top: 20px;
min-height: 1.8rem;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
`; // support old version of slideshow without delay propertiy
function SlideshowBlock(entity) {
var _images$, _images$$resized;
const images = entity.getData();
return /*#__PURE__*/_react.default.createElement(Figure, null, /*#__PURE__*/_react.default.createElement(Image, {
src: images === null || images === void 0 ? void 0 : (_images$ = images[0]) === null || _images$ === void 0 ? void 0 : (_images$$resized = _images$.resized) === null || _images$$resized === void 0 ? void 0 : _images$$resized.original,
onError: e => {
var _images$2, _images$2$imageFile;
return e.currentTarget.src = images === null || images === void 0 ? void 0 : (_images$2 = images[0]) === null || _images$2 === void 0 ? void 0 : (_images$2$imageFile = _images$2.imageFile) === null || _images$2$imageFile === void 0 ? void 0 : _images$2$imageFile.url;
}
}), /*#__PURE__*/_react.default.createElement(SlideshowCount, null, "+", images.length));
} // 202206 latest version of slideshow, support delay property
/**
* Supports sliding with mouse drag, and button clicks for navigation.
*
* Inspired by [Works of Claudia Conceicao](https://codepen.io/cconceicao/pen/PBQawy),
* [twreporter slideshow component](https://github.com/twreporter/twreporter-npm-packages/blob/master/packages/react-article-components/src/components/body/slideshow/index.js)
*/
function SlideshowBlockV2(entity, contentLayout) {
var _images$indexOfCurren;
const slidesBoxRef = (0, _react.useRef)(null);
/** Current index of the displayed slide */
const [indexOfCurrentImage, setIndexOfCurrentImage] = (0, _react.useState)(0);
/** Whether allow slide shifting animation */
const [isShifting, setIsShifting] = (0, _react.useState)(false);
/* Distance of dragging, which will increase/decrease value when dragging, and reset to `0` when dragging complete */
const [dragDistance, setDragDistance] = (0, _react.useState)(0);
/** Position of slide box */
const slideBoxPosition = `calc(${sliderWidth} * ${slidesOffset + indexOfCurrentImage} * -1 + ${dragDistance}px)`;
/**
* TODO: add type in images
*/
const {
images
} = (0, _react.useMemo)(() => entity.getData(), [entity]);
const displayedImage = (0, _react.useMemo)(() => images.map(image => {
const {
resized,
resizedWebp
} = image;
return {
resized,
resizedWebp
};
}), images);
const slidesLength = images.length;
const descOfCurrentImage = images === null || images === void 0 ? void 0 : (_images$indexOfCurren = images[indexOfCurrentImage]) === null || _images$indexOfCurren === void 0 ? void 0 : _images$indexOfCurren.desc;
if (contentLayout === 'amp') {
return /*#__PURE__*/_react.default.createElement(_ampSlideshowBlock.default, {
entity: entity
});
}
/**
* Clone first and last slide.
* Assuming there are three images [ A, B, C ] for slideshow.
* After cloning, there is seven images [ B(clone), C(clone), A, B, C, A(clone), B(clone) ] for rendering.
* Users can see the previous or next image in the process of dragging.
* For example, if drag backward from first A (the third image in array), users can see C(clone) and B(clone) when dragging.
*
* The cloned element is only show in the process of dragging.
* For example, even if users drag backward from first A, and stop it at C(clone), the slide is showing C , not C(clone).
* We doing this effect by recalculating the position of slide box.
*
* Why did cloned element only show at the process of dragging, and not show when dragging is end? There is two purposes:
* 1. Show cloned element at the process of dragging, is let users can see last image even if drag backward from first image.
* 2. Now Show cloned element displayed after the dragging is because if we displayed the cloned element, next dragging process will not as expected.
* For example, if we display C(clone), the next time users drag, there will be no element to drag when dragging backwards.
*
* The amount of item need to clone is decided by variable `slidesOffset`
*/
const slidesWithClone = (0, _react.useMemo)(() => [...displayedImage.slice(-slidesOffset), ...displayedImage, ...(displayedImage === null || displayedImage === void 0 ? void 0 : displayedImage.slice(0, slidesOffset))], [displayedImage]);
const slidesJsx = (0, _react.useMemo)(() => slidesWithClone.map((item, index) => {
/**
* Why image with this index should load immediately?
* Assuming there are three images [ A, B, C ] for slideshow.
* After cloning, there is seven images [ B(clone), C(clone), A, B, C, A(clone), B(clone) ] for rendering.
* If user dragging from A to C(clone), after dragging, the function `handleTransitionEnd` will set state `setIndexOfCurrentImage`,
* and then display 'C'.
* However, before dragging, C is not loaded, and after `handleTransitionEnd` is triggered, C is on appear and start to lazy-load,
* and before C is loaded, there is no image show on the screen.
* At the point of user experience, the slideshow will flash and blink: Has Image C -> No Image -> Has Image C again.
* To avoid this problem to hurt user experience, we decide to immediately load image C, despite time of initial load will longer.
*
*/
const isNeedToLoadImmediately = index === slidesLength + slidesOffset - 1;
return /*#__PURE__*/_react.default.createElement(_reactImage.default, {
images: item.resized,
imagesWebP: item.resizedWebp,
key: index,
loadingImage: loadingImage,
defaultImage: defaultImage,
objectFit: 'contain',
priority: isNeedToLoadImmediately,
intersectionObserverOptions: {
root: null,
rootMargin: '0px',
threshold: 0
}
});
}), [slidesWithClone]);
const handleClickPrev = () => {
if (isShifting) {
return;
}
setIsShifting(true);
setIndexOfCurrentImage(pre => pre - 1);
};
const handleClickNext = () => {
if (isShifting) {
return;
}
setIsShifting(true);
setIndexOfCurrentImage(pre => pre + 1);
};
/**
* Check `indexOfCurrentImage` after transition and reset if needed.
* It is needed to reset if scrolling backward from the first image to the last image,
* or scrolling forward from the last image to the first image.
*/
const handleTransitionEnd = () => {
setIsShifting(false);
if (indexOfCurrentImage <= -1) {
setIndexOfCurrentImage(slidesLength - 1);
} else if (indexOfCurrentImage >= slidesLength) {
setIndexOfCurrentImage(0);
}
};
(0, _react.useEffect)(() => {
const slidesBox = slidesBoxRef === null || slidesBoxRef === void 0 ? void 0 : slidesBoxRef.current;
if (slidesBox) {
/** Threshold of slide change */
const threshold = 0.25;
let dragDistance = 0;
/** Position of pointer when start dragging */
let dragStartPositionX = 0;
/**
* Record the mouse position and slidesBox position when dragging starts,
* and register dragEnd and dragAction for pointer-related events
*/
const dragStart = e => {
e.preventDefault();
dragStartPositionX = e.pageX;
slidesBox.addEventListener('pointerup', dragEnd);
slidesBox.addEventListener('pointerout', dragEnd);
slidesBox.addEventListener('pointermove', dragAction);
};
/**
* Calculate the distance of dragging, and adjust moving distance of the slidesBox accordingly to achieve dragging effect.
* It will recalculate the value of current position of slidesBox, starting position of dragging,
* distance of dragging when slidesBox is dragging.
*/
const dragAction = e => {
dragDistance = e.pageX - dragStartPositionX;
setDragDistance(dragDistance);
};
/**
* Calculate the position of `slidesBox` to decider should show next of previous image.
*/
const dragEnd = () => {
setIsShifting(true);
if (dragDistance / slidesBox.offsetWidth < -threshold) {
//move forward to show next image
setIndexOfCurrentImage(pre => pre + 1);
} else if (dragDistance / slidesBox.offsetWidth > threshold) {
//move backward to show previous image
setIndexOfCurrentImage(pre => pre - 1);
} else {//do not move, show current image
} //reset drag distance
dragDistance = 0;
setDragDistance(0);
slidesBox.removeEventListener('pointerup', dragEnd);
slidesBox.removeEventListener('pointerout', dragEnd);
slidesBox.removeEventListener('pointermove', dragAction);
}; // Add listener of Drag events
slidesBox.addEventListener('pointerdown', dragStart);
return () => {
slidesBox.removeEventListener('pointerdown', dragStart);
};
}
}, []);
return /*#__PURE__*/_react.default.createElement(Wrapper, null, /*#__PURE__*/_react.default.createElement(SlideshowV2, null, /*#__PURE__*/_react.default.createElement(ClickButtonPrev, {
onClick: handleClickPrev
}), /*#__PURE__*/_react.default.createElement(ClickButtonNext, {
onClick: handleClickNext
}), /*#__PURE__*/_react.default.createElement(SlidesBox, {
style: {
transform: `translateX(${slideBoxPosition})`
},
ref: slidesBoxRef,
amount: slidesWithClone.length,
isShifting: isShifting,
index: indexOfCurrentImage,
onTransitionEnd: handleTransitionEnd
}, slidesJsx)), /*#__PURE__*/_react.default.createElement(Desc, null, descOfCurrentImage));
}