Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(wip) Playlist #1025

Merged
merged 89 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
c259947
Limit playlist length and check if level is already in list
k2xl Oct 20, 2023
bd85632
add remove from playlist functionality
k2xl Oct 20, 2023
a592e87
updateOne
k2xl Oct 20, 2023
addf221
cleanup
k2xl Oct 20, 2023
39acdcc
populate flag
k2xl Oct 20, 2023
abbe0fb
tooltip
k2xl Oct 21, 2023
db2c12b
play later rename
k2xl Oct 21, 2023
aff93ce
handing off to sspenst
k2xl Oct 21, 2023
4fa4f65
useSWR instead of fetch for the getReviews and getRecords
k2xl Oct 21, 2023
ef6c2a2
Merge branch 'main' into playlist
k2xl Oct 21, 2023
2a44b67
unrelated fix for formatted user level undefinede
k2xl Oct 21, 2023
6c973f3
play later api
k2xl Oct 21, 2023
6c4968b
fix from renames
k2xl Oct 21, 2023
8da2920
private and reserve play-later slug
k2xl Oct 22, 2023
f2a5f7e
Private updates
k2xl Oct 22, 2023
95a4af2
show hide list
k2xl Oct 22, 2023
6fb6690
formatting
k2xl Oct 22, 2023
1217750
left scrollbar
k2xl Oct 22, 2023
2bb1f3b
auto smooth scroll to current level
k2xl Oct 22, 2023
5613540
Fix title of collection
k2xl Oct 22, 2023
1cf2185
play later link broken fix
k2xl Oct 22, 2023
4d34d47
fix hide show
k2xl Oct 22, 2023
e45e433
copy tweaks
k2xl Oct 22, 2023
42b7509
fix build
k2xl Oct 22, 2023
9de8723
add test
k2xl Oct 22, 2023
0584ee9
collections can now be marked private
k2xl Oct 22, 2023
7a4d91e
allowing creating of private levels on create
k2xl Oct 22, 2023
9c913db
update collection tests for private check
k2xl Oct 22, 2023
f2161f1
cleanup
k2xl Oct 22, 2023
c6bad48
more tests and cleanup
k2xl Oct 22, 2023
392c021
100% coverage for play-later
k2xl Oct 22, 2023
4adf81c
optimize play-attempt.test and format it. combine promise.alls.
k2xl Oct 22, 2023
1987d92
test for reserved slug name
k2xl Oct 22, 2023
2cb551f
update tests some more
k2xl Oct 22, 2023
6959e08
more tests
k2xl Oct 22, 2023
27d2b7f
Merge branch 'main' into playlist
sspenst Oct 22, 2023
a8cdaef
private to isPrivate
k2xl Oct 23, 2023
9b33e0d
fix test
k2xl Oct 23, 2023
2e561ed
improve coverage of naturalsort
k2xl Oct 23, 2023
94c05b4
Handle PlayLater type in collection update. Allow making it private/p…
k2xl Oct 23, 2023
606ab96
[unrelated to PR] add test for api/user/[id]/index
k2xl Oct 23, 2023
c5dc301
fix test
k2xl Oct 25, 2023
528180e
gate play later behind pro
k2xl Oct 26, 2023
5f34e62
fix tests
k2xl Oct 26, 2023
92b704f
add to play later from search
k2xl Oct 26, 2023
17e23b7
cleanup
k2xl Oct 26, 2023
fec24ea
pass stats in to selectoption
k2xl Oct 26, 2023
54a0fba
See play collection directly from search results
k2xl Oct 26, 2023
363032f
use session storage
k2xl Oct 26, 2023
ee9c801
cleanup
k2xl Oct 26, 2023
908adf4
Merge branch 'main' into playlist
k2xl Oct 28, 2023
0fa2662
doh
k2xl Oct 28, 2023
166f066
search query in title link
k2xl Oct 28, 2023
94fa4dd
fix play later link
k2xl Oct 28, 2023
f8ed421
include more in level_search_default_projection
k2xl Oct 28, 2023
8f96d72
add data only in search rather than generically
k2xl Oct 28, 2023
52c8982
3 dot
k2xl Oct 28, 2023
d980555
cleanup
k2xl Oct 28, 2023
a5ed89c
edit level modal when no collection show a bit more info about what a…
k2xl Oct 28, 2023
29a574b
move <Solved/> to top right
k2xl Oct 29, 2023
455a844
move play later to top
k2xl Oct 29, 2023
a4fbc17
bring to top
k2xl Oct 29, 2023
0e68823
helper file
k2xl Oct 29, 2023
598278b
toasterror undo
k2xl Oct 29, 2023
84f3e75
mobile view for list of collections
k2xl Oct 29, 2023
adbe51e
comment
k2xl Oct 29, 2023
93bc945
add test. cleanup. also settimeout on the scroll only for mobile
k2xl Oct 30, 2023
e92016d
select card ux updates
sspenst Oct 31, 2023
f6808f4
Merge branch 'main' into playlist
sspenst Oct 31, 2023
f9503d4
ux updates
sspenst Oct 31, 2023
f6407da
fix bringToTop
sspenst Oct 31, 2023
cff9bb3
models/constants
sspenst Oct 31, 2023
7c6eb49
working on collection UX
sspenst Oct 31, 2023
3e68b59
naturalSort custom compare function
sspenst Oct 31, 2023
b9df0f3
refactor level title out of gameLayout
sspenst Oct 31, 2023
23ef8f1
tweaking
sspenst Oct 31, 2023
4424848
more efficient collection fetching
sspenst Nov 1, 2023
21ebd7a
tempCollection tweaks
sspenst Nov 1, 2023
4b3e6ed
Add one click undo for play later
k2xl Nov 1, 2023
84f52e6
fix auto scroll to level in modal
k2xl Nov 1, 2023
19a4e2d
userAgent now helps set defaults for useDeviceCheck
k2xl Nov 1, 2023
babb7c8
prevent relayout when collection is still loading
k2xl Nov 1, 2023
628f0a7
show + and - buttons hover effect only for desktop
k2xl Nov 1, 2023
49edf2f
fix tests
k2xl Nov 1, 2023
b0996db
fixes
sspenst Nov 1, 2023
6de4ae7
more fixes
sspenst Nov 2, 2023
dc12926
we are getting close
sspenst Nov 2, 2023
88af08e
fix apis and tests
sspenst Nov 2, 2023
9ca9390
lint
sspenst Nov 2, 2023
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
2 changes: 1 addition & 1 deletion components/buttons/filterButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, { useContext } from 'react';
import { toast } from 'react-hot-toast';

