chore(lab): Use locale and hover resize consistently
This commit is contained in:
parent
2380b4f2db
commit
e8d747a949
18 changed files with 162 additions and 96 deletions
|
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react'
|
|||
import Logo from 'shared/components/logos/freesewing.js'
|
||||
import Link from 'next/link'
|
||||
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 CloseIcon from 'shared/components/icons/close.js'
|
||||
import MenuIcon from 'shared/components/icons/menu.js'
|
||||
|
@ -82,7 +82,7 @@ const Header = ({ app }) => {
|
|||
</div>
|
||||
<div className="hidden sm:flex flex-row items-center">
|
||||
<ThemePicker app={app} />
|
||||
<LanguagePicker app={app} />
|
||||
<LocalePicker app={app} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,7 @@ import mustache from 'mustache'
|
|||
import useLocalStorage from 'shared/hooks/useLocalStorage.js'
|
||||
// Patterns
|
||||
import patterns from 'site/patterns.json'
|
||||
// Languages
|
||||
// Locales
|
||||
import { strings } from 'pkgs/i18n'
|
||||
|
||||
// Initial navigation
|
||||
|
@ -70,7 +70,7 @@ function useApp(full = true) {
|
|||
// Persistent state
|
||||
const [account, setAccount] = useLocalStorage('account', { username: false })
|
||||
const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light')
|
||||
const [language, setLanguage] = useLocalStorage('language', 'en')
|
||||
const [locale, setLocale] = useLocalStorage('locale', 'en')
|
||||
|
||||
// React State
|
||||
const [primaryMenu, setPrimaryMenu] = useState(false)
|
||||
|
@ -81,20 +81,20 @@ function useApp(full = true) {
|
|||
|
||||
// State methods
|
||||
const togglePrimaryMenu = () => setPrimaryMenu(!primaryMenu)
|
||||
const changeLanguage = lang => {
|
||||
setLanguage(lang)
|
||||
setNavigation(translateNavigation(lang, t))
|
||||
const changeLocale = loc => {
|
||||
setLocale(loc)
|
||||
setNavigation(translateNavigation(loc, t))
|
||||
}
|
||||
|
||||
/*
|
||||
* Translation method
|
||||
*/
|
||||
const t = (key, props=false, toLanguage=false) => {
|
||||
if (!toLanguage) toLanguage = language
|
||||
const t = (key, props=false, toLocale=false) => {
|
||||
if (!toLocale) toLocale = locale
|
||||
const template =
|
||||
strings[toLanguage][key] ||
|
||||
strings[toLanguage][`app.${key}`] ||
|
||||
strings[toLanguage][`plugin.${key}`] ||
|
||||
strings[toLocale][key] ||
|
||||
strings[toLocale][`app.${key}`] ||
|
||||
strings[toLocale][`plugin.${key}`] ||
|
||||
strings.en[`app.${key}`] ||
|
||||
false
|
||||
if (!props && template) return template
|
||||
|
@ -117,7 +117,7 @@ function useApp(full = true) {
|
|||
patterns,
|
||||
|
||||
// State
|
||||
language,
|
||||
locale,
|
||||
loading,
|
||||
navigation,
|
||||
pattern,
|
||||
|
@ -137,11 +137,11 @@ function useApp(full = true) {
|
|||
|
||||
// State handlers
|
||||
togglePrimaryMenu,
|
||||
changeLanguage,
|
||||
changeLocale,
|
||||
|
||||
// Translation
|
||||
t,
|
||||
languages: Object.keys(strings),
|
||||
locales: Object.keys(strings),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
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'
|
||||
|
||||
const LanguagePicker = ({ app }) => {
|
||||
const LocalePicker = ({ app }) => {
|
||||
return (
|
||||
<div className="dropdown">
|
||||
<div tabIndex="0" className={`
|
||||
|
@ -10,14 +10,14 @@ const LanguagePicker = ({ app }) => {
|
|||
sm:btn-ghost
|
||||
hover:bg-neutral hover:border-neutral-content
|
||||
`}>
|
||||
<LanguageIcon />
|
||||
<span>{languages[app.language]}</span>
|
||||
<LocaleIcon />
|
||||
<span>{languages[app.locale]}</span>
|
||||
</div>
|
||||
<ul tabIndex="0" className="p-2 shadow menu dropdown-content bg-base-100 rounded-box w-52">
|
||||
{Object.keys(languages).map(language => (
|
||||
<li key={language}>
|
||||
<button onClick={() => app.changeLanguage(language)} className="btn btn-ghost text-base-content hover:bg-base-200">
|
||||
{languages[language]}
|
||||
{Object.keys(app.locales).map(locale => (
|
||||
<li key={locale}>
|
||||
<button onClick={() => app.changeLocale(locale)} className="btn btn-ghost text-base-content hover:bg-base-200">
|
||||
{languages[locale]}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
|
@ -26,4 +26,4 @@ const LanguagePicker = ({ app }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default LanguagePicker
|
||||
export default LocalePicker
|
|
@ -46,7 +46,7 @@ const currentChildren = current => Object.values(order(current))
|
|||
// Shared classes for links
|
||||
// Exported for re-use
|
||||
export const linkClasses = `text-lg lg:text-xl
|
||||
py-1 hover:cursor-pointer
|
||||
py-1
|
||||
text-base-content sm:text-neutral-content
|
||||
hover:text-secondary
|
||||
sm:hover:text-secondary-focus
|
||||
|
@ -84,6 +84,7 @@ const SubLevel = ({ nodes={}, active }) => (
|
|||
<a title={child.__title} className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-pointer
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
${child.__slug === active
|
||||
|
@ -117,6 +118,7 @@ const SubLevel = ({ nodes={}, active }) => (
|
|||
pl-2 border-l-2
|
||||
grow
|
||||
${linkClasses}
|
||||
hover:cursor-pointer
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
${child.__slug === active
|
||||
|
@ -157,7 +159,13 @@ const TopLevel = ({ icon, title, nav, current, slug, hasChildren=false, active }
|
|||
`}>
|
||||
<span className="text-secondary-focus">{icon}</span>
|
||||
<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}
|
||||
</a>
|
||||
</Link>
|
||||
|
|
|
@ -11,15 +11,15 @@ const LabDraft = ({ app, pattern, gist, updateGist }) => {
|
|||
return (
|
||||
<Svg {...patternProps}>
|
||||
<Defs {...patternProps} />
|
||||
<style>{`:root { --pattern-scale: ${gist.settings.scale || 1}}`}</style>
|
||||
<style>{`:root { --pattern-scale: ${gist.scale || 1}}`}</style>
|
||||
<g>
|
||||
{Object.keys(patternProps.parts).map((name) => (
|
||||
<Part
|
||||
key={name}
|
||||
part={patternProps.parts[name]}
|
||||
language={gist.settings.locale}
|
||||
paperless={gist.settings.paperless}
|
||||
units={gist.settings.units}
|
||||
locale={gist.locale}
|
||||
paperless={gist.paperless}
|
||||
units={gist.units}
|
||||
name={name}
|
||||
app={app}
|
||||
/>
|
||||
|
|
|
@ -103,7 +103,7 @@ const Part = (props) => {
|
|||
key={name}
|
||||
name={name}
|
||||
part={props.name}
|
||||
language={props.language}
|
||||
locale={props.locale}
|
||||
path={props.part.paths[name]}
|
||||
focus={props.focus}
|
||||
topLeft={props.part.topLeft}
|
||||
|
@ -118,7 +118,7 @@ const Part = (props) => {
|
|||
key={name}
|
||||
name={name}
|
||||
part={props.name}
|
||||
language={props.language}
|
||||
locale={props.locale}
|
||||
point={props.part.points[name]}
|
||||
focus={props.focus}
|
||||
topLeft={props.part.topLeft}
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react'
|
|||
const Svg = ({
|
||||
embed = true,
|
||||
develop = false,
|
||||
language = 'en',
|
||||
locale = 'en',
|
||||
className = 'freesewing pattern',
|
||||
style = {},
|
||||
viewBox = false,
|
||||
|
@ -15,7 +15,7 @@ const Svg = ({
|
|||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
'xmlns:svg': 'http://www.w3.org/2000/svg',
|
||||
xmlnsXlink: 'http://www.w3.org/1999/xlink',
|
||||
xmlLang: language,
|
||||
xmlLang: locale,
|
||||
viewBox: viewBox || `0 0 ${width} ${height}`,
|
||||
className,
|
||||
style
|
||||
|
|
|
@ -3,7 +3,7 @@ const TextOnPath = (props) => {
|
|||
// Handle translation
|
||||
let translated = ''
|
||||
for (let string of props.path.attributes.getAsArray('data-text')) {
|
||||
translated += props.app.t(string, false, props.language).replace(/"/g, '"') + ' '
|
||||
translated += props.app.t(string, false, props.locale).replace(/"/g, '"') + ' '
|
||||
}
|
||||
const textPathProps = {
|
||||
xlinkHref: '#' + props.pathId,
|
||||
|
|
|
@ -3,7 +3,7 @@ const Text = (props) => {
|
|||
// Handle translation
|
||||
let translated = ''
|
||||
for (let string of props.point.attributes.getAsArray('data-text')) {
|
||||
translated += props.app.t(string.toString(), false, props.language).replace(/"/g, '"') + ' '
|
||||
translated += props.app.t(string.toString(), false, props.locale).replace(/"/g, '"') + ' '
|
||||
}
|
||||
// Handle muti-line text
|
||||
if (translated.indexOf('\n') !== -1) {
|
||||
|
|
|
@ -50,7 +50,7 @@ const DesignOptionPctDeg = props => {
|
|||
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(`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>
|
||||
<div className="flex flex-row justify-between">
|
||||
{editOption
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useState } from 'react'
|
|||
|
||||
const CoreSettingList = props => {
|
||||
const { dflt, list } = props
|
||||
const val = props.gist?.settings?.[props.setting]
|
||||
const val = props.gist?.[props.setting]
|
||||
|
||||
const [value, setValue] = useState(val)
|
||||
|
||||
|
@ -10,10 +10,15 @@ const CoreSettingList = props => {
|
|||
if (newVal === dflt) reset()
|
||||
else {
|
||||
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 (
|
||||
<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">
|
||||
|
|
|
@ -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
|
|
@ -4,9 +4,16 @@ import Setting from './setting.js'
|
|||
|
||||
const settings = {
|
||||
locale: {
|
||||
dflt: 'en',
|
||||
list: ['de', 'en', 'es', 'fr', 'nl'],
|
||||
},
|
||||
units: {
|
||||
list: ['metric', 'imperial'],
|
||||
},
|
||||
margin: {
|
||||
min: 0,
|
||||
max: 25,
|
||||
dflt: 2,
|
||||
},
|
||||
}
|
||||
|
||||
const CoreSettings = props => {
|
||||
|
@ -22,7 +29,7 @@ const CoreSettings = props => {
|
|||
items-center
|
||||
`}>
|
||||
<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')}
|
||||
</span>
|
||||
<Chevron />
|
||||
|
|
|
@ -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 CountOption from 'shared/components/workbench/inputs/design-option-count'
|
||||
import ListSetting from './core-setting-list'
|
||||
import MmSetting from './core-setting-mm'
|
||||
import { formatMm, formatPercentage, optionType } from 'shared/utils.js'
|
||||
|
||||
const settings = {
|
||||
locale: props => {
|
||||
return (
|
||||
<span className="text-secondary">
|
||||
{props.app.t(`i18n.${props.gist.settings.locale}`)}
|
||||
{props.app.t(`i18n.${props.gist.locale}`)}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
pct: props => {
|
||||
const val = (typeof props.gist?.options?.[props.option] === 'undefined')
|
||||
? props.pattern.config.options[props.option].pct/100
|
||||
: props.gist.options[props.option]
|
||||
units: props => {
|
||||
return (
|
||||
<span className={
|
||||
val=== props.pattern.config.options[props.option].pct/100
|
||||
? '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 className="text-secondary">
|
||||
{props.app.t(`app.${props.gist.units}Units`)}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
bool: props => {
|
||||
const dflt = props.pattern.config.options[props.option].bool
|
||||
const current = props.gist?.options?.[props.option]
|
||||
margin: props => {
|
||||
return (
|
||||
<span className={
|
||||
(dflt==current || typeof current === 'undefined')
|
||||
? 'text-secondary'
|
||||
: 'text-accent'
|
||||
}>
|
||||
{props.gist?.options?.[props.option]
|
||||
? props.app.t('app.yes')
|
||||
: props.app.t('app.no')
|
||||
}
|
||||
</span>
|
||||
<span className="text-secondary" dangerouslySetInnerHTML={{
|
||||
__html: formatMm(props.gist.margin, props.gist.units)
|
||||
}} />
|
||||
)
|
||||
},
|
||||
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}°</span>
|
||||
: <span className="text-accent">{current}°</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>
|
||||
|
@ -73,14 +34,21 @@ const Tmp = props => <p>not yet</p>
|
|||
const inputs = {
|
||||
locale: props => <ListSetting
|
||||
{...props}
|
||||
list={props.app.languages.map(key => ({
|
||||
list={props.config.list.map(key => ({
|
||||
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 Input = inputs[props.setting]
|
||||
const Value = settings[props.setting]
|
||||
|
@ -109,6 +77,7 @@ const Setting = props => {
|
|||
<div className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-pointer
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
text-base-content sm:text-neutral-content
|
||||
|
@ -143,6 +112,7 @@ const Setting = props => {
|
|||
<div className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-resize
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
text-base-content sm:text-neutral-content
|
||||
|
|
|
@ -18,6 +18,7 @@ const OptionGroup = props => {
|
|||
<div className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-resize
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
text-base-content sm:text-neutral-content
|
||||
|
|
|
@ -16,6 +16,7 @@ const OptionSubGroup = props => {
|
|||
<div className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-resize
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
text-base-content sm:text-neutral-content
|
||||
|
|
|
@ -37,7 +37,7 @@ const Modes = props => {
|
|||
items-center
|
||||
`}>
|
||||
<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')}
|
||||
</span>
|
||||
<Chevron />
|
||||
|
@ -48,6 +48,7 @@ const Modes = props => {
|
|||
<button title={entry.title} className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-pointer
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
text-left
|
||||
|
|
|
@ -10,13 +10,13 @@ import defaultSettings from 'shared/components/workbench/default-settings.js'
|
|||
|
||||
|
||||
// Generates a default pattern gist to start from
|
||||
const defaultGist = (pattern, language='en') => {
|
||||
const defaultGist = (pattern, locale='en') => {
|
||||
const gist = {
|
||||
design: pattern.config.name,
|
||||
version: pattern.config.version,
|
||||
settings: defaultSettings
|
||||
...defaultSettings
|
||||
}
|
||||
if (language) gist.settings.locale = language
|
||||
if (locale) gist.locale = locale
|
||||
|
||||
return gist
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ const WorkbenchWrapper = ({ app, pattern }) => {
|
|||
|
||||
// State for display mode and gist
|
||||
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,
|
||||
// force mode to measurements
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue