1
0
Fork 0

[react] fix: Various fixes for the measurement view and editor (#233)

Various fixes for the measurement view and editor:

- Translate measurements in the measurement set view and align the measurements table properly
- Display a question mark next to the measurements that links to the corresponding docs page

This fixes a part of #226

Reviewed-on: https://codeberg.org/freesewing/freesewing/pulls/233
Reviewed-by: Joost De Cock <joostdecock@noreply.codeberg.org>
Co-authored-by: Jonathan Haas <haasjona@gmail.com>
Co-committed-by: Jonathan Haas <haasjona@gmail.com>
This commit is contained in:
Jonathan Haas 2025-04-13 08:57:54 +00:00 committed by Joost De Cock
parent c5b0daf390
commit 36da79afb6
10 changed files with 49 additions and 19 deletions

View file

@ -76,8 +76,9 @@ const t = (input) => {
* @param {number} id - The ID of the measurements set to load * @param {number} id - The ID of the measurements set to load
* @param {bool} publicOnly - FIXME * @param {bool} publicOnly - FIXME
* @param {function} Link - An optional framework-specific Link component to use for client-side routing * @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 }) => { export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvider = false }) => {
if (!Link) Link = WebLink if (!Link) Link = WebLink
// Hooks // Hooks
@ -459,7 +460,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
title={<MeasurementValue {...{ m, val, imperial: !displayAsMetric }} />} title={<MeasurementValue {...{ m, val, imperial: !displayAsMetric }} />}
key={m} key={m}
> >
<span className="tw-font-medium">{m}</span> <span className="tw-font-medium">{measurementTranslations[m]}</span>
</DisplayRow> </DisplayRow>
) : null ) : null
)} )}
@ -491,6 +492,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
current={mset.measies[m]} current={mset.measies[m]}
original={mset.measies[m]} original={mset.measies[m]}
update={updateMeasies} update={updateMeasies}
helpProvider={measurementHelpProvider}
/> />
))} ))}

View file

@ -17,7 +17,7 @@ import {
/* /*
* A component to display a row of data * A component to display a row of data
*/ */
export const DisplayRow = ({ title, children, keyWidth = 'w-24' }) => ( export const DisplayRow = ({ title, children, keyWidth = 'tw-w-24' }) => (
<div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center lg:tw-gap-4 tw-my-2 tw-w-full"> <div className="tw-flex tw-flex-row tw-flex-wrap tw-items-center lg:tw-gap-4 tw-my-2 tw-w-full">
<div <div
className={`${keyWidth} tw-text-left md:tw-text-right tw-block md:tw-inline tw-font-bold tw-pr-4 tw-shrink-0`} className={`${keyWidth} tw-text-left md:tw-text-right tw-block md:tw-inline tw-font-bold tw-pr-4 tw-shrink-0`}

View file

@ -9,9 +9,10 @@ import { MeasurementInput } from '@freesewing/react/components/Input'
* @param {object} props.state - The ViewWrapper state object * @param {object} props.state - The ViewWrapper state object
* @param {object} props.state.settings - The current settings * @param {object} props.state.settings - The current settings
* @param {object} props.update - Helper object for updating the ViewWrapper state * @param {object} props.update - Helper object for updating the ViewWrapper state
* @param {object} props.helpProvider - A function that takes a measurement and returns a url or action to show help for that measurement
* @return {function} MeasurementsEditor - React component * @return {function} MeasurementsEditor - React component
*/ */
export const MeasurementsEditor = ({ Design, update, state }) => { export const MeasurementsEditor = ({ Design, update, state, helpProvider = false }) => {
/* /*
* Helper method to handle state updates for measurements * Helper method to handle state updates for measurements
*/ */
@ -34,6 +35,7 @@ export const MeasurementsEditor = ({ Design, update, state }) => {
original={state.settings.measurements?.[m]} original={state.settings.measurements?.[m]}
update={(m, newVal) => onUpdate(m, newVal)} update={(m, newVal) => onUpdate(m, newVal)}
id={`edit-${m}`} id={`edit-${m}`}
helpProvider={helpProvider}
/> />
))} ))}
<br /> <br />

View file

@ -37,6 +37,7 @@ const iconClasses = {
* @param {Array} props.missingMeasurements - List of missing measurements for the current design * @param {Array} props.missingMeasurements - List of missing measurements for the current design
* @param {Object} props.state - The editor state object * @param {Object} props.state - The editor state object
* @param {Object} props.update - Helper object for updating the editor state * @param {Object} props.update - Helper object for updating the editor state
* @param {object} props.helpProvider - A function that takes a measurement and returns a url or action to show help for that measurement
* @return {Function} MeasurementsView - React component * @return {Function} MeasurementsView - React component
*/ */
export const MeasurementsView = ({ export const MeasurementsView = ({
@ -46,6 +47,7 @@ export const MeasurementsView = ({
state, state,
update, update,
design, design,
measurementHelpProvider = false,
}) => { }) => {
/* /*
* If there is no view set, completing measurements will switch to the view picker * If there is no view set, completing measurements will switch to the view picker
@ -168,7 +170,10 @@ export const MeasurementsView = ({
</div> </div>
<p className="tw-text-left">You can manually set or override measurements below.</p> <p className="tw-text-left">You can manually set or override measurements below.</p>
</Fragment>, </Fragment>,
<MeasurementsEditor key={2} {...{ Design, config, update, state }} />, <MeasurementsEditor
key={2}
{...{ Design, config, update, state, helpProvider: measurementHelpProvider }}
/>,
'edit', 'edit',
]) ])

View file

@ -40,6 +40,7 @@ export const Editor = ({
preload = {}, preload = {},
setTitle = false, setTitle = false,
localDesigns = {}, localDesigns = {},
measurementHelpProvider = false,
}) => { }) => {
/* /*
* Bundle all designs * Bundle all designs
@ -144,6 +145,7 @@ export const Editor = ({
{...extraProps} {...extraProps}
{...{ view, update, designs, config: editorConfig }} {...{ view, update, designs, config: editorConfig }}
state={passDownState} state={passDownState}
measurementHelpProvider={measurementHelpProvider}
/> />
</LoadingStatusContextProvider> </LoadingStatusContextProvider>
</ModalContextProvider> </ModalContextProvider>

View file

@ -16,7 +16,7 @@ import { useDropzone } from 'react-dropzone'
import { useBackend } from '@freesewing/react/hooks/useBackend' import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components // Components
import { Link as WebLink } from '@freesewing/react/components/Link' import { Link as WebLink } from '@freesewing/react/components/Link'
import { TrashIcon, ResetIcon, UploadIcon } from '@freesewing/react/components/Icon' import { TrashIcon, ResetIcon, UploadIcon, HelpIcon } from '@freesewing/react/components/Icon'
import { ModalWrapper } from '@freesewing/react/components/Modal' import { ModalWrapper } from '@freesewing/react/components/Modal'
import { isDegreeMeasurement } from '@freesewing/config' import { isDegreeMeasurement } from '@freesewing/config'
import { Tabs, Tab } from '@freesewing/react/components/Tab' import { Tabs, Tab } from '@freesewing/react/components/Tab'
@ -49,17 +49,17 @@ export const _Tab = ({
const HelpLink = ({ help, Link = false }) => { const HelpLink = ({ help, Link = false }) => {
if (!Link) Link = WebLink if (!Link) Link = WebLink
if (typeof helpLink === 'function') if (typeof help === 'function')
return ( return (
<button onClick={() => help} title="Show help"> <button onClick={() => help} title="Show help">
<QuestionIcon className="tw-w-5 tw-h-5" /> <HelpIcon className="tw-w-5 tw-h-5" />
</button> </button>
) )
if (typeof helpLink === 'string') if (typeof help === 'string')
return ( return (
<Link href={help} target="_BLANK" rel="nofollow" title="Show help"> <Link href={help} target="_BLANK" rel="nofollow" title="Show help">
<QuestionIcon className="tw-w-5 tw-h-5" /> <HelpIcon className="tw-w-5 tw-h-5" />
</Link> </Link>
) )
@ -77,14 +77,14 @@ export const FormControl = ({
labelBR = false, // Optional bottom-right label labelBR = false, // Optional bottom-right label
forId = false, // ID of the for element we are wrapping forId = false, // ID of the for element we are wrapping
help = false, // An optional URL/method to link/show help/docs help = false, // An optional URL/method to link/show help/docs
Link = false, // An optionan framework-specific link components Link = false, // An optional framework-specific link components
}) => { }) => {
if (labelBR && !labelBL) labelBL = <span></span> if (labelBR && !labelBL) labelBL = <span></span>
const topLabelChildren = ( const topLabelChildren = (
<> <>
{label ? ( {label ? (
<span className="tw-daisy-label-text tw-text-sm lg:tw-text-base tw-font-bold tw-mb-1 tw-text-inherit"> <span className="tw-daisy-label-text tw-text-sm lg:tw-text-base tw-font-bold tw-mb-1 tw-text-inherit tw-inline-flex tw-items-center tw-gap-1">
{label} <HelpLink {...{ help, Link }} /> {label} <HelpLink {...{ help, Link }} />
</span> </span>
) : ( ) : (
@ -541,6 +541,7 @@ export const MeasurementInput = ({
update, // The onChange handler update, // The onChange handler
placeholder, // The placeholder content placeholder, // The placeholder content
id = '', // An id to tie the input to the label id = '', // An id to tie the input to the label
helpProvider = false, // a function that provides a url or an action to display help for a measurement
}) => { }) => {
const isDegree = isDegreeMeasurement(m) const isDegree = isDegreeMeasurement(m)
const units = imperial ? 'imperial' : 'metric' const units = imperial ? 'imperial' : 'metric'
@ -604,6 +605,7 @@ export const MeasurementInput = ({
<FormControl <FormControl
label={measurementsTranslations[m] + (isDegree ? ' (°)' : '')} label={measurementsTranslations[m] + (isDegree ? ' (°)' : '')}
forId={id} forId={id}
help={typeof helpProvider === 'function' ? helpProvider(m) : helpProvider}
labelBL={bottomLeftLabel} labelBL={bottomLeftLabel}
> >
<label <label

View file

@ -25,8 +25,15 @@ export const AnchorLink = ({ children, id = '', title = false }) => (
* @param {string} props.className - Any non-default CSS classes to apply * @param {string} props.className - Any non-default CSS classes to apply
* @param {string} props.style - Any non-default styles to apply * @param {string} props.style - Any non-default styles to apply
*/ */
export const Link = ({ href, title = false, children, className = linkClasses, style = {} }) => ( export const Link = ({
<a href={href} className={className} title={title ? title : ''} style={style}> href,
title = false,
children,
className = linkClasses,
target,
style = {},
}) => (
<a href={href} target={target} className={className} title={title ? title : ''} style={style}>
{children} {children}
</a> </a>
) )

View file

@ -11,6 +11,10 @@ import Link from '@docusaurus/Link'
<DocusaurusDoc> <DocusaurusDoc>
<RoleBlock user> <RoleBlock user>
<Set Link={Link} id={getSearchParam('id')} /> <Set
Link={Link}
id={getSearchParam('id')}
measurementHelpProvider={(m) => `/docs/measurements/${m.toLowerCase()}`}
/>
</RoleBlock> </RoleBlock>
</DocusaurusDoc> </DocusaurusDoc>

View file

@ -18,7 +18,10 @@ const EditorPage = () => {
return ( return (
<BareLayout title={title}> <BareLayout title={title}>
<Editor setTitle={setTitle} /> <Editor
setTitle={setTitle}
measurementHelpProvider={(m) => `/docs/measurements/${m.toLowerCase()}`}
/>
</BareLayout> </BareLayout>
) )
} }

View file

@ -38,7 +38,10 @@ export const MeasurementImage = (props) => {
.split('/') .split('/')
.filter((dir) => dir) .filter((dir) => dir)
.pop() .pop()
if (!m || !measurements[caseMap[m]]) .toLowerCase()
// look up the internal measurement key case-insensitively
const mappedMeasurement = measurements[caseMap[m]]
if (!m || !mappedMeasurement)
return <p>Unable to match the input {m} to MeasurementImage to a measurement name</p> return <p>Unable to match the input {m} to MeasurementImage to a measurement name</p>
const pose = seated.includes(m) ? 'seated' : 'standing' const pose = seated.includes(m) ? 'seated' : 'standing'
@ -50,7 +53,7 @@ export const MeasurementImage = (props) => {
height={sarahImages[m].height} height={sarahImages[m].height}
width={sarahImages[m].width} width={sarahImages[m].width}
src={sarahImages[m]} src={sarahImages[m]}
alt={measurements[caseMap[m]]} alt={mappedMeasurement}
style={{ ...style, backgroundImage: `url(/img/sarah-${pose}.jpg)` }} style={{ ...style, backgroundImage: `url(/img/sarah-${pose}.jpg)` }}
/> />
</TabItem> </TabItem>
@ -59,7 +62,7 @@ export const MeasurementImage = (props) => {
height={timImages[m].height} height={timImages[m].height}
width={timImages[m].width} width={timImages[m].width}
src={timImages[m]} src={timImages[m]}
alt={measurements[caseMap[m]]} alt={mappedMeasurement}
style={{ ...style, backgroundImage: `url(/img/tim-${pose}.jpg)` }} style={{ ...style, backgroundImage: `url(/img/tim-${pose}.jpg)` }}
/> />
</TabItem> </TabItem>