interface FilterButtonProps {
element: JSX.Element | string;
element: React.ReactNode;
first?: boolean;
last?: boolean;
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
Expand Down
2 changes: 1 addition & 1 deletion components/cards/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import StyledTooltip from '../page/styledTooltip';
interface CardProps {
children: React.ReactNode;
id: string;
title: JSX.Element | string;
title: React.ReactNode;
tooltip?: string;
}

Expand Down
103 changes: 103 additions & 0 deletions components/cards/playLaterToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { AppContext } from '@root/contexts/appContext';
import isPro from '@root/helpers/isPro';
import { EnrichedLevel } from '@root/models/db/level';
import Link from 'next/link';
import React, { useContext, useState } from 'react';
import toast from 'react-hot-toast';
import StyledTooltip from '../page/styledTooltip';

interface PlayLaterToggleButtonProps {
className?: string;
level: EnrichedLevel;
}

export function PlayLaterToggleButton({ className, level }: PlayLaterToggleButtonProps) {
const { mutatePlayLater, playLater, user } = useContext(AppContext);
const isInPlayLater = !!(playLater && playLater[level._id.toString()]);
const [isLoading, setIsLoading] = useState(false);

if (!user || !isPro(user) || !playLater) {
return null;
}

const boldedLevelName = <span className='font-bold'>{level.name}</span>;
const fetchFunc = async (remove: boolean) => {
setIsLoading(true);
toast.dismiss();
toast.loading(remove ? 'Removing...' : 'Adding...', {
position: 'bottom-center',
});

const res = await fetch('/api/play-later/', {
method: remove ? 'DELETE' : 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: level._id.toString(),
}),
});

toast.dismiss();

if (res.ok) {
const message = (
<div className='flex flex-col items-center w-92 max-w-full text-center'>
<span>{remove ? ['Removed ', boldedLevelName, ' from'] : ['Added ', boldedLevelName, ' to']} <Link className='underline' href={`/collection/${user.name}/play-later`}>Play Later</Link></span>
<button className='text-sm underline' onClick={() => fetchFunc(!remove)}>Undo</button>
</div>
);

toast.success(message, {
duration: 5000,
position: 'bottom-center',
icon: remove ? '➖' : '➕',
});
mutatePlayLater();
} else {
let resp;

try {
resp = await res.json();
} catch (e) {
console.error(e);
}

toast.error(resp?.error || 'Could not update Play Later', {
duration: 5000,
position: 'bottom-center',
});
}

setIsLoading(false);
};

const id = `play-later-btn-tooltip-${level._id.toString()}`;

