1
0
Fork 0

chore(lab): Use locale and hover resize consistently

This commit is contained in:
Joost De Cock 2022-01-27 18:07:37 +01:00
parent 2380b4f2db
commit e8d747a949
18 changed files with 162 additions and 96 deletions

View file

@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
import Logo from 'shared/components/logos/freesewing.js' import Logo from 'shared/components/logos/freesewing.js'
import Link from 'next/link' import Link from 'next/link'
import ThemePicker from 'shared/components/theme-picker.js' import ThemePicker from 'shared/components/theme-picker.js'
import LanguagePicker from 'shared/components/language-picker.js' import LocalePicker from 'shared/components/locale-picker.js'
import PatternPicker from 'site/components/pattern-picker.js' import PatternPicker from 'site/components/pattern-picker.js'
import CloseIcon from 'shared/components/icons/close.js' import CloseIcon from 'shared/components/icons/close.js'
import MenuIcon from 'shared/components/icons/menu.js' import MenuIcon from 'shared/components/icons/menu.js'
@ -82,7 +82,7 @@ const Header = ({ app }) => {
</div> </div>
<div className="hidden sm:flex flex-row items-center"> <div className="hidden sm:flex flex-row items-center">
<ThemePicker app={app} /> <ThemePicker app={app} />
<LanguagePicker app={app} /> <LocalePicker app={app} />
</div> </div>
</div> </div>
</div> </div>

View file

@ -5,7 +5,7 @@ import mustache from 'mustache'
import useLocalStorage from 'shared/hooks/useLocalStorage.js' import useLocalStorage from 'shared/hooks/useLocalStorage.js'
// Patterns // Patterns
import patterns from 'site/patterns.json' import patterns from 'site/patterns.json'
// Languages // Locales
import { strings } from 'pkgs/i18n' import { strings } from 'pkgs/i18n'
// Initial navigation // Initial navigation
@ -70,7 +70,7 @@ function useApp(full = true) {
// Persistent state // Persistent state
const [account, setAccount] = useLocalStorage('account', { username: false }) const [account, setAccount] = useLocalStorage('account', { username: false })
const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light') const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light')
const [language, setLanguage] = useLocalStorage('language', 'en') const [locale, setLocale] = useLocalStorage('locale', 'en')
// React State // React State
const [primaryMenu, setPrimaryMenu] = useState(false) const [primaryMenu, setPrimaryMenu] = useState(false)
@ -81,20 +81,20 @@ function useApp(full = true) {
// State methods // State methods
const togglePrimaryMenu = () => setPrimaryMenu(!primaryMenu) const togglePrimaryMenu = () => setPrimaryMenu(!primaryMenu)
const changeLanguage = lang => { const changeLocale = loc => {
setLanguage(lang) setLocale(loc)
setNavigation(translateNavigation(lang, t)) setNavigation(translateNavigation(loc, t))
} }
/* /*
* Translation method * Translation method
*/ */
const t = (key, props=false, toLanguage=false) => { const t = (key, props=false, toLocale=false) => {
if (!toLanguage) toLanguage = language if (!toLocale) toLocale = locale
const template = const template =
strings[toLanguage][key] || strings[toLocale][key] ||
strings[toLanguage][`app.${key}`] || strings[toLocale][`app.${key}`] ||
strings[toLanguage][`plugin.${key}`] || strings[toLocale][`plugin.${key}`] ||
strings.en[`app.${key}`] || strings.en[`app.${key}`] ||
false false
if (!props && template) return template if (!props && template) return template
@ -117,7 +117,7 @@ function useApp(full = true) {
patterns, patterns,
// State // State
language, locale,
loading, loading,
navigation, navigation,
pattern, pattern,
@ -137,11 +137,11 @@ function useApp(full = true) {
// State handlers // State handlers
togglePrimaryMenu, togglePrimaryMenu,
changeLanguage, changeLocale,
// Translation // Translation
t, t,
languages: Object.keys(strings), locales: Object.keys(strings),
} }
} }

View file

@ -1,8 +1,8 @@
import themes from 'shared/themes/index.js' import themes from 'shared/themes/index.js'
import LanguageIcon from 'shared/components/icons/i18n.js' import LocaleIcon from 'shared/components/icons/i18n.js'
import { languages } from 'pkgs/i18n' import { languages } from 'pkgs/i18n'
const LanguagePicker = ({ app }) => { const LocalePicker = ({ app }) => {
return ( return (
<div className="dropdown"> <div className="dropdown">
<div tabIndex="0" className={` <div tabIndex="0" className={`
@ -10,14 +10,14 @@ const LanguagePicker = ({ app }) => {
sm:btn-ghost sm:btn-ghost
hover:bg-neutral hover:border-neutral-content hover:bg-neutral hover:border-neutral-content
`}> `}>
<LanguageIcon /> <LocaleIcon />
<span>{languages[app.language]}</span> <span>{languages[app.locale]}</span>
</div> </div>
<ul tabIndex="0" className="p-2 shadow menu dropdown-content bg-base-100 rounded-box w-52"> <ul tabIndex="0" className="p-2 shadow menu dropdown-content bg-base-100 rounded-box w-52">
{Object.keys(languages).map(language => ( {Object.keys(app.locales).map(locale => (
<li key={language}> <li key={locale}>
<button onClick={() => app.changeLanguage(language)} className="btn btn-ghost text-base-content hover:bg-base-200"> <button onClick={() => app.changeLocale(locale)} className="btn btn-ghost text-base-content hover:bg-base-200">
{languages[language]} {languages[locale]}
</button> </button>
</li> </li>
))} ))}
@ -26,4 +26,4 @@ const LanguagePicker = ({ app }) => {
) )
} }
export default LanguagePicker export default LocalePicker

View file

@ -46,7 +46,7 @@ const currentChildren = current => Object.values(order(current))
// Shared classes for links // Shared classes for links
// Exported for re-use // Exported for re-use
export const linkClasses = `text-lg lg:text-xl export const linkClasses = `text-lg lg:text-xl
py-1 hover:cursor-pointer py-1
text-base-content sm:text-neutral-content text-base-content sm:text-neutral-content
hover:text-secondary hover:text-secondary
sm:hover:text-secondary-focus sm:hover:text-secondary-focus
@ -84,6 +84,7 @@ const SubLevel = ({ nodes={}, active }) => (
<a title={child.__title} className={` <a title={child.__title} className={`
grow pl-2 border-l-2 grow pl-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-pointer
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
${child.__slug === active ${child.__slug === active
@ -117,6 +118,7 @@ const SubLevel = ({ nodes={}, active }) => (
pl-2 border-l-2 pl-2 border-l-2
grow grow
${linkClasses} ${linkClasses}
hover:cursor-pointer
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
${child.__slug === active ${child.__slug === active
@ -157,7 +159,13 @@ const TopLevel = ({ icon, title, nav, current, slug, hasChildren=false, active }
`}> `}>
<span className="text-secondary-focus">{icon}</span> <span className="text-secondary-focus">{icon}</span>
<Link href={`/${slug}`}> <Link href={`/${slug}`}>
<a className={`grow ${linkClasses} ${slug === active ? 'text-secondary sm:text-secondary-focus' : ''}`}> <a className={`
grow ${linkClasses} hover:cursor-pointer
${slug === active
? 'text-secondary sm:text-secondary-focus'
: ''
}`}
>
{title} {title}
</a> </a>
</Link> </Link>

View file

@ -11,15 +11,15 @@ const LabDraft = ({ app, pattern, gist, updateGist }) => {
return ( return (
<Svg {...patternProps}> <Svg {...patternProps}>
<Defs {...patternProps} /> <Defs {...patternProps} />
<style>{`:root { --pattern-scale: ${gist.settings.scale || 1}}`}</style> <style>{`:root { --pattern-scale: ${gist.scale || 1}}`}</style>
<g> <g>
{Object.keys(patternProps.parts).map((name) => ( {Object.keys(patternProps.parts).map((name) => (
<Part <Part
key={name} key={name}
part={patternProps.parts[name]} part={patternProps.parts[name]}
language={gist.settings.locale} locale={gist.locale}
paperless={gist.settings.paperless} paperless={gist.paperless}
units={gist.settings.units} units={gist.units}
name={name} name={name}
app={app} app={app}
/> />

View file

@ -103,7 +103,7 @@ const Part = (props) => {
key={name} key={name}
name={name} name={name}
part={props.name} part={props.name}
language={props.language} locale={props.locale}
path={props.part.paths[name]} path={props.part.paths[name]}
focus={props.focus} focus={props.focus}
topLeft={props.part.topLeft} topLeft={props.part.topLeft}
@ -118,7 +118,7 @@ const Part = (props) => {
key={name} key={name}
name={name} name={name}
part={props.name} part={props.name}
language={props.language} locale={props.locale}
point={props.part.points[name]} point={props.part.points[name]}
focus={props.focus} focus={props.focus}
topLeft={props.part.topLeft} topLeft={props.part.topLeft}

View file

@ -3,7 +3,7 @@ import React from 'react'
const Svg = ({ const Svg = ({
embed = true, embed = true,
develop = false, develop = false,
language = 'en', locale = 'en',
className = 'freesewing pattern', className = 'freesewing pattern',
style = {}, style = {},
viewBox = false, viewBox = false,
@ -15,7 +15,7 @@ const Svg = ({
xmlns: 'http://www.w3.org/2000/svg', xmlns: 'http://www.w3.org/2000/svg',
'xmlns:svg': 'http://www.w3.org/2000/svg', 'xmlns:svg': 'http://www.w3.org/2000/svg',
xmlnsXlink: 'http://www.w3.org/1999/xlink', xmlnsXlink: 'http://www.w3.org/1999/xlink',
xmlLang: language, xmlLang: locale,
viewBox: viewBox || `0 0 ${width} ${height}`, viewBox: viewBox || `0 0 ${width} ${height}`,
className, className,
style style

View file

@ -3,7 +3,7 @@ const TextOnPath = (props) => {
// Handle translation // Handle translation
let translated = '' let translated = ''
for (let string of props.path.attributes.getAsArray('data-text')) { for (let string of props.path.attributes.getAsArray('data-text')) {
translated += props.app.t(string, false, props.language).replace(/&quot;/g, '"') + ' ' translated += props.app.t(string, false, props.locale).replace(/&quot;/g, '"') + ' '
} }
const textPathProps = { const textPathProps = {
xlinkHref: '#' + props.pathId, xlinkHref: '#' + props.pathId,

View file

@ -3,7 +3,7 @@ const Text = (props) => {
// Handle translation // Handle translation
let translated = '' let translated = ''
for (let string of props.point.attributes.getAsArray('data-text')) { for (let string of props.point.attributes.getAsArray('data-text')) {
translated += props.app.t(string.toString(), false, props.language).replace(/&quot;/g, '"') + ' ' translated += props.app.t(string.toString(), false, props.locale).replace(/&quot;/g, '"') + ' '
} }
// Handle muti-line text // Handle muti-line text
if (translated.indexOf('\n') !== -1) { if (translated.indexOf('\n') !== -1) {

View file

@ -50,7 +50,7 @@ const DesignOptionPctDeg = props => {
return ( return (
<div className="py-4 mx-6 border-l-2 pl-2"> <div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-neutral-content opacity-60 italic"> <p className="m-0 p-0 px-2 mb-2 text-neutral-content opacity-60 italic">
{props.app.t(`options.${props.pattern.config.name}.${props.option}.description`, props.app.language)} {props.app.t(`options.${props.pattern.config.name}.${props.option}.description`, props.app.locale)}
</p> </p>
<div className="flex flex-row justify-between"> <div className="flex flex-row justify-between">
{editOption {editOption

View file

@ -2,7 +2,7 @@ import { useState } from 'react'
const CoreSettingList = props => { const CoreSettingList = props => {
const { dflt, list } = props const { dflt, list } = props
const val = props.gist?.settings?.[props.setting] const val = props.gist?.[props.setting]
const [value, setValue] = useState(val) const [value, setValue] = useState(val)
@ -10,10 +10,15 @@ const CoreSettingList = props => {
if (newVal === dflt) reset() if (newVal === dflt) reset()
else { else {
setValue(newVal) setValue(newVal)
props.updateGist(['settings', props.setting], newVal) props.updateGist([props.setting], newVal)
} }
} }
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return ( return (
<div className="py-4 mx-6 border-l-2 pl-2"> <div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-neutral-content opacity-60 italic"> <p className="m-0 p-0 px-2 mb-2 text-neutral-content opacity-60 italic">

View file

@ -0,0 +1,73 @@
import { useState } from 'react'
import { formatMm } from 'shared/utils.js'
import ClearIcon from 'shared/components/icons/clear.js'
import EditIcon from 'shared/components/icons/edit.js'
const CoreSettingMm = props => {
const { dflt, min, max } = props
const val = props.gist?.[props.setting]
const [value, setValue] = useState(val)
const handleChange = evt => {
const newVal = parseFloat(evt.target.value)
if (newVal === dflt) reset()
else {
setValue(newVal)
props.updateGist([props.setting], newVal)
}
}
const reset = () => {
setValue(props.dflt)
props.updateGist([props.setting], props.dflt)
}
return (
<div className="py-4 mx-6 border-l-2 pl-2">
<p className="m-0 p-0 px-2 mb-2 text-neutral-content opacity-60 italic">
{props.app.t(`settings.${props.setting}.description`)}
</p>
<div className="flex flex-row justify-between">
<span
className="opacity-50"
dangerouslySetInnerHTML={{__html: formatMm(min, props.gist.units)}}
/>
<span
className={`font-bold ${val===dflt ? 'text-secondary' : 'text-accent'}`}
dangerouslySetInnerHTML={{__html: formatMm(val, props.gist.units)}}
/>
<span
className="opacity-50"
dangerouslySetInnerHTML={{__html: formatMm(max, props.gist.units)}}
/>
</div>
<input
type="range"
max={max}
min={min}
step={0.1}
value={value}
onChange={handleChange}
className={`
range range-sm mt-1
${val === dflt ? 'range-secondary' : 'range-accent'}
`}
/>
<div className="flex flex-row justify-between">
<span />
<button
title={props.app.t('app.reset')}
className="btn btn-ghost btn-xs text-accent"
disabled={val === dflt}
onClick={reset}
>
<ClearIcon />
</button>
</div>
</div>
)
}
export default CoreSettingMm

View file

@ -4,9 +4,16 @@ import Setting from './setting.js'
const settings = { const settings = {
locale: { locale: {
dflt: 'en',
list: ['de', 'en', 'es', 'fr', 'nl'], list: ['de', 'en', 'es', 'fr', 'nl'],
}, },
units: {
list: ['metric', 'imperial'],
},
margin: {
min: 0,
max: 25,
dflt: 2,
},
} }
const CoreSettings = props => { const CoreSettings = props => {
@ -22,7 +29,7 @@ const CoreSettings = props => {
items-center items-center
`}> `}>
<span className="text-secondary-focus mr-4"><SettingsIcon /></span> <span className="text-secondary-focus mr-4"><SettingsIcon /></span>
<span className={`grow ${linkClasses}`}> <span className={`grow ${linkClasses} hover:cursor-resize`}>
{props.app.t('app.settings')} {props.app.t('app.settings')}
</span> </span>
<Chevron /> <Chevron />

View file

@ -2,70 +2,31 @@ import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
import PctDegOption from 'shared/components/workbench/inputs/design-option-pct-deg' import PctDegOption from 'shared/components/workbench/inputs/design-option-pct-deg'
import CountOption from 'shared/components/workbench/inputs/design-option-count' import CountOption from 'shared/components/workbench/inputs/design-option-count'
import ListSetting from './core-setting-list' import ListSetting from './core-setting-list'
import MmSetting from './core-setting-mm'
import { formatMm, formatPercentage, optionType } from 'shared/utils.js' import { formatMm, formatPercentage, optionType } from 'shared/utils.js'
const settings = { const settings = {
locale: props => { locale: props => {
return ( return (
<span className="text-secondary"> <span className="text-secondary">
{props.app.t(`i18n.${props.gist.settings.locale}`)} {props.app.t(`i18n.${props.gist.locale}`)}
</span> </span>
) )
}, },
pct: props => { units: props => {
const val = (typeof props.gist?.options?.[props.option] === 'undefined')
? props.pattern.config.options[props.option].pct/100
: props.gist.options[props.option]
return ( return (
<span className={ <span className="text-secondary">
val=== props.pattern.config.options[props.option].pct/100 {props.app.t(`app.${props.gist.units}Units`)}
? 'text-secondary'
: 'text-accent'
}>
{formatPercentage(val)}
{props.pattern.config.options[props.option]?.toAbs
? ' | ' +formatMm(props.pattern.config.options[props.option]?.toAbs(val, props.gist))
: null
}
</span> </span>
) )
}, },
bool: props => { margin: props => {
const dflt = props.pattern.config.options[props.option].bool
const current = props.gist?.options?.[props.option]
return ( return (
<span className={ <span className="text-secondary" dangerouslySetInnerHTML={{
(dflt==current || typeof current === 'undefined') __html: formatMm(props.gist.margin, props.gist.units)
? 'text-secondary' }} />
: 'text-accent'
}>
{props.gist?.options?.[props.option]
? props.app.t('app.yes')
: props.app.t('app.no')
}
</span>
) )
}, },
count: props => {
const dflt = props.pattern.config.options[props.option].count
const current = props.gist?.options?.[props.option]
return (dflt==current || typeof current === 'undefined')
? <span className="text-secondary">{dflt}</span>
: <span className="text-accent">{current}</span>
},
deg: props => {
const dflt = props.pattern.config.options[props.option].deg
const current = props.gist?.options?.[props.option]
return (dflt==current || typeof current === 'undefined')
? <span className="text-secondary">{dflt}&deg;</span>
: <span className="text-accent">{current}&deg;</span>
},
mm: props => {
return <p>No mm val yet</p>
},
constant: props => {
return <p>No constant val yet</p>
},
} }
const Tmp = props => <p>not yet</p> const Tmp = props => <p>not yet</p>
@ -73,14 +34,21 @@ const Tmp = props => <p>not yet</p>
const inputs = { const inputs = {
locale: props => <ListSetting locale: props => <ListSetting
{...props} {...props}
list={props.app.languages.map(key => ({ list={props.config.list.map(key => ({
key, key,
title: props.app.t(`i18n.${key}`) title: props.app.t(`i18n.${key}`)
}))} }))}
/>, />,
units: props => <ListSetting
{...props}
list={props.config.list.map(key => ({
key,
title: props.app.t(`app.${key}Units`)
}))}
/>,
margin: props => <MmSetting {...props} {...props.config} />,
} }
const Setting = props => { const Setting = props => {
const Input = inputs[props.setting] const Input = inputs[props.setting]
const Value = settings[props.setting] const Value = settings[props.setting]
@ -109,6 +77,7 @@ const Setting = props => {
<div className={` <div className={`
grow pl-2 border-l-2 grow pl-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-pointer
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-base-content sm:text-neutral-content text-base-content sm:text-neutral-content
@ -143,6 +112,7 @@ const Setting = props => {
<div className={` <div className={`
grow pl-2 border-l-2 grow pl-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-resize
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-base-content sm:text-neutral-content text-base-content sm:text-neutral-content

View file

@ -18,6 +18,7 @@ const OptionGroup = props => {
<div className={` <div className={`
grow pl-2 border-l-2 grow pl-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-resize
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-base-content sm:text-neutral-content text-base-content sm:text-neutral-content

View file

@ -16,6 +16,7 @@ const OptionSubGroup = props => {
<div className={` <div className={`
grow pl-2 border-l-2 grow pl-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-resize
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-base-content sm:text-neutral-content text-base-content sm:text-neutral-content

View file

@ -37,7 +37,7 @@ const Modes = props => {
items-center items-center
`}> `}>
<span className="text-secondary-focus mr-4"><MenuIcon /></span> <span className="text-secondary-focus mr-4"><MenuIcon /></span>
<span className={`grow ${linkClasses}`}> <span className={`grow ${linkClasses} hover:cursor-resize`}>
{props.app.t('app.modes')} {props.app.t('app.modes')}
</span> </span>
<Chevron /> <Chevron />
@ -48,6 +48,7 @@ const Modes = props => {
<button title={entry.title} className={` <button title={entry.title} className={`
grow pl-2 border-l-2 grow pl-2 border-l-2
${linkClasses} ${linkClasses}
hover:cursor-pointer
hover:border-secondary hover:border-secondary
sm:hover:border-secondary-focus sm:hover:border-secondary-focus
text-left text-left

View file

@ -10,13 +10,13 @@ import defaultSettings from 'shared/components/workbench/default-settings.js'
// Generates a default pattern gist to start from // Generates a default pattern gist to start from
const defaultGist = (pattern, language='en') => { const defaultGist = (pattern, locale='en') => {
const gist = { const gist = {
design: pattern.config.name, design: pattern.config.name,
version: pattern.config.version, version: pattern.config.version,
settings: defaultSettings ...defaultSettings
} }
if (language) gist.settings.locale = language if (locale) gist.locale = locale
return gist return gist
} }
@ -41,7 +41,7 @@ const WorkbenchWrapper = ({ app, pattern }) => {
// State for display mode and gist // State for display mode and gist
const [mode, setMode] = useState('measurements') const [mode, setMode] = useState('measurements')
const [gist, setGist] = useLocalStorage('gist', defaultGist(pattern, app.language)) const [gist, setGist] = useLocalStorage('gist', defaultGist(pattern, app.locale))
// If we don't have the requiremed measurements, // If we don't have the requiremed measurements,
// force mode to measurements // force mode to measurements