wip: Started working on new development environment
This commit is contained in:
parent
ace86eaf85
commit
54aefa8437
45 changed files with 1722 additions and 43 deletions
192
packages/freesewing.shared/components/workbench/index.js
Normal file
192
packages/freesewing.shared/components/workbench/index.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
import Link from 'next/link'
|
||||
import orderBy from 'lodash.orderby'
|
||||
import ThemePicker from 'shared/components/theme-picker.js'
|
||||
import RssIcon from 'shared/components/icons/rss.js'
|
||||
import TutorialIcon from 'shared/components/icons/tutorial.js'
|
||||
import GuideIcon from 'shared/components/icons/guide.js'
|
||||
import HelpIcon from 'shared/components/icons/help.js'
|
||||
import DocsIcon from 'shared/components/icons/docs.js'
|
||||
|
||||
// Don't show children for blog and showcase posts
|
||||
const keepClosed = ['blog', 'showcase', ]
|
||||
|
||||
// TODO: For now we force tailwind to pickup these styles
|
||||
// At some point this should 'just work' though, but let's not worry about it now
|
||||
const force = [
|
||||
<p className="w-6 mr-2"/>,
|
||||
<p className="w-8 mr-3"/>
|
||||
]
|
||||
|
||||
// List of icons matched to top-level slug
|
||||
const icons = {
|
||||
blog: <RssIcon />,
|
||||
tutorials: <TutorialIcon />,
|
||||
guides: <GuideIcon />,
|
||||
howtos: <HelpIcon />,
|
||||
reference: <DocsIcon />
|
||||
}
|
||||
|
||||
/* helper method to order nav entries */
|
||||
const order = obj => orderBy(obj, ['__order', '__title'], ['asc', 'asc'])
|
||||
|
||||
// Component for the collapse toggle
|
||||
const Chevron = ({w=8, m=2}) => <svg className={`
|
||||
fill-current opacity-75 w-${w} h-${w} mr-${m}
|
||||
details-toggle hover:text-secondary sm:hover:text-secondary-focus
|
||||
`}
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
|
||||
<path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"/>
|
||||
</svg>
|
||||
|
||||
// Helper method to filter out the real children
|
||||
const currentChildren = current => Object.values(order(current))
|
||||
.filter(entry => (typeof entry === 'object'))
|
||||
|
||||
// Shared classes for links
|
||||
const linkClasses = `text-lg lg:text-xl
|
||||
py-1 hover:cursor-pointer
|
||||
text-base-content sm:text-neutral-content
|
||||
hover:text-secondary
|
||||
sm:hover:text-secondary-focus
|
||||
`
|
||||
|
||||
// Figure out whether a page is on the path to the active page
|
||||
const isActive = (slug, active) => {
|
||||
if (slug === active) return true
|
||||
let result = true
|
||||
const slugParts = slug.split('/')
|
||||
const activeParts = active.split('/')
|
||||
for (const i in slugParts) {
|
||||
if (slugParts[i] !== activeParts[i]) result = false
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Component that renders a sublevel of navigation
|
||||
const SubLevel = ({ nodes={}, active }) => (
|
||||
<ul className="pl-5 list-inside">
|
||||
{currentChildren(nodes).map(child => (Object.keys(child).length > 4)
|
||||
? (
|
||||
<li key={child.__slug} className="flex flex-row">
|
||||
<details className="grow" open={isActive(child.__slug, active)}>
|
||||
<summary className={`
|
||||
flex flex-row
|
||||
px-2
|
||||
text-base-content
|
||||
sm:text-neutral-content
|
||||
hover:cursor-row-resize
|
||||
items-center
|
||||
`}>
|
||||
<Link href={`/${child.__slug}`}>
|
||||
<a title={child.__title} className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
${child.__slug === active
|
||||
? 'text-secondary border-secondary sm:text-secondary-focus sm:border-secondary-focus'
|
||||
: 'text-base-content sm:text-neutral-content'
|
||||
}
|
||||
`}>
|
||||
<span className={`
|
||||
text-3xl mr-2 inline-block p-0 leading-3
|
||||
${child.__slug === active
|
||||
? 'text-secondary sm:text-secondary-focus translate-y-1'
|
||||
: 'translate-y-3'
|
||||
}
|
||||
`}>
|
||||
{child.__slug === active ? <>•</> : <>°</>}
|
||||
</span>
|
||||
<span className={child.__slug === active ? 'font-bold' : ''}>
|
||||
{ child.__linktitle || child.__title }
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
<Chevron w={6} m={3}/>
|
||||
</summary>
|
||||
<SubLevel nodes={child} active={active} />
|
||||
</details>
|
||||
</li>
|
||||
) : (
|
||||
<li className='pl-2 flex flex-row items-center' key={child.__slug}>
|
||||
<Link href={`/${child.__slug}`} title={child.__title}>
|
||||
<a className={`
|
||||
pl-2 border-l-2
|
||||
grow
|
||||
${linkClasses}
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
${child.__slug === active
|
||||
? 'text-secondary border-secondary sm:text-secondary-focus sm:border-secondary-focus'
|
||||
: 'text-base-content sm:text-neutral-content'
|
||||
}`}>
|
||||
<span className={`
|
||||
text-3xl mr-2 inline-block p-0 leading-3
|
||||
${child.__slug === active
|
||||
? 'text-secondary sm:text-secondary-focus translate-y-1'
|
||||
: 'translate-y-3'
|
||||
}
|
||||
`}>
|
||||
{child.__slug === active ? <>•</> : <>°</>}
|
||||
</span>
|
||||
<span className={child.__slug === active ? 'font-bold' : ''}>
|
||||
{child.__linktitle}
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
|
||||
)}
|
||||
</ul>
|
||||
)
|
||||
|
||||
// Component that renders a toplevel of navigation
|
||||
const TopLevel = ({ icon, title, nav, current, slug, hasChildren=false, active }) => (
|
||||
<details className='py-1' open={((keepClosed.indexOf(current.__slug) === -1) ? 1 : 0)}>
|
||||
<summary className={`
|
||||
flex flex-row uppercase gap-4 font-bold text-lg
|
||||
hover:cursor-row-resize
|
||||
p-2
|
||||
text-base-content
|
||||
sm:text-neutral-content
|
||||
items-center
|
||||
`}>
|
||||
<span className="text-secondary-focus">{icon}</span>
|
||||
<Link href={`/${slug}`}>
|
||||
<a className={`grow ${linkClasses} ${slug === active ? 'text-secondary sm:text-secondary-focus' : ''}`}>
|
||||
{title}
|
||||
</a>
|
||||
</Link>
|
||||
{hasChildren && <Chevron />}
|
||||
</summary>
|
||||
{hasChildren && <SubLevel nodes={current} active={active} />}
|
||||
</details>
|
||||
)
|
||||
|
||||
const Navigation = ({ app, active }) => {
|
||||
if (!app.navigation) return null
|
||||
const output = []
|
||||
for (const page of order(app.navigation)) output.push(<TopLevel
|
||||
key={page.__slug}
|
||||
icon={icons[page.__slug] || <span className="text-3xl mr-2 translate-y-3 inline-block p-0 leading-3">°</span>}
|
||||
title={page.__title}
|
||||
slug={page.__slug}
|
||||
hasChildren={keepClosed.indexOf(page.__slug) === -1}
|
||||
nav={app.navigation}
|
||||
current={order(app.navigation[page.__slug])}
|
||||
active={active}
|
||||
/>)
|
||||
|
||||
return <div className='pb-20'>{output}</div>
|
||||
}
|
||||
|
||||
const PrimaryMenu = ({ app, active }) => (
|
||||
<nav className="sm:max-w-lg grow mb-12">
|
||||
<ThemePicker app={app} className="w-full sm:hidden"/>
|
||||
<Navigation app={app} active={active} />
|
||||
</nav>
|
||||
)
|
||||
|
||||
export default PrimaryMenu
|
|
@ -0,0 +1,88 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
/*
|
||||
* This is a single input for a measurements
|
||||
* Note that it keeps local state with whatever the user types
|
||||
* but will only trigger a gist update if the input is valid.
|
||||
*
|
||||
* m holds the measurement name. It's just so long to type
|
||||
* measurement and I always have some typo in it because dyslexia.
|
||||
*/
|
||||
const MeasurementInput = ({ m, gist, app, updateMeasurements }) => {
|
||||
const prefix = (app.site === 'org') ? '' : 'https://freesewing.org'
|
||||
const title = app.t(`measurements.${m}`)
|
||||
console.log('render', m)
|
||||
const isValid = input => {
|
||||
if (input === null || input === '') return null
|
||||
return !isNaN(input)
|
||||
}
|
||||
|
||||
const update = evt => {
|
||||
setVal(evt.target.value)
|
||||
const ok = isValid(evt.target.value)
|
||||
console.log({ok})
|
||||
if (ok) {
|
||||
setValid(true)
|
||||
updateMeasurements(evt.target.value, m)
|
||||
} else setValid(false)
|
||||
}
|
||||
|
||||
const [val, setVal] = useState(gist?.measurements?.[m] || null)
|
||||
const [valid, setValid] = useState(typeof gist?.measurements?.[m] === 'undefined'
|
||||
? null :
|
||||
isValid(gist.measurements[m])
|
||||
)
|
||||
|
||||
if (!m) return null
|
||||
|
||||
return (
|
||||
<div className="form-control mb-2" key={`wrap-${m}`}>
|
||||
<label className="label">
|
||||
<span className="label-text font-bold text-xl">{title}</span>
|
||||
<a
|
||||
href={`${prefix}/docs/measurements/${m.toLowerCase()}`}
|
||||
className="label-text-alt text-secondary hover:text-secondary-focus hover:underline"
|
||||
title={`${app.t('docs')}: ${app.t(`measurements.${m}`)}`}
|
||||
tabIndex="-1"
|
||||
>
|
||||
{app.t('docs')}
|
||||
</a>
|
||||
</label>
|
||||
<label className="input-group input-group-lg">
|
||||
<input
|
||||
key={`input-${m}`}
|
||||
type="text"
|
||||
placeholder={title}
|
||||
className={`
|
||||
input input-lg input-bordered grow text-base-content
|
||||
${valid === false && 'input-error'}
|
||||
${valid === true && 'input-success'}
|
||||
`}
|
||||
value={val}
|
||||
onChange={update}
|
||||
/>
|
||||
<span className={`
|
||||
${valid === false && 'bg-error text-neutral-content'}
|
||||
${valid === true && 'bg-success text-neutral-content'}
|
||||
${valid === null && 'bg-base-200 text-base-content'}
|
||||
`}>
|
||||
cm
|
||||
</span>
|
||||
</label>
|
||||
<label className="label">
|
||||
<span className="label-text-alt">
|
||||
{valid === null
|
||||
? ''
|
||||
: valid
|
||||
? 'Looks good'
|
||||
: 'Invalid'
|
||||
}
|
||||
{val}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MeasurementInput
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import MeasurementInput from './input-measurement.js'
|
||||
|
||||
const WorkbenchMeasurements = ({ app, pattern, gist, updateGist }) => {
|
||||
|
||||
// Method to handle measurement updates
|
||||
const updateMeasurements = (value, m=false) => {
|
||||
if (m === false) {
|
||||
// Set all measurements
|
||||
} else {
|
||||
// Set one measurement
|
||||
const newValues = {...gist.measurements}
|
||||
newValues[m] = value.trim()
|
||||
updateGist('measurements', newValues)
|
||||
}
|
||||
}
|
||||
// Save us some typing
|
||||
const inputProps = { app, updateMeasurements, gist }
|
||||
|
||||
return (
|
||||
<div className="m-auto max-w-prose">
|
||||
<h1>
|
||||
<span className='capitalize mr-4 opacity-70'>
|
||||
{pattern.config.name}:
|
||||
</span>
|
||||
{app.t('measurements')}
|
||||
</h1>
|
||||
{pattern.config.measurements && (
|
||||
<>
|
||||
<h2>{app.t('requiredMeasurements')}</h2>
|
||||
{pattern.config.measurements.map(m => (
|
||||
<MeasurementInput key={m} m={m} {...inputProps} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{pattern.config.optionalMeasurements && (
|
||||
<>
|
||||
<h2>{app.t('optionalMeasurements')}</h2>
|
||||
{pattern.config.optionalMeasurements.map(m => (
|
||||
<MeasurementInput key={m} m={m} {...inputProps} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkbenchMeasurements
|
||||
|
53
packages/freesewing.shared/components/workbench/menu.js
Normal file
53
packages/freesewing.shared/components/workbench/menu.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import orderBy from 'lodash.orderby'
|
||||
import OptionsIcon from 'shared/components/icons/options.js'
|
||||
import SettingsIcon from 'shared/components/icons/settings.js'
|
||||
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
|
||||
|
||||
const structure = (pattern, app) => ({
|
||||
modes: [
|
||||
{ title: `Draft ${pattern.config.name}`, action: '' },
|
||||
],
|
||||
options: [
|
||||
{ title: `Draft ${pattern.config.name}`, action: '' },
|
||||
],
|
||||
settings: [
|
||||
{ title: `Draft ${pattern.config.name}`, action: '' },
|
||||
],
|
||||
})
|
||||
|
||||
const TopLevel = ({ icon, title }) => (
|
||||
<details className='py-1'>
|
||||
<summary className={`
|
||||
flex flex-row uppercase gap-4 font-bold text-lg
|
||||
hover:cursor-row-resize
|
||||
p-2
|
||||
text-base-content
|
||||
sm:text-neutral-content
|
||||
items-center
|
||||
`}>
|
||||
<span className="text-secondary-focus mr-4">{icon}</span>
|
||||
<span className={`grow ${linkClasses}`}>
|
||||
{title}
|
||||
</span>
|
||||
<Chevron />
|
||||
</summary>
|
||||
fixme
|
||||
</details>
|
||||
)
|
||||
|
||||
const Menu = ({ app, pattern }) => ([
|
||||
<TopLevel key='a' title='Toggles' icon={<OptionsIcon />} pattern={pattern} />,
|
||||
<TopLevel key='b' title='Modes' icon={<OptionsIcon />} pattern={pattern} />,
|
||||
<TopLevel key='c' title='Design Options' icon={<OptionsIcon />} pattern={pattern} />,
|
||||
<TopLevel key='d' title='Pattern Settings' icon={<SettingsIcon />} pattern={pattern} />,
|
||||
])
|
||||
|
||||
const WorkbenchMenu = ({ app, pattern }) => (
|
||||
<nav className="smmax-w-96 grow mb-12">
|
||||
<Menu app={app} pattern={pattern} />
|
||||
</nav>
|
||||
)
|
||||
|
||||
export default WorkbenchMenu
|
Loading…
Add table
Add a link
Reference in a new issue