wip(org): Started working on v3 workbench
This commit is contained in:
parent
bd3ce90e1a
commit
0dece4d70e
27 changed files with 1332 additions and 636 deletions
970
CHANGELOG.md
970
CHANGELOG.md
File diff suppressed because it is too large
Load diff
|
@ -378,6 +378,8 @@ shared:
|
||||||
'file-saver': '2.0.5'
|
'file-saver': '2.0.5'
|
||||||
'front-matter': '4.0.2'
|
'front-matter': '4.0.2'
|
||||||
'highlight.js': '11.7.0'
|
'highlight.js': '11.7.0'
|
||||||
|
'jotai': '2.1.0'
|
||||||
|
'jotai-location': '0.5.1'
|
||||||
'lodash.clonedeep': '4.5.0'
|
'lodash.clonedeep': '4.5.0'
|
||||||
'lodash.orderby': *_orderby
|
'lodash.orderby': *_orderby
|
||||||
'lodash.unset': *_unset
|
'lodash.unset': *_unset
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import designsByType from './designs.json' assert { type: 'json' }
|
import designs from './designs.json' assert { type: 'json' }
|
||||||
import packages from './packages.json' assert { type: 'json' }
|
import packages from './packages.json' assert { type: 'json' }
|
||||||
import plugins from './plugins.json' assert { type: 'json' }
|
import plugins from './plugins.json' assert { type: 'json' }
|
||||||
import sites from './sites.json' assert { type: 'json' }
|
import sites from './sites.json' assert { type: 'json' }
|
||||||
|
@ -21,15 +21,8 @@ const unpackDesigns = (obj, folder) =>
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
const designs = {
|
|
||||||
...designsByType.accessories,
|
|
||||||
...designsByType.blocks,
|
|
||||||
...designsByType.garments,
|
|
||||||
...designsByType.utilities,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-Export imported JSON
|
// Re-Export imported JSON
|
||||||
export { designs, designsByType, packages, plugins, sites }
|
export { designs, packages, plugins, sites }
|
||||||
|
|
||||||
// All software
|
// All software
|
||||||
export const software = {
|
export const software = {
|
||||||
|
|
|
@ -6,12 +6,7 @@ import chalk from 'chalk'
|
||||||
import mustache from 'mustache'
|
import mustache from 'mustache'
|
||||||
import conf from '../lerna.json' assert { type: 'json' }
|
import conf from '../lerna.json' assert { type: 'json' }
|
||||||
const { version } = conf
|
const { version } = conf
|
||||||
import {
|
import { software, publishedTypes as types, designs, plugins } from '../config/software/index.mjs'
|
||||||
software as software,
|
|
||||||
publishedTypes as types,
|
|
||||||
designs,
|
|
||||||
plugins,
|
|
||||||
} from '../config/software/index.mjs'
|
|
||||||
import { buildOrder } from '../config/build-order.mjs'
|
import { buildOrder } from '../config/build-order.mjs'
|
||||||
import rootPackageJson from '../package.json' assert { type: 'json' }
|
import rootPackageJson from '../package.json' assert { type: 'json' }
|
||||||
import { capitalize } from '../packages/core/src/index.mjs'
|
import { capitalize } from '../packages/core/src/index.mjs'
|
||||||
|
@ -466,7 +461,7 @@ function formatDate(date) {
|
||||||
function validate() {
|
function validate() {
|
||||||
for (const type in repo.dirs) {
|
for (const type in repo.dirs) {
|
||||||
for (const dir of repo.dirs[type]) {
|
for (const dir of repo.dirs[type]) {
|
||||||
if (typeof software[dir] === 'undefined' || typeof software[dir].description !== 'string') {
|
if (typeof software?.[dir]?.description !== 'string') {
|
||||||
log.write(chalk.redBright(` No description for package ${type}/${dir}` + '\n'))
|
log.write(chalk.redBright(` No description for package ${type}/${dir}` + '\n'))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,11 +142,11 @@ sets.push(
|
||||||
nameEs: `Cis-Hombre Gigante - ${size}%`,
|
nameEs: `Cis-Hombre Gigante - ${size}%`,
|
||||||
nameFr: `Cis-Homme Géant - ${size}%`,
|
nameFr: `Cis-Homme Géant - ${size}%`,
|
||||||
nameNl: `Cis-Heer Reus - ${size}%`,
|
nameNl: `Cis-Heer Reus - ${size}%`,
|
||||||
tagsEn: ['cis-female', 'giants'],
|
tagsEn: ['cis-male', 'giants'],
|
||||||
tagsDe: ['cis-weiblich', 'riesen'],
|
tagsDe: ['cis-männlich', 'riesen'],
|
||||||
tagsEs: ['cis-mujer', 'gigantes'],
|
tagsEs: ['cis-hombre', 'gigantes'],
|
||||||
tagsFr: ['cis-femme', 'géants'],
|
tagsFr: ['cis-homme', 'géants'],
|
||||||
tagsNl: ['cis-dame', 'reuzen'],
|
tagsNl: ['cis-heer', 'reuzen'],
|
||||||
measies: cisMaleGiant[size],
|
measies: cisMaleGiant[size],
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,65 +25,27 @@ import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-p
|
||||||
import { ModalLocalePicker, ns as localeNs } from 'shared/components/modal/locale-picker.mjs'
|
import { ModalLocalePicker, ns as localeNs } from 'shared/components/modal/locale-picker.mjs'
|
||||||
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
||||||
|
|
||||||
|
import { NavButton, NavSpacer, colors } from 'shared/components/workbench/header.mjs'
|
||||||
|
|
||||||
export const ns = ['header', 'sections', ...themeNs, ...localeNs]
|
export const ns = ['header', 'sections', ...themeNs, ...localeNs]
|
||||||
|
|
||||||
const NavButton = ({ href, label, color, children, onClick = false, extraClasses = '' }) => {
|
|
||||||
const className =
|
|
||||||
'border-0 px-1 lg:px-4 text-base py-3 lg:py-4 text-center flex flex-col items-center 2xl:w-36 ' +
|
|
||||||
`hover:bg-${color}-400 text-${color}-400 hover:text-neutral grow lg:grow-0 ${extraClasses}`
|
|
||||||
const span = <span className="block font-bold hidden 2xl:block">{label}</span>
|
|
||||||
|
|
||||||
return onClick ? (
|
|
||||||
<button {...{ onClick, className }} title={label}>
|
|
||||||
{children}
|
|
||||||
{span}
|
|
||||||
</button>
|
|
||||||
) : (
|
|
||||||
<Link {...{ href, className }} title={label}>
|
|
||||||
{children}
|
|
||||||
{span}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const NavSpacer = () => (
|
|
||||||
<div className="hidden lg:block text-base lg:text-4xl font-thin opacity-30 px-0.5 lg:px-2">|</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export const colors = {
|
|
||||||
menu: 'red',
|
|
||||||
designs: 'orange',
|
|
||||||
patterns: 'yellow',
|
|
||||||
sets: 'lime',
|
|
||||||
showcase: 'green',
|
|
||||||
docs: 'cyan',
|
|
||||||
theme: 'blue',
|
|
||||||
language: 'indigo',
|
|
||||||
search: 'violet',
|
|
||||||
account: 'purple',
|
|
||||||
}
|
|
||||||
|
|
||||||
const NavIcons = ({ setModal, setSearch }) => {
|
const NavIcons = ({ setModal, setSearch }) => {
|
||||||
const { t } = useTranslation(['header'])
|
const { t } = useTranslation(['header'])
|
||||||
const iconSize = 'h-6 w-6 lg:h-12 lg:w-12'
|
const iconSize = 'h-6 w-6 lg:h-12 lg:w-12'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NavButton
|
<NavButton onClick={() => setModal(<ModalMenu />)} label={t('header:menu')} color={colors[0]}>
|
||||||
onClick={() => setModal(<ModalMenu />)}
|
|
||||||
label={t('header:menu')}
|
|
||||||
color={colors.menu}
|
|
||||||
>
|
|
||||||
<MenuIcon className={iconSize} />
|
<MenuIcon className={iconSize} />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<NavSpacer />
|
<NavSpacer />
|
||||||
<NavButton href="/designs" label={t('header:designs')} color={colors.designs}>
|
<NavButton href="/designs" label={t('header:designs')} color={colors[1]}>
|
||||||
<DesignIcon className={iconSize} />
|
<DesignIcon className={iconSize} />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<NavButton
|
<NavButton
|
||||||
href="/patterns"
|
href="/patterns"
|
||||||
label={t('header:patterns')}
|
label={t('header:patterns')}
|
||||||
color={colors.patterns}
|
color={colors[2]}
|
||||||
extraClasses="hidden lg:flex"
|
extraClasses="hidden lg:flex"
|
||||||
>
|
>
|
||||||
<PageIcon className={iconSize} />
|
<PageIcon className={iconSize} />
|
||||||
|
@ -91,7 +53,7 @@ const NavIcons = ({ setModal, setSearch }) => {
|
||||||
<NavButton
|
<NavButton
|
||||||
href="/sets"
|
href="/sets"
|
||||||
label={t('header:sets')}
|
label={t('header:sets')}
|
||||||
color={colors.sets}
|
color={colors[3]}
|
||||||
extraClasses="hidden lg:flex"
|
extraClasses="hidden lg:flex"
|
||||||
>
|
>
|
||||||
<MeasureIcon className={iconSize} />
|
<MeasureIcon className={iconSize} />
|
||||||
|
@ -99,7 +61,7 @@ const NavIcons = ({ setModal, setSearch }) => {
|
||||||
<NavButton
|
<NavButton
|
||||||
href="/showcase"
|
href="/showcase"
|
||||||
label={t('header:showcase')}
|
label={t('header:showcase')}
|
||||||
color={colors.showcase}
|
color={colors[4]}
|
||||||
extraClasses="hidden lg:flex"
|
extraClasses="hidden lg:flex"
|
||||||
>
|
>
|
||||||
<ShowcaseIcon className={iconSize} />
|
<ShowcaseIcon className={iconSize} />
|
||||||
|
@ -107,7 +69,7 @@ const NavIcons = ({ setModal, setSearch }) => {
|
||||||
<NavButton
|
<NavButton
|
||||||
href="/docs"
|
href="/docs"
|
||||||
label={t('header:docs')}
|
label={t('header:docs')}
|
||||||
color={colors.docs}
|
color={colors[5]}
|
||||||
extraClasses="hidden lg:flex"
|
extraClasses="hidden lg:flex"
|
||||||
>
|
>
|
||||||
<DocsIcon className={iconSize} />
|
<DocsIcon className={iconSize} />
|
||||||
|
@ -116,22 +78,22 @@ const NavIcons = ({ setModal, setSearch }) => {
|
||||||
<NavButton
|
<NavButton
|
||||||
onClick={() => setModal(<ModalThemePicker />)}
|
onClick={() => setModal(<ModalThemePicker />)}
|
||||||
label={t('header:theme')}
|
label={t('header:theme')}
|
||||||
color={colors.theme}
|
color={colors[6]}
|
||||||
>
|
>
|
||||||
<ThemeIcon className={iconSize} />
|
<ThemeIcon className={iconSize} />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<NavButton
|
<NavButton
|
||||||
onClick={() => setModal(<ModalLocalePicker />)}
|
onClick={() => setModal(<ModalLocalePicker />)}
|
||||||
label={t('header:language')}
|
label={t('header:language')}
|
||||||
color={colors.language}
|
color={colors[7]}
|
||||||
>
|
>
|
||||||
<I18nIcon className={iconSize} />
|
<I18nIcon className={iconSize} />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<NavButton onClick={() => setSearch(true)} label={t('header:search')} color={colors.search}>
|
<NavButton onClick={() => setSearch(true)} label={t('header:search')} color={colors[8]}>
|
||||||
<SearchIcon className={iconSize} />
|
<SearchIcon className={iconSize} />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
<NavSpacer />
|
<NavSpacer />
|
||||||
<NavButton href="/account" label={t('header:account')} color={colors.account}>
|
<NavButton href="/account" label={t('header:account')} color={colors[9]}>
|
||||||
<UserIcon className={iconSize} />
|
<UserIcon className={iconSize} />
|
||||||
</NavButton>
|
</NavButton>
|
||||||
</>
|
</>
|
||||||
|
|
7
sites/org/components/layouts/workbench.mjs
Normal file
7
sites/org/components/layouts/workbench.mjs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const ns = []
|
||||||
|
|
||||||
|
export const WorkbenchLayout = (props) => (
|
||||||
|
<section id="fs-workbench" className="my-2 lg:mt-32 lg:px-8">
|
||||||
|
{props.children}
|
||||||
|
</section>
|
||||||
|
)
|
|
@ -5,7 +5,7 @@ import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
|
||||||
|
|
||||||
export const ns = primaryNs
|
export const ns = primaryNs
|
||||||
|
|
||||||
export const ModalMenu = ({ app }) => {
|
export const ModalMenu = () => {
|
||||||
const { t } = useTranslation(ns)
|
const { t } = useTranslation(ns)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -20,11 +20,11 @@ export const ModalMenu = ({ app }) => {
|
||||||
>
|
>
|
||||||
<div className="w-full lg:w-1/2">
|
<div className="w-full lg:w-1/2">
|
||||||
<h3>{t('mainSections')}</h3>
|
<h3>{t('mainSections')}</h3>
|
||||||
<SectionsMenu app={app} />
|
<SectionsMenu />
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full lg:w-1/2">
|
<div className="w-full lg:w-1/2">
|
||||||
<h3>{t('currentSection')}</h3>
|
<h3>{t('currentSection')}</h3>
|
||||||
<ActiveSection app={app} bare />
|
<ActiveSection bare />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
|
import { useContext } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { icons, ns as sectionsNs } from 'shared/components/navigation/primary.mjs'
|
import { icons, ns as sectionsNs } from 'shared/components/navigation/primary.mjs'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import orderBy from 'lodash.orderby'
|
import orderBy from 'lodash.orderby'
|
||||||
import { colors } from 'site/components/header/index.mjs'
|
import { colors } from 'site/components/header/index.mjs'
|
||||||
|
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
||||||
|
|
||||||
export const ns = sectionsNs
|
export const ns = sectionsNs
|
||||||
|
|
||||||
export const SectionsMenu = ({ app }) => {
|
export const SectionsMenu = () => {
|
||||||
const { t } = useTranslation(ns)
|
const { t } = useTranslation(ns)
|
||||||
if (!app.state.sections) return null
|
const { sections = false, slug } = useContext(NavigationContext)
|
||||||
|
if (!sections) return null
|
||||||
|
|
||||||
// Ensure each page as an `o` key so we can put them in order
|
// Ensure each page as an `o` key so we can put them in order
|
||||||
const sortableSections = app.state.sections.map((s) => ({ ...s, o: s.o ? s.o : s.t }))
|
const sortableSections = sections.map((s) => ({ ...s, o: s.o ? s.o : s.t }))
|
||||||
const output = []
|
const output = []
|
||||||
for (const page of orderBy(sortableSections, ['o', 't'])) {
|
for (const page of orderBy(sortableSections, ['o', 't'])) {
|
||||||
const item = (
|
const item = (
|
||||||
|
|
|
@ -5,7 +5,15 @@ import { Search, ns as searchNs } from 'site/components/search.mjs'
|
||||||
|
|
||||||
export const ns = [...new Set([...headerNs, ...footerNs, ...searchNs])]
|
export const ns = [...new Set([...headerNs, ...footerNs, ...searchNs])]
|
||||||
|
|
||||||
export const LayoutWrapper = ({ app, children = [], search, setSearch, noSearch = false }) => {
|
export const LayoutWrapper = ({
|
||||||
|
children = [],
|
||||||
|
search,
|
||||||
|
setSearch,
|
||||||
|
noSearch = false,
|
||||||
|
header = false,
|
||||||
|
}) => {
|
||||||
|
const ChosenHeader = header ? header : Header
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
|
@ -17,7 +25,7 @@ export const LayoutWrapper = ({ app, children = [], search, setSearch, noSearch
|
||||||
<Head>
|
<Head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</Head>
|
</Head>
|
||||||
<Header app={app} setSearch={setSearch} />
|
<ChosenHeader setSearch={setSearch} />
|
||||||
<main className="grow">{children}</main>
|
<main className="grow">{children}</main>
|
||||||
{!noSearch && search && (
|
{!noSearch && search && (
|
||||||
<>
|
<>
|
||||||
|
@ -30,12 +38,12 @@ export const LayoutWrapper = ({ app, children = [], search, setSearch, noSearch
|
||||||
lg:max-w-4xl
|
lg:max-w-4xl
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<Search app={app} search={search} setSearch={setSearch} />
|
<Search search={search} setSearch={setSearch} />
|
||||||
</div>
|
</div>
|
||||||
<div className="fixed top-0 left-0 w-full min-h-screen bg-neutral z-20 bg-opacity-70"></div>
|
<div className="fixed top-0 left-0 w-full min-h-screen bg-neutral z-20 bg-opacity-70"></div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Footer app={app} />
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
57
sites/org/pages/new/pattern/aaron/cset/[id].mjs
Normal file
57
sites/org/pages/new/pattern/aaron/cset/[id].mjs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// Hooks
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
// Dependencies
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { Aaron } from '@freesewing/aaron'
|
||||||
|
// Components
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import { Workbench, ns as wbNs } from 'shared/components/workbench/index.mjs'
|
||||||
|
import { WorkbenchLayout } from 'site/components/layouts/workbench.mjs'
|
||||||
|
import { Null } from 'shared/components/null.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const namespaces = [...new Set(['aaron', ...wbNs, ...pageNs])]
|
||||||
|
|
||||||
|
const NewAaronPage = ({ page, id }) => {
|
||||||
|
const backend = useBackend()
|
||||||
|
const [set, setSet] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const getCuratedSet = async () => {
|
||||||
|
const result = await backend.getCuratedSet(id)
|
||||||
|
if (result.success) setSet(result.data.curatedSet)
|
||||||
|
}
|
||||||
|
getCuratedSet()
|
||||||
|
}, [id])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageWrapper {...page} title="Aaron" layout={WorkbenchLayout} header={Null}>
|
||||||
|
<Workbench design="aaron" Design={Aaron} set={set} />
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewAaronPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale, params }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, namespaces)),
|
||||||
|
id: params.id,
|
||||||
|
page: {
|
||||||
|
locale,
|
||||||
|
path: ['new', 'pattern', 'aaron', params.id],
|
||||||
|
title: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
return {
|
||||||
|
paths: [],
|
||||||
|
fallback: true,
|
||||||
|
}
|
||||||
|
}
|
37
sites/org/pages/new/pattern/aaron/index.mjs
Normal file
37
sites/org/pages/new/pattern/aaron/index.mjs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Dependencies
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
// Components
|
||||||
|
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||||
|
import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs'
|
||||||
|
import { SetPicker, ns as setsNs } from 'shared/components/sets/set-picker.mjs'
|
||||||
|
|
||||||
|
// Translation namespaces used on this page
|
||||||
|
const namespaces = [...new Set(['account', ...setsNs, ...authNs, ...pageNs])]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each page MUST be wrapped in the PageWrapper component.
|
||||||
|
* You also MUST spread props.page into this wrapper component
|
||||||
|
* when path and locale come from static props (as here)
|
||||||
|
* or set them manually.
|
||||||
|
*/
|
||||||
|
const NewPatternPickSetPage = ({ page }) => (
|
||||||
|
<PageWrapper {...page}>
|
||||||
|
<SetPicker design="aaron" />
|
||||||
|
</PageWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default NewPatternPickSetPage
|
||||||
|
|
||||||
|
export async function getStaticProps({ locale }) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, namespaces)),
|
||||||
|
page: {
|
||||||
|
locale,
|
||||||
|
path: ['new', 'pattern', 'aaron'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,12 @@ export const IconWrapper = ({
|
||||||
<> {children} </>
|
<> {children} </>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const BeakerIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714c0 .597.237 1.17.659 1.591L19.8 15.3M14.25 3.104c.251.023.501.05.75.082M19.8 15.3l-1.57.393A9.065 9.065 0 0112 15a9.065 9.065 0 00-6.23-.693L5 14.5m14.8.8l1.402 1.402c1.232 1.232.65 3.318-1.067 3.611A48.309 48.309 0 0112 21c-2.773 0-5.491-.235-8.135-.687-1.718-.293-2.3-2.379-1.067-3.61L5 14.5" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
export const BioIcon = (props) => (
|
export const BioIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" />
|
<path d="M7.5 8.25h9m-9 3H12m-9.75 1.51c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 01.865-.501 48.172 48.172 0 003.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0012 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018z" />
|
||||||
|
@ -36,6 +42,12 @@ export const BoxIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const BriefcaseIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M20.25 14.15v4.25c0 1.094-.787 2.036-1.872 2.18-2.087.277-4.216.42-6.378.42s-4.291-.143-6.378-.42c-1.085-.144-1.872-1.086-1.872-2.18v-4.25m16.5 0a2.18 2.18 0 00.75-1.661V8.706c0-1.081-.768-2.015-1.837-2.175a48.114 48.114 0 00-3.413-.387m4.5 8.006c-.194.165-.42.295-.673.38A23.978 23.978 0 0112 15.75c-2.648 0-5.195-.429-7.577-1.22a2.016 2.016 0 01-.673-.38m0 0A2.18 2.18 0 013 12.489V8.706c0-1.081.768-2.015 1.837-2.175a48.111 48.111 0 013.413-.387m7.5 0V5.25A2.25 2.25 0 0013.5 3h-3a2.25 2.25 0 00-2.25 2.25v.894m7.5 0a48.667 48.667 0 00-7.5 0M12 12.75h.008v.008H12v-.008z" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
export const Camera = (props) => (
|
export const Camera = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<>
|
<>
|
||||||
|
@ -91,6 +103,12 @@ export const CloseIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const CodeIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
export const CogIcon = (props) => (
|
export const CogIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
<path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||||
|
@ -121,6 +139,12 @@ export const CopyIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const CutIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M7.848 8.25l1.536.887M7.848 8.25a3 3 0 11-5.196-3 3 3 0 015.196 3zm1.536.887a2.165 2.165 0 011.083 1.839c.005.351.054.695.14 1.024M9.384 9.137l2.077 1.199M7.848 15.75l1.536-.887m-1.536.887a3 3 0 11-5.196 3 3 3 0 015.196-3zm1.536-.887a2.165 2.165 0 001.083-1.838c.005-.352.054-.695.14-1.025m-1.223 2.863l2.077-1.199m0-3.328a4.323 4.323 0 012.068-1.379l5.325-1.628a4.5 4.5 0 012.48-.044l.803.215-7.794 4.5m-2.882-1.664A4.331 4.331 0 0010.607 12m3.736 0l7.794 4.5-.802.215a4.5 4.5 0 01-2.48-.043l-5.326-1.629a4.324 4.324 0 01-2.068-1.379M14.343 12l-2.882 1.664" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
export const DesignIcon = (props) => (
|
export const DesignIcon = (props) => (
|
||||||
<IconWrapper {...props} stroke={0} fill>
|
<IconWrapper {...props} stroke={0} fill>
|
||||||
<path d="m11.975 2.9104c-1.5285 0-2.7845 1.2563-2.7845 2.7848 0 0.7494 0.30048 1.4389 0.78637 1.9394a0.79437 0.79437 0 0 0 0.0084 0.00839c0.38087 0.38087 0.74541 0.62517 0.94538 0.82483 0.19998 0.19966 0.25013 0.2645 0.25013 0.51907v0.65964l-9.1217 5.2665c-0.28478 0.16442-0.83603 0.46612-1.3165 0.9611-0.48047 0.49498-0.92451 1.3399-0.66684 2.2585 0.22026 0.78524 0.7746 1.3486 1.3416 1.5878 0.56697 0.23928 1.0982 0.23415 1.4685 0.23415h18.041c0.37033 0 0.90158 0.0051 1.4686-0.23415 0.56697-0.23928 1.1215-0.80261 1.3418-1.5878 0.25767-0.91859-0.18662-1.7636-0.66709-2.2585-0.48046-0.49498-1.0315-0.79669-1.3162-0.9611l-8.9844-5.1873v-0.73889c0-0.70372-0.35623-1.2837-0.71653-1.6435-0.35778-0.3572-0.70316-0.58503-0.93768-0.81789-0.20864-0.21601-0.33607-0.50298-0.33607-0.83033 0-0.67 0.52595-1.1962 1.1959-1.1962 0.67001 0 1.1962 0.5262 1.1962 1.1962a0.79429 0.79429 0 0 0 0.79434 0.79427 0.79429 0.79429 0 0 0 0.79427-0.79427c0-1.5285-1.2563-2.7848-2.7848-2.7848zm-0.06859 8.2927 8.9919 5.1914c0.28947 0.16712 0.69347 0.41336 0.94393 0.67138 0.25046 0.25803 0.31301 0.3714 0.24754 0.60483-0.10289 0.36677-0.19003 0.40213-0.35969 0.47373-0.16967 0.07161-0.47013 0.09952-0.80336 0.09952h-18.041c-0.33323 0-0.6337-0.02792-0.80336-0.09952-0.16967-0.07161-0.25675-0.10696-0.35963-0.47373-0.06548-0.23342-0.00303-0.3468 0.24748-0.60483 0.25046-0.25803 0.65471-0.50426 0.94418-0.67138z" />
|
<path d="m11.975 2.9104c-1.5285 0-2.7845 1.2563-2.7845 2.7848 0 0.7494 0.30048 1.4389 0.78637 1.9394a0.79437 0.79437 0 0 0 0.0084 0.00839c0.38087 0.38087 0.74541 0.62517 0.94538 0.82483 0.19998 0.19966 0.25013 0.2645 0.25013 0.51907v0.65964l-9.1217 5.2665c-0.28478 0.16442-0.83603 0.46612-1.3165 0.9611-0.48047 0.49498-0.92451 1.3399-0.66684 2.2585 0.22026 0.78524 0.7746 1.3486 1.3416 1.5878 0.56697 0.23928 1.0982 0.23415 1.4685 0.23415h18.041c0.37033 0 0.90158 0.0051 1.4686-0.23415 0.56697-0.23928 1.1215-0.80261 1.3418-1.5878 0.25767-0.91859-0.18662-1.7636-0.66709-2.2585-0.48046-0.49498-1.0315-0.79669-1.3162-0.9611l-8.9844-5.1873v-0.73889c0-0.70372-0.35623-1.2837-0.71653-1.6435-0.35778-0.3572-0.70316-0.58503-0.93768-0.81789-0.20864-0.21601-0.33607-0.50298-0.33607-0.83033 0-0.67 0.52595-1.1962 1.1959-1.1962 0.67001 0 1.1962 0.5262 1.1962 1.1962a0.79429 0.79429 0 0 0 0.79434 0.79427 0.79429 0.79429 0 0 0 0.79427-0.79427c0-1.5285-1.2563-2.7848-2.7848-2.7848zm-0.06859 8.2927 8.9919 5.1914c0.28947 0.16712 0.69347 0.41336 0.94393 0.67138 0.25046 0.25803 0.31301 0.3714 0.24754 0.60483-0.10289 0.36677-0.19003 0.40213-0.35969 0.47373-0.16967 0.07161-0.47013 0.09952-0.80336 0.09952h-18.041c-0.33323 0-0.6337-0.02792-0.80336-0.09952-0.16967-0.07161-0.25675-0.10696-0.35963-0.47373-0.06548-0.23342-0.00303-0.3468 0.24748-0.60483 0.25046-0.25803 0.65471-0.50426 0.94418-0.67138z" />
|
||||||
|
@ -150,6 +174,12 @@ export const DownIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const DownloadIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M12 9.75v6.75m0 0l-3-3m3 3l3-3m-8.25 6a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
export const EditIcon = (props) => (
|
export const EditIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
<path d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
|
@ -321,7 +351,7 @@ export const OpenSourceIcon = (props) => (
|
||||||
|
|
||||||
export const OptionsIcon = (props) => (
|
export const OptionsIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
|
<path d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -437,6 +467,12 @@ export const UnitsIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const UploadIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M12 16.5V9.75m0 0l3 3m-3-3l-3 3M6.75 19.5a4.5 4.5 0 01-1.41-8.775 5.25 5.25 0 0110.233-2.33 3 3 0 013.758 3.848A3.752 3.752 0 0118 19.5H6.75z" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
export const UserIcon = (props) => (
|
export const UserIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
@ -455,6 +491,12 @@ export const WarningIcon = (props) => (
|
||||||
</IconWrapper>
|
</IconWrapper>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const WrenchIcon = (props) => (
|
||||||
|
<IconWrapper {...props}>
|
||||||
|
<path d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z" />
|
||||||
|
</IconWrapper>
|
||||||
|
)
|
||||||
|
|
||||||
export const XrayIcon = (props) => (
|
export const XrayIcon = (props) => (
|
||||||
<IconWrapper {...props}>
|
<IconWrapper {...props}>
|
||||||
<path d="M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5" />
|
<path d="M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5" />
|
||||||
|
|
1
sites/shared/components/null.mjs
Normal file
1
sites/shared/components/null.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const Null = () => null
|
|
@ -20,7 +20,7 @@ export const CuratedSetLacksMeasies = ({ set, design, t, language }) => (
|
||||||
<ChoiceLink
|
<ChoiceLink
|
||||||
icon={<NoIcon className="w-10 h-10 text-error" />}
|
icon={<NoIcon className="w-10 h-10 text-error" />}
|
||||||
title={<Title set={set} language={language} />}
|
title={<Title set={set} language={language} />}
|
||||||
href={`/sets/${set.id}`}
|
href={`/new/pattern/${design}/sets/${set.id}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<WarningIcon className="w-6 h-6 shrink-0 text-error" />
|
<WarningIcon className="w-6 h-6 shrink-0 text-error" />
|
||||||
|
@ -29,21 +29,19 @@ export const CuratedSetLacksMeasies = ({ set, design, t, language }) => (
|
||||||
</ChoiceLink>
|
</ChoiceLink>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const SetSummary = ({ set, design, t }) => (
|
export const CuratedSetSummary = ({ set, language, href }) => (
|
||||||
<ChoiceLink
|
<ChoiceLink
|
||||||
title={<Title set={set} />}
|
title={<Title set={set} language={language} />}
|
||||||
icon={<OkIcon className="w-10 h-10 text-success" stroke={3} />}
|
icon={<OkIcon className="w-10 h-10 text-success" stroke={3} />}
|
||||||
href="/new/pattern"
|
href={href}
|
||||||
>
|
/>
|
||||||
<button className="btn btn-secondary w-full">Use it</button>
|
|
||||||
</ChoiceLink>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
export const CuratedSetCandidate = ({ set, design, requiredMeasies = [] }) => {
|
export const CuratedSetCandidate = ({ set, design, requiredMeasies = [], href }) => {
|
||||||
const { t, i18n } = useTranslation(['sets'])
|
const { t, i18n } = useTranslation(['sets'])
|
||||||
const { language } = i18n
|
const { language } = i18n
|
||||||
|
|
||||||
const setProps = { set, design, t, language }
|
const setProps = { set, design, t, language, href }
|
||||||
|
|
||||||
// Quick check for required measurements
|
// Quick check for required measurements
|
||||||
if (!set.measies || Object.keys(set.measies).length < requiredMeasies.length)
|
if (!set.measies || Object.keys(set.measies).length < requiredMeasies.length)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import orderBy from 'lodash.orderby'
|
import orderBy from 'lodash.orderby'
|
||||||
import { measurements } from 'site/prebuild/design-measurements.mjs'
|
import { measurements } from 'site/prebuild/design-measurements.mjs'
|
||||||
|
import { siteConfig } from 'site/site.config.mjs'
|
||||||
|
import { capitalize } from 'shared/utils.mjs'
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
|
@ -10,31 +12,73 @@ import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||||
import { SetCandidate, ns as setNs } from 'shared/components/sets/set-candidate.mjs'
|
import { SetCandidate, ns as setNs } from 'shared/components/sets/set-candidate.mjs'
|
||||||
import { CuratedSetCandidate } from 'shared/components/sets/curated-set-candidate.mjs'
|
import { CuratedSetCandidate } from 'shared/components/sets/curated-set-candidate.mjs'
|
||||||
import { PopoutWrapper } from 'shared/components/wrappers/popout.mjs'
|
import { PopoutWrapper } from 'shared/components/wrappers/popout.mjs'
|
||||||
|
import { Tag } from 'shared/components/tag.mjs'
|
||||||
|
import { FilterIcon } from 'shared/components/icons.mjs'
|
||||||
|
|
||||||
export const ns = setNs
|
export const ns = setNs
|
||||||
|
|
||||||
export const CuratedSetPicker = ({ design }) => {
|
export const CuratedSetPicker = ({ design, language }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { account, token } = useAccount()
|
const { account, token } = useAccount()
|
||||||
const backend = useBackend(token)
|
const backend = useBackend(token)
|
||||||
const { t } = useTranslation('sets')
|
const { t } = useTranslation('sets')
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [curatedSets, setCuratedSets] = useState({})
|
const [curatedSets, setCuratedSets] = useState([])
|
||||||
const [list, setList] = useState([])
|
const [filter, setFilter] = useState([])
|
||||||
|
const [tags, setTags] = useState([])
|
||||||
|
const [reload, setReload] = useState(0)
|
||||||
|
|
||||||
|
// Force a refresh
|
||||||
|
const refresh = () => setReload(reload + 1)
|
||||||
|
|
||||||
// Effects
|
// Effects
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getCuratedSets = async () => {
|
const getCuratedSets = async () => {
|
||||||
const result = await backend.getCuratedSets()
|
const result = await backend.getCuratedSets()
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const all = {}
|
const all = []
|
||||||
for (const set of result.data.curatedSets) all[set.id] = set
|
const allTags = new Set()
|
||||||
|
for (const set of result.data.curatedSets) {
|
||||||
|
all.push(set)
|
||||||
|
for (const tag of set[`tags${capitalize(language)}`]) allTags.add(tag)
|
||||||
|
}
|
||||||
setCuratedSets(all)
|
setCuratedSets(all)
|
||||||
|
setTags([...allTags])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getCuratedSets()
|
getCuratedSets()
|
||||||
}, [])
|
}, [reload])
|
||||||
|
|
||||||
|
const addFilter = (tag) => {
|
||||||
|
const newFilter = [...filter, tag]
|
||||||
|
setFilter(newFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeFilter = (tag) => {
|
||||||
|
const newFilter = filter.filter((t) => t !== tag)
|
||||||
|
setFilter(newFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyFilter = () => {
|
||||||
|
const newList = new Set()
|
||||||
|
for (const set of curatedSets) {
|
||||||
|
const setTags = []
|
||||||
|
for (const lang of siteConfig.languages) {
|
||||||
|
const key = `tags${capitalize(lang)}`
|
||||||
|
setTags.push(...set[key])
|
||||||
|
}
|
||||||
|
let match = 0
|
||||||
|
for (const tag of filter) {
|
||||||
|
if (setTags.includes(tag)) match++
|
||||||
|
}
|
||||||
|
if (match === filter.length) newList.add(set)
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...newList]
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = applyFilter()
|
||||||
|
|
||||||
// Need to sort designs by their translated title
|
// Need to sort designs by their translated title
|
||||||
const translated = {}
|
const translated = {}
|
||||||
|
@ -42,17 +86,31 @@ export const CuratedSetPicker = ({ design }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h2>{t('chooseSet')}</h2>
|
<h3>{t('curatedSets')}</h3>
|
||||||
<PopoutWrapper tip>
|
{tags.map((tag) => (
|
||||||
<h5>{t('patternForWhichSet')}</h5>
|
<Tag onClick={() => addFilter(tag)}>{tag}</Tag>
|
||||||
<p>{t('fsmtm')}</p>
|
))}
|
||||||
</PopoutWrapper>
|
<div className="my-2 p-2 px-4 border rounded-lg bg-secondary bg-opacity-10 max-w-xl">
|
||||||
{Object.keys(curatedSets).length > 0 ? (
|
<div className="flex flex-row items-center justify-between gap-2">
|
||||||
<>
|
<FilterIcon className="w-6 h-6 text-secondary" />
|
||||||
|
<span>
|
||||||
|
{list.length} / {curatedSets.length}
|
||||||
|
</span>
|
||||||
|
<button onClick={() => setFilter([])} className="btn btn-secondary btn-sm">
|
||||||
|
clear
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{filter.map((tag) => (
|
||||||
|
<Tag onClick={() => removeFilter(tag)} color="success" hoverColor="error">
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
{orderBy(curatedSets, ['name'], ['asc']).map((set) => (
|
{orderBy(list, ['name'], ['asc']).map((set) => (
|
||||||
<div className="w-full lg:w-96">
|
<div className="w-full lg:w-96">
|
||||||
<CuratedSetCandidate
|
<CuratedSetCandidate
|
||||||
|
href={`/new/pattern/${design}/cset/${set.id}`}
|
||||||
set={set}
|
set={set}
|
||||||
requiredMeasies={measurements[design]}
|
requiredMeasies={measurements[design]}
|
||||||
design={design}
|
design={design}
|
||||||
|
@ -61,20 +119,13 @@ export const CuratedSetPicker = ({ design }) => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
|
||||||
<PopoutWrapper fixme compact>
|
|
||||||
Implement UI for when there are no sets
|
|
||||||
</PopoutWrapper>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserSetPicker = ({ design }) => {
|
export const UserSetPicker = ({ design, t, language }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { account, token } = useAccount()
|
const { account, token } = useAccount()
|
||||||
const backend = useBackend(token)
|
const backend = useBackend(token)
|
||||||
const { t } = useTranslation('sets')
|
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [sets, setSets] = useState({})
|
const [sets, setSets] = useState({})
|
||||||
|
@ -97,13 +148,14 @@ export const UserSetPicker = ({ design }) => {
|
||||||
const translated = {}
|
const translated = {}
|
||||||
for (const d of list) translated[t(`${d}.t`)] = d
|
for (const d of list) translated[t(`${d}.t`)] = d
|
||||||
|
|
||||||
return (
|
return Object.keys(sets).length < 1 ? (
|
||||||
<>
|
|
||||||
<h2>{t('chooseSet')}</h2>
|
|
||||||
<PopoutWrapper tip>
|
<PopoutWrapper tip>
|
||||||
<h5>{t('patternForWhichSet')}</h5>
|
<h5>{t('patternForWhichSet')}</h5>
|
||||||
<p>{t('fsmtm')}</p>
|
<p>{t('fsmtm')}</p>
|
||||||
</PopoutWrapper>
|
</PopoutWrapper>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h3>{t('yourSets')}</h3>
|
||||||
{Object.keys(sets).length > 0 ? (
|
{Object.keys(sets).length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-row flex-wrap gap-2">
|
<div className="flex flex-row flex-wrap gap-2">
|
||||||
|
@ -123,9 +175,25 @@ export const UserSetPicker = ({ design }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SetPicker = ({ design }) => (
|
export const BookmarkedSetPicker = ({ design, t }) => (
|
||||||
<>
|
<>
|
||||||
<UserSetPicker design={design} />
|
<h3>{t('bookmarkedSets')}</h3>
|
||||||
<CuratedSetPicker design={design} />
|
<PopoutWrapper fixme>Implement bookmarked set picker (also implement bookmarks)</PopoutWrapper>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const SetPicker = ({ design }) => {
|
||||||
|
const { t, i18n } = useTranslation('sets')
|
||||||
|
const { language } = i18n
|
||||||
|
|
||||||
|
const pickerProps = { design, t, language }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2>{t('chooseSet')}</h2>
|
||||||
|
<UserSetPicker {...pickerProps} />
|
||||||
|
<BookmarkedSetPicker {...pickerProps} />
|
||||||
|
<CuratedSetPicker {...pickerProps} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ chooseSet: Please choose a set of measurements
|
||||||
fsmtm: FreeSewing generates made-to-measure sewing patterns.
|
fsmtm: FreeSewing generates made-to-measure sewing patterns.
|
||||||
patternForWhichSet: Which set of measurements should we generate a pattern for?
|
patternForWhichSet: Which set of measurements should we generate a pattern for?
|
||||||
yourSets: Your measurements sets
|
yourSets: Your measurements sets
|
||||||
starredSets: Measurements sets you've starred
|
bookmarkedSets: Measurements sets you've bookmarked
|
||||||
ourSets: Some popular public measurements sets
|
curatedSets: FreeSewing's Curated Measurements Sets
|
||||||
curatedSets: Curated Measurements Sets
|
|
||||||
curatedSetsAbout: Sets of measurements curated by FreeSewing that you can use to test our platform, or your designs.
|
curatedSetsAbout: Sets of measurements curated by FreeSewing that you can use to test our platform, or your designs.
|
||||||
curateCuratedSets: Curate our selection of Curated Measurements Sets
|
curateCuratedSets: Curate our selection of Curated Measurements Sets
|
||||||
|
useThisSet: Use this set of measurements
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import { SvgWrapper } from './svg.mjs'
|
import { SvgWrapper } from './svg.mjs'
|
||||||
import { DraftError } from './error.mjs'
|
import { DraftError } from './error.mjs'
|
||||||
|
|
||||||
export const LabDraft = (props) => {
|
export const DraftView = ({ pattern, setView, gist, updateGist }) => {
|
||||||
const { app, draft, gist, updateGist, unsetGist, showInfo, feedback, hasRequiredMeasurements } =
|
//const { app, draft, gist, updateGist, unsetGist, showInfo, feedback, hasRequiredMeasurements } = props
|
||||||
props
|
|
||||||
|
|
||||||
if (!draft || !hasRequiredMeasurements) return null
|
if (!pattern) return null
|
||||||
|
|
||||||
// Render as SVG
|
// Render as SVG
|
||||||
if (gist?.renderer === 'svg') {
|
if (gist?.renderer === 'svg') {
|
||||||
let svg
|
let svg
|
||||||
try {
|
try {
|
||||||
svg = draft.render()
|
svg = pattern.render()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Failed to render design', error)
|
console.log('Failed to render design', error)
|
||||||
return <DraftError error={error} {...props} />
|
return <DraftError error={error} {...props} />
|
||||||
|
@ -22,7 +21,7 @@ export const LabDraft = (props) => {
|
||||||
// Render as React
|
// Render as React
|
||||||
let patternProps = {}
|
let patternProps = {}
|
||||||
try {
|
try {
|
||||||
patternProps = draft.getRenderProps()
|
patternProps = pattern.getRenderProps()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Failed to get render props for design', error)
|
console.log('Failed to get render props for design', error)
|
||||||
return (
|
return (
|
||||||
|
@ -41,23 +40,23 @@ export const LabDraft = (props) => {
|
||||||
errors.push(...set.error)
|
errors.push(...set.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(patternProps)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{errors.length > 0 ? (
|
{errors.length > 0 ? (
|
||||||
<DraftError
|
<DraftError
|
||||||
{...{
|
{...{
|
||||||
draft,
|
pattern,
|
||||||
patternProps,
|
patternProps,
|
||||||
updateGist,
|
updateGist,
|
||||||
patternLogs: draft.store.logs,
|
patternLogs: pattern.store.logs,
|
||||||
setLogs: draft.setStores[0].logs,
|
setLogs: pattern.setStores[0].logs,
|
||||||
errors,
|
errors,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<SvgWrapper
|
<SvgWrapper {...{ pattern, patternProps, gist, updateGist }} />
|
||||||
{...{ draft, patternProps, gist, updateGist, unsetGist, showInfo, app, feedback }}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
195
sites/shared/components/workbench/header.mjs
Normal file
195
sites/shared/components/workbench/header.mjs
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
// Hooks
|
||||||
|
import { useState, useEffect, useContext } from 'react'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
// Context
|
||||||
|
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||||
|
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||||
|
// Components
|
||||||
|
import {
|
||||||
|
BeakerIcon,
|
||||||
|
BriefcaseIcon,
|
||||||
|
ClearIcon,
|
||||||
|
CodeIcon,
|
||||||
|
CutIcon,
|
||||||
|
HelpIcon,
|
||||||
|
MenuIcon,
|
||||||
|
OptionsIcon,
|
||||||
|
PrintIcon,
|
||||||
|
SettingsIcon,
|
||||||
|
UploadIcon,
|
||||||
|
WrenchIcon,
|
||||||
|
} from 'shared/components/icons.mjs'
|
||||||
|
import { Ribbon } from 'shared/components/ribbon.mjs'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
||||||
|
|
||||||
|
export const ns = ['workbench', 'sections']
|
||||||
|
|
||||||
|
export const NavButton = ({
|
||||||
|
href,
|
||||||
|
label,
|
||||||
|
color,
|
||||||
|
children,
|
||||||
|
onClick = false,
|
||||||
|
extraClasses = '',
|
||||||
|
active = false,
|
||||||
|
}) => {
|
||||||
|
const className =
|
||||||
|
'border-0 px-1 lg:px-4 text-base py-3 lg:py-4 text-center flex flex-col items-center 2xl:w-36 ' +
|
||||||
|
`hover:bg-${color}-400 text-${color}-400 hover:text-neutral grow lg:grow-0 relative ${extraClasses} ${
|
||||||
|
active ? 'font-heavy' : ''
|
||||||
|
}`
|
||||||
|
const span = <span className="block font-bold hidden 2xl:block">{label}</span>
|
||||||
|
|
||||||
|
return onClick ? (
|
||||||
|
<button {...{ onClick, className }} title={label}>
|
||||||
|
{children}
|
||||||
|
{span}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<Link {...{ href, className }} title={label}>
|
||||||
|
{children}
|
||||||
|
{span}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NavSpacer = () => (
|
||||||
|
<div className="hidden lg:block text-base lg:text-4xl font-thin opacity-30 px-0.5 lg:px-2">|</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const colors = [
|
||||||
|
'red',
|
||||||
|
'orange',
|
||||||
|
'yellow',
|
||||||
|
'lime',
|
||||||
|
'green',
|
||||||
|
'cyan',
|
||||||
|
'blue',
|
||||||
|
'indigo',
|
||||||
|
'violet',
|
||||||
|
'purple',
|
||||||
|
]
|
||||||
|
const views = ['menu', 'draft', 'test', 'print', 'cut', 'save', 'export', 'edit', 'clear', 'help']
|
||||||
|
|
||||||
|
const NavIcons = ({ setModal, setView, view }) => {
|
||||||
|
const { t } = useTranslation(['header'])
|
||||||
|
const iconSize = 'h-6 w-6 lg:h-12 lg:w-12'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setModal(<ModalMenu />)}
|
||||||
|
label={t('workbench:menu')}
|
||||||
|
color={colors[0]}
|
||||||
|
>
|
||||||
|
<MenuIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavSpacer />
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('draft')}
|
||||||
|
label={t('workbench:draft')}
|
||||||
|
color={colors[1]}
|
||||||
|
active={view === 'draft'}
|
||||||
|
>
|
||||||
|
<OptionsIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('test')}
|
||||||
|
label={t('workbench:test')}
|
||||||
|
color={colors[2]}
|
||||||
|
extraClasses="hidden lg:flex"
|
||||||
|
>
|
||||||
|
<BeakerIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('print')}
|
||||||
|
label={t('workbench:printLayout')}
|
||||||
|
color={colors[3]}
|
||||||
|
extraClasses="hidden lg:flex"
|
||||||
|
>
|
||||||
|
<PrintIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('cut')}
|
||||||
|
label={t('workbench:cutLayout')}
|
||||||
|
color={colors[4]}
|
||||||
|
extraClasses="hidden lg:flex"
|
||||||
|
>
|
||||||
|
<CutIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavSpacer />
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('save')}
|
||||||
|
label={t('workbench:save')}
|
||||||
|
color={colors[5]}
|
||||||
|
extraClasses="hidden lg:flex"
|
||||||
|
>
|
||||||
|
<UploadIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('export')}
|
||||||
|
label={t('workbench:export')}
|
||||||
|
color={colors[6]}
|
||||||
|
extraClasses="hidden lg:flex"
|
||||||
|
>
|
||||||
|
<BriefcaseIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('edit')}
|
||||||
|
label={t('workbench:edit')}
|
||||||
|
color={colors[7]}
|
||||||
|
extraClasses="hidden lg:flex"
|
||||||
|
>
|
||||||
|
<CodeIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavButton
|
||||||
|
onClick={() => setView('clear')}
|
||||||
|
label={t('workbench:clear')}
|
||||||
|
color={colors[8]}
|
||||||
|
extraClasses="hidden lg:flex"
|
||||||
|
>
|
||||||
|
<ClearIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
<NavSpacer />
|
||||||
|
<NavButton href="/account" label={t('workbench:help')} color={colors[9]}>
|
||||||
|
<HelpIcon className={iconSize} />
|
||||||
|
</NavButton>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WorkbenchHeader = ({ view, setView }) => {
|
||||||
|
const { setModal } = useContext(ModalContext)
|
||||||
|
const { loading } = useContext(LoadingContext)
|
||||||
|
const [show, setShow] = useState(true)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header
|
||||||
|
className={`
|
||||||
|
fixed bottom-0 lg:bottom-auto lg:top-0 left-0
|
||||||
|
bg-neutral
|
||||||
|
w-full
|
||||||
|
z-30
|
||||||
|
transition-transform
|
||||||
|
${show || loading ? '' : 'fixed bottom-0 lg:top-0 left-0 translate-y-36 lg:-translate-y-36'}
|
||||||
|
drop-shadow-xl
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div className="m-auto md:px-8">
|
||||||
|
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
||||||
|
{/* Non-mobile content */}
|
||||||
|
<div className="hidden lg:flex lg:px-2 flex-row items-center justify-center w-full">
|
||||||
|
<NavIcons setModal={setModal} setView={setView} view={view} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile content */}
|
||||||
|
<div className="flex lg:hidden flex-row items-center justify-between w-full">
|
||||||
|
<NavIcons setModal={setModal} setView={setView} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Ribbon />
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
292
sites/shared/components/workbench/index.mjs
Normal file
292
sites/shared/components/workbench/index.mjs
Normal file
|
@ -0,0 +1,292 @@
|
||||||
|
// Hooks
|
||||||
|
import { useEffect, useState, useMemo } from 'react'
|
||||||
|
import { useGist } from 'shared/hooks/useGist'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { useView } from 'shared/hooks/use-view.mjs'
|
||||||
|
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||||
|
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||||
|
// Dependencies
|
||||||
|
import { pluginTheme } from '@freesewing/plugin-theme'
|
||||||
|
import { pluginI18n } from '@freesewing/plugin-i18n'
|
||||||
|
import { preloaders } from 'shared/components/workbench/preloaders.mjs'
|
||||||
|
import _set from 'lodash.set'
|
||||||
|
// Components
|
||||||
|
import { WorkbenchMenu } from 'shared/components/workbench/menu/index.mjs'
|
||||||
|
import { DraftError } from 'shared/components/workbench/draft/error.mjs'
|
||||||
|
import { Modal } from 'shared/components/modal/modal.mjs'
|
||||||
|
import { ErrorBoundary } from 'shared/components/error/error-boundary.mjs'
|
||||||
|
// Views
|
||||||
|
import { WorkbenchMeasurements } from 'shared/components/workbench/measurements/index.mjs'
|
||||||
|
import { LabSample } from 'shared/components/workbench/sample.mjs'
|
||||||
|
import { ExportDraft } from 'shared/components/workbench/exporting/index.mjs'
|
||||||
|
import { GistAsJson, GistAsYaml } from 'shared/components/workbench/gist.mjs'
|
||||||
|
import { DraftLogs } from 'shared/components/workbench/logs.mjs'
|
||||||
|
import { CutLayout } from 'shared/components/workbench/layout/cut/index.mjs'
|
||||||
|
import { PrintLayout } from 'shared/components/workbench/layout/print/index.mjs'
|
||||||
|
import { EditYaml } from 'shared/components/workbench/edit/index.mjs'
|
||||||
|
|
||||||
|
// Components
|
||||||
|
import { WorkbenchHeader } from './header.mjs'
|
||||||
|
import { ErrorView } from 'shared/components/error/view.mjs'
|
||||||
|
// Views
|
||||||
|
import { DraftView } from 'shared/components/workbench/draft/index.mjs'
|
||||||
|
|
||||||
|
export const ns = ['workbench']
|
||||||
|
|
||||||
|
const loadDefaultSettings = ({ locale = 'en', units = 'metric' }) => ({
|
||||||
|
settings: {
|
||||||
|
sa: 0,
|
||||||
|
scale: 1,
|
||||||
|
complete: true,
|
||||||
|
paperless: false,
|
||||||
|
margin: 2,
|
||||||
|
units,
|
||||||
|
locale,
|
||||||
|
embed: true,
|
||||||
|
},
|
||||||
|
renderer: 'react',
|
||||||
|
//saBool: false,
|
||||||
|
//saMm: 10,
|
||||||
|
//debug: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
const draftViews = ['draft', 'test']
|
||||||
|
|
||||||
|
export const Workbench = ({ design, Design, set = false }) => {
|
||||||
|
// Hooks
|
||||||
|
const { t, i18n } = useTranslation(ns)
|
||||||
|
const { language } = i18n
|
||||||
|
const { account, token } = useAccount()
|
||||||
|
const { backend } = useBackend(token)
|
||||||
|
|
||||||
|
const defaults = loadDefaultSettings({
|
||||||
|
units: account.imperial ? 'imperial' : 'metric',
|
||||||
|
locale: language,
|
||||||
|
})
|
||||||
|
if (set) defaults.settings.measurements = set.measies
|
||||||
|
|
||||||
|
// State
|
||||||
|
const [view, setView] = useView()
|
||||||
|
const [gist, setGist] = useState({ ...defaults, embed: true, renderer: 'react' })
|
||||||
|
const [error, setError] = useState(false)
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
useEffect(() => {
|
||||||
|
if (set.measies) updateGist('settings.measurements', set.measies)
|
||||||
|
}, [set])
|
||||||
|
|
||||||
|
// Don't bother without a set or Design
|
||||||
|
if (!set || !Design) return null
|
||||||
|
|
||||||
|
// Short-circuit errors early
|
||||||
|
if (error)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WorkbenchHeader setView={setView} />
|
||||||
|
{error}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper method to update the gist
|
||||||
|
const updateGist = (path, val) => {
|
||||||
|
const newGist = { ...gist }
|
||||||
|
_set(newGist, path, val)
|
||||||
|
setGist(newGist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the pattern here so we can pass it down to both the view and the options menu
|
||||||
|
const pattern = draftViews.includes(view) ? new Design(gist.settings) : false
|
||||||
|
if (pattern) {
|
||||||
|
// add theme to svg renderer
|
||||||
|
if (gist.renderer === 'svg') {
|
||||||
|
pattern.use(pluginI18n, { t })
|
||||||
|
pattern.use(pluginTheme, { skipGrid: ['pages'] })
|
||||||
|
}
|
||||||
|
|
||||||
|
// draft it for draft and event views. Other views may add plugins, etc and we don't want to draft twice
|
||||||
|
try {
|
||||||
|
pattern.draft()
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
setError(<ErrorView>{JSON.stringify(error)}</ErrorView>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<WorkbenchHeader setView={setView} view={view} />
|
||||||
|
{view === 'draft' && <DraftView {...{ pattern, setView, gist, updateGist }} />}
|
||||||
|
<p>view is {view}</p>
|
||||||
|
<button onClick={() => setView('alt')}>alt</button>
|
||||||
|
<button onClick={() => setView('draft')}>draft</button>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const views = {
|
||||||
|
measurements: WorkbenchMeasurements,
|
||||||
|
//draft: LabDraft,
|
||||||
|
test: LabSample,
|
||||||
|
printingLayout: PrintLayout,
|
||||||
|
cuttingLayout: CutLayout,
|
||||||
|
export: ExportDraft,
|
||||||
|
logs: DraftLogs,
|
||||||
|
yaml: GistAsYaml,
|
||||||
|
json: GistAsJson,
|
||||||
|
edit: EditYaml,
|
||||||
|
welcome: () => <p>TODO</p>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasRequiredMeasurementsMethod = (design, gist) => {
|
||||||
|
if (design.patternConfig?.measurements?.length > 0 && !gist.measurements) return false
|
||||||
|
|
||||||
|
for (const m of design.patternConfig?.measurements || []) {
|
||||||
|
if (!gist.measurements[m]) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const doPreload = async (preload, from, design, gist, setGist, setPreloaded) => {
|
||||||
|
const g = await preloaders[from](preload, design)
|
||||||
|
setPreloaded(preload)
|
||||||
|
setGist({ ...gist, ...g.settings })
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This component wraps the workbench and is in charge of
|
||||||
|
* keeping the gist state, which will trickle down
|
||||||
|
* to all workbench subcomponents
|
||||||
|
*/
|
||||||
|
export const WorkbenchWrapper = ({
|
||||||
|
app,
|
||||||
|
design,
|
||||||
|
preload = false,
|
||||||
|
from = false,
|
||||||
|
layout = false,
|
||||||
|
}) => {
|
||||||
|
// State for gist
|
||||||
|
const { gist, setGist, unsetGist, updateGist, gistReady, undoGist, resetGist } = useGist(
|
||||||
|
design.designConfig?.data?.name,
|
||||||
|
app.locale
|
||||||
|
)
|
||||||
|
const [messages, setMessages] = useState([])
|
||||||
|
const [popup, setPopup] = useState(false)
|
||||||
|
const [preloaded, setPreloaded] = useState(false)
|
||||||
|
// we'll only use this if the renderer is svg, but we can't call hooks conditionally
|
||||||
|
const { t } = useTranslation(['plugin'])
|
||||||
|
|
||||||
|
// We'll use this in more than one location
|
||||||
|
const hasRequiredMeasurements = hasRequiredMeasurementsMethod(design, gist)
|
||||||
|
|
||||||
|
// If we don't have the required measurements,
|
||||||
|
// force view to measurements
|
||||||
|
useEffect(() => {
|
||||||
|
if (!gistReady) return
|
||||||
|
if (!['measurements', 'edit'].includes(gist._state?.view) && !hasRequiredMeasurements)
|
||||||
|
updateGist(['_state', 'view'], 'measurements')
|
||||||
|
}, [gistReady, gist._state?.view, hasRequiredMeasurements, updateGist])
|
||||||
|
|
||||||
|
// If we need to preload the gist, do so
|
||||||
|
useEffect(() => {
|
||||||
|
if (preload && preload !== preloaded && from && preloaders[from]) {
|
||||||
|
doPreload(preload, from, design, gist, setGist, setPreloaded)
|
||||||
|
}
|
||||||
|
}, [preload, preloaded, from, design, gist, setGist])
|
||||||
|
|
||||||
|
// Helper methods to manage the gist state
|
||||||
|
const updateWBGist = useMemo(
|
||||||
|
() =>
|
||||||
|
(path, value, closeNav = false, addToHistory = true) => {
|
||||||
|
updateGist(path, value, addToHistory)
|
||||||
|
// Force close of menu on mobile if it is open
|
||||||
|
if (closeNav && app.primaryMenu) app.setPrimaryMenu(false)
|
||||||
|
},
|
||||||
|
[app, updateGist]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper methods to handle messages
|
||||||
|
const feedback = {
|
||||||
|
add: (msg) => {
|
||||||
|
const newMsgs = [...messages]
|
||||||
|
if (Array.isArray(msg)) newMsgs.push(...msg)
|
||||||
|
else newMsgs.push(msg)
|
||||||
|
setMessages(newMsgs)
|
||||||
|
},
|
||||||
|
set: setMessages,
|
||||||
|
clear: () => setMessages([]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't do anything until the gist is ready
|
||||||
|
if (!gistReady) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the draft here so we can pass it down to both the view and the options menu
|
||||||
|
let draft = false
|
||||||
|
if (['draft', 'logs', 'test', 'printingLayout'].indexOf(gist._state?.view) !== -1) {
|
||||||
|
gist.embed = true
|
||||||
|
// get the appropriate layout for the view
|
||||||
|
const layout = gist.layouts?.[gist._state.view] || gist.layout || true
|
||||||
|
// hand it separately to the design
|
||||||
|
draft = new design({ ...gist, layout })
|
||||||
|
//draft.__init()
|
||||||
|
|
||||||
|
// add theme to svg renderer
|
||||||
|
if (gist.renderer === 'svg') {
|
||||||
|
draft.use(pluginI18n, { t })
|
||||||
|
draft.use(pluginTheme, { skipGrid: ['pages'] })
|
||||||
|
}
|
||||||
|
|
||||||
|
// draft it for draft and event views. Other views may add plugins, etc and we don't want to draft twice
|
||||||
|
try {
|
||||||
|
if (['draft', 'logs'].indexOf(gist._state.view) > -1) draft.draft()
|
||||||
|
} catch (error) {
|
||||||
|
return <DraftError error={error} app={app} draft={draft} at={'draft'} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Props to pass down
|
||||||
|
const componentProps = {
|
||||||
|
app,
|
||||||
|
design,
|
||||||
|
gist,
|
||||||
|
updateGist: updateWBGist,
|
||||||
|
unsetGist,
|
||||||
|
setGist,
|
||||||
|
feedback,
|
||||||
|
gistReady,
|
||||||
|
showInfo: setPopup,
|
||||||
|
hasRequiredMeasurements,
|
||||||
|
draft,
|
||||||
|
}
|
||||||
|
// Required props for layout
|
||||||
|
const layoutProps = {
|
||||||
|
app: app,
|
||||||
|
noSearch: true,
|
||||||
|
workbench: true,
|
||||||
|
AltMenu: <WorkbenchMenu {...componentProps} />,
|
||||||
|
showInfo: setPopup,
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorProps = {
|
||||||
|
undoGist,
|
||||||
|
resetGist,
|
||||||
|
gist,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Layout to use
|
||||||
|
const LayoutComponent = layout
|
||||||
|
|
||||||
|
const Component = views[gist._state?.view] ? views[gist._state.view] : views.welcome
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayoutComponent {...layoutProps}>
|
||||||
|
{messages}
|
||||||
|
<ErrorBoundary {...errorProps}>
|
||||||
|
<Component {...componentProps} draft={draft} />
|
||||||
|
{popup && <Modal cancel={() => setPopup(false)}>{popup}</Modal>}
|
||||||
|
</ErrorBoundary>
|
||||||
|
</LayoutComponent>
|
||||||
|
)
|
||||||
|
}
|
11
sites/shared/components/workbench/workbench.en.yaml
Normal file
11
sites/shared/components/workbench/workbench.en.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
menu: Menu
|
||||||
|
draft: Draft
|
||||||
|
test: Test
|
||||||
|
printLayout: Print Layout
|
||||||
|
cutLayout: Cut Layout
|
||||||
|
save: Save
|
||||||
|
export: Export
|
||||||
|
edit: Edit
|
||||||
|
clear: Clear
|
||||||
|
help: Help
|
||||||
|
|
|
@ -19,7 +19,14 @@ export const PageWrapper = (props) => {
|
||||||
/*
|
/*
|
||||||
* Deconstruct props
|
* Deconstruct props
|
||||||
*/
|
*/
|
||||||
const { layout = DocsLayout, footer = true, children = [], path = [], locale = 'en' } = props
|
const {
|
||||||
|
layout = DocsLayout,
|
||||||
|
footer = true,
|
||||||
|
header = false,
|
||||||
|
children = [],
|
||||||
|
path = [],
|
||||||
|
locale = 'en',
|
||||||
|
} = props
|
||||||
// Title is typically set in props.t but check props.title too
|
// Title is typically set in props.t but check props.title too
|
||||||
const pageTitle = props.t ? props.t : props.title ? props.title : null
|
const pageTitle = props.t ? props.t : props.title ? props.title : null
|
||||||
|
|
||||||
|
@ -37,7 +44,6 @@ export const PageWrapper = (props) => {
|
||||||
*/
|
*/
|
||||||
const { modalContent } = useContext(ModalContext)
|
const { modalContent } = useContext(ModalContext)
|
||||||
const { title, setNavigation } = useContext(NavigationContext)
|
const { title, setNavigation } = useContext(NavigationContext)
|
||||||
console.log({ title })
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update navigation context with title and path
|
* Update navigation context with title and path
|
||||||
|
@ -66,7 +72,7 @@ export const PageWrapper = (props) => {
|
||||||
const [search, setSearch] = useState(false)
|
const [search, setSearch] = useState(false)
|
||||||
|
|
||||||
// Helper object to pass props down (keeps things DRY)
|
// Helper object to pass props down (keeps things DRY)
|
||||||
const childProps = { footer, pageTitle }
|
const childProps = { footer, header, pageTitle }
|
||||||
|
|
||||||
// Make layout prop into a (uppercase) component
|
// Make layout prop into a (uppercase) component
|
||||||
const Layout = layout
|
const Layout = layout
|
||||||
|
|
|
@ -12,7 +12,6 @@ export const NavigationContext = React.createContext(defaultNavigationContext)
|
||||||
|
|
||||||
export const NavigationContextProvider = ({ children }) => {
|
export const NavigationContextProvider = ({ children }) => {
|
||||||
function setNavigation(newValues) {
|
function setNavigation(newValues) {
|
||||||
console.log('setting title to', newValues.title)
|
|
||||||
setValue({
|
setValue({
|
||||||
...value,
|
...value,
|
||||||
...newValues,
|
...newValues,
|
||||||
|
|
8
sites/shared/hooks/use-view.mjs
Normal file
8
sites/shared/hooks/use-view.mjs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { useAtom } from 'jotai'
|
||||||
|
import { atomWithHash } from 'jotai-location'
|
||||||
|
|
||||||
|
const viewAtom = atomWithHash('view', 'draft')
|
||||||
|
|
||||||
|
export const useView = () => {
|
||||||
|
return useAtom(viewAtom)
|
||||||
|
}
|
|
@ -28,6 +28,8 @@
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"front-matter": "4.0.2",
|
"front-matter": "4.0.2",
|
||||||
"highlight.js": "11.7.0",
|
"highlight.js": "11.7.0",
|
||||||
|
"jotai": "2.1.0",
|
||||||
|
"jotai-location": "0.5.1",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"lodash.orderby": "4.6.0",
|
"lodash.orderby": "4.6.0",
|
||||||
"lodash.unset": "4.5.2",
|
"lodash.unset": "4.5.2",
|
||||||
|
|
|
@ -92,7 +92,8 @@ export const optionType = (option) => {
|
||||||
return 'constant'
|
return 'constant'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const capitalize = (string) => string.charAt(0).toUpperCase() + string.slice(1)
|
export const capitalize = (string) =>
|
||||||
|
typeof string === 'string' ? string.charAt(0).toUpperCase() + string.slice(1) : ''
|
||||||
|
|
||||||
export const strapiImage = (
|
export const strapiImage = (
|
||||||
img,
|
img,
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -11469,6 +11469,16 @@ jiti@^1.17.2:
|
||||||
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
|
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
|
||||||
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
|
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
|
||||||
|
|
||||||
|
jotai-location@0.5.1:
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jotai-location/-/jotai-location-0.5.1.tgz#1a08b683cd7823ce57f7fef8b98335f1ce5c7105"
|
||||||
|
integrity sha512-6b34X6PpUaXmHCcyxdMFUHgRLUEp+SFHq9UxHbg5HxHC1LddVyVZbPJI+P15+SOQJcUTH3KrsIeKmeLko+Vw/A==
|
||||||
|
|
||||||
|
jotai@2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jotai/-/jotai-2.1.0.tgz#b1a9525345518453802e4a64d99e2800598bab76"
|
||||||
|
integrity sha512-fR82PtHAmEQrc/daMEYGc4EteW96/b6wodtDSCzLvoJA/6y4YG70er4hh2f8CYwYjqwQ0eZUModGfG4DmwkTyQ==
|
||||||
|
|
||||||
js-base64@^2.1.9:
|
js-base64@^2.1.9:
|
||||||
version "2.6.4"
|
version "2.6.4"
|
||||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
|
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue