// Dependencies import { control as controlConfig, isDegreeMeasurement, measurements, urls, } from '@freesewing/config' import { measurements as measurementTranslations } from '@freesewing/i18n' import { measurements as designMeasurements } from '@freesewing/collection' import { cloudflareImageUrl, formatMm, horFlexClasses, linkClasses, shortDate, timeAgo, } from '@freesewing/utils' // Context import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' import { ModalContext } from '@freesewing/react/context/Modal' // Hooks import React, { Fragment, useContext, useEffect, useState } from 'react' import { useAccount } from '@freesewing/react/hooks/useAccount' import { useBackend } from '@freesewing/react/hooks/useBackend' // Components import { Link as WebLink } from '@freesewing/react/components/Link' import { BoolNoIcon, BoolYesIcon, CloneIcon, CompareIcon, CuratedMeasurementsSetIcon, EditIcon, NoIcon, OkIcon, ResetIcon, ShowcaseIcon, UploadIcon, } from '@freesewing/react/components/Icon' import { BookmarkButton, MsetCard } from '@freesewing/react/components/Account' import { DesignInput, ListInput, MarkdownInput, MeasurementInput, PassiveImageInput, StringInput, ToggleInput, } from '@freesewing/react/components/Input' import { Pattern } from '../Pattern/index.mjs' import { DisplayRow } from './shared.mjs' import Markdown from 'react-markdown' import { ModalWrapper } from '@freesewing/react/components/Modal' import { Json } from '@freesewing/react/components/Json' import { Yaml } from '@freesewing/react/components/Yaml' import { Popout } from '@freesewing/react/components/Popout' import { bundlePatternTranslations, draft, flattenFlags } from '../Editor/lib/index.mjs' import { Bonny } from '@freesewing/bonny' import { MiniNote, MiniTip } from '../Mini/index.mjs' const t = (input) => { console.log('t called', input) return input } /* * Component to show an individual measurements set * * @param {object} props - All React props * @param {number} id - The ID of the measurements set to load * @param {bool} publicOnly - If the set should be used with the backend.getPublicSet method * @param {function} Link - An optional framework-specific Link component to use for client-side routing * @param {object} measurementHelpProvider - A function that returns a url or action to show help for a specific measurement */ export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvider = false }) => { if (!Link) Link = WebLink // Hooks const { account, control } = useAccount() const { setLoadingStatus } = useContext(LoadingStatusContext) const backend = useBackend() // Context const { setModal } = useContext(ModalContext) const [filter, setFilter] = useState(false) const [edit, setEdit] = useState(false) const [suggest, setSuggest] = useState(false) const [render, setRender] = useState(false) const [mset, setMset] = useState() // Set fields for editing const [name, setName] = useState(mset?.name) const [image, setImage] = useState(mset?.image) const [isPublic, setIsPublic] = useState(mset?.public ? true : false) const [imperial, setImperial] = useState(mset?.imperial ? true : false) const [notes, setNotes] = useState(mset?.notes || '') const [measies, setMeasies] = useState({}) const [displayAsMetric, setDisplayAsMetric] = useState(mset?.imperial ? false : true) // Effect useEffect(() => { const getSet = async () => { setLoadingStatus([true, 'Contacting the backend']) const [status, body] = await backend.getSet(id) if (status === 200 && body.result === 'success') { setMset(body.set) setName(body.set.name) setImage(body.set.image) setIsPublic(body.set.public ? true : false) setImperial(body.set.imperial ? true : false) setNotes(body.set.notes) setMeasies(body.set.measies) setDisplayAsMetric(body.set.imperial ? false : true) setLoadingStatus([true, 'Measurements set loaded', true, true]) } else setLoadingStatus([true, 'An error occured while contacting the backend', true, false]) } const getPublicSet = async () => { setLoadingStatus([true, 'Contacting the backend']) const [status, body] = await backend.getPublicSet(id) if (status === 200 && body.result === 'success') { const isImperial = body.units === 'imperial' setMset({ ...body, public: true, measies: body.measurements, imperial: isImperial, }) setName(body.name) setImage(body.image) setIsPublic(body.public ? true : false) setImperial(isImperial) setNotes(body.notes) setMeasies(body.measurements) setDisplayAsMetric(!isImperial) setLoadingStatus([true, 'Measurements set loaded', true, true]) } else setLoadingStatus([ true, 'An error occured while loading this measurements set', true, false, ]) } if (id) { if (publicOnly) getPublicSet() else getSet() } }, [id, publicOnly]) const filterMeasurements = () => filter ? designMeasurements[filter].sort() : measurements.sort() if (!id || !mset) return null const updateMeasies = (m, val) => { const newMeasies = { ...measies } newMeasies[m] = val setMeasies(newMeasies) } const save = async () => { setLoadingStatus([true, 'Gathering info']) // Compile data const data = { measies: {} } if (name || name !== mset.name) data.name = name if (image || image !== mset.image) data.img = image if ([true, false].includes(isPublic) && isPublic !== mset.public) data.public = isPublic if ([true, false].includes(imperial) && imperial !== mset.imperial) data.imperial = imperial if (notes || notes !== mset.notes) data.notes = notes // Add measurements for (const m of measurements) { if (measies[m] || measies[m] !== mset.measies[m]) data.measies[m] = measies[m] } setLoadingStatus([true, 'Saving measurements set']) const [status, body] = await backend.updateSet(mset.id, data) if (status === 200 && body.result === 'success') { setMset(body.set) setEdit(false) setLoadingStatus([true, 'Nailed it', true, true]) } else setLoadingStatus([true, 'That did not go as planned. Saving the set failed.', true, false]) } const togglePublic = async () => { setLoadingStatus([true, 'Getting ready']) const [status, body] = await backend.updateSet(mset.id, { public: !mset.public }) if (status === 200 && body.result === 'success') { setMset(body.set) setLoadingStatus([true, 'Alright, done!', true, true]) } else setLoadingStatus([true, 'Backend says no :(', true, false]) } const importSet = async () => { setLoadingStatus([true, 'Importing data']) // Compile data const data = { ...mset, userId: account.id, measies: { ...mset.measies }, } delete data.img const [status, body] = await backend.createSet(data) if (status === 201 && body.result === 'created') { setLoadingStatus([true, 'Loading newly created set', true, true]) window.location = `/account/data/sets/set?id=${body.set.id}` } else setLoadingStatus([true, 'We failed to create this measurements set', true, false]) } const heading = ( <>
{account.control > 2 && mset.public && mset.userId !== account.id ? (
JSON YAML
) : ( )} {account.control > 3 && mset.userId === account.id ? (
) : ( )} {account.id && account.control > 2 && mset.public && mset.userId !== account.id ? ( ) : null} {account.control > 2 ? ( ) : null} {!publicOnly && ( <> {account.control > 2 ? ( ) : null} {edit ? ( <> ) : ( )} )} {account.control > 1 && account?.compare ? ( ) : null} {account.control > 2 && mset.userId === account.id ? ( ) : null}
) if (suggest) return (
{heading}
) if (!edit) { if (render) return (
{heading}
) return (
{heading}

Data

{mset.name} {mset.imperial ? 'Imperial' : 'Metric'} {control >= controlConfig.account.sets.notes && ( {mset.notes} )} {control >= controlConfig.account.sets.public && ( <> {mset.userId === account.id && (
{mset.public ? ( ) : ( )}
)} {mset.public && ( {`/set?id=${mset.id}`} )} )} {control >= controlConfig.account.sets.createdAt && ( {timeAgo(mset.createdAt, false)} ({shortDate(mset.createdAt, false)}) )} {control >= controlConfig.account.sets.updatedAt && ( {timeAgo(mset.updatedAt, false)} ({shortDate(mset.updatedAt, false)}) )} {control >= controlConfig.account.sets.id && {mset.id}} {Object.keys(mset.measies).length > 0 && ( <>

Measurements

setDisplayAsMetric(!displayAsMetric)} current={displayAsMetric} /> {Object.entries(mset.measies).map(([m, val]) => val > 0 ? ( } key={m} > {measurementTranslations[m]} ) : null )} )}
) } return (
{heading}

Measurements

Clear filter} />
{filterMeasurements().map((m) => ( ))}