return <>
<button
className={className}
data-tooltip-content={isInPlayLater ? 'Remove from Play Later' : 'Add to Play Later'}
data-tooltip-id={id}
disabled={isLoading}
onClick={async (e) => {
e.preventDefault();
fetchFunc(isInPlayLater);
}}
style={{
color: 'var(--color)',
}}
>
{isInPlayLater ?
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' strokeWidth={1.5} stroke='currentColor' className='w-6 h-6'>
<path strokeLinecap='round' strokeLinejoin='round' d='M19.5 12h-15' />
</svg>
:
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' strokeWidth={1.5} stroke='currentColor' className='w-6 h-6'>
<path strokeLinecap='round' strokeLinejoin='round' d='M12 4.5v15m7.5-7.5h-15' />
</svg>
}
</button>
<StyledTooltip id={id} />
</>;
}
26 changes: 22 additions & 4 deletions components/cards/selectCard.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { AppContext } from '@root/contexts/appContext';
import classNames from 'classnames';
import Link from 'next/link';
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import Dimensions from '../../constants/dimensions';
import getPngDataClient from '../../helpers/getPngDataClient';
import SelectOption from '../../models/selectOption';
import SaveLevelToModal from '../modal/saveLevelToModal';
import { PlayLaterToggleButton } from './playLaterToggleButton';
import styles from './SelectCard.module.css';
import SelectCardContent from './selectCardContent';

