wip: Updates to lab to support v3 patterns
This commit is contained in:
parent
d59bab6a6c
commit
a023f1810c
19 changed files with 101 additions and 155 deletions
|
@ -3,7 +3,6 @@ import Link from 'next/link'
|
|||
import ThemePicker from 'shared/components/theme-picker.js'
|
||||
import LocalePicker from 'shared/components/locale-picker.js'
|
||||
import PatternPicker from 'site/components/pattern-picker.js'
|
||||
import VersionPicker from 'site/components/version-picker.js'
|
||||
import CloseIcon from 'shared/components/icons/close.js'
|
||||
import MenuIcon from 'shared/components/icons/menu.js'
|
||||
|
||||
|
@ -69,7 +68,6 @@ const Header = ({ app }) => {
|
|||
</button>
|
||||
<div className="hidden md:flex flex-row items-center gap-2">
|
||||
<PatternPicker app={app} />
|
||||
<VersionPicker app={app} />
|
||||
</div>
|
||||
<div className="hidden md:flex md:flex-row gap-2">
|
||||
<Link href="/">
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import ThemePicker from 'shared/components/theme-picker.js'
|
||||
import LocalePicker from 'shared/components/locale-picker.js'
|
||||
import VersionPicker from 'site/components/version-picker.js'
|
||||
|
||||
export const BeforeNav = ({ app }) => (
|
||||
<>
|
||||
|
@ -9,7 +8,6 @@ export const BeforeNav = ({ app }) => (
|
|||
<LocalePicker app={app} />
|
||||
</div>
|
||||
<div className="md:hidden flex flex-row flex-wrap sm:flex-nowrap gap-2 mb-2">
|
||||
<VersionPicker app={app} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import React from 'react'
|
||||
import VersionsIcon from 'shared/components/icons/versions.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import versions from 'site/versions.json'
|
||||
import useVersion from 'site/hooks/useVersion.js'
|
||||
import {Picker, PickerLink} from 'shared/components/picker'
|
||||
|
||||
export const defaultVersion = 'next'
|
||||
|
||||
export const formatVersionTitle = version => (!version || version === defaultVersion)
|
||||
? `${defaultVersion} version`
|
||||
: `Version ${version}`
|
||||
|
||||
export const formatVersionUri = (version=false, design=false) => {
|
||||
if (!version && !design) return '/'
|
||||
if (!version && design) return `/${design}`
|
||||
if (version && !design) return `/v/${version}`
|
||||
|
||||
return `/v/${version}/${design}`
|
||||
}
|
||||
|
||||
const PatternPicker = ({ app }) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
const version = useVersion()
|
||||
|
||||
const pickerProps = {
|
||||
title: formatVersionTitle(version),
|
||||
Icon: VersionsIcon,
|
||||
ariaLabel: t('versionPicker')
|
||||
}
|
||||
|
||||
return (<Picker {...pickerProps}>
|
||||
{[defaultVersion, ...versions].map(v => (
|
||||
<PickerLink key={v} href={formatVersionUri(v)}>{formatVersionTitle(v)}</PickerLink>
|
||||
))}
|
||||
</Picker>)
|
||||
}
|
||||
|
||||
export default PatternPicker
|
|
@ -2,17 +2,15 @@ import { useState } from 'react'
|
|||
// Stores state in local storage
|
||||
import useLocalStorage from 'shared/hooks/useLocalStorage.js'
|
||||
// Designs
|
||||
import { designsByType } from 'config/software/index.mjs'
|
||||
import { designsByType } from 'prebuild/designs-by-type.mjs'
|
||||
// Locale and translation
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { capitalize } from 'shared/utils.mjs'
|
||||
import { formatVersionUri } from '../components/version-picker.js'
|
||||
import useVersion from 'site/hooks/useVersion.js'
|
||||
import useTheme from 'shared/hooks/useTheme'
|
||||
|
||||
// Initial navigation
|
||||
const initialNavigation = (t, version) => {
|
||||
const initialNavigation = t => {
|
||||
const base = {
|
||||
accessories: {
|
||||
__title: t('accessoryDesigns'),
|
||||
|
@ -35,7 +33,7 @@ const initialNavigation = (t, version) => {
|
|||
for (const design in designsByType[type]) {
|
||||
base[type][design] = {
|
||||
__title: capitalize(design),
|
||||
__slug: formatVersionUri(version,design)
|
||||
__slug: design
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,9 +55,6 @@ for (const type in designsByType) {
|
|||
|
||||
function useApp(full = true) {
|
||||
|
||||
// Version
|
||||
const version = useVersion()
|
||||
|
||||
// Load translation method
|
||||
const locale = useRouter().locale
|
||||
const { t } = useTranslation(['app'])
|
||||
|
@ -70,7 +65,7 @@ function useApp(full = true) {
|
|||
|
||||
// React State
|
||||
const [primaryMenu, setPrimaryMenu] = useState(false)
|
||||
const [navigation, setNavigation] = useState(initialNavigation(t, version))
|
||||
const [navigation, setNavigation] = useState(initialNavigation(t))
|
||||
const [slug, setSlug] = useState('/')
|
||||
const [design, setDesign] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import { useRouter } from 'next/router'
|
||||
|
||||
export const defaultVersion = 'next'
|
||||
|
||||
const useVersion = () => {
|
||||
const { pathname } = useRouter()
|
||||
const chunks = pathname.split('/')
|
||||
const version = (chunks[1] === 'v')
|
||||
? chunks[2]
|
||||
: defaultVersion
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
export default useVersion
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import path from 'path'
|
||||
import i18nConfig from './next-i18next.config.js'
|
||||
import { designs, plugins } from '../../config/software/index.mjs'
|
||||
import { designs } from './prebuild/designs.mjs'
|
||||
import { plugins } from './prebuild/plugins.mjs'
|
||||
import { banner } from '../../scripts/banner.mjs'
|
||||
|
||||
let greeting = false
|
||||
|
@ -14,11 +15,11 @@ const config = {
|
|||
webpack: (config, options) => {
|
||||
|
||||
// JSON support
|
||||
config.module.rules.push({
|
||||
test: /\.json$/,
|
||||
type: 'json',
|
||||
use: 'json-loader'
|
||||
})
|
||||
//config.module.rules.push({
|
||||
// test: /\.json$/,
|
||||
// type: 'json',
|
||||
// use: 'json-loader'
|
||||
//})
|
||||
|
||||
// YAML support
|
||||
config.module.rules.push({
|
||||
|
@ -36,7 +37,7 @@ const config = {
|
|||
// Aliases
|
||||
config.resolve.alias.shared = path.resolve('../shared/')
|
||||
config.resolve.alias.site = path.resolve('.')
|
||||
config.resolve.alias.lib = path.resolve('./lib')
|
||||
config.resolve.alias.prebuild = path.resolve('./prebuild')
|
||||
config.resolve.alias.config = path.resolve('../../config/')
|
||||
config.resolve.alias.designs = path.resolve('../../designs/')
|
||||
config.resolve.alias.plugins = path.resolve('../../plugins/')
|
||||
|
|
|
@ -2,12 +2,10 @@ import Page from 'site/components/wrappers/page.js'
|
|||
import useApp from 'site/hooks/useApp.js'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { formatVersionTitle } from 'site/components/version-picker.js'
|
||||
import Layout from 'site/components/layouts/bare'
|
||||
import { PageTitle } from 'shared/components/layouts/default'
|
||||
import availableVersions from 'site/available-versions.json'
|
||||
|
||||
const DesignLinks = ({ list, prefix='', version=false }) => {
|
||||
const DesignLinks = ({ list, prefix='' }) => {
|
||||
const { t } = useTranslation(['patterns'])
|
||||
|
||||
return (
|
||||
|
@ -27,7 +25,7 @@ const DesignLinks = ({ list, prefix='', version=false }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const PatternListPageTemplate = ({ section=false, version=false }) => {
|
||||
const PatternListPageTemplate = ({ section=false }) => {
|
||||
const app = useApp()
|
||||
const { t } = useTranslation(['app'])
|
||||
|
||||
|
@ -35,45 +33,22 @@ const PatternListPageTemplate = ({ section=false, version=false }) => {
|
|||
? app.navigation[section].__title
|
||||
: t('designs')
|
||||
|
||||
const sectionDesigns = (section=false, version=false) => {
|
||||
const sectionDesigns = (section=false) => {
|
||||
if (!section) {
|
||||
const all = []
|
||||
if (!version || version === 'next') {
|
||||
for (const section in app.designs) all.push(...app.designs[section])
|
||||
return all
|
||||
}
|
||||
else if (availableVersions[version]) return availableVersions[version]
|
||||
} else {
|
||||
if (!version || version === 'next') return app.designs[section]
|
||||
else if (availableVersions[version]) return availableVersions[version]
|
||||
}
|
||||
for (const section in app.designs) all.push(...app.designs[section])
|
||||
return all
|
||||
} else return app.designs[section]
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
return (
|
||||
<Page app={app} title={`FreeSewing Lab: ${formatVersionTitle(version)}`} layout={Layout}>
|
||||
<Page app={app} title={`FreeSewing Lab: ${title}`} layout={Layout}>
|
||||
<div className="max-w-7xl m-auto py-20 md:py-36 min-h-screen">
|
||||
<section className="px-8">
|
||||
<PageTitle app={app} slug={section ? app.navigation[section].__slug : '/' } title={title} />
|
||||
{version && version !== 'next'
|
||||
? (
|
||||
<ul className="flex flex-col flex-wrap gap-2">
|
||||
{availableVersions[version].map( d => (
|
||||
<li key={d} className="p-2">
|
||||
<Link href={`/v/${version}/${d}`}>
|
||||
<a className="capitalize text-xl p-4 font-bold text-secondary hover:text-secondary-focus hover:underline">
|
||||
{t(`patterns:${d}.t`)}
|
||||
<br />
|
||||
<span className="text-lg font-normal p-4 text-base-content">{t(`patterns:${d}.d`)}</span>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
: <DesignLinks list={sectionDesigns(section)} />
|
||||
}
|
||||
<DesignLinks list={sectionDesigns(section)} />
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Json from 'shared/components/json.js'
|
||||
import Json from 'shared/components/json-highlight.js'
|
||||
|
||||
const GistAsJson = props => (
|
||||
<div className="max-w-screen-xl m-auto">
|
|
@ -1,33 +1,33 @@
|
|||
import React, {useMemo, useEffect, useState} from 'react'
|
||||
import MeasurementInput from '../inputs/measurement.js'
|
||||
import { withBreasts, withoutBreasts } from '@freesewing/models'
|
||||
import { menswear, womenswear } from '@freesewing/models'
|
||||
import nonHuman from './non-human.js'
|
||||
import WithBreastsIcon from 'shared/components/icons/with-breasts.js'
|
||||
import WithoutBreastsIcon from 'shared/components/icons/without-breasts.js'
|
||||
import WomenswearIcon from 'shared/components/icons/womenswear.js'
|
||||
import MenswearIcon from 'shared/components/icons/menswear.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Setting from '../menu/core-settings/setting';
|
||||
import {settings} from '../menu/core-settings/index';
|
||||
import { Tab, Tabs } from 'shared/components/mdx/tabs.js'
|
||||
|
||||
|
||||
const groups = {
|
||||
people: {
|
||||
with: withBreasts,
|
||||
without: withoutBreasts,
|
||||
},
|
||||
people: { menswear, womenswear },
|
||||
dolls: {
|
||||
with: nonHuman.withBreasts.dolls,
|
||||
without: nonHuman.withoutBreasts.dolls,
|
||||
menswear: nonHuman.menswear.dolls,
|
||||
womenswear: nonHuman.womenswear.dolls,
|
||||
},
|
||||
giants: {
|
||||
with: nonHuman.withBreasts.giants,
|
||||
without: nonHuman.withoutBreasts.giants,
|
||||
menswear: nonHuman.menswear.giants,
|
||||
womenswear: nonHuman.womenswear.giants,
|
||||
}
|
||||
}
|
||||
|
||||
const icons = {
|
||||
with: <WithBreastsIcon />,
|
||||
without: <WithoutBreastsIcon />,
|
||||
menswear: <MenswearIcon />,
|
||||
womenswear: <WomenswearIcon />,
|
||||
}
|
||||
|
||||
|
||||
const WorkbenchMeasurements = ({ app, design, gist, updateGist, gistReady }) => {
|
||||
const { t } = useTranslation(['app', 'cfp'])
|
||||
|
||||
|
@ -72,7 +72,7 @@ const WorkbenchMeasurements = ({ app, design, gist, updateGist, gistReady }) =>
|
|||
<Tab tabId={group}>
|
||||
{Object.keys(icons).map(type => (
|
||||
<React.Fragment key={type}>
|
||||
<h4 className="mt-4">{t(`${type}Breasts`)}</h4>
|
||||
<h4 className="mt-4">{t(type)}</h4>
|
||||
<ul className="flex flex-row flex-wrap gap-2">
|
||||
{Object.keys(groups[group][type]).map((m) => (
|
||||
<li key={`${m}-${type}-${group}`} className="">
|
||||
|
@ -81,9 +81,8 @@ const WorkbenchMeasurements = ({ app, design, gist, updateGist, gistReady }) =>
|
|||
onClick={() => updateMeasurements(groups[group][type][m], false)}
|
||||
>
|
||||
{icons[type]}
|
||||
{t('size')}
|
||||
{ group === 'people'
|
||||
? m.replace('size', '')
|
||||
? m.slice(-2)
|
||||
: m
|
||||
}
|
||||
</button>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { withBreasts, withoutBreasts } from '@freesewing/models'
|
||||
import { menswear42, womenswear34 } from '@freesewing/models'
|
||||
|
||||
const nonHuman = {
|
||||
withoutBreasts: {
|
||||
menswear: {
|
||||
dolls: {},
|
||||
giants: {}
|
||||
},
|
||||
withBreasts: {
|
||||
womenswear: {
|
||||
dolls: {},
|
||||
giants: {}
|
||||
}
|
||||
|
@ -14,34 +14,34 @@ const round = val => Math.round(val*10)/10
|
|||
|
||||
for (let i=0.1;i<0.7;i+=0.1) {
|
||||
const name = `${Math.round(i*10)}/10`
|
||||
nonHuman.withBreasts.dolls[name] = {}
|
||||
// withBreasts: Based on Anneke (size 34)
|
||||
for (const [m, val] of Object.entries(withBreasts.size34)) {
|
||||
nonHuman.withBreasts.dolls[name][m] = (m === 'shoulderSlope')
|
||||
nonHuman.womenswear.dolls[name] = {}
|
||||
// womenswear: Based on womenswear34
|
||||
for (const [m, val] of Object.entries(womenswear34)) {
|
||||
nonHuman.womenswear.dolls[name][m] = (m === 'shoulderSlope')
|
||||
? val
|
||||
: round(val * i)
|
||||
}
|
||||
// withoutBreasts: Based on Ronan (size 42)
|
||||
nonHuman.withoutBreasts.dolls[name] = {}
|
||||
for (const [m, val] of Object.entries(withoutBreasts.size42)) {
|
||||
nonHuman.withoutBreasts.dolls[name][m] = (m === 'shoulderSlope')
|
||||
nonHuman.menswear.dolls[name] = {}
|
||||
// menswear: Based on menswear42
|
||||
for (const [m, val] of Object.entries(menswear42)) {
|
||||
nonHuman.menswear.dolls[name][m] = (m === 'shoulderSlope')
|
||||
? val
|
||||
: round(val * i)
|
||||
}
|
||||
}
|
||||
for (let i=1.5;i<=3;i+=0.5) {
|
||||
const name = `${i}/1`
|
||||
nonHuman.withBreasts.giants[name] = {}
|
||||
// withBreasts: Based on Anneke (size 34)
|
||||
for (const [m, val] of Object.entries(withBreasts.size34)) {
|
||||
nonHuman.withBreasts.giants[name][m] = (m === 'shoulderSlope')
|
||||
nonHuman.womenswear.giants[name] = {}
|
||||
// womenswear: Based on womenswear34
|
||||
for (const [m, val] of Object.entries(womenswear34)) {
|
||||
nonHuman.womenswear.giants[name][m] = (m === 'shoulderSlope')
|
||||
? val
|
||||
: round(val * i)
|
||||
}
|
||||
nonHuman.withoutBreasts.giants[name] = {}
|
||||
// withoutBreasts: Based on Ronan (size 42)
|
||||
for (const [m, val] of Object.entries(withoutBreasts.size42)) {
|
||||
nonHuman.withoutBreasts.giants[name][m] = (m === 'shoulderSlope')
|
||||
nonHuman.menswear.giants[name] = {}
|
||||
// menswear: Based on menswear42
|
||||
for (const [m, val] of Object.entries(menswear42)) {
|
||||
nonHuman.menswear.giants[name][m] = (m === 'shoulderSlope')
|
||||
? val
|
||||
: round(val * i)
|
||||
}
|
||||
|
|
|
@ -3,9 +3,11 @@ import { Chevron } from 'shared/components/navigation/primary.js'
|
|||
import OptionGroup from './option-group'
|
||||
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { optionsMenuStructure } from 'shared/utils.mjs'
|
||||
|
||||
const DesignOptions = props => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const optionsMenu = optionsMenuStructure(props.design.config.options)
|
||||
|
||||
return (
|
||||
<Details open>
|
||||
|
@ -14,12 +16,14 @@ const DesignOptions = props => {
|
|||
<Chevron />
|
||||
</TopSummary>
|
||||
<Ul className="pl-5 list-inside">
|
||||
{Object.keys(props.design.config.optionGroups).map(group => (
|
||||
<OptionGroup {...props} group={group} key={group} />
|
||||
))}
|
||||
{Object.entries(optionsMenu).map(([group, options]) => typeof options === "string"
|
||||
? <p>top-level option</p>
|
||||
: <OptionGroup {...props} group={group} options={options} key={group} />
|
||||
)}
|
||||
</Ul>
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
|
||||
export default DesignOptions
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useTranslation } from 'next-i18next'
|
|||
|
||||
const OptionGroup = props => {
|
||||
const { t } = useTranslation(['optiongroups'])
|
||||
const config = props.config || props.design.config.optionGroups[props.group]
|
||||
|
||||
return (
|
||||
<Li>
|
||||
<Details>
|
||||
|
@ -19,10 +19,10 @@ const OptionGroup = props => {
|
|||
<Chevron />
|
||||
</Summary>
|
||||
<Ul>
|
||||
{config.map(option =>
|
||||
typeof option === 'string' ? <Option {...props} option={option} key={option} />
|
||||
: Object.keys(option).map((sub) => <OptionGroup {...props} config={option[sub]} group={sub} key={sub}/>)
|
||||
)}
|
||||
{Object.entries(props.options).map(([option, type]) => typeof type === "string"
|
||||
? <Option {...props} type={type} option={option} key={option} />
|
||||
: <OptionGroup {...props} group={option} options={type} key={option}/>)
|
||||
}
|
||||
</Ul>
|
||||
</Details>
|
||||
</Li>
|
||||
|
|
|
@ -12,7 +12,7 @@ import Measurements from 'shared/components/workbench/measurements/index.js'
|
|||
import LabDraft from 'shared/components/workbench/draft/index.js'
|
||||
import LabSample from 'shared/components/workbench/sample.js'
|
||||
import ExportDraft from 'shared/components/workbench/exporting/index.js'
|
||||
import GistAsJson from 'shared/components/workbench/json.js'
|
||||
import GistAsJson from 'shared/components/workbench/gist-as-json.js'
|
||||
import GistAsYaml from 'shared/components/workbench/yaml.js'
|
||||
import DraftEvents from 'shared/components/workbench/events.js'
|
||||
import CutLayout from 'shared/components/workbench/layout/cut'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import fs_ from 'fs'
|
||||
import path from 'path'
|
||||
import { capitalize } from '../utils.mjs'
|
||||
import { designsByType } from '../../../config/software/index.mjs'
|
||||
import { designsByType, plugins, designs } from '../../../config/software/index.mjs'
|
||||
|
||||
const fs = fs_.promises
|
||||
|
||||
|
@ -43,7 +43,6 @@ export const prebuildLab = async (site) => {
|
|||
// Iterate over sections
|
||||
console.log(`Generating pages for ${section} designs`)
|
||||
for (const design in designsByType[section]) {
|
||||
|
||||
// Generate pattern pages for next
|
||||
console.log(` - ${design}`)
|
||||
const page = pageTemplate(design)
|
||||
|
@ -62,6 +61,24 @@ export const prebuildLab = async (site) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Write designs file
|
||||
const header = "// This file is auto-generated by the prebuild script | Any changes will be overwritten\n"
|
||||
const nl = "\n"
|
||||
promises.push(
|
||||
fs.writeFile(
|
||||
path.resolve('..', 'lab', 'prebuild', 'designs.mjs'),
|
||||
`${header}export const designs = ${JSON.stringify(Object.keys(designs))}${nl}`
|
||||
),
|
||||
fs.writeFile(
|
||||
path.resolve('..', 'lab', 'prebuild', 'plugins.mjs'),
|
||||
`${header}export const plugins = ${JSON.stringify(Object.keys(plugins))}${nl}`
|
||||
),
|
||||
fs.writeFile(
|
||||
path.resolve('..', 'lab', 'prebuild', 'designs-by-type.mjs'),
|
||||
`${header}export const designsByType = ${JSON.stringify(designsByType)}${nl}`
|
||||
),
|
||||
)
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import get from 'lodash.get'
|
||||
import set from 'lodash.set'
|
||||
|
||||
// Generic rounding method
|
||||
export const round = (val, decimals=1) => Math.round(val*Math.pow(10, decimals))/Math.pow(10, decimals)
|
||||
|
@ -159,3 +160,16 @@ export const measurementAsMm = (value, units = "metric") => {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const optionsMenuStructure = options => {
|
||||
if (!options) return options
|
||||
const menu = {}
|
||||
for (const [name, option] of Object.entries(options)) {
|
||||
if (typeof option === 'object') {
|
||||
set(menu, (option.menu ? `${option.menu}.${name}` : name), optionType(option))
|
||||
}
|
||||
}
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue