punkty rwd

This commit is contained in:
Pawel33359
2026-01-02 14:39:16 +01:00
parent fa01cd97e3
commit df964a8088
16 changed files with 415 additions and 178 deletions

View File

@@ -17,6 +17,7 @@ Użyte biblioteki
2. React Router 2. React Router
3. zustand 3. zustand
4. react shadow 4. react shadow
5. react-hot-toast
Dodać contentEditable Dodać contentEditable
przepisać działanie tej listy. Może przygotować gotowy komponent od listy w zustand przepisać działanie tej listy. Może przygotować gotowy komponent od listy w zustand
@@ -29,10 +30,8 @@ Jak zrobić Preview - CodeBox
2. Do Preview powinno się dać dodać jakieś funkcje pozwalające zmieniać zustand 2. Do Preview powinno się dać dodać jakieś funkcje pozwalające zmieniać zustand
3. zmiany w codebox powinny też pozwalać na zmianę zustand'a(może lepiej najpierw generować kod w codebox a później wklejać go do preview? tylko co później z podpinaniem funkcji do np punktów żeby dało się je przesuwać myszką? i pobieranie i używanie danych z GraphQL w Preview?) 3. zmiany w codebox powinny też pozwalać na zmianę zustand'a(może lepiej najpierw generować kod w codebox a później wklejać go do preview? tylko co później z podpinaniem funkcji do np punktów żeby dało się je przesuwać myszką? i pobieranie i używanie danych z GraphQL w Preview?)
Trzy położenia punktów Trzy położenia punktów(+ wycentrowanie położenia boxa produktowego)
za duże plusy - kwestia czcionki apliakcji pewnie
xd przez shadow roota stylowanie komponentów z MUI nie działa w preview xd przez shadow roota stylowanie komponentów z MUI nie działa w preview
poprawić rerenderowanie szczególnie inputow poprawić rerenderowanie szczególnie inputow
problem rem em w shadow root problem rem em w shadow root

30
package-lock.json generated
View File

