Skip to content

Commit

Permalink
generic leveldropdown component
Browse files Browse the repository at this point in the history
  • Loading branch information
sspenst committed Dec 17, 2023
1 parent a505ef8 commit 8f73bdf
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 96 deletions.
14 changes: 7 additions & 7 deletions components/cards/selectCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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 SaveToCollectionModal from '../modal/saveToCollectionModal';
import StyledTooltip from '../page/styledTooltip';
import { PlayLaterToggleButton } from './playLaterToggleButton';
import styles from './SelectCard.module.css';
Expand All @@ -18,7 +18,7 @@ interface SelectCardProps {

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

useEffect(() => {
Expand All @@ -29,7 +29,7 @@ export default function SelectCard({ option, prefetch }: SelectCardProps) {

const color = option.disabled ? 'var(--bg-color-4)' :
option.stats?.getColor('var(--color)') ?? 'var(--color)';
const tooltipId = `save-level-to-${option.id}`;
const tooltipId = `save-to-collection-${option.id}`;

return (
<div
Expand Down Expand Up @@ -110,16 +110,16 @@ export default function SelectCard({ option, prefetch }: SelectCardProps) {
className='absolute bottom-2 right-2 select-card-button'
data-tooltip-content='Save Level To...'
data-tooltip-id={tooltipId}
onClick={() => setIsSaveLevelToModalOpen(true)}
onClick={() => setIsSaveToCollectionModalOpen(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>
<StyledTooltip id={tooltipId} />
<SaveLevelToModal
closeModal={() => setIsSaveLevelToModalOpen(false)}
isOpen={isSaveLevelToModalOpen}
<SaveToCollectionModal
closeModal={() => setIsSaveToCollectionModalOpen(false)}
isOpen={isSaveToCollectionModalOpen}
level={option.level}
/>
</>}
Expand Down
57 changes: 32 additions & 25 deletions components/cards/selectCard2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import getPngDataClient from '../../helpers/getPngDataClient';
import SelectOption from '../../models/selectOption';
import FormattedDifficulty from '../formatted/formattedDifficulty';
import FormattedUser from '../formatted/formattedUser';
import SaveLevelToModal from '../modal/saveLevelToModal';
import LevelDropdown from '../level/info/levelDropdown';
import SaveToCollectionModal from '../modal/saveToCollectionModal';
import ProfileAvatar from '../profile/profileAvatar';
import { PlayLaterToggleButton } from './playLaterToggleButton';
import styles from './SelectCard.module.css';
Expand All @@ -19,7 +20,7 @@ interface SelectCard2Props {
}

export default function SelectCard2({ option }: SelectCard2Props) {
const [isSaveLevelToModalOpen, setIsSaveLevelToModalOpen] = useState(false);
const [isSaveToCollectionModalOpen, setIsSaveToCollectionModalOpen] = useState(false);

const color = option.disabled ? 'var(--bg-color-4)' :
option.stats?.getColor('var(--color)') ?? 'var(--color)';
Expand Down Expand Up @@ -58,31 +59,37 @@ export default function SelectCard2({ option }: SelectCard2Props) {
</div>
}
</div>
<div className='flex gap-3'>
<Link className='h-fit' href={getProfileSlug(user)} passHref>
<ProfileAvatar user={user} />
</Link>
<div className='flex flex-col gap-0.5 overflow-hidden'>
<span className='font-bold overflow-hidden' style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
}}>
{option.level?.name}
</span>
<FormattedUser className='font-medium text-sm gray' hideAvatar id='author' size={Dimensions.AvatarSizeSmall} user={user} />
{/* {!option.hideStats && option.stats && <div className='italic text-xs'>{option.stats.getText()} steps</div>} */}
<div className='flex text-xs items-center gap-1 pt-0.5'>
<FormattedDifficulty
difficultyEstimate={option.level.calc_difficulty_estimate}
id={option.id}
uniqueUsers={option.level.calc_playattempts_unique_users_count !== undefined ?
option.level.calc_playattempts_unique_users_count :
option.level.calc_playattempts_unique_users.length}
/>
{/* <span>-</span> */}
<div className='flex justify-between'>
<div className='flex gap-3'>
<Link className='h-fit' href={getProfileSlug(user)} passHref>
<ProfileAvatar user={user} />
</Link>
<div className='flex flex-col gap-0.5 overflow-hidden'>
<span className='font-bold overflow-hidden' style={{
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
}}>
{option.level?.name}
</span>
<FormattedUser className='font-medium text-sm gray' hideAvatar id='author' size={Dimensions.AvatarSizeSmall} user={user} />
{/* {!option.hideStats && option.stats && <div className='italic text-xs'>{option.stats.getText()} steps</div>} */}
<div className='flex text-xs items-center gap-1 pt-0.5'>
<FormattedDifficulty
difficultyEstimate={option.level.calc_difficulty_estimate}
id={option.id}
uniqueUsers={option.level.calc_playattempts_unique_users_count !== undefined ?
option.level.calc_playattempts_unique_users_count :
option.level.calc_playattempts_unique_users.length}
/>
{/* <span>-</span> */}
</div>
</div>
</div>
{/* prevent clicking parent level link */}
<div onClick={e => e.preventDefault()}>
<LevelDropdown level={option.level} />
</div>
</div>
</Link>
);
Expand Down
4 changes: 2 additions & 2 deletions components/level/gameWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export default function GameWrapper({ chapter, collection, level, onNext, onPrev
</div>
{collection &&
<div className={classNames(
'hidden flex-col items-center border-l border-color-4 w-60',
'hidden flex-col items-center border-l border-color-3 w-60',
// NB: we want to keep this component in the DOM when it is hidden by the user
// this allows updating the collection level list on level change to continue running behind the scenes
{ 'xl:flex': !isCollectionViewHidden },
Expand All @@ -237,7 +237,7 @@ export default function GameWrapper({ chapter, collection, level, onNext, onPrev
{getCollectionLevelList('sidebar')}
</div>
}
<div className='hidden xl:flex flex-col border-l border-color-4 break-words z-10 h-full w-100'>
<div className='hidden xl:flex flex-col border-l border-color-3 break-words z-10 h-full w-100'>
{collection && isCollectionViewHidden &&
<button
className='flex items-center gap-4 w-full border-b border-color-4 hover-bg-3 transition px-4 py-3'
Expand Down
153 changes: 120 additions & 33 deletions components/level/info/levelDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Menu, Transition } from '@headlessui/react';
import ArchiveLevelModal from '@root/components/modal/archiveLevelModal';
import EditLevelModal from '@root/components/modal/editLevelModal';
import SaveLevelToModal from '@root/components/modal/saveLevelToModal';
import SaveToCollectionModal from '@root/components/modal/saveToCollectionModal';
import UnpublishLevelModal from '@root/components/modal/unpublishLevelModal';
import { AppContext } from '@root/contexts/appContext';
import { PageContext } from '@root/contexts/pageContext';
import isCurator from '@root/helpers/isCurator';
import Level from '@root/models/db/level';
import classNames from 'classnames';
import Link from 'next/link';
import React, { Fragment, useContext, useState } from 'react';
import toast from 'react-hot-toast';

Expand All @@ -18,13 +19,71 @@ interface LevelDropdownProps {
export default function LevelDropdown({ level }: LevelDropdownProps) {
const [isArchiveLevelOpen, setIsArchiveLevelOpen] = useState(false);
const [isEditLevelOpen, setIsEditLevelOpen] = useState(false);
const [isSaveLevelToOpen, setIsSaveLevelToOpen] = useState(false);
const [isPlayLaterLoading, setIsPlayLaterLoading] = useState(false);
const [isSaveToCollectionOpen, setIsSaveToCollectionOpen] = useState(false);
const [isUnpublishLevelOpen, setIsUnpublishLevelOpen] = useState(false);
const { mutatePlayLater, playLater, user } = useContext(AppContext);
const { setPreventKeyDownEvent } = useContext(PageContext);
const { user } = useContext(AppContext);

const canEdit = level.userId._id === user?._id || isCurator(user);
const isNotAuthor = level.userId._id !== user?._id;
const boldedLevelName = <span className='font-bold'>{level.name}</span>;
const isInPlayLater = !!(playLater && playLater[level._id.toString()]);

const fetchPlayLater = async (remove: boolean) => {
if (!user) {
return;
}

setIsPlayLaterLoading(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={() => fetchPlayLater(!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',
});
}

setIsPlayLaterLoading(false);
};

return (<>
<Menu as='div' className='relative'>
Expand All @@ -44,12 +103,61 @@ export default function LevelDropdown({ level }: LevelDropdownProps) {
>
<Menu.Items className='absolute right-0 m-1 w-fit origin-top-right rounded-[10px] shadow-lg border z-20 bg-1 border-color-3'>
<div className='px-1 py-1'>
{user && <>
<Menu.Item>
{({ active }) => (
<button
className='flex w-full items-center rounded-md cursor-pointer px-3 py-2 gap-3 whitespace-nowrap'
disabled={isPlayLaterLoading}
onClick={() => fetchPlayLater(isInPlayLater)}
style={{
backgroundColor: active ? 'var(--bg-color-3)' : undefined,
}}
>
{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>
<span>Remove from Play Later</span>
</>
:
<>
<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>
<span>Add to Play Later</span>
</>
}
</button>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<div
className='flex w-full items-center rounded-md cursor-pointer px-3 py-2 gap-3 whitespace-nowrap'
onClick={() => {
setIsSaveToCollectionOpen(true);
setPreventKeyDownEvent(true);
}}
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='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>
<span>Save to collection</span>
</div>
)}
</Menu.Item>
</>}
<Menu.Item>
{({ active }) => (
<div
className='flex w-full items-center rounded-md cursor-pointer px-3 py-2 gap-3 whitespace-nowrap'
onClick={() => {
navigator.clipboard.writeText(window.location.href);
navigator.clipboard.writeText(`${window.location.origin}/level/${level.slug}`);
toast.dismiss();
toast.success('Link copied to clipboard');
}}
Expand All @@ -60,7 +168,7 @@ export default function LevelDropdown({ level }: LevelDropdownProps) {
<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='M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244' />
</svg>
Copy link
<span>Copy link</span>
</div>
)}
</Menu.Item>
Expand All @@ -80,31 +188,10 @@ export default function LevelDropdown({ level }: LevelDropdownProps) {
<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='M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5A3.375 3.375 0 006.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0015 2.25h-1.5a2.251 2.251 0 00-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 00-9-9z' />
</svg>
Copy level data
<span>Copy level data</span>
</div>
)}
</Menu.Item>
{user &&
<Menu.Item>
{({ active }) => (
<div
className='flex w-full items-center rounded-md cursor-pointer px-3 py-2 gap-3 whitespace-nowrap'
onClick={() => {
setIsSaveLevelToOpen(true);
setPreventKeyDownEvent(true);
}}
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='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>
Save
</div>
)}
</Menu.Item>
}
{canEdit && <>
<Menu.Item>
{({ active }) => (
Expand All @@ -121,7 +208,7 @@ export default function LevelDropdown({ level }: LevelDropdownProps) {
<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='M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487zm0 0L19.5 7.125' />
</svg>
Edit
<span>Edit</span>
</div>
)}
</Menu.Item>
Expand All @@ -140,7 +227,7 @@ export default function LevelDropdown({ level }: LevelDropdownProps) {
<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='M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z' />
</svg>
Archive
<span>Archive</span>
</div>
)}
</Menu.Item>
Expand All @@ -159,7 +246,7 @@ export default function LevelDropdown({ level }: LevelDropdownProps) {
<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='M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0' />
</svg>
Unpublish
<span>Unpublish</span>
</div>
)}
</Menu.Item>
Expand All @@ -169,12 +256,12 @@ export default function LevelDropdown({ level }: LevelDropdownProps) {
</Transition>
</Menu>
{user &&
<SaveLevelToModal
<SaveToCollectionModal
closeModal={() => {
setIsSaveLevelToOpen(false);
setIsSaveToCollectionOpen(false);
setPreventKeyDownEvent(false);
}}
isOpen={isSaveLevelToOpen}
isOpen={isSaveToCollectionOpen}
level={level}
/>
}
Expand Down
Loading

0 comments on commit 8f73bdf

Please sign in to comment.