feature/IO-3499-React-19: Move react-grid-gallery to a vendor directory internally, will be removed shortly but for now we keep it
This commit is contained in:
@@ -38,7 +38,7 @@ class ErrorBoundary extends React.Component {
|
||||
}
|
||||
|
||||
handleErrorSubmit = () => {
|
||||
window.$crisp.push([
|
||||
window.$crisp?.push([
|
||||
"do",
|
||||
"message:send",
|
||||
[
|
||||
@@ -53,7 +53,7 @@ class ErrorBoundary extends React.Component {
|
||||
]
|
||||
]);
|
||||
|
||||
window.$crisp.push(["do", "chat:open"]);
|
||||
window.$crisp?.push(["do", "chat:open"]);
|
||||
|
||||
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
|
||||
|
||||
@@ -78,7 +78,7 @@ class ErrorBoundary extends React.Component {
|
||||
if (this.state.hasErrored === true) {
|
||||
logImEXEvent("error_boundary_rendered", { error, info });
|
||||
|
||||
window.$crisp.push([
|
||||
window.$crisp?.push([
|
||||
"set",
|
||||
"session:event",
|
||||
[
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Col, Row, Space } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Gallery } from "react-grid-gallery";
|
||||
import { Gallery } from "../../vendor/react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Lightbox from "react-image-lightbox";
|
||||
import "react-image-lightbox/style.css";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { Gallery } from "react-grid-gallery";
|
||||
import { Gallery } from "../../vendor/react-grid-gallery";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export function CsiContainerPage({ currentUser }) {
|
||||
const getAxiosData = useCallback(async () => {
|
||||
try {
|
||||
try {
|
||||
window.$crisp.push(["do", "chat:hide"]);
|
||||
window.$crisp?.push(["do", "chat:hide"]);
|
||||
} catch {
|
||||
console.log("Unable to attach to crisp instance. ");
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ export function* signInSuccessSaga({ payload }) {
|
||||
LogRocket.identify(payload.email);
|
||||
|
||||
try {
|
||||
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
||||
window.$crisp?.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
||||
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
@@ -269,9 +269,9 @@ export function* signInSuccessSaga({ payload }) {
|
||||
]
|
||||
: [])
|
||||
];
|
||||
window.$crisp.push(["set", "session:segments", [segs]]);
|
||||
window.$crisp?.push(["set", "session:segments", [segs]]);
|
||||
if (isParts) {
|
||||
window.$crisp.push(["do", "chat:hide"]);
|
||||
window.$crisp?.push(["do", "chat:hide"]);
|
||||
}
|
||||
} catch {
|
||||
// no-op
|
||||
@@ -359,9 +359,9 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
|
||||
try {
|
||||
//amplitude.setGroup('Shop', payload.shopname);
|
||||
window.$crisp.push(["set", "user:company", [payload.shopname]]);
|
||||
window.$crisp?.push(["set", "user:company", [payload.shopname]]);
|
||||
if (authRecord[0] && authRecord[0].user.validemail) {
|
||||
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
window.$crisp?.push(["set", "user:email", [authRecord[0].user.email]]);
|
||||
}
|
||||
|
||||
// Build consolidated Crisp segments including instance, region, features, and parts mode
|
||||
@@ -402,10 +402,10 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
segments.push(InstanceRenderManager({ imex: "ImexPartsManagement", rome: "RomePartsManagement" }));
|
||||
}
|
||||
|
||||
window.$crisp.push(["set", "session:segments", [segments]]);
|
||||
window.$crisp?.push(["set", "session:segments", [segments]]);
|
||||
|
||||
// Hide/show Crisp chat based on parts mode or features
|
||||
window.$crisp.push(["do", isParts ? "chat:hide" : "chat:show"]);
|
||||
window.$crisp?.push(["do", isParts ? "chat:hide" : "chat:show"]);
|
||||
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
|
||||
62
client/src/vendor/react-grid-gallery/CheckButton.jsx
vendored
Normal file
62
client/src/vendor/react-grid-gallery/CheckButton.jsx
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
import { useState } from "react";
|
||||
import * as styles from "./styles";
|
||||
|
||||
export const CheckButton = ({
|
||||
isSelected = false,
|
||||
isVisible = true,
|
||||
onClick,
|
||||
color = "#FFFFFFB2",
|
||||
selectedColor = "#4285F4FF",
|
||||
hoverColor = "#FFFFFFFF",
|
||||
}) => {
|
||||
const [hover, setHover] = useState(false);
|
||||
|
||||
const circleStyle = { display: isSelected ? "block" : "none" };
|
||||
const fillColor = isSelected ? selectedColor : hover ? hoverColor : color;
|
||||
|
||||
const handleMouseOver = () => setHover(true);
|
||||
const handleMouseOut = () => setHover(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="grid-gallery-item_check-button"
|
||||
title="Select"
|
||||
style={styles.checkButton({ isVisible })}
|
||||
onClick={onClick}
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
>
|
||||
<svg
|
||||
fill={fillColor}
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<radialGradient
|
||||
id="shadow"
|
||||
cx="38"
|
||||
cy="95.488"
|
||||
r="10.488"
|
||||
gradientTransform="matrix(1 0 0 -1 -26 109)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset=".832" stopColor="#010101"></stop>
|
||||
<stop offset="1" stopColor="#010101" stopOpacity="0"></stop>
|
||||
</radialGradient>
|
||||
|
||||
<circle
|
||||
style={circleStyle}
|
||||
opacity=".26"
|
||||
fill="url(#shadow)"
|
||||
cx="12"
|
||||
cy="13.512"
|
||||
r="10.488"
|
||||
/>
|
||||
<circle style={circleStyle} fill="#FFF" cx="12" cy="12.2" r="8.292" />
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
63
client/src/vendor/react-grid-gallery/Gallery.jsx
vendored
Normal file
63
client/src/vendor/react-grid-gallery/Gallery.jsx
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import { Image } from "./Image";
|
||||
import { useContainerWidth } from "./useContainerWidth";
|
||||
import { buildLayoutFlat } from "./buildLayout";
|
||||
import * as styles from "./styles";
|
||||
|
||||
export const Gallery = ({
|
||||
images,
|
||||
id = "ReactGridGallery",
|
||||
enableImageSelection = true,
|
||||
onSelect = () => {},
|
||||
rowHeight = 180,
|
||||
maxRows,
|
||||
margin = 2,
|
||||
defaultContainerWidth = 0,
|
||||
onClick = () => {},
|
||||
tileViewportStyle,
|
||||
thumbnailStyle,
|
||||
tagStyle,
|
||||
thumbnailImageComponent
|
||||
}) => {
|
||||
const { containerRef, containerWidth } = useContainerWidth(defaultContainerWidth);
|
||||
|
||||
const thumbnails = buildLayoutFlat(images, {
|
||||
containerWidth,
|
||||
maxRows,
|
||||
rowHeight,
|
||||
margin
|
||||
});
|
||||
|
||||
const handleSelect = (index, event) => {
|
||||
event.preventDefault();
|
||||
onSelect(index, images[index], event);
|
||||
};
|
||||
|
||||
const handleClick = (index, event) => {
|
||||
onClick(index, images[index], event);
|
||||
};
|
||||
|
||||
return (
|
||||
<div id={id} className="ReactGridGallery" ref={containerRef}>
|
||||
<div style={styles.gallery}>
|
||||
{thumbnails.map((item, index) => (
|
||||
<Image
|
||||
key={item.key || index}
|
||||
item={item}
|
||||
index={index}
|
||||
margin={margin}
|
||||
height={rowHeight}
|
||||
isSelectable={enableImageSelection}
|
||||
onClick={handleClick}
|
||||
onSelect={handleSelect}
|
||||
tagStyle={tagStyle}
|
||||
tileViewportStyle={tileViewportStyle}
|
||||
thumbnailStyle={thumbnailStyle}
|
||||
thumbnailImageComponent={thumbnailImageComponent}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Gallery.displayName = "Gallery";
|
||||
133
client/src/vendor/react-grid-gallery/Image.jsx
vendored
Normal file
133
client/src/vendor/react-grid-gallery/Image.jsx
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
import { useState } from "react";
|
||||
import { CheckButton } from "./CheckButton";
|
||||
import * as styles from "./styles";
|
||||
import { getStyle } from "./styles";
|
||||
|
||||
export const Image = ({
|
||||
item,
|
||||
thumbnailImageComponent: ThumbnailImageComponent,
|
||||
isSelectable = true,
|
||||
thumbnailStyle,
|
||||
tagStyle,
|
||||
tileViewportStyle,
|
||||
margin,
|
||||
index,
|
||||
onSelect,
|
||||
onClick,
|
||||
}) => {
|
||||
const styleContext = { item };
|
||||
|
||||
const [hover, setHover] = useState(false);
|
||||
|
||||
const thumbnailProps = {
|
||||
key: index,
|
||||
"data-testid": "grid-gallery-item_thumbnail",
|
||||
src: item.src,
|
||||
alt: item.alt ? item.alt : "",
|
||||
title: typeof item.caption === "string" ? item.caption : null,
|
||||
style: getStyle(thumbnailStyle, styles.thumbnail, styleContext),
|
||||
};
|
||||
|
||||
const handleCheckButtonClick = (event) => {
|
||||
if (!isSelectable) {
|
||||
return;
|
||||
}
|
||||
onSelect(index, event);
|
||||
};
|
||||
|
||||
const handleViewportClick = (event) => {
|
||||
onClick(index, event);
|
||||
};
|
||||
|
||||
const thumbnailImageProps = {
|
||||
item,
|
||||
index,
|
||||
margin,
|
||||
onSelect,
|
||||
onClick,
|
||||
isSelectable,
|
||||
tileViewportStyle,
|
||||
thumbnailStyle,
|
||||
tagStyle,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="ReactGridGallery_tile"
|
||||
data-testid="grid-gallery-item"
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
style={styles.galleryItem({ margin })}
|
||||
>
|
||||
<div
|
||||
className="ReactGridGallery_tile-icon-bar"
|
||||
style={styles.tileIconBar}
|
||||
>
|
||||
<CheckButton
|
||||
isSelected={item.isSelected}
|
||||
isVisible={item.isSelected || (isSelectable && hover)}
|
||||
onClick={handleCheckButtonClick}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!!item.tags && (
|
||||
<div
|
||||
className="ReactGridGallery_tile-bottom-bar"
|
||||
style={styles.bottomBar}
|
||||
>
|
||||
{item.tags.map((tag, index) => (
|
||||
<div
|
||||
key={tag.key || index}
|
||||
title={tag.title}
|
||||
style={styles.tagItemBlock}
|
||||
>
|
||||
<span style={getStyle(tagStyle, styles.tagItem, styleContext)}>
|
||||
{tag.value}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!!item.customOverlay && (
|
||||
<div
|
||||
className="ReactGridGallery_custom-overlay"
|
||||
style={styles.customOverlay({ hover })}
|
||||
>
|
||||
{item.customOverlay}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="ReactGridGallery_tile-overlay"
|
||||
style={styles.tileOverlay({
|
||||
showOverlay: hover && !item.isSelected && isSelectable,
|
||||
})}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="ReactGridGallery_tile-viewport"
|
||||
data-testid="grid-gallery-item_viewport"
|
||||
style={getStyle(tileViewportStyle, styles.tileViewport, styleContext)}
|
||||
onClick={handleViewportClick}
|
||||
>
|
||||
{ThumbnailImageComponent ? (
|
||||
<ThumbnailImageComponent
|
||||
{...thumbnailImageProps}
|
||||
imageProps={thumbnailProps}
|
||||
/>
|
||||
) : (
|
||||
<img {...thumbnailProps} />
|
||||
)}
|
||||
</div>
|
||||
{item.thumbnailCaption && (
|
||||
<div
|
||||
className="ReactGridGallery_tile-description"
|
||||
style={styles.tileDescription}
|
||||
>
|
||||
{item.thumbnailCaption}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
100
client/src/vendor/react-grid-gallery/buildLayout.js
vendored
Normal file
100
client/src/vendor/react-grid-gallery/buildLayout.js
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
const calculateCutOff = (
|
||||
items,
|
||||
totalRowWidth,
|
||||
protrudingWidth
|
||||
) => {
|
||||
const cutOff = [];
|
||||
let cutSum = 0;
|
||||
for (let i in items) {
|
||||
const item = items[i];
|
||||
const fractionOfWidth = item.scaledWidth / totalRowWidth;
|
||||
cutOff[i] = Math.floor(fractionOfWidth * protrudingWidth);
|
||||
cutSum += cutOff[i];
|
||||
}
|
||||
|
||||
let stillToCutOff = protrudingWidth - cutSum;
|
||||
while (stillToCutOff > 0) {
|
||||
for (let i in cutOff) {
|
||||
cutOff[i]++;
|
||||
stillToCutOff--;
|
||||
if (stillToCutOff < 0) break;
|
||||
}
|
||||
}
|
||||
return cutOff;
|
||||
};
|
||||
|
||||
const getRow = (
|
||||
images,
|
||||
{ containerWidth, rowHeight, margin }
|
||||
) => {
|
||||
const row = [];
|
||||
const imgMargin = 2 * margin;
|
||||
const items = [...images];
|
||||
|
||||
let totalRowWidth = 0;
|
||||
while (items.length > 0 && totalRowWidth < containerWidth) {
|
||||
const item = items.shift();
|
||||
const scaledWidth = Math.floor(rowHeight * (item.width / item.height));
|
||||
const extendedItem = {
|
||||
...item,
|
||||
scaledHeight: rowHeight,
|
||||
scaledWidth,
|
||||
viewportWidth: scaledWidth,
|
||||
marginLeft: 0,
|
||||
};
|
||||
row.push(extendedItem);
|
||||
totalRowWidth += extendedItem.scaledWidth + imgMargin;
|
||||
}
|
||||
|
||||
const protrudingWidth = totalRowWidth - containerWidth;
|
||||
if (row.length > 0 && protrudingWidth > 0) {
|
||||
const cutoff = calculateCutOff(row, totalRowWidth, protrudingWidth);
|
||||
for (const i in row) {
|
||||
const pixelsToRemove = cutoff[i];
|
||||
const item = row[i];
|
||||
item.marginLeft = -Math.abs(Math.floor(pixelsToRemove / 2));
|
||||
item.viewportWidth = item.scaledWidth - pixelsToRemove;
|
||||
}
|
||||
}
|
||||
|
||||
return [row, items];
|
||||
};
|
||||
|
||||
const getRows = (
|
||||
images,
|
||||
options,
|
||||
rows = []
|
||||
) => {
|
||||
const [row, imagesLeft] = getRow(images, options);
|
||||
const nextRows = [...rows, row];
|
||||
|
||||
if (options.maxRows && nextRows.length >= options.maxRows) {
|
||||
return nextRows;
|
||||
}
|
||||
if (imagesLeft.length) {
|
||||
return getRows(imagesLeft, options, nextRows);
|
||||
}
|
||||
return nextRows;
|
||||
};
|
||||
|
||||
export const buildLayout = (
|
||||
images,
|
||||
{ containerWidth, maxRows, rowHeight, margin }
|
||||
) => {
|
||||
rowHeight = typeof rowHeight === "undefined" ? 180 : rowHeight;
|
||||
margin = typeof margin === "undefined" ? 2 : margin;
|
||||
|
||||
if (!images) return [];
|
||||
if (!containerWidth) return [];
|
||||
|
||||
const options = { containerWidth, maxRows, rowHeight, margin };
|
||||
return getRows(images, options);
|
||||
};
|
||||
|
||||
export const buildLayoutFlat = (
|
||||
images,
|
||||
options
|
||||
) => {
|
||||
const rows = buildLayout(images, options);
|
||||
return [].concat.apply([], rows);
|
||||
};
|
||||
3
client/src/vendor/react-grid-gallery/index.js
vendored
Normal file
3
client/src/vendor/react-grid-gallery/index.js
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
export { Gallery } from "./Gallery";
|
||||
export { CheckButton } from "./CheckButton";
|
||||
export { buildLayout, buildLayoutFlat } from "./buildLayout";
|
||||
185
client/src/vendor/react-grid-gallery/styles.js
vendored
Normal file
185
client/src/vendor/react-grid-gallery/styles.js
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
export const getStyle = (
|
||||
styleProp,
|
||||
fallback,
|
||||
context
|
||||
) => {
|
||||
if (typeof styleProp === "function") {
|
||||
return styleProp(context);
|
||||
}
|
||||
if (typeof styleProp === "object") {
|
||||
return styleProp;
|
||||
}
|
||||
return fallback(context);
|
||||
};
|
||||
|
||||
const rotationTransformMap = {
|
||||
3: "rotate(180deg)",
|
||||
2: "rotateY(180deg)",
|
||||
4: "rotate(180deg) rotateY(180deg)",
|
||||
5: "rotate(270deg) rotateY(180deg)",
|
||||
6: "rotate(90deg)",
|
||||
7: "rotate(90deg) rotateY(180deg)",
|
||||
8: "rotate(270deg)",
|
||||
};
|
||||
|
||||
const SELECTION_MARGIN = 16;
|
||||
|
||||
export const gallery = {
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
};
|
||||
|
||||
export const thumbnail = ({ item }) => {
|
||||
const rotationTransformValue = rotationTransformMap[item.orientation];
|
||||
|
||||
const style = {
|
||||
cursor: "pointer",
|
||||
maxWidth: "none",
|
||||
width: item.scaledWidth,
|
||||
height: item.scaledHeight,
|
||||
marginLeft: item.marginLeft,
|
||||
marginTop: 0,
|
||||
transform: rotationTransformValue,
|
||||
};
|
||||
|
||||
if (item.isSelected) {
|
||||
const ratio = item.scaledWidth / item.scaledHeight;
|
||||
const viewportHeight = item.scaledHeight - SELECTION_MARGIN * 2;
|
||||
const viewportWidth = item.viewportWidth - SELECTION_MARGIN * 2;
|
||||
|
||||
let height, width;
|
||||
if (item.scaledWidth > item.scaledHeight) {
|
||||
width = item.scaledWidth - SELECTION_MARGIN * 2;
|
||||
height = Math.floor(width / ratio);
|
||||
} else {
|
||||
height = item.scaledHeight - SELECTION_MARGIN * 2;
|
||||
width = Math.floor(height * ratio);
|
||||
}
|
||||
|
||||
const marginTop = Math.abs(Math.floor((viewportHeight - height) / 2));
|
||||
const marginLeft = Math.abs(Math.floor((viewportWidth - width) / 2));
|
||||
|
||||
style.width = width;
|
||||
style.height = height;
|
||||
style.marginLeft = marginLeft === 0 ? 0 : -marginLeft;
|
||||
style.marginTop = marginTop === 0 ? 0 : -marginTop;
|
||||
}
|
||||
|
||||
return style;
|
||||
};
|
||||
|
||||
export const tileViewport = ({
|
||||
item,
|
||||
}) => {
|
||||
const styles = {
|
||||
width: item.viewportWidth,
|
||||
height: item.scaledHeight,
|
||||
overflow: "hidden",
|
||||
};
|
||||
if (item.nano) {
|
||||
styles.background = `url(${item.nano})`;
|
||||
styles.backgroundSize = "cover";
|
||||
styles.backgroundPosition = "center center";
|
||||
}
|
||||
if (item.isSelected) {
|
||||
styles.width = item.viewportWidth - SELECTION_MARGIN * 2;
|
||||
styles.height = item.scaledHeight - SELECTION_MARGIN * 2;
|
||||
styles.margin = SELECTION_MARGIN;
|
||||
}
|
||||
return styles;
|
||||
};
|
||||
|
||||
export const customOverlay = ({
|
||||
hover,
|
||||
}) => ({
|
||||
pointerEvents: "none",
|
||||
opacity: hover ? 1 : 0,
|
||||
position: "absolute",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
export const galleryItem = ({ margin }) => ({
|
||||
margin,
|
||||
WebkitUserSelect: "none",
|
||||
position: "relative",
|
||||
background: "#eee",
|
||||
padding: "0px",
|
||||
});
|
||||
|
||||
export const tileOverlay = ({
|
||||
showOverlay,
|
||||
}) => ({
|
||||
pointerEvents: "none",
|
||||
opacity: 1,
|
||||
position: "absolute",
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
background: showOverlay
|
||||
? "linear-gradient(to bottom,rgba(0,0,0,0.26),transparent 56px,transparent)"
|
||||
: "none",
|
||||
});
|
||||
|
||||
export const tileIconBar = {
|
||||
pointerEvents: "none",
|
||||
opacity: 1,
|
||||
position: "absolute",
|
||||
height: "36px",
|
||||
width: "100%",
|
||||
};
|
||||
|
||||
export const tileDescription = {
|
||||
background: "white",
|
||||
width: "100%",
|
||||
margin: 0,
|
||||
userSelect: "text",
|
||||
WebkitUserSelect: "text",
|
||||
MozUserSelect: "text",
|
||||
overflow: "hidden",
|
||||
};
|
||||
|
||||
export const bottomBar = {
|
||||
padding: "2px",
|
||||
pointerEvents: "none",
|
||||
position: "absolute",
|
||||
minHeight: "0px",
|
||||
maxHeight: "160px",
|
||||
width: "100%",
|
||||
bottom: "0px",
|
||||
overflow: "hidden",
|
||||
};
|
||||
|
||||
export const tagItemBlock = {
|
||||
display: "inline-block",
|
||||
cursor: "pointer",
|
||||
pointerEvents: "visible",
|
||||
margin: "2px",
|
||||
};
|
||||
|
||||
export const tagItem = () => ({
|
||||
display: "inline",
|
||||
padding: ".2em .6em .3em",
|
||||
fontSize: "75%",
|
||||
fontWeight: "600",
|
||||
lineHeight: "1",
|
||||
color: "yellow",
|
||||
background: "rgba(0,0,0,0.65)",
|
||||
textAlign: "center",
|
||||
whiteSpace: "nowrap",
|
||||
verticalAlign: "baseline",
|
||||
borderRadius: ".25em",
|
||||
});
|
||||
|
||||
export const checkButton = ({
|
||||
isVisible,
|
||||
}) => ({
|
||||
visibility: isVisible ? "visible" : "hidden",
|
||||
background: "none",
|
||||
float: "left",
|
||||
width: 36,
|
||||
height: 36,
|
||||
border: "none",
|
||||
padding: 6,
|
||||
cursor: "pointer",
|
||||
pointerEvents: "visible",
|
||||
});
|
||||
37
client/src/vendor/react-grid-gallery/useContainerWidth.js
vendored
Normal file
37
client/src/vendor/react-grid-gallery/useContainerWidth.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
export function useContainerWidth(defaultContainerWidth) {
|
||||
const ref = useRef(null);
|
||||
const observerRef = useRef();
|
||||
|
||||
const [containerWidth, setContainerWidth] = useState(defaultContainerWidth);
|
||||
|
||||
const containerRef = useCallback((node) => {
|
||||
observerRef.current?.disconnect();
|
||||
observerRef.current = undefined;
|
||||
|
||||
ref.current = node;
|
||||
|
||||
const updateWidth = () => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
let width = ref.current.clientWidth;
|
||||
try {
|
||||
width = ref.current.getBoundingClientRect().width;
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
setContainerWidth(Math.floor(width));
|
||||
};
|
||||
|
||||
updateWidth();
|
||||
|
||||
if (node && typeof ResizeObserver !== "undefined") {
|
||||
observerRef.current = new ResizeObserver(updateWidth);
|
||||
observerRef.current.observe(node);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return { containerRef, containerWidth };
|
||||
}
|
||||
@@ -21,10 +21,11 @@ services:
|
||||
- redis-node-1-data:/data
|
||||
- redis-lock:/redis-lock
|
||||
healthcheck:
|
||||
test: [ "CMD", "redis-cli", "ping" ]
|
||||
test: [ "CMD", "sh", "-c", "redis-cli ping && redis-cli cluster info | grep cluster_state:ok" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 15s
|
||||
|
||||
# Redis Node 2
|
||||
redis-node-2:
|
||||
@@ -39,10 +40,11 @@ services:
|
||||
- redis-node-2-data:/data
|
||||
- redis-lock:/redis-lock
|
||||
healthcheck:
|
||||
test: [ "CMD", "redis-cli", "ping" ]
|
||||
test: [ "CMD", "sh", "-c", "redis-cli ping && redis-cli cluster info | grep cluster_state:ok" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 15s
|
||||
|
||||
# Redis Node 3
|
||||
redis-node-3:
|
||||
@@ -57,10 +59,11 @@ services:
|
||||
- redis-node-3-data:/data
|
||||
- redis-lock:/redis-lock
|
||||
healthcheck:
|
||||
test: [ "CMD", "redis-cli", "ping" ]
|
||||
test: [ "CMD", "sh", "-c", "redis-cli ping && redis-cli cluster info | grep cluster_state:ok" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 15s
|
||||
|
||||
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
||||
# Notes: Set the ENV Debug to 1 for additional logging
|
||||
|
||||
@@ -28,6 +28,10 @@ if [ ! -f "$LOCKFILE" ]; then
|
||||
--cluster-replicas 0
|
||||
|
||||
echo "Redis Cluster initialized."
|
||||
|
||||
# Wait for cluster to be fully ready
|
||||
echo "Waiting for cluster to be fully operational..."
|
||||
sleep 3
|
||||
else
|
||||
echo "Cluster already initialized, skipping initialization."
|
||||
fi
|
||||
|
||||
10
server.js
10
server.js
@@ -213,7 +213,15 @@ const connectToRedisCluster = async () => {
|
||||
const clusterRetryStrategy = (times) => {
|
||||
const delay =
|
||||
Math.min(CLUSTER_RETRY_BASE_DELAY + times * 50, CLUSTER_RETRY_MAX_DELAY) + Math.random() * CLUSTER_RETRY_JITTER;
|
||||
logger.log(`Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`, "WARN", "redis", "api");
|
||||
// Only log every 5th retry or after 10 attempts to reduce noise during startup
|
||||
if (times % 5 === 0 || times > 10) {
|
||||
logger.log(
|
||||
`Redis cluster not yet ready. Retry attempt ${times}, waiting ${delay.toFixed(2)}ms`,
|
||||
"WARN",
|
||||
"redis",
|
||||
"api"
|
||||
);
|
||||
}
|
||||
return delay;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user