@@ -14,6 +14,7 @@
"@mui/material": "^7.3.6", "@mui/material": "^7.3.6",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.11.0", "react-router-dom": "^7.11.0",
"react-shadow": "^20.6.0", "react-shadow": "^20.6.0",
"zustand": "^5.0.9" "zustand": "^5.0.9"
@@ -2267,7 +2268,8 @@
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
@@ -2724,6 +2726,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/goober": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz",
"integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==",
"license": "MIT",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3302,6 +3313,23 @@
"react": "^19.2.3" "react": "^19.2.3"
} }
}, },
"node_modules/react-hot-toast": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz",
"integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==",
"license": "MIT",
"dependencies": {
"csstype": "^3.1.3",
"goober": "^2.1.16"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=16",
"react-dom": ">=16"
}
},
"node_modules/react-is": { "node_modules/react-is": {
"version": "19.2.3", "version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz",

View File

@@ -16,6 +16,7 @@
"@mui/material": "^7.3.6", "@mui/material": "^7.3.6",
"react": "^19.2.0", "react": "^19.2.0",
"react-dom": "^19.2.0", "react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.11.0", "react-router-dom": "^7.11.0",
"react-shadow": "^20.6.0", "react-shadow": "^20.6.0",
"zustand": "^5.0.9" "zustand": "^5.0.9"

View File

@@ -3,22 +3,22 @@ import Home from "./pages/Home";
import "./styles/index.css"; import "./styles/index.css";
import AppLayout from "./ui/AppLayout"; import AppLayout from "./ui/AppLayout";
import Instruction from "./pages/Instruction"; import Instruction from "./pages/Instruction";
import { Toaster } from "react-hot-toast";
function App() { function App() {
return ( return (
// <AppLayout> <>
// <Home/> <Toaster />
// </AppLayout> <Routes>
<Routes> <Route element={<AppLayout />}>
<Route element={<AppLayout/>}> <Route path="/" element={<Home />} />
<Route path="/" element={<Home/>}/> </Route>
</Route> <Route element={<AppLayout showSidebar={false} />}>
<Route element={<AppLayout showSidebar={false}/>}> <Route path="/instruction" element={<Instruction />} />
<Route path="/instruction" element={<Instruction/>}/> </Route>
</Route> </Routes>
</Routes> </>
) );
} }
export default App export default App;

View File

@@ -1,17 +1,29 @@
import { BREAKPOINTS } from "./rwd";
// Constants for PhotoModule // Constants for PhotoModule
export const DIRECTIONS = ["tl", "tr", "bl", "br"]; // top-left, top-right, bottom-left, bottom-right
export const DIRECTIONS_NAMES = { // top-left, top-right, bottom-left, bottom-right
tl: "Góra Lewo", export const DIRECTIONS = {
tr: "Góra Prawo", "t-l": "Góra Lewo",
bl: "Dół Lewo", "t-r": "Góra Prawo",
br: "Dół Prawo", "b-l": "Dół Lewo",
"b-r": "Dół Prawo",
}; };
export const DEFAULT_POINT = { export const DEFAULT_POINT = {
x: 0, x: 0,
y: 0, y: 0,
positions: {
single: { x: 0, y: 0 },
// ...Object.BREAKPOINTS
desktop: { x: 0, y: 0 },
tablet: { x: 0, y: 0 },
mobile: { x: 0, y: 0 },
},
id: 0, id: 0,
direction: DIRECTIONS[0], direction: Object.keys(DIRECTIONS)[0],
}; // Default point structure }; // Default point structure
export const URL_RADIO_DATA = [ export const URL_RADIO_DATA = [

View File

@@ -1,5 +1,5 @@
export const BREAKPOINTS = { export const BREAKPOINTS = {
mobile: 0,
tablet: 757,
desktop: 979, desktop: 979,
tablet: 757,
mobile: 0,
}; };

View File

@@ -1,16 +1,62 @@
import { TextareaAutosize } from "@mui/material"; import { Button, TextareaAutosize } from "@mui/material";
import GenericBox from "../../../ui/GenericBox"; import GenericBox from "../../../ui/GenericBox";
import GeneratePreview from "../generated/GeneratePreview";
import { useSharedState } from "../../../store/useSharedState";
import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast";
export default function CodeBox() {
const state = useSharedState();
const hiddenRef = useRef(null);
const [code, setCode] = useState("");
// Whenever state changes, update the string
useEffect(() => {
if (hiddenRef.current) {
setCode(hiddenRef.current.innerHTML);
}
}, [state.urls, state.urlRadioPoint, state.points, state.photoAlt]); // reactive slices
const copyCode = () => {
navigator.clipboard.writeText("code");
toast.success("Skopiowano tekst!");
};
const clearCode = () => {
state.clearAll();
toast.success("Wyczyszczono kod");
};
function CodeBox() {
return ( return (
<GenericBox variant="outer" title="Twój Kod" canCollapse={false}> <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}
/>
</div>
<div style={{ display: "flex", gap: "1rem" }}>
<Button variant="contained" onClick={copyCode}>
Kopiuj
</Button>
<Button variant="contained" onClick={clearCode}>
Wyczyść
</Button>
<Button variant="contained">Wczytaj kod</Button>
</div>
{/* Textarea showing the HTML */}
<TextareaAutosize <TextareaAutosize
sx={{ width: "100%", height: "100%" }} sx={{ width: "100%", height: "100%" }}
style={{ resize: "none" }} style={{ resize: "none" }}
minRows={5} minRows={5}
value={code}
/> />
</GenericBox> </GenericBox>
); );
} }
export default CodeBox;

View File

@@ -2,6 +2,7 @@ import styled from "@emotion/styled";
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import { BREAKPOINTS } from "../../../constants/rwd"; import { BREAKPOINTS } from "../../../constants/rwd";
import { useSharedState } from "../../../store/useSharedState"; import { useSharedState } from "../../../store/useSharedState";
import { capitalizeFirstLetter } from "../../../utils/capitalizeFirstLetter";
const StyledPreviewTabsContainer = styled("div")({ const StyledPreviewTabsContainer = styled("div")({
display: "grid", display: "grid",
@@ -16,16 +17,18 @@ function PreviewRWDTabs() {
return ( return (
<StyledPreviewTabsContainer> <StyledPreviewTabsContainer>
{currentPreviewMode === "single" ? null : Object.keys(BREAKPOINTS).map((key) => ( {currentPreviewMode === "single"
<Button ? null
key={key} : Object.keys(BREAKPOINTS).map((key) => (
variant="contained" <Button
disabled={currentPreviewMode === key} key={key}
onClick={() => setPreviewMode(key)} variant="contained"
> disabled={currentPreviewMode === key}
{key.charAt(0).toUpperCase() + key.slice(1)} onClick={() => setPreviewMode(key)}
</Button> >
))} {capitalizeFirstLetter(key)}
</Button>
))}
</StyledPreviewTabsContainer> </StyledPreviewTabsContainer>
); );
} }

View File

@@ -12,59 +12,6 @@ function GeneratePreview({ preview = true }) {
<div className="idm_picture__module"> <div className="idm_picture__module">
{<GeneratePreviewImage preview={preview} urls={urls} />} {<GeneratePreviewImage preview={preview} urls={urls} />}
{<GeneratePreviewPoints preview={preview} />} {<GeneratePreviewPoints preview={preview} />}
{/*
<div className="idm_picture__overlay">
<div
className="idm_picture__product"
style={{
"--photo-prod-point-desktop-top": "50%",
"--photo-prod-point-desktop-left": "60%",
"--photo-prod-point-tablet-top": "50%",
"--photo-prod-point-tablet-left": "60%",
"--photo-prod-point-mobile-top": "50%",
"--photo-prod-point-mobile-left": "60%",
}}
>
<button
className="idm_picture__product_point"
aria-describedby="prod_id_199974"
aria-label="Product Info"
tabIndex="-1"
>
+
</button>
<div
className="product_info"
id="prod_id_199974"
data-id="199974"
></div>
</div>
<div
className="idm_picture__product"
style={{
"--photo-prod-point-desktop-top": "30%",
"--photo-prod-point-desktop-left": "20%",
"--photo-prod-point-tablet-top": "30%",
"--photo-prod-point-tablet-left": "20%",
"--photo-prod-point-mobile-top": "30%",
"--photo-prod-point-mobile-left": "20%",
}}
>
<button
className="idm_picture__product_point"
aria-describedby="prod_id_196974"
aria-label="Product Info"
tabIndex="-1"
>
+
</button>
<div
className="product_info"
id="prod_id_196974"
data-id="196974"
></div>
</div>
</div> */}
</div> </div>
); );
} }

View File

@@ -3,22 +3,27 @@ import { useSharedState } from "../../../store/useSharedState";
// //
function GeneratePreviewSinglePoint({ index, preview, containerRef }) { function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
const { x, y, id, direction } = useSharedState( const { positions, id, direction } = useSharedState(
(state) => state.points[index] (state) => state.points[index]
); );
const setPoint = useSharedState((state) => state.setSinglePoint); // you need a setter in your store 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 = (e) => { const onPointerDown = (e) => {
e.preventDefault(); e.preventDefault();
const container = containerRef.current; const container = containerRef.current;
console.log(containerRef);
if (!container) return; if (!container) return;
const rect = container.getBoundingClientRect(); const rect = container.getBoundingClientRect();
const startX = e.clientX; const startX = e.clientX;
const startY = e.clientY; const startY = e.clientY;
const initialX = x; const initialX = positions[previewMode].x;
const initialY = y; const initialY = positions[previewMode].y;
const onPointerMove = (moveEvent) => { const onPointerMove = (moveEvent) => {
const deltaX = ((moveEvent.clientX - startX) / rect.width) * 100; const deltaX = ((moveEvent.clientX - startX) / rect.width) * 100;
@@ -28,7 +33,8 @@ function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
const newX = Math.min(100, Math.max(0, initialX + deltaX)); const newX = Math.min(100, Math.max(0, initialX + deltaX));
const newY = Math.min(100, Math.max(0, initialY + deltaY)); const newY = Math.min(100, Math.max(0, initialY + deltaY));
setPoint(index, { x: newX, y: newY }); setSinglePointPosition(index, previewMode, "x", newX);
setSinglePointPosition(index, previewMode, "y", newY);
}; };
const onPointerUp = () => { const onPointerUp = () => {
@@ -40,17 +46,58 @@ function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
window.addEventListener("pointerup", onPointerUp); window.addEventListener("pointerup", onPointerUp);
}; };
if (preview)
return (
<div
className="idm_picture__product"
style={{
top: `${positions[previewMode].x}%`,
left: `${positions[previewMode].y}%`,
}}
>
<button
className="idm_picture__product_point"
// aria-describedby="prod_id_199974"
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>
</div>
);
return ( return (
<div <div
className="idm_picture__product" className="idm_picture__product"
style={{ style={
"--photo-prod-point-desktop-top": `${y}%`, previewMode === "single"
"--photo-prod-point-desktop-left": `${x}%`, ? { top: positions.single.y, left: positions.single.y }
// "--photo-prod-point-tablet-top": "50%", : {
// "--photo-prod-point-tablet-left": "60%", "--photo-prod-point-desktop-top": `${positions.desktop.y}%`,
// "--photo-prod-point-mobile-top": "50%", "--photo-prod-point-desktop-left": `${positions.desktop.x}%`,
// "--photo-prod-point-mobile-left": "60%", "--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}%`,
}
}
> >
<button <button
className="idm_picture__product_point" className="idm_picture__product_point"
@@ -65,6 +112,8 @@ function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
className="product_info" className="product_info"
// id="prod_id_199974" // id="prod_id_199974"
data-id={id} data-id={id}
data-direction-x={directionX}
data-direction-y={directionY}
> >
{preview && ( {preview && (
<> <>

View File

@@ -2,7 +2,7 @@ function GenerateStyle() {
return ( return (
<style> <style>
{` {`
.idm_picture__module{ .idm_picture__module{
--photo-prod-box-bg: #fff; --photo-prod-box-bg: #fff;
--photo-prod-box-text: #111; --photo-prod-box-text: #111;
--photo-prod-point-shadow: rgba(255,255,255,.5); --photo-prod-point-shadow: rgba(255,255,255,.5);
@@ -14,10 +14,10 @@ function GenerateStyle() {
--photo-prod-point-size: 24px; --photo-prod-point-size: 24px;
--photo-prod-box-radius: 20px; --photo-prod-box-radius: 20px;
--photo-prod-box-radius-rb: 0px 20px 20px 20px; --photo-prod-box-radius-br: 0px 20px 20px 20px;
--photo-prod-box-radius-lb: 20px 0px 20px 20px; --photo-prod-box-radius-bl: 20px 0px 20px 20px;
--photo-prod-box-radius-lu: 20px 20px 0px 20px; --photo-prod-box-radius-tl: 20px 20px 0px 20px;
--photo-prod-box-radius-ru: 20px 20px 20px 0px; --photo-prod-box-radius-tr: 20px 20px 20px 0px;
--photo-prod-point-desktop-top: 0%; --photo-prod-point-desktop-top: 0%;
@@ -26,6 +26,14 @@ function GenerateStyle() {
--photo-prod-point-tablet-left: 0%; --photo-prod-point-tablet-left: 0%;
--photo-prod-point-mobile-top: 0%; --photo-prod-point-mobile-top: 0%;
--photo-prod-point-mobile-left: 0%; --photo-prod-point-mobile-left: 0%;
--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{ .idm_picture__module{
@@ -38,14 +46,12 @@ function GenerateStyle() {
.idm_picture__img{ .idm_picture__img{
width: 100%; width: 100%;
} }
.idm_picture__overlay{ .idm_picture__overlay{
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; position: absolute;
top: 0; top: 0;
} }
/* ========================= /* =========================
PRODUCT POINT ( + ) PRODUCT POINT ( + )
========================= */ ========================= */
@@ -119,14 +125,56 @@ PULSE ANIMATION
flex-direction: column; flex-direction: column;
padding: var(--photo-prod-box-pad-top) var(--photo-prod-box-pad-left); padding: var(--photo-prod-box-pad-top) var(--photo-prod-box-pad-left);
gap: 0.5em; gap: 0.5em;
margin-left: var(--photo-prod-box-pad-left);
margin-top: var(--photo-prod-box-pad-top);
display: none; display: none;
max-width: var(--photo-prod-box-width); max-width: var(--photo-prod-box-width);
border-radius: var(--photo-prod-box-radius-rb); position: absolute;
/* opacity: 0; width: max-content;
transition: opacity 0.3s; */ }
.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));
}
.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));
}
.product_info[data-direction-y="t"]::before{
top: 0;
}
.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[data-direction-y="t"][data-direction-x="l"]{
border-radius: var(--photo-prod-box-radius-tl);
}
.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);
} }
.product_name{ .product_name{
@@ -172,6 +220,9 @@ PULSE ANIMATION
} }
} }
.idm_picture__product_point{ .idm_picture__product_point{
cursor: grab; cursor: grab;
} }

View File

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

View File

@@ -0,0 +1,115 @@
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";
const StyledPointsContainer = styled("div")({
display: "grid",
gridTemplateColumns: "100px 1fr 1fr",
gap: "0.5rem",
alignItems: "center",
});
function PhotoPointPosition({ index }) {
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition
);
const urlRadioPoint = useSharedState((state) => state.urlRadioPoint);
const positions = useSharedState((state) => state.points[index].positions);
const handleChangeNumber = ({
e,
brName,
positionName,
min = 0,
max = 100,
}) => {
let newValue = Number(e.target.value);
if (newValue < min) newValue = min;
if (newValue > max) newValue = max;
setSinglePointPosition(index, brName, positionName, newValue);
};
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>
))}
</>
)}
</div>
);
}
export default PhotoPointPosition;

View File

@@ -1,29 +1,19 @@
import { MenuItem, Select, FormControl, InputLabel } from "@mui/material"; import { MenuItem, Select, FormControl, InputLabel } from "@mui/material";
import InputField from "../../ui/InputField"; import InputField from "../../ui/InputField";
import { DIRECTIONS, DIRECTIONS_NAMES } from "./../../constants/photo"; import { DIRECTIONS } from "./../../constants/photo";
import GenericBox from "../../ui/GenericBox"; import GenericBox from "../../ui/GenericBox";
import { useSharedState } from "../../store/useSharedState"; import { useSharedState } from "../../store/useSharedState";
import PhotoPointPosition from "./PhotoPointPosition";
function PhotoSinglePoint({ index }) { function PhotoSinglePoint({ index }) {
const setSinglePoint = useSharedState((state) => state.setSinglePoint); const setSinglePoint = useSharedState((state) => state.setSinglePoint);
const removeSinglePoint = useSharedState((state) => state.removeSinglePoint); const removeSinglePoint = useSharedState((state) => state.removeSinglePoint);
const point = useSharedState((state) => state.points[index]); const direction = useSharedState((state) => state.points[index].direction);
if (!point) return null; const id = useSharedState((state) => state.points[index].id);
const { x, y, direction, id } = point;
const handleChangeNumber = ({ e, name, min = 0, max = 100 }) => {
let newValue = Number(e.target.value);
if (newValue < min) newValue = min;
if (newValue > max) newValue = max;
setSinglePoint(index, { [name]: newValue });
};
return ( return (
<GenericBox <GenericBox
variant="inner" variant="inner"
sx={{ gridTemplateColumns: "repeat(2, 1fr)" }}
title={`${index + 1}`} title={`${index + 1}`}
removeFn={() => removeSinglePoint(index)} removeFn={() => removeSinglePoint(index)}
> >
@@ -32,24 +22,10 @@ function PhotoSinglePoint({ index }) {
type="text" type="text"
name="id produktu" name="id produktu"
value={id} value={id}
sx={{ gridColumn: "span 2" }}
onChange={(e) => setSinglePoint(index, { id: e.target.value })} onChange={(e) => setSinglePoint(index, { id: e.target.value })}
/> />
<InputField <PhotoPointPosition index={index} />
id={`point-${index}-x`} <FormControl fullWidth variant="outlined">
type="number"
name="Położenie x"
value={x}
onChange={(e) => handleChangeNumber({ e, name: "x", min: 0, max: 100 })}
/>
<InputField
id={`point-${index}-y`}
type="number"
name="Położenie y"
value={y}
onChange={(e) => handleChangeNumber({ e, name: "y", min: 0, max: 100 })}
/>
<FormControl fullWidth variant="outlined" sx={{ gridColumn: "span 2" }}>
<InputLabel id="direction-label">Kierunek</InputLabel> <InputLabel id="direction-label">Kierunek</InputLabel>
<Select <Select
labelId="direction-label" labelId="direction-label"
@@ -57,9 +33,9 @@ function PhotoSinglePoint({ index }) {
label="Kierunek" label="Kierunek"
onChange={(e) => setSinglePoint(index, { direction: e.target.value })} onChange={(e) => setSinglePoint(index, { direction: e.target.value })}
> >
{DIRECTIONS.map((dir) => ( {Object.keys(DIRECTIONS).map((dir) => (
<MenuItem key={dir} value={dir}> <MenuItem key={dir} value={dir}>
{DIRECTIONS_NAMES[dir]} {DIRECTIONS[dir]}
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>

View File

@@ -1,37 +1,18 @@
import { create } from "zustand"; import { create } from "zustand";
import { DEFAULT_POINT, URL_RADIO_DATA } from "./../constants/photo"; import { DEFAULT_POINT, URL_RADIO_DATA } from "./../constants/photo";
//
/*
const [urlRadio, setUrlRadio] = useState(URL_RADIO_DATA[0].value);
const [points, setPoints] = useState([
{
x: 80,
y: 80,
id: 75667,
direction: "tl",
},
]);
url: {
desktop: "",
tablet: "",
mobile: "",
single: ""
}
*/
// export const createBox = () => ({}); // export const createBox = () => ({});
const defaultState = {
export const useSharedState = create((set, get) => ({
//DATA
urls: { desktop: "", tablet: "", mobile: "", single: "" }, urls: { desktop: "", tablet: "", mobile: "", single: "" },
urlRadioPoint: URL_RADIO_DATA[0].value, urlRadioPoint: URL_RADIO_DATA[0].value,
points: [{ ...DEFAULT_POINT }], points: [{ ...DEFAULT_POINT }],
photoAlt: "Zdjęcie pokazowe", photoAlt: "Zdjęcie pokazowe",
previewMode: "single", // desktop, tablet, mobile previewMode: "single", // desktop, tablet, mobile
};
export const useSharedState = create((set, get) => ({
//DATA
...defaultState,
//SETTERS/UPDATERS //SETTERS/UPDATERS
setUrl: (type, url) => setUrl: (type, url) =>
@@ -51,6 +32,24 @@ export const useSharedState = create((set, get) => ({
), ),
})), })),
setSinglePointPosition: (id, brName, positionName, newValue) =>
set((state) => ({
points: state.points.map((p, index) =>
index === id
? {
...p,
positions: {
...p.positions,
[brName]: {
...p.positions[brName],
[positionName]: newValue,
},
},
}
: p
),
})),
// Add a point AND increment pointsLength // Add a point AND increment pointsLength
addSinglePoint: (point) => addSinglePoint: (point) =>
set((state) => ({ set((state) => ({
@@ -62,4 +61,6 @@ export const useSharedState = create((set, get) => ({
set((state) => ({ set((state) => ({
points: state.points.filter((p, index) => index !== id), points: state.points.filter((p, index) => index !== id),
})), })),
clearAll: () => set(() => ({ ...defaultState })),
})); }));

View File

@@ -0,0 +1,4 @@
export function capitalizeFirstLetter(str) {
if (!str) return "";
return str.charAt(0).toUpperCase() + str.slice(1);
}