Data

{/* Name is always shown */} val && val.length > 0} /> {/* img: Control level determines whether or not to show this */} {account.control >= controlConfig.account.sets.img ? ( val.length > 0} /> ) : null} {/* public: Control level determines whether or not to show this */} {account.control >= controlConfig.account.sets.public ? ( Public measurements set
), desc: 'Others are allowed to use these measurements to generate or test patterns', }, { val: false, label: (
Private measurements set
), desc: 'These measurements cannot be used by other users or visitors', }, ]} current={isPublic} /> ) : null} {/* units: Control level determines whether or not to show this */} {account.control >= controlConfig.account.sets.units ? ( <> Metric units (cm) cm ), desc: 'Pick this if you prefer cm over inches', }, { val: true, label: (
Imperial units (inch)
), desc: 'Pick this if you prefer inches over cm', }, ]} current={imperial} /> Note: You must save after changing Units to have the change take effect on this page. ) : null} {/* notes: Control level determines whether or not to show this */} {account.control >= controlConfig.account.sets.notes ? ( ) : null} ) } /** * A (helper) component to display a measurements value * * @param {object} props - All React props * @param {string} val - The value * @param {string} m - The measurement name * @param {bool} imperial - True for imperial measurements, or metric by default */ export const MeasurementValue = ({ val, m, imperial = false }) => isDegreeMeasurement(m) ? ( {val}° ) : ( ) /** * React component to suggest a measurements set for curation * * @param {object} props - All React props * @param {string} mset - The measurements set */ export const SuggestCset = ({ mset, Link }) => { // State const [height, setHeight] = useState('') const [img, setImg] = useState('') const [name, setName] = useState('') const [notes, setNotes] = useState('') const [submission, setSubmission] = useState(false) console.log(mset) // Hooks const backend = useBackend() // Method to submit the form const suggestSet = async () => { setLoadingStatus([true, 'Contacting backend']) const result = await backend.suggestCset({ set: mset.id, height, img, name, notes }) if (result.success && result.data.submission) { setSubmission(result.data.submission) setLoadingStatus([true, 'Nailed it', true, true]) } else setLoadingStatus([true, 'An unexpected error occured. Please report this.', true, false]) } const missing = [] for (const m of measurements) { if (typeof mset.measies[m] === 'undefined') missing.push(m) } if (submission) { const url = `/curate/sets/suggested/${submission.id}` return ( <>

Thank you

Your submission has been registered and will be processed by one of our curators.

It is available at: {url}

) } return ( <>

Suggest a measurements set for curation

{missing.length > 0 ? : } Measurements

{missing.length > 0 ? ( <>

To ensure curated measurements sets work for all designs, you need to provide a full set of measurements.

Your measurements set is missing the following measurements:

) : (

All measurements are available.

)}

{name.length > 1 ? : } Name

Each curated set has a name. You can suggest your own name or a pseudonym.

val.length > 1} />

