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 = () => {
|
handleErrorSubmit = () => {
|
||||||
window.$crisp.push([
|
window.$crisp?.push([
|
||||||
"do",
|
"do",
|
||||||
"message:send",
|
"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**
|
// 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) {
|
if (this.state.hasErrored === true) {
|
||||||
logImEXEvent("error_boundary_rendered", { error, info });
|
logImEXEvent("error_boundary_rendered", { error, info });
|
||||||
|
|
||||||
window.$crisp.push([
|
window.$crisp?.push([
|
||||||
"set",
|
"set",
|
||||||
"session:event",
|
"session:event",
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Col, Row, Space } from "antd";
|
import { Button, Card, Col, Row, Space } from "antd";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Gallery } from "react-grid-gallery";
|
import { Gallery } from "../../vendor/react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Lightbox from "react-image-lightbox";
|
import Lightbox from "react-image-lightbox";
|
||||||
import "react-image-lightbox/style.css";
|
import "react-image-lightbox/style.css";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Gallery } from "react-grid-gallery";
|
import { Gallery } from "../../vendor/react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export function CsiContainerPage({ currentUser }) {
|
|||||||
const getAxiosData = useCallback(async () => {
|
const getAxiosData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
window.$crisp.push(["do", "chat:hide"]);
|
window.$crisp?.push(["do", "chat:hide"]);
|
||||||
} catch {
|
} catch {
|
||||||
console.log("Unable to attach to crisp instance. ");
|
console.log("Unable to attach to crisp instance. ");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export function* signInSuccessSaga({ payload }) {
|
|||||||
LogRocket.identify(payload.email);
|
LogRocket.identify(payload.email);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
window.$crisp?.push(["set", "user:nickname", [payload.displayName || payload.email]]);
|
||||||
|
|
||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
executeFunction: true,
|
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) {
|
if (isParts) {
|
||||||
window.$crisp.push(["do", "chat:hide"]);
|
window.$crisp?.push(["do", "chat:hide"]);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// no-op
|
// no-op
|
||||||
@@ -359,9 +359,9 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
//amplitude.setGroup('Shop', payload.shopname);
|
//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) {
|
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
|
// 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" }));
|
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
|
// 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({
|
InstanceRenderManager({
|
||||||
executeFunction: true,
|
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-node-1-data:/data
|
||||||
- redis-lock:/redis-lock
|
- redis-lock:/redis-lock
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "redis-cli", "ping" ]
|
test: [ "CMD", "sh", "-c", "redis-cli ping && redis-cli cluster info | grep cluster_state:ok" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
start_period: 15s
|
||||||
|
|
||||||
# Redis Node 2
|
# Redis Node 2
|
||||||
redis-node-2:
|
redis-node-2:
|
||||||
@@ -39,10 +40,11 @@ services:
|
|||||||
- redis-node-2-data:/data
|
- redis-node-2-data:/data
|
||||||
- redis-lock:/redis-lock
|
- redis-lock:/redis-lock
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "redis-cli", "ping" ]
|
test: [ "CMD", "sh", "-c", "redis-cli ping && redis-cli cluster info | grep cluster_state:ok" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
start_period: 15s
|
||||||
|
|
||||||
# Redis Node 3
|
# Redis Node 3
|
||||||
redis-node-3:
|
redis-node-3:
|
||||||
@@ -57,10 +59,11 @@ services:
|
|||||||
- redis-node-3-data:/data
|
- redis-node-3-data:/data
|
||||||
- redis-lock:/redis-lock
|
- redis-lock:/redis-lock
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "redis-cli", "ping" ]
|
test: [ "CMD", "sh", "-c", "redis-cli ping && redis-cli cluster info | grep cluster_state:ok" ]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
start_period: 15s
|
||||||
|
|
||||||
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
||||||
# Notes: Set the ENV Debug to 1 for additional logging
|
# Notes: Set the ENV Debug to 1 for additional logging
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ if [ ! -f "$LOCKFILE" ]; then
|
|||||||
--cluster-replicas 0
|
--cluster-replicas 0
|
||||||
|
|
||||||
echo "Redis Cluster initialized."
|
echo "Redis Cluster initialized."
|
||||||
|
|
||||||
|
# Wait for cluster to be fully ready
|
||||||
|
echo "Waiting for cluster to be fully operational..."
|
||||||
|
sleep 3
|
||||||
else
|
else
|
||||||
echo "Cluster already initialized, skipping initialization."
|
echo "Cluster already initialized, skipping initialization."
|
||||||
fi
|
fi
|
||||||
|
|||||||
10
server.js
10
server.js
@@ -213,7 +213,15 @@ const connectToRedisCluster = async () => {
|
|||||||
const clusterRetryStrategy = (times) => {
|
const clusterRetryStrategy = (times) => {
|
||||||
const delay =
|
const delay =
|
||||||
Math.min(CLUSTER_RETRY_BASE_DELAY + times * 50, CLUSTER_RETRY_MAX_DELAY) + Math.random() * CLUSTER_RETRY_JITTER;
|
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;
|
return delay;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user