Expand All @@ -14,6 +17,8 @@ interface SelectCardProps {

export default function SelectCard({ option, prefetch }: SelectCardProps) {
const [backgroundImage, setBackgroundImage] = useState<string>();
const [isSaveLevelToModalOpen, setIsSaveLevelToModalOpen] = useState(false);
const { user } = useContext(AppContext);

useEffect(() => {
if (option.level) {
Expand All @@ -26,10 +31,10 @@ export default function SelectCard({ option, prefetch }: SelectCardProps) {

return (
<div
className='p-3 overflow-hidden relative inline-block align-middle'
className='p-3 overflow-hidden relative inline-block align-middle select-card max-w-full'
key={`select-card-${option.id}`}
>
<div className='wrapper rounded-md overflow-hidden relative'
<div className='wrapper rounded-md overflow-hidden relative max-w-full'
style={{
height: option.height ?? Dimensions.OptionHeight,
width: option.width ?? Dimensions.OptionWidth,
Expand All @@ -48,7 +53,7 @@ export default function SelectCard({ option, prefetch }: SelectCardProps) {
{option.href ?
<Link
className={classNames(
'border-2 rounded-md items-center flex justify-center text-center',
'border-2 rounded-md items-center flex justify-center text-center max-w-full',
!option.disabled ? styles['card-border'] : undefined,
{ 'text-xl': !option.stats },
)}
Expand Down Expand Up @@ -86,6 +91,19 @@ export default function SelectCard({ option, prefetch }: SelectCardProps) {
<SelectCardContent option={option} />
</button>
}
{option.level && user && <>
<PlayLaterToggleButton className='absolute bottom-2 left-2 h-6 select-card-button' level={option.level} />
<button className='absolute bottom-2 right-2 select-card-button' onClick={() => setIsSaveLevelToModalOpen(true)}>
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' strokeWidth={1.5} stroke='currentColor' className='w-6 h-6' style={{ minWidth: 24, minHeight: 24 }}>
<path strokeLinecap='round' strokeLinejoin='round' d='M12 10.5v6m3-3H9m4.06-7.19l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z' />
</svg>
</button>
<SaveLevelToModal
closeModal={() => setIsSaveLevelToModalOpen(false)}
isOpen={isSaveLevelToModalOpen}
level={option.level}
/>
</>}
</div>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions components/cards/selectCardContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface SelectCardContentProps {
export default function SelectCardContent({ option }: SelectCardContentProps) {
return (<>
<div
className='font-bold break-words p-2'
className='font-bold break-words p-2 max-w-full'
style={{
width: option.width ?? Dimensions.OptionWidth,
}}
Expand All @@ -33,11 +33,11 @@ export default function SelectCardContent({ option }: SelectCardContentProps) {
/>
</div>
}
{option.stats && <div className='pt-1 italic'>{option.stats.getText()}</div>}
{!option.hideStats && option.stats && <div className='pt-1 italic'>{option.stats.getText()}</div>}
</div>
</div>
{option.stats?.isSolved() &&
<div className='absolute bottom-0 right-0'>
<div className='absolute top-0 right-0'>
<Solved />
</div>
}
Expand Down
2 changes: 1 addition & 1 deletion components/formatted/formattedDifficulty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export default function FormattedDifficulty({ difficultyEstimate, id, uniqueUser
return (
<div className='flex justify-center difficultyText'>
<div data-tooltip-id={`difficulty-${id}`} data-tooltip-content={tooltip}>
<span className='text-md pr-1'>{difficulty.emoji}</span>
<span className='pr-1'>{difficulty.emoji}</span>
<span className='italic pr-1' style={{
color: color,
textShadow: '1px 1px black',
Expand Down
77 changes: 56 additions & 21 deletions components/header/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import DismissToast from '../toasts/dismissToast';
import MusicIcon from './musicIcon';

export default function Dropdown() {
const { forceUpdate, mutateUser, setShouldAttemptAuth, user, userLoading } = useContext(AppContext);
const { forceUpdate, mutateUser, playLater, setShouldAttemptAuth, user, userLoading } = useContext(AppContext);
const [isMusicModalOpen, setIsMusicModalOpen] = useState(false);
const [isThemeOpen, setIsThemeOpen] = useState(false);
const router = useRouter();
Expand Down Expand Up @@ -60,6 +60,10 @@ export default function Dropdown() {

const isLoggedIn = !userLoading && user;

function Divider() {
return <div className='opacity-30 m-1 h-px bg-4' />;
}

return (<>
<Menu>
<Menu.Button id='dropdownMenuBtn' aria-label='dropdown menu'>
Expand Down Expand Up @@ -100,15 +104,10 @@ export default function Dropdown() {
<span className='font-bold'>{user.score}</span>
<StyledTooltip id='levels-solved-dropdown' />
</div>
<div
className='opacity-30 m-1 h-px'
style={{
backgroundColor: 'var(--bg-color-4)',
}}
/>
<Divider />
</div>
}
{isLoggedIn && !isPro(user) &&
{isLoggedIn && !isPro(user) && <>
<Menu.Item>
{({ active }) => (
<Link href='/settings/pro' passHref>
Expand All @@ -124,7 +123,8 @@ export default function Dropdown() {
</Link>
)}
</Menu.Item>
}
<Divider />
</>}
<div className='block sm:hidden'>
<Menu.Item>
{({ active }) => (
Expand Down Expand Up @@ -162,6 +162,51 @@ export default function Dropdown() {
</Link>
)}
</Menu.Item>
{isPro(user) &&
<Menu.Item>
{({ active }) => (
<Link
href={`/collection/${user.name}/play-later`}
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
if (!playLater || Object.keys(playLater).length === 0) {
toast.success('Add a level to your Play Later collection first!', { icon: '⚠️', duration: 3000 });
e.preventDefault();
}
}}
>
<div
className='flex w-full items-center rounded-md cursor-pointer px-3 py-2 gap-3'
style={{
backgroundColor: active ? 'var(--bg-color-3)' : undefined,
}}
>
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' className='w-5 h-5' viewBox='0 0 16 16'>
<path d='M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z' />
<path d='M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z' />
</svg>
Play Later
</div>
</Link>
)}
</Menu.Item>
}
<Menu.Item>
{({ active }) => (
<Link href={`${getProfileSlug(user)}/collections`} passHref>
<div
className='flex w-full items-center rounded-md cursor-pointer px-3 py-2 gap-3'
style={{
backgroundColor: active ? 'var(--bg-color-3)' : undefined,
}}
>
<svg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' strokeWidth={1.5} stroke='currentColor' className='w-5 h-5'>
<path strokeLinecap='round' strokeLinejoin='round' d='M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z' />
</svg>
Collections
</div>
</Link>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<Link href={getProfileSlug(user)} passHref>
Expand All @@ -179,12 +224,7 @@ export default function Dropdown() {
</Link>
)}
</Menu.Item>
<div
className='opacity-30 m-1 h-px'
style={{
backgroundColor: 'var(--bg-color-4)',
}}
/>
<Divider />
</>}
<Menu.Item>
{({ active }) => (
Expand Down Expand Up @@ -243,12 +283,7 @@ export default function Dropdown() {
</Link>
)}
</Menu.Item>
<div
className='opacity-30 m-1 h-px'
style={{
backgroundColor: 'var(--bg-color-4)',
}}
/>
<Divider />
<Menu.Item>
{({ active }) => (
<div
Expand Down
2 changes: 1 addition & 1 deletion components/homepage/recommendedLevel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface RecommendedLevelProps {
id: string;
level?: EnrichedLevel | null;
onClick?: (option: SelectOption) => void;
title: JSX.Element | string;
title: React.ReactNode;
tooltip?: string;
}

Expand Down
2 changes: 1 addition & 1 deletion components/homepage/tutorial.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ export default function Tutorial() {
}}>
Now <Link href='/signup' className='font-bold text-blue-500 hover:text-blue-400'>sign up</Link> for free to explore the world of Pathology!
</div>
<div className='text-md fadeIn' style={{
<div className='fadeIn' style={{
pointerEvents: 'all',
animationDelay: '2.5s'
}}>
Expand Down
Loading
Loading