{height.length > 1 ? : } Height

To allow organizing and presenting our curated sets in a structured way, we organize them by height.

val.length > 1} />

{img.length > 0 ? : } Image

Finally, we need a picture. Please refer to the documentation to see what makes a good picture for a curated measurements set. Documentation

val.length > 1} />

Notes

If you would like to add any notes, you can do so here.

This field supports markdown true} /> ) } /** * React component to render a preview of a measurement set using the bonny pattern * * @param {object} props - All React props * @param {string} mset - The measurements set */ export const RenderedCSet = ({ mset, imperial }) => { const [previewVisible, setPreviewVisible] = useState(false) const missing = [] for (const m of measurements) { if (typeof mset.measies[m] === 'undefined') missing.push(m) } if (missing.length > 0) return ( <>

Validation messages

To validate and preview a measurement set, all measurements need to be entered.

Your measurements set is missing the following measurements:

    {missing.map((m) => (
  • {m}
  • ))}
) const strings = bundlePatternTranslations('bonny') const { pattern } = draft(Bonny, { measurements: mset.measies }) const flags = pattern.setStores?.[0]?.plugins?.['plugin-annotations']?.flags console.log('flags', pattern, flags, strings) return ( <>

Measurement analysis

Based on your measurements, we estimate your body to be about{' '} {formatMm(pattern.parts[0].front.points.head.y * -1, imperial)} high.

Here is what the automated analysis found:

{Object.entries(flattenFlags(flags)).map(([key, flag], i) => { const desc = strings[flag.desc] || flag.desc return (
{flag.type === 'warn' ? ( {desc} ) : ( {desc} )}
) })}

Preview

{previewVisible ? ( ) : ( <>

This feature creates a visual preview of your body based on your measurements.

It’s meant to help you spot possible mistakes and better understand how the software sees your measurements, but keep in mind:

  • The preview is a simple line drawing, but it does include features like chest shape and crotch placement. If that feels uncomfortable, you may prefer to skip using this tool.
  • This preview is an approximation, not an exact representation. Bodies have many variations that can't be captured with just a few measurements. We are missing some information, like how weight is distributed or your posture.
  • If something looks off, it{' '} doesn’t necessarily mean your measurements are wrong, but it might be worth double-checking. Sometimes, differences come from how the preview is generated rather than an error in measuring.
  • Just like this preview, some sewing patterns may need to assume certain body proportions. If this preview looks different from what you expect, some patterns may also need adjustment, to get a perfect fit.
)} ) } export const NewSet = () => { // Hooks const backend = useBackend() const { account } = useAccount() const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext) // State const [name, setName] = useState('') // Use account setting for imperial const imperial = account.imperial // Helper method to create a new set const createSet = async () => { setLoadingStatus([true, 'Storing new measurements set']) const [status, body] = await backend.createSet({ name, imperial }) if (status === 201 && body.result === 'created') { setLoadingStatus([true, 'Nailed it', true, true]) window.location = `/account/data/sets/set?id=${body.set.id}` } else setLoadingStatus([ true, 'Failed to save the measurments set. Please report this.', true, false, ]) } return (
Name

Give this set of measurements a name. That will help tell them apart.

val && val.length > 0} placeholder={'Georg Cantor'} />
) }