// Dependencies import { atomWithHash } from 'jotai-location' import { about, collection, tags, techniques, designers, developers, examples, measurements, requiredMeasurements, optionalMeasurements, } from '@freesewing/collection' import { capitalize, linkClasses, mutateObject } from '@freesewing/utils' import { measurements as measurementsTranslations } from '@freesewing/i18n' // Context import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' import { ModalContext } from '@freesewing/react/context/Modal' // Hooks import React, { useState, useContext, Fragment } from 'react' import { useAtom } from 'jotai' // Components import { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link' import { CircleIcon, CisFemaleIcon, DocsIcon, FilterIcon, HeartIcon, NewPatternIcon, ResetIcon, ShowcaseIcon, } from '@freesewing/react/components/Icon' import { lineDrawingsFront as lineDrawings, lineDrawingsBack, } from '@freesewing/react/components/LineDrawing' import { IconButton } from '@freesewing/react/components/Button' import { ModalWrapper } from '@freesewing/react/components/Modal' import { KeyVal } from '@freesewing/react/components/KeyVal' import { MissingLinedrawing } from '../LineDrawing/missing.mjs' const filterAtom = atomWithHash('filter', { example: true }) export const useFilter = () => { return useAtom(filterAtom) } /** * React component to show the FreeSewing collection and pick a design * * @param {object} props - All React props * @param {function} Link - An optional framework specific Link component for client-side routing * @param {bool} editor - Set this to when loaded in the editor (this will make the display more dense) * @param {bool} onClick - Set this to trigger an onClick event, rather than using links */ export const Collection = ({ Link = false, linkTo = 'about', editor = false, onClick = false }) => { if (!Link) Link = WebLink // State const [filter, setFilter] = useFilter() const [showFilters, setShowFilters] = useState(false) /* * Apply filter */ const filtered = {} for (const d of collection) { let skip = false if ( filter.tag && filter.tag.filter((tag) => about[d].tags.includes(tag)).length < filter.tag.length ) skip = true if ( filter.tech && filter.tech.filter((tech) => about[d].techniques.includes(tech)).length < filter.tech.length ) skip = true if (filter.difficulty && filter.difficulty !== about[d].difficulty) skip = true if (!skip) filtered[d] = d } const updateFilter = (path, val) => { // Allow clicking the same difficulty to remove it as a filter if (path === 'difficulty' && val === filter.difficulty) val = 'unset' const newFilter = mutateObject({ ...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]) } return ( <>
{Object.keys(filtered) .sort() .map((d) => onClick ? ( ) : ( {d} ) )}
{showFilters ? ( <>
Filters ({Object.keys(filtered).length}/{collection.length})
Tags: {tags.map((tag) => ( ))}
Techniques {techniques.sort().map((tech) => ( ))}
Difficulty: {[1, 2, 3, 4, 5].map((score) => ( ))}
) : (
)}
{Object.keys(filtered) .sort() .map((d) => ( ))}
) } /* * A helper component to show a design technique * * @param {object} props - All React props * @param {function} props.Link - A Link component, typically specific to the framework for client-side routing * @param {string} props.technique - The technique name/id */ const Technique = ({ Link = WebLink, technique }) => ( {technique} ) /* * A helper component to show a design tag * * @param {object} props - All React props * @param {function} props.Link - A Link component, typically specific to the framework for client-side routing * @param {string} props.tag - The tag name/id */ const Tag = ({ Link = WebLink, technique }) => ( {tag} ) const DesignCard = ({ name, lineDrawing = false, linkTo, Link, onClick }) => { if (!Link) Link = WebLink const LineDrawing = lineDrawing && lineDrawings[name] ? lineDrawings[name] : ({ className }) =>
const exampleImageUrl = examples.href[name] ? examples.href[name] : noExample const bg = { aspectRatio: '1/1.4' } if (!lineDrawing) { bg.backgroundImage = `url(${exampleImageUrl}` bg.backgroundSize = 'cover' bg.backgroundPosition = 'center center' } const inner = (
{about[name].name}
{lineDrawing ? (
) : ( )}
) return onClick ? ( ) : ( {inner} ) } /* * A helper component to show difficulety of a design * * @param {object} props - All React props * @param {number} props.score - The difficulty score of the design (1-5) */ const Difficulty = ({ score = 0, className = '' }) => (
{[0, 1, 2, 3, 4].map((i) => ( ))}
) const linkBuilders = { new: (design) => `/editor/#s={%22design%22%3A%22${design.toLowerCase()}%22%2C%22view%22%3A%22draft%22}`, docs: (design) => `/docs/designs/${design.toLowerCase()}/`, about: (design) => `/designs/${design.toLowerCase()}/`, } const noExample = 'https://images.pexels.com/photos/5626595/pexels-photo-5626595.jpeg?cs=srgb&fm=jpg&w=640&h=427' /** * React component to show info about a FreeSewing design * * @param {object} props - All React props * @param {string} design - The name/id of the design * @param {function} Link - An optional framework specific Link component for client-side routing */ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false }) => { if (!Link) Link = WebLink // State const [back, setBack] = useState(false) // Context const { setModal, clearModal } = useContext(ModalContext) const { setLoadingStatus } = useContext(LoadingStatusContext) if (!design) return null // Line drawings const LineDrawing = lineDrawings[design] || MissingLinedrawing const LineDrawingBack = lineDrawingsBack[design] || null // Make sure these always hold arrays, that way we can just map() over them in the JSX output const codeBy = Array.isArray(about[design].code) ? about[design].code : [about[design].code] const designBy = Array.isArray(about[design].design) ? about[design].design : [about[design].design] const tags = about[design].tags || [] const techniques = about[design].techniques || [] const colors = { 1: 'success', 2: 'success', 3: 'warning', 4: 'warning', 5: 'error', } const makeButton = (
New {capitalize(design)} pattern
) const buttons = noDocsLink ? ( makeButton ) : (
Documentation {makeButton}
) return ( <>
{buttons}
{back ? : }
{LineDrawingBack ? ( ) : null}
Description
{about[design].description}
By
{codeBy.map((code) => ( ))} {designBy.map((code) => ( ))}
Difficulty
{optionalMeasurements[design].length > 0 ? ( <>
Optional Measurements
{optionalMeasurements[design].map((m, i) => ( {measurementsTranslations[m]} {i < optionalMeasurements[design].length - 1 ? , : null} ))}
) : null} {requiredMeasurements[design].length > 0 ? ( <>
Required Measurements
{requiredMeasurements[design].map((m, i) => ( {measurementsTranslations[m]} {i < requiredMeasurements[design].length - 1 ? , : null} ))}
) : null}
Tags
{tags.map((tag) => ( {tag} ))}
Techniques
{techniques.map((tech) => ( {tech} ))}
Examples
Documentation
Designer Notes, What You Need, Fabric Options, Cutting Instructions, Design Options, Sewing Instructions
{buttons}
) } const SharingIsCaring = ({ design }) => ( <>

Use #FreeSewing{capitalize(design)} to facilitate discovery

Please use the{' '} #FreeSewing{capitalize(design)} {' '} hashtag when discussing FreeSewing's {capitalize(design)} pattern online.
Doing so can help others discover your post, which really is a win-win.

If you like, you can copy the hashtag below:

)