Skip to content

Commit

Permalink
feat: 동영상 연결 및 슬로프 운행 현황 추가 (#6)
Browse files Browse the repository at this point in the history
* feat: 리스트 클릭시 색상 변경 및 취소가능하도록 추가

* refactor: map 분리

* refactor: camera 컴포넌트 분리

* remove: 사용하지 않는 파일 제거

* fix: 타입문제 해결

* feat: 동영상 플레이어 추가

* feat: 비디오 열릴시 다른 요소와의 상호작용 막음

* fix: 확대 상태에서 비디오를 켤시 비디오가 확대상태인 문제 해결

* fix: 핀치 및 드래그시 가장자리 벗어나지 못하도록 사용성 개선

* feat: slop 상태 페이지 생성

* feat: 슬로프 운행 현황 리스트 퍼블리싱

* feat: 전역 폰트 적용

* fix: 디자인 퍼블리싱 수정

* feat: 슬로프 운행 현황 추가

* design: 테이블 상단 마진 추가

* fix: 코드리뷰 반영
  • Loading branch information
Yoon-Hae-Min authored Aug 14, 2024
1 parent c3c41c5 commit b490f1d
Show file tree
Hide file tree
Showing 28 changed files with 482 additions and 197 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"react-hls-player": "^3.0.7",
"tailwind-scrollbar-hide": "^1.1.7",
"tailwindcss-animate": "^1.0.7",
"ts-pattern": "^5.2.0",
Expand Down
12 changes: 11 additions & 1 deletion src/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import url('https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/static/pretendard-dynamic-subset.min.css');

@tailwind base;
@tailwind components;
@tailwind utilities;
Expand Down Expand Up @@ -91,11 +93,19 @@
}
html,
body {
@apply font-sans;
touch-action: none;
}
body.video-active * {
pointer-events: none;
}

body.video-active .video-close {
pointer-events: auto;
}
}

