Rozbudowa RWD

This commit is contained in:
2026-01-07 15:09:16 +01:00
parent df964a8088
commit e6a9465195
24 changed files with 728 additions and 311 deletions

178
IdoSellAddOn/script.js Normal file
View File

@@ -0,0 +1,178 @@
function idmObserveEachOnce(elements, callback, options = {}) {
const observer = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
callback(entry);
obs.unobserve(entry.target);
});
},
{
threshold: 0.1,
...options,
}
);
elements.forEach((el) => observer.observe(el));
return observer;
}
idmObserveEachOnce(
document.querySelectorAll(".idm_picture__module"),
(entry) => {
idmPictureModuleProducts(entry.target);
}
);
function idmGetSingleProdGraphQL(prodId) {
const IDM_PRICE_QUERY = (priceType) => `price {
rebateCodeActive
price {
${priceType} {
value
formatted
}
}
omnibusPrice {
${priceType} {
value
formatted
}
}
omnibusPriceDetails {
unit {
${priceType} {
value
formatted
}
}
youSavePercent
omnibusPriceIsHigherThanSellingPrice
newPriceEffectiveUntil {
formatted
}
}
max {
${priceType} {
value
formatted
}
}
unit {
${priceType} {
value
formatted
}
}
unitConvertedPrice {
${priceType} {
value
formatted
}
}
youSavePercent
beforeRebate {
${priceType} {
value
formatted
}
}
beforeRebateDetails {
youSavePercent
unit {
${priceType} {
value
formatted
}
}
}
advancePrice {
${priceType} {
value
formatted
}
}
suggested {
${priceType} {
value
formatted
}
}
rebateNumber {
number
${priceType} {
value
formatted
}
}
}`;
return `
prod${prodId}: product(productId: ${prodId}) {
product {
id
name
link
${IDM_PRICE_QUERY(app_shop.vars.priceType)}
}
}`;
}
async function idmPictureModuleProducts(containerEL) {
try {
const allProdEl = containerEL.querySelectorAll(".product_info[data-id]");
const productsId = [];
allProdEl.forEach((prodEl) => {
if (prodEl.dataset?.id) productsId.push(prodEl.dataset?.id);
});
const res = await fetch("/graphql/v1/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `{${productsId.reduce(
(acc, val, index) => acc + `${idmGetSingleProdGraphQL(val)}`,
""
)}}`,
}),
});
const data = await res.json();
const products = Object.values(data?.data)?.map((prod) => prod.product);
allProdEl.forEach((prodEl) => {
const prodData = products.find((p) => p.id === +prodEl.dataset.id);
if (!prodData) return;
prodEl.classList.add("--mod-init");
prodEl.innerHTML = `
<a class="product_name" href="${prodData.link}">${prodData.name}</a>
<span class="product_price">${
prodData.price?.price?.[app_shop.vars.priceType]?.formatted
}</span>
`;
});
containerEL.querySelectorAll(".product_info:not(.--mod-init)");
} catch (err) {
console.error(err);
}
}
document.body.addEventListener("click", (e) => {
if (app_shop.vars.view === 3 || app_shop.vars.view === 4) return;
const prodContainerEl = e.target.closest(".idm_picture__product");
if (!prodContainerEl) return;
if (prodContainerEl.classList.contains("--show"))
return prodContainerEl.classList.remove("--show");
const moduleContainer = prodContainerEl.closest(".idm_picture__module");
if (!moduleContainer) return;
moduleContainer
.querySelectorAll(".idm_picture__product.--show")
.forEach((prodConEl) => prodConEl.classList.remove("--show"));
prodContainerEl.classList.add("--show");
});

208
IdoSellAddOn/style.html Normal file
View File

@@ -0,0 +1,208 @@
<style>
.idm_picture__module{
--photo-prod-box-bg: #fff;
--photo-prod-box-text: #111;
--photo-prod-point-shadow: rgba(255,255,255,.5);
--photo-prod-box-pad-top: 1rem;
--photo-prod-box-pad-left: 2rem;
--photo-prod-box-width: 30rem;
--photo-prod-point-size: 24px;
--photo-prod-box-radius-br: 0px 20px 20px 20px;
--photo-prod-box-radius-bl: 20px 0px 20px 20px;
--photo-prod-box-radius-tl: 20px 20px 0px 20px;
--photo-prod-box-radius-tr: 20px 20px 20px 0px;
--photo-prod-point-desktop-top: 0%;
--photo-prod-point-desktop-left: 0%;
--photo-prod-point-tablet-top: 0%;
--photo-prod-point-tablet-left: 0%;
--photo-prod-point-mobile-top: 0%;
--photo-prod-point-mobile-left: 0%;
--photo-box-offset: 1rem;
}
.idm_picture__module{
position: relative;
}
.idm_picture__product{
position: absolute;
z-index: 10;
}
.idm_picture__img{
width: 100%;
}
.idm_picture__overlay{
width: 100%;
height: 100%;
position: absolute;
top: 0;
}
/* =========================
PRODUCT POINT ( + )
========================= */
.idm_picture__product_point{
position: relative;
width: var(--photo-prod-point-size);
height: var(--photo-prod-point-size);
border-radius: 50%;
background: var(--photo-prod-box-bg);
color: var(--photo-prod-box-text);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.6rem;
font-weight: 600;
line-height: 1;
z-index: 1;
}
/* Pulsating halo */
.idm_picture__product_point::before{
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
background: var(--photo-prod-point-shadow);
animation: idmPulse 1.5s ease-in-out infinite;
z-index: -1;
}
/* Optional: stop pulse on hover */
.idm_picture__product:hover .idm_picture__product_point:before{
animation-play-state: paused;
}
/* Focus accessibility */
.idm_picture__product_point:focus-visible{
outline: 2px solid #000;
outline-offset: 4px;
}
/* =========================
PULSE ANIMATION
========================= */
@keyframes idmPulse{
0%{
opacity: 1;
box-shadow: 0 0 0px 0px var(--photo-prod-point-shadow);
}
70%{
box-shadow: 0 0 5px 10px var(--photo-prod-point-shadow);
opacity: 0.8;
}
100%{
box-shadow: 0 0 5px 10px var(--photo-prod-point-shadow);
opacity: 0;
}
}
.product_info{
background: var(--photo-prod-box-bg);
/* display: flex; */
flex-direction: column;
padding: var(--photo-prod-box-pad-top) var(--photo-prod-box-pad-left);
gap: 0.5rem;
display: none;
max-width: var(--photo-prod-box-width);
position: absolute;
width: max-content;
box-shadow: 0px 0px 10px 1px #000;
}
.product_info::before{
content: "";
position: absolute;
display: block;
width: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
height: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
z-index: -1;
}
.product_info{
bottom: var(--photo-prod-box-dir-t, auto);
top: var(--photo-prod-box-dir-b, auto);
right: var(--photo-prod-box-dir-l, auto);
left: var(--photo-prod-box-dir-r, auto);
border-radius: var(--photo-prod-box-radius);
}
.product_info::before{
top: var(--photo-prod-box-dir-t-before, auto);
bottom: var(--photo-prod-box-dir-b-before, auto);
right: var(--photo-prod-box-dir-r-before, auto);
left: var(--photo-prod-box-dir-l-before, auto);
}
.idm_picture__product .product_info .product_name{
font-size: 1.6rem;
color: var(--photo-prod-box-text);
}
.idm_picture__product .product_info .product_name:hover{
color: var(--primary-color, #000)!important;
}
.idm_picture__product .product_info .product_price{
font-size: 1.6rem;
color: var(--photo-prod-box-text);
}
@media(min-width: 979px){
.idm_picture__product:hover .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
}
}
@media(max-width: 978px){
.idm_picture__product.--show .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
}
}
@keyframes idmShowUp{
from{
opacity: 0;
}
to{
opacity: 1;
}
}
.idm_picture__product{
top: var(--photo-prod-point-mobile-top);
left: var(--photo-prod-point-mobile-left);
display: var(--photo-prod-point-mobile-display, block);
}
@media(min-width: 757px){
.idm_picture__product{
top: var(--photo-prod-point-tablet-top);
left: var(--photo-prod-point-tablet-left);
display: var(--photo-prod-point-tablet-display, block);
}
}
@media(min-width: 979px){
.idm_picture__product{
top: var(--photo-prod-point-desktop-top);
left: var(--photo-prod-point-desktop-left);
display: var(--photo-prod-point-desktop-display, block);
}
}
</style>

View File

@@ -35,3 +35,7 @@ xd przez shadow roota stylowanie komponentów z MUI nie działa w preview
poprawić rerenderowanie szczególnie inputow
problem rem em w shadow root
Zmiana szerokości kod|preview

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 KiB

View File

@@ -1,9 +1,9 @@
import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import "./styles/index.css";
import AppLayout from "./ui/AppLayout";
import Instruction from "./pages/Instruction";
import { Toaster } from "react-hot-toast";
import { Alert } from "@mui/material";
function App() {
return (

View File

@@ -1,7 +1,4 @@
import { BREAKPOINTS } from "./rwd";
// Constants for PhotoModule
// top-left, top-right, bottom-left, bottom-right
export const DIRECTIONS = {
"t-l": "Góra Lewo",
@@ -15,11 +12,11 @@ export const DEFAULT_POINT = {
y: 0,
positions: {
single: { x: 0, y: 0 },
single: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
// ...Object.BREAKPOINTS
desktop: { x: 0, y: 0 },
tablet: { x: 0, y: 0 },
mobile: { x: 0, y: 0 },
desktop: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
tablet: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
mobile: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
},
id: 0,

View File

@@ -15,10 +15,10 @@ export default function CodeBox() {
if (hiddenRef.current) {
setCode(hiddenRef.current.innerHTML);
}
}, [state.urls, state.urlRadioPoint, state.points, state.photoAlt]); // reactive slices
}, [state.urls, state.points, state.photoAlt, state.previewMode]); // reactive slices
const copyCode = () => {
navigator.clipboard.writeText("code");
navigator.clipboard.writeText(code);
toast.success("Skopiowano tekst!");
};
@@ -31,13 +31,7 @@ export default function CodeBox() {
<GenericBox variant="outer" title="Twój Kod" canCollapse={false}>
{/* Hidden live component */}
<div style={{ display: "none" }} ref={hiddenRef}>
<GeneratePreview
preview={false}
urls={state.urls}
urlRadioPoint={state.urlRadioPoint}
points={state.points}
photoAlt={state.photoAlt}
/>
<GeneratePreview preview={false} />
</div>
<div style={{ display: "flex", gap: "1rem" }}>
@@ -47,7 +41,7 @@ export default function CodeBox() {
<Button variant="contained" onClick={clearCode}>
Wyczyść
</Button>
<Button variant="contained">Wczytaj kod</Button>
{/* <Button variant="contained">Wczytaj kod</Button> */}
</div>
{/* Textarea showing the HTML */}

View File

@@ -1,5 +1,5 @@
import styled from "@emotion/styled";
import { Button } from "@mui/material";
import { Button, Tab, Tabs } from "@mui/material";
import { BREAKPOINTS } from "../../../constants/rwd";
import { useSharedState } from "../../../store/useSharedState";
import { capitalizeFirstLetter } from "../../../utils/capitalizeFirstLetter";
@@ -15,21 +15,43 @@ function PreviewRWDTabs() {
const currentPreviewMode = useSharedState((state) => state.previewMode);
const setPreviewMode = useSharedState((state) => state.setPreviewMode);
if (currentPreviewMode === "single") return null;
return (
<StyledPreviewTabsContainer>
{currentPreviewMode === "single"
? null
: Object.keys(BREAKPOINTS).map((key) => (
<Button
key={key}
variant="contained"
disabled={currentPreviewMode === key}
onClick={() => setPreviewMode(key)}
>
{capitalizeFirstLetter(key)}
</Button>
))}
</StyledPreviewTabsContainer>
// <StyledPreviewTabsContainer>
// {currentPreviewMode === "single"
// ? null
// : Object.keys(BREAKPOINTS).map((key) => (
// <Button
// key={key}
// variant="contained"
// disabled={currentPreviewMode === key}
// onClick={() => setPreviewMode(key)}
// >
// {capitalizeFirstLetter(key)}
// </Button>
// ))}
// </StyledPreviewTabsContainer>
<Tabs
value={currentPreviewMode}
onChange={(_, newVal) => setPreviewMode(newVal)}
variant="fullWidth"
sx={(theme) => ({
backgroundColor: theme.palette.background.header,
// borderRadius: "16px 16px 0 0",
})}
>
{Object.keys(BREAKPOINTS).map((key) => (
<Tab
key={key}
value={key}
label={capitalizeFirstLetter(key)}
sx={{
color: (theme) => theme.palette.secondary.light, // inactive color
}}
/>
))}
</Tabs>
);
}

View File

@@ -5,13 +5,14 @@ import GeneratePreviewPoints from "./GeneratePreviewPoints";
function GeneratePreview({ preview = true }) {
const previewMode = useSharedState((state) => state.previewMode); //
const urls = useSharedState((state) => state.urls);
const uniqueId = Math.ceil(Math.random() * 100000 + 1);
if (preview && urls[previewMode] === "") return null;
return (
<div className="idm_picture__module">
<div className="idm_picture__module" id={`idm-picture-module-${uniqueId}`}>
{<GeneratePreviewImage preview={preview} urls={urls} />}
{<GeneratePreviewPoints preview={preview} />}
{<GeneratePreviewPoints preview={preview} uniqueId={uniqueId} />}
</div>
);
}

View File

@@ -3,16 +3,15 @@ import { useSharedState } from "../../../store/useSharedState";
function GeneratePreviewImage({ preview, urls }) {
const imagePrefix =
import.meta.env.MODE === "development"
? import.meta.env.VITE_PUBLIC_URL
? `${import.meta.env.VITE_PUBLIC_URL}/`
: "";
const previewMode = useSharedState((state) => state.previewMode);
const urlRadioPoint = useSharedState((state) => state.urlRadioPoint);
const photoAlt = useSharedState((state) => state.photoAlt);
if (preview)
return (
<img
src={`${imagePrefix}/${urls[previewMode]}`}
src={`${imagePrefix}${urls[previewMode]}`}
alt={photoAlt || ""}
className="idm_picture__img"
loading="lazy"
@@ -21,18 +20,18 @@ function GeneratePreviewImage({ preview, urls }) {
return (
<>
{urlRadioPoint === "rwd" ? (
{previewMode !== "single" ? (
<picture>
<source
srcSet={`${imagePrefix}/${urls.desktop}`}
srcSet={`${imagePrefix}${urls.desktop}`}
media="(min-width: 979px)"
/>
<source
srcSet={`${imagePrefix}/${urls.tablet}`}
srcSet={`${imagePrefix}${urls.tablet}`}
media="(min-width: 757px)"
/>
<img
src={`${imagePrefix}/${urls.mobile}`}
src={`${imagePrefix}${urls.mobile}`}
alt={photoAlt || ""}
className="idm_picture__img"
loading="lazy"
@@ -40,7 +39,7 @@ function GeneratePreviewImage({ preview, urls }) {
</picture>
) : (
<img
src={`${imagePrefix}/${urls.single}`}
src={`${imagePrefix}${urls.single}`}
alt={photoAlt || ""}
className="idm_picture__img"
loading="lazy"

View File

@@ -2,7 +2,7 @@ import { useRef } from "react";
import { useSharedState } from "../../../store/useSharedState";
import GeneratePreviewSinglePoint from "./GeneratePreviewSinglePoint";
function GeneratePreviewPoints({ preview }) {
function GeneratePreviewPoints({ preview, uniqueId }) {
const pointsLength = useSharedState((state) => state.points.length);
const containerRef = useRef();
@@ -14,6 +14,7 @@ function GeneratePreviewPoints({ preview }) {
index={index}
preview={preview}
containerRef={containerRef}
uniqueId={uniqueId}
/>
))}
</div>

View File

@@ -1,49 +1,45 @@
import { BREAKPOINTS } from "../../../constants/rwd";
import { useDragMove } from "../../../hooks/useDragMove";
import { usePrepareRWDStyle } from "../../../hooks/usePrepareRWDStyle";
import { useSharedState } from "../../../store/useSharedState";
// Problemy - unikalne id elementu pod SEO
//
function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
const { positions, id, direction } = useSharedState(
(state) => state.points[index]
);
function GeneratePreviewSinglePoint({
index,
preview,
containerRef,
uniqueId,
}) {
const { positions, id } = useSharedState((state) => state.points[index]);
const previewMode = useSharedState((state) => state.previewMode);
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition
); // you need a setter in your store
const [directionY, directionX] = direction.split("-");
const onPointerDown = useDragMove({
ref: containerRef,
x: positions[previewMode].x,
y: positions[previewMode].y,
changeXFn: (newX) => setSinglePointPosition(index, previewMode, "x", newX),
changeYFn: (newY) => setSinglePointPosition(index, previewMode, "y", newY),
});
const onPointerDown = (e) => {
e.preventDefault();
const container = containerRef.current;
if (!container) return;
const prodBoxUniqueId = `prod-id-${id}-${uniqueId}`;
const rect = container.getBoundingClientRect();
const startX = e.clientX;
const startY = e.clientY;
const initialX = positions[previewMode].x;
const initialY = positions[previewMode].y;
const generatePointStyles = (mode) => {
const [dirY, dirX] = positions[mode].direction.split("-");
return `#${prodBoxUniqueId}{
--photo-prod-box-dir-${dirY}: calc(100% + var(--photo-box-offset));
--photo-prod-box-dir-${dirX}: calc(100% + var(--photo-box-offset));
const onPointerMove = (moveEvent) => {
const deltaX = ((moveEvent.clientX - startX) / rect.width) * 100;
const deltaY = ((moveEvent.clientY - startY) / rect.height) * 100;
--photo-prod-box-dir-${dirY}-before: 0;
--photo-prod-box-dir-${dirX}-before: 0;
// clamp between 0% and 100%
const newX = Math.min(100, Math.max(0, initialX + deltaX));
const newY = Math.min(100, Math.max(0, initialY + deltaY));
setSinglePointPosition(index, previewMode, "x", newX);
setSinglePointPosition(index, previewMode, "y", newY);
};
const onPointerUp = () => {
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
};
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
--photo-prod-box-radius: var(--photo-prod-box-radius-${
dirY + dirX
});
}`;
};
if (preview)
@@ -51,35 +47,27 @@ function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
<div
className="idm_picture__product"
style={{
top: `${positions[previewMode].x}%`,
left: `${positions[previewMode].y}%`,
top: `${positions[previewMode].y}%`,
left: `${positions[previewMode].x}%`,
display: positions[previewMode].hide ? "none" : "block",
}}
>
<button
className="idm_picture__product_point"
// aria-describedby="prod_id_199974"
aria-describedby={prodBoxUniqueId}
aria-label="Product Info"
tabIndex="-1"
onPointerDown={onPointerDown}
>
+
</button>
<div
className="product_info"
// id="prod_id_199974"
data-id={id}
data-direction-x={directionX}
data-direction-y={directionY}
>
{preview && (
<>
<a className="product_name" href="#">
Produkt {index + 1}
</a>
<span className="product_price">XX,XX </span>
</>
)}
<div className="product_info" id={prodBoxUniqueId} data-id={id}>
<a className="product_name" href="#">
Produkt {index + 1}
</a>
<span className="product_price">XX,XX </span>
</div>
<style>{generatePointStyles(previewMode)}</style>
</div>
);
@@ -88,42 +76,37 @@ function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
className="idm_picture__product"
style={
previewMode === "single"
? { top: positions.single.y, left: positions.single.y }
: {
"--photo-prod-point-desktop-top": `${positions.desktop.y}%`,
"--photo-prod-point-desktop-left": `${positions.desktop.x}%`,
"--photo-prod-point-tablet-top": `${positions.tablet.y}%`,
"--photo-prod-point-tablet-left": `${positions.tablet.x}%`,
"--photo-prod-point-mobile-top": `${positions.mobile.y}%`,
"--photo-prod-point-mobile-left": `${positions.mobile.x}%`,
}
? { top: `${positions.single.y}%`, left: `${positions.single.x}%` }
: Object.keys(BREAKPOINTS).reduce((acc, key) => {
acc[`--photo-prod-point-${key}-top`] = `${positions[key].y}%`;
acc[`--photo-prod-point-${key}-left`] = `${positions[key].x}%`;
acc[`--photo-prod-point-${key}-display`] = positions[key].hide
? "none"
: "block";
return acc;
}, {})
}
>
<button
className="idm_picture__product_point"
// aria-describedby="prod_id_199974"
aria-describedby={prodBoxUniqueId}
aria-label="Product Info"
tabIndex="-1"
onPointerDown={onPointerDown}
>
+
</button>
<div
className="product_info"
// id="prod_id_199974"
data-id={id}
data-direction-x={directionX}
data-direction-y={directionY}
>
{preview && (
<>
<a className="product_name" href="#">
Produkt {index + 1}
</a>
<span className="product_price">XX,XX </span>
</>
<div className="product_info" id={prodBoxUniqueId} data-id={id}></div>
<style>
{previewMode === "single" ? (
<style>{generatePointStyles("single")}</style>
) : (
usePrepareRWDStyle({
desktopStyle: generatePointStyles("desktop"),
mobileStyle: generatePointStyles("mobile"),
tabletStyle: generatePointStyles("tablet"),
})
)}
</div>
</style>
</div>
);
}

View File

@@ -13,7 +13,6 @@ function GenerateStyle() {
--photo-prod-box-width: 30em;
--photo-prod-point-size: 24px;
--photo-prod-box-radius: 20px;
--photo-prod-box-radius-br: 0px 20px 20px 20px;
--photo-prod-box-radius-bl: 20px 0px 20px 20px;
--photo-prod-box-radius-tl: 20px 20px 0px 20px;
@@ -29,11 +28,6 @@ function GenerateStyle() {
--photo-box-offset: 1em;
// --photo-box-offset-t: calc(-1 * var(--photo-box-offset));
// --photo-box-offset-b: calc(var(--photo-prod-point-size) + var(--photo-box-offset));
// --photo-box-offset-l: calc(var(--photo-prod-point-size) + var(--photo-box-offset));
// --photo-box-offset-r: calc(-1 * var(--photo-box-offset));
}
.idm_picture__module{
@@ -129,6 +123,8 @@ PULSE ANIMATION
max-width: var(--photo-prod-box-width);
position: absolute;
width: max-content;
box-shadow: 0px 0px 10px 1px #000;
}
.product_info::before{
content: "";
@@ -136,61 +132,51 @@ PULSE ANIMATION
display: block;
width: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
height: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
}
.product_info[data-direction-y="t"]{
bottom: calc(100% + var(--photo-box-offset));
}
.product_info[data-direction-y="b"]{
top: calc(100% + var(--photo-box-offset));
}
.product_info[data-direction-x="l"]{
right: calc(100% + var(--photo-box-offset));
}
.product_info[data-direction-x="r"]{
left: calc(100% + var(--photo-box-offset));
z-index: -1;
}
.product_info[data-direction-y="t"]::before{
top: 0;
.product_info{
bottom: var(--photo-prod-box-dir-t, auto);
top: var(--photo-prod-box-dir-b, auto);
right: var(--photo-prod-box-dir-l, auto);
left: var(--photo-prod-box-dir-r, auto);
border-radius: var(--photo-prod-box-radius);
}
.product_info[data-direction-y="b"]::before{
bottom: 0;
}
.product_info[data-direction-x="l"]::before{
left: 0;
}
.product_info[data-direction-x="r"]::before{
right: 0;
.product_info::before{
top: var(--photo-prod-box-dir-t-before, auto);
bottom: var(--photo-prod-box-dir-b-before, auto);
right: var(--photo-prod-box-dir-r-before, auto);
left: var(--photo-prod-box-dir-l-before, auto);
}
.product_info[data-direction-y="t"][data-direction-x="l"]{
border-radius: var(--photo-prod-box-radius-tl);
.idm_picture__product .product_info .product_name{
font-size: 1.6em;
color: var(--photo-prod-box-text);
}
.product_info[data-direction-y="t"][data-direction-x="r"]{
border-radius: var(--photo-prod-box-radius-tr);
}
.product_info[data-direction-y="b"][data-direction-x="l"]{
border-radius: var(--photo-prod-box-radius-bl);
}
.product_info[data-direction-y="b"][data-direction-x="r"]{
border-radius: var(--photo-prod-box-radius-br);
.idm_picture__product .product_info .product_name:hover{
color: var(--primary-color, #000)!important;
}
.product_name{
.idm_picture__product .product_info .product_price{
font-size: 1.6em;
color: var(--photo-prod-box-text);
}
.product_price{
font-size: 1.6em;
color: var(--photo-prod-box-text);
@media(min-width: 979px){
.idm_picture__product:hover .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
}
}
.idm_picture__product:hover .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
@media(max-width: 978px){
.idm_picture__product.--show .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
}
}
@keyframes idmShowUp{
@@ -206,23 +192,24 @@ PULSE ANIMATION
.idm_picture__product{
top: var(--photo-prod-point-mobile-top);
left: var(--photo-prod-point-mobile-left);
display: var(--photo-prod-point-mobile-display, block);
}
@media(min-width: 757px){
.idm_picture__product{
top: var(--photo-prod-point-tablet-top);
left: var(--photo-prod-point-tablet-left);
display: var(--photo-prod-point-tablet-display, block);
}
}
@media(min-width: 979px){
.idm_picture__product{
top: var(--photo-prod-point-desktop-top);
left: var(--photo-prod-point-desktop-left);
display: var(--photo-prod-point-desktop-display, block);
}
}
.idm_picture__product_point{
cursor: grab;
}

View File

@@ -1,5 +0,0 @@
import { useSharedState } from "../../../store/useSharedState";
export default function useRecoverCode(code) {
const state = useSharedState();
}

View File

@@ -0,0 +1,29 @@
import { Checkbox, FormControl, FormControlLabel } from "@mui/material";
import { useSharedState } from "../../store/useSharedState";
function PhotoPointCheckbox({ index }) {
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition
);
const previewMode = useSharedState((state) => state.previewMode);
const hide = useSharedState(
(state) => state.points[index].positions[previewMode].hide
);
if (previewMode === "single") return null;
const handleChange = () => {
setSinglePointPosition(index, previewMode, "hide", !hide);
};
return (
<FormControl>
<FormControlLabel
control={<Checkbox onChange={handleChange} checked={hide} />}
label="Schowaj punkt dla tego widoku"
/>
</FormControl>
);
}
export default PhotoPointCheckbox;

View File

@@ -0,0 +1,35 @@
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import { DIRECTIONS } from "../../constants/photo";
import { useSharedState } from "../../store/useSharedState";
export default function PhotoPointDirection({ index }) {
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition
);
const previewMode = useSharedState((state) => state.previewMode);
const direction = useSharedState(
(state) => state.points[index].positions[previewMode].direction
);
const handleChange = (value) => {
setSinglePointPosition(index, previewMode, "direction", value);
};
return (
<FormControl fullWidth variant="outlined">
<InputLabel id="direction-label">Kierunek</InputLabel>
<Select
labelId="direction-label"
value={direction}
label="Kierunek"
onChange={(e) => handleChange(e.target.value)}
>
{Object.keys(DIRECTIONS).map((dir) => (
<MenuItem key={dir} value={dir}>
{DIRECTIONS[dir]}
</MenuItem>
))}
</Select>
</FormControl>
);
}

View File

@@ -1,5 +1,4 @@
import styled from "@emotion/styled";
import { BREAKPOINTS } from "../../constants/rwd";
import { useSharedState } from "../../store/useSharedState";
import InputField from "../../ui/InputField";
import { capitalizeFirstLetter } from "../../utils/capitalizeFirstLetter";
@@ -15,8 +14,14 @@ function PhotoPointPosition({ index }) {
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition
);
const urlRadioPoint = useSharedState((state) => state.urlRadioPoint);
const positions = useSharedState((state) => state.points[index].positions);
const previewMode = useSharedState((state) => state.previewMode);
const posX = useSharedState(
(state) => state.points[index].positions[previewMode].x
);
const posY = useSharedState(
(state) => state.points[index].positions[previewMode].y
);
const handleChangeNumber = ({
e,
@@ -35,79 +40,44 @@ function PhotoPointPosition({ index }) {
return (
<div style={{ display: "flex", gap: "1.5rem", flexDirection: "column" }}>
{urlRadioPoint === "single" ? (
<StyledPointsContainer>
<span>Położenie:</span>
<InputField
id={`point-${index}-x`}
type="number"
name="lewo-prawo"
value={positions.single.x}
onChange={(e) =>
handleChangeNumber({
e,
brName: "single",
positionName: "x",
min: 0,
max: 100,
})
}
/>
<InputField
id={`point-${index}-y`}
type="number"
name="góra-dół"
value={positions.single.y}
onChange={(e) =>
handleChangeNumber({
e,
brName: "single",
positionName: "y",
min: 0,
max: 100,
})
}
/>
</StyledPointsContainer>
) : (
<>
{Object.keys(BREAKPOINTS).map((brPoint) => (
<StyledPointsContainer key={brPoint}>
<span>{capitalizeFirstLetter(brPoint)}:</span>
<InputField
id={`point-${index}-x`}
type="number"
name="lewo-prawo"
value={positions[brPoint].x}
onChange={(e) =>
handleChangeNumber({
e,
brName: brPoint,
positionName: "x",
min: 0,
max: 100,
})
}
/>
<InputField
id={`point-${index}-y`}
type="number"
name="góra-dół"
value={positions[brPoint].y}
onChange={(e) =>
handleChangeNumber({
e,
brName: brPoint,
positionName: "y",
min: 0,
max: 100,
})
}
/>
</StyledPointsContainer>
))}
</>
)}
<StyledPointsContainer>
<span>
{previewMode === "single"
? "Położenie"
: capitalizeFirstLetter(previewMode)}
:
</span>
<InputField
id={`point-${index}-x`}
type="number"
name="lewo-prawo"
value={posX}
onChange={(e) =>
handleChangeNumber({
e,
brName: previewMode,
positionName: "x",
min: 0,
max: 100,
})
}
/>
<InputField
id={`point-${index}-y`}
type="number"
name="góra-dół"
value={posY}
onChange={(e) =>
handleChangeNumber({
e,
brName: previewMode,
positionName: "y",
min: 0,
max: 100,
})
}
/>
</StyledPointsContainer>
</div>
);
}

View File

@@ -1,14 +1,13 @@
import { MenuItem, Select, FormControl, InputLabel } from "@mui/material";
import InputField from "../../ui/InputField";
import { DIRECTIONS } from "./../../constants/photo";
import GenericBox from "../../ui/GenericBox";
import { useSharedState } from "../../store/useSharedState";
import PhotoPointPosition from "./PhotoPointPosition";
import PhotoPointCheckbox from "./PhotoPointCheckbox";
import PhotoPointDirection from "./PhotoPointDirection";
function PhotoSinglePoint({ index }) {
const setSinglePoint = useSharedState((state) => state.setSinglePoint);
const removeSinglePoint = useSharedState((state) => state.removeSinglePoint);
const direction = useSharedState((state) => state.points[index].direction);
const id = useSharedState((state) => state.points[index].id);
return (
@@ -25,21 +24,8 @@ function PhotoSinglePoint({ index }) {
onChange={(e) => setSinglePoint(index, { id: e.target.value })}
/>
<PhotoPointPosition index={index} />
<FormControl fullWidth variant="outlined">
<InputLabel id="direction-label">Kierunek</InputLabel>
<Select
labelId="direction-label"
value={direction}
label="Kierunek"
onChange={(e) => setSinglePoint(index, { direction: e.target.value })}
>
{Object.keys(DIRECTIONS).map((dir) => (
<MenuItem key={dir} value={dir}>
{DIRECTIONS[dir]}
</MenuItem>
))}
</Select>
</FormControl>
<PhotoPointDirection index={index} />
<PhotoPointCheckbox index={index} />
</GenericBox>
);
}

View File

@@ -4,24 +4,22 @@ import GenericBox from "../../ui/GenericBox";
import GenericRadioGroup from "../../ui/GenericRadioGroup";
import InputField from "../../ui/InputField";
import { URL_RADIO_DATA } from "./../../constants/photo";
import { useState } from "react";
function PhotoUrl() {
const urlRadioPoint = useSharedState((state) => state.urlRadioPoint);
const setUrlRadioPoint = useSharedState((state) => state.setUrlRadioPoint);
const previewMode = useSharedState((state) => state.previewMode);
const setPreviewMode = useSharedState((state) => state.setPreviewMode);
const urlSingle = useSharedState((state) => state.urls.single);
const urlDesktop = useSharedState((state) => state.urls.desktop);
const urlTablet = useSharedState((state) => state.urls.tablet);
const urlMobile = useSharedState((state) => state.urls.mobile);
const [urlPoint, setUrlPoint] = useState("single");
const url = useSharedState((state) => state.urls[previewMode]);
const setUrl = useSharedState((state) => state.setUrl);
const photoAlt = useSharedState((state) => state.photoAlt);
const setPhotoAlt = useSharedState((state) => state.setPhotoAlt);
const setPreviewMode = useSharedState((state) => state.setPreviewMode);
const handleUrlRadioChange = (event) => {
setUrlRadioPoint(event.target.value);
setUrlPoint(event.target.value);
setPreviewMode(event.target.value === "rwd" ? "desktop" : "single");
};
const handleChangeURL = ({ event, type }) => {
@@ -33,35 +31,17 @@ function PhotoUrl() {
<GenericBox variant="inner" title="Zdjęcie">
<GenericRadioGroup
radioData={URL_RADIO_DATA}
value={urlRadioPoint}
value={urlPoint}
onChange={handleUrlRadioChange}
direction="row"
/>
{urlRadioPoint === "rwd" ? (
<>
<InputField
name="URL zdjęcia desktop"
onChange={(e) => handleChangeURL({ event: e, type: "desktop" })}
value={urlDesktop}
/>
<InputField
name="URL zdjęcia tablet"
onChange={(e) => handleChangeURL({ event: e, type: "tablet" })}
value={urlTablet}
/>
<InputField
name="URL zdjęcia mobile"
onChange={(e) => handleChangeURL({ event: e, type: "mobile" })}
value={urlMobile}
/>
</>
) : (
<InputField
name="URL zdjęcia"
onChange={(e) => handleChangeURL({ event: e, type: "single" })}
value={urlSingle}
/>
)}
<InputField
name={`URL zdjęcia ${
previewMode === "single" ? "" : `na ${previewMode}`
}`}
onChange={(e) => handleChangeURL({ event: e, type: previewMode })}
value={url}
/>
<Divider />
<InputField
name="Alt zdjęcia"

34
src/hooks/useDragMove.js Normal file
View File

@@ -0,0 +1,34 @@
// onPointerDown
export function useDragMove({ ref, x, y, changeXFn, changeYFn }) {
return function (e) {
e.preventDefault();
const container = ref.current;
if (!container) return;
const rect = container.getBoundingClientRect();
const startX = e.clientX;
const startY = e.clientY;
const initialX = x;
const initialY = y;
const onPointerMove = (moveEvent) => {
const deltaX = ((moveEvent.clientX - startX) / rect.width) * 100;
const deltaY = ((moveEvent.clientY - startY) / rect.height) * 100;
// clamp between 0% and 100%
const newX = Math.min(100, Math.max(0, initialX + deltaX));
const newY = Math.min(100, Math.max(0, initialY + deltaY));
changeXFn(newX);
changeYFn(newY);
};
const onPointerUp = () => {
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
};
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
};
}

View File

@@ -0,0 +1,17 @@
import { BREAKPOINTS } from "../constants/rwd";
export function usePrepareRWDStyle({ mobileStyle, desktopStyle, tabletStyle }) {
return `
@media(max-width: ${BREAKPOINTS.tablet - 1}px){
${mobileStyle}
}
@media(min-width: ${BREAKPOINTS.tablet}px) and (max-width: ${
BREAKPOINTS.desktop - 1
}px){
${tabletStyle}
}
@media(min-width: ${BREAKPOINTS.desktop}px){
${desktopStyle}
}
`;
}

View File

@@ -1,7 +1,7 @@
function Instruction() {
return (
<div>
Tu jest instrukcja
Tu miała być instrukcja
</div>
)
}

View File

@@ -1,10 +1,9 @@
import { create } from "zustand";
import { DEFAULT_POINT, URL_RADIO_DATA } from "./../constants/photo";
import { DEFAULT_POINT } from "./../constants/photo";
// export const createBox = () => ({});
const defaultState = {
urls: { desktop: "", tablet: "", mobile: "", single: "" },
urlRadioPoint: URL_RADIO_DATA[0].value,
points: [{ ...DEFAULT_POINT }],
photoAlt: "Zdjęcie pokazowe",
previewMode: "single", // desktop, tablet, mobile
@@ -19,7 +18,6 @@ export const useSharedState = create((set, get) => ({
set((state) => ({ urls: { ...state.urls, [type]: url } })),
setPhotoAlt: (alt) => set({ photoAlt: alt }),
setUrlRadioPoint: (value) => set({ urlRadioPoint: value }),
setPoints: (points) => set({ points }),
setPointsLength: (length) => set({ pointsLength: length }),
setPreviewMode: (mode) => set({ previewMode: mode }),

View File

@@ -1 +0,0 @@