2023-09-24 19:07:16 +02:00
|
|
|
import { designs, tags, techniques } from 'shared/config/designs.mjs'
|
2023-11-01 13:42:03 +01:00
|
|
|
import { DesignCard, DesignLink, ns as designNs } from 'shared/components/designs/design.mjs'
|
2023-05-05 19:56:51 +02:00
|
|
|
import { useTranslation } from 'next-i18next'
|
2023-11-01 13:42:03 +01:00
|
|
|
import { useState } from 'react'
|
|
|
|
import { FilterIcon, ShowcaseIcon, CisFemaleIcon, ResetIcon } from 'shared/components/icons.mjs'
|
2023-09-24 19:07:16 +02:00
|
|
|
import { useAtom } from 'jotai'
|
|
|
|
import { atomWithHash } from 'jotai-location'
|
2023-10-02 16:00:31 +02:00
|
|
|
import { capitalize, objUpdate } from 'shared/utils.mjs'
|
2023-09-24 19:07:16 +02:00
|
|
|
import { Difficulty } from 'shared/components/designs/difficulty.mjs'
|
2023-11-01 13:42:03 +01:00
|
|
|
import { collection } from 'site/hooks/use-design.mjs'
|
2023-10-02 16:00:31 +02:00
|
|
|
|
2023-05-01 18:27:06 +02:00
|
|
|
export const ns = designNs
|
|
|
|
|
2023-09-24 19:07:16 +02:00
|
|
|
const filterAtom = atomWithHash('filter', { example: true })
|
|
|
|
|
|
|
|
export const useFilter = () => {
|
|
|
|
return useAtom(filterAtom)
|
|
|
|
}
|
|
|
|
|
2023-10-02 16:00:31 +02:00
|
|
|
export const DesignPicker = ({ linkTo = 'new', altLinkTo = 'docs' }) => {
|
2023-05-05 19:56:51 +02:00
|
|
|
const { t } = useTranslation('designs')
|
2023-09-24 19:07:16 +02:00
|
|
|
const [filter, setFilter] = useFilter()
|
2023-11-01 13:42:03 +01:00
|
|
|
const [showFilters, setShowFilters] = useState(false)
|
2023-05-01 18:27:06 +02:00
|
|
|
|
2023-05-05 19:56:51 +02:00
|
|
|
// Need to sort designs by their translated title
|
2023-09-24 19:07:16 +02:00
|
|
|
// let's also apply the filters while we're at it
|
2023-05-05 19:56:51 +02:00
|
|
|
const translated = {}
|
2023-11-01 13:42:03 +01:00
|
|
|
for (const d of collection) {
|
2023-09-24 19:07:16 +02:00
|
|
|
let skip = false
|
|
|
|
if (
|
|
|
|
filter.tag &&
|
|
|
|
filter.tag.filter((tag) => designs[d].tags.includes(tag)).length < filter.tag.length
|
|
|
|
)
|
|
|
|
skip = true
|
|
|
|
if (
|
|
|
|
filter.tech &&
|
|
|
|
filter.tech.filter((tech) => designs[d].techniques.includes(tech)).length < filter.tech.length
|
|
|
|
)
|
|
|
|
skip = true
|
|
|
|
if (filter.difficulty && filter.difficulty !== designs[d].difficulty) skip = true
|
|
|
|
if (!skip) translated[t(`${d}.t`)] = d
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateFilter = (path, val) => {
|
|
|
|
const newFilter = objUpdate({ ...filter }, path, val)
|
|
|
|
setFilter(newFilter)
|
|
|
|
}
|
|
|
|
|
|
|
|
const toggle = (type, val) => {
|
|
|
|
const current = filter[type] || []
|
|
|
|
const newSet = new Set(current)
|
|
|
|
if (newSet.has(val)) newSet.delete(val)
|
|
|
|
else newSet.add(val)
|
|
|
|
updateFilter(type, [...newSet])
|
|
|
|
}
|
2023-05-05 19:56:51 +02:00
|
|
|
|
|
|
|
return (
|
2023-09-24 19:07:16 +02:00
|
|
|
<>
|
|
|
|
<div className="max-w-7xl m-auto">
|
2023-10-02 16:00:31 +02:00
|
|
|
<div className="flex flex-row flex-wrap gap-1 justify-center font-medium">
|
|
|
|
{Object.values(translated)
|
|
|
|
.sort()
|
|
|
|
.map((d) => (
|
|
|
|
<DesignLink key={d} linkTo={linkTo} altLinkTo={altLinkTo} name={capitalize(d)} />
|
|
|
|
))}
|
|
|
|
</div>
|
2023-11-01 13:42:03 +01:00
|
|
|
{showFilters ? (
|
|
|
|
<>
|
|
|
|
<h6 className="text-center mb-0 mt-4">
|
|
|
|
Filters ({Object.keys(translated).length}/{Object.keys(designs).length})
|
|
|
|
</h6>
|
|
|
|
<div className="flex flex-row gap-1 items-center justify-center flex-wrap my-2">
|
|
|
|
<b>{t('tags:tags')}:</b>
|
|
|
|
{tags.map((tag) => (
|
|
|
|
<button
|
|
|
|
key={tag}
|
|
|
|
className={`badge font-medium hover:shadow
|
|
|
|
${
|
|
|
|
filter?.tag && Array.isArray(filter.tag) && filter.tag.includes(tag)
|
|
|
|
? 'badge badge-success hover:badge-error'
|
|
|
|
: 'badge badge-primary hover:badge-success'
|
|
|
|
}`}
|
|
|
|
onClick={() => toggle('tag', tag)}
|
|
|
|
>
|
|
|
|
{t(`tags:${tag}`)}
|
|
|
|
</button>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
<div className="flex flex-row gap-1 items-center justify-center flex-wrap my-4">
|
|
|
|
<b>{t('techniques:techniques')}:</b>
|
|
|
|
{techniques.map((tech) => (
|
|
|
|
<button
|
|
|
|
key={tech}
|
|
|
|
className={`badge font-medium hover:shadow
|
|
|
|
${
|
|
|
|
filter?.tech && Array.isArray(filter.tech) && filter.tech.includes(tech)
|
|
|
|
? 'badge badge-success hover:badge-error'
|
|
|
|
: 'badge badge-accent hover:badge-success'
|
|
|
|
}`}
|
|
|
|
onClick={() => toggle('tech', tech)}
|
|
|
|
>
|
|
|
|
{t(`techniques:${tech}`)}
|
|
|
|
</button>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
<div className="flex flex-row gap-2 items-center justify-center flex-wrap my-4">
|
|
|
|
<b>{t('tags:difficulty')}:</b>
|
|
|
|
{[1, 2, 3, 4, 5].map((score) => (
|
|
|
|
<button
|
|
|
|
onClick={() => updateFilter('difficulty', score)}
|
|
|
|
key={score}
|
|
|
|
className={`btn btn-sm ${
|
|
|
|
filter.difficulty === score ? 'btn-secondary btn-outline' : 'btn-ghost'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<Difficulty score={score} />
|
|
|
|
</button>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
<div className="flex flex-row gap-4 items-center justify-center flex-wrap my-2">
|
|
|
|
<button
|
|
|
|
className="btn btn-secondary btn-outline"
|
|
|
|
onClick={() => updateFilter('example', !filter.example)}
|
|
|
|
>
|
|
|
|
{filter.example ? <CisFemaleIcon /> : <ShowcaseIcon />}
|
|
|
|
{filter.example ? t('tags:showLineDrawings') : t('tags:showExamples')}
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
className="btn btn-secondary btn-outline"
|
|
|
|
onClick={() => setFilter({ example: 1 })}
|
|
|
|
>
|
|
|
|
<ResetIcon />
|
|
|
|
{t('tags:clearFilter')}
|
|
|
|
</button>
|
|
|
|
<button
|
|
|
|
className="btn btn-secondary btn-outline"
|
|
|
|
onClick={() => setShowFilters(false)}
|
|
|
|
>
|
|
|
|
<FilterIcon />
|
|
|
|
{t('tags:hideFilters')}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : (
|
|
|
|
<div className="flex flex-row gap-4 items-center justify-center flex-wrap my-2">
|
2023-09-24 19:07:16 +02:00
|
|
|
<button
|
2023-11-01 13:42:03 +01:00
|
|
|
className="btn btn-secondary btn-outline"
|
|
|
|
onClick={() => updateFilter('example', !filter.example)}
|
2023-09-24 19:07:16 +02:00
|
|
|
>
|
2023-11-01 13:42:03 +01:00
|
|
|
{filter.example ? <CisFemaleIcon /> : <ShowcaseIcon />}
|
|
|
|
{filter.example ? t('tags:showLineDrawings') : t('tags:showExamples')}
|
2023-09-24 19:07:16 +02:00
|
|
|
</button>
|
2023-11-01 13:42:03 +01:00
|
|
|
<button className="btn btn-secondary btn-outline" onClick={() => setShowFilters(true)}>
|
|
|
|
<FilterIcon />
|
|
|
|
{t('tags:showFilters')}
|
2023-09-24 19:07:16 +02:00
|
|
|
</button>
|
2023-11-01 13:42:03 +01:00
|
|
|
</div>
|
|
|
|
)}
|
2023-09-24 19:07:16 +02:00
|
|
|
</div>
|
2023-11-01 13:42:03 +01:00
|
|
|
<div className="grid grid-cols-2 gap-2 mt-4 justify-center sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-8 mb-8">
|
2023-09-24 19:07:16 +02:00
|
|
|
{Object.keys(translated)
|
|
|
|
.sort()
|
|
|
|
.map((d) => (
|
2023-11-01 13:42:03 +01:00
|
|
|
<DesignCard
|
2023-09-24 19:07:16 +02:00
|
|
|
name={translated[d]}
|
|
|
|
key={d}
|
2023-10-02 16:00:31 +02:00
|
|
|
linkTo={linkTo}
|
|
|
|
altLinkTo={altLinkTo}
|
2023-09-24 19:07:16 +02:00
|
|
|
lineDrawing={filter.example ? false : true}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</>
|
2023-05-05 19:56:51 +02:00
|
|
|
)
|
2023-05-01 18:27:06 +02:00
|
|
|
}
|