Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,404 changes: 36 additions & 1,368 deletions frontend/package-lock.json

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"@vitejs/plugin-react": "^4.5.0",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^5.2.2",
"vite": "^5.2.0"
},
Expand Down
1 change: 0 additions & 1 deletion frontend/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
2 changes: 1 addition & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { LoginModal } from './components/modals/LoginModal';

const App: React.FC = () => {
return (
<main className="flex justify-center items-center h-screen">
<main className="app-shell">
<Chessrepeat />
<LoginModal />
</main>
Expand Down
244 changes: 134 additions & 110 deletions frontend/src/Chessrepeat.tsx

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions frontend/src/components/Chessground.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* Wrapper chessground mounts into. It must be square and fill its column;
the board graphic itself is painted by the theme (.blue cg-board). */
.chessground-wrap {
width: 100%;
aspect-ratio: 1;
background-size: cover;
background-position: center;
}
27 changes: 13 additions & 14 deletions frontend/src/components/Chessground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ import { Chessground as NativeChessground } from 'chessground';
import type { Api } from 'chessground/api';
import type { Config } from 'chessground/config';
import { useEffect, useRef, useState } from 'react';
import './Chessground.css';

export function Chessground(props: Config & { setBoardFen?: (fen: string) => void }) {
export function Chessground(
props: Config & {
setBoardFen?: (fen: string) => void;
/* Where chessground publishes ---cg-width/---cg-height. It rounds the
board down to a whole number of 8 device pixels, so anything that has
to line up with the board's real edge (the progress bar) needs those
vars on a shared ancestor, not on the board wrapper itself. */
dimensionsRef?: React.RefObject<HTMLElement | null>;
},
) {
console.log("PROPS", props);
const [api, setApi] = useState<Api | null>(null);
const ref = useRef<HTMLDivElement>(null);
Expand All @@ -24,7 +34,7 @@ export function Chessground(props: Config & { setBoardFen?: (fen: string) => voi
} else {
const chessgroundApi = NativeChessground(ref.current, {
...props,
addDimensionsCssVarsTo: ref.current,
addDimensionsCssVarsTo: props.dimensionsRef?.current ?? ref.current,
events: {
change: () => {
if (props.setBoardFen && chessgroundApi) {
Expand Down Expand Up @@ -56,16 +66,5 @@ export function Chessground(props: Config & { setBoardFen?: (fen: string) => voi
});
}, [api, props]);

return (
<div
className="merida blue"
style={{
aspectRatio: 1,
width: '100%',
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
ref={ref}
/>
);
return <div className="chessground-wrap merida blue" ref={ref} />;
}
8 changes: 8 additions & 0 deletions frontend/src/components/CommentBox.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.comment-box {
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
background: var(--c-blue-50);
box-shadow: inset 0 0 0 1px var(--c-blue-200);
font-size: 0.875rem;
color: var(--c-brand-blue);
}
5 changes: 2 additions & 3 deletions frontend/src/components/CommentBox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useTrainerStore } from '../store/state';
import './CommentBox.css';

export const CommentBox = () => {
const selectedNode = useTrainerStore((s) => s.selectedNode);
Expand All @@ -7,8 +8,6 @@ export const CommentBox = () => {
if (!comment) return null;

return (
<div className="px-3 py-2 text-sm text-brand-blue bg-blue-50 rounded-md ring-1 ring-blue-200">
{comment}
</div>
<div className="comment-box">{comment}</div>
);
};
6 changes: 6 additions & 0 deletions frontend/src/components/Debug.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/* Dev-only readout of the training context; not rendered in normal use. */
.debug-panel {
position: absolute;
bottom: 0;
padding: 3.75rem;
}
3 changes: 2 additions & 1 deletion frontend/src/components/Debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { parseSan } from 'chessops/san';
import { useTrainerStore } from '../store/state';
import { Chess } from 'chessops';
import { INITIAL_BOARD_FEN, parseFen } from 'chessops/fen';
import './Debug.css';

export const Debug: React.FC = () => {
const trainingPath = useTrainerStore.getState().trainableContext?.startingPath;
Expand All @@ -16,7 +17,7 @@ export const Debug: React.FC = () => {
if (!chapter) return undefined;
console.log(targetMove?.data?.fen);
return (
<div className="absolute bottom-0 p-15">
<div className="debug-panel">
<div>{`chapter ${chapter.unseenCount}`}</div>
<div>{`TrainingPath ${trainingPath}`}</div>
<div>{`selectedPath ${selectedPath}`}</div>
Expand Down
166 changes: 72 additions & 94 deletions frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SiDiscord, SiGithub } from 'react-icons/si';
import { BookOpen, Bug, Globe, LogIn, LogOut, User } from 'lucide-react';
import { LogIn, LogOut, User } from 'lucide-react';
import { useAuthStore } from '../store/auth';
import { useTrainerStore, type Peer } from '../store/state';

Expand All @@ -8,10 +8,9 @@ interface Props {
// on Chessrepeat; Login omits this.
connectedUsers?: Peer[];
incomingCollaboratorsCount?: number;
onOpenCollaborators?: () => void;
}

export function Header({ connectedUsers, incomingCollaboratorsCount = 0, onOpenCollaborators }: Props) {
export function Header({ connectedUsers, incomingCollaboratorsCount = 0 }: Props) {
const authUser = useAuthStore((s) => s.user);
const clearAuth = useAuthStore((s) => s.clearAuth);
const openLogin = useAuthStore((s) => s.openLogin);
Expand All @@ -28,85 +27,13 @@ export function Header({ connectedUsers, incomingCollaboratorsCount = 0, onOpenC
<span className="accent">repeat</span>
</div>

<a
href="https://discord.gg/xhjra9W6Bh"
target="_blank"
rel="noopener noreferrer"
title="Join our Discord"
className="header-link"
>
<span>join discord</span>
<SiDiscord />
</a>

<a
href="https://github.com/jacokyle01/chessrepeat"
target="_blank"
rel="noopener noreferrer"
title="View on GitHub"
className="header-link"
>
<span>view github</span>
<SiGithub />
</a>

<a
href="https://github.com/jacokyle01/chessrepeat#readme"
target="_blank"
rel="noopener noreferrer"
title="Read the docs"
className="header-link"
>
<span>docs</span>
<BookOpen />
</a>

<a
href="mailto:jacokyle01@gmail.com?subject=Bug Report | chessrepeat"
title="Report a Bug"
className="header-link"
>
<span>report bug</span>
<Bug />
</a>

{authUser && onOpenCollaborators && (
<button
type="button"
onClick={onOpenCollaborators}
title="Collaborators"
className="header-link relative"
>
<span>collaborators</span>
<Globe />
</button>
)}

<div className="header-actions">
{peers.length > 0 && (
<div className="header-peers">
{peers.map((u) => {
// train collaborators get a light-blue ring; everyone else
// (owner, edit) gets dark blue. Owner appears as a peer
// only when a collaborator is viewing — same color as edit
// since they have full access too.
const ring = u.permission === 'train' ? 'ring-brand-blue-light' : 'ring-brand-blue';
return (
<img
key={u.username}
src={u.picture}
alt={u.username}
title={`${u.username} (${u.permission})`}
referrerPolicy="no-referrer"
className={`header-peer ring-2 ${ring}`}
/>
);
})}
</div>
)}

{/* Identity sits next to the wordmark; the links are pushed right. Who
you are and the control that changes it live in one recessed track,
the same treatment the peer avatars and the board's segmented
controls get. */}
<div className="header-auth">
{authUser ? (
<>
<div className="header-identity">
<span className="header-user">
{authUser.picture ? (
<img
Expand All @@ -116,36 +43,87 @@ export function Header({ connectedUsers, incomingCollaboratorsCount = 0, onOpenC
className="header-avatar"
/>
) : (
<User className="h-5 w-5" />
<User />
)}
<span className="text-sm">{authUser.username ?? 'Unnamed'}</span>
<span className="header-username">{authUser.username ?? 'Unnamed'}</span>
</span>
<button
type="button"
onClick={() => {
clearAuth();
//TODO we don't need to reload but this make it easy ..
//TODO we don't need to reload but this make it easy ..
location.reload();
}}
title="Sign out"
className="header-link"
>
<LogOut />
</button>
</>
</div>
) : (
showSignIn && (
<button
type="button"
onClick={openLogin}
className="header-link header-signin"
title="Sign in"
>
<span>sign in</span>
<LogIn />
</button>
<div className="header-identity">
<button
type="button"
onClick={openLogin}
className="header-link header-signin"
title="Sign in"
>
<span>sign in</span>
<LogIn />
</button>
</div>
)
)}

{/* Live view: just the faces of the people on this repertoire now.
Sits with the identity on the left rather than in the right-hand
link cluster — it's about who's here, not somewhere to go. */}
{peers.length > 0 && (
<div className="header-peers" title="Here now">
{peers.map((u) => {
// train collaborators get a light-blue ring; everyone else
// (owner, edit) gets dark blue. Owner appears as a peer
// only when a collaborator is viewing — same color as edit
// since they have full access too.
const ring = u.permission === 'train' ? 'header-peer-train' : 'header-peer-edit';
return (
<img
key={u.username}
src={u.picture}
alt={u.username}
title={`${u.username} (${u.permission})`}
referrerPolicy="no-referrer"
className={`header-peer ${ring}`}
/>
);
})}
</div>
)}
</div>

<div className="header-actions">
<a
href="https://discord.gg/xhjra9W6Bh"
target="_blank"
rel="noopener noreferrer"
title="Join our Discord"
className="header-link"
>
<span>join discord</span>
<SiDiscord />
</a>

<a
href="https://github.com/jacokyle01/chessrepeat"
target="_blank"
rel="noopener noreferrer"
title="View on GitHub"
className="header-link"
>
<span>view github</span>
<SiGithub />
</a>
</div>
</div>
);
Expand Down
Loading