@layer typography {
@layer base {
/* Headings */
.h1 {
@apply text-3xl font-bold leading-[1.6rem] tracking-[0.02rem];
Expand Down
5 changes: 1 addition & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import '@/app/globals.css';

import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import Providers from './_providers';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
title: 'WeSki',
description: 'We Ski',
Expand All @@ -14,7 +11,7 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>
<body>
<Providers>{children}</Providers>
</body>
</html>
Expand Down
1 change: 0 additions & 1 deletion src/app/mobile/congestion/page.tsx

This file was deleted.

1 change: 1 addition & 0 deletions src/app/mobile/slop-status/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@/pages/slop-status/ui/slop-status-page';
15 changes: 0 additions & 15 deletions src/app/mobile/weather/layout.tsx

This file was deleted.

26 changes: 26 additions & 0 deletions src/entities/slop/model/jisan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export const JISAN: ResortInfo = {
Element: Lemon1LiftPath,
webcam: null,
isOpen: false,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
{
id: 'lemon1-1-lift',
Expand All @@ -27,6 +30,9 @@ export const JISAN: ResortInfo = {
Element: Lemon1Sub1LiftPath,
webcam: null,
isOpen: false,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
{
id: 'orange2-lift',
Expand All @@ -40,8 +46,12 @@ export const JISAN: ResortInfo = {
top: 'top-[69%]',
left: 'left-[29%]',
},
src: 'http://konjiam.live.cdn.cloudn.co.kr/konjiam/cam01.stream/playlist.m3u8',
},
isOpen: true,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
{
id: 'orange3-lift',
Expand All @@ -55,8 +65,12 @@ export const JISAN: ResortInfo = {
top: 'top-[64%]',
left: 'left-[38%]',
},
src: 'http://konjiam.live.cdn.cloudn.co.kr/konjiam/cam01.stream/playlist.m3u8',
},
isOpen: true,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
{
id: 'new-orange-lift',
Expand All @@ -65,6 +79,9 @@ export const JISAN: ResortInfo = {
Element: NewOrangeLiftPath,
webcam: null,
isOpen: false,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
{
id: 'blue-lift',
Expand All @@ -80,6 +97,9 @@ export const JISAN: ResortInfo = {
},
},
isOpen: true,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
{
id: 'silver6-lift',
Expand All @@ -88,6 +108,9 @@ export const JISAN: ResortInfo = {
Element: Silver6LiftPath,
webcam: null,
isOpen: false,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
{
id: 'silver7-lift',
Expand All @@ -96,6 +119,9 @@ export const JISAN: ResortInfo = {
Element: Silver7LiftPath,
webcam: null,
isOpen: false,
isDayOpen: false,
isNightOpen: false,
isLateNightOpen: false,
},
],
};
4 changes: 4 additions & 0 deletions src/entities/slop/model/model.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export type ResortInfo = {
name: string;
Element: React.FC;
isOpen: boolean;
isDayOpen: boolean;
isNightOpen: boolean;
isLateNightOpen: boolean;

webcam: {
id: string;
name: string;
Expand Down
8 changes: 5 additions & 3 deletions src/entities/slop/ui/level-chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,20 @@ const LEVEL: Record<Level, { text: string; color: string }> = {
};

interface LevelChipProps {
className?: string;
level: Level;
}

const LevelChip = ({ level }: LevelChipProps) => {
const LevelChip = ({ level, className }: LevelChipProps) => {
return (
<div
className={cn(
'flex h-[25px] w-[56px] items-center justify-center rounded-[6px] border-[1px] border-white border-opacity-10',
LEVEL[level].color
LEVEL[level].color,
className
)}
>
<p className="body1 text-white">{LEVEL[level].text}</p>
<p className="body1-semibold text-white">{LEVEL[level].text}</p>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@ const useMapPinch = (containerRef: RefObject<HTMLElement>) => {
},
onDrag: ({ pinching, cancel, offset: [x, y] }) => {
if (pinching) return cancel();
api.start({ x, y });
},
onDragEnd: () => {
const [boundedX, boundedY] = getBoundedPositions(

const [{ min: minX, max: maxX }, { min: minY, max: maxY }] = getBoundedPositions(
{
x: style.x.get(),
y: style.y.get(),
Expand All @@ -46,6 +44,10 @@ const useMapPinch = (containerRef: RefObject<HTMLElement>) => {
height: containerRef.current!.getBoundingClientRect().height,
}
);

const boundedX = Math.min(Math.max(x, minX), maxX);
const boundedY = Math.min(Math.max(y, minY), maxY);

api.start({ x: boundedX, y: boundedY });
},
},
Expand Down
46 changes: 46 additions & 0 deletions src/features/slop/ui/slop-camera.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useState } from 'react';
import { createPortal } from 'react-dom';
import { cn } from '@/shared/lib';
import CameraButton from '@/shared/ui/cam-button';
import { Tooltip } from '@/shared/ui/tooltip';
import SlopVideo from './slop-video';

interface SlopWebcamProps {
id: string;
name: string;
position: {
top: string;
left: string;
};
videoSrc?: string;
isOpen: boolean;
renderTarget: React.RefObject<HTMLElement>;
}

const SlopCamera = ({ name, position, isOpen, videoSrc, renderTarget }: SlopWebcamProps) => {
const [isVideoOpen, setIsVideoOpen] = useState(false);

const toggleVideo = () => {
setIsVideoOpen((pre) => !pre);
};

return (
<>
<div className={cn('absolute z-10', position.top, position.left)}>
<div className={cn('relative')}>
<Tooltip trigger={<CameraButton />} isOpen={isOpen}>
<p className={cn('body3-medium')} onClick={toggleVideo}>
{name}
</p>
</Tooltip>
</div>
</div>
{renderTarget?.current &&
isVideoOpen &&
videoSrc &&
createPortal(<SlopVideo src={videoSrc} closeVideo={toggleVideo} />, renderTarget.current)}
</>
);
};

export default SlopCamera;
54 changes: 54 additions & 0 deletions src/features/slop/ui/slop-map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { animated } from '@react-spring/web';
import type { StaticImageData } from 'next/image';
import Image from 'next/image';
import type { ComponentType } from 'react';
import React from 'react';
import type { Level } from '@/entities/slop/model/model';
import { cn } from '@/shared/lib';
import useMapPinch from '../hooks/useMapPinch';

interface SlopMapProps {
containerRef: React.RefObject<HTMLElement>;
children?: React.ReactNode;
mapSrc: StaticImageData;

slops: {
id: string;
level: Level;
Element: ComponentType<{
color?: string;
}>;
}[];
selectedSlop: string | null;
}

const SlopMap = ({ containerRef, children, mapSrc, slops, selectedSlop }: SlopMapProps) => {
const { ref, style } = useMapPinch(containerRef);

return (
<animated.div
ref={ref}
style={{
touchAction: 'none',
display: 'inline-block',
width: '100%',
height: '100%',
...style,
}}
>
<Image src={mapSrc} alt="이미지" width={420} height={750} />

{slops.map((slop) => (
<div key={slop.id} className={cn('absolute top-0 w-full')}>
<slop.Element
color={selectedSlop !== slop.id && selectedSlop !== null ? 'fill-gray-40' : undefined}
/>
</div>
))}

{children}
</animated.div>
);
};

export default SlopMap;
Loading

0 comments on commit b490f1d

Please sign in to comment.