-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #48 from softeerbootcamp4th/feature/22-v2lInteraction
[feat] V2L 인터랙션 구현 ( resolved #22 )
- Loading branch information
Showing
18 changed files
with
548 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { useSyncExternalStore, useEffect } from "react"; | ||
|
||
const mountedStore = { | ||
mounted: false, | ||
listeners: new Set(), | ||
mount() { | ||
mountedStore.mounted = true; | ||
mountedStore.listeners.forEach((listener) => listener()); | ||
}, | ||
subscribe(listener) { | ||
mountedStore.listeners.add(listener); | ||
return () => mountedStore.listeners.delete(listener); | ||
}, | ||
getSnapshot() { | ||
return mountedStore.mounted; | ||
}, | ||
}; | ||
|
||
/** | ||
* react 클라이언트 only 래퍼 입니다. | ||
* 이 래퍼 컴포넌트는 클라이언트에서만 필요한 동작이 필요할 때, SSR시와 하이드레이션시 반환한 함수가 | ||
* 동일한 폴백 엘리먼트를 렌더링하도록 보장합니다. | ||
*/ | ||
export default function ClientOnly({ children, fallback }) { | ||
const mounted = useSyncExternalStore( | ||
mountedStore.subscribe, | ||
mountedStore.getSnapshot, | ||
() => false, | ||
); | ||
useEffect(() => { | ||
mountedStore.mount(); | ||
}, []); | ||
|
||
if (!mounted) return fallback; | ||
return children; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import { useImperativeHandle } from "react"; | |
import InteractionDescription from "../InteractionDescription.jsx"; | ||
import Phone from "./Phone.jsx"; | ||
import useIslandDrag from "./useIslandDrag.js"; | ||
import style from "./style.module.css"; | ||
|
||
import seat from "./assets/seat.png"; | ||
import univasalIsland1x from "./assets/[email protected]"; | ||
|
@@ -21,26 +22,9 @@ function UnivasalIslandInteraction({ interactCallback, $ref }) { | |
|
||
useImperativeHandle($ref, () => ({ reset }), [reset]); | ||
|
||
const seatHullStyle = `absolute w-[1200px] h-[800px] | ||
bottom-[min(calc(100%-800px),-140px)] | ||
lg:bottom-[min(calc(100%-900px),-170px)] | ||
xl:bottom-[min(calc(100%-1000px),-200px)] | ||
flex justify-center items-end select-none`; | ||
|
||
const seatStyle = `w-[317.44px] h-[501.88px] | ||
lg:w-[385.46px] lg:h-[610.64px] | ||
xl:w-[453.48px] xl:h-[718.4px]`; | ||
|
||
const univasalIslandStaticStyle = `w-[158.2px] h-[546px] | ||
lg:w-[192.1px] lg:h-[663px] | ||
xl:w-[226px] xl:h-[780px] | ||
flex flex-col gap-2 cursor-pointer touch-none`; | ||
|
||
const snapAreaStyle = `absolute scale-50 | ||
left-[21px] top-[40px] w-[54px] h-[97px] | ||
lg:left-[25px] lg:top-[49px] lg:w-[66px] lg:h-[118px] | ||
xl:left-[30px] xl:top-[56px] xl:w-[77px] xl:h-[140px] | ||
`; | ||
const seatHullStyle = `absolute w-[1200px] h-[800px] ${style.hull} flex justify-center items-end select-none`; | ||
const univasalIslandStaticStyle = `${style.island} flex flex-col gap-2 cursor-pointer touch-none`; | ||
const snapAreaStyle = `absolute scale-50 ${style.snap}`; | ||
|
||
return ( | ||
<article className="relative w-full h-full overflow-hidden flex items-center flex-col"> | ||
|
@@ -52,7 +36,7 @@ function UnivasalIslandInteraction({ interactCallback, $ref }) { | |
/> | ||
<div className={seatHullStyle}> | ||
<img | ||
className={seatStyle} | ||
className={style.seat} | ||
src={seat} | ||
alt="left seat" | ||
draggable="false" | ||
|
@@ -79,7 +63,7 @@ function UnivasalIslandInteraction({ interactCallback, $ref }) { | |
<div className={snapAreaStyle} ref={phoneSnapArea}></div> | ||
</div> | ||
<img | ||
className={seatStyle} | ||
className={style.seat} | ||
src={seat} | ||
alt="right seat" | ||
draggable="false" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
.hull { | ||
bottom: min(calc(100% - 800px), -140px); | ||
} | ||
.seat { | ||
width: 317.44px; | ||
height: 501.88px; | ||
} | ||
.island { | ||
width: 158.2px; | ||
height: 546px; | ||
} | ||
.phone { | ||
top: 293px; | ||
left: 541px; | ||
width: 54px; | ||
height: 97px; | ||
} | ||
.snap { | ||
top: 40px; | ||
left: 21px; | ||
width: 54px; | ||
height: 97px; | ||
} | ||
|
||
@media (min-width: 1024px) { | ||
.hull { | ||
bottom: min(calc(100% - 900px), -170px); | ||
} | ||
.seat { | ||
width: 385.46px; | ||
height: 610.64px; | ||
} | ||
.island { | ||
width: 192.1px; | ||
height: 663px; | ||
} | ||
.phone { | ||
top: 185px; | ||
left: 528px; | ||
width: 66px; | ||
height: 118px; | ||
} | ||
.snap { | ||
top: 49px; | ||
left: 25px; | ||
width: 66px; | ||
height: 118px; | ||
} | ||
} | ||
|
||
@media (min-width: 1280px) { | ||
.hull { | ||
bottom: min(calc(100% - 1000px), -200px); | ||
} | ||
.seat { | ||
width: 453.48px; | ||
height: 718.4px; | ||
} | ||
.island { | ||
width: 226px; | ||
height: 780px; | ||
} | ||
.phone { | ||
top: 75px; | ||
left: 516px; | ||
width: 77px; | ||
height: 140px; | ||
} | ||
.snap { | ||
top: 56px; | ||
left: 30px; | ||
width: 77px; | ||
height: 140px; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { useState, useEffect, useImperativeHandle } from "react"; | ||
import generateRandomPuzzle from "./generateRandom.js"; | ||
import { generatePiece, generateAnswer, checkPuzzle } from "./utils.js"; | ||
import PuzzlePiece from "./PuzzlePiece.jsx"; | ||
import style from "./style.module.css"; | ||
import car1x from "./assets/[email protected]"; | ||
import car2x from "./assets/[email protected]"; | ||
import panContainer1x from "./assets/[email protected]"; | ||
import panContainer2x from "./assets/[email protected]"; | ||
import pan from "./assets/pan.svg"; | ||
|
||
// ─│┌┐┘└ | ||
|
||
function Puzzle({ interactCallback, $ref }) { | ||
const [answer, setAnswer] = useState( | ||
generateAnswer(` | ||
─┐. | ||
.│. | ||
.└─`), | ||
); | ||
const [piece, setPiece] = useState( | ||
generatePiece(` | ||
│┘─ | ||
─││ | ||
│┘│`), | ||
); | ||
|
||
function reset() { | ||
const [randAnswer, randPiece] = generateRandomPuzzle(); | ||
setAnswer(randAnswer); | ||
setPiece(randPiece); | ||
} | ||
|
||
useEffect(reset, []); | ||
useImperativeHandle($ref, () => ({ reset }), []); | ||
|
||
const isCorrect = checkPuzzle(piece, answer); | ||
|
||
return ( | ||
<div className="relative flex flex-col md:flex-row gap-8 md:gap-0"> | ||
<div className="flex items-center h-28 -translate-x-16 md:translate-x-0"> | ||
<img | ||
className="object-right w-72 h-28 object-scale-down" | ||
width="279" | ||
height="100" | ||
src={car1x} | ||
srcSet={`${car1x} 1x, ${car2x} 2x`} | ||
alt="start position" | ||
/> | ||
<div className="w-8 h-2 bg-blue-300"></div> | ||
<svg className="block md:hidden stroke-blue-300 w-12 h-28 absolute right-px overflow-visible fill-none"> | ||
<path | ||
d="M 0 56 C 50 56 50 120 0 120 H -270 C -290 120 -300 130 -300 150 V 170 C -300 190 -290 200 -270 200 H -255" | ||
strokeWidth="8" | ||
/> | ||
</svg> | ||
</div> | ||
<div className="grid grid-rows-3 grid-cols-3 gap-4 z-10 w-[23rem] flex-shrink-0"> | ||
{piece.map((shape, i) => { | ||
const onClick = () => { | ||
setPiece((board) => { | ||
const newBoard = [...board]; | ||
newBoard[i] = board[i].rotated(); | ||
return newBoard; | ||
}); | ||
interactCallback?.(); | ||
}; | ||
const fixRotate = () => { | ||
setPiece((board) => { | ||
const newBoard = [...board]; | ||
newBoard[i] = board[i].fixedRotated(); | ||
return newBoard; | ||
}); | ||
}; | ||
|
||
return ( | ||
<PuzzlePiece | ||
shape={shape} | ||
key={`puzzle-${i}`} | ||
onClick={onClick} | ||
fixRotate={fixRotate} | ||
/> | ||
); | ||
})} | ||
</div> | ||
<div className="flex items-end absolute bottom-0 -right-28 md:relative md:bottom-auto md:right-auto"> | ||
<div className="w-28 h-28 flex items-center relative"> | ||
<svg className="stroke-blue-300 w-12 h-28 overflow-visible fill-none"> | ||
<path | ||
d="M 0 56 H 32 C 44 56 44 76 32 76 H 24 C 12 76 12 96 24 96 H 56" | ||
strokeWidth="8" | ||
/> | ||
</svg> | ||
<img | ||
className="object-left w-16 h-28 object-scale-down" | ||
width="80" | ||
height="130" | ||
src={panContainer1x} | ||
srcSet={`${panContainer1x} 1x, ${panContainer2x} 2x`} | ||
alt="start position" | ||
/> | ||
<img | ||
className={`absolute right-2 top-3 ${isCorrect ? style.rotate : ""}`} | ||
src={pan} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default Puzzle; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { useState } from "react"; | ||
import { LINEAR } from "./constants.js"; | ||
|
||
function PuzzlePiece({ shape, onClick, fixRotate }) { | ||
const [fixing, setFixing] = useState(false); | ||
const style = { | ||
transform: `rotate( ${shape.rotate * 90}deg)`, | ||
}; | ||
|
||
return ( | ||
<div | ||
style={style} | ||
className={`size-28 bg-black rounded-xl border-2 border-white transition-transform ease-out ${fixing ? "duration-0" : "duration-500"}`} | ||
onClick={() => { | ||
onClick(); | ||
setFixing(false); | ||
}} | ||
onTransitionEnd={() => { | ||
if (shape.rotate < 4) return; | ||
setFixing(true); | ||
fixRotate(); | ||
}} | ||
> | ||
<svg | ||
className="size-full stroke-blue-300 fill-transparent" | ||
version="1.1" | ||
xmlns="http://www.w3.org/2000/svg" | ||
> | ||
<path | ||
d={shape.type === LINEAR ? "M 0 54 H 108" : "M108 54 H 54 V 108"} | ||
strokeWidth="8" | ||
/> | ||
</svg> | ||
</div> | ||
); | ||
} | ||
|
||
export default PuzzlePiece; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export const LINEAR = Symbol("linear"); | ||
export const CURVED = Symbol("curved"); | ||
export const ANY = Symbol("any"); |
Oops, something went wrong.