wip(lab): Work on porting lab/workbenck to named exports
This commit is contained in:
parent
db789180b6
commit
59708f534d
85 changed files with 1339 additions and 1149 deletions
|
@ -1,28 +0,0 @@
|
|||
import Popout from 'shared/components/popout.js'
|
||||
|
||||
const About = () => (
|
||||
<Popout tip>
|
||||
<h2>What to expect at lab.freesewing.org</h2>
|
||||
<p>
|
||||
The FreeSewing lab is an online environment to road-test our various patterns/designs.
|
||||
</p>
|
||||
<p>
|
||||
This website runs the bleeding edge of our code base.
|
||||
Some patterns here may be unreleased or at various states of being worked on.
|
||||
As such, this website is intended for FreeSewing contributors or people interested
|
||||
in what happens under the hood.
|
||||
</p>
|
||||
<p>
|
||||
If you want sewing patterns to actually start making something,
|
||||
please visit <a
|
||||
href="https://freesewing.org/"
|
||||
className="text-secondary font-bold hover-text-secondary-focus hover:underline"
|
||||
>freesewing.org</a>, our flagship website for makers.
|
||||
</p>
|
||||
<Popout fixme>
|
||||
This lacks translation
|
||||
</Popout>
|
||||
</Popout>
|
||||
)
|
||||
|
||||
export default About
|
24
sites/lab/components/about.mjs
Normal file
24
sites/lab/components/about.mjs
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
||||
export const About = () => (
|
||||
<Popout tip>
|
||||
<h2>What to expect at lab.freesewing.org</h2>
|
||||
<p>The FreeSewing lab is an online environment to road-test our various patterns/designs.</p>
|
||||
<p>
|
||||
This website runs the bleeding edge of our code base. Some patterns here may be unreleased or
|
||||
at various states of being worked on. As such, this website is intended for FreeSewing
|
||||
contributors or people interested in what happens under the hood.
|
||||
</p>
|
||||
<p>
|
||||
If you want sewing patterns to actually start making something, please visit{' '}
|
||||
<a
|
||||
href="https://freesewing.org/"
|
||||
className="text-secondary font-bold hover-text-secondary-focus hover:underline"
|
||||
>
|
||||
freesewing.org
|
||||
</a>
|
||||
, our flagship website for makers.
|
||||
</p>
|
||||
<Popout fixme>This lacks translation</Popout>
|
||||
</Popout>
|
||||
)
|
|
@ -1,11 +1,10 @@
|
|||
import React, { Fragment } from 'react'
|
||||
import DesignIcon from 'shared/components/icons/design'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import DownIcon from 'shared/components/icons/down'
|
||||
import Link from 'next/link'
|
||||
import { Popover, Transition } from '@headlessui/react'
|
||||
import { DesignIcon, DownIcon } from 'shared/components/icons.mjs'
|
||||
|
||||
const PatternPicker = ({ app }) => {
|
||||
export const DesignPicker = ({ app }) => {
|
||||
const { t } = useTranslation(['common'])
|
||||
|
||||
const sectionPatterns = (section) =>
|
||||
|
@ -68,5 +67,3 @@ const PatternPicker = ({ app }) => {
|
|||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default PatternPicker
|
|
@ -1,18 +1,21 @@
|
|||
import Logo from 'shared/components/logos/freesewing.js'
|
||||
import OsiLogo from 'shared/components/logos/osi.js'
|
||||
import CreativeCommonsLogo from 'shared/components/logos/cc.js'
|
||||
import CcByLogo from 'shared/components/logos/cc-by.js'
|
||||
// Hooks
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Ribbon from 'shared/components/ribbon.js'
|
||||
// Components
|
||||
import Link from 'next/link'
|
||||
import { WordMark } from 'shared/components/wordmark.js'
|
||||
|
||||
import DiscordIcon from 'shared/components/icons/discord.js'
|
||||
import FacebookIcon from 'shared/components/icons/facebook.js'
|
||||
import GithubIcon from 'shared/components/icons/github.js'
|
||||
import InstagramIcon from 'shared/components/icons/instagram.js'
|
||||
import RedditIcon from 'shared/components/icons/reddit.js'
|
||||
import TwitterIcon from 'shared/components/icons/twitter.js'
|
||||
import { FreeSewingLogo } from 'shared/components/logos/freesewing.mjs'
|
||||
import { OsiLogo } from 'shared/components/logos/osi.mjs'
|
||||
import { CCLogo } from 'shared/components/logos/cc.mjs'
|
||||
import { CCByLogo } from 'shared/components/logos/cc-by.mjs'
|
||||
import { Ribbon } from 'shared/components/ribbon.mjs'
|
||||
import { WordMark } from 'shared/components/wordmark.mjs'
|
||||
import {
|
||||
DiscordIcon,
|
||||
FacebookIcon,
|
||||
GithubIcon,
|
||||
InstagramIcon,
|
||||
RedditIcon,
|
||||
TwitterIcon,
|
||||
} from 'shared/components/icons.mjs'
|
||||
|
||||
// Classes
|
||||
const link = 'text-secondary font-bold hover:pointer hover:underline px-1'
|
||||
|
@ -77,7 +80,7 @@ const social = {
|
|||
},
|
||||
}
|
||||
|
||||
const Footer = ({ app }) => {
|
||||
export const Footer = ({ app }) => {
|
||||
const { t } = useTranslation(['common', 'patrons'])
|
||||
|
||||
return (
|
||||
|
@ -88,10 +91,10 @@ const Footer = ({ app }) => {
|
|||
<div className="mb-20 order-1 mt-20 2xl:mt-0 2xl:mb-0">
|
||||
<div className="max-w-md m-auto">
|
||||
<div>
|
||||
<CreativeCommonsLogo className="w-64 m-auto" />
|
||||
<CCLogo className="w-64 m-auto" />
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 justify-center items-center mt-8">
|
||||
<CcByLogo className="w-8 lg:w-12" />
|
||||
<CCByLogo className="w-8 lg:w-12" />
|
||||
<p className="text-neutral-content text-right basis-4/5 lg:basis-3/4 leading-5">
|
||||
{translations.cc}
|
||||
</p>
|
||||
|
@ -157,7 +160,7 @@ const Footer = ({ app }) => {
|
|||
{/* Col 3 - Logo & Slogan */}
|
||||
<div className="w-full 4xl:w-auto xl:max-w-md mb-8 text-center order-3 mt-0 lg:mt-20 2xl:mt-0 2xl:mb-0">
|
||||
<div className="max-w-md m-auto">
|
||||
<Logo stroke="none" size={164} className="w-40 lg:w-64 m-auto m-auto" />
|
||||
<FreeSewingLogo stroke="none" size={164} className="w-40 lg:w-64 m-auto m-auto" />
|
||||
<h5 className="lg:text-3xl mt-4">
|
||||
<WordMark />
|
||||
</h5>
|
||||
|
@ -172,5 +175,3 @@ const Footer = ({ app }) => {
|
|||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
|
@ -1,13 +1,14 @@
|
|||
// Hooks
|
||||
import { useState, useEffect } from 'react'
|
||||
import ThemePicker from 'shared/components/theme-picker.js'
|
||||
import LocalePicker from 'shared/components/locale-picker.js'
|
||||
import DesignPicker from 'site/components/design-picker.js'
|
||||
import CloseIcon from 'shared/components/icons/close.js'
|
||||
import MenuIcon from 'shared/components/icons/menu.js'
|
||||
import Ribbon from 'shared/components/ribbon.js'
|
||||
import { WordMark } from 'shared/components/wordmark.js'
|
||||
// Components
|
||||
import { ThemePicker } from 'shared/components/theme-picker/index.mjs'
|
||||
import { LocalePicker } from 'shared/components/locale-picker/index.mjs'
|
||||
import { DesignPicker } from 'site/components/design-picker.mjs'
|
||||
import { CloseIcon, MenuIcon } from 'shared/components/icons.mjs'
|
||||
import { Ribbon } from 'shared/components/ribbon.mjs'
|
||||
import { WordMark } from 'shared/components/wordmark.mjs'
|
||||
|
||||
const Header = ({ app }) => {
|
||||
export const Header = ({ app }) => {
|
||||
const [prevScrollPos, setPrevScrollPos] = useState(0)
|
||||
const [show, setShow] = useState(true)
|
||||
|
||||
|
@ -67,5 +68,3 @@ const Header = ({ app }) => {
|
|||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
|
@ -1,49 +1,53 @@
|
|||
import Popout from 'shared/components/popout.js'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
||||
const HelpUs = ({ mdx=false, slug='/' }) => (
|
||||
export const HelpUs = ({ mdx = false, slug = '/' }) => (
|
||||
<details className="mt-4">
|
||||
<summary>Click here to learn how you can help us improve this page</summary>
|
||||
{mdx && (
|
||||
<Popout tip className='max-w-prose'>
|
||||
<Popout tip className="max-w-prose">
|
||||
<h6>Found a mistake?</h6>
|
||||
You can <a
|
||||
You can{' '}
|
||||
<a
|
||||
href={`https://github.com/freesewing/freesewing/edit/develop/markdown/dev/${slug}/en.md`}
|
||||
className="text-secondary font-bold"
|
||||
>edit this page on Github</a> and help us improve our documentation.
|
||||
>
|
||||
edit this page on Github
|
||||
</a>{' '}
|
||||
and help us improve our documentation.
|
||||
</Popout>
|
||||
)}
|
||||
<Popout comment by="joost" className='max-w-prose'>
|
||||
<Popout comment by="joost" className="max-w-prose">
|
||||
<h6>Does this look ok?</h6>
|
||||
<img
|
||||
className="my-4 rounded"
|
||||
src={`https://canary.backend.freesewing.org/og-img/en/dev${slug}`}
|
||||
/>
|
||||
<p>
|
||||
If it looks ok, great! But if not, please let me know about it.
|
||||
Either by <a href="https://discord.freesewing.org/" className="text-secondary">
|
||||
If it looks ok, great! But if not, please let me know about it. Either by{' '}
|
||||
<a href="https://discord.freesewing.org/" className="text-secondary">
|
||||
reaching out on Discord
|
||||
</a> or feel free to <a
|
||||
</a>{' '}
|
||||
or feel free to{' '}
|
||||
<a
|
||||
href="https://github.com/freesewing/freesewing/issues/new/choose"
|
||||
className="text-secondary"
|
||||
>create
|
||||
an issue on Github</a>.
|
||||
>
|
||||
create an issue on Github
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
<h6>Why do you ask?</h6>
|
||||
<p className="text-base">
|
||||
I recently added a backend endpoint to auto-generate pretty (I hope) Open Graph images.
|
||||
They are those little preview images you see when you paste a link in Discord (for example).
|
||||
I recently added a backend endpoint to auto-generate pretty (I hope) Open Graph images. They
|
||||
are those little preview images you see when you paste a link in Discord (for example).
|
||||
</p>
|
||||
<p className="text-base">
|
||||
This idea is that it will auto-generate an image, but I am certain there are some edge cases
|
||||
where that will not work.
|
||||
There are hundreds of pages on this website and checking them all one by one is not something
|
||||
I see myself doing. But since you are here on this page, perhaps you could see if the image
|
||||
above looks ok.
|
||||
where that will not work. There are hundreds of pages on this website and checking them all
|
||||
one by one is not something I see myself doing. But since you are here on this page, perhaps
|
||||
you could see if the image above looks ok.
|
||||
</p>
|
||||
<p className="text-base">Thank you, I really appreciate your help with this.</p>
|
||||
</Popout>
|
||||
</details>
|
||||
)
|
||||
|
||||
export default HelpUs
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import Aside from 'shared/components/navigation/aside'
|
||||
import { BeforeNav } from './lab'
|
||||
|
||||
const DefaultLayout = ({ app, children = [] }) => {
|
||||
const router = useRouter()
|
||||
const slug = router.asPath.slice(1)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Aside app={app} slug={slug} before={<BeforeNav app={app} />} mobileOnly />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DefaultLayout
|
15
sites/lab/components/layouts/bare.mjs
Normal file
15
sites/lab/components/layouts/bare.mjs
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { useRouter } from 'next/router'
|
||||
import { AsideNavigation } from 'shared/components/navigation/aside.mjs'
|
||||
import { BeforeNav } from './lab.mjs'
|
||||
|
||||
export const BareLayout = ({ app, children = [] }) => {
|
||||
const router = useRouter()
|
||||
const slug = router.asPath.slice(1)
|
||||
|
||||
return (
|
||||
<>
|
||||
<AsideNavigation app={app} slug={slug} before={<BeforeNav app={app} />} mobileOnly />
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import ThemePicker from 'shared/components/theme-picker.js'
|
||||
import LocalePicker from 'shared/components/locale-picker.js'
|
||||
import { ThemePicker } from 'shared/components/theme-picker/index.mjs'
|
||||
import { LocalePicker } from 'shared/components/locale-picker/index.mjs'
|
||||
|
||||
export const BeforeNav = ({ app }) => (
|
||||
<>
|
||||
|
@ -11,7 +11,7 @@ export const BeforeNav = ({ app }) => (
|
|||
</>
|
||||
)
|
||||
|
||||
const LabLayout = ({ app, AltMenu, children = [] }) => (
|
||||
export const LabLayout = ({ app, AltMenu, children = [] }) => (
|
||||
<div className="py-24 lg:py-36 flex flex-row">
|
||||
<div className="w-full xl:w-3/4 px-8">{children}</div>
|
||||
<aside
|
||||
|
@ -42,5 +42,3 @@ const LabLayout = ({ app, AltMenu, children = [] }) => (
|
|||
</aside>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default LabLayout
|
|
@ -1,3 +0,0 @@
|
|||
const noSearch = () => null
|
||||
|
||||
export default noSearch
|
1
sites/lab/components/search.mjs
Normal file
1
sites/lab/components/search.mjs
Normal file
|
@ -0,0 +1 @@
|
|||
export const Search = () => null
|
|
@ -1,9 +1,10 @@
|
|||
// Hooks
|
||||
import { useRouter } from 'next/router'
|
||||
// Site components
|
||||
import Header from 'site/components/header'
|
||||
import Footer from 'site/components/footer'
|
||||
// Components
|
||||
import { Header } from 'site/components/header.mjs'
|
||||
import { Footer } from 'site/components/footer.mjs'
|
||||
|
||||
const LayoutWrapper = ({ app, children = [] }) => {
|
||||
export const LayoutWrapper = ({ app, children = [] }) => {
|
||||
const startNavigation = () => {
|
||||
app.startLoading()
|
||||
// Force close of menu on mobile if it is open
|
||||
|
@ -28,5 +29,3 @@ const LayoutWrapper = ({ app, children = [] }) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LayoutWrapper
|
|
@ -1,11 +1,12 @@
|
|||
// Hooks
|
||||
import { useEffect } from 'react'
|
||||
import { useSwipeable } from 'react-swipeable'
|
||||
import { useRouter } from 'next/router'
|
||||
// Layouts components
|
||||
import LayoutWrapper from 'site/components/wrappers/layout'
|
||||
// Components
|
||||
import { LayoutWrapper } from 'site/components/wrappers/layout.mjs'
|
||||
|
||||
/* This component should wrap all page content */
|
||||
const PageWrapper = ({
|
||||
export const PageWrapper = ({
|
||||
title = 'FIXME: No title set',
|
||||
app = false,
|
||||
layout = false,
|
||||
|
@ -42,5 +43,3 @@ const PageWrapper = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageWrapper
|
|
@ -1,13 +1,13 @@
|
|||
// Hooks
|
||||
import { useState } from 'react'
|
||||
// Stores state in local storage
|
||||
import useLocalStorage from 'shared/hooks/useLocalStorage.js'
|
||||
// Designs
|
||||
import { designsByType } from 'prebuild/designs-by-type.mjs'
|
||||
// Locale and translation
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useLocalStorage } from 'shared/hooks/useLocalStorage.mjs'
|
||||
import { useTheme } from 'shared/hooks/useTheme.mjs'
|
||||
// Dependenices
|
||||
import { designsByType } from 'prebuild/designs-by-type.mjs'
|
||||
// Components
|
||||
import { capitalize } from 'shared/utils.mjs'
|
||||
import useTheme from 'shared/hooks/useTheme'
|
||||
|
||||
// Initial navigation
|
||||
const initialNavigation = (t) => {
|
|
@ -1,10 +1,12 @@
|
|||
import Page from 'site/components/wrappers/page.js'
|
||||
import useApp from 'site/hooks/useApp.js'
|
||||
import Link from 'next/link'
|
||||
// Hooks
|
||||
import { useApp } from 'site/hooks/useApp.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Layout from 'site/components/layouts/bare'
|
||||
import { PageTitle } from 'shared/components/layouts/default'
|
||||
// Components
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import { PageWrapper } from 'site/components/wrappers/page.mjs'
|
||||
import { BareLayout } from 'site/components/layouts/bare.mjs'
|
||||
import { PageTitle } from 'shared/components/layouts/default.mjs'
|
||||
|
||||
const DesignLinks = ({ list, prefix = '' }) => {
|
||||
const { t } = useTranslation(['patterns'])
|
||||
|
@ -29,7 +31,7 @@ const DesignLinks = ({ list, prefix = '' }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const PatternListPageTemplate = ({ section = false }) => {
|
||||
export const PatternListPageTemplate = ({ section = false }) => {
|
||||
const app = useApp()
|
||||
const { t } = useTranslation(['app'])
|
||||
|
||||
|
@ -47,7 +49,7 @@ const PatternListPageTemplate = ({ section = false }) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<Page app={app} title={`FreeSewing Lab: ${title}`} layout={Layout}>
|
||||
<PageWrapper app={app} title={`FreeSewing Lab: ${title}`} layout={BareLayout}>
|
||||
<Head>
|
||||
<title>{fullTitle}</title>
|
||||
</Head>
|
||||
|
@ -61,8 +63,6 @@ const PatternListPageTemplate = ({ section = false }) => {
|
|||
<DesignLinks list={sectionDesigns(section)} />
|
||||
</section>
|
||||
</div>
|
||||
</Page>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default PatternListPageTemplate
|
|
@ -1,12 +1,15 @@
|
|||
import Page from 'site/components/wrappers/page.js'
|
||||
import useApp from 'site/hooks/useApp.js'
|
||||
import WorkbenchWrapper from 'shared/components/wrappers/workbench.js'
|
||||
// Hooks
|
||||
import { useApp } from 'site/hooks/useApp.mjs'
|
||||
import { useRouter } from 'next/router'
|
||||
import Layout from 'site/components/layouts/lab'
|
||||
import Head from 'next/head'
|
||||
// Dependencies
|
||||
import { capitalize } from 'shared/utils.mjs'
|
||||
// Components
|
||||
import Head from 'next/head'
|
||||
import { PageWrapper } from 'site/components/wrappers/page.mjs'
|
||||
import { WorkbenchWrapper } from 'shared/components/wrappers/workbench.mjs'
|
||||
import { LabLayout } from 'site/components/layouts/lab.mjs'
|
||||
|
||||
const WorkbenchPage = ({ design }) => {
|
||||
export const WorkbenchPage = ({ design }) => {
|
||||
const app = useApp()
|
||||
const router = useRouter()
|
||||
const { preload, from } = router.query
|
||||
|
@ -16,13 +19,11 @@ const WorkbenchPage = ({ design }) => {
|
|||
const fullTitle = title + ' - FreeSewing Lab'
|
||||
|
||||
return (
|
||||
<Page app={app}>
|
||||
<PageWrapper app={app}>
|
||||
<Head>
|
||||
<title>{fullTitle}</title>
|
||||
</Head>
|
||||
<WorkbenchWrapper {...{ app, design, preload, from }} layout={Layout} />
|
||||
</Page>
|
||||
<WorkbenchWrapper {...{ app, design, preload, from }} layout={LabLayout} />
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkbenchPage
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import Page from 'site/components/wrappers/page.js'
|
||||
import useApp from 'site/hooks/useApp.js'
|
||||
import Head from 'next/head'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import Layout from 'site/components/layouts/bare'
|
||||
// Hooks
|
||||
import useApp from 'site/hooks/useApp.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { Icons } from 'shared/components/navigation/primary'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
import Head from 'next/head'
|
||||
import { PageWrapper } from 'site/components/wrappers/page.mjs'
|
||||
import { BareLayout } from 'site/components/layouts/bare.mjs'
|
||||
import { Icons } from 'shared/components/navigation/primary.mjs'
|
||||
|
||||
const title = 'Welcome to the FreeSewing Lab'
|
||||
|
||||
|
@ -12,7 +15,7 @@ const HomePage = () => {
|
|||
const app = useApp()
|
||||
const { t } = useTranslation(['lab'])
|
||||
return (
|
||||
<Page app={app} title="{title}" layout={Layout}>
|
||||
<PageWrapper app={app} title="{title}" layout={BareLayout}>
|
||||
<Head>
|
||||
<meta property="og:title" content="FreeSewing.dev" key="title" />
|
||||
<meta property="og:type" content="article" key="type" />
|
||||
|
@ -148,7 +151,7 @@ const HomePage = () => {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ export const CopyIcon = (props) => (
|
|||
)
|
||||
|
||||
export const DesignIcon = (props) => (
|
||||
<IconWrapper {...props} stroke={1}>
|
||||
<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" />
|
||||
</IconWrapper>
|
||||
)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
const defaultSettings = {
|
||||
sa: 0,
|
||||
saBool: false,
|
||||
saMm: 10,
|
||||
scale: 1,
|
||||
complete: true,
|
||||
paperless: false,
|
||||
units: 'metric',
|
||||
locale: 'en',
|
||||
margin: 2,
|
||||
renderer: 'react',
|
||||
embed: true,
|
||||
debug: true,
|
||||
}
|
||||
|
||||
export default defaultSettings
|
|
@ -1,4 +1,4 @@
|
|||
const Circle = (props) =>
|
||||
export const Circle = (props) =>
|
||||
props.point.attributes.getAsArray('data-circle').map((r, i) => {
|
||||
const circleProps = props.point.attributes.asPropsIfPrefixIs('data-circle-')
|
||||
const extraProps = {}
|
||||
|
@ -12,5 +12,3 @@ const Circle = (props) =>
|
|||
|
||||
return <circle key={r} cx={props.point.x} cy={props.point.y} r={r} {...extraProps} />
|
||||
})
|
||||
|
||||
export default Circle
|
|
@ -48,7 +48,7 @@ const grids = {
|
|||
`,
|
||||
}
|
||||
|
||||
const Defs = (props) => {
|
||||
export const Defs = (props) => {
|
||||
let defs = props.svg.defs
|
||||
if (props.settings[0].paperless) {
|
||||
defs += grids[props.settings[0].units || 'metric']
|
||||
|
@ -65,5 +65,3 @@ const Defs = (props) => {
|
|||
|
||||
return <defs dangerouslySetInnerHTML={{ __html: defs }} />
|
||||
}
|
||||
|
||||
export default Defs
|
|
@ -1,6 +1,6 @@
|
|||
import DefaultErrorView from 'shared/components/error/view'
|
||||
import { ErrorView } from 'shared/components/error/view.mjs'
|
||||
|
||||
const Error = (props) => {
|
||||
export const Error = (props) => {
|
||||
const errors = {
|
||||
pattern: 0,
|
||||
sets: 0,
|
||||
|
@ -58,10 +58,8 @@ const Error = (props) => {
|
|||
)
|
||||
|
||||
return (
|
||||
<DefaultErrorView inspectChildren={ic} {...props}>
|
||||
<ErrorView inspectChildren={ic} {...props}>
|
||||
<p>No need to be alarmed, but we ran into some trouble while drafting this pattern.</p>
|
||||
</DefaultErrorView>
|
||||
</ErrorView>
|
||||
)
|
||||
}
|
||||
|
||||
export default Error
|
|
@ -1,7 +1,7 @@
|
|||
import SvgWrapper from './svg-wrapper'
|
||||
import Error from './error.js'
|
||||
import { SvgWrapper } from './svg.mjs'
|
||||
import { Error } from './error.mjs'
|
||||
|
||||
const LabDraft = (props) => {
|
||||
export const LabDraft = (props) => {
|
||||
const { app, draft, gist, updateGist, unsetGist, showInfo, feedback, hasRequiredMeasurements } =
|
||||
props
|
||||
|
||||
|
@ -60,5 +60,3 @@ const LabDraft = (props) => {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LabDraft
|
|
@ -1,10 +1,9 @@
|
|||
import { forwardRef } from 'react'
|
||||
import Path from '../path'
|
||||
import Point from '../point'
|
||||
import Snippet from '../snippet'
|
||||
import { getProps } from '../utils'
|
||||
import { round } from 'shared/utils'
|
||||
import { Tr, KeyTd, ValTd, Attributes, pointCoords } from '../path/index'
|
||||
import { Point } from './point.mjs'
|
||||
import { Snippet } from './snippet.mjs'
|
||||
import { getProps } from './utils.mjs'
|
||||
import { round } from 'shared/utils.mjs'
|
||||
import { Path, Tr, KeyTd, ValTd, Attributes, pointCoords } from './path.mjs'
|
||||
|
||||
const partInfo = (props) => (
|
||||
<div className="p-4 border bg-neutral bg-opacity-40 shadow rounded-lg">
|
||||
|
@ -142,7 +141,7 @@ export const PartInner = forwardRef((props, ref) => {
|
|||
|
||||
PartInner.displayName = 'PartInner'
|
||||
|
||||
const Part = (props) => {
|
||||
export const Part = (props) => {
|
||||
const { partName, part } = props
|
||||
|
||||
return (
|
||||
|
@ -155,14 +154,3 @@ const Part = (props) => {
|
|||
</g>
|
||||
)
|
||||
}
|
||||
/*
|
||||
<ErrorBoundary
|
||||
x={part.topLeft.x}
|
||||
y={part.topLeft.y}
|
||||
width={part.width}
|
||||
height={part.height}
|
||||
>
|
||||
</ErrorBoundary>
|
||||
*/
|
||||
|
||||
export default Part
|
|
@ -1,7 +1,7 @@
|
|||
import TextOnPath from '../text-on-path'
|
||||
import { getProps } from '../utils'
|
||||
import { round, formatMm } from 'shared/utils'
|
||||
import RawSpan from 'shared/components/raw-span'
|
||||
import { TextOnPath } from './text.mjs'
|
||||
import { getProps } from './utils.mjs'
|
||||
import { round, formatMm } from 'shared/utils.mjs'
|
||||
import { RawSpan } from 'shared/components/raw-span.mjs'
|
||||
|
||||
export const pointCoords = (point) =>
|
||||
point ? `[ ${round(point.x, 2)}, ${round(point.y, 2)} ]` : null
|
||||
|
@ -620,7 +620,7 @@ const XrayPath = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
const Path = (props) => {
|
||||
export const Path = (props) => {
|
||||
const { path, partName, pathName } = props
|
||||
if (path.hidden) return null
|
||||
const output = []
|
||||
|
@ -641,5 +641,3 @@ const Path = (props) => {
|
|||
|
||||
return output
|
||||
}
|
||||
|
||||
export default Path
|
|
@ -1,7 +1,7 @@
|
|||
import Text from '../text'
|
||||
import Circle from '../circle'
|
||||
import { Tr, KeyTd, ValTd, Attributes, pointCoords } from '../path/index'
|
||||
import { withinPartBounds } from '../utils.js'
|
||||
import { Text } from './text.mjs'
|
||||
import { Circle } from './circle.mjs'
|
||||
import { Tr, KeyTd, ValTd, Attributes, pointCoords } from './path.mjs'
|
||||
import { withinPartBounds } from './utils.mjs'
|
||||
|
||||
const RevealPoint = (props) => {
|
||||
const r = 15 * props.gist.scale
|
||||
|
@ -82,7 +82,7 @@ const XrayPoint = (props) => (
|
|||
</g>
|
||||
)
|
||||
|
||||
const Point = (props) => {
|
||||
export const Point = (props) => {
|
||||
const { point, pointName, partName, gist, part } = props
|
||||
// Don't include parts outside the part bounding box
|
||||
if (!withinPartBounds(point, part)) return null
|
||||
|
@ -103,5 +103,3 @@ const Point = (props) => {
|
|||
|
||||
return output.length < 1 ? null : output
|
||||
}
|
||||
|
||||
export default Point
|
|
@ -1,9 +1,8 @@
|
|||
import React from 'react'
|
||||
import { getProps } from '../utils'
|
||||
import { Tr, KeyTd, ValTd, Attributes, pointCoords } from '../path/index'
|
||||
import { getProps } from './utils.mjs'
|
||||
import { Tr, KeyTd, ValTd, Attributes, pointCoords } from './path.mjs'
|
||||
|
||||
const snippetInfo = (props) => {
|
||||
|
||||
return (
|
||||
<div className="p-4 border bg-neutral bg-opacity-40 shadow rounded-lg">
|
||||
<h5 className="text-neutral-content text-center pb-4">Snippet info</h5>
|
||||
|
@ -27,30 +26,30 @@ const snippetInfo = (props) => {
|
|||
</Tr>
|
||||
<Tr>
|
||||
<KeyTd>Attributes</KeyTd>
|
||||
<ValTd><Attributes list={props.snippet.attributes.list}/></ValTd>
|
||||
<ValTd>
|
||||
<Attributes list={props.snippet.attributes.list} />
|
||||
</ValTd>
|
||||
</Tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="flex flex-col flex-wrap gap-2 mt-4">
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={() => console.log(props.snippet)}
|
||||
>console.log(snippet)</button>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={() => console.table(props.snippet)}
|
||||
>console.table(snippet)</button>
|
||||
<button className="btn btn-success" onClick={() => console.log(props.snippet)}>
|
||||
console.log(snippet)
|
||||
</button>
|
||||
<button className="btn btn-success" onClick={() => console.table(props.snippet)}>
|
||||
console.table(snippet)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Snippet = (props) => {
|
||||
export const Snippet = (props) => {
|
||||
if (!props.snippet?.anchor) return null
|
||||
const snippetProps = {
|
||||
xlinkHref: '#' + props.snippet.def,
|
||||
x: props.snippet.anchor.x,
|
||||
y: props.snippet.anchor.y
|
||||
y: props.snippet.anchor.y,
|
||||
}
|
||||
let scale = props.snippet.attributes.get('data-scale')
|
||||
let rotate = props.snippet.attributes.get('data-rotate')
|
||||
|
@ -66,13 +65,16 @@ const Snippet = (props) => {
|
|||
}
|
||||
}
|
||||
|
||||
return <use
|
||||
return (
|
||||
<use
|
||||
{...snippetProps}
|
||||
{...getProps(props.snippet)}
|
||||
color="currentColor"
|
||||
className="hover:cursor-pointer hover:fill-primary hover:text-secondary"
|
||||
onClick={(evt) => { evt.stopPropagation(); props.showInfo(snippetInfo(props)) }}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation()
|
||||
props.showInfo(snippetInfo(props))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Snippet
|
|
@ -1,7 +1,7 @@
|
|||
import Part from './part'
|
||||
import { getProps } from './utils'
|
||||
import { Part } from './part.mjs'
|
||||
import { getProps } from './utils.mjs'
|
||||
|
||||
const Stack = (props) => {
|
||||
export const Stack = (props) => {
|
||||
const { stack, gist, updateGist, unsetGist, showInfo } = props
|
||||
|
||||
return (
|
||||
|
@ -17,5 +17,3 @@ const Stack = (props) => {
|
|||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
export default Stack
|
|
@ -1,9 +1,49 @@
|
|||
import { forwardRef } from 'react'
|
||||
import { SizeMe } from 'react-sizeme'
|
||||
import { TransformWrapper, TransformComponent } from 'react-zoom-pan-pinch'
|
||||
import Svg from './svg'
|
||||
import Defs from './defs'
|
||||
import Stack from './stack'
|
||||
import { forwardRef } from 'react'
|
||||
import { Defs } from './defs.mjs'
|
||||
import { Stack } from './stack.mjs'
|
||||
|
||||
export const Svg = forwardRef(
|
||||
(
|
||||
{
|
||||
embed = true,
|
||||
develop = false,
|
||||
locale = 'en',
|
||||
className = 'freesewing pattern',
|
||||
style = {},
|
||||
viewBox = false,
|
||||
width,
|
||||
height,
|
||||
children,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
if (width < 1) width = 1000
|
||||
if (height < 1) height = 1000
|
||||
let attributes = {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
'xmlns:svg': 'http://www.w3.org/2000/svg',
|
||||
xmlnsXlink: 'http://www.w3.org/1999/xlink',
|
||||
xmlLang: locale,
|
||||
viewBox: viewBox || `0 0 ${width} ${height}`,
|
||||
className,
|
||||
style,
|
||||
}
|
||||
|
||||
if (!embed) {
|
||||
attributes.width = width + 'mm'
|
||||
attributes.height = height + 'mm'
|
||||
}
|
||||
if (develop) attributes.className += ' develop'
|
||||
|
||||
return (
|
||||
<svg {...attributes} ref={ref}>
|
||||
{children}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
/* What's with all the wrapping?
|
||||
*
|
||||
|
@ -26,7 +66,7 @@ import { forwardRef } from 'react'
|
|||
* Also still to see how this will work with SSR
|
||||
*/
|
||||
|
||||
const SvgWrapper = forwardRef((props, ref) => {
|
||||
export const SvgWrapper = forwardRef((props, ref) => {
|
||||
const { patternProps = false, gist, updateGist, unsetGist, showInfo, viewBox } = props
|
||||
|
||||
if (!patternProps) return null
|
||||
|
@ -65,7 +105,3 @@ const SvgWrapper = forwardRef((props, ref) => {
|
|||
</SizeMe>
|
||||
)
|
||||
})
|
||||
|
||||
SvgWrapper.displayName = 'SvgWrapper'
|
||||
|
||||
export default SvgWrapper
|
|
@ -1,35 +0,0 @@
|
|||
import {forwardRef} from 'react'
|
||||
|
||||
const Svg = forwardRef(({
|
||||
embed = true,
|
||||
develop = false,
|
||||
locale = 'en',
|
||||
className = 'freesewing pattern',
|
||||
style = {},
|
||||
viewBox = false,
|
||||
width,
|
||||
height,
|
||||
children
|
||||
}, ref) => {
|
||||
if (width < 1) width = 1000
|
||||
if (height < 1) height = 1000
|
||||
let attributes = {
|
||||
xmlns: 'http://www.w3.org/2000/svg',
|
||||
'xmlns:svg': 'http://www.w3.org/2000/svg',
|
||||
xmlnsXlink: 'http://www.w3.org/1999/xlink',
|
||||
xmlLang: locale,
|
||||
viewBox: viewBox || `0 0 ${width} ${height}`,
|
||||
className,
|
||||
style
|
||||
}
|
||||
|
||||
if (!embed) {
|
||||
attributes.width = width + 'mm'
|
||||
attributes.height = height + 'mm'
|
||||
}
|
||||
if (develop) attributes.className += ' develop'
|
||||
|
||||
return <svg {...attributes} ref={ref}>{children}</svg>
|
||||
})
|
||||
|
||||
export default Svg
|
|
@ -1,45 +0,0 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { pathInfo } from '../path/index'
|
||||
|
||||
const XrayTextOnPath = props => (
|
||||
<tspan
|
||||
{...props.attr}
|
||||
dangerouslySetInnerHTML={{__html: props.translated}}
|
||||
className={`${props.attr.className} no-fill stroke-transparent stroke-4xl opacity-10 hover:cursor-pointer hover:stroke-secondary`}
|
||||
style={{strokeOpacity: 0.25}}
|
||||
onClick={(evt) => { evt.stopPropagation(); props.showInfo(pathInfo(props)) }}
|
||||
/>
|
||||
)
|
||||
|
||||
const TextOnPath = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
// Handle translation (and spaces)
|
||||
let translated = ''
|
||||
for (let string of props.path.attributes.getAsArray('data-text')) {
|
||||
translated += t(string).replace(/"/g, '"') + ' '
|
||||
}
|
||||
const textPathProps = {
|
||||
xlinkHref: '#' + props.pathId,
|
||||
startOffset: '0%'
|
||||
}
|
||||
const align = props.path.attributes.get('data-text-class')
|
||||
if (align && align.indexOf('center') > -1) textPathProps.startOffset = '50%'
|
||||
else if (align && align.indexOf('right') > -1) textPathProps.startOffset = '100%'
|
||||
|
||||
const attr = props.path.attributes.asPropsIfPrefixIs('data-text-')
|
||||
|
||||
return (
|
||||
<text>
|
||||
<textPath {...textPathProps}>
|
||||
<tspan {...attr} dangerouslySetInnerHTML={{__html: translated}} />
|
||||
</textPath>
|
||||
{props.gist._state?.xray?.enabled && (
|
||||
<textPath {...textPathProps}>
|
||||
<XrayTextOnPath {...props} attr={attr} translated={translated}/>
|
||||
</textPath>
|
||||
)}
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextOnPath
|
148
sites/shared/components/workbench/draft/text.mjs
Normal file
148
sites/shared/components/workbench/draft/text.mjs
Normal file
|
@ -0,0 +1,148 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { Tr, KeyTd, ValTd, Attributes, pointCoords, pathInfo } from './path.mjs'
|
||||
|
||||
const textInfo = (props) =>
|
||||
props.point ? (
|
||||
<div className="p-4 border bg-neutral bg-opacity-60 shadow rounded-lg">
|
||||
<h5 className="text-neutral-content text-center pb-4">Text info</h5>
|
||||
<table className="border-collapse h-fit">
|
||||
<tbody>
|
||||
<Tr>
|
||||
<KeyTd>Coordinates</KeyTd>
|
||||
<ValTd>{pointCoords(props.point)}</ValTd>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<KeyTd>Name</KeyTd>
|
||||
<ValTd>{props.pointName}</ValTd>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<KeyTd>Part</KeyTd>
|
||||
<ValTd>{props.partName}</ValTd>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<KeyTd>Attributes</KeyTd>
|
||||
<ValTd>
|
||||
<Attributes list={props.point.attributes.list} />
|
||||
</ValTd>
|
||||
</Tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="flex flex-col flex-wrap gap-2 mt-4">
|
||||
<button className="btn btn-success" onClick={() => console.log(props.point)}>
|
||||
console.log(point)
|
||||
</button>
|
||||
<button className="btn btn-success" onClick={() => console.table(props.point)}>
|
||||
console.table(point)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
|
||||
const XrayText = (props) => (
|
||||
<text x={props.point.x} y={props.point.y} {...props.attr}>
|
||||
<TextSpans
|
||||
{...props}
|
||||
className="stroke-transparent stroke-4xl opacity-10 hover:cursor-pointer hover:stroke-secondary"
|
||||
style={{ strokeOpacity: 0.25 }}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation()
|
||||
props.showInfo(textInfo(props))
|
||||
}}
|
||||
/>
|
||||
</text>
|
||||
)
|
||||
|
||||
const TextSpans = ({ point, className = '', style = {}, onClick = null }) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
let text = []
|
||||
// Handle translation
|
||||
let translated = ''
|
||||
for (const string of point.attributes.getAsArray('data-text')) {
|
||||
if (string) translated += t(string.toString()).replace(/"/g, '"') + ' '
|
||||
}
|
||||
// Handle muti-line text
|
||||
if (translated.indexOf('\n') !== -1) {
|
||||
let key = 0
|
||||
let lines = translated.split('\n')
|
||||
text.push(<tspan key={'tspan-' + key}>{lines.shift()}</tspan>)
|
||||
for (let line of lines) {
|
||||
key++
|
||||
text.push(
|
||||
<tspan
|
||||
key={'tspan-' + key}
|
||||
x={point.x}
|
||||
dy={point.attributes.get('data-text-lineheight') || 12}
|
||||
className={className}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
>
|
||||
{line.toString().replace(/"/g, '"')}
|
||||
</tspan>
|
||||
)
|
||||
}
|
||||
} else
|
||||
text.push(
|
||||
<tspan key="tspan-1" className={className} style={style} onClick={onClick}>
|
||||
{translated}
|
||||
</tspan>
|
||||
)
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
export const Text = (props) => {
|
||||
const attr = props.point.attributes.asPropsIfPrefixIs('data-text-')
|
||||
|
||||
return (
|
||||
<>
|
||||
<text x={props.point.x} y={props.point.y} {...attr}>
|
||||
<TextSpans {...props} />
|
||||
</text>
|
||||
{props.gist._state?.xray?.enabled && <XrayText {...props} attr={attr} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const XrayTextOnPath = (props) => (
|
||||
<tspan
|
||||
{...props.attr}
|
||||
dangerouslySetInnerHTML={{ __html: props.translated }}
|
||||
className={`${props.attr.className} no-fill stroke-transparent stroke-4xl opacity-10 hover:cursor-pointer hover:stroke-secondary`}
|
||||
style={{ strokeOpacity: 0.25 }}
|
||||
onClick={(evt) => {
|
||||
evt.stopPropagation()
|
||||
props.showInfo(pathInfo(props))
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
export const TextOnPath = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
// Handle translation (and spaces)
|
||||
let translated = ''
|
||||
for (let string of props.path.attributes.getAsArray('data-text')) {
|
||||
translated += t(string).replace(/"/g, '"') + ' '
|
||||
}
|
||||
const textPathProps = {
|
||||
xlinkHref: '#' + props.pathId,
|
||||
startOffset: '0%',
|
||||
}
|
||||
const align = props.path.attributes.get('data-text-class')
|
||||
if (align && align.indexOf('center') > -1) textPathProps.startOffset = '50%'
|
||||
else if (align && align.indexOf('right') > -1) textPathProps.startOffset = '100%'
|
||||
|
||||
const attr = props.path.attributes.asPropsIfPrefixIs('data-text-')
|
||||
|
||||
return (
|
||||
<text>
|
||||
<textPath {...textPathProps}>
|
||||
<tspan {...attr} dangerouslySetInnerHTML={{ __html: translated }} />
|
||||
</textPath>
|
||||
{props.gist._state?.xray?.enabled && (
|
||||
<textPath {...textPathProps}>
|
||||
<XrayTextOnPath {...props} attr={attr} translated={translated} />
|
||||
</textPath>
|
||||
)}
|
||||
</text>
|
||||
)
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { Tr, KeyTd, ValTd, Attributes, pointCoords } from '../path/index'
|
||||
|
||||
const textInfo = (props) => props.point
|
||||
? (
|
||||
<div className="p-4 border bg-neutral bg-opacity-60 shadow rounded-lg">
|
||||
<h5 className="text-neutral-content text-center pb-4">Text info</h5>
|
||||
<table className="border-collapse h-fit">
|
||||
<tbody>
|
||||
<Tr>
|
||||
<KeyTd>Coordinates</KeyTd>
|
||||
<ValTd>{pointCoords(props.point)}</ValTd>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<KeyTd>Name</KeyTd>
|
||||
<ValTd>{props.pointName}</ValTd>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<KeyTd>Part</KeyTd>
|
||||
<ValTd>{props.partName}</ValTd>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<KeyTd>Attributes</KeyTd>
|
||||
<ValTd><Attributes list={props.point.attributes.list} /></ValTd>
|
||||
</Tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="flex flex-col flex-wrap gap-2 mt-4">
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={() => console.log(props.point)}
|
||||
>console.log(point)</button>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={() => console.table(props.point)}
|
||||
>console.table(point)</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null
|
||||
|
||||
const XrayText = props => (
|
||||
<text
|
||||
x={props.point.x}
|
||||
y={props.point.y}
|
||||
{...props.attr}
|
||||
>
|
||||
<TextSpans {...props}
|
||||
className="stroke-transparent stroke-4xl opacity-10 hover:cursor-pointer hover:stroke-secondary"
|
||||
style={{strokeOpacity: 0.25}}
|
||||
onClick={(evt) => { evt.stopPropagation(); props.showInfo(textInfo(props)) }}
|
||||
/>
|
||||
</text>
|
||||
)
|
||||
|
||||
const TextSpans = ({ point, className='', style={}, onClick=null }) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
let text = []
|
||||
// Handle translation
|
||||
let translated = ''
|
||||
for (const string of point.attributes.getAsArray('data-text')) {
|
||||
if (string) translated += t(string.toString()).replace(/"/g, '"') + ' '
|
||||
}
|
||||
// Handle muti-line text
|
||||
if (translated.indexOf('\n') !== -1) {
|
||||
let key = 0
|
||||
let lines = translated.split('\n')
|
||||
text.push(<tspan key={'tspan-' + key}>{lines.shift()}</tspan>)
|
||||
for (let line of lines) {
|
||||
key++
|
||||
text.push(
|
||||
<tspan
|
||||
key={'tspan-' + key}
|
||||
x={point.x}
|
||||
dy={point.attributes.get('data-text-lineheight') || 12}
|
||||
className={className} style={style}
|
||||
onClick={onClick}
|
||||
>
|
||||
{line.toString().replace(/"/g, '"')}
|
||||
</tspan>
|
||||
)
|
||||
}
|
||||
} else text.push(<tspan key="tspan-1"
|
||||
className={className}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
>{translated}</tspan>)
|
||||
|
||||
return text
|
||||
|
||||
}
|
||||
|
||||
const Text = (props) => {
|
||||
const attr = props.point.attributes.asPropsIfPrefixIs('data-text-')
|
||||
|
||||
return (
|
||||
<>
|
||||
<text
|
||||
x={props.point.x}
|
||||
y={props.point.y}
|
||||
{...attr}
|
||||
>
|
||||
<TextSpans {...props}/>
|
||||
</text>
|
||||
{props.gist._state?.xray?.enabled && <XrayText {...props} attr={attr}/>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Text
|
|
@ -1,8 +1,8 @@
|
|||
import Worker from 'web-worker'
|
||||
import fileSaver from 'file-saver'
|
||||
import { themePlugin } from '@freesewing/plugin-theme'
|
||||
import { pagesPlugin } from '../layout/print/plugin'
|
||||
import { capitalize } from 'shared/utils'
|
||||
import { pagesPlugin } from '../layout/print/plugin.mjs'
|
||||
import { capitalize } from 'shared/utils.mjs'
|
||||
|
||||
export const exportTypes = {
|
||||
exportForPrinting: ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid'],
|
|
@ -3,7 +3,7 @@
|
|||
* */
|
||||
import yaml from 'js-yaml'
|
||||
import axios from 'axios'
|
||||
import PdfMaker from './pdf-maker'
|
||||
import { PdfMaker } from './pdf-maker'
|
||||
|
||||
/** when the worker receives data from the page, do the appropriate export */
|
||||
addEventListener('message', async (e) => {
|
||||
|
@ -32,7 +32,7 @@ const postSuccess = (blob) => {
|
|||
/** export a blob */
|
||||
const exportBlob = (blobContent, type) => {
|
||||
const blob = new Blob([blobContent], {
|
||||
type: `${type};charset=utf-8`
|
||||
type: `${type};charset=utf-8`,
|
||||
})
|
||||
postSuccess(blob)
|
||||
}
|
||||
|
@ -50,14 +50,15 @@ const exportPdf = async (data) => {
|
|||
}
|
||||
|
||||
const exportGithubGist = (data) => {
|
||||
axios.post('https://backend.freesewing.org/github/gist', {
|
||||
axios
|
||||
.post('https://backend.freesewing.org/github/gist', {
|
||||
design: data.design,
|
||||
data: yaml.dump(data)
|
||||
data: yaml.dump(data),
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
postMessage({
|
||||
success: true,
|
||||
link: 'https://gist.github.com/' + res.data.id
|
||||
link: 'https://gist.github.com/' + res.data.id,
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
import { useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Popout from 'shared/components/popout'
|
||||
import WebLink from 'shared/components/web-link'
|
||||
import { exportTypes, handleExport } from './export-handler'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
import { WebLink } from 'shared/components/web-link.mjs'
|
||||
import { exportTypes, handleExport } from './export-handler.mjs'
|
||||
|
||||
const ExportDraft = ({ gist, design, app }) => {
|
||||
export const ExportDraft = ({ gist, design, app }) => {
|
||||
const [link, setLink] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
const [format, setFormat] = useState(false)
|
||||
|
@ -64,5 +64,3 @@ const ExportDraft = ({ gist, design, app }) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ExportDraft
|
|
@ -1,6 +1,6 @@
|
|||
import PDFDocument from 'pdfkit/js/pdfkit.standalone'
|
||||
import SVGtoPDF from 'svg-to-pdfkit'
|
||||
import { logoPath } from 'shared/components/icons.mjs'
|
||||
import { logoPath } from 'shared/logos/freesewing.mjs'
|
||||
|
||||
/** an svg of the logo to put on the cover page */
|
||||
const logoSvg = `<svg viewBox="0 0 25 25">
|
||||
|
@ -18,7 +18,7 @@ const mmToPoints = 2.834645669291339
|
|||
* Freesewing's first explicit class?
|
||||
* handles pdf exporting
|
||||
*/
|
||||
export default class PdfMaker {
|
||||
export class PdfMaker {
|
||||
/** the svg as text to embed in the pdf */
|
||||
svg
|
||||
/** the document configuration */
|
|
@ -1,9 +0,0 @@
|
|||
import Json from 'shared/components/json-highlight.js'
|
||||
|
||||
const GistAsJson = props => (
|
||||
<div className="max-w-screen-xl m-auto">
|
||||
<Json>{JSON.stringify(props.gist, null, 2)}</Json>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default GistAsJson
|
58
sites/shared/components/workbench/gist.mjs
Normal file
58
sites/shared/components/workbench/gist.mjs
Normal file
|
@ -0,0 +1,58 @@
|
|||
import yaml from 'js-yaml'
|
||||
import axios from 'axios'
|
||||
import { Json } from 'shared/components/json.mjs'
|
||||
import { Yaml } from 'shared/components/yaml.mjs'
|
||||
|
||||
export const GistAsJson = (props) => (
|
||||
<div className="max-w-screen-xl m-auto">
|
||||
<Json>{JSON.stringify(props.gist, null, 2)}</Json>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const GistAsYaml = (props) => (
|
||||
<div className="max-w-screen-xl m-auto">
|
||||
<Yaml json={JSON.stringify(props.gist, null, 2)} />
|
||||
</div>
|
||||
)
|
||||
|
||||
export const defaultGist = {
|
||||
sa: 0,
|
||||
saBool: false,
|
||||
saMm: 10,
|
||||
scale: 1,
|
||||
complete: true,
|
||||
paperless: false,
|
||||
units: 'metric',
|
||||
locale: 'en',
|
||||
margin: 2,
|
||||
renderer: 'react',
|
||||
embed: true,
|
||||
debug: true,
|
||||
}
|
||||
|
||||
export const preloadGist = {
|
||||
github: async (id, design) => {
|
||||
let result
|
||||
try {
|
||||
result = await axios.get(`https://api.github.com/gists/${id}`)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return [false, 'An unexpected error occured']
|
||||
}
|
||||
|
||||
if (result.data.files['pattern.yaml'].content) {
|
||||
let g = yaml.load(result.data.files['pattern.yaml'].content)
|
||||
|
||||
if (g.design !== undefined && g.design !== design.designConfig.data.name)
|
||||
return [
|
||||
false,
|
||||
`You tried loading a configuration for ${g.design} into a ${design.designConfig.data.name} development environment`,
|
||||
]
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// TODO notify people of these errors
|
||||
else return [false, 'This gist does not seem to be a valid pattern configuration']
|
||||
},
|
||||
}
|
|
@ -23,7 +23,7 @@ const EditCount = (props) => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const DesignOptionCount = (props) => {
|
||||
export const DesignOptionCount = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const { count, max, min } = props.design.patternConfig.options[props.option]
|
||||
const val =
|
||||
|
@ -104,5 +104,3 @@ const DesignOptionCount = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DesignOptionCount
|
|
@ -2,7 +2,7 @@ import { useState } from 'react'
|
|||
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const DesignOptionList = (props) => {
|
||||
export const DesignOptionList = (props) => {
|
||||
const { t } = useTranslation([`o_${props.design.designConfig.data.name}`])
|
||||
const { dflt, list, doNotTranslate = false } = props.design.patternConfig.options[props.option]
|
||||
const val =
|
|
@ -33,7 +33,7 @@ const EditOption = (props) => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const DesignOptionPctDeg = (props) => {
|
||||
export const DesignOptionPctDeg = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const suffix = props.type === 'deg' ? '°' : '%'
|
||||
const factor = props.type === 'deg' ? 1 : 100
|
||||
|
@ -134,5 +134,3 @@ const DesignOptionPctDeg = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DesignOptionPctDeg
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { isDegreeMeasurement } from '../../../config/measurements'
|
||||
import { measurementAsMm } from 'shared/utils'
|
||||
import { measurementAsMm } from 'shared/utils.mjs'
|
||||
|
||||
/*
|
||||
* This is a single input for a measurements
|
||||
|
@ -11,60 +11,65 @@ import { measurementAsMm } from 'shared/utils'
|
|||
* 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, focus }) => {
|
||||
export const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) => {
|
||||
const { t } = useTranslation(['app', 'measurements'])
|
||||
const prefix = (app.site === 'org') ? '' : 'https://freesewing.org'
|
||||
const prefix = app.site === 'org' ? '' : 'https://freesewing.org'
|
||||
const title = t(`measurements:${m}`)
|
||||
|
||||
const isDegree = isDegreeMeasurement(m);
|
||||
const factor = useMemo(() => (isDegree ? 1 : (gist.units == 'imperial' ? 25.4 : 10)), [gist.units])
|
||||
const isDegree = isDegreeMeasurement(m)
|
||||
const factor = useMemo(() => (isDegree ? 1 : gist.units == 'imperial' ? 25.4 : 10), [gist.units])
|
||||
|
||||
const isValValid = val => (typeof val === 'undefined' || val === '')
|
||||
? null
|
||||
: val != false && !isNaN(val)
|
||||
const isValid = (newVal) => (typeof newVal === 'undefined')
|
||||
? isValValid(val)
|
||||
: isValValid(newVal)
|
||||
const isValValid = (val) =>
|
||||
typeof val === 'undefined' || val === '' ? null : val != false && !isNaN(val)
|
||||
const isValid = (newVal) => (typeof newVal === 'undefined' ? isValValid(val) : isValValid(newVal))
|
||||
|
||||
const [val, setVal] = useState(gist.measurements?.[m] / factor || '')
|
||||
|
||||
// keep a single reference to a debounce timer
|
||||
const debounceTimeout = useRef(null);
|
||||
const input = useRef(null);
|
||||
const debounceTimeout = useRef(null)
|
||||
const input = useRef(null)
|
||||
|
||||
// onChange
|
||||
const update = useCallback((evt) => {
|
||||
evt.stopPropagation();
|
||||
let evtVal = evt.target.value;
|
||||
const update = useCallback(
|
||||
(evt) => {
|
||||
evt.stopPropagation()
|
||||
let evtVal = evt.target.value
|
||||
// set Val immediately so that the input reflects it
|
||||
setVal(evtVal)
|
||||
|
||||
let useVal = isDegree ? evtVal : measurementAsMm(evtVal, gist.units);
|
||||
let useVal = isDegree ? evtVal : measurementAsMm(evtVal, gist.units)
|
||||
const ok = isValid(useVal)
|
||||
// only set to the gist if it's valid
|
||||
if (ok) {
|
||||
// debounce in case it's still changing
|
||||
if (debounceTimeout.current !== null) { clearTimeout(debounceTimeout.current); }
|
||||
if (debounceTimeout.current !== null) {
|
||||
clearTimeout(debounceTimeout.current)
|
||||
}
|
||||
debounceTimeout.current = setTimeout(() => {
|
||||
// clear the timeout reference
|
||||
debounceTimeout.current = null;
|
||||
debounceTimeout.current = null
|
||||
updateMeasurements(useVal, m)
|
||||
}, 500);
|
||||
}, 500)
|
||||
}
|
||||
}, [gist.units])
|
||||
},
|
||||
[gist.units]
|
||||
)
|
||||
|
||||
// use this for better update efficiency
|
||||
// FIXME: This breaks gist updates.
|
||||
// See: https://github.com/freesewing/freesewing/issues/2281
|
||||
const memoVal = useMemo(() => gist.measurements?.[m], [gist])
|
||||
// track validity against the value and the units
|
||||
const valid = useMemo(() => isValid(isDegree ? val : measurementAsMm(val, gist.units)), [val, gist.units])
|
||||
const valid = useMemo(
|
||||
() => isValid(isDegree ? val : measurementAsMm(val, gist.units)),
|
||||
[val, gist.units]
|
||||
)
|
||||
|
||||
// hook to update the value or format when the gist changes
|
||||
useEffect(() => {
|
||||
// set the value to the proper value and format
|
||||
if (memoVal) {
|
||||
let gistVal = +(memoVal / factor).toFixed(2);
|
||||
let gistVal = +(memoVal / factor).toFixed(2)
|
||||
setVal(gistVal)
|
||||
}
|
||||
}, [memoVal, factor])
|
||||
|
@ -72,7 +77,7 @@ const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) => {
|
|||
// focus when prompted by parent
|
||||
useEffect(() => {
|
||||
if (focus) {
|
||||
input.current.focus();
|
||||
input.current.focus()
|
||||
}
|
||||
}, [focus])
|
||||
|
||||
|
@ -110,25 +115,27 @@ const MeasurementInput = ({ m, gist, app, updateMeasurements, focus }) => {
|
|||
value={val}
|
||||
onChange={update}
|
||||
/>
|
||||
<span role="img" className={`bg-transparent border-y
|
||||
<span
|
||||
role="img"
|
||||
className={`bg-transparent border-y
|
||||
${valid === false && 'border-error text-neutral-content'}
|
||||
${valid === true && 'border-success text-neutral-content'}
|
||||
${valid === null && 'border-base-200 text-base-content'}
|
||||
`}>
|
||||
{(valid === true) && '👍'}
|
||||
{(valid === false) && '🤔'}
|
||||
`}
|
||||
>
|
||||
{valid === true && '👍'}
|
||||
{valid === false && '🤔'}
|
||||
</span>
|
||||
<span className={`
|
||||
<span
|
||||
className={`
|
||||
${valid === false && 'bg-error text-neutral-content'}
|
||||
${valid === true && 'bg-success text-neutral-content'}
|
||||
${valid === null && 'bg-base-200 text-base-content'}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
{isDegree ? '° ' : gist.units == 'metric' ? 'cm' : 'in'}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MeasurementInput
|
||||
|
|
@ -1,23 +1,15 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import Settings from './settings'
|
||||
import { CutLayoutSettings } from './settings.mjs'
|
||||
|
||||
const CutLayout = props => {
|
||||
export const CutLayout = (props) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
|
||||
let name = props.design.designConfig.data.name
|
||||
name = name.replace('@freesewing/', '')
|
||||
return (
|
||||
<div>
|
||||
<h2 className="capitalize">
|
||||
{
|
||||
t('layoutThing', { thing: name })
|
||||
+ ': '
|
||||
+ t('forCutting')
|
||||
}
|
||||
</h2>
|
||||
<h2 className="capitalize">{t('layoutThing', { thing: name }) + ': ' + t('forCutting')}</h2>
|
||||
<Settings {...props} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CutLayout
|
|
@ -1,10 +1,7 @@
|
|||
const CutLayoutSettings = props => {
|
||||
|
||||
export const CutLayoutSettings = (props) => {
|
||||
return (
|
||||
<div>
|
||||
<p>Fixme: Cut layout settings here</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CutLayoutSettings
|
222
sites/shared/components/workbench/layout/default.mjs
Normal file
222
sites/shared/components/workbench/layout/default.mjs
Normal file
|
@ -0,0 +1,222 @@
|
|||
// Hooks
|
||||
import { useState, Fragment } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
// Depenencies
|
||||
import get from 'lodash.get'
|
||||
// Components
|
||||
import Link from 'next/link'
|
||||
import { FreeSewingLogo } from 'shared/components/logos/freesewing.mjs'
|
||||
import { PrimaryNavigation } from 'shared/components/navigation/primary.mjs'
|
||||
import { RightIcon, LeftIcon } from 'shared/components/icons.mjs'
|
||||
import { Header } from 'site/components/header.mjs'
|
||||
import { Footer } from 'site/components/footer.mjs'
|
||||
import { Search } from 'site/components/search.mjs'
|
||||
|
||||
export const PageTitle = ({ app, slug, title }) => {
|
||||
if (title) return <h1>{title}</h1>
|
||||
if (slug) return <h1>{get(app.navigation, slug.split('/')).__title}</h1>
|
||||
|
||||
return <h1>FIXME: This page has no title</h1>
|
||||
}
|
||||
|
||||
const Breadcrumbs = ({ app, slug = false, title }) => {
|
||||
if (!slug) return null
|
||||
const crumbs = []
|
||||
const chunks = slug.split('/')
|
||||
for (const i in chunks) {
|
||||
const j = parseInt(i) + parseInt(1)
|
||||
const page = get(app.navigation, chunks.slice(0, j))
|
||||
if (page) crumbs.push([page.__linktitle, '/' + chunks.slice(0, j).join('/'), j < chunks.length])
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="flex flex-row flex-wrap gap-2 font-bold">
|
||||
<li>
|
||||
<Link href="/" title="To the homepage" className="text-base-content">
|
||||
<Logo size={24} fill="currentColor" stroke={false} />
|
||||
</Link>
|
||||
</li>
|
||||
{crumbs.map((crumb) => (
|
||||
<Fragment key={crumb[1]}>
|
||||
<li className="text-base-content">»</li>
|
||||
<li>
|
||||
{crumb[2] ? (
|
||||
<Link
|
||||
href={crumb[1]}
|
||||
title={crumb[0]}
|
||||
className="text-secondary hover:text-secondary-focus"
|
||||
>
|
||||
{crumb[0]}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="text-base-content">{crumb[0]}</span>
|
||||
)}
|
||||
</li>
|
||||
</Fragment>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
const asideClasses = `
|
||||
fixed top-0 right-0
|
||||
pt-28
|
||||
sm:pt-8 sm:mt-16
|
||||
pb-4 px-2
|
||||
sm:relative sm:transform-none
|
||||
h-screen w-screen
|
||||
bg-base-100
|
||||
sm:bg-base-50
|
||||
sm:flex
|
||||
sm:sticky
|
||||
overflow-y-scroll
|
||||
z-20
|
||||
bg-base-100 text-base-content
|
||||
transition-all
|
||||
xl:w-1/4
|
||||
`
|
||||
|
||||
export const DefaultLayout = ({
|
||||
app,
|
||||
title = false,
|
||||
children = [],
|
||||
search,
|
||||
setSearch,
|
||||
noSearch = false,
|
||||
workbench = false,
|
||||
AltMenu = null,
|
||||
}) => {
|
||||
const startNavigation = () => {
|
||||
app.startLoading()
|
||||
// Force close of menu on mobile if it is open
|
||||
if (app.primaryNavigation) app.setPrimaryNavigation(false)
|
||||
// Force close of search modal if it is open
|
||||
if (search) setSearch(false)
|
||||
}
|
||||
|
||||
const router = useRouter()
|
||||
router.events?.on('routeChangeStart', startNavigation)
|
||||
router.events?.on('routeChangeComplete', () => app.stopLoading())
|
||||
const slug = router.asPath.slice(1)
|
||||
const [collapsePrimaryNav, setCollapsePrimaryNav] = useState(workbench || false)
|
||||
const [collapseAltMenu, setCollapseAltMenu] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
flex flex-col justify-between
|
||||
min-h-screen
|
||||
bg-base-100
|
||||
`}
|
||||
>
|
||||
<Header app={app} setSearch={setSearch} />
|
||||
<main className="grow bg-base-100">
|
||||
<div className="m-auto flex flex-row justify-center">
|
||||
<aside
|
||||
className={`
|
||||
${asideClasses}
|
||||
${app.primaryMenu ? '' : 'translate-x-[-100%]'} transition-transform
|
||||
sm:flex-row-reverse
|
||||
${workbench && collapsePrimaryNav ? 'sm:px-0 sm:w-16' : 'sm:px-1 md:px-4 lg:px-8'}
|
||||
w-96
|
||||
`}
|
||||
>
|
||||
{workbench && (
|
||||
<div className={`hidden sm:flex`}>
|
||||
<button
|
||||
className="text-secondary-focus h-full px-2 pl-4 hover:animate-pulse"
|
||||
onClick={() => setCollapsePrimaryNav(!collapsePrimaryNav)}
|
||||
>
|
||||
{collapsePrimaryNav ? (
|
||||
<>
|
||||
<RightIcon />
|
||||
<RightIcon />
|
||||
<RightIcon />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<LeftIcon />
|
||||
<LeftIcon />
|
||||
<LeftIcon />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<PrimaryNavigation app={app} active={slug} />
|
||||
</aside>
|
||||
|
||||
<div className="p-0 m-0 bg-base-100">
|
||||
<section
|
||||
className={`
|
||||
p-4 pt-24 sm:pt-28
|
||||
sm:px-1 md:px-4 lg:px-8
|
||||
${workbench && collapsePrimaryNav ? '' : 'max-w-7xl'}
|
||||
`}
|
||||
>
|
||||
<div>
|
||||
{title && (
|
||||
<>
|
||||
<Breadcrumbs app={app} slug={slug} title={title} />
|
||||
<PageTitle app={app} slug={slug} title={title} />
|
||||
</>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{workbench && AltMenu && (
|
||||
<aside
|
||||
className={`
|
||||
${asideClasses}
|
||||
${app.primaryMenu ? '' : 'translate-x-[-100%]'} transition-transform
|
||||
sm:flex-row
|
||||
${collapseAltMenu ? 'sm:px-0 sm:w-16' : 'sm:px-1 md:px-4 lg:px-8 sm:w-[38.2%]'}
|
||||
`}
|
||||
>
|
||||
<div className={`hidden sm:flex`}>
|
||||
<button
|
||||
className="text-secondary-focus h-full px-2 pr-4 hover:animate-pulse"
|
||||
onClick={() => setCollapseAltMenu(!collapseAltMenu)}
|
||||
>
|
||||
{collapseAltMenu ? (
|
||||
<>
|
||||
<Left />
|
||||
<Left />
|
||||
<Left />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Right />
|
||||
<Right />
|
||||
<Right />
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{AltMenu}
|
||||
</aside>
|
||||
)}
|
||||
</main>
|
||||
{!noSearch && search && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
fixed w-full max-h-screen bg-base-100 top-0 z-30 pt-0 pb-16 px-8
|
||||
sm:rounded-lg sm:top-24
|
||||
sm:max-w-xl sm:m-auto sm:inset-x-12
|
||||
md:max-w-2xl
|
||||
lg:max-w-4xl
|
||||
`}
|
||||
>
|
||||
<Search app={app} search={search} setSearch={setSearch} />
|
||||
</div>
|
||||
<div className="fixed top-0 left-0 w-full min-h-screen bg-neutral z-20 bg-opacity-70"></div>
|
||||
</>
|
||||
)}
|
||||
<Footer app={app} />
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
import {FlipIconInner} from 'shared/components/icons/flip'
|
||||
import {RotateIconInner} from 'shared/components/icons/rotate'
|
||||
import {ClearIconInner} from 'shared/components/icons/clear'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const rectSize = 24
|
||||
|
||||
const Button = ({onClickCb, transform, Icon, children}) => {
|
||||
const _onClick = (event) => {
|
||||
event.stopPropagation();
|
||||
onClickCb(event);
|
||||
}
|
||||
|
||||
return <g className="svg-layout-button group" transform={transform}>
|
||||
<rect width={rectSize} height={rectSize} className="button"/>
|
||||
<Icon />
|
||||
<text className="invisible group-hover:visible text-xl">{children}</text>
|
||||
<rect width={rectSize} height={rectSize} onClick={_onClick} className="fill-transparent"/>
|
||||
</g>}
|
||||
|
||||
/** buttons for manipulating the part */
|
||||
const Buttons = ({ transform, flip, rotate, setRotate, resetPart, rotate90}) => {
|
||||
const {t} = useTranslation('workbench')
|
||||
return (
|
||||
<g transform={transform} >
|
||||
{rotate
|
||||
? <circle cx="0" cy="0" r="50" className='stroke-2xl muted' />
|
||||
: <path d="M -50, 0 l 100,0 M 0,-50 l 0,100" className="stroke-2xl muted" />
|
||||
}
|
||||
<Button
|
||||
onClickCb={resetPart}
|
||||
transform={`translate(${rectSize/-2}, ${rectSize/-2})`}
|
||||
Icon={ClearIconInner}>
|
||||
{t('toolbar.resetPart')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => rotate90(-1)}
|
||||
transform={`translate(${rectSize* -2.7}, ${rectSize/-2})`}
|
||||
Icon={RotateIconInner}
|
||||
>
|
||||
{t('toolbar.rotateCCW')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => flip('y')}
|
||||
transform={`translate(${rectSize* 0.6}, ${rectSize/-2})`}
|
||||
Icon={() => <FlipIconInner rotate="270" />}
|
||||
>
|
||||
{t('toolbar.flipY')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => flip('x')}
|
||||
transform={`translate(${rectSize* -1.6}, ${rectSize/-2})`}
|
||||
Icon={FlipIconInner}>
|
||||
{t('toolbar.flipX')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => rotate90()}
|
||||
transform={`translate(${rectSize* 1.7}, ${rectSize/-2})`}
|
||||
Icon={() => <RotateIconInner flipX={true}/>}>
|
||||
{t('toolbar.rotateCW')}
|
||||
</Button>
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
export default Buttons
|
98
sites/shared/components/workbench/layout/draft/buttons.mjs
Normal file
98
sites/shared/components/workbench/layout/draft/buttons.mjs
Normal file
|
@ -0,0 +1,98 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const Triangle = ({ transform = 'translate(0,0)', fill = 'currentColor' }) => (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
transform={transform}
|
||||
style={{ fill }}
|
||||
transform-origin="12 12"
|
||||
d="M1 12m9 3m-6 4h2c3 0 3 -3 3-3L9 3c-0-1.732 -2.25-2.6125 -3.325 -.77L2 16c-.77 1.333.192 3 1.732 3z"
|
||||
/>
|
||||
)
|
||||
|
||||
const FlipIconInner = ({ x = 0, y = 0, rotate = 0, ...style }) => (
|
||||
<g transform={`translate(${x},${y}) rotate(${rotate})`} transform-origin="12 12" style={style}>
|
||||
<Triangle fill="none" transform="translate(0, 2.5)" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" transform="translate(12, 2)" d="M0 0L0 20" />
|
||||
<Line />
|
||||
<Triangle transform="scale(-1,1) translate(0,2.5)" />
|
||||
</g>
|
||||
)
|
||||
|
||||
const RotateIconInner = ({ flipX = false }) => (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"
|
||||
transform={flipX ? 'scale(-1,1)' : ''}
|
||||
transform-origin="12 12"
|
||||
/>
|
||||
)
|
||||
|
||||
const rectSize = 24
|
||||
|
||||
const Button = ({ onClickCb, transform, Icon, children }) => {
|
||||
const _onClick = (event) => {
|
||||
event.stopPropagation()
|
||||
onClickCb(event)
|
||||
}
|
||||
|
||||
return (
|
||||
<g className="svg-layout-button group" transform={transform}>
|
||||
<rect width={rectSize} height={rectSize} className="button" />
|
||||
<Icon />
|
||||
<text className="invisible group-hover:visible text-xl">{children}</text>
|
||||
<rect width={rectSize} height={rectSize} onClick={_onClick} className="fill-transparent" />
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
/** buttons for manipulating the part */
|
||||
export const Buttons = ({ transform, flip, rotate, setRotate, resetPart, rotate90 }) => {
|
||||
const { t } = useTranslation('workbench')
|
||||
return (
|
||||
<g transform={transform}>
|
||||
{rotate ? (
|
||||
<circle cx="0" cy="0" r="50" className="stroke-2xl muted" />
|
||||
) : (
|
||||
<path d="M -50, 0 l 100,0 M 0,-50 l 0,100" className="stroke-2xl muted" />
|
||||
)}
|
||||
<Button
|
||||
onClickCb={resetPart}
|
||||
transform={`translate(${rectSize / -2}, ${rectSize / -2})`}
|
||||
Icon={ClearIconInner}
|
||||
>
|
||||
{t('toolbar.resetPart')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => rotate90(-1)}
|
||||
transform={`translate(${rectSize * -2.7}, ${rectSize / -2})`}
|
||||
Icon={RotateIconInner}
|
||||
>
|
||||
{t('toolbar.rotateCCW')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => flip('y')}
|
||||
transform={`translate(${rectSize * 0.6}, ${rectSize / -2})`}
|
||||
Icon={() => <FlipIconInner rotate="270" />}
|
||||
>
|
||||
{t('toolbar.flipY')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => flip('x')}
|
||||
transform={`translate(${rectSize * -1.6}, ${rectSize / -2})`}
|
||||
Icon={FlipIconInner}
|
||||
>
|
||||
{t('toolbar.flipX')}
|
||||
</Button>
|
||||
<Button
|
||||
onClickCb={() => rotate90()}
|
||||
transform={`translate(${rectSize * 1.7}, ${rectSize / -2})`}
|
||||
Icon={() => <RotateIconInner flipX={true} />}
|
||||
>
|
||||
{t('toolbar.rotateCW')}
|
||||
</Button>
|
||||
</g>
|
||||
)
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
import { useRef } from 'react'
|
||||
import Stack from './stack'
|
||||
import SvgWrapper from '../../draft/svg-wrapper'
|
||||
import { PartInner } from '../../draft/part'
|
||||
import { Stack } from './stack.mjs'
|
||||
import { SvgWrapper } from '../../draft/svg.mjs'
|
||||
import { PartInner } from '../../draft/part.mjs'
|
||||
|
||||
const Draft = (props) => {
|
||||
export const Draft = (props) => {
|
||||
const {
|
||||
draft,
|
||||
patternProps,
|
||||
|
@ -111,5 +111,3 @@ const Draft = (props) => {
|
|||
</SvgWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default Draft
|
|
@ -43,15 +43,15 @@
|
|||
* I've sort of left it at this because I'm starting to wonder if we should perhaps re-think
|
||||
* how custom layouts are supported in the core. And I would like to discuss this with the core team.
|
||||
*/
|
||||
import Part from '../../draft/part'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { generateStackTransform } from '@freesewing/core'
|
||||
import { getProps, angle } from '../../draft/utils'
|
||||
import { Part } from '../../draft/part.mjs'
|
||||
import { getProps, angle } from '../../draft/utils.mjs'
|
||||
import { drag } from 'd3-drag'
|
||||
import { select } from 'd3-selection'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import Buttons from './buttons'
|
||||
import { Buttons } from './buttons.mjs'
|
||||
|
||||
const Stack = (props) => {
|
||||
export const Stack = (props) => {
|
||||
const { layout, stack, stackName, gist } = props
|
||||
|
||||
const stackLayout = layout.stacks?.[stackName]
|
||||
|
@ -265,5 +265,3 @@ const Stack = (props) => {
|
|||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
export default Stack
|
|
@ -1,15 +1,15 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Settings from './settings'
|
||||
import Draft from '../draft/index'
|
||||
import { pagesPlugin } from './plugin'
|
||||
import { Settings } from './settings.mjs'
|
||||
import { Draft } from '../draft/index.mjs'
|
||||
import { pagesPlugin } from './plugin.mjs'
|
||||
import {
|
||||
handleExport,
|
||||
defaultPdfSettings,
|
||||
} from 'shared/components/workbench/exporting/export-handler'
|
||||
import Popout from 'shared/components/popout'
|
||||
} from 'shared/components/workbench/exporting/export-handler.mjs'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
||||
const PrintLayout = (props) => {
|
||||
export const PrintLayout = (props) => {
|
||||
// disable xray
|
||||
useEffect(() => {
|
||||
if (props.gist?._state?.xray?.enabled) props.updateGist(['_state', 'xray', 'enabled'], false)
|
||||
|
@ -76,5 +76,3 @@ const PrintLayout = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintLayout
|
|
@ -1,7 +1,7 @@
|
|||
import { PageIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const PageOrientationPicker = ({ gist, updateGist }) => {
|
||||
export const PageOrientationPicker = ({ gist, updateGist }) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
|
||||
return (
|
||||
|
@ -30,5 +30,3 @@ const PageOrientationPicker = ({ gist, updateGist }) => {
|
|||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageOrientationPicker
|
|
@ -1,10 +1,10 @@
|
|||
import { PageSizeIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import Popout from 'shared/components/popout'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
||||
const sizes = ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'tabloid']
|
||||
|
||||
const PageSizePicker = ({ gist, updateGist }) => {
|
||||
export const PageSizePicker = ({ gist, updateGist }) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
const setSize = (size) => {
|
||||
updateGist(['_state', 'layout', 'forPrinting', 'page', 'size'], size)
|
||||
|
@ -56,5 +56,3 @@ const PageSizePicker = ({ gist, updateGist }) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageSizePicker
|
|
@ -1,9 +1,9 @@
|
|||
import PageSizePicker from './pagesize-picker'
|
||||
import OrientationPicker from './orientation-picker'
|
||||
import { PageSizePicker } from './pagesize-picker.mjs'
|
||||
import { OrientationPicker } from './orientation-picker.mjs'
|
||||
import { PrintIcon, RightIcon, ClearIcon, ExportIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const PrintLayoutSettings = (props) => {
|
||||
export const PrintLayoutSettings = (props) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
let pages = props.draft?.setStores[0].get('pages')
|
||||
if (!pages) return null
|
||||
|
@ -106,5 +106,3 @@ const PrintLayoutSettings = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintLayoutSettings
|
|
@ -1,9 +1,8 @@
|
|||
import { useState } from 'react'
|
||||
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
|
||||
|
||||
const CoreSettingBool = props => {
|
||||
|
||||
export const CoreSettingBool = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const [value, setValue] = useState(props.gist[props.setting])
|
||||
|
||||
|
@ -24,5 +23,3 @@ const CoreSettingBool = props => {
|
|||
</Li>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoreSettingBool
|
|
@ -1,8 +1,8 @@
|
|||
import { useState } from 'react'
|
||||
import { Deg } from 'shared/components/workbench/menu/index.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { Deg } from 'shared/components/workbench/menu/index.mjs'
|
||||
|
||||
const CoreSettingList = props => {
|
||||
export const CoreSettingList = (props) => {
|
||||
const { t } = useTranslation(['settings'])
|
||||
const { dflt } = props
|
||||
const val = props.gist?.[props.setting]
|
||||
|
@ -29,7 +29,7 @@ const CoreSettingList = props => {
|
|||
</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="grow">
|
||||
{props.list.map(entry => (
|
||||
{props.list.map((entry) => (
|
||||
<button
|
||||
key={entry.key}
|
||||
onClick={() => handleChange(entry.key)}
|
||||
|
@ -47,5 +47,3 @@ const CoreSettingList = props => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoreSettingList
|
|
@ -1,9 +1,9 @@
|
|||
import { useState } from 'react'
|
||||
import { formatMm } from 'shared/utils'
|
||||
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { formatMm } from 'shared/utils.mjs'
|
||||
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||
|
||||
const CoreSettingMm = (props) => {
|
||||
export const CoreSettingMm = (props) => {
|
||||
const { t } = useTranslation(['app', 'settings'])
|
||||
const { dflt, min, max } = props
|
||||
const val = props.gist?.[props.setting]
|
||||
|
@ -69,5 +69,3 @@ const CoreSettingMm = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoreSettingMm
|
|
@ -2,7 +2,7 @@ import { useState } from 'react'
|
|||
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const CoreSettingNr = (props) => {
|
||||
export const CoreSettingNr = (props) => {
|
||||
const { t } = useTranslation(['app', 'settings'])
|
||||
const { dflt, min, max } = props
|
||||
const val = props.gist?.[props.setting]
|
||||
|
@ -61,5 +61,3 @@ const CoreSettingNr = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoreSettingNr
|
|
@ -2,7 +2,7 @@ import { ClearIcon } from 'shared/components/icons.mjs'
|
|||
import orderBy from 'lodash.orderby'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const CoreSettingOnly = (props) => {
|
||||
export const CoreSettingOnly = (props) => {
|
||||
const { t } = useTranslation(['app', 'parts', 'settings'])
|
||||
const list = props.draft.config.draftOrder
|
||||
const partNames = list.map((part) => ({ id: part, name: t(`parts:${part}`) }))
|
||||
|
@ -66,5 +66,3 @@ const CoreSettingOnly = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoreSettingOnly
|
|
@ -1,9 +1,8 @@
|
|||
import { useState } from 'react'
|
||||
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.js'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { SecText, SumButton, Li, SumDiv, Deg } from 'shared/components/workbench/menu/index.mjs'
|
||||
|
||||
const CoreSettingSaBool = props => {
|
||||
|
||||
export const CoreSettingSaBool = (props) => {
|
||||
const { t } = useTranslation(['app', 'settings'])
|
||||
const [value, setValue] = useState(props.gist.saBool || false)
|
||||
|
||||
|
@ -11,7 +10,7 @@ const CoreSettingSaBool = props => {
|
|||
props.setGist({
|
||||
...props.gist,
|
||||
saBool: !value,
|
||||
sa: value ? 0 : props.gist.saMm
|
||||
sa: value ? 0 : props.gist.saMm,
|
||||
})
|
||||
setValue(!value)
|
||||
}
|
||||
|
@ -28,5 +27,3 @@ const CoreSettingSaBool = props => {
|
|||
</Li>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoreSettingSaBool
|
|
@ -1,9 +1,9 @@
|
|||
import { useState } from 'react'
|
||||
import { formatMm } from 'shared/utils'
|
||||
import { formatMm } from 'shared/utils.mjs'
|
||||
import { ClearIcon } from 'shared/components/icons.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const CoreSettingMm = (props) => {
|
||||
export const CoreSettingMm = (props) => {
|
||||
const { t } = useTranslation(['app', 'settings'])
|
||||
const { dflt, min, max } = props
|
||||
const val = props.gist?.[props.setting]
|
||||
|
@ -70,5 +70,3 @@ const CoreSettingMm = (props) => {
|
|||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CoreSettingMm
|
|
@ -1,116 +0,0 @@
|
|||
import { Chevron } from 'shared/components/navigation/primary'
|
||||
import ListSetting from './core-setting-list'
|
||||
import OnlySetting from './core-setting-only'
|
||||
import MmSetting from './core-setting-mm'
|
||||
import NrSetting from './core-setting-nr'
|
||||
import BoolSetting from './core-setting-bool'
|
||||
import SaBoolSetting from './core-setting-sa-bool'
|
||||
import SaMmSetting from './core-setting-sa-mm'
|
||||
import { formatMm } from 'shared/utils'
|
||||
import { SecText, Li, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu/index'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const settings = {
|
||||
paperless: props => (
|
||||
<SecText>
|
||||
{props.t(props.gist.paperless ? 'yes' : 'no')}
|
||||
</SecText>
|
||||
),
|
||||
complete: props => (
|
||||
<SecText>
|
||||
{props.t(props.gist.complete ? 'yes' : 'no')}
|
||||
</SecText>
|
||||
),
|
||||
debug: props => (
|
||||
<SecText>
|
||||
{props.t(props.gist.debug ? 'yes' : 'no')}
|
||||
</SecText>
|
||||
),
|
||||
locale: props => (
|
||||
<SecText>
|
||||
{props.t(`i18n:${props.gist.locale}`)}
|
||||
</SecText>
|
||||
),
|
||||
units: props => (
|
||||
<SecText>
|
||||
{props.t(`${props.gist.units}Units`)}
|
||||
</SecText>
|
||||
),
|
||||
margin: props => <SecText raw={formatMm(props.gist.margin, props.gist.units)} />,
|
||||
scale: props => props.gist.scale === 1
|
||||
? <SecText>{props.gist.scale}</SecText>
|
||||
: <span className="text-accent">{props.gist.scale}</span>,
|
||||
saMm: props => <SecText raw={formatMm(props.gist.saMm, props.gist.units)} />,
|
||||
renderer: props => (
|
||||
<SecText>
|
||||
{props.config.titles[props.gist.renderer]}
|
||||
</SecText>
|
||||
),
|
||||
only: props => (props.gist?.only && props.gist.only.length > 0)
|
||||
? <SecText>{props.gist.only.length}</SecText>
|
||||
: <span className="text-secondary-focus">{props.t('default')}</span>
|
||||
}
|
||||
|
||||
const inputs = {
|
||||
locale: props => <ListSetting
|
||||
{...props}
|
||||
list={props.config.list.map(key => ({
|
||||
key,
|
||||
title: props.t(`i18n:${key}`)
|
||||
}))}
|
||||
/>,
|
||||
units: props => <ListSetting
|
||||
{...props}
|
||||
list={props.config.list.map(key => ({
|
||||
key,
|
||||
title: props.t(`${key}Units`)
|
||||
}))}
|
||||
/>,
|
||||
margin: props => <MmSetting {...props} {...props.config} />,
|
||||
scale: props => <NrSetting {...props} {...props.config} />,
|
||||
saMm: props => <SaMmSetting {...props} {...props.config} />,
|
||||
renderer: props => <ListSetting
|
||||
{...props}
|
||||
list={props.config.list.map(key => ({
|
||||
key,
|
||||
title: props.config.titles[key]
|
||||
}))}
|
||||
/>,
|
||||
only: props => <OnlySetting {...props} />
|
||||
}
|
||||
|
||||
const Setting = props => {
|
||||
const { t } = useTranslation(['app', 'i18n', 'settings'])
|
||||
if (props.setting === 'saBool')
|
||||
return <SaBoolSetting {...props} {...props.config} />
|
||||
if (['paperless', 'complete', 'debug', 'xray'].indexOf(props.setting) !== -1)
|
||||
return <BoolSetting {...props} {...props.config} />
|
||||
|
||||
const Input = inputs[props.setting]
|
||||
const Value = settings[props.setting]
|
||||
|
||||
return (
|
||||
<Li>
|
||||
<Details>
|
||||
<Summary>
|
||||
<SumDiv>
|
||||
<Deg />
|
||||
{props.setting === 'saMm'
|
||||
? (
|
||||
<>
|
||||
<span>{t(`settings:sa.t`)}</span>
|
||||
</>
|
||||
)
|
||||
: <span>{t(`settings:${props.setting}.t`)}</span>
|
||||
}
|
||||
</SumDiv>
|
||||
<Value setting={props.setting} {...props} t={t}/>
|
||||
<Chevron />
|
||||
</Summary>
|
||||
<Input {...props} t={t} />
|
||||
</Details>
|
||||
</Li>
|
||||
)
|
||||
}
|
||||
|
||||
export default Setting
|
107
sites/shared/components/workbench/menu/core-settings/setting.mjs
Normal file
107
sites/shared/components/workbench/menu/core-settings/setting.mjs
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { Chevron } from 'shared/components/navigation/primary.mjs'
|
||||
import { CoreSettingList as ListSetting } from './core-setting-list.mjs'
|
||||
import { CoreSettingOnly as OnlySetting } from './core-setting-only.mjs'
|
||||
import { CoreSettingMm as MmSetting } from './core-setting-mm.mjs'
|
||||
import { CoreSettingNr as NrSetting } from './core-setting-nr.mjs'
|
||||
import { CoreSettingBool as BoolSetting } from './core-setting-bool.mjs'
|
||||
import { CoreSettingSaBool as SaBoolSetting } from './core-setting-sa-bool.mjs'
|
||||
import { CoreSettingSaMm as SaMmSetting } from './core-setting-sa-mm.mjs'
|
||||
import { formatMm } from 'shared/utils.mjs'
|
||||
import {
|
||||
SecText,
|
||||
Li,
|
||||
Details,
|
||||
Summary,
|
||||
SumDiv,
|
||||
Deg,
|
||||
} from 'shared/components/workbench/menu/index.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const settings = {
|
||||
paperless: (props) => <SecText>{props.t(props.gist.paperless ? 'yes' : 'no')}</SecText>,
|
||||
complete: (props) => <SecText>{props.t(props.gist.complete ? 'yes' : 'no')}</SecText>,
|
||||
debug: (props) => <SecText>{props.t(props.gist.debug ? 'yes' : 'no')}</SecText>,
|
||||
locale: (props) => <SecText>{props.t(`i18n:${props.gist.locale}`)}</SecText>,
|
||||
units: (props) => <SecText>{props.t(`${props.gist.units}Units`)}</SecText>,
|
||||
margin: (props) => <SecText raw={formatMm(props.gist.margin, props.gist.units)} />,
|
||||
scale: (props) =>
|
||||
props.gist.scale === 1 ? (
|
||||
<SecText>{props.gist.scale}</SecText>
|
||||
) : (
|
||||
<span className="text-accent">{props.gist.scale}</span>
|
||||
),
|
||||
saMm: (props) => <SecText raw={formatMm(props.gist.saMm, props.gist.units)} />,
|
||||
renderer: (props) => <SecText>{props.config.titles[props.gist.renderer]}</SecText>,
|
||||
only: (props) =>
|
||||
props.gist?.only && props.gist.only.length > 0 ? (
|
||||
<SecText>{props.gist.only.length}</SecText>
|
||||
) : (
|
||||
<span className="text-secondary-focus">{props.t('default')}</span>
|
||||
),
|
||||
}
|
||||
|
||||
const inputs = {
|
||||
locale: (props) => (
|
||||
<ListSetting
|
||||
{...props}
|
||||
list={props.config.list.map((key) => ({
|
||||
key,
|
||||
title: props.t(`i18n:${key}`),
|
||||
}))}
|
||||
/>
|
||||
),
|
||||
units: (props) => (
|
||||
<ListSetting
|
||||
{...props}
|
||||
list={props.config.list.map((key) => ({
|
||||
key,
|
||||
title: props.t(`${key}Units`),
|
||||
}))}
|
||||
/>
|
||||
),
|
||||
margin: (props) => <MmSetting {...props} {...props.config} />,
|
||||
scale: (props) => <NrSetting {...props} {...props.config} />,
|
||||
saMm: (props) => <SaMmSetting {...props} {...props.config} />,
|
||||
renderer: (props) => (
|
||||
<ListSetting
|
||||
{...props}
|
||||
list={props.config.list.map((key) => ({
|
||||
key,
|
||||
title: props.config.titles[key],
|
||||
}))}
|
||||
/>
|
||||
),
|
||||
only: (props) => <OnlySetting {...props} />,
|
||||
}
|
||||
|
||||
export const Setting = (props) => {
|
||||
const { t } = useTranslation(['app', 'i18n', 'settings'])
|
||||
if (props.setting === 'saBool') return <SaBoolSetting {...props} {...props.config} />
|
||||
if (['paperless', 'complete', 'debug', 'xray'].indexOf(props.setting) !== -1)
|
||||
return <BoolSetting {...props} {...props.config} />
|
||||
|
||||
const Input = inputs[props.setting]
|
||||
const Value = settings[props.setting]
|
||||
|
||||
return (
|
||||
<Li>
|
||||
<Details>
|
||||
<Summary>
|
||||
<SumDiv>
|
||||
<Deg />
|
||||
{props.setting === 'saMm' ? (
|
||||
<>
|
||||
<span>{t(`settings:sa.t`)}</span>
|
||||
</>
|
||||
) : (
|
||||
<span>{t(`settings:${props.setting}.t`)}</span>
|
||||
)}
|
||||
</SumDiv>
|
||||
<Value setting={props.setting} {...props} t={t} />
|
||||
<Chevron />
|
||||
</Summary>
|
||||
<Input {...props} t={t} />
|
||||
</Details>
|
||||
</Li>
|
||||
)
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { OptionsIcon } from 'shared/components/icons.mjs'
|
||||
import { Chevron } from 'shared/components/navigation/primary.js'
|
||||
import OptionGroup from './option-group'
|
||||
import OptionComponent from './option'
|
||||
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu'
|
||||
import { OptionGroup } from './option-group.mjs'
|
||||
import { OptionComponent } from './option.mjs'
|
||||
import { Ul, Details, TopSummary, TopSumTitle } from 'shared/components/workbench/menu.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { optionsMenuStructure } from 'shared/utils.mjs'
|
||||
|
||||
const DesignOptions = (props) => {
|
||||
export const DesignOptions = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const Option = props.Option ? props.Option : OptionComponent
|
||||
const optionsMenu = optionsMenuStructure(props.design.patternConfig.options)
|
||||
|
@ -29,5 +29,3 @@ const DesignOptions = (props) => {
|
|||
</Details>
|
||||
)
|
||||
}
|
||||
|
||||
export default DesignOptions
|
|
@ -1,8 +1,8 @@
|
|||
import { Chevron } from 'shared/components/navigation/primary.js'
|
||||
import { Li, Ul, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu'
|
||||
import { Chevron } from 'shared/components/navigation/primary.mjs'
|
||||
import { Li, Ul, Details, Summary, SumDiv, Deg } from 'shared/components/workbench/menu.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
const OptionGroup = props => {
|
||||
export const OptionGroup = (props) => {
|
||||
const { t } = useTranslation(['optiongroups'])
|
||||
const Option = props.Option
|
||||
|
||||
|
@ -12,21 +12,20 @@ const OptionGroup = props => {
|
|||
<Summary>
|
||||
<SumDiv>
|
||||
<Deg />
|
||||
<span className="font-bold">
|
||||
{ t(props.group) }
|
||||
</span>
|
||||
<span className="font-bold">{t(props.group)}</span>
|
||||
</SumDiv>
|
||||
<Chevron />
|
||||
</Summary>
|
||||
<Ul>
|
||||
{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} Option={Option}/>)
|
||||
}
|
||||
{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} Option={Option} />
|
||||
)
|
||||
)}
|
||||
</Ul>
|
||||
</Details>
|
||||
</Li>
|
||||
)
|
||||
}
|
||||
|
||||
export default OptionGroup
|
|
@ -1,15 +0,0 @@
|
|||
import PctDegOption from 'shared/components/workbench/inputs/design-option-pct-deg'
|
||||
import CountOption from 'shared/components/workbench/inputs/design-option-count'
|
||||
import ListOption from 'shared/components/workbench/inputs/design-option-list'
|
||||
import Popout from 'shared/components/popout'
|
||||
|
||||
export const Tmp = props => <p>not yet</p>
|
||||
|
||||
export const inputs = {
|
||||
pct: PctDegOption,
|
||||
count: CountOption,
|
||||
deg: props => (<PctDegOption {...props} type='deg' />),
|
||||
list: ListOption,
|
||||
mm: () => <Popout fixme compact>Mm options are deprecated. Please report this</Popout>,
|
||||
constant: Tmp,
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { PctDegOption } from 'shared/components/workbench/inputs/design-option-pct-deg.mjs'
|
||||
import { CountOption } from 'shared/components/workbench/inputs/design-option-count.mjs'
|
||||
import { ListOption } from 'shared/components/workbench/inputs/design-option-list.mjs'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
||||
export const Tmp = (props) => <p>not yet</p>
|
||||
|
||||
export const inputs = {
|
||||
pct: PctDegOption,
|
||||
count: CountOption,
|
||||
deg: (props) => <PctDegOption {...props} type="deg" />,
|
||||
list: ListOption,
|
||||
mm: () => (
|
||||
<Popout fixme compact>
|
||||
Mm options are deprecated. Please report this
|
||||
</Popout>
|
||||
),
|
||||
constant: Tmp,
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { formatMm, formatPercentage} from 'shared/utils'
|
||||
|
||||
export const values = {
|
||||
pct: props => {
|
||||
const val = (typeof props.gist?.options?.[props.option] === 'undefined')
|
||||
? props.design.patternConfig.options[props.option].pct/100
|
||||
: props.gist.options[props.option]
|
||||
return (
|
||||
<span className={
|
||||
val=== props.design.patternConfig.options[props.option].pct/100
|
||||
? 'text-secondary-focus'
|
||||
: 'text-accent'
|
||||
}>
|
||||
{formatPercentage(val)}
|
||||
{props.design.patternConfig.options[props.option]?.toAbs && props.gist.measurements
|
||||
? ' | ' +formatMm(props.design.patternConfig.options[props.option]?.toAbs(val, props.gist))
|
||||
: null
|
||||
}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
bool: props => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const dflt = props.design.patternConfig.options[props.option].bool
|
||||
let current = props.gist?.options?.[props.option]
|
||||
current = current === undefined ? dflt : current;
|
||||
return (
|
||||
<span className={
|
||||
(dflt==current || typeof current === 'undefined')
|
||||
? 'text-secondary-focus'
|
||||
: 'text-accent'
|
||||
}>
|
||||
{current
|
||||
? t('yes')
|
||||
: t('no')
|
||||
}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
count: props => {
|
||||
const dflt = props.design.patternConfig.options[props.option].count
|
||||
const current = props.gist?.options?.[props.option]
|
||||
return (dflt==current || typeof current === 'undefined')
|
||||
? (<span className="text-secondary-focus">{dflt}</span>)
|
||||
: (<span className="text-accent">{current}</span>)
|
||||
},
|
||||
list: props => {
|
||||
const dflt = props.design.patternConfig.options[props.option].dflt
|
||||
const current = props.gist?.options?.[props.option]
|
||||
const prefix = `${props.option}.o.`
|
||||
const translate = props.design.patternConfig.options[props.option]?.doNotTranslate
|
||||
? (input) => input
|
||||
: (input) => props.t(prefix+input)
|
||||
return (dflt==current || typeof current === 'undefined')
|
||||
? (<span className="text-secondary-focus">{translate(dflt)}</span>)
|
||||
: (<span className="text-accent">{translate(current)}</span>)
|
||||
},
|
||||
deg: props => {
|
||||
const dflt = props.design.patternConfig.options[props.option].deg
|
||||
const current = props.gist?.options?.[props.option]
|
||||
return (dflt==current || typeof current === 'undefined')
|
||||
? (<span className="text-secondary-focus">{dflt}°</span>)
|
||||
: (<span className="text-accent">{current}°</span>)
|
||||
},
|
||||
mm: props => {
|
||||
return (<p>No mm val yet</p>)
|
||||
},
|
||||
constant: props => {
|
||||
return (<p>No constant val yet</p>)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { formatMm, formatPercentage } from 'shared/utils.mjs'
|
||||
|
||||
export const values = {
|
||||
pct: (props) => {
|
||||
const val =
|
||||
typeof props.gist?.options?.[props.option] === 'undefined'
|
||||
? props.design.patternConfig.options[props.option].pct / 100
|
||||
: props.gist.options[props.option]
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
val === props.design.patternConfig.options[props.option].pct / 100
|
||||
? 'text-secondary-focus'
|
||||
: 'text-accent'
|
||||
}
|
||||
>
|
||||
{formatPercentage(val)}
|
||||
{props.design.patternConfig.options[props.option]?.toAbs && props.gist.measurements
|
||||
? ' | ' +
|
||||
formatMm(props.design.patternConfig.options[props.option]?.toAbs(val, props.gist))
|
||||
: null}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
bool: (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const dflt = props.design.patternConfig.options[props.option].bool
|
||||
let current = props.gist?.options?.[props.option]
|
||||
current = current === undefined ? dflt : current
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
dflt == current || typeof current === 'undefined' ? 'text-secondary-focus' : 'text-accent'
|
||||
}
|
||||
>
|
||||
{current ? t('yes') : t('no')}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
count: (props) => {
|
||||
const dflt = props.design.patternConfig.options[props.option].count
|
||||
const current = props.gist?.options?.[props.option]
|
||||
return dflt == current || typeof current === 'undefined' ? (
|
||||
<span className="text-secondary-focus">{dflt}</span>
|
||||
) : (
|
||||
<span className="text-accent">{current}</span>
|
||||
)
|
||||
},
|
||||
list: (props) => {
|
||||
const dflt = props.design.patternConfig.options[props.option].dflt
|
||||
const current = props.gist?.options?.[props.option]
|
||||
const prefix = `${props.option}.o.`
|
||||
const translate = props.design.patternConfig.options[props.option]?.doNotTranslate
|
||||
? (input) => input
|
||||
: (input) => props.t(prefix + input)
|
||||
return dflt == current || typeof current === 'undefined' ? (
|
||||
<span className="text-secondary-focus">{translate(dflt)}</span>
|
||||
) : (
|
||||
<span className="text-accent">{translate(current)}</span>
|
||||
)
|
||||
},
|
||||
deg: (props) => {
|
||||
const dflt = props.design.patternConfig.options[props.option].deg
|
||||
const current = props.gist?.options?.[props.option]
|
||||
return dflt == current || typeof current === 'undefined' ? (
|
||||
<span className="text-secondary-focus">{dflt}°</span>
|
||||
) : (
|
||||
<span className="text-accent">{current}°</span>
|
||||
)
|
||||
},
|
||||
mm: (props) => {
|
||||
return <p>No mm val yet</p>
|
||||
},
|
||||
constant: (props) => {
|
||||
return <p>No constant val yet</p>
|
||||
},
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
import { Chevron } from 'shared/components/navigation/primary'
|
||||
import { optionType } from 'shared/utils'
|
||||
import { Li, Details, Summary, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu'
|
||||
import { Chevron } from 'shared/components/navigation/primary.mjs'
|
||||
import { optionType } from 'shared/utils.mjs'
|
||||
import { Li, Details, Summary, SumButton, SumDiv, Deg } from 'shared/components/workbench/menu.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { values } from 'shared/components/workbench/menu/design-options/option-value'
|
||||
import { inputs } from 'shared/components/workbench/menu/design-options/option-input'
|
||||
import { values } from 'shared/components/workbench/menu/design-options/option-value.mjs'
|
||||
import { inputs } from 'shared/components/workbench/menu/design-options/option-input.mjs'
|
||||
|
||||
const Option = (props) => {
|
||||
export const Option = (props) => {
|
||||
const { t } = useTranslation([`o_${props.design.designConfig.data.name}`])
|
||||
const opt = props.design.patternConfig.options[props.option]
|
||||
const type = optionType(opt)
|
||||
|
@ -57,5 +57,3 @@ const Option = (props) => {
|
|||
</Li>
|
||||
)
|
||||
}
|
||||
|
||||
export default Option
|
|
@ -1,67 +1,82 @@
|
|||
import { linkClasses } from 'shared/components/navigation/primary.js'
|
||||
import ViewMenu from './view.js'
|
||||
import DesignOptions from './design-options'
|
||||
import CoreSettings from './core-settings'
|
||||
import Xray from './xray'
|
||||
import TestDesignOptions from './test-design-options'
|
||||
import { linkClasses } from 'shared/components/navigation/primary.mjs'
|
||||
import { ViewMenu } from './view.mjs'
|
||||
import { DesignOptions } from './design-options.mjs'
|
||||
import { CoreSettings } from './core-settings.mjs'
|
||||
import { Xray } from './xray'
|
||||
import { TestDesignOptions } from './test-design-options.mjs'
|
||||
|
||||
export const Ul = props => <ul className="pl-5 list-inside">{props.children}</ul>
|
||||
export const Li = props => (
|
||||
<li className="flex flex-row hover:border-r-2 hover:border-r-secondary">
|
||||
{props.children}
|
||||
</li>
|
||||
export const Ul = (props) => <ul className="pl-5 list-inside">{props.children}</ul>
|
||||
export const Li = (props) => (
|
||||
<li className="flex flex-row hover:border-r-2 hover:border-r-secondary">{props.children}</li>
|
||||
)
|
||||
export const Details = props => (
|
||||
export const Details = (props) => (
|
||||
<details className="grow" open={props.open || false}>
|
||||
{props.children}
|
||||
</details>
|
||||
)
|
||||
export const Deg = props => <span className="text-3xl inline-block p-0 leading-3 px-2 translate-y-3">°</span>
|
||||
export const NoSumDiv = props => (
|
||||
<div className={`
|
||||
export const Deg = (props) => (
|
||||
<span className="text-3xl inline-block p-0 leading-3 px-2 translate-y-3">°</span>
|
||||
)
|
||||
export const NoSumDiv = (props) => (
|
||||
<div
|
||||
className={`
|
||||
grow px-2 ml-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-resize
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
text-base-content sm:text-base-content
|
||||
`}>{props.children}</div>
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
export const SumDiv = props => (
|
||||
<div className={`
|
||||
export const SumDiv = (props) => (
|
||||
<div
|
||||
className={`
|
||||
grow pl-2 border-l-2
|
||||
${linkClasses}
|
||||
hover:cursor-resize
|
||||
hover:border-secondary
|
||||
sm:hover:border-secondary-focus
|
||||
text-base-content sm:text-base-content
|
||||
`}>{props.children}</div>
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
export const Summary = props => (
|
||||
<summary className={`
|
||||
export const Summary = (props) => (
|
||||
<summary
|
||||
className={`
|
||||
flex flex-row
|
||||
px-2
|
||||
text-base-content
|
||||
sm:text-base-content
|
||||
hover:cursor-row-resize
|
||||
items-center
|
||||
`}>{props.children}</summary>
|
||||
`}
|
||||
>
|
||||
{props.children}
|
||||
</summary>
|
||||
)
|
||||
export const TopSummary = props => (
|
||||
<summary className={`
|
||||
export const TopSummary = (props) => (
|
||||
<summary
|
||||
className={`
|
||||
flex flex-row gap-4 text-lg
|
||||
hover:cursor-row-resize
|
||||
p-2
|
||||
text-base-content
|
||||
sm:text-base-content
|
||||
items-center
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<span className="text-secondary-focus mr-4">{props.icon || null}</span>
|
||||
{props.children}
|
||||
</summary>
|
||||
)
|
||||
export const SumButton = props => (
|
||||
<button className={`
|
||||
export const SumButton = (props) => (
|
||||
<button
|
||||
className={`
|
||||
flex flex-row
|
||||
px-2
|
||||
w-full justify-between
|
||||
|
@ -71,18 +86,25 @@ export const SumButton = props => (
|
|||
hover:cursor-pointer
|
||||
items-center
|
||||
mr-4
|
||||
`} onClick={props.onClick}>{props.children}</button>
|
||||
`}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
)
|
||||
export const TopSumTitle = props => (
|
||||
export const TopSumTitle = (props) => (
|
||||
<span className={`grow ${linkClasses} hover:cursor-resize font-bold uppercase`}>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
export const SecText = props => props.raw
|
||||
? <span className="text-secondary-focus" dangerouslySetInnerHTML={{__html: props.raw}} />
|
||||
: <span className="text-secondary-focus">{props.children}</span>
|
||||
export const SecText = (props) =>
|
||||
props.raw ? (
|
||||
<span className="text-secondary-focus" dangerouslySetInnerHTML={{ __html: props.raw }} />
|
||||
) : (
|
||||
<span className="text-secondary-focus">{props.children}</span>
|
||||
)
|
||||
|
||||
const WorkbenchMenu = props => {
|
||||
export const WorkbenchMenu = (props) => {
|
||||
return (
|
||||
<nav className="grow mb-12">
|
||||
<ViewMenu {...props} />
|
||||
|
@ -97,5 +119,3 @@ const WorkbenchMenu = props => {
|
|||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkbenchMenu
|
|
@ -1,9 +1,9 @@
|
|||
import { MenuIcon } from 'shared/components/icons.mjs'
|
||||
import { linkClasses, Chevron } from 'shared/components/navigation/primary.js'
|
||||
import { linkClasses, Chevron } from 'shared/components/navigation/primary.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { defaultGist } from 'shared/hooks/useGist'
|
||||
import { defaultGist } from 'shared/hooks/useGist.mjs'
|
||||
|
||||
const View = (props) => {
|
||||
export const View = (props) => {
|
||||
const { t } = useTranslation(['app'])
|
||||
const entries = [
|
||||
{
|
||||
|
@ -126,5 +126,3 @@ const View = (props) => {
|
|||
</details>
|
||||
)
|
||||
}
|
||||
|
||||
export default View
|
|
@ -1,31 +0,0 @@
|
|||
import yaml from 'js-yaml'
|
||||
import axios from 'axios'
|
||||
|
||||
const preload = {
|
||||
github: async (id, design) => {
|
||||
let result
|
||||
try {
|
||||
result = await axios.get(`https://api.github.com/gists/${id}`)
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err)
|
||||
return [false, 'An unexpected error occured']
|
||||
}
|
||||
|
||||
if (result.data.files['pattern.yaml'].content) {
|
||||
let g = yaml.load(result.data.files['pattern.yaml'].content)
|
||||
|
||||
if (g.design !== undefined && g.design !== design.designConfig.data.name) return [
|
||||
false, `You tried loading a configuration for ${g.design} into a ${design.designConfig.data.name} development environment`
|
||||
]
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
// TODO notify people of these errors
|
||||
else return [false, 'This gist does not seem to be a valid pattern configuration']
|
||||
}
|
||||
}
|
||||
|
||||
export default preload
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import SvgWrapper from './draft/svg-wrapper'
|
||||
import Error from './draft/error.js'
|
||||
import { svgattrPlugin } from '@freesewing/plugin-svgattr'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { svgattrPlugin } from '@freesewing/plugin-svgattr'
|
||||
import { SvgWrapper } from './draft/svg-wrapper.mjs'
|
||||
import { Error } from './draft/error.mjs'
|
||||
|
||||
const LabSample = ({ gist, draft, updateGist, unsetGist, showInfo, app, feedback }) => {
|
||||
|
||||
export const LabSample = ({ gist, draft, updateGist, unsetGist, showInfo, app, feedback }) => {
|
||||
const { t } = useTranslation(['workbench'])
|
||||
let svg
|
||||
let title = ''
|
||||
|
@ -13,19 +12,18 @@ const LabSample = ({ gist, draft, updateGist, unsetGist, showInfo, app, feedback
|
|||
if (gist.sample) {
|
||||
try {
|
||||
draft.use(svgattrPlugin, {
|
||||
class: 'freesewing pattern max-h-screen'
|
||||
class: 'freesewing pattern max-h-screen',
|
||||
})
|
||||
draft = draft.sample()
|
||||
// Render as React
|
||||
patternProps = draft.getRenderProps()
|
||||
for (const logs of patternProps.logs) errors.push(...logs.error)
|
||||
}
|
||||
catch(err) {
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
if (gist.sample.type === 'option') {
|
||||
title = t('testThing', {
|
||||
thing: ' : ' + t('option') + ' : ' + gist.sample.option
|
||||
thing: ' : ' + t('option') + ' : ' + gist.sample.option,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -43,5 +41,3 @@ const LabSample = ({ gist, draft, updateGist, unsetGist, showInfo, app, feedback
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LabSample
|
|
@ -1,9 +0,0 @@
|
|||
import Yaml from 'shared/components/yaml.js'
|
||||
|
||||
const GistAsYaml = props => (
|
||||
<div className="max-w-screen-xl m-auto">
|
||||
<Yaml json={JSON.stringify(props.gist, null, 2)} />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default GistAsYaml
|
|
@ -1,24 +1,25 @@
|
|||
// Hooks
|
||||
import { useEffect, useState, useMemo } from 'react'
|
||||
import { useGist } from 'shared/hooks/useGist'
|
||||
import Layout from 'shared/components/layouts/default'
|
||||
import Menu from 'shared/components/workbench/menu/index.js'
|
||||
import DraftError from 'shared/components/workbench/draft/error.js'
|
||||
// Dependencies
|
||||
import { pluginTheme } from '@freesewing/plugin-theme'
|
||||
import preloaders from 'shared/components/workbench/preload.js'
|
||||
import Modal from 'shared/components/modal'
|
||||
|
||||
// Components
|
||||
import { DefaultLayout } from 'shared/components/workbench/layout/default.mjs'
|
||||
import { Menu } from 'shared/components/workbench/menu/index.mjs'
|
||||
import { DraftError } from 'shared/components/workbench/draft/error.mjs'
|
||||
import { Modal } from 'shared/components/modal.mjs'
|
||||
import { ErrorBoundary } from 'shared/components/error/error-boundary.mjs'
|
||||
// Views
|
||||
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/gist-as-json.js'
|
||||
import GistAsYaml from 'shared/components/workbench/yaml.js'
|
||||
import DraftLogs from 'shared/components/workbench/logs.js'
|
||||
import CutLayout from 'shared/components/workbench/layout/cut'
|
||||
import PrintingLayout from 'shared/components/workbench/layout/print'
|
||||
|
||||
import ErrorBoundary from 'shared/components/error/error-boundary'
|
||||
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/gist-as-json.js'
|
||||
import { GistAsYaml } from 'shared/components/workbench/yaml.js'
|
||||
import { DraftLogs } from 'shared/components/workbench/logs.js'
|
||||
import { CutLayout } from 'shared/components/workbench/layout/cut'
|
||||
import { PrintingLayout } from 'shared/components/workbench/layout/print'
|
||||
|
||||
const views = {
|
||||
measurements: Measurements,
|
||||
|
@ -54,7 +55,13 @@ const doPreload = async (preload, from, design, gist, setGist, setPreloaded) =>
|
|||
* keeping the gist state, which will trickle down
|
||||
* to all workbench subcomponents
|
||||
*/
|
||||
const WorkbenchWrapper = ({ app, design, preload = false, from = false, layout = false }) => {
|
||||
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,
|
||||
|
@ -175,5 +182,3 @@ const WorkbenchWrapper = ({ app, design, preload = false, from = false, layout =
|
|||
</LayoutComponent>
|
||||
)
|
||||
}
|
||||
|
||||
export default WorkbenchWrapper
|
|
@ -1,88 +0,0 @@
|
|||
import useLocalStorage from './useLocalStorage';
|
||||
import set from 'lodash.set'
|
||||
import unset from 'lodash.unset'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import defaultSettings from 'shared/components/workbench/default-settings.js'
|
||||
import {useState} from 'react'
|
||||
|
||||
// Generates a default design gist to start from
|
||||
export const defaultGist = (design, locale='en') => {
|
||||
const gist = {
|
||||
design,
|
||||
...defaultSettings,
|
||||
_state: {view: 'draft'}
|
||||
}
|
||||
if (locale) gist.locale = locale
|
||||
|
||||
return gist
|
||||
}
|
||||
|
||||
// generate the gist state and its handlers
|
||||
export function useGist(design, app) {
|
||||
// get the localstorage state and setter
|
||||
const [gist, _setGist, gistReady] = useLocalStorage(`${design}_gist`, defaultGist(design, app.locale));
|
||||
const [gistHistory, setGistHistory] = useState([]);
|
||||
const [gistFuture, setGistFuture] = useState([]);
|
||||
|
||||
const setGist = (newGist, addToHistory=true) => {
|
||||
let oldGist
|
||||
_setGist((gistState) => {
|
||||
// have to clone it or nested objects will be referenced instead of copied, which defeats the purpose
|
||||
if (addToHistory) oldGist = cloneDeep(gistState)
|
||||
|
||||
return typeof newGist === 'function' ? newGist(cloneDeep(gistState)) : newGist
|
||||
})
|
||||
|
||||
if (addToHistory) {
|
||||
setGistHistory((history) => {
|
||||
return [...history, oldGist]
|
||||
})
|
||||
setGistFuture([])
|
||||
}
|
||||
}
|
||||
|
||||
/** update a single gist value */
|
||||
const updateGist = (path, value, addToHistory=true) => {
|
||||
setGist((gistState) => {
|
||||
const newGist = {...gistState};
|
||||
set(newGist, path, value);
|
||||
return newGist;
|
||||
}, addToHistory)
|
||||
}
|
||||
|
||||
/** unset a single gist value */
|
||||
const unsetGist = (path, addToHistory=true) => {
|
||||
setGist((gistState) => {
|
||||
const newGist = {...gistState};
|
||||
unset(newGist, path);
|
||||
return newGist;
|
||||
}, addToHistory)
|
||||
}
|
||||
|
||||
const undoGist = () => {
|
||||
_setGist((gistState) => {
|
||||
let prevGist;
|
||||
setGistHistory((history) => {
|
||||
const newHistory = [...history]
|
||||
prevGist = newHistory.pop() || defaultGist(design, app.locale);
|
||||
return newHistory;
|
||||
})
|
||||
setGistFuture((future) => [gistState, ...future]);
|
||||
|
||||
return {...prevGist}
|
||||
})
|
||||
}
|
||||
|
||||
const redoGist = () => {
|
||||
const newHistory = [...gistHistory, gist]
|
||||
const newFuture = [...gistFuture]
|
||||
const newGist = newFuture.shift()
|
||||
setGistHistory(newHistory)
|
||||
setGistFuture(newFuture)
|
||||
_setGist(newGist)
|
||||
}
|
||||
|
||||
const resetGist = () => setGist(defaultGist(design, app.locale))
|
||||
|
||||
return {gist, setGist, unsetGist, gistReady, updateGist, undoGist, redoGist, resetGist};
|
||||
}
|
91
sites/shared/hooks/useGist.mjs
Normal file
91
sites/shared/hooks/useGist.mjs
Normal file
|
@ -0,0 +1,91 @@
|
|||
import { useState } from 'react'
|
||||
import set from 'lodash.set'
|
||||
import unset from 'lodash.unset'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import { useLocalStorage } from './useLocalStorage'
|
||||
import { defaultSettings } from 'shared/components/workbench/default-settings.mjs'
|
||||
|
||||
// Generates a default design gist to start from
|
||||
export const defaultGist = (design, locale = 'en') => {
|
||||
const gist = {
|
||||
design,
|
||||
...defaultSettings,
|
||||
_state: { view: 'draft' },
|
||||
}
|
||||
if (locale) gist.locale = locale
|
||||
|
||||
return gist
|
||||
}
|
||||
|
||||
// generate the gist state and its handlers
|
||||
export function useGist(design, app) {
|
||||
// get the localstorage state and setter
|
||||
const [gist, _setGist, gistReady] = useLocalStorage(
|
||||
`${design}_gist`,
|
||||
defaultGist(design, app.locale)
|
||||
)
|
||||
const [gistHistory, setGistHistory] = useState([])
|
||||
const [gistFuture, setGistFuture] = useState([])
|
||||
|
||||
const setGist = (newGist, addToHistory = true) => {
|
||||
let oldGist
|
||||
_setGist((gistState) => {
|
||||
// have to clone it or nested objects will be referenced instead of copied, which defeats the purpose
|
||||
if (addToHistory) oldGist = cloneDeep(gistState)
|
||||
|
||||
return typeof newGist === 'function' ? newGist(cloneDeep(gistState)) : newGist
|
||||
})
|
||||
|
||||
if (addToHistory) {
|
||||
setGistHistory((history) => {
|
||||
return [...history, oldGist]
|
||||
})
|
||||
setGistFuture([])
|
||||
}
|
||||
}
|
||||
|
||||
/** update a single gist value */
|
||||
const updateGist = (path, value, addToHistory = true) => {
|
||||
setGist((gistState) => {
|
||||
const newGist = { ...gistState }
|
||||
set(newGist, path, value)
|
||||
return newGist
|
||||
}, addToHistory)
|
||||
}
|
||||
|
||||
/** unset a single gist value */
|
||||
const unsetGist = (path, addToHistory = true) => {
|
||||
setGist((gistState) => {
|
||||
const newGist = { ...gistState }
|
||||
unset(newGist, path)
|
||||
return newGist
|
||||
}, addToHistory)
|
||||
}
|
||||
|
||||
const undoGist = () => {
|
||||
_setGist((gistState) => {
|
||||
let prevGist
|
||||
setGistHistory((history) => {
|
||||
const newHistory = [...history]
|
||||
prevGist = newHistory.pop() || defaultGist(design, app.locale)
|
||||
return newHistory
|
||||
})
|
||||
setGistFuture((future) => [gistState, ...future])
|
||||
|
||||
return { ...prevGist }
|
||||
})
|
||||
}
|
||||
|
||||
const redoGist = () => {
|
||||
const newHistory = [...gistHistory, gist]
|
||||
const newFuture = [...gistFuture]
|
||||
const newGist = newFuture.shift()
|
||||
setGistHistory(newHistory)
|
||||
setGistFuture(newFuture)
|
||||
_setGist(newGist)
|
||||
}
|
||||
|
||||
const resetGist = () => setGist(defaultGist(design, app.locale))
|
||||
|
||||
return { gist, setGist, unsetGist, gistReady, updateGist, undoGist, redoGist, resetGist }
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
import { useState, useEffect, useRef, useReducer } from 'react'
|
||||
|
||||
// See: https://usehooks.com/useLocalStorage/
|
||||
function useLocalStorage(key, initialValue, reducer) {
|
||||
export function useLocalStorage(key, initialValue, reducer) {
|
||||
const prefix = 'fs_'
|
||||
const [storedValue, setStoredValue] = typeof reducer == 'function' ? useReducer(reducer, initialValue) : useState(initialValue);
|
||||
const [storedValue, setStoredValue] =
|
||||
typeof reducer == 'function' ? useReducer(reducer, initialValue) : useState(initialValue)
|
||||
// use this to track whether it's mounted. useful for doing other effects outside this hook
|
||||
const [ready, setReady] = useState(false);
|
||||
const readyInternal = useRef(false);
|
||||
const [ready, setReady] = useState(false)
|
||||
const readyInternal = useRef(false)
|
||||
const setValue = setStoredValue
|
||||
|
||||
// set to localstorage every time the storedValue changes
|
||||
|
@ -18,9 +19,9 @@ function useLocalStorage(key, initialValue, reducer) {
|
|||
|
||||
// get the item from localstorage after the component has mounted. empty brackets mean it runs one time
|
||||
useEffect(() => {
|
||||
readyInternal.current = true;
|
||||
readyInternal.current = true
|
||||
const item = window.localStorage.getItem(prefix + key)
|
||||
let valueToSet = storedValue;
|
||||
let valueToSet = storedValue
|
||||
if (item) {
|
||||
valueToSet = JSON.parse(item)
|
||||
}
|
||||
|
@ -30,10 +31,8 @@ function useLocalStorage(key, initialValue, reducer) {
|
|||
}
|
||||
|
||||
setValue(valueToSet)
|
||||
setReady(true);
|
||||
setReady(true)
|
||||
}, [])
|
||||
|
||||
return [storedValue, setValue, ready]
|
||||
}
|
||||
|
||||
export default useLocalStorage
|
|
@ -1,22 +0,0 @@
|
|||
import useLocalStorage from 'shared/hooks/useLocalStorage';
|
||||
import {useEffect } from 'react'
|
||||
|
||||
function useTheme() {
|
||||
// make a local storage item for the theme
|
||||
const [storedTheme, setStoredTheme, ready] = useLocalStorage('theme', undefined);
|
||||
|
||||
useEffect(() => {
|
||||
// set the default theme based on user prefence after mounting
|
||||
if (ready && storedTheme === undefined) {
|
||||
const prefersDarkMode = (typeof window !== 'undefined' && typeof window.matchMedia === 'function')
|
||||
? window.matchMedia(`(prefers-color-scheme: dark`).matches
|
||||
: undefined
|
||||
|
||||
setStoredTheme(prefersDarkMode ? 'dark' : 'light')
|
||||
}
|
||||
}, [ready])
|
||||
|
||||
return [storedTheme, setStoredTheme];
|
||||
}
|
||||
|
||||
export default useTheme;
|
21
sites/shared/hooks/useTheme.mjs
Normal file
21
sites/shared/hooks/useTheme.mjs
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { useLocalStorage } from 'shared/hooks/useLocalStorage'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export function useTheme() {
|
||||
// make a local storage item for the theme
|
||||
const [storedTheme, setStoredTheme, ready] = useLocalStorage('theme', undefined)
|
||||
|
||||
useEffect(() => {
|
||||
// set the default theme based on user prefence after mounting
|
||||
if (ready && storedTheme === undefined) {
|
||||
const prefersDarkMode =
|
||||
typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
||||
? window.matchMedia(`(prefers-color-scheme: dark`).matches
|
||||
: undefined
|
||||
|
||||
setStoredTheme(prefersDarkMode ? 'dark' : 'light')
|
||||
}
|
||||
}, [ready])
|
||||
|
||||
return [storedTheme, setStoredTheme]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue