diff --git a/src/features/ui/components/episode-list-item/episode-list-item.module.css b/src/features/ui/components/episode-list-item/episode-list-item.module.css new file mode 100644 index 0000000..8501ad8 --- /dev/null +++ b/src/features/ui/components/episode-list-item/episode-list-item.module.css @@ -0,0 +1,19 @@ +.container { + display: flex; + background-color: white; + flex-direction: column; + padding: 1rem; + border-radius: 0.5rem; + text-align: start; + appearance: none; + border: none; + cursor: pointer; +} + +.name { + font-weight: bold; +} + +.description { + font-size: 1rem; +} diff --git a/src/features/ui/components/episode-list-item/episode-list-item.tsx b/src/features/ui/components/episode-list-item/episode-list-item.tsx new file mode 100644 index 0000000..dea17d9 --- /dev/null +++ b/src/features/ui/components/episode-list-item/episode-list-item.tsx @@ -0,0 +1,17 @@ +import { EpisodeRecord } from '../../../editor/types'; +import { useGameState } from '../../../game/hooks/use-game-state'; +import $$ from './episode-list-item.module.css'; + +export const EpisodeListItem = ({ levels, name, symbols }: EpisodeRecord) => { + const { setEpisode } = useGameState(); + + return ( + + ); +}; diff --git a/src/features/ui/components/episode-list-item/index.ts b/src/features/ui/components/episode-list-item/index.ts new file mode 100644 index 0000000..ed78850 --- /dev/null +++ b/src/features/ui/components/episode-list-item/index.ts @@ -0,0 +1 @@ +export * from './episode-list-item'; diff --git a/src/features/ui/components/episodes-view/episodes-view.module.css b/src/features/ui/components/episodes-view/episodes-view.module.css new file mode 100644 index 0000000..c7890a8 --- /dev/null +++ b/src/features/ui/components/episodes-view/episodes-view.module.css @@ -0,0 +1,12 @@ +.container { + overflow-y: scroll; + overflow-x: hidden; + max-height: 100%; +} + +.episodes { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 2rem; +} diff --git a/src/features/ui/components/episodes-view/episodes-view.tsx b/src/features/ui/components/episodes-view/episodes-view.tsx new file mode 100644 index 0000000..4c74568 --- /dev/null +++ b/src/features/ui/components/episodes-view/episodes-view.tsx @@ -0,0 +1,24 @@ +import { useEpisodes } from '../../../editor/hooks/use-episodes'; +import { EpisodeListItem } from '../episode-list-item'; +import $$ from './episodes-view.module.css'; + +export const EpisodesView = () => { + const { episodes, isLoading } = useEpisodes(); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+
+ {episodes.map((episode) => ( + + ))} +
+
+ ); +}; diff --git a/src/features/ui/components/episodes-view/index.ts b/src/features/ui/components/episodes-view/index.ts new file mode 100644 index 0000000..dc9f5dc --- /dev/null +++ b/src/features/ui/components/episodes-view/index.ts @@ -0,0 +1 @@ +export * from './episodes-view'; diff --git a/src/features/ui/components/game-view/game-view.tsx b/src/features/ui/components/game-view/game-view.tsx new file mode 100644 index 0000000..cd8a47a --- /dev/null +++ b/src/features/ui/components/game-view/game-view.tsx @@ -0,0 +1,27 @@ +import { useMemo } from 'react'; + +import { getIdEntities } from '../../../editor/utils/get-id-entities'; +import { useControls } from '../../../game/hooks/use-controls'; +import { useGame } from '../../../game/hooks/use-game'; +import { useGameState } from '../../../game/hooks/use-game-state'; +import { BoardView } from '../board-view'; +import { SwipeArea } from '../swipe-area'; + +export const GameView = () => { + const { level } = useGameState(); + + const levelEntities = useMemo(() => getIdEntities(level), [level]); + + const { entities, setEntities } = useGame({ + level: { entities: levelEntities, id: '' }, + }); + + const { swipeProps } = useControls({ setEntities }); + + return ( + <> + + + + ); +}; diff --git a/src/features/ui/components/game-view/index.ts b/src/features/ui/components/game-view/index.ts new file mode 100644 index 0000000..23a8a8f --- /dev/null +++ b/src/features/ui/components/game-view/index.ts @@ -0,0 +1 @@ +export * from './game-view'; diff --git a/src/features/ui/components/level-list-item/index.ts b/src/features/ui/components/level-list-item/index.ts new file mode 100644 index 0000000..158a5fa --- /dev/null +++ b/src/features/ui/components/level-list-item/index.ts @@ -0,0 +1 @@ +export * from './level-list-item'; diff --git a/src/features/ui/components/level-list-item/level-list-item.module.css b/src/features/ui/components/level-list-item/level-list-item.module.css new file mode 100644 index 0000000..eebb507 --- /dev/null +++ b/src/features/ui/components/level-list-item/level-list-item.module.css @@ -0,0 +1,28 @@ +.container { + display: flex; + border-radius: 1rem; + background-color: white; + appearance: none; + border: none; + cursor: pointer; + flex-direction: column; + align-items: center; + padding: 0; + overflow: hidden; + z-index: 1; + outline: none; +} + +.info { + padding: 1rem; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + gap: 0.5rem; +} + +.name { + font-weight: bold; + white-space: nowrap; +} diff --git a/src/features/ui/components/level-list-item/level-list-item.tsx b/src/features/ui/components/level-list-item/level-list-item.tsx new file mode 100644 index 0000000..62ac76b --- /dev/null +++ b/src/features/ui/components/level-list-item/level-list-item.tsx @@ -0,0 +1,72 @@ +import { memo } from 'react'; + +import { MAX_GRID_HEIGHT, MAX_GRID_WIDTH } from '../../../editor/constants'; +import { LevelRecord } from '../../../editor/types'; +import { getIdEntities } from '../../../editor/utils/get-id-entities'; +import { getBounds } from '../../../engine/utils/get-bounds'; +import { useGameState } from '../../../game/hooks/use-game-state'; +import { TILE_SIZE } from '../../constants'; +import { EntityView } from '../entity-view'; +import $$ from './level-list-item.module.css'; + +export const LevelListItem = ({ + level: { id, name, steps }, +}: { + level: LevelRecord; +}) => { + const { setLevel } = useGameState(); + + return ( + + ); +}; + +const SCALE = 0.5; + +const Preview = memo(({ id }: { id: string }) => { + const entities = getIdEntities(id); + const { bottomRight, topLeft } = getBounds({ entities }); + const width = bottomRight.x - topLeft.x; + const height = bottomRight.y - topLeft.y; + + return ( +
+
+				{entities.map((entity) => (
+					
+				))}
+			
+
+ ); +}); +Preview.displayName = 'Preview'; diff --git a/src/features/ui/components/levels-view/index.ts b/src/features/ui/components/levels-view/index.ts new file mode 100644 index 0000000..21c4775 --- /dev/null +++ b/src/features/ui/components/levels-view/index.ts @@ -0,0 +1 @@ +export * from './levels-view'; diff --git a/src/features/ui/components/levels-view/levels-view.module.css b/src/features/ui/components/levels-view/levels-view.module.css new file mode 100644 index 0000000..e7b0178 --- /dev/null +++ b/src/features/ui/components/levels-view/levels-view.module.css @@ -0,0 +1,13 @@ +.container { + overflow-x: hidden; + overflow-y: scroll; + max-height: 100%; +} + +.levels { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 1rem; + padding: 1rem; +} diff --git a/src/features/ui/components/levels-view/levels-view.tsx b/src/features/ui/components/levels-view/levels-view.tsx new file mode 100644 index 0000000..09ded45 --- /dev/null +++ b/src/features/ui/components/levels-view/levels-view.tsx @@ -0,0 +1,25 @@ +import { useEpisodeLevels } from '../../../editor/hooks/use-episode-levels'; +import { getParsedLevelRecord } from '../../../editor/utils/get-parsed-level-record'; +import { LevelListItem } from '../level-list-item'; +import $$ from './levels-view.module.css'; + +export const LevelsView = () => { + const { isLoading, levels } = useEpisodeLevels(); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+
+ {levels.map((level) => ( + + ))} +
+
+ ); +}; diff --git a/src/game.tsx b/src/game.tsx index 4564cb7..9c28eb9 100644 --- a/src/game.tsx +++ b/src/game.tsx @@ -1,42 +1,22 @@ import './global.css'; -import { useEpisodes } from './features/editor/hooks/use-episodes'; -import { VECTOR_LEFT } from './features/engine/constants'; -import { Entity } from './features/engine/types/entities'; -import { createEntity } from './features/engine/utils/create-entity'; -import { useControls } from './features/game/hooks/use-controls'; -import { useGame } from './features/game/hooks/use-game'; -import { BoardView } from './features/ui/components/board-view'; -import { SwipeArea } from './features/ui/components/swipe-area'; - -const level: Entity[] = [ - createEntity('floor', { position: { x: 2, y: 0 } }), - createEntity('floor', { position: { x: 2, y: 1 } }), - createEntity('floor', { position: { x: 1, y: 0 } }), - createEntity('floor', { position: { x: 1, y: 1 } }), - createEntity('floor', { direction: VECTOR_LEFT, position: { x: 2, y: 2 } }), - createEntity('floor', { position: { x: 1, y: 2 } }), - createEntity('floor', { position: { x: 0, y: 2 }, target: 1 }), - createEntity('dice', { position: { x: 2, y: 0 }, value: 0 }), - createEntity('dice', { position: { x: 1, y: 1 } }), -]; +import { useGameState } from './features/game/hooks/use-game-state'; +import { EpisodesView } from './features/ui/components/episodes-view'; +import { GameView } from './features/ui/components/game-view'; +import { LevelsView } from './features/ui/components/levels-view'; const Game = () => { - const { entities, setEntities } = useGame({ - level: { entities: level, id: '' }, - }); + const { episode, level } = useGameState(); - const { swipeProps } = useControls({ setEntities }); + if (!episode) { + return ; + } - const { episodes } = useEpisodes(); + if (!level) { + return ; + } - return ( - <> -
{JSON.stringify(episodes, null, 2)}
- - - - ); + return <>{Boolean(level) && }; }; export default Game; diff --git a/src/global.css b/src/global.css index 345ed34..0eef387 100644 --- a/src/global.css +++ b/src/global.css @@ -1,6 +1,6 @@ #root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; + height: 100vh; + width: 100%; + overflow: hidden; + position: relative; }