1
0
Fork 0

Merge pull request 'feat: Docs for @freesewing/react, the new editor, and other improvements' (#348) from cdocs into develop

Reviewed-on: https://codeberg.org/freesewing/freesewing/pulls/348
This commit is contained in:
Joost De Cock 2025-06-01 17:45:13 +02:00
commit 94d3f4c09f
457 changed files with 17818 additions and 8053 deletions

View file

@ -83,6 +83,7 @@ packageJson:
"./components/Echart": "./components/Echart/index.mjs"
"./components/Editor": "./components/Editor/index.mjs"
"./components/Heading": "./components/Heading/index.mjs"
"./components/Help": "./components/Help/index.mjs"
"./components/Highlight": "./components/Highlight/index.mjs"
"./components/Icon": "./components/Icon/index.mjs"
"./components/Input": "./components/Input/index.mjs"
@ -124,6 +125,7 @@ packageJson:
"./hooks/useControl": "./hooks/useControl/index.mjs"
"./hooks/useDesign": "./hooks/useDesign/index.mjs"
"./hooks/useDesignTranslation": "./hooks/useDesignTranslation/index.mjs"
"./hooks/useFilter": "./hooks/useFilter/index.mjs"
"./hooks/useSelection": "./hooks/useSelection/index.mjs"
"./hooks/useStateObject": "./hooks/useStateObject/index.mjs"
# Lib

24
eslint.config.mjs Normal file
View file

@ -0,0 +1,24 @@
import js from '@eslint/js'
import globals from 'globals'
import pluginReact from 'eslint-plugin-react'
import json from '@eslint/json'
import markdown from '@eslint/markdown'
import css from '@eslint/css'
import { defineConfig } from 'eslint/config'
export default defineConfig([
{ files: ['**/*.{js,mjs,cjs,jsx}'], plugins: { js }, extends: ['js/recommended'] },
{
files: ['**/*.{js,mjs,cjs,jsx}'],
languageOptions: { globals: { ...globals.browser, ...globals.node } },
},
pluginReact.configs.flat.recommended,
{ files: ['**/*.json'], plugins: { json }, language: 'json/json', extends: ['json/recommended'] },
{
files: ['**/*.md'],
plugins: { markdown },
language: 'markdown/commonmark',
extends: ['markdown/recommended'],
},
{ files: ['**/*.css'], plugins: { css }, language: 'css/css', extends: ['css/recommended'] },
])

4277
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -61,17 +61,23 @@
"devDependencies": {
"@commitlint/cli": "^19.0.3",
"@commitlint/config-conventional": "^19.0.3",
"@eslint/css": "^0.8.1",
"@eslint/js": "^9.27.0",
"@eslint/json": "^0.12.0",
"@eslint/markdown": "^6.4.0",
"@nx/eslint": "20.2.1",
"all-contributors-cli": "^6.26.1",
"axios": "^1.5.1",
"chalk": "^4.1.0",
"cross-env": "^7.0.2",
"eslint": "^8.23.1",
"eslint": "^9.27.0",
"eslint-plugin-jsonc": "^2.4.0",
"eslint-plugin-markdown": "^5.0.0",
"eslint-plugin-mongo": "^1.0.5",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-yaml": "^0.5.0",
"execa": "^9.3.1",
"globals": "^16.2.0",
"husky": "^9.0.10",
"js-yaml": "^4.0.0",
"lerna": "^8.0.0",
@ -97,7 +103,6 @@
"@babel/plugin-syntax-import-assertions": "^7.22.5",
"@babel/preset-react": "^7.22.15",
"c8": "^10.1.2",
"eslint-config-next": "^14.0.1",
"glob": "^11.0.1",
"rehype-format": "^5.0.0",
"rehype-stringify": "^10.0.1",

View file

@ -1,58 +1,45 @@
// Dependencies
import { DateTime } from 'luxon'
import orderBy from 'lodash/orderBy.js'
import { capitalize, shortDate } from '@freesewing/utils'
import { shortDate } from '@freesewing/utils'
import { apikeyLevels } from '@freesewing/config'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useEffect, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import { useSelection } from '@freesewing/react/hooks/useSelection'
// Components
import { TableWrapper } from '@freesewing/react/components/Table'
import { Link as WebLink } from '@freesewing/react/components/Link'
import {
BoolNoIcon,
BoolYesIcon,
PlusIcon,
RightIcon,
TrashIcon,
} from '@freesewing/react/components/Icon'
import { PlusIcon, RightIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { Uuid } from '@freesewing/react/components/Uuid'
import { Popout } from '@freesewing/react/components/Popout'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { NumberCircle } from '@freesewing/react/components/Number'
import { StringInput, FormControl, ListInput } from '@freesewing/react/components/Input'
import { DisplayRow } from './shared.mjs'
import { CopyToClipboardButton } from '@freesewing/react/components/CopyToClipboardButton'
import { StringInput, Fieldset, ListInput } from '@freesewing/react/components/Input'
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
import { TimeAgo, TimeToGo } from '@freesewing/react/components/Time'
import { KeyVal } from '@freesewing/react/components/KeyVal'
const t = (input) => {
console.log('t called', input)
return input
}
const fields = {
id: 'Key',
name: 'Name',
calls: 'Calls',
level: 'Level',
level: 'Level',
createdAt: 'Created',
expiresAt: 'Expires',
}
/*
* Component for the account/apikeys page
/**
* A component to mange the user's API keys
*
* @params {object} props - All React props
* @params {function} Link - A framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @param {JSX.Element} [props.Link] - An optional framework-specific Link component
* @returns {JSX.Element}
*/
export const Apikeys = ({ Link = false }) => {
if (!Link) Link = WebLink
@ -240,13 +227,6 @@ const NewApikey = ({ onCreate = false }) => {
} else setLoadingStatus([true, 'An error occured. Please report this', true, false])
}
const clear = () => {
setApikey(false)
setGenerate(false)
setName('')
setLevel(1)
}
return (
<div className="tw:w-full">
<h2>New API key {apikey ? `: ${apikey.name}` : ''}</h2>
@ -311,7 +291,7 @@ const ShowNewApikey = ({ apikey }) => (
<CopyToClipboardButton sup content={apikey.secret} label="API key secret" />
</h6>
<pre>{apikey.secret}</pre>
<Popout warning compact>
<Popout type="warning" compact>
This is the only time you can see the key secret, make sure to copy it.
</Popout>
</div>
@ -331,7 +311,7 @@ const ExpiryPicker = ({ expires, setExpires }) => {
return (
<div className="tw:flex tw:flex-row tw:gap-2 tw:items-center">
<FormControl
<Fieldset
label="Key Expiry"
labelBL={shortDate(expires)}
labelBR={<TimeToGo iso={expires} />}
@ -344,7 +324,7 @@ const ExpiryPicker = ({ expires, setExpires }) => {
className="tw:daisy-range tw:daisy-range-secondary tw:w-full"
onChange={update}
/>
</FormControl>
</Fieldset>
</div>
)
}

View file

@ -17,12 +17,14 @@ import { PassiveImageInput } from '@freesewing/react/components/Input'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
/*
* Component for the account/bio page
/**
* Component to manage the user's Avatar
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @params {function} props.Link - A framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @param {boolean} [props.welcome = false] - Set to true to render the welcome/onboarding flow
* @param {function} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Avatar = ({ welcome = false, Link = false }) => {
if (!Link) Link = WebLink
@ -98,7 +100,7 @@ export const Avatar = ({ welcome = false, Link = false }) => {
<>
<p className="tw:text-right">
<button
className="tw:daisy-btn tw:daisy-btn-primary tw:w-full tw:lg:w-auto mt-8"
className="tw:daisy-btn tw:daisy-btn-primary tw:w-full tw:lg:w-auto tw:mt-4"
onClick={save}
>
<SaveIcon /> Save Avatar

View file

@ -16,12 +16,14 @@ import { MarkdownInput } from '@freesewing/react/components/Input'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
/*
* Component for the account/bio page
/**
* Component to manage the user's Bio
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @params {function} props.Link - A framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @param {boolean} [props.welcome = false] - Set to true to render the welcome/onboarding flow
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Bio = ({ welcome = false, Link = false }) => {
if (!Link) Link = WebLink

View file

@ -7,7 +7,7 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Components
import { BookmarkIcon, LeftIcon, PlusIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { BookmarkIcon, PlusIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { Link as WebLink } from '@freesewing/react/components/Link'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { StringInput } from '@freesewing/react/components/Input'
@ -25,12 +25,15 @@ const types = {
}
/**
* Component for the account/bookmarks page
* A component to manage the user's bookmarks
*
* @component
* @returns {JSX.Element}
*/
export const Bookmarks = () => {
// Hooks & Context
const backend = useBackend()
const { setModal, clearModal } = useContext(ModalContext)
const { setModal } = useContext(ModalContext)
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
// State
@ -178,7 +181,7 @@ export const Bookmarks = () => {
* @param {object} props - All the React props
* @param {function} onCreated - An optional method to call when the bookmark is created
*/
export const NewBookmark = ({ onCreated = false }) => {
const NewBookmark = ({ onCreated = false }) => {
// Hooks
const { setLoadingStatus } = useContext(LoadingStatusContext)
const { clearModal } = useContext(ModalContext)
@ -191,7 +194,7 @@ export const NewBookmark = ({ onCreated = false }) => {
// This method will create the bookmark
const createBookmark = async () => {
setLoadingStatus([true, 'Processing update'])
const [status, body] = await backend.createBookmark({
const [status] = await backend.createBookmark({
title,
url,
type: 'custom',
@ -242,13 +245,15 @@ export const NewBookmark = ({ onCreated = false }) => {
)
}
/*
* A component to add a bookmark from wherever
/**
* Component to add a bookmark to the user's account
*
* @params {object} props - All React props
* @params {string} props.href - The bookmark href
* @params {string} props.title - The bookmark title
* @params {string} props.type - The bookmark type
* @component
* @param {object} props - All component props
* @param {string} props.slug - The bookmark slug/href
* @param {string} props.title - The bookmark title
* @param {string} props.type - The bookmark type, one of design, pattern, set, cset, doc, or custom
* @returns {JSX.Element}
*/
export const BookmarkButton = ({ slug, type, title }) => {
const { setModal } = useContext(ModalContext)
@ -279,19 +284,20 @@ export const BookmarkButton = ({ slug, type, title }) => {
/*
* A component to create a bookmark, preloaded with props
*
* @params {object} props - All React props
* @params {string} props.href - The bookmark href
* @params {string} props.title - The bookmark title
* @params {string} props.type - The bookmark type
*
* @component
* @param {object} props - All component props
* @param {string} props.href - The bookmark href
* @param {string} props.title - The bookmark title
* @param {string} props.type - The bookmark type
* @returns {JSX.Element}
*/
export const CreateBookmark = ({ type, title, slug }) => {
const CreateBookmark = ({ type, title, slug }) => {
const backend = useBackend()
const [name, setName] = useState(title)
const { setLoadingStatus } = useContext(LoadingStatusContext)
const { setModal } = useContext(ModalContext)
const url = `/${slug}`
const url = slug.toLowerCase().slice(0, 4) === 'http' ? slug : `/${slug}`
const bookmark = async (evt) => {
evt.stopPropagation()

View file

@ -1,17 +1,13 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
import { NoIcon, OkIcon, RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
@ -31,11 +27,12 @@ const strings = {
},
}
/*
* Component for the account/preferences/compare page
/**
* A component to manage the user's compare setting
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @component
* @params {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
* @returns {JSX.Element}
*/
export const Compare = ({ welcome = false }) => {
// Hooks

View file

@ -1,42 +1,23 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { linkClasses, navigate } from '@freesewing/utils'
import { navigate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
const strings = {
yes: {
title: 'Yes, in case it may help me',
desc:
'Allowing us to compare your measurments to a baseline or others measurements sets ' +
'allows us to detect potential problems in your measurements or patterns.',
},
no: {
title: 'No, never compare',
desc:
'We get it, comparison is the thief of joy. Just be aware that this limits our ability ' +
'to warn you about potential problems in your measurements sets or patterns.',
},
}
/*
* Component for the account/preferences/consent page
/**
* A component to manage the user's consent setting
*
* @params {object} props - All React props
* @params {bool} props.signUp - Set to true to use this component on the initial signUp
* @param {function} props.Link - An optional framework-specific Link component
* @component
* @param {object} props - All component props
* @param {bool} [props.signUp = false] - Set to true to use this component on the initial signUp
* @param {function} [props.Link = false] - An optional framework-specific Link component
* @returns {JSX.Element}
*/
export const Consent = ({ signUp = false, Link = false, title = false }) => {
if (!Link) Link = WebLink
@ -77,7 +58,7 @@ export const Consent = ({ signUp = false, Link = false, title = false }) => {
// Helper method to remove the account
const removeAccount = async () => {
setLoadingStatus([true, 'One moment please'])
const [status, body] = await backend.removeAccount()
const [status] = await backend.removeAccount()
if (status === 200) {
setLoadingStatus([true, 'All done, farewell', true, true])
setToken(null)

View file

@ -1,48 +1,27 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { controlDesc } from '@freesewing/config'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import React from 'react'
import { useControl } from '@freesewing/react/hooks/useControl'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { RightIcon, NoIcon, OkIcon, SaveIcon } from '@freesewing/react/components/Icon'
import { RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { ControlScore } from '@freesewing/react/components/Control'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
const strings = {
1: {
title: 'Keep it as simple as possible',
desc:
'Allowing us to compare your measurments to a baseline or others measurements sets ' +
'allows us to detect potential problems in your measurements or patterns.',
},
2: {
title: 'No, never compare',
desc:
'We get it, comparison is the thief of joy. Just be aware that this limits our ability ' +
'to warn you about potential problems in your measurements sets or patterns.',
},
}
/*
* Component for the account/preferences/control page
/**
* A component to manage the user's control/UX setting
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @component
* @param {object} props - All component props
* @param {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
* @returns {JSX.Element}
*/
export const Control = ({ welcome = false }) => {
// Hooks
const { control, updateControl } = useControl()
const { control, setControl } = useControl()
// Helper to get the link to the next onboarding step
const nextHref = welcome
@ -67,7 +46,7 @@ export const Control = ({ welcome = false }) => {
desc: controlDesc[val].desc,
}))}
current={control}
update={updateControl}
update={setControl}
/>
{welcome ? (
<>

View file

@ -1,15 +1,11 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { validateEmail, validateTld, getSearchParam } from '@freesewing/utils'
import { validateEmail, validateTld, getSearchParam, navigate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext, useEffect } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SaveIcon } from '@freesewing/react/components/Icon'
@ -17,14 +13,15 @@ import { EmailInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { Spinner } from '@freesewing/react/components/Spinner'
/*
* Component for the account/bio page
/**
* A component to manage the user's email address
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @params {function} props.Link - A framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Email = ({ welcome = false, Link = false }) => {
export const Email = ({ Link = false }) => {
if (!Link) Link = WebLink
// Hooks
@ -53,7 +50,7 @@ export const Email = ({ welcome = false, Link = false }) => {
return (
<div className="tw:w-full">
{changed ? (
<Popout note>
<Popout type="note">
<h3>Please confirm this change</h3>
<p>
We have sent an E-mail to your new address to confirm this change. Please click the link
@ -87,6 +84,14 @@ export const Email = ({ welcome = false, Link = false }) => {
)
}
/**
* A component to render the confirmation after changing the user's email
*
* @component
* @param {object} props - All component props
* @param {function} [props.onSuccess = false] - A method to call after the email is changed
* @returns {JSX.Element}
*/
export const EmailChangeConfirmation = ({ onSuccess = false }) => {
// State
const [error, setError] = useState(false)
@ -94,7 +99,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
const [check, setCheck] = useState()
// Hooks
const { setAccount, setToken } = useAccount()
const { setAccount } = useAccount()
const backend = useBackend()
// Context
@ -124,7 +129,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
})
// If it works, store account, which runs the onSuccess handler
if (body.result === 'success' && body.account) return storeAccount(body)
if (status === 200 && body.result === 'success' && body.account) return storeAccount(body)
// If we get here, we're not sure what's wrong
if (body.error) return setError(body.error)
return setError(true)
@ -150,7 +155,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
if (!id || !check)
return (
<>
<h1>One moment pleae</h1>
<h1>One moment please</h1>
<Spinner className="tw:w-8 tw:h-8 tw:m-auto tw:animate-spin" />
</>
)

View file

@ -1,19 +1,19 @@
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { DownloadIcon } from '@freesewing/react/components/Icon'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
/*
* Component for the account/actions/export page
/**
* A component to manage the user's export account data action
*
* @component
* @returns {JSX.Element}
*/
export const Export = () => {
// Hooks
@ -36,7 +36,7 @@ export const Export = () => {
return (
<div className="tw:max-w-xl">
{link ? (
<Popout link>
<Popout type="link">
<h5>Your data was exported and is available for download at the following location:</h5>
<p className="tw:text-lg">
<WebLink href={link}>{link}</WebLink>

View file

@ -10,8 +10,11 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
import { SaveIcon } from '@freesewing/react/components/Icon'
import { StringInput } from '@freesewing/react/components/Input'
/*
* Component for the account/social/github page
/**
* A component to manage the user's Github handle in their account data
*
* @component
* @returns {JSX.Element}
*/
export const Github = () => {
// Hooks

View file

@ -1,22 +1,16 @@
// Hooks
import React, { useState } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
/*
* Component to display a user ID
/**
* A component to display the current user's ID
*
* @params {object} props - All React props
* @params {function} props.Link - A framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @returns {JSX.Element}
*/
export const UserId = ({ Link = false }) => {
if (!Link) Link = WebLink
export const UserId = () => {
// Hooks
const { account } = useAccount()
const [id, setId] = useState(account.id)
return id || null
return account.id || null
}

View file

@ -1,24 +1,21 @@
// Dependencies
import yaml from 'js-yaml'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SaveIcon } from '@freesewing/react/components/Icon'
import { FileInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { Yaml } from '@freesewing/react/components/Yaml'
/*
* Component for the account/bio page
/**
* A component to manage the importing of a measurements set into a user's account data
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @params {function} props.Link - A framework specific Link component for client-side routing
* @component
* @returns {JSX.Element}
*/
export const ImportSet = () => {
// Hooks
@ -42,7 +39,7 @@ export const ImportSet = () => {
if (set.measurements || set.measies) {
const name = set.name || 'J. Doe'
setLoadingStatus([true, `Importing ${name}`])
const [status, body] = await backend.createSet({
const [status] = await backend.createSet({
name: set.name || 'J. Doe',
units: set.units || 'metric',
notes: set.notes || '',
@ -77,7 +74,7 @@ export const ImportSet = () => {
multiple: false,
}}
/>
<Popout tip>
<Popout type="tip">
<p>
To import a measurement set, you should have a JSON or YAML file that has the following
structure:

View file

@ -1,64 +0,0 @@
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { SaveIcon } from '@freesewing/react/components/Icon'
import { StringInput } from '@freesewing/react/components/Input'
/*
* Component for the account/social/github page
*/
export const Instagram = () => {
// Hooks
const { account, setAccount } = useAccount()
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
// State
const [githubUsername, setGithubUsername] = useState(account.data.githubUsername || '')
const [githubEmail, setGithubEmail] = useState(account.data.githubEmail || '')
// Helper method to save changes
const save = async () => {
setLoadingStatus([true, 'Saving bio'])
const [status, body] = await backend.updateAccount({ data: { githubUsername, githubEmail } })
if (status === 200 && body.result === 'success') {
setAccount(body.account)
setLoadingStatus([true, 'GitHub info updated', true, true])
} else setLoadingStatus([true, 'Something went wrong. Please report this', true, true])
}
return (
<div className="tw:w-full">
<StringInput
id="account-github-email"
label="GitHub Email Address"
current={githubEmail}
update={setGithubEmail}
valid={(val) => val.length > 0}
placeholder={'joost@joost.at'}
/>
<StringInput
id="account-github-username"
label="GitHub Username"
current={githubUsername}
update={setGithubUsername}
valid={(val) => val.length > 0}
placeholder={'joostdecock'}
/>
<p className="tw:text-right">
<button
className="tw:daisy-btn tw:daisy-btn-primary tw:w-full tw:lg:w-auto tw:mt-8"
onClick={save}
>
<SaveIcon /> Save
</button>
</p>
</div>
)
}

View file

@ -6,7 +6,7 @@ import React, { useState, useEffect } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as DefautLink } from '@freesewing/react/components/Link'
import { Link as DefaultLink } from '@freesewing/react/components/Link'
import { ControlScore } from '@freesewing/react/components/Control'
import {
MeasurementsSetIcon,
@ -93,13 +93,13 @@ const titles = {
const YesNo = ({ check }) => (check ? <BoolYesIcon /> : <BoolNoIcon />)
const t = (input) => input
/**
* The Links component shows all of the links to manage your account
* A component to manage the user's Instagram handle in their account data
*
* @param {object} props - All the React props
* @param {function} Link - A custom Link component, typically the Docusaurus one, but it's optional
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Links = ({ Link = false }) => {
// Use custom Link component if available

View file

@ -1,30 +1,26 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { horFlexClasses } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, LockIcon } from '@freesewing/react/components/Icon'
import { PasswordInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { NumberCircle } from '@freesewing/react/components/Number'
import { CopyToClipboardButton } from '@freesewing/react/components/CopyToClipboardButton'
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
/*
* Component for the account/security/password page
/**
* A component to manage the user's MFA settings
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @component
* @param {object} props - All component props
* @param {string} [props.title = false] - Whether or not to display a title
* @returns {JSX.Element}
*/
export const Mfa = ({ welcome = false, title = true }) => {
export const Mfa = ({ title = true }) => {
// Hooks
const backend = useBackend()
const { account, setAccount } = useAccount()
@ -199,7 +195,7 @@ export const Mfa = ({ welcome = false, title = true }) => {
<LockIcon />
Set up Mult-Factor Authentication
</button>
<Popout tip>
<Popout type="tip">
<h5>Please consider enabling Two-Factor Authentication</h5>
<p>
We do not enforce a password policy, but we do recommend you enable Multi-Factor

View file

@ -1,29 +1,28 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { linkClasses } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
import { NoIcon, OkIcon, RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
/*
* Component for the account/preferences/newsletter page
/**
* A component to manage the user's newsletter subscription in their account data
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @param {function} props.Link - An optional framework-specific Link component
* @component
* @params {object} props - All Component props
* @param {bool} props.welcome - Set to true to use this component on the welcome page
* @param {React.Component} props.Link - An optional framework-specific Link component
* @returns {JSX.Element}
*/
export const Newsletter = ({ welcome = false, Link = false }) => {
if (!Link) Link = WebLink
@ -109,7 +108,7 @@ export const Newsletter = ({ welcome = false, Link = false }) => {
</>
) : null}
{welcome ? null : (
<Popout tip>
<Popout type="tip">
<h5>You can unsubscribe at any time with the link below</h5>
<p>
This unsubscribe link will also be included at the bottom of every newsletter we send

View file

@ -1,28 +1,25 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { horFlexClasses } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { RightIcon, SaveIcon } from '@freesewing/react/components/Icon'
import { PasswordInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
/*
* Component for the account/security/password page
/**
* A component to manage the user's password
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @component
* @param {object} props - All component props
* @param {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Password = ({ welcome = false, Link = false }) => {
export const Password = ({ Link = false }) => {
if (!Link) Link = WebLink
// Hooks
const backend = useBackend()
@ -60,7 +57,7 @@ export const Password = ({ welcome = false, Link = false }) => {
<SaveIcon /> Save
</button>
{!account.mfaEnabled && (
<Popout tip>
<Popout type="tip">
<h5>Please consider enabling Two-Factor Authentication</h5>
<p>
We do not enforce a password policy, but we do recommend you enable Two-Factor

View file

@ -1,25 +1,13 @@
// Dependencies
import orderBy from 'lodash/orderBy.js'
import {
cloudflareImageUrl,
capitalize,
shortDate,
horFlexClasses,
newPatternUrl,
patternUrlFromState,
} from '@freesewing/utils'
import { cloudflareImageUrl, horFlexClasses, patternUrlFromState } from '@freesewing/utils'
import { urls, control as controlConfig } from '@freesewing/config'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useEffect, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import { useSelection } from '@freesewing/react/hooks/useSelection'
// Components
import Markdown from 'react-markdown'
import {
@ -28,7 +16,7 @@ import {
PassiveImageInput,
ListInput,
} from '@freesewing/react/components/Input'
import { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link'
import { Link as WebLink } from '@freesewing/react/components/Link'
import {
BoolNoIcon,
BoolYesIcon,
@ -42,12 +30,20 @@ import {
ResetIcon,
UploadIcon,
} from '@freesewing/react/components/Icon'
import { DisplayRow } from './shared.mjs'
import { TimeAgo } from '@freesewing/react/components/Time'
import { Popout } from '@freesewing/react/components/Popout'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { KeyVal } from '@freesewing/react/components/KeyVal'
/**
* A component to manage a pattern in the user's account data
*
* @component
* @param {object} props - All component props
* @param {number} props.id - The pattern ID to load
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Pattern = ({ id, Link }) => {
if (!Link) Link = WebLink
// Hooks
@ -137,7 +133,7 @@ export const Pattern = ({ id, Link }) => {
return (
<div className="tw:w-full">
{pattern.public ? (
<Popout note>
<Popout type="note">
<h5>This is the private view of your pattern</h5>
<p>
Everyone can access the public view since this is a public pattern.
@ -254,6 +250,21 @@ export const Pattern = ({ id, Link }) => {
)
}
/**
* A component to display a card representing a pattern in the user's account data.
*
* This is a pure render component, you have to pass in the data.
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @param {string} [props.href = false] - An optional URL the pattern card should link to
* @param {function} [props.onClick = false] - An optional onClick handler
* @param {object} props.pattern - An object holding the pattern data
* @param {string} [props.size = 'md'] - The size, one of lg, md, sm, or xs
* @param {boolean} [props.useA = false] - Whether to use an a tag of Link component when passing in a href prop
* @returns {JSX.Element}
*/
export const PatternCard = ({
pattern,
href = false,
@ -265,7 +276,7 @@ export const PatternCard = ({
if (!Link) Link = WebLink
const sizes = {
lg: 96,
md: 52,
md: 48,
sm: 36,
xs: 20,
}
@ -315,25 +326,16 @@ export const PatternCard = ({
const BadgeLink = ({ label, href }) => (
<a
href={href}
className="tw:daisy-badge tw:daisy-badge-secondary tw:font-bold tw:daisy-badge-lg tw:hover:text-secondary-content tw:hover:no-underline"
className="tw:daisy-badge tw:daisy-badge-secondary tw:font-bold tw:daisy-badge-lg tw:hover:text-secondary-content tw:hover:cursor-pointer"
>
{label}
<span className="tw:text-secondary-content hover:tw:decoration-0">{label}</span>
</a>
)
/**
* Helper component to show the pattern title, image, and various buttons
*/
const PatternHeader = ({
pattern,
Link,
account,
setModal,
setEdit,
togglePublic,
save,
clone,
}) => (
const PatternHeader = ({ pattern, Link, account, setModal, setEdit, togglePublic, clone }) => (
<>
<h2>{pattern.name}</h2>
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-2 tw:text-sm tw:items-center tw:mb-2">

View file

@ -1,16 +1,12 @@
// Dependencies
import orderBy from 'lodash/orderBy.js'
import { capitalize, shortDate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useEffect, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import { useSelection } from '@freesewing/react/hooks/useSelection'
// Components
import { TableWrapper } from '@freesewing/react/components/Table'
import { PatternCard } from '@freesewing/react/components/Account'
@ -23,16 +19,13 @@ import {
TrashIcon,
} from '@freesewing/react/components/Icon'
const t = (input) => {
console.log('t called', input)
return input
}
/*
* Component for the account/patterns page
/**
* A component to display and manage the list of patterns in the user's account
*
* @params {object} props - All React props
* @params {function} Link - A framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Patterns = ({ Link = false }) => {
if (!Link) Link = WebLink
@ -95,14 +88,16 @@ export const Patterns = ({ Link = false }) => {
onClick={removeSelectedPatterns}
disabled={count < 1}
>
<TrashIcon /> {count} {t('patterns')}
<TrashIcon /> {count} Patterns
</button>
<Link
className="tw:daisy-btn tw:daisy-btn-primary tw:capitalize tw:w-full tw:md:w-auto tw:hover:text-primary-content"
href="/editor/"
>
<span className="tw:text-primary-content">
<PlusIcon />
Create a new pattern
</span>
<span className="tw:text-primary-content">Create a new pattern</span>
</Link>
</div>
<TableWrapper>

View file

@ -19,11 +19,52 @@ const labels = {
website: 'Website',
}
/**
* A component to manage the user's Instagram handle in their account data
*
* @component
* @returns {JSX.Element}
*/
export const Instagram = () => <Platform platform="instagram" />
/**
* A component to manage the user's Mastodon handle in their account data
*
* @component
* @returns {JSX.Element}
*/
export const Mastodon = () => <Platform platform="mastodon" />
/**
* A component to manage the user's Reddit handle in their account data
*
* @component
* @returns {JSX.Element}
*/
export const Reddit = () => <Platform platform="reddit" />
/**
* A component to manage the user's Twitch handle in their account data
*
* @component
* @returns {JSX.Element}
*/
export const Twitch = () => <Platform platform="twitch" />
/**
* A component to manage the user's Tiktok handle in their account data
*
* @component
* @returns {JSX.Element}
*/
export const Tiktok = () => <Platform platform="tiktok" />
/**
* A component to manage the user's website URL in their account data
*
* @component
* @returns {JSX.Element}
*/
export const Website = () => <Platform platform="website" />
/*

View file

@ -1,19 +1,18 @@
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { ReloadIcon } from '@freesewing/react/components/Icon'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
/*
* Component for the account/actions/export page
/**
* A component handle a reload of the account data
*
* @component
* @returns {JSX.Element}
*/
export const Reload = () => {
// Hooks

View file

@ -3,21 +3,20 @@ import { navigate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { BackIcon as ExitIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
import { ModalWrapper } from '@freesewing/react/components/Modal'
/*
* Component for the account/actions/remove page
/**
* A component to handle the removal of a user's account
*
* @component
* @returns {JSX.Element}
*/
export const Remove = () => {
// Hooks

View file

@ -6,19 +6,23 @@ import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { BackIcon, NoIcon } from '@freesewing/react/components/Icon'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
import { ModalWrapper } from '@freesewing/react/components/Modal'
/*
* Component for the account/actions/restrict page
/**
* A component to manage the user's options to restrict processing of their data
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Restrict = ({ Link = false }) => {
if (!Link) Link = WebLink
@ -34,7 +38,7 @@ export const Restrict = ({ Link = false }) => {
// Helper method to restrict the account
const restrictAccount = async () => {
setLoadingStatus([true, 'Talking to the backend'])
const [status, body] = await backend.restrictAccount()
const [status] = await backend.restrictAccount()
if (status === 200) {
setLoadingStatus([true, 'Done. Consider yourself restrcited.', true, true])
signOut()
@ -46,7 +50,7 @@ export const Restrict = ({ Link = false }) => {
<>
<p>This button is red for a reason.</p>
<IconButton onClick={restrictAccount} color="error">
<Nocon />
<NoIcon />
Remove your FreeSewing account
</IconButton>
</>

View file

@ -1,6 +1,14 @@
import React from 'react'
import { KeyVal } from '@freesewing/react/components/KeyVal'
/**
* A component to display the user's role
*
* @component
* @param {object} props - All component props
* @param {string} props.role - The user role, either user or admin
* @returns {JSX.Element}
*/
export const UserRole = ({ role }) => {
if (role === 'user') return <RoleUser />
if (role === 'admin') return <RoleAdmin />

View file

@ -15,6 +15,7 @@ import {
shortDate,
timeAgo,
} from '@freesewing/utils'
import { modalMeasurementHelp } from '@freesewing/react/components/Help'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
@ -58,21 +59,16 @@ import { bundlePatternTranslations, draft, flattenFlags } from '../Editor/lib/in
import { Bonny } from '@freesewing/bonny'
import { MiniNote, MiniTip } from '../Mini/index.mjs'
const t = (input) => {
console.log('t called', input)
return input
}
/*
/**
* Component to show an individual measurements set
*
* @param {object} props - All React props
* @param {number} id - The ID of the measurements set to load
* @param {bool} publicOnly - If the set should be used with the backend.getPublicSet method
* @param {function} Link - An optional framework-specific Link component to use for client-side routing
* @param {object} measurementHelpProvider - A function that returns a url or action to show help for a specific measurement
* @component
* @param {object} props - All Component props
* @param {number} props.id - The ID of the measurements set to load
* @param {bool} [props.publicOnly = false] - If the set should be used with the backend.getPublicSet method
* @param {function} [props.Link = false] - An optional framework-specific Link component to use for client-side routing
*/
export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvider = false }) => {
export const Set = ({ id, publicOnly = false, Link = false }) => {
if (!Link) Link = WebLink
// Hooks
@ -384,7 +380,7 @@ export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvi
return (
<div className="tw:w-full">
{heading}
<RenderedCSet {...{ mset, setLoadingStatus, backend, imperial }} />
<RenderedCset {...{ mset, setLoadingStatus, backend, imperial }} />
</div>
)
@ -490,7 +486,7 @@ export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvi
current={mset.measies[m]}
original={mset.measies[m]}
update={updateMeasies}
helpProvider={measurementHelpProvider}
help={() => modalMeasurementHelp(m, setModal)}
/>
))}
@ -619,36 +615,38 @@ export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvi
)
}
/**
/*
* A (helper) component to display a measurements value
*
* @component
* @param {object} props - All React props
* @param {string} val - The value
* @param {string} m - The measurement name
* @param {bool} imperial - True for imperial measurements, or metric by default
*/
export const MeasurementValue = ({ val, m, imperial = false }) =>
const MeasurementValue = ({ val, m, imperial = false }) =>
isDegreeMeasurement(m) ? (
<span>{val}°</span>
) : (
<span dangerouslySetInnerHTML={{ __html: formatMm(val, imperial) }}></span>
)
/**
/*
* React component to suggest a measurements set for curation
*
* @component
* @param {object} props - All React props
* @param {string} mset - The measurements set
*/
export const SuggestCset = ({ mset, Link }) => {
const SuggestCset = ({ mset, Link }) => {
// State
const [height, setHeight] = useState('')
const [img, setImg] = useState('')
const [name, setName] = useState('')
const [notes, setNotes] = useState('')
const [submission, setSubmission] = useState(false)
console.log(mset)
// Context
const { setLoadingStatus } = useContext(LoadingStatusContext)
// Hooks
const backend = useBackend()
@ -745,7 +743,7 @@ export const SuggestCset = ({ mset, Link }) => {
Notes
</h4>
<p>If you would like to add any notes, you can do so here.</p>
<Popout tip compact>
<Popout type="tip" compact>
This field supports markdown
</Popout>
<MarkdownInput label="Notes" current={notes} update={setNotes} valid={() => true} />
@ -760,13 +758,14 @@ export const SuggestCset = ({ mset, Link }) => {
)
}
/**
/*
* React component to render a preview of a measurement set using the bonny pattern
*
* @component
* @param {object} props - All React props
* @param {string} mset - The measurements set
*/
export const RenderedCSet = ({ mset, imperial }) => {
const RenderedCset = ({ mset, imperial }) => {
const [previewVisible, setPreviewVisible] = useState(false)
const missing = []
@ -799,7 +798,7 @@ export const RenderedCSet = ({ mset, imperial }) => {
<strong>{formatMm(pattern.parts[0].front.points.head.y * -1, imperial)}</strong> high.
</p>
<p>Here is what the automated analysis found:</p>
{Object.entries(flattenFlags(flags)).map(([key, flag], i) => {
{Object.entries(flattenFlags(flags)).map(([key, flag]) => {
const desc = strings[flag.desc] || flag.desc
return (
@ -840,7 +839,7 @@ export const RenderedCSet = ({ mset, imperial }) => {
</li>
<li>
This preview is an <strong>approximation</strong>, not an exact representation. Bodies
have many variations that can't be captured with just a few measurements. We are
have many variations that can&apos;t be captured with just a few measurements. We are
missing some information, like how weight is distributed or your posture.
</li>
<li>
@ -867,11 +866,17 @@ export const RenderedCSet = ({ mset, imperial }) => {
)
}
/**
* A component to create a new measurements set.
*
* @component
* @returns {JSX.Element}
*/
export const NewSet = () => {
// Hooks
const backend = useBackend()
const { account } = useAccount()
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
const { setLoadingStatus } = useContext(LoadingStatusContext)
// State
const [name, setName] = useState('')

View file

@ -1,8 +1,7 @@
// Dependencies
import { measurements } from '@freesewing/config'
import { measurements as measurementsTranslations } from '@freesewing/i18n'
import { requiredMeasurements as designMeasurements } from '@freesewing/collection'
import { cloudflareImageUrl, capitalize, hasRequiredMeasurements } from '@freesewing/utils'
import { cloudflareImageUrl, hasRequiredMeasurements } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
@ -16,11 +15,13 @@ import { NoIcon, OkIcon, PlusIcon, TrashIcon, UploadIcon } from '@freesewing/rea
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { NewSet } from './Set.mjs'
/*
* The component for the an account/sets page
/**
* The component for the measurements sets in the user's account.
*
* @param {object} props - All React props
* @param {function} Link - An optional framework-specific Link component
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Sets = ({ Link = false }) => {
if (!Link) Link = WebLink
@ -160,16 +161,20 @@ export const Sets = ({ Link = false }) => {
}
/**
* React component to display a (card of a) single measurements set
* A component to render a card of a single measurements set.
*
* @param {object} props - All React props
* @param {function} Link - An optional framework-specific Link component
* @param {string} design - The designs for which to check required measurements
* @param {test} href - Where the set should link to
* @param {function} onClick - What to do when clicking on a set
* @param {object} set - The (data of the) measurements set
* @param {string} size - Size of the card
* @param {bool} useA - Whether to use an A tag or not
* This is a pure render component, you need to pass in the data.
*
* @component
* @param {object} props - All component props
* @param {React.Component} [props.Link = false] - An optional framework-specific Link component
* @param {string} [props.design = false] - An optional design name to check for required measurements in this set
* @param {string} [props.href = false] - On optional href for the card to link to
* @param {function} [props.onClick = false] - On optional onClick handler method
* @param {object} props.set - The data of the measurements set
* @param {string} [props.size = 'lg'] - Size of the card, one of lg, md, or sm
* @param {bool} [props.useA = false] - Whether to use an A tag or a Link component for href
* @returns {JSX.Element}
*/
export const MsetCard = ({
Link = false,
@ -183,7 +188,7 @@ export const MsetCard = ({
if (!Link) Link = WebLink
const sizes = {
lg: 96,
md: 52,
md: 48,
sm: 36,
}
const s = sizes[size]

View file

@ -1,6 +1,14 @@
import React from 'react'
import { KeyVal } from '@freesewing/react/components/KeyVal'
/**
* Displays a badge for the account status you pass it
*
* @component
* @param {object} props - All component props
* @param {number} props.status - The account status. One of -2, -1, 0, or 1
* @returns {JSX.Element}
*/
export const AccountStatus = ({ status }) => {
if (status === 0) return <AccountInactive />
if (status === 1) return <AccountActive />

View file

@ -1,27 +1,24 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
import { RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { NumberCircle } from '@freesewing/react/components/Number'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
/*
* Component for the account/preferences/units page
/**
* A component to manage the user's units
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @component
* @param {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
* @returns {JSX.Element}
*/
export const Units = ({ welcome = false }) => {
// Hooks

View file

@ -16,12 +16,14 @@ import { StringInput } from '@freesewing/react/components/Input'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
/*
* Component for the account/username page
/**
* A component to manage the user's username
*
* @params {object} props - All React props
* @params {bool} props.welcome - Set to true to use this component on the welcome page
* @params {function} props.Link - A framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @param {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
* @param {React.Component} [props.Link = false] - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Username = ({ welcome = false, Link = false }) => {
if (!Link) Link = WebLink
@ -55,10 +57,6 @@ export const Username = ({ welcome = false, Link = false }) => {
? '/welcome/' + welcomeSteps[account.control][5]
: '/docs/about/guide'
let btnClasses = 'daisy-btn mt-4 capitalize '
if (welcome) btnClasses += 'w-64 daisy-btn-secondary'
else btnClasses += 'w-full daisy-btn-primary'
return (
<div className="tw:w-full">
<StringInput

View file

@ -1,71 +1,69 @@
import React from 'react'
import { AccountStatus } from './Status.mjs'
import { Bookmarks, BookmarkButton } from './Bookmarks.mjs'
import { Links } from './Links.mjs'
import { Set, NewSet } from './Set.mjs'
import { Sets, MsetCard } from './Sets.mjs'
import { Patterns } from './Patterns.mjs'
import { Pattern, PatternCard } from './Pattern.mjs'
import { Apikeys } from './Apikeys.mjs'
import { Username } from './Username.mjs'
import { Bio } from './Bio.mjs'
import { Avatar } from './Avatar.mjs'
import { Email, EmailChangeConfirmation } from './Email.mjs'
import { Github } from './Github.mjs'
import { Instagram, Mastodon, Reddit, Twitch, Tiktok, Website } from './Platform.mjs'
import { Bio } from './Bio.mjs'
import { BookmarkButton, Bookmarks } from './Bookmarks.mjs'
import { Compare } from './Compare.mjs'
import { Control } from './Control.mjs'
import { Units } from './Units.mjs'
import { Newsletter } from './Newsletter.mjs'
import { Consent } from './Consent.mjs'
import { Password } from './Password.mjs'
import { Mfa } from './Mfa.mjs'
import { ImportSet } from './Import.mjs'
import { Control } from './Control.mjs'
import { Email, EmailChangeConfirmation } from './Email.mjs'
import { Export } from './Export.mjs'
import { Github } from './Github.mjs'
import { ImportSet } from './Import.mjs'
import { Instagram, Mastodon, Reddit, Twitch, Tiktok, Website } from './Platform.mjs'
import { Links } from './Links.mjs'
import { Mfa } from './Mfa.mjs'
import { MsetCard, Sets } from './Sets.mjs'
import { Newsletter } from './Newsletter.mjs'
import { Password } from './Password.mjs'
import { Pattern, PatternCard } from './Pattern.mjs'
import { Patterns } from './Patterns.mjs'
import { Reload } from './Reload.mjs'
import { Remove } from './Remove.mjs'
import { Restrict } from './Restrict.mjs'
import { Set, NewSet } from './Set.mjs'
import { Units } from './Units.mjs'
import { UserId } from './Id.mjs'
import { Username } from './Username.mjs'
import { UserRole } from './Role.mjs'
export {
AccountStatus,
Bookmarks,
BookmarkButton,
Links,
Set,
NewSet,
Sets,
MsetCard,
Patterns,
Pattern,
PatternCard,
Apikeys,
Username,
Bio,
Avatar,
Bio,
BookmarkButton,
Bookmarks,
Compare,
Consent,
Control,
Email,
EmailChangeConfirmation,
Github,
Instagram,
Mastodon,
Reddit,
Twitch,
Tiktok,
Website,
Compare,
Control,
Units,
Newsletter,
Consent,
Password,
Mfa,
ImportSet,
Export,
Github,
ImportSet,
Instagram,
Links,
Mastodon,
Mfa,
MsetCard,
NewSet,
Newsletter,
Password,
Pattern,
PatternCard,
Patterns,
Reddit,
Reload,
Remove,
Restrict,
Set,
Sets,
Tiktok,
Twitch,
Units,
UserId,
Username,
UserRole,
Website,
}

View file

@ -8,9 +8,6 @@ import {
CompareIcon,
DocsIcon,
UserIcon,
LeftIcon,
OkIcon,
NoIcon,
ShowcaseIcon,
} from '@freesewing/react/components/Icon'

View file

@ -1,8 +1,7 @@
// Dependencies
import { uiRoles as roles } from '@freesewing/config'
import { userAvatarUrl } from '@freesewing/utils'
// Hooks
import React, { useState, useContext, useEffect } from 'react'
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Context
@ -14,11 +13,16 @@ import { Spinner } from '@freesewing/react/components/Spinner'
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SearchIcon } from '@freesewing/react/components/Icon'
import { KeyVal } from '@freesewing/react/components/KeyVal'
import { Markdown } from '@freesewing/react/components/Markdown'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { AccountStatus, UserRole } from '@freesewing/react/components/Account'
export const SubscriberAdministration = ({ page }) => {
/**
* A component to manage FreeSewing newsletter subscribers (requires admin role)
*
* @component
* @returns {JSX.Element}
*/
export const SubscriberAdministration = () => {
const [subscribers, setSubscribers] = useState()
const [q, setQ] = useState()
const [hits, setHits] = useState([])
@ -106,6 +110,14 @@ export const SubscriberAdministration = ({ page }) => {
)
}
/**
* A component to manage FreeSewing users (requires the admin role)
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const UserAdministration = ({ Link = false }) => {
const backend = useBackend()
@ -154,7 +166,7 @@ export const UserAdministration = ({ Link = false }) => {
)
}
export const Hits = ({ results, Link = false }) => {
const Hits = ({ results, Link = false }) => {
if (!Link) Link = WebLink
return (
@ -179,7 +191,7 @@ export const Hits = ({ results, Link = false }) => {
)
}
export const User = ({ user, Link }) => {
const User = ({ user, Link }) => {
const { setModal } = useContext(ModalContext)
const { setLoadingStatus } = useContext(LoadingStatusContext)
const backend = useBackend()
@ -274,7 +286,7 @@ export const User = ({ user, Link }) => {
)
}
export const ImpersonateButton = ({ userId }) => {
const ImpersonateButton = ({ userId }) => {
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
const { impersonate } = useAccount()
@ -299,95 +311,3 @@ export const ImpersonateButton = ({ userId }) => {
</button>
)
}
export const Row = ({ title, val }) => (
<tr className="py-1">
<td className="text-sm px-2 text-right font-bold">{title}</td>
<td className="text-sm">{val}</td>
</tr>
)
export const ManageUser = ({ userId }) => {
// Hooks
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
const { account } = useAccount()
const { role } = account
// State
const [user, setUser] = useState({})
const [patterns, setPatterns] = useState({})
const [sets, setSets] = useState({})
// Effect
useEffect(() => {
const loadUser = async () => {
const result = await backend.adminLoadUser(userId)
if (result.success) {
setUser(result.data.user)
setPatterns(result.data.patterns)
setSets(result.data.sets)
}
}
loadUser()
}, [userId])
const updateUser = async (data) => {
setLoadingStatus([true, 'status:contactingBackend'])
const result = await backend.adminUpdateUser({ id: userId, data })
if (result.success) {
setLoadingStatus([true, 'status:settingsSaved', true, true])
setUser(result.data.user)
} else setLoadingStatus([true, 'status:backendError', true, false])
}
return user.id ? (
<div className="my-8">
<ShowUser
user={user}
button={role === 'admin' ? <ImpersonateButton userId={user.id} /> : null}
/>
{role === 'admin' ? (
<div className="flex flex-row flex-wrap gap-2 my-2">
{roles.map((role) => (
<button
key={role}
className="btn btn-primary btn-outline btn-sm"
onClick={() => updateUser({ role })}
disabled={role === user.role}
>
Assign {role} role
</button>
))}
</div>
) : null}
<div className="flex flex-row flex-wrap gap-2 my-2 mb-4">
{user.mfaEnabled && (
<button
className="btn btn-warning btn-outline btn-sm"
onClick={() => updateUser({ mfaEnabled: false })}
>
Disable MFA
</button>
)}
{Object.keys(freeSewingConfig.statuses).map((status) => (
<button
key={status}
className="btn btn-warning btn-outline btn-sm"
onClick={() => updateUser({ status })}
disabled={Number(status) === user.status}
>
Set {freeSewingConfig.statuses[status].name.toUpperCase()} status
</button>
))}
</div>
<Tabs tabs="Account, Patterns, Sets">
<Tab tabId="Account">{user.id ? <Json js={user} /> : null}</Tab>
<Tab tabId="Patterns">{patterns ? <Json js={patterns} /> : null}</Tab>
<Tab id="Sets">{sets ? <Json js={sets} /> : null}</Tab>
</Tabs>
</div>
) : (
<Loading />
)
}

View file

@ -1,12 +1,16 @@
import React from 'react'
/*
* The actual Breadcrumbs component
/**
* A component to render breadcrumbs
*
* @param {object} props - All the React props
* @param {array} props.crumbs - The crumbs, an array with objects with href, label keys
* @param {function} Link - An optional custom component to use to render the Link
* @param {text} title - The title of the current page
* This is a pure render component, you need to pass in the data.
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @param {array} [props.crumbs = []] - The crumbs, an array with objects with href & label properties
* @param {text} title - The title of the current (final) page the breadcrumbs lead to
* @returns {JSX.Element}
*/
export const Breadcrumbs = ({ crumbs = [], title, Link = false }) => {
if (Link === false) Link = RegularLink
@ -18,13 +22,20 @@ export const Breadcrumbs = ({ crumbs = [], title, Link = false }) => {
style={{ paddingLeft: 0 }}
>
<li className="tw:inline">
<Link href="/">Home</Link>
<Link href="/">
<b>Home</b>
</Link>
</li>
<Spacer />
{crumbs.map((crumb, i) => (
<React.Fragment key={i}>
<li key={i} className="tw:inline">
<Link href={crumb.href}>{crumb.label}</Link>
</li>
<li key={i} className="tw:inline">
<Spacer />
</li>
</React.Fragment>
))}
<li className="tw:inline">{title}</li>
</ul>

View file

@ -1,4 +1,69 @@
import React from 'react'
import React, { useContext, useState } from 'react'
import { copyToClipboard } from '@freesewing/utils'
import { CopyIcon, OkIcon } from '@freesewing/react/components/Icon'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
const handleCopied = (content, setCopied, setLoadingStatus, label, handler = false) => {
copyToClipboard(content)
setCopied(true)
setLoadingStatus([
true,
label ? `${label} copied to clipboard` : 'Copied to clipboard',
true,
true,
])
setTimeout(() => setCopied(false), 1000)
if (typeof handler === 'function') handler(content, label)
}
/**
* A component to copy something to the clipboard
*
* @component
* @param {object} props - All component props
* @param {JSX.element} props.children - The component children
* @param {string} [props.btnClasses = 'tw:daisy-btn tw:daisy-btn-ghost tw:hover:border-transparent w:hover:border-transparent tw:hover:shadow-none'] - The content that should be copied to the clipboard
* @param {string} props.content - The content that should be copied to the clipboard
* @param {string} props.label - The label to show when the content is copied
* @param {function} [props.onCopy=false] - An optional handler to call after copying to the clipboard, receives content, label as parameters
* @param {boolean} props.sup - Set this to true to render as superscript (above the line)
* @returns {JSX.Element}
*/
export const CopyToClipboardButton = ({
children,
content,
label = false,
sup = false,
btnClasses = 'tw:daisy-btn tw:daisy-btn-ghost tw:hover:border-transparent w:hover:border-transparent tw:hover:shadow-none',
onCopy = false,
}) => {
const [copied, setCopied] = useState(false)
const { setLoadingStatus } = useContext(LoadingStatusContext)
const style = sup ? 'tw:w-4 tw:h-4 tw:-mt-4 tw:-ml-1' : 'tw:w-5 tw:h-5'
return (
<button
className={
(copied ? 'tw:text-success ' : '') +
btnClasses +
' tw:w-full tw:lg:w-auto tw:group tw:flex tw:flex-row tw:justify-between'
}
onClick={() => handleCopied(content, setCopied, setLoadingStatus, label, onCopy)}
>
{sup ? children : null}
{copied ? (
<OkIcon
className={`${style} tw:text-success-content tw:bg-success tw:rounded-full tw:p-1`}
stroke={4}
/>
) : (
<CopyIcon className={`${style} tw:text-inherit tw:group-hover:text-secondary`} />
)}
{sup ? null : children}
</button>
)
}
/**
* A button with an icon and a label, something which we commonly use across our UI.

View file

@ -1,36 +1,25 @@
// Dependencies
import { atomWithHash } from 'jotai-location'
import {
about,
collection,
tags,
techniques,
designers,
developers,
examples,
measurements,
requiredMeasurements,
optionalMeasurements,
} from '@freesewing/collection'
import { capitalize, linkClasses, mutateObject } from '@freesewing/utils'
import { measurements as measurementsTranslations } from '@freesewing/i18n'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useContext, Fragment } from 'react'
import { useAtom } from 'jotai'
import React, { useState, Fragment } from 'react'
import { useFilter } from '@freesewing/react/hooks/useFilter'
// Components
import { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link'
import { Link as WebLink } from '@freesewing/react/components/Link'
import {
CircleIcon,
CisFemaleIcon,
DocsIcon,
FilterIcon,
HeartIcon,
NewPatternIcon,
ResetIcon,
ShowcaseIcon,
@ -40,23 +29,19 @@ import {
lineDrawingsBack,
} from '@freesewing/react/components/LineDrawing'
import { IconButton } from '@freesewing/react/components/Button'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { KeyVal } from '@freesewing/react/components/KeyVal'
import { MissingLinedrawing } from '../LineDrawing/missing.mjs'
const filterAtom = atomWithHash('filter', { example: true })
export const useFilter = () => {
return useAtom(filterAtom)
}
/**
* React component to show the FreeSewing collection and pick a design
* A component to show the FreeSewing collection and pick a design.
*
* @param {object} props - All React props
* @param {function} Link - An optional framework specific Link component for client-side routing
* @param {bool} editor - Set this to when loaded in the editor (this will make the display more dense)
* @param {bool} onClick - Set this to trigger an onClick event, rather than using links
* @component
* @param {object} props - All component props
* @param {React.Component} [props.Link = false] - A framework specific Link component for client-side routing
* @param {boolean} [props.editor = false] - Set this to true when rendering inside the FreeSewing editor
* @param {string} [props.linkTo = 'about'] - This controls where to link the design to. One of 'new', 'docs', or 'about'.
* @param {functino} [props.onClick = false] - You can pass in an onClick handler rather than using links
* @returns {JSX.Element}
*/
export const Collection = ({ Link = false, linkTo = 'about', editor = false, onClick = false }) => {
if (!Link) Link = WebLink
@ -184,17 +169,17 @@ export const Collection = ({ Link = false, linkTo = 'about', editor = false, onC
<div className="tw:flex tw:flex-row tw:gap-4 tw:items-center tw:justify-center tw:flex-wrap tw:my-2">
<button
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
onClick={() => updateFilter('example', !filter.example)}
onClick={() => updateFilter('ld', !filter.ld)}
>
{filter.example ? <CisFemaleIcon /> : <ShowcaseIcon />}
{filter.example ? 'Show Line Drawings' : 'Show Examples'}
{filter.ld ? <CisFemaleIcon /> : <ShowcaseIcon />}
{filter.ld ? 'Show Examples' : 'Show Line Drawings'}
</button>
<button
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
onClick={() => setFilter({ example: 1 })}
onClick={() => setFilter({ ld: 1 })}
>
<ResetIcon />
Clear Filter
Clear Filters
</button>
<button
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
@ -209,10 +194,10 @@ export const Collection = ({ Link = false, linkTo = 'about', editor = false, onC
<div className="tw:flex tw:flex-row tw:gap-4 tw:items-center tw:justify-center tw:flex-wrap tw:my-2">
<button
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
onClick={() => updateFilter('example', !filter.example)}
onClick={() => updateFilter('ld', !filter.ld)}
>
{filter.example ? <CisFemaleIcon /> : <ShowcaseIcon />}
{filter.example ? 'Show Line Drawings' : 'Show Examples'}
{filter.ld ? <ShowcaseIcon /> : <CisFemaleIcon />}
{filter.ld ? 'Show Examples' : 'Show Line Drawings'}
</button>
<button
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
@ -235,7 +220,7 @@ export const Collection = ({ Link = false, linkTo = 'about', editor = false, onC
key={d}
linkTo={linkTo}
onClick={onClick}
lineDrawing={filter.example ? false : true}
lineDrawing={filter.ld ? true : false}
/>
))}
</div>
@ -243,38 +228,6 @@ export const Collection = ({ Link = false, linkTo = 'about', editor = false, onC
)
}
/*
* A helper component to show a design technique
*
* @param {object} props - All React props
* @param {function} props.Link - A Link component, typically specific to the framework for client-side routing
* @param {string} props.technique - The technique name/id
*/
const Technique = ({ Link = WebLink, technique }) => (
<Link
href={`/designs/techniques/${technique}`}
className="tw:daisy-badge tw:daisy-badge-accent hover:tw:daisy-badge-secondary tw:hover:shadow tw:font-medium"
>
{technique}
</Link>
)
/*
* A helper component to show a design tag
*
* @param {object} props - All React props
* @param {function} props.Link - A Link component, typically specific to the framework for client-side routing
* @param {string} props.tag - The tag name/id
*/
const Tag = ({ Link = WebLink, technique }) => (
<Link
href={`/designs/tags/${tag}`}
className="tw:daisy-badge tw:daisy-badge-primary hover:tw:daisy-badge-secondary tw:hover:shadow tw:font-medium"
>
{tag}
</Link>
)
const DesignCard = ({ name, lineDrawing = false, linkTo, Link, onClick }) => {
if (!Link) Link = WebLink
@ -363,11 +316,14 @@ const noExample =
'https://images.pexels.com/photos/5626595/pexels-photo-5626595.jpeg?cs=srgb&fm=jpg&w=640&h=427'
/**
* React component to show info about a FreeSewing design
* A component to show info about a FreeSewing design
*
* @param {object} props - All React props
* @param {string} design - The name/id of the design
* @param {function} Link - An optional framework specific Link component for client-side routing
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @param {string} props.design - The name/id of the design
* @param {boolean} props.noDocsLink - Set this to true to not render a link to the documentation
* @returns {JSX.Element}
*/
export const DesignInfo = ({ Link = false, design = false, noDocsLink = false }) => {
if (!Link) Link = WebLink
@ -375,10 +331,6 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
// State
const [back, setBack] = useState(false)
// Context
const { setModal, clearModal } = useContext(ModalContext)
const { setLoadingStatus } = useContext(LoadingStatusContext)
if (!design) return null
// Line drawings
@ -392,13 +344,6 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
: [about[design].design]
const tags = about[design].tags || []
const techniques = about[design].techniques || []
const colors = {
1: 'success',
2: 'success',
3: 'warning',
4: 'warning',
5: 'error',
}
const makeButton = (
<div className={`tw:grid tw:grid-cols-1 tw:gap-2 tw:mb-4`}>
@ -506,7 +451,7 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
className="tw:daisy-badge tw:daisy-badge-primary tw:font-medium tw:hover:shadow tw:hover:cursor-pointer"
href={`/designs/#filter={"example"%3Atrue%2C"tag"%3A["${tag}"]}`}
>
{tag}
<span className="tw:text-primary-content">{tag}</span>
</Link>
))}
</div>
@ -518,7 +463,7 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
className="tw:daisy-badge tw:daisy-badge-accent tw:font-medium tw:hover:shadow tw:hover:cursor-pointer"
href={`/designs/#filter={"example"%3Atrue%2C"tag"%3A["${tech}"]}`}
>
{tech}
<span className="tw:text-accent-content">{tech}</span>
</Link>
))}
</div>
@ -555,21 +500,3 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
</>
)
}
const SharingIsCaring = ({ design }) => (
<>
<h2>
Use <b>#FreeSewing{capitalize(design)}</b> to facilitate discovery
</h2>
<p>
Please use the{' '}
<b>
<code>#FreeSewing{capitalize(design)}</code>
</b>{' '}
hashtag when discussing FreeSewing&apos;s <b>{capitalize(design)}</b> pattern online.
<br />
Doing so can help others discover your post, which really is a win-win.
</p>
<p>If you like, you can copy the hashtag below:</p>
</>
)

View file

@ -2,9 +2,17 @@ import React from 'react'
import { controlDesc } from '@freesewing/config'
import { BulletIcon } from '@freesewing/react/components/Icon'
export const ControlScore = ({ control, color = 'base-content' }) =>
/**
* A component to render a visualisation of the user's control/UX setting
*
* @component
* @param {object} props - All component props
* @param {number} props.control - The user's control setting (a number)
* @returns {JSX.Element}
*/
export const ControlScore = ({ control }) =>
control ? (
<div className={`tw:flex tw:flex-row tw:items-center tw:text-${color}`}>
<div className={`tw:flex tw:flex-row tw:items-center tw:text-base-content`}>
{Object.keys(controlDesc).map((score) => (
<BulletIcon
fill={control >= score ? true : false}

View file

@ -1,49 +0,0 @@
import React, { useContext, useState } from 'react'
import { copyToClipboard } from '@freesewing/utils'
import ReactDOMServer from 'react-dom/server'
import { CopyIcon, OkIcon } from '@freesewing/react/components/Icon'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
const strip = (html) =>
typeof DOMParser === 'undefined'
? html
: new DOMParser().parseFromString(html, 'text/html').body.textContent || ''
const handleCopied = (content, setCopied, setLoadingStatus, label) => {
copyToClipboard(content)
setCopied(true)
setLoadingStatus([
true,
label ? `${label} copied to clipboard` : 'Copied to clipboard',
true,
true,
])
setTimeout(() => setCopied(false), 1000)
}
export const CopyToClipboardButton = ({ children, content, label = false, sup = false }) => {
const [copied, setCopied] = useState(false)
const { setLoadingStatus } = useContext(LoadingStatusContext)
const style = sup ? 'tw:w-4 tw:h-4 tw:-mt-4' : 'tw:w-5 tw:h-5'
return (
<button
className={
(copied ? 'tw:text-success ' : '') +
'tw:daisy-btn tw:w-full tw:daisy-btn-ghost tw:lg:w-auto'
}
onClick={() => handleCopied(content, setCopied, setLoadingStatus, label)}
>
{copied ? (
<OkIcon
className={`${style} tw:text-success-content tw:bg-success tw:rounded-full tw:p-1`}
stroke={4}
/>
) : (
<CopyIcon className={`${style} tw:text-inherit`} />
)}
{children}
</button>
)
}

View file

@ -12,6 +12,18 @@ import { Spinner } from '@freesewing/react/components/Spinner'
import { Markdown } from '@freesewing/react/components/Markdown'
import { KeyVal } from '@freesewing/react/components/KeyVal'
/**
* A component to render a lineup of curated measurements sets.
*
* You need to provide either a clickHandler or a method to resolve the URL to link to as the href prop.
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @param {function} [props.clickHandler = false] - An optional function to call when a set is clicked
* @param {function} [props.href = false] - An optional function that should return the URL to be used for a given set
* @returns {JSX.Element}
*/
export const CuratedSetLineup = ({ href = false, clickHandler = false, Link = false }) => {
if (!Link) Link = WebLink
// Hooks
@ -86,6 +98,15 @@ export const CuratedSetLineup = ({ href = false, clickHandler = false, Link = fa
)
}
/**
* A component to render a curated measurements set
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @param {number} props.id - The ID of the curated set
* @returns {JSX.Element}
*/
export const CuratedSet = ({ Link = false, id = false }) => {
if (!Link) Link = WebLink
// Hooks

View file

@ -1,105 +0,0 @@
// Dependencies
import { collection } from '@freesewing/collection'
import { capitalize } from '@freesewing/utils'
// Hooks
import React, { useState } from 'react'
// Components
import { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link'
import { ShowcaseIcon } from '@freesewing/react/components/Icon'
const linkBuilders = {
new: (design) => `/-/?d=${design.toLowerCase()}`,
docs: (design) => `/docs/designs/${design.toLowerCase()}`,
}
export const DesignTechnique = ({ technique }) => (
<Link
className="badge badge-accent hover:badge-secondary hover:shadow font-medium"
href={`/designs/techniques/${technique}`}
>
{technique}
</Link>
)
export const DesignTag = ({ tag }) => {
const { t } = useTranslation(['tags'])
return (
<Link
className="badge badge-primary hover:badge-secondary hover:shadow font-medium"
href={`/designs/tags/${tag}`}
>
{t(tag)}
</Link>
)
}
export const DesignLink = ({ name, linkTo = 'new', className = linkClasses }) => (
<Link href={linkBuilders[linkTo](name)} className={className}>
{name}
</Link>
)
export const DesignCard = ({ name, lineDrawing = false }) => {
const { t } = useTranslation(ns)
// Context
const { setModal } = useContext(ModalContext)
const LineDrawing =
lineDrawing && lineDrawings[name]
? lineDrawings[name]
: ({ className }) => <div className={className}></div>
const exampleImageUrl = designImages[name]
? designImages[name]
: 'https://images.pexels.com/photos/5626595/pexels-photo-5626595.jpeg?cs=srgb&dl=pexels-frida-toth-5626595.jpg&fm=jpg&w=640&h=427&_gl=1*vmxq7y*_ga*MTM0OTk5OTY4NS4xNjYxMjUyMjc0*_ga_8JE65Q40S6*MTY5NTU1NDc0Mi4yNS4xLjE2OTU1NTU1NjIuMC4wLjA.'
const bg = lineDrawing
? {}
: {
backgroundImage: `url(${exampleImageUrl}`,
backgroundSize: 'cover',
backgroundPosition: 'center center',
}
return (
<button
onClick={() =>
setModal(
<ModalWrapper
flex="col"
justify="top lg:justify-center"
slideFrom="right"
keepOpenOnClick
>
<h1>{t(`designs:${name}.t`)}</h1>
<DesignInfo design={name} modal />
</ModalWrapper>
)
}
>
<div
className={`flex flex-col flex-nowrap items-start justify-start gap-2 h-80 w-full
btn btn-ghost border border-neutral p-0 border-b-none
hover:border hover:border-secondary
relative`}
style={bg}
>
<h5
className={`text-center py-2 px-4 rounded-t-lg m-0 w-full
${lineDrawing ? '' : 'bg-neutral/70 text-neutral-content'}`}
>
{t(`designs:${name}.t`)}
</h5>
<div className={lineDrawing ? 'p-4 grow w-full' : 'py-8'}>
<LineDrawing className="h-64 max-w-full m-auto my-4 text-base-content" />
</div>
<div
className={`pt-0 m-0 -mt-2 text-center w-full
${lineDrawing ? 'bg-transparent text-base-content' : 'bg-neutral/70 text-neutral-content'}`}
></div>
</div>
</button>
)
}

View file

@ -2,7 +2,24 @@ import React from 'react'
import { diffWords, diffJson } from 'diff'
import ReactDiffViewer from 'react-diff-viewer-continued'
/**
* A method to diff JSON content
*
* @public
* @param {object} from - Once side of the diff
* @param {object} to - Other side of the diff
* @returns {object}
*/
export const diffJSON = (from, to) => diffJson(from, to)
/**
* A method to diff string content
*
* @public
* @param {string} from - Once side of the diff
* @param {string} to - Other side of the diff
* @returns {object}
*/
export const diffCheck = (from, to) => diffWords(from, to)
export const DiffViewer = (props) => <ReactDiffViewer {...props} />

View file

@ -9,6 +9,7 @@ import {
ChatIcon,
DesignIcon,
DocsIcon,
HelpIcon,
ShowcaseIcon,
RssIcon,
LockIcon,
@ -16,16 +17,25 @@ import {
} from '@freesewing/react/components/Icon'
import { Layout as DefaultLayout } from '@freesewing/react/components/Layout'
/*
* This component should be the top level of a Docusaurus page
/**
* This component should be the top level of a Docusaurus page where you want access to context.
*
* This sets up the various context providers before
* passing all props down to the InnerPageWrapper.
* This is required because the context providers need to
* be setup for the modal and loading state work we do in the InnerPageWrapper
*
* be set up for the modal and loading state work.
* We also re-use the Docusaurus Layout component here, which needs to be at
* the top level of the page
* the top level of the page.
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.DocusaurusLayout - The docusaurus layout to apply
* @param {React.Component} props.Layout - The layout to apply inside docusaurus
* @param {JSX.Element} props.children - The component children
* @param {array} props.crumbs - The page breadcrumbs
* @param {string} props.description - The page description
* @param {string} props.title - The page title
* @returns {JSX.Element}
*/
export const DocusaurusPage = (props) => {
const DocusaurusLayout = props.DocusaurusLayout
@ -42,14 +52,23 @@ export const DocusaurusPage = (props) => {
)
}
/*
/**
* This component should be the top level of any Docusaurus content that's not
* a full page where you want access to context (typically account pages and so on)
* a full page where you want access to context (typically account pages and so on).
*
* This sets up the various context providers before
* passing all props down to the InnerPageWrapper.
* This is required because the context providers need to
* be setup for the modal and loading state work we do in the InnerPageWrapper
*
* @component
* @param {object} props - All component props
* @param {React.Component} props.Layout - The layout to apply inside docusaurus
* @param {JSX.Element} props.children - The component children
* @param {array} props.crumbs - The page breadcrumbs
* @param {string} props.description - The page description
* @param {string} props.title - The page title
* @returns {JSX.Element}
*/
export const DocusaurusDoc = (props) => (
<ModalContextProvider>
@ -94,6 +113,19 @@ const InnerDocusaurusPage = ({
)
}
/**
* A component to render (some) custom navbar items in Docusaurus
*
* This can be used to swizzle the default NavbarItem in Docusaurus.
* You should pass it in the default NavbarItem and it will use that
* for all but the following: account, designs, docs, blog, showcase, forum
*
* @component
* @param {object} props - All component props
* @param {string} props.id - The navbar item ID
* @param {React.Component} props.Default - The default NavbarItem component to use
* @returns {JSX.Element}
*/
export const NavbarItem = (props) => {
const { id, Default } = props
@ -153,4 +185,7 @@ const navbarItems = {
Link={props.Link}
/>
),
support: (props) => (
<SimpleNavbarItem label="Support" href="/support/" Icon={HelpIcon} Link={props.Link} />
),
}

View file

@ -3,6 +3,12 @@ import * as _echarts from 'echarts'
import ReactECharts from 'echarts-for-react'
import { Popout } from '@freesewing/react/components/Popout'
/**
* Re-export of Apache Echarts
*
* @public
* @constant
*/
export const echarts = _echarts
echarts.registerTheme('light', {
@ -12,6 +18,25 @@ echarts.registerTheme('dark', {
backgroundColor: 'transparent',
})
/**
* A component to provide Echart functionality.
*
* This is a wrapper around Apache Echarts. The option prop is for echarts.
*
* @component
* @param {object} props - All component props
* @param {object} [props.option = false] - The Echarts option object. This is
* marked as optional because this component will show a loading message when
* option is not an object. However, that is intended for use-cases where
* option relies on async code. This component is pointless if you do not
* (eventually) pass it an option prop.
* @param {string} [props.theme = 'light'] - The theme to use for echarts. Supports 'light' and 'dark'.
* @param {number} [props.h = 400] - The height of the chart, in pixels. Charts
* are rendered as SVG, we need to set a height because without a height, some
* browsers will not properly render the SVG element. This is an Echart
* limitation.
* @returns {JSX.Element}
*/
export const ChartWrapper = ({ option = false, theme = 'light', h = 400 }) => {
return option ? (
<ReactECharts option={option} className="class_2" theme={theme} style={{ height: h }} />

View file

@ -4,12 +4,12 @@ import React, { useState } from 'react'
* DaisyUI's accordion seems rather unreliable.
* So instead, we handle this in React state
*/
const getProps = (isActive = false) => ({
const getProps = () => ({
className: `tw:p-0 tw:rounded-lg tw:bg-transparent tw:hover:cursor-pointer
tw:w-full tw:h-auto tw:content-start tw:text-left tw:list-none`,
})
const getSubProps = (isActive) => ({
const getSubProps = () => ({
className: `tw:p-0 tw:rounded-none tw:bg-transparent tw:w-full tw:h-auto
tw:content-start tw:text-left tw:list-none`,
})

View file

@ -1,6 +1,6 @@
import React from 'react'
import mustache from 'mustache'
import { flattenFlags, stripNamespace, bundlePatternTranslations } from '../lib/index.mjs'
import { flattenFlags } from '../lib/index.mjs'
import {
ChatIcon,
ErrorIcon,
@ -115,7 +115,7 @@ export const FlagsAccordionTitle = ({ flags }) => {
)
}
export const FlagsAccordionEntries = ({ flags, update, pattern, strings }) => {
export const FlagsAccordionEntries = ({ flags, update, strings }) => {
const flagList = flattenFlags(flags)
if (Object.keys(flagList).length < 1) return null

View file

@ -17,6 +17,7 @@ import {
ExportIcon,
FixmeIcon,
FlagIcon,
MeasurementsIcon,
OptionsIcon,
PaperlessIcon,
PrintIcon,
@ -24,7 +25,6 @@ import {
ResetIcon,
RightIcon,
RocketIcon,
RotateIcon,
SaIcon,
SaveAsIcon,
SaveIcon,
@ -53,6 +53,7 @@ const headerMenuIcons = {
settings: SettingsIcon,
ui: UiIcon,
layout: PrintIcon,
measurements: MeasurementsIcon,
}
export const HeaderMenuIcon = (props) => {
@ -122,7 +123,7 @@ export const HeaderMenuTestViewDesignMeasurements = (props) => {
tooltip="See how changes to a measurement influence the pattern being generated."
toggle={
<>
<HeaderMenuIcon name="options" extraClasses="tw:text-secondary" />
<HeaderMenuIcon name="measurements" extraClasses="tw:text-secondary" />
<span className="tw:hidden tw:lg:inline">Test Measurements</span>
</>
}
@ -133,7 +134,7 @@ export const HeaderMenuTestViewDesignMeasurements = (props) => {
}
export const HeaderMenuDropdown = (props) => {
const { tooltip, toggle, open, setOpen, id, end = false } = props
const { toggle, open, setOpen, id } = props
const [localOpen, setLocalOpen] = useState(false)
useEffect(() => {
@ -460,18 +461,16 @@ export const HeaderMenuUndoIcons = (props) => {
}
export const HeaderMenuTestIcons = (props) => {
const { update, state, Design } = props
const { update } = props
const Button = HeaderMenuButton
const size = 'tw:w-5 tw:h-5'
const undos = state._?.undos && state._.undos.length > 0 ? state._.undos : false
return (
<div className="tw:flex tw:flex-row tw:flex-wrap tw:items-center tw:justify-center tw:px-0.5 tw:lg:px-1">
<div className="tw:flex tw:flex-row tw:flex-wrap tw:items-center tw:justify-center tw:px-0.5 tw:lg:px-1 tw:items-center">
<Button
updateHandler={() => update.settings('sample', undefined)}
tooltip="Clear the test so you can select another"
>
Clear Test
<ResetIcon /> <span className="tw:hidden tw:lg:inline">Clear Test</span>
</Button>
</div>
)
@ -531,7 +530,7 @@ export const HeaderMenuButton = ({
}) => (
<Tooltip tip={tooltip}>
<button
className={`${lgOnly ? 'tw:hidden tw:lg:inline' : ''} tw:daisy-btn tw:daisy-btn-ghost tw:daisy-btn-sm tw:px-1 tw:disabled:bg-transparent`}
className={`${lgOnly ? 'tw:hidden tw:lg:inline' : ''} tw:daisy-btn tw:daisy-btn-ghost tw:my-1 tw:px-1 tw:disabled:bg-transparent`}
onClick={updateHandler}
disabled={disabled}
>
@ -607,7 +606,7 @@ export const HeaderMenuViewMenu = (props) => {
}
export const HeaderMenuLayoutView = (props) => (
<>
<div className="tw:flex tw:flex-row tw:items-center">
<HeaderMenuDropdown
{...props}
id="layoutOptions"
@ -622,7 +621,7 @@ export const HeaderMenuLayoutView = (props) => (
<LayoutSettingsMenu {...props} />
</HeaderMenuDropdown>
<HeaderMenuLayoutViewIcons {...props} />
</>
</div>
)
export const HeaderMenuLayoutViewIcons = (props) => {
@ -660,11 +659,6 @@ export const HeaderMenuLayoutViewIcons = (props) => {
}
const pages = pattern.setStores[0].get('pages', {})
const format = state.ui.print?.pages?.size
? state.ui.print.pages.size
: settings.units === 'imperial'
? 'letter'
: 'a4'
const { cols, rows, count } = pages
const blank = cols * rows - count

View file

@ -35,11 +35,6 @@ export const LoadingStatus = ({ state, update }) => {
if (!state._.loading || Object.keys(state._.loading).length < 1) return null
const colorClasses = {
info: 'tw:bg-info tw:text-info-content',
primary: 'tw:bg-primary tw:text-primary-content',
}
return (
<div className="tw:fixed tw:bottom-4 md:tw:buttom-28 tw:left-0 tw:w-full tw:z-30 tw:md:px-4 tw:md:mx-auto mb-4">
<div className="tw:flex tw:flex-col tw:gap-2">

View file

@ -1,5 +1,10 @@
import React from 'react'
import React, { useContext } from 'react'
import { measurements as measurementTranslations } from '@freesewing/i18n'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
// Components
import { MeasurementInput } from '@freesewing/react/components/Input'
import { modalMeasurementHelp } from '@freesewing/react/components/Help'
/**
* This MeasurementsEditor component allows inline-editing of the measurements
@ -9,10 +14,13 @@ import { MeasurementInput } from '@freesewing/react/components/Input'
* @param {object} props.state - The ViewWrapper state object
* @param {object} props.state.settings - The current settings
* @param {object} props.update - Helper object for updating the ViewWrapper state
* @param {object} props.helpProvider - A function that takes a measurement and returns a url or action to show help for that measurement
* @return {function} MeasurementsEditor - React component
*/
export const MeasurementsEditor = ({ Design, update, state, helpProvider = false }) => {
export const MeasurementsEditor = ({ Design, update, state }) => {
// Context
const { setModal, modalContent } = useContext(ModalContext)
console.log({ modalContent })
/*
* Helper method to handle state updates for measurements
*/
@ -26,7 +34,7 @@ export const MeasurementsEditor = ({ Design, update, state, helpProvider = false
const { settings = {} } = state
return (
<div className="tw:max-w-2xl tw:mx-auto">
<div className="tw:max-w-xl tw:w-full tw:mx-auto">
<h4>Required Measurements</h4>
{Object.keys(Design.patternConfig.measurements).length === 0 ? (
<p>This design does not require any measurements.</p>
@ -40,7 +48,8 @@ export const MeasurementsEditor = ({ Design, update, state, helpProvider = false
original={settings.measurements?.[m]}
update={(m, newVal) => onUpdate(m, newVal)}
id={`edit-${m}`}
helpProvider={helpProvider}
label={measurementTranslations[m]}
help={() => modalMeasurementHelp(m, setModal)}
/>
))}
<br />
@ -58,6 +67,7 @@ export const MeasurementsEditor = ({ Design, update, state, helpProvider = false
original={settings.measurements?.[m]}
update={(m, newVal) => onUpdate(m, newVal)}
id={`edit-${m}`}
help={() => modalMeasurementHelp(m, setModal)}
/>
))
)}

View file

@ -2,7 +2,6 @@ import React, { useRef, useState, useEffect, useCallback } from 'react'
import { ZoomablePattern } from './ZoomablePattern.mjs'
import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
import { getProps } from '@freesewing/react/components/Pattern'
import { FlipIcon, RotateIcon, ResetIcon } from '@freesewing/react/components/Icon'
import { drag } from 'd3-drag'
import { select } from 'd3-selection'
//import { Buttons } from './transform-buttons.mjs'
@ -85,7 +84,7 @@ export const MovablePattern = ({
const sortedRenderProps = { ...renderProps, stacks: sortedStacks }
const Stack = ({ stackName, stack, settings, components, t }) => (
const Stack = ({ stackName, stack, settings, components }) => (
<MovableStack
{...{
stackName,
@ -397,7 +396,7 @@ function angle(pointA, pointB) {
const rectSize = 24
const Button = ({ onClickCb, transform, Icon, children, title = '' }) => {
const Button = ({ onClickCb, transform, Icon, title = '' }) => {
const _onClick = (event) => {
event.stopPropagation()
onClickCb(event)
@ -413,22 +412,24 @@ const Button = ({ onClickCb, transform, Icon, children, title = '' }) => {
)
}
export const ShowButtonsToggle = ({ ui, update }) => {
const hideButtons = (evt) => {
update.ui('hideMovableButtons', !evt.target.checked)
}
return (
<label className="label cursor-pointer">
<span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={!ui.hideMovableButtons}
onChange={hideButtons}
const InnerFlipIcon = () => (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
/>
</label>
)
}
)
const InnerRotateIcon = ({ props }) => (
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M 19.5,12 C 19.5,10.768 19.454,9.547 19.362,8.338 19.21576,6.3582806 17.641719,4.7842398 15.662,4.638 14.504476,4.5506731 13.344609,4.5048098 12.184624,4.5004103 M 19.5,12 l 3,-3 m -3,3 -3,-3 m -12,3 c 0,1.232 0.046,2.453 0.138,3.662 0.1462398,1.979719 1.7202806,3.55376 3.7,3.7 1.295324,0.09777 2.593584,0.143587 3.891661,0.13746 M 4.5,12 l 3,3 m -3,-3 -3,3"
{...props}
/>
)
const InnerResetIcon = () => (
<path d="M12 9.75 14.25 12m0 0 2.25 2.25M14.25 12l2.25-2.25M14.25 12 12 14.25m-2.58 4.92-6.374-6.375a1.125 1.125 0 0 1 0-1.59L9.42 4.83c.21-.211.497-.33.795-.33H19.5a2.25 2.25 0 0 1 2.25 2.25v10.5a2.25 2.25 0 0 1-2.25 2.25h-9.284c-.298 0-.585-.119-.795-.33Z" />
)
/** buttons for manipulating the part */
export const Buttons = ({ transform, flip, rotate, resetPart, rotate90, iconSize }) => {
@ -455,31 +456,31 @@ export const Buttons = ({ transform, flip, rotate, resetPart, rotate90, iconSize
<Button
onClickCb={resetPart}
transform={`translate(${rectSize / -2}, ${rectSize / -2})`}
Icon={() => <ResetIcon wrapped={0} />}
Icon={() => <InnerResetIcon />}
title="Reset part orientation"
/>
<Button
onClickCb={() => rotate90()}
transform={`translate(${rectSize * -2.7}, ${rectSize / -2})`}
Icon={() => <RotateIcon wrapped={0} style={{}} />}
Icon={() => <InnerRotateIcon />}
title="Rotate part clockwise"
/>
<Button
onClickCb={() => flip('y')}
transform={`rotate(90) translate(${rectSize / -2}, ${rectSize * -1.6})`}
Icon={() => <FlipIcon wrapped={0} />}
Icon={() => <InnerFlipIcon />}
title="Flip part top/bottom"
/>
<Button
onClickCb={() => flip('x')}
transform={`translate(${rectSize * -1.6}, ${rectSize / -2})`}
Icon={() => <FlipIcon style={{}} wrapped={0} />}
Icon={() => <InnerFlipIcon />}
title="Flip part left/right"
/>
<Button
onClickCb={() => rotate90(-1)}
transform={`translate(${rectSize * 1.7}, ${rectSize / -2})`}
Icon={() => <RotateIcon transform="scale(-1,1), translate(-24,0)" wrapped={0} />}
Icon={() => <InnerRotateIcon transform="scale(-1,1), translate(-24,0)" />}
title="Rotate part counter-clockwise"
/>
</g>

View file

@ -1,17 +1,12 @@
import React from 'react'
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
import { ZoomContextProvider } from './ZoomablePattern.mjs'
import {
HeaderMenu,
HeaderMenuDraftViewDesignOptions,
HeaderMenuDraftViewCoreSettings,
HeaderMenuDraftViewUiPreferences,
HeaderMenuDraftViewFlags,
} from './HeaderMenu.mjs'
import { HeaderMenu } from './HeaderMenu.mjs'
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.mjs'
import { Accordion } from './Accordion.mjs'
import { LayoutSettingsMenu } from './menus/LayoutMenu.mjs'
import { TestOptionsMenu, TestMeasurementsMenu } from './menus/TestMenu.mjs'
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
/**
* A layout for views that include a drafted pattern
@ -24,9 +19,8 @@ import { Accordion } from './Accordion.mjs'
* @param {object] pattern - The drafted pattern
*/
export const PatternLayout = (props) => {
const { menu = null, Design, pattern, update, config, state } = props
const { Design, pattern, update, config } = props
const i18n = useDesignTranslation(Design.designConfig.data.id)
const flags = props.pattern?.setStores?.[0]?.plugins?.['plugin-annotations']?.flags
return (
<ZoomContextProvider>
@ -39,11 +33,19 @@ export const PatternLayout = (props) => {
<div className="tw:lg:w-2/3 tw:flex tw:flex-col tw:h-full tw:grow tw:p-2 tw:shadow tw:mx-2">
{props.output}
</div>
{state.ui?.aside ? (
<div
className={`tw:hidden tw:md:block tw:w-1/3 tw:shrink tw:grow-0 tw:lg:p-4 tw:max-w-2xl tw:h-full tw:overflow-scroll`}
>
<h5 className="tw:capitalize">{pattern.designConfig.data.id} Options</h5>
<PatternAsideMenu {...props} i18n={i18n} />
</div>
</div>
</ZoomContextProvider>
)
}
const PatternAsideMenu = (props) => {
if (!props.state.ui?.aside) return null
if (props.state.view === 'draft')
return (
<PatternAsideWrapper>
<h5 className="tw:capitalize">{props.pattern.designConfig.data.id} Options</h5>
<SideMenuUl>
<DesignOptionsMenu {...props} />
</SideMenuUl>
@ -55,14 +57,42 @@ export const PatternLayout = (props) => {
<SideMenuUl>
<UiPreferencesMenu {...props} />
</SideMenuUl>
</div>
) : null}
</div>
</div>
</ZoomContextProvider>
</PatternAsideWrapper>
)
if (props.state.view === 'layout')
return (
<PatternAsideWrapper>
<h5>Layout Settings</h5>
<SideMenuUl>
<LayoutSettingsMenu {...props} />
</SideMenuUl>
</PatternAsideWrapper>
)
if (props.state.view === 'test')
return (
<PatternAsideWrapper>
<h5>Test Design Options</h5>
<SideMenuUl>
<TestOptionsMenu {...props} />
</SideMenuUl>
<h5>Test Measurements</h5>
<SideMenuUl>
<TestMeasurementsMenu {...props} />
</SideMenuUl>
</PatternAsideWrapper>
)
return null
}
const PatternAsideWrapper = ({ children }) => (
<div
className={`tw:hidden tw:md:block tw:w-1/3 tw:shrink tw:grow-0 tw:lg:p-4 tw:max-w-2xl tw:h-full tw:overflow-scroll`}
>
{children}
</div>
)
export const SideMenuUl = ({ children }) => (
<ul
className="tw:daisy-menu tw:daisy-dropdown-content tw:flex-nowrap tw:bg-base-200 tw:rounded-box tw:z-1 tw:w-full tw:p-0 tw:pl-0"

View file

@ -47,7 +47,7 @@ export const UserSetPicker = ({
if (!hasSets)
return (
<div className="tw:w-full tw:max-w-3xl tw:mx-auto">
<Popout tip>
<Popout type="tip">
<h5> You do not (yet) have any of your own measurements sets</h5>
<p>
You can store your measurements as a measurements set, after which you can generate as
@ -58,7 +58,7 @@ export const UserSetPicker = ({
href={config.hrefNewSet}
className="tw:daisy-btn tw:daisy-btn-accent tw:capitalize"
target="_BLANK"
rel="nofollow"
rel="noreferrer"
>
Create a new measurements set
</a>
@ -88,7 +88,7 @@ export const UserSetPicker = ({
)}
{lackingSets.length > 0 ? (
<div className="tw:my-4">
<Popout note>
<Popout type="note">
<h5>
Some of your measurements sets lack the measurements required to generate this pattern
</h5>
@ -174,7 +174,7 @@ export const BookmarkedSetPicker = ({
)}
{lackingSets.length > 0 && (
<div className="tw:my-4">
<Popout note>
<Popout type="note">
<h5>
Some of these measurements sets lack the measurements required to generate this
pattern

View file

@ -7,7 +7,7 @@ import { ZoomInIcon, ZoomOutIcon, RotateIcon } from '@freesewing/react/component
* A pattern you can pan and zoom
*/
export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref) {
const { renderProps, rotate, update, components = {}, strings = {} } = props
const { renderProps, rotate, components = {}, strings = {} } = props
const { onTransformed, zoomFunctions, setZoomFunctions } = useContext(ZoomContext)
return (

View file

@ -1,15 +1,27 @@
// Dependencies
import { menuValueWasChanged } from '../../lib/index.mjs'
import { designOptionType } from '@freesewing/utils'
import {
modalDesignOptionHelp,
modalCoreSettingHelp,
modalUiPreferenceHelp,
} from '@freesewing/react/components/Help'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useMemo } from 'react'
import React, { useState, useMemo, useContext } from 'react'
// Components
import { SubAccordion } from '../Accordion.mjs'
import { EditIcon, GroupIcon, OptionsIcon, ResetIcon } from '@freesewing/react/components/Icon'
import { FormControl } from '@freesewing/react/components/Input'
import {
HelpIcon,
EditIcon,
GroupIcon,
OptionsIcon,
ResetIcon,
} from '@freesewing/react/components/Icon'
import { Fieldset } from '@freesewing/react/components/Input'
import { MiniTip } from '@freesewing/react/components/Mini'
/** @type {String} class to apply to buttons on open menu items */
const iconButtonClass = 'tw:daisy-btn tw:daisy-btn-xs tw:daisy-btn-ghost tw:px-0 tw:text-accent'
/**
@ -26,6 +38,7 @@ const iconButtonClass = 'tw:daisy-btn tw:daisy-btn-xs tw:daisy-btn-ghost tw:px-0
* @param {React.Component} Value a value display component this menu item will use
* @param {Boolean} allowOverride all a text input to be used to override the given input component
* @param {Number} ux the user-defined ux level
* @param {strign} type one of designOption, coreSetting, or uiPreference
*/
export const MenuItem = ({
name,
@ -37,11 +50,11 @@ export const MenuItem = ({
allowOverride = false,
ux = 5,
state,
docs,
config,
Design,
i18n,
type,
}) => {
const { setModal } = useContext(ModalContext)
// Local state - whether the override input should be shown
const [override, setOverride] = useState(false)
@ -72,6 +85,39 @@ export const MenuItem = ({
// get buttons for open and closed states
const buttons = []
if (type === 'designOption')
buttons.push(
<button
key="help"
className="tw:daisy-btn tw:daisy-btn-xs tw:daisy-btn-ghost tw:px-0 tw:text-success"
onClick={() => modalDesignOptionHelp(Design.designConfig.data.id, name, setModal)}
title="Show help for this design option"
>
<HelpIcon />
</button>
)
else if (type === 'coreSetting')
buttons.push(
<button
key="help"
className="tw:daisy-btn tw:daisy-btn-xs tw:daisy-btn-ghost tw:px-0 tw:text-success"
onClick={() => modalCoreSettingHelp(name, setModal)}
title="Show help for this core setting"
>
<HelpIcon />
</button>
)
else if (type === 'uiPreference')
buttons.push(
<button
key="help"
className="tw:daisy-btn tw:daisy-btn-xs tw:daisy-btn-ghost tw:px-0 tw:text-success"
onClick={() => modalUiPreferenceHelp(name, setModal)}
title="Show help for this UI preference"
>
<HelpIcon />
</button>
)
if (allowOverride)
buttons.push(
<button
@ -106,7 +152,7 @@ export const MenuItem = ({
return (
<>
<FormControl
<Fieldset
label={false}
id={config.name}
labelBR={<div className="tw:flex tw:flex-row tw:items-center tw:gap-2">{buttons}</div>}
@ -119,7 +165,7 @@ export const MenuItem = ({
}
>
<Input {...drillProps} />
</FormControl>
</Fieldset>
{config.about ? <MiniTip>{config.about}</MiniTip> : null}
</>
)

View file

@ -21,7 +21,7 @@ import {
MenuScaleSettingValue,
} from './Value.mjs'
import { MenuItemGroup, MenuItem } from './Container.mjs'
import { SettingsIcon } from '@freesewing/react/components/Icon'
import { SettingsIcon, TrashIcon } from '@freesewing/react/components/Icon'
/**
* The core settings menu
@ -124,6 +124,7 @@ export const CoreSetting = ({ name, config, ux, updateHandler, current, passProp
return (
<MenuItem
type="coreSetting"
{...{
name,
config,

View file

@ -121,6 +121,7 @@ export const DesignOption = ({ config, settings, ux, inputs, values, ...rest })
return (
<MenuItem
type="designOption"
{...{
config,
ux,

View file

@ -3,7 +3,7 @@ import { OptionsIcon, SettingsIcon, UiIcon } from '@freesewing/react/components/
import { DesignOptionsMenu } from './DesignOptionsMenu.mjs'
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
import { UiPreferencesMenu } from './UiPreferencesMenu.mjs'
import { FlagsAccordionEntries } from '../Flag.mjs'
import { FlagsAccordionEntries, FlagsAccordionTitle } from '../Flag.mjs'
import { Accordion } from '../Accordion.mjs'
export const DraftMenu = ({ Design, pattern, state, update, i18n }) => {

View file

@ -16,12 +16,7 @@ import { mergeOptions } from '@freesewing/core'
import { KeyVal } from '@freesewing/react/components/KeyVal'
/** A boolean version of {@see MenuListInput} that sets up the necessary configuration */
export const MenuBoolInput = (props) => {
const { name, config } = props
//const boolConfig = useBoolConfig(name, config)
return <MenuListInput {...props} />
}
export const MenuBoolInput = (props) => <MenuListInput {...props} />
/** A placeholder for an input to handle constant values */
export const MenuConstantInput = ({
@ -59,41 +54,6 @@ export const MenuDegInput = (props) => {
)
}
const getTitleAndDesc = (config = {}, i18n = {}, isDesignOption = false) => {
if (config.choiceTitles && config.choiceDescriptions) {
const current = typeof config.current === 'undefined' ? config.dflt : config.current
return {
title: config.choiceTitles[current],
desc: config.choiceDescriptions[current],
}
}
let titleKey = config.choiceTitles
? 'fixme' //config.choiceTitles[entry]
: isDesignOption
? i18n?.en?.o?.[name] || name
: `${name}.o.${entry}`
if (!config.choiceTitles && i18n && i18n.en.o[`${name}.${entry}`])
titleKey = i18n.en.o[`${name}.${entry}`]
const title = config.title
? config.title
: config.titleMethod
? config.titleMethod(entry)
: typeof titleKey === 'string'
? i18n.en.o[titleKey]?.t
: titleKey.t
const desc = config.valueMethod
? config.valueMethod(entry)
: typeof titleKey === 'string'
? i18n.en.o[titleKey]?.d
: titleKey.d
return {
title: 'fixmeTitle',
desc: 'fixmeDesc',
}
}
/**
* An input for selecting and item from a list
* @param {String} options.name the name of the property this input changes
@ -111,11 +71,9 @@ export const MenuListInput = ({
current,
updateHandler,
compact = false,
t,
changed,
design,
isDesignOption = false,
i18n,
}) => {
const handleChange = useSharedHandlers({
dflt: config.dflt,
@ -125,7 +83,7 @@ export const MenuListInput = ({
})
return config.list.map((entry) => {
const { title = false, about = false } = config //getTitleAndDesc(config, i18n, isDesignOption)
const { title = false, about = false } = config
if (!title || !about) console.log('No title or about in', { name, config, design })
const sideBySide = config.sideBySide || about.length + title.length < 42
@ -286,7 +244,6 @@ export const MenuSliderInput = ({
setReset,
children,
changed,
i18n,
state,
Design,
}) => {

View file

@ -61,63 +61,3 @@ export const LayoutSettingsMenu = ({ update, state, Design }) => {
/>
)
}
/*
const PrintActions = ({ state, update }) => (
<SubAccordion
items={[
[
<div className="tw:w-full tw:flex tw:flex-row tw:gap2 tw:justify-between" key={1}>
<div className="tw:flex tw:flex-row tw:items-center tw:gap-2">
<LeftRightIcon />
<span>{'workbench:partTransfo'}</span>
</div>
{state.ui.hideMovableButtons ? <BoolNoIcon /> : <BoolYesIcon />}
</div>,
<ListInput
key={2}
update={() => update.state.ui('hideMovableButtons', state.ui.hideMovableButtons ? false : true)}
label={
<span className="tw:text-base tw:font-normal">{'workbench:partTransfoDesc'}</span>
}
list={[
{
val: true,
label: 'workbench:partTransfoNo',
desc: 'workbench:partTransfoNoDesc',
},
{
val: false,
label: 'workbench:partTransfoYes',
desc: 'workbench:partTransfoYesDesc',
},
]}
current={state.ui.hideMovableButtons ? true : false}
/>,
'partTransfo',
],
[
<div className="tw:w-full tw:flex tw:flex-row tw:gap2 tw:justify-between" key={1}>
<div className="tw:flex tw:flex-row tw:items-center tw:gap-2">
<ResetIcon />
<span>{'workbench:resetPrintLayout'}</span>
</div>
<WarningIcon />
</div>,
<Fragment key={2}>
<p>{'workbench:resetPrintLayoutDesc'}</p>
<button
className={`${horFlexClasses} tw:btn tw:btn-warning tw:btn-outline tw:w-full`}
onClick={() => update.ui(['layouts', 'print'])}
>
<ResetIcon />
<span>{'workbench:resetPrintLayout'}</span>
</button>
</Fragment>,
'reset',
],
]}
/>
)
*/

View file

@ -5,7 +5,7 @@ import { measurements as measurementsTranslations } from '@freesewing/i18n'
import React, { useMemo } from 'react'
// Components
import { MenuButtonGroup } from './Container.mjs'
import { BeakerIcon, OptionsIcon } from '@freesewing/react/components/Icon'
import { MeasurementsIcon, OptionsIcon } from '@freesewing/react/components/Icon'
/**
* The test design options menu
@ -87,12 +87,12 @@ const SampleOptionButton = ({ name, i18n, update }) => (
}
onClick={() => update.settings('sample', { type: 'option', option: name })}
>
<BeakerIcon className="tw:w-5 tw:h-5" />
<OptionsIcon className="tw:w-5 tw:h-5" />
<span>{i18n.en?.o[name]?.t ?? name}</span>
</button>
)
const SampleMeasurementButton = ({ name, i18n, update }) => (
const SampleMeasurementButton = ({ name, update }) => (
<button
className={
'tw:daisy-btn tw:daisy-btn-outline tw:daisy-btn-sm tw:mx-2 ' +
@ -100,7 +100,7 @@ const SampleMeasurementButton = ({ name, i18n, update }) => (
}
onClick={() => update.settings('sample', { type: 'measurement', measurement: name })}
>
<BeakerIcon className="tw:w-5 tw:h-5" />
<MeasurementsIcon className="tw:w-5 tw:h-5" />
<span>{measurementsTranslations[name]}</span>
</button>
)

View file

@ -19,7 +19,7 @@ export const UiPreferencesMenu = ({ update, state, Design }) => {
}
const values = {
aside: MenuListValue,
ux: (props) => <span>{state.ui.ux}/5</span>,
ux: () => <span>{state.ui.ux}/5</span>,
rotate: MenuListValue,
renderer: MenuListValue,
}
@ -53,5 +53,11 @@ export const UiPreferencesMenu = ({ update, state, Design }) => {
}
export const UiPreference = ({ name, ux, ...rest }) => (
<MenuItem {...rest} name={name} allowToggle={!['ux', 'view'].includes(name) && ux > 3} ux={ux} />
<MenuItem
type="uiPreference"
{...rest}
name={name}
allowToggle={!['ux', 'view'].includes(name) && ux > 3}
ux={ux}
/>
)

View file

@ -160,11 +160,10 @@ export const MenuScaleSettingValue = ({ current, config, changed }) => (
/**
* Displays the value for core's only setting
*
* @param {object} config - The option config
* @param {number} current - The current (count) value
* @param {bool} changed - Whether or not the value is non-default
*/
export const MenuOnlySettingValue = ({ current, config }) => (
export const MenuOnlySettingValue = ({ current }) => (
<MenuHighlightValue changed={current !== undefined}>
{current === undefined ? '-' : current.length}
</MenuHighlightValue>

View file

@ -5,10 +5,9 @@ import { Collection } from '@freesewing/react/components/Collection'
* The designs view is loaded if and only if no design name is passed to the editor
*
* @param {Object} props - All the props
* @param {Object} designs - Object holding all designs
* @param {Object} update - ViewWrapper state update object
*/
export const DesignsView = ({ designs = {}, update }) => (
export const DesignsView = ({ update }) => (
<div className="tw:text-center tw:mt-8 tw:mb-24 tw:p-2 lg: tw:p-8">
<h1>Choose a design from the FreeSewing collection</h1>
<Collection

View file

@ -1,11 +1,14 @@
// Dependencies
import { linkClasses, capitalize } from '@freesewing/utils'
import { linkClasses } from '@freesewing/utils'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React from 'react'
import React, { useContext } from 'react'
// Components
import { H1, H5 } from '@freesewing/react/components/Heading'
import { Popout } from '@freesewing/react/components/Popout'
import { H1, H2 } from '@freesewing/react/components/Heading'
import { modalDocsHelp } from '@freesewing/react/components/Help'
import { HeaderMenu } from '../HeaderMenu.mjs'
import { Popout } from '@freesewing/react/components/Popout'
/**
* This is the docs view, it just shows content
@ -16,51 +19,55 @@ import { HeaderMenu } from '../HeaderMenu.mjs'
* @param {Object} props.update - Helper object for updating the editor state
*/
export const DocsView = ({ state, config, update }) => {
const { setModal, modalContent } = useContext(ModalContext)
return (
<>
<HeaderMenu state={state} {...{ config, update }} />
<div className="tw:m-auto tw:mt-8 tw:max-w-2xl tw:px-4 tw:mb-8">
<H1>Documentation</H1>
{state?.design ? (
<Popout link>
<H5>Design Documentation</H5>
<p className="tw:text-lg">
You can find documentation for the {capitalize(state.design)} design at:
<br />
<b>
<>
<H2>Design Documentation</H2>
<Popout type="link" compact dense>
<div className="tw:font-bold tw:py-1">
<a
className={linkClasses}
href={`https://freesewing.eu/docs/designs/${state.design}`}
>{`FreeSewing.eu/docs/designs/${state.design}`}</a>
</b>
</p>
</div>
</Popout>
<button
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline tw:mt-4"
onClick={() => modalDocsHelp(`docs/designs/${state.design}`, setModal)}
>
Open without leaving the Editor
</button>
</>
) : null}
<Popout link>
<H5>Understanding the FreeSewing Pattern Editor</H5>
<p className="tw:text-lg">
Please refer to the pattern editor documentation at:
<br />
<b>
<H2>Editor Documentation</H2>
<Popout type="link" compact dense>
<div className="tw:font-bold tw:py-1">
<a
className={linkClasses}
href="https://freesewing.eu/docs/about/editor"
>{`FreeSewing.eu/docs/about/editor`}</a>
</b>
</p>
href="https://freesewing.eu/docs/editor"
>{`FreeSewing.eu/docs/editor`}</a>
</div>
</Popout>
<Popout tip>
<H5>
Looking for info on how it <em>really</em> works?
</H5>
<p>
Documentation for developers and contributors is available at{' '}
<button
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline tw:mt-4"
onClick={() => modalDocsHelp(`docs/editor`, setModal)}
>
Open without leaving the Editor
</button>
<H2>Developer Documentation</H2>
<Popout type="link" compact>
<b>
<a className={linkClasses} href="https://freesewing.dev/">{`FreeSewing.dev`}</a>
</b>
</p>
</Popout>
</div>
{modalContent}
</>
)
}

View file

@ -14,7 +14,7 @@ export const DraftErrorHandler = ({ failure, errors }) => {
}
return (
<Popout error>
<Popout type="error">
<p>
Sorry, there were problems drafting your pattern with the given measurements and options.
</p>
@ -27,8 +27,8 @@ export const DraftErrorHandler = ({ failure, errors }) => {
.
</p>
<p>
If you believe your measurements are correct and/or if you'd like further assistance, you
can ask for help <Link href="https://forum.freesewing.eu">on our forum</Link>,{' '}
If you believe your measurements are correct and/or if you&apos;d like further assistance,
you can ask for help <Link href="https://forum.freesewing.eu">on our forum</Link>,{' '}
<Link href="https://discord.freesewing.org">our Discord server</Link>, or{' '}
<Link href="https://codeberg.org/freesewing/freesewing/issues">report an issue</Link>.
</p>

View file

@ -1,7 +1,9 @@
// Dependencies
import React from 'react'
import React, { useContext } from 'react'
import { bundlePatternTranslations, draft, missingMeasurements } from '../../lib/index.mjs'
import { colors, darkColors } from '@freesewing/plugin-theme'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import { useColorMode } from '@docusaurus/theme-common'
// Components
@ -29,6 +31,7 @@ import { translateStrings } from '../../../Pattern/index.mjs'
* @return {function} DraftView - React component
*/
export const DraftView = ({ Design, state, update, config, plugins = [], PluginOutput = Null }) => {
const { modalContent } = useContext(ModalContext)
/*
* We need to manipulate the theme for SVG in the browser
*/
@ -103,6 +106,7 @@ export const DraftView = ({ Design, state, update, config, plugins = [], PluginO
rotate={state.ui.rotate}
update={update}
/>
{modalContent}
</>
)
}

View file

@ -23,7 +23,6 @@ import { yaml as yamlLang } from '@codemirror/lang-yaml'
* @param {Object} props.update - Helper object for updating the editor state
*/
export const EditSettingsView = (props) => {
const [settings, setSettings] = useState(props.state?.settings || {})
const { state, config, update } = props
return (
@ -96,7 +95,7 @@ export const PrimedSettingsEditor = (props) => {
setSettings(newSettings)
}
} catch (err) {
// This is fine
console.log(err)
}
}

View file

@ -8,7 +8,7 @@ import React, { useState } from 'react'
// Components
import { H1, H2, H3, H5 } from '@freesewing/react/components/Heading'
import { HeaderMenu } from '../HeaderMenu.mjs'
import { CopyToClipboardButton } from '@freesewing/react/components/CopyToClipboardButton'
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
import { Highlight } from '@freesewing/react/components/Highlight'
import { EditIcon, CodeIcon, TipIcon, PrintIcon } from '@freesewing/react/components/Icon'
@ -23,10 +23,10 @@ import { EditIcon, CodeIcon, TipIcon, PrintIcon } from '@freesewing/react/compon
export const ExportView = (props) => {
const { config, state, update } = props
const { settings = {} } = state // Guard against undefined settings
const [link, setLink] = useState(false)
const [format, setFormat] = useState(false)
const setLink = useState(false)[1]
const setFormat = useState(false)[1]
const { protocol, hostname, port } = window.location
const { protocol, port } = window.location
const site =
(protocol === 'https:' && port === 443) || (protocol === 'http:' && port === 80)
? `${window.location.protocol}//${window.location.hostname}`
@ -58,10 +58,26 @@ export const ExportView = (props) => {
<H2>Share your pattern</H2>
<p>If you merely want to share your pattern with others, you can copy these URLs:</p>
<div className="tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-2 tw:mt-2 ">
<CopyToClipboardButton content={urls.a} update={update}>
<CopyToClipboardButton
content={urls.a}
btnClasses="tw:daisy-btn tw-daisy-btn-neutral tw:daisy-btn-outline"
label="Pattern and Measurements URL"
onCopy={() => {
console.log('handler called')
update.notifySuccess('Pattern and Measurements URL copied to clipboard')
}}
>
Pattern and Measurements
</CopyToClipboardButton>
<CopyToClipboardButton content={urls.b} update={update}>
<CopyToClipboardButton
content={urls.b}
btnClasses="tw:daisy-btn tw-daisy-btn-neutral tw:daisy-btn-outline"
label="Pattern URL"
onCopy={() => {
console.log('handler called')
update.notifySuccess('Pattern URL copied to clipboard')
}}
>
Pattern only
</CopyToClipboardButton>
</div>
@ -102,6 +118,7 @@ export const ExportView = (props) => {
<H3>ISO paper sizes</H3>
{['a4', 'a3', 'a2', 'a1', 'a0'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
onClick={() => exportPattern({ ...exportProps, format })}
>
@ -114,6 +131,7 @@ export const ExportView = (props) => {
<H3>Other paper sizes</H3>
{['letter', 'legal', 'tabloid'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
onClick={() => exportPattern({ ...exportProps, format })}
>
@ -130,6 +148,7 @@ export const ExportView = (props) => {
<div className="tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-2 tw:mt-2">
{['svg', 'pdf'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
onClick={() => exportPattern({ ...exportProps, format })}
>
@ -143,6 +162,7 @@ export const ExportView = (props) => {
<div className="tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-2 tw:mt-2">
{['json', 'yaml'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
onClick={() => exportPattern({ ...exportProps, format })}
>

View file

@ -11,7 +11,7 @@ import { DraftErrorHandler } from './DraftErrorHandler.mjs'
export const LayoutView = (props) => {
const { config, state, update, Design } = props
const { ui, settings } = state
const { settings } = state
const defaultSettings = defaultPrintSettings(settings?.units)
// Settings for the tiler plugin

View file

@ -2,8 +2,10 @@
import { designMeasurements } from '../../lib/index.mjs'
import { capitalize, horFlexClasses as horFlexClasses } from '@freesewing/utils'
import { measurements as measurementsTranslations } from '@freesewing/i18n'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { Fragment, useState, useEffect } from 'react'
import React, { Fragment, useState, useEffect, useContext } from 'react'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Popout } from '@freesewing/react/components/Popout'
@ -37,7 +39,6 @@ const iconClasses = {
* @param {Array} props.missingMeasurements - List of missing measurements for the current design
* @param {Object} props.state - The editor state object
* @param {Object} props.update - Helper object for updating the editor state
* @param {object} props.helpProvider - A function that takes a measurement and returns a url or action to show help for that measurement
* @return {Function} MeasurementsView - React component
*/
export const MeasurementsView = ({
@ -47,8 +48,8 @@ export const MeasurementsView = ({
state,
update,
design,
measurementHelpProvider = false,
}) => {
const { modalContent } = useContext(ModalContext)
/*
* If there is no view set, completing measurements will switch to the view picker
* Which is a bit confusing. So in this case, set the view to measurements.
@ -170,10 +171,7 @@ export const MeasurementsView = ({
</div>
<p className="tw:text-left">You can manually set or override measurements below.</p>
</Fragment>,
<MeasurementsEditor
key={2}
{...{ Design, config, update, state, helpProvider: measurementHelpProvider }}
/>,
<MeasurementsEditor key={2} {...{ Design, config, update, state }} />,
'edit',
])
@ -183,7 +181,7 @@ export const MeasurementsView = ({
<div className="tw:max-w-7xl tw:mt-8 tw:mx-auto tw:px-4 tw:mb-4">
<H1>Measurements</H1>
{missingMeasurements && missingMeasurements.length > 0 ? (
<Popout note dense noP>
<Popout type="note" dense>
<h3>
To generate this pattern, we need {missingMeasurements.length} additional measurement
{missingMeasurements.length === 1 ? '' : 's'}:
@ -198,7 +196,7 @@ export const MeasurementsView = ({
</ol>
</Popout>
) : (
<Popout tip dense noP>
<Popout type="tip" dense>
<H5>We have all required measurements to draft this pattern</H5>
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-2 tw:mt-2">
<button
@ -218,6 +216,7 @@ export const MeasurementsView = ({
)}
{items.length > 1 ? <Accordion items={items} /> : items}
</div>
{modalContent}
</>
)
}

View file

@ -8,8 +8,8 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { RoleBlock } from '@freesewing/react/components/Role'
import { Popout } from '@freesewing/react/components/Popout'
import { StringInput } from '@freesewing/react/components/Input'
import { SaveAsIcon } from '@freesewing/react/components/Icon'
import { StringInput, MarkdownInput } from '@freesewing/react/components/Input'
import { SaveAsIcon, SaveIcon } from '@freesewing/react/components/Icon'
import { H1 } from '@freesewing/react/components/Heading'
import { Link, SuccessLink } from '@freesewing/react/components/Link'
import { HeaderMenu } from '../HeaderMenu.mjs'
@ -67,7 +67,8 @@ export const SaveView = ({ config, state, update }) => {
}
const savePattern = async () => {
setLoadingStatus([true, 'Saving pattern...'])
const loadingId = 'savePattern'
update.startLoading(loadingId)
const patternData = {
design: state.design,
settings,
@ -77,17 +78,10 @@ export const SaveView = ({ config, state, update }) => {
}
const result = await backend.updatePattern(saveAs.pattern, patternData)
if (result.success) {
//setLoadingStatus([
// true,
// <>
// {t('status:patternSaved')} <small>[#{saveAs.pattern}]</small>
// </>,
// true,
// true,
//])
update.stopLoading(loadingId)
setSavedId(saveAs.pattern)
update.notify({ color: 'success', msg: 'boom' }, saveAs.pattern)
} //else setLoadingStatus([true, 'backendError', true, false])
update.notifySuccess('Pattern saved', loadingId)
}
}
return (
@ -98,17 +92,17 @@ export const SaveView = ({ config, state, update }) => {
<>
<h2>Save Pattern</h2>
{savedId && (
<Popout link>
<Popout type="link">
<h5>Pattern Saved</h5>
See: <Link href={`/account/patterns/${savedId}`}>/account/patterns/{savedId}</Link>
</Popout>
)}
<button
className={`${classeshorFlexNoSm} tw:btn tw:btn-primary tw:btn-lg tw:w-full tw:mt-2 tw:my-8`}
className={`tw:flex tw:flex-row tw:items-center tw:gap-2 tw:btn tw:btn-primary tw:btn-lg tw:w-full tw:mt-2 tw:my-8`}
onClick={savePattern}
>
<SaveIcon className="tw:h-8 tw:w-8" />
Save Patter #{saveAs.pattern}
Save Pattern #{saveAs.pattern}
</button>
</>
) : null}
@ -145,11 +139,7 @@ export const SaveView = ({ config, state, update }) => {
}
/>
{withNotes ? (
<Swizzled.components.MarkdownInput
label="Pattern notes"
current={notes}
update={setNotes}
/>
<MarkdownInput label="Pattern notes" current={notes} update={setNotes} />
) : null}
<div className="tw:flex tw:flex-row tw:gap-2 tw:mt-8">
<button
@ -172,7 +162,7 @@ export const SaveView = ({ config, state, update }) => {
To access your saved patterns, go to:
<b>
{' '}
<Link href="/account/patterns">/account/patterns</Link>
<Link href="/account/data/patterns/">/account/data/patterns</Link>
</b>
</p>
</div>

View file

@ -14,6 +14,7 @@ import { ZoomablePattern } from '../ZoomablePattern.mjs'
import { PatternLayout } from '../PatternLayout.mjs'
import { HeaderMenu } from '../HeaderMenu.mjs'
import { H1, H3, H4, H5 } from '@freesewing/react/components/Heading'
import { OptionsIcon, MeasurementsIcon } from '@freesewing/react/components/Icon'
/**
* The test view allows users to test options and measurements
@ -69,7 +70,6 @@ export const TestView = ({ Design, state, update, config }) => {
patternLocale={state.locale || 'en'}
rotate={state.ui.rotate}
strings={strings}
rotate={state.ui.rotate}
/>
)
@ -88,6 +88,8 @@ export const TestView = ({ Design, state, update, config }) => {
't',
'ASC'
)
const btnClasses =
'tw:my-0.5 tw:block tw:daisy-btn tw:daisy-btn-primary tw:daisy-btn-outline tw:daisy-btn-xs tw:flex tw:flex-row tw:items-center tw:justify-between tw:w-full tw:max-w-64'
return (
<>
@ -107,12 +109,13 @@ export const TestView = ({ Design, state, update, config }) => {
{trm.map(({ t, m }) => (
<button
key={m}
className="tw:my-0.5 tw:block tw:daisy-btn tw:daisy-btn-primary tw:daisy-btn-outline tw:daisy-btn-xs"
className={btnClasses}
onClick={() =>
update.settings(['sample'], { type: 'measurement', measurement: m })
}
>
{t}
<MeasurementsIcon className="tw:w-4 tw:h-4" />
<span>{t}</span>
</button>
))}
</div>
@ -123,12 +126,13 @@ export const TestView = ({ Design, state, update, config }) => {
{tom.map(({ t, m }) => (
<button
key={m}
className="tw:my-0.5 tw:block tw:daisy-btn tw:daisy-btn-primary tw:daisy-btn-outline tw:daisy-btn-xs"
className={btnClasses}
onClick={() =>
update.settings(['sample'], { type: 'measurement', measurement: m })
}
>
{t}
<MeasurementsIcon className="tw:w-4 tw:h-4" />
<span>{t}</span>
</button>
))}
</div>
@ -159,6 +163,8 @@ const SampleOptionsMenu = ({ Design, state, update }) => {
const SampleOptionsSubMenu = ({ structure, update, level = 1 }) => {
const output = []
const btnClasses =
'tw:my-0.5 tw:block tw:daisy-btn tw:daisy-btn-primary tw:daisy-btn-outline tw:daisy-btn-xs tw:flex tw:flex-row tw:items-center tw:justify-between tw:w-full tw:max-w-64'
/*
* Show entries alphabetic, but force groups last, and advanced last among them
*/
@ -176,10 +182,11 @@ const SampleOptionsSubMenu = ({ structure, update, level = 1 }) => {
output.push(
<button
key={name}
className="tw:my-0.5 tw:block tw:daisy-btn tw:daisy-btn-primary tw:daisy-btn-outline tw:daisy-btn-xs"
className={btnClasses}
onClick={() => update.settings(['sample'], { type: 'option', option: name })}
>
{struct.title}
<OptionsIcon className="tw:w-4 tw:h-4" />
<span>{struct.title}</span>
</button>
)
}

View file

@ -1,6 +1,5 @@
// Dependencies
import React, { useState, useEffect } from 'react'
import * as echarts from 'echarts'
import { timingPlugin } from '@freesewing/plugin-timing'
// Components
import { ChartWrapper } from '@freesewing/react/components/Echart'
@ -36,18 +35,6 @@ const TimingHeader = ({ timing, parts }) => {
) : null
}
const resolveColor = (color) => {
const [c, i] = color.split('-')
return tailwindColors[c][i]
}
const getColor = (i, colors) =>
new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: resolveColor(colors[i % colors.length]) + 'dd' },
{ offset: 1, color: resolveColor(colors[i % colors.length]) },
])
const timeScore = (took) => {
if (took < 25) return 'Very Fast'
if (took < 50) return 'Fast'
@ -58,7 +45,6 @@ const timeScore = (took) => {
}
const option = (parts, data, took, setData) => ({
//color: colors.map((color) => resolveColor(color)),
title: {
text: `Timing of most recent draft: ${timeScore(took)}`,
left: 'center',
@ -127,7 +113,7 @@ const option = (parts, data, took, setData) => ({
},
],
yAxis: [{ type: 'value' }],
series: parts.map((name, i) => ({
series: parts.map((name) => ({
name,
type: 'line',
stack: 'Total',

View file

@ -27,7 +27,7 @@ export const UndosView = ({ Design, update, state, config }) => {
<H1>Undo History</H1>
<p className="tw:mb-4">Time-travel through your recent pattern changes.</p>
{steps.length < 1 ? (
<Popout note>
<Popout type="note">
<h4>Your undo history is currently empty</h4>
<p>When you make changes to your pattern, they will show up here.</p>
<p>For example, you can click the button below to change the pattern rotation:</p>
@ -108,18 +108,17 @@ export const UndoStep = ({ update, state, step, Design, compact = false, index =
return (
<>
<p className="tw:text-sm tw:italic tw:font-medium tw:opacity-70 tw:text-right tw:p-0 tw:tw:m-0 tw:-mb-2 tw:pr-2">
<UndoStepTimeAgo step={step} />
</p>
<ButtonFrame onClick={() => update.restore(index, state._)}>
<div className="tw:flex tw:flex-row tw:items-center tw:justify-between tw:gap-2 tw:w-full tw:m-0 tw:p-0 tw:-mt-2 tw:text-lg">
<span className="tw:flex tw:flex-row tw:gap-2 tw:items-center">
{data.fieldIcon || null}
{data.title}
</span>
<div className="tw:flex tw:flex-col tw:font-medium tw:items-end tw:w-full tw:-mb-2">
<div className="tw:text-sm tw:-mt-2 tw:italic">
<UndoStepTimeAgo step={step} />
</div>
<div className="tw:flex tw:flex-row tw:items-center tw:justify-start tw:gap-2 tw:w-full tw:text-lg tw:-mt-2">
<span className="tw:opacity-70 tw:flex tw:flex-row tw:gap-1 tw:items-center tw:text-base">
{data.icon || null} {data.menu}
{data.menu}
</span>
<span>&raquo;</span>
<span className="tw:flex tw:flex-row tw:gap-2 tw:items-center">{data.title}</span>
</div>
<div className="tw:flex tw:flex-row tw:gap-1 tw:items-center tw:align-start tw:w-full">
{data.msg ? (
@ -136,6 +135,7 @@ export const UndoStep = ({ update, state, step, Design, compact = false, index =
</>
)}
</div>
</div>
</ButtonFrame>
</>
)

View file

@ -28,7 +28,7 @@ export const ViewPicker = ({ Design, update, state }) => {
.map((view) => (
<MainCard key={view} {...{ view, update, Design }} />
))}
<Popout note>
<Popout type="note">
<div className="tw:text-left">
<H5>pe:measurementsFreeViewsOnly.t:</H5>
<p>pe:measurementsFreeViewsOnly.d</p>
@ -70,7 +70,7 @@ export const ViewPicker = ({ Design, update, state }) => {
)
}
const MainCard = ({ view, update, Design }) => {
const MainCard = ({ view, update }) => {
const Icon = viewIcons[view]
return (

View file

@ -37,6 +37,7 @@ export const useEditorState = (init = {}, setEphemeralState, config) => {
if (typeof data.s === 'object') setState(data.s)
else setState(init)
} catch (err) {
console.log(err)
setState(init)
}
}
@ -45,31 +46,6 @@ export const useEditorState = (init = {}, setEphemeralState, config) => {
return [state, setState, update]
}
/*
* Our URL state library does not support storing Javascript objects out of the box.
* But it allows us to pass a customer parser to handle them, so this is that parser
*/
const pojoParser = {
parse: (v) => {
let val
try {
val = JSON.parse(v)
} catch (err) {
val = null
}
return val
},
serialize: (v) => {
let val
try {
val = JSON.stringify(v)
} catch (err) {
val = null
}
return val
},
}
function getHashData() {
if (!window) return false

View file

@ -19,16 +19,18 @@ import { useAccount } from '../../hooks/useAccount/index.mjs'
* FreeSewing's pattern editor
*
* Editor is the high-level FreeSewing component
* that provides the entire pattern editing environment
* that provides the entire pattern editing environment.
* This is a high-level wrapper that figures out what view to load initially,
* and handles state for the pattern, including the view
* and handles state for the pattern, including the view.
*
* @component
* @param {object} props - All React props
* @param {object} props.config - A configuration object for the editor
* @param {object} props.design - A design name to force the editor to use this design
* @param {object} props.preload - Any state to preload
* @param {function} props.setTitle - A way to set the page title (optional)
* @param {object} props.localDesigns - A way to add local designs to the editor (optional)
* @param {object} [props.config = {}] - A configuration object for the editor
* @param {object} [props.design = false] - A design name to preset the editor to use this design
* @param {object} [props.preload = {}] - Any state to preload
* @param {function} [props.setTitle = false] - A way to set the page title
* @param {object} [props.localDesigns = {}] - A way to add local designs to the editor
* @param {function} [props.measurementsHelpProvider = false] - A function that should return to a URL for measurements help
*/
export const Editor = ({
config = {},
@ -161,7 +163,7 @@ export const Editor = ({
)
}
/**
/*
* Helper method to figure out what view to load
* based on the props passed in, and destructure
* the props we need for it.

View file

@ -2,7 +2,7 @@ import React from 'react'
// Dependencies
import { defaultConfig as config } from '../config/index.mjs'
import { measurementAsMm } from '@freesewing/utils'
import { linkClasses } from '@freesewing/utils'
/*
* Components
* Note that these are only used as returns values
@ -61,12 +61,6 @@ export function menuCoreSettingsSaboolHandler({ toggleSa }) {
return toggleSa
}
const CoreDocsLink = ({ item }) => (
<a href={`/docs/about/site/draft/#${item.toLowerCase()}`} className={`${linkClasses} tw:px-2`}>
Learn more
</a>
)
export function menuCoreSettingsStructure({
units = 'metric',
sabool = false,
@ -77,12 +71,7 @@ export function menuCoreSettingsStructure({
sabool: {
dense: true,
title: 'Include seam allowance',
about: (
<>
Controls whether or not you want to include seam allowance on your pattern.
<CoreDocsLink item="sabool" />
</>
),
about: <>Controls whether or not you want to include seam allowance on your pattern.</>,
ux: config.uxLevels.core.sa,
list: [0, 1],
choiceTitles: {
@ -99,12 +88,7 @@ export function menuCoreSettingsStructure({
samm: sabool
? {
title: 'Seam Allowance Size',
about: (
<>
Controls the size of the pattern's seam allowance.
<CoreDocsLink item="sa" />
</>
),
about: <>Controls the size of the pattern&apos;s seam allowance.</>,
ux: config.uxLevels.core.sa,
min: 0,
max: units === 'imperial' ? 25.4 : 25, // values are in mm
@ -115,12 +99,7 @@ export function menuCoreSettingsStructure({
units: {
dense: true,
title: 'Pattern units',
about: (
<>
Allows you to switch between metric and imperial units on the pattern.
<CoreDocsLink item="units" />
</>
),
about: <>Allows you to switch between metric and imperial units on the pattern.</>,
ux: config.uxLevels.core.units,
list: ['metric', 'imperial'],
dflt: accountUnits,
@ -141,7 +120,6 @@ export function menuCoreSettingsStructure({
<>
Trees are awesome, and taping together sewing patterns is not much fun. Try our paperless
mode to avoid the need to print out your pattern altogether.
<CoreDocsLink item="paperless" />
</>
),
ux: config.uxLevels.core.paperless,
@ -164,7 +142,6 @@ export function menuCoreSettingsStructure({
<>
Controls how detailed the pattern is; Either a complete pattern with all details, or a
basic outline of the pattern parts.
<CoreDocsLink item="complete" />
</>
),
ux: config.uxLevels.core.complete,
@ -187,7 +164,6 @@ export function menuCoreSettingsStructure({
<>
Controls efforts to save paper. Disable this to expand all pattern parts at the cost of
using more space & paper.
<CoreDocsLink item="expand" />
</>
),
ux: config.uxLevels.core.expand,
@ -206,12 +182,7 @@ export function menuCoreSettingsStructure({
only: {
dense: true,
title: 'Only included selected pattern parts',
about: (
<>
Allows you to control what parts to include in your pattern.
<CoreDocsLink item="only" />
</>
),
about: <>Allows you to control what parts to include in your pattern.</>,
ux: config.uxLevels.core.only,
dflt: false,
list: parts,
@ -224,7 +195,6 @@ export function menuCoreSettingsStructure({
<>
Allows you to control the scale of annotations on the pattern. This is most useful when
generating very small patterns, like for doll outfits.
<CoreDocsLink item="scale" />
</>
),
ux: config.uxLevels.core.scale,
@ -240,7 +210,6 @@ export function menuCoreSettingsStructure({
<>
Controls the gap between pattern parts, as well as the gap between the parts and the page
edge.
<CoreDocsLink item="margin" />
</>
),
ux: config.uxLevels.core.margin,

View file

@ -2,17 +2,6 @@ import React from 'react'
import { mergeOptions } from '@freesewing/core'
import { designOptionType, set, orderBy } from '@freesewing/utils'
import { i18n } from '@freesewing/collection'
import { linkClasses } from '@freesewing/utils'
const DesignDocsLink = ({ design, item }) => (
<a
href={`/docs/designs/${design}/options/#${item.toLowerCase()}`}
className={`${linkClasses} tw:px-2`}
target="_BLANK"
>
Learn more
</a>
)
export function menuDesignOptionsStructure(design, options, settings, asFullList = false) {
if (!options) return options
@ -23,12 +12,7 @@ export function menuDesignOptionsStructure(design, options, settings, asFullList
...option,
name,
title: i18n[design]?.en?.o?.[name]?.t || name,
about: (
<span>
{i18n[design]?.en?.o?.[name]?.d || name}
<DesignDocsLink item={name} design={design} />
</span>
),
about: <span>{i18n[design]?.en?.o?.[name]?.d || name}</span>,
dense: true,
sideBySide: true,
}
@ -62,14 +46,14 @@ export function menuDesignOptionsStructure(design, options, settings, asFullList
option.valueTitles = {}
option.choiceTitles = {}
option.choiceDescriptions = {}
for (const entry of option.list) {
option.list.forEach(() => {
option.choiceTitles.false = eno[`${option.name}No`]?.t || option.name
option.choiceDescriptions.false = eno[`${option.name}No`]?.d || 'No'
option.valueTitles.false = 'No'
option.choiceTitles.true = eno[`${option.name}Yes`]?.t || 'Yes'
option.choiceDescriptions.true = eno[`${option.name}Yes`]?.d || 'No'
option.valueTitles.true = 'Yes'
}
})
}
if (typeof option.menu === 'function')
option.menu = asFullList

View file

@ -2,7 +2,7 @@
import React from 'react'
import { defaultConfig } from '../config/index.mjs'
import { round, formatMm, randomLoadingMessage } from '@freesewing/utils'
import { formatDesignOptionValue, menuCoreSettingsStructure } from './index.mjs'
import { formatDesignOptionValue, menuCoreSettingsStructure, fractionToDecimal } from './index.mjs'
import { menuUiPreferencesStructure } from './ui-preferences.mjs'
import { i18n } from '@freesewing/collection'
import { i18n as pluginI18n } from '@freesewing/core-plugins'
@ -320,7 +320,7 @@ export function initialEditorState(preload = {}, config) {
return initial
}
/**
/*
* round a value to the correct number of decimal places to display all supplied digits after multiplication
* this is a workaround for floating point errors
* examples:
@ -721,15 +721,15 @@ export function cloudImageUrl({ id = 'default-avatar', variant = 'public' }) {
/*
* Return something default so that people will actually change it
*/
if (!id || id === 'default-avatar') return config.cloudImageDflt
if (!id || id === 'default-avatar') return defaultConfig.cloudImageDflt
/*
* If the variant is invalid, set it to the smallest thumbnail so
* people don't load enourmous images by accident
*/
if (!config.cloudImageVariants.includes(variant)) variant = 'sq100'
if (!defaultConfig.cloudImageVariants.includes(variant)) variant = 'sq100'
return `${config.cloudImageUrl}${id}/${variant}`
return `${defaultConfig.cloudImageUrl}${id}/${variant}`
}
/**
* This method does nothing. It is used to disable certain methods
@ -746,25 +746,6 @@ export function noop() {
export function notEmpty(value) {
return String(value).length > 0
}
/**
* Helper method to merge arrays of translation namespaces
*
* Note that this method is variadic
*
* @param {[string]} namespaces - A string or array of strings of namespaces
* @return {[string]} namespaces - A merged array of all namespaces
*/
export function nsMerge(...args) {
const ns = new Set()
for (const arg of args) {
if (typeof arg === 'string') ns.add(arg)
else if (Array.isArray(arg)) {
for (const el of nsMerge(...arg)) ns.add(el)
}
}
return [...ns]
}
/*
* A translation fallback method in case none is passed in

View file

@ -3,7 +3,7 @@ import fileSaver from 'file-saver'
import { themePlugin } from '@freesewing/plugin-theme'
import { pluginI18n } from '@freesewing/plugin-i18n'
import { tilerPlugin } from './plugin-tiler.mjs'
import { capitalize, escapeSvgText, formatMm, get } from '@freesewing/utils'
import { capitalize, escapeSvgText, get } from '@freesewing/utils'
import mustache from 'mustache'
import he from 'he'
import yaml from 'js-yaml'

View file

@ -28,7 +28,6 @@ import {
menuValueWasChanged,
noop,
notEmpty,
nsMerge,
objUpdate,
sample,
settingsValueIsCustom,
@ -91,7 +90,6 @@ export {
menuValueWasChanged,
noop,
notEmpty,
nsMerge,
objUpdate,
sample,
settingsValueIsCustom,

View file

@ -1,5 +1,4 @@
import React from 'react'
import { linkClasses } from '@freesewing/utils'
import {
CoverPageIcon,
PageMarginIcon,
@ -9,13 +8,6 @@ import {
ScaleIcon,
} from '@freesewing/react/components/Icon'
const UiDocsLink = ({ item }) => (
<a href={`/docs/about/site/draft/#${item.toLowerCase()}`} className={`${linkClasses} tw:px-2`}>
Learn more
</a>
)
const sizes = ['a4', 'a3', 'a2', 'a1', 'a0', 'letter', 'legal', 'tabloid']
const defaultPrintSettings = (units) => ({
size: units === 'imperial' ? 'letter' : 'a4',
orientation: 'portrait',

View file

@ -1,14 +1,7 @@
import React from 'react'
import { defaultConfig } from '../config/index.mjs'
import { linkClasses } from '@freesewing/utils'
import { AsideIcon, RotateIcon, RocketIcon, UxIcon } from '@freesewing/react/components/Icon'
const UiDocsLink = ({ item }) => (
<a href={`/docs/about/site/draft/#${item.toLowerCase()}`} className={`${linkClasses} tw:px-2`}>
Learn more
</a>
)
export function menuUiPreferencesStructure() {
const uiUx = defaultConfig.uxLevels.ui
const uiPreferences = {
@ -19,7 +12,6 @@ export function menuUiPreferencesStructure() {
<span>
Uses the right side of the screen for the Design Options, Core Settings, and UI
Preferences menus.
<UiDocsLink item="aside" />
</span>
),
ux: uiUx.aside,
@ -35,10 +27,7 @@ export function menuUiPreferencesStructure() {
dense: true,
title: 'User Experience',
about: (
<span>
Controls the user experience, from keep it simple, to give me all the powers.
<UiDocsLink item="control" />
</span>
<span>Controls the user experience, from keep it simple, to give me all the powers.</span>
),
ux: uiUx.ux,
emoji: '🖥️',
@ -63,12 +52,7 @@ export function menuUiPreferencesStructure() {
rotate: {
dense: true,
title: 'Rotate Pattern',
about: (
<span>
Allows you to rotate your pattern 90 degrees, handy for tall patterns.
<UiDocsLink item="rotate" />
</span>
),
about: <span>Allows you to rotate your pattern 90 degrees, handy for tall patterns.</span>,
ux: uiUx.rotate,
list: [0, 1],
choiceTitles: {
@ -81,12 +65,7 @@ export function menuUiPreferencesStructure() {
renderer: {
dense: true,
title: 'Pattern render engine',
about: (
<span>
Change the underlying method for rendering the pattern on screen.
<UiDocsLink item="renderer" />
</span>
),
about: <span>Change the underlying method for rendering the pattern on screen.</span>,
ux: uiUx.renderer,
list: ['react', 'svg'],
choiceTitles: {

View file

@ -1,35 +1,119 @@
import React from 'react'
/**
* A component to renderd a styled H1 tag.
*
* Because of the TailwindCSS reset, common tags are unstyled.
* When you need to render a H1 tag outside of a context where it is
* automatically styled (such as inside markdown) you can use this.
* Alternatively, you can wrap your content in div.prose which will apply the
* styles in CSS.
*
* @component
* @param {object} props - All component props
* @param {JSX.element} props.children - The component children
* @returns {JSX.Element}
*/
export const H1 = ({ children }) => (
<h1 className="tw:text-5xl tw:pt-5 tw:pb-4 tw:font-thin tw:tracking-tighter tw:lg:text-6xl">
{children}
</h1>
)
/**
* A component to renderd a styled H2 tag.
*
* Because of the TailwindCSS reset, common tags are unstyled.
* When you need to render a H2 tag outside of a context where it is
* automatically styled (such as inside markdown) you can use this.
* Alternatively, you can wrap your content in div.prose which will apply the
* styles in CSS.
*
* @component
* @param {object} props - All component props
* @param {JSX.element} props.children - The component children
* @returns {JSX.Element}
*/
export const H2 = ({ children }) => (
<h2 className="tw:text-3xl tw:pt-4 tw:pb-3 tw:font-black tw:tracking-tighter tw:m-0 tw:lg:text-4xl">
{children}
</h2>
)
/**
* A component to renderd a styled H3 tag.
*
* Because of the TailwindCSS reset, common tags are unstyled.
* When you need to render a H3 tag outside of a context where it is
* automatically styled (such as inside markdown) you can use this.
* Alternatively, you can wrap your content in div.prose which will apply the
* styles in CSS.
*
* @component
* @param {object} props - All component props
* @param {JSX.element} props.children - The component children
* @returns {JSX.Element}
*/
export const H3 = ({ children }) => (
<h3 className="tw:text-2xl tw:pt-3 tw:pb-2 tw:font-extrabold tw:m-0 tw:tracking-tighter tw:lg:text-3xl">
{children}
</h3>
)
/**
* A component to renderd a styled H4 tag.
*
* Because of the TailwindCSS reset, common tags are unstyled.
* When you need to render a H4 tag outside of a context where it is
* automatically styled (such as inside markdown) you can use this.
* Alternatively, you can wrap your content in div.prose which will apply the
* styles in CSS.
*
* @component
* @param {object} props - All component props
* @param {JSX.element} props.children - The component children
* @returns {JSX.Element}
*/
export const H4 = ({ children }) => (
<h4 className="tw:text-xl tw:pt-2 tw:pb-1 tw:font-bold tw:m-0 tw:tracking-tighter tw:lg:text-2xl">
{children}
</h4>
)
/**
* A component to renderd a styled H5 tag.
*
* Because of the TailwindCSS reset, common tags are unstyled.
* When you need to render a H5 tag outside of a context where it is
* automatically styled (such as inside markdown) you can use this.
* Alternatively, you can wrap your content in div.prose which will apply the
* styles in CSS.
*
* @component
* @param {object} props - All component props
* @param {JSX.element} props.children - The component children
* @returns {JSX.Element}
*/
export const H5 = ({ children }) => (
<h5 className="tw:text-lg tw:py-1 tw:font-semibold tw:m-0 tw:tracking-tight tw:lg:text-xl">
{children}
</h5>
)
/**
* A component to renderd a styled H6 tag.
*
* Because of the TailwindCSS reset, common tags are unstyled.
* When you need to render a H6 tag outside of a context where it is
* automatically styled (such as inside markdown) you can use this.
* Alternatively, you can wrap your content in div.prose which will apply the
* styles in CSS.
*
* @component
* @param {object} props - All component props
* @param {JSX.element} props.children - The component children
* @returns {JSX.Element}
*/
export const H6 = ({ children }) => (
<h6 className="tw:text-base tw:py-1 tw:font-medium tw:italic tw:m-0 tw:tracking-tight tw:lg:text-lg">
{children}

View file

@ -0,0 +1,154 @@
import React from 'react'
// Components
import { ModalWrapper } from '@freesewing/react/components/Modal'
/*
* A component to display an iframe intended for a modal window.
*
* All props are passed down to the iframe tag.
*
* @component
* @param {object} props - All component props
* @returns {JSX.Element}
*/
const Iframe = (props) => (
<iframe
{...props}
style={{ height: '90vh', width: '90vw' }}
className="tw:w-full tw:mx-auto tw:max-w-4xl"
/>
)
/*
* A component to display an iframe with FreeSewing.eu docs content intended for a modal window.
*
* @component
* @param {object} props - All component props
* @param {string} props.path - The (relative) URL path of the page to load
* @returns {JSX.Element}
*/
const DocsHelp = ({ path }) => (
<Iframe src={`https://freesewing.eu/${path}/?docusaurus-data-fs-embed=true`} />
)
/*
* A component to display inline help for a design option
*
* This is typically loaded as modal content as it returns an iframe.
*
* @component
* @param {object} props - All component props
* @param {string} props.design - The design name
* @param {string} props.o - The option ID
* @returns {JSX.Element}
*/
const DesignOptionHelp = ({ design, o }) =>
design && o ? (
<Iframe
src={`https://freesewing.eu/docs/designs/${design.toLowerCase()}/options/${o.toLowerCase()}/index.html?docusaurus-data-fs-embed=true`}
title="Design Options Help"
/>
) : (
<p>Invalid props provided to DesignOptionHelp.</p>
)
/*
* A component to display inline help for a core setting
*
* This is typically loaded as modal content as it returns an iframe.
*
* @component
* @param {object} props - All component props
* @param {string} props.name - The core setting name/id
* @returns {JSX.Element}
*/
const CoreSettingHelp = ({ name }) =>
name ? (
<Iframe
src={`https://freesewing.eu/docs/editor/menus/settings/${name.toLowerCase()}/index.html?docusaurus-data-fs-embed=true`}
title="Core Setting Help"
/>
) : (
<p>Invalid props provided to CoreSettingsHelp.</p>
)
/*
* A component to display inline help for a UI preference
*
* This is typically loaded as modal content as it returns an iframe.
*
* @component
* @param {object} props - All component props
* @param {string} props.name - The core setting name/id
* @returns {JSX.Element}
*/
const UiPreferenceHelp = ({ name }) =>
name ? (
<Iframe
src={`https://freesewing.eu/docs/editor/menus/preferences/${name.toLowerCase()}/index.html?docusaurus-data-fs-embed=true`}
title="UI Preferences Help"
/>
) : (
<p>Invalid props provided to UiPreferenceHelp.</p>
)
/*
* A component to display inline help for a measurement.
*
* This is typically loaded as modal content as it returns an iframe
*
* @component
* @param {object} props - All component props
* @param {string} [props.m = undefined] - The measurment name (id)
* @returns {JSX.Element}
*/
const MeasurementHelp = ({ m }) =>
m ? (
<iframe
src={`https://freesewing.eu/docs/measurements/${m.toLowerCase()}/index.html?docusaurus-data-fs-embed=true`}
title="Measurement Help"
style={{ height: '90vh', width: '90vw' }}
/>
) : (
<p>No measurement name provided in the m prop.</p>
)
export function modalCoreSettingHelp(name, setModal) {
setModal(
<ModalWrapper fullWidth keepOpenOnClick>
<CoreSettingHelp name={name} />
</ModalWrapper>
)
}
export function modalUiPreferenceHelp(name, setModal) {
setModal(
<ModalWrapper fullWidth keepOpenOnClick>
<UiPreferenceHelp name={name} />
</ModalWrapper>
)
}
export function modalDesignOptionHelp(design, o, setModal) {
setModal(
<ModalWrapper fullWidth keepOpenOnClick>
<DesignOptionHelp {...{ design, o }} />
</ModalWrapper>
)
}
export function modalMeasurementHelp(m, setModal) {
setModal(
<ModalWrapper fullWidth keepOpenOnClick>
<MeasurementHelp m={m} />
</ModalWrapper>
)
}
export function modalDocsHelp(path, setModal) {
setModal(
<ModalWrapper wide keepOpenOnClick>
<DocsHelp path={path} />
</ModalWrapper>
)
}

View file

@ -1,5 +1,5 @@
import React from 'react'
import { CopyToClipboardButton } from '@freesewing/react/components/CopyToClipboardButton'
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
const defaultTitles = {
js: 'Javascript',
@ -12,13 +12,17 @@ const defaultTitles = {
/**
* A React component to highlight code
*
* @params {object} props - All React props
* @params {string} language - The language to highlight
* @params {object} children - The React children
* @params {bool} raw - Set this to true to not escape tags
* @params {string} title - Title for the highlight
* @params {string} copy - Content to copy to clipboard
* @params {bool} noCopy - Do not add copy to clipboard
* @component
* @param {object} props - All React props
* @param {string} [props.language = 'txt'] - The language to highlight
* @param {JSX.Element} props.children - The React children (this is the code to highlight)
* @param {bool} [props.raw = false] - Set this to true to not escape tags
* @param {string} [props.title = false] - Title for the highlight. When
* language is js, bash, sh, json, or yaml the title will be set accordingly
* (but you can still pass in a custom one)
* @param {string} [props.copy = props.children] - Content for the copy to clipboard button, by default this will use props.children
* @param {bool} [props.noCopy = false] - Set this to true to not add the copy to clipboard button
* @returns {JSX.Element}
*/
export const Highlight = ({
language = 'txt',

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,15 @@ import React from 'react'
import { Highlight } from '@freesewing/react/components/Highlight'
import hljs from 'highlight.js/lib/common'
/**
* A component to code-highlight JSON data
*
* @component
* @param {object} props - All component props
* @param {object} [props.js = undefined] - An optional Javascript Object to highlight
* @param {JSX.Element} props.children - The component children, will be rendered if props.js is not set
* @returns {JSX.Element}
*/
export const Json = (props) => {
const code = props.js ? JSON.stringify(props.js, null, 2) : props.children

View file

@ -2,6 +2,19 @@ import React, { useState, useContext } from 'react'
import { copyToClipboard } from '@freesewing/utils'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
/**
* A component to display key/value pairs
*
* @component
* @param {object} props - All component props
* @param {boolean} [props.small = false] - Set this to render a small version
* @param {string} [props.color = primary] - The DaisyUI color to apply
* @param {boolean} [props.href = false] - Set this to make this a link
* @param {string} props.k - The key to display (key is a reserved react prop, so we use k)
* @param {boolean} [props.onClick = false] - Set this to make this a button
* @param {string} props.val - The value to display
* @returns {JSX.Element}
*/
export const KeyVal = ({
k,
val,
@ -10,7 +23,7 @@ export const KeyVal = ({
href = false,
onClick = false,
}) => {
const [copied, setCopied] = useState(false)
const setCopied = useState(false)[1]
const { setLoadingStatus } = useContext(LoadingStatusContext)
let colorClasses1 = primaryClasses1
@ -25,6 +38,7 @@ export const KeyVal = ({
if (color === 'secondary') colorClasses2 = secondaryClasses2
else if (color === 'neutral') colorClasses2 = neutralClasses2
else if (color === 'accent') colorClasses2 = accentClasses2
else if (color === 'info') colorClasses2 = infoClasses2
else if (color === 'warning') colorClasses2 = warningClasses2
else if (color === 'success') colorClasses2 = successClasses2
else if (color === 'error') colorClasses2 = errorClasses2
@ -69,7 +83,6 @@ export const KeyVal = ({
const LinkKeyVal = ({
k,
val,
color = 'primary',
small = false,
href = false,
colorClasses1,
@ -120,21 +133,6 @@ const successClasses2 = `tw:text-success tw:border-success`
const errorClasses1 = `tw:text-error-content tw:bg-error tw:border-error`
const errorClasses2 = `tw:text-error tw:border-error`
const PrimarySpans = ({ small, k, val }) => (
<>
<span
className={`${sharedClasses} ${small ? 'tw:rounded-l' : 'tw:rounded-l-lg'} ${primaryClasses} ${small ? 'tw:text-xs' : ''}`}
>
{k}
</span>
<span
className={`${sharedClasses} ${small ? 'tw:rounded-r' : 'tw:rounded-r-lg'} ${primaryClasses} ${small ? 'tw:text-xs' : ''}`}
>
{val}
</span>
</>
)
const handleCopied = (setCopied, setLoadingStatus, label) => {
setCopied(true)
setLoadingStatus([

View file

@ -2,17 +2,18 @@ import React from 'react'
import { Breadcrumbs } from '@freesewing/react/components/Breadcrumbs'
import { Link as DefaultLink } from '@freesewing/react/components/Link'
/*
/**
* This is the default layout, including title and breadcrumbs
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @param {array} props.crumbs - Data for the breadcrumbs
* @param {string} props.description - The page description
* @param {function} props.Link - An optional framework specific Link component
* @component
* @param {object} props - All component props
* @param {function} [props.Link = false] - An optional framework specific Link component
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @param {array} [props.crumbs = []] - Data for the breadcrumbs, see Breadcrumbs
* @param {string} props.title - The page title
* @returns {JSX.Element}
*/
export const Layout = ({ children = [], crumbs = [], description, Link = false, title }) => {
export const Layout = ({ children = [], crumbs = [], Link = false, title }) => {
if (!Link) Link = DefaultLink
return (
@ -26,11 +27,13 @@ export const Layout = ({ children = [], crumbs = [], description, Link = false,
)
}
/*
/**
* This is the base layout
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @component
* @param {object} props - All component props
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @returns {JSX.Element}
*/
export const BaseLayout = ({ children }) => (
<div className="tw:flex tw:flex-row tw:items-start tw:w-full tw:justify-between tw:2xl:px-36 tw:xl:px-12 tw:px-4 tw:gap-0 tw:lg:gap-4 tw:xl:gap-8 3xl:tw:gap-12">
@ -38,11 +41,13 @@ export const BaseLayout = ({ children }) => (
</div>
)
/*
/**
* The left column of the base layout
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @component
* @param {object} props - All component props
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @returns {JSX.Element}
*/
export const BaseLayoutLeft = ({ children = [] }) => (
<div className="tw:max-w-96 tw:w-1/4 tw:hidden tw:lg:block tw:shrink-0 tw:my-8 tw:sticky tw:top-4 tw:max-h-screen tw:overflow-scroll">
@ -50,11 +55,13 @@ export const BaseLayoutLeft = ({ children = [] }) => (
</div>
)
/*
/**
* The right column of the base layout
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @component
* @param {object} props - All component props
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @returns {JSX.Element}
*/
export const BaseLayoutRight = ({ children = [] }) => (
<div className="tw:max-w-96 tw:w-1/4 tw:hidden tw:xl:block tw:my-8 tw:sticky tw:top-2">
@ -62,12 +69,14 @@ export const BaseLayoutRight = ({ children = [] }) => (
</div>
)
/*
/**
* The main column for prose (text like docs and so on)
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @param {array} props.wide - Whether or not to use the wide view
* @component
* @param {object} props - All component props
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @param {boolean} [props.wide false] - Set this to true to render a full-width prose layout
* @returns {JSX.Element}
*/
export const BaseLayoutProse = ({ children = [], wide = false }) => (
<div className={`tw:grow tw:w-full tw:m-auto tw:max-w-${wide ? 'full' : 'prose'} tw:my-8`}>
@ -75,21 +84,25 @@ export const BaseLayoutProse = ({ children = [], wide = false }) => (
</div>
)
/*
/**
* The central column for wide content (no max-width)
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @component
* @param {object} props - All component props
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @returns {JSX.Element}
*/
export const BaseLayoutWide = ({ children = [] }) => (
<div className="tw:grow tw:w-full tw:m-auto tw:my-8 tw:grow">{children}</div>
)
/*
/**
* A layout for pages that do their own title/layout, like the sign in page
*
* @param {object} props - All React props
* @param {array} props.children - The content to go in the layout
* @component
* @param {object} props - All component props
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @returns {JSX.Element}
*/
export const NoTitleLayout = ({ children }) => {
return (

View file

@ -10,9 +10,11 @@ const strokeScale = 0.5
/**
* A linedrawing component for Aaron
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const Aaron = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="-2 -15 152 152" {...{ stroke, className }}>
@ -24,9 +26,11 @@ export const Aaron = ({ className, stroke = 1 }) => (
/**
* A linedrawing component for the front of Aaron
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const AaronFront = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="-23 0 119 119" {...{ className }}>
@ -37,9 +41,11 @@ export const AaronFront = ({ className, stroke = 1 }) => (
/**
* A linedrawing component for the front of Aaron
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const AaronBack = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="51 0 119 119" {...{ className }}>
@ -50,7 +56,7 @@ export const AaronBack = ({ className, stroke = 1 }) => (
/*
* SVG elements for the front
*/
export const Front = ({ stroke }) => (
const Front = ({ stroke }) => (
<>
<path
key="stitches"

View file

@ -10,9 +10,11 @@ const strokeScale = 0.5
/**
* A linedrawing component for Albert
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const Albert = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="-10 -2 146 146" {...{ className }}>
@ -24,9 +26,11 @@ export const Albert = ({ className, stroke = 1 }) => (
/**
* A linedrawing component for the front of Albert
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const AlbertFront = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="-41 -2 145 145" {...{ className }}>
@ -37,12 +41,14 @@ export const AlbertFront = ({ className, stroke = 1 }) => (
/**
* A linedrawing component for the front of Albert
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const AlbertBack = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="74 0 74 119" {...props}>
<LineDrawingWrapper viewBox="74 0 74 119" {...{ className, stroke }}>
<Back stroke={stroke * strokeScale} />
</LineDrawingWrapper>
)
@ -50,7 +56,7 @@ export const AlbertBack = ({ className, stroke = 1 }) => (
/*
* SVG elements for the front
*/
export const Front = ({ stroke }) => (
const Front = ({ stroke }) => (
<>
<path
key="stitches"

View file

@ -10,9 +10,11 @@ const strokeScale = 0.4
/**
* A linedrawing component for Bee
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const Bee = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="-20 0 120 120" {...{ stroke, className }}>
@ -21,14 +23,20 @@ export const Bee = ({ className, stroke = 1 }) => (
)
/**
* Front is the same
* A linedrawing component for the front of Bee
*
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const BeeFront = Bee
/*
* SVG elements for the front
*/
export const Front = ({ stroke }) => (
const Front = ({ stroke }) => (
<>
<path
key="stitches"

View file

@ -10,9 +10,11 @@ const strokeScale = 0.5
/**
* A linedrawing component for Bella
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const Bella = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="2 -30 155 155" {...{ stroke, className }}>
@ -24,9 +26,11 @@ export const Bella = ({ className, stroke = 1 }) => (
/**
* A linedrawing component for the front of Bella
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const BellaFront = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="-4 2 89 86" {...{ className, stroke }}>
@ -34,8 +38,14 @@ export const BellaFront = ({ className, stroke = 1 }) => (
</LineDrawingWrapper>
)
/*
* React component for the back
/**
* A linedrawing component for the back of Bella
*
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const BellaBack = ({
className = 'tw:w-full', // CSS classes to apply
@ -54,7 +64,7 @@ export const BellaBack = ({
/*
* SVG elements for the front
*/
export const Front = ({ stroke }) => (
const Front = ({ stroke }) => (
<>
<path
key="stitches"

View file

@ -10,9 +10,11 @@ const strokeScale = 1.2
/**
* A linedrawing component for Benjamin
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const Benjamin = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="0 -44 138 138" {...{ className, stroke }}>
@ -20,15 +22,21 @@ export const Benjamin = ({ className, stroke = 1 }) => (
</LineDrawingWrapper>
)
/*
* Front is the same
/**
* A linedrawing component for the front of Benjamin
*
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const BenjaminFront = Benjamin
/*
* SVG elements for the front
*/
export const Front = ({ stroke }) => (
const Front = ({ stroke }) => (
<>
<path
key="folds"

View file

@ -10,12 +10,14 @@ const strokeScale = 0.7
/**
* A linedrawing component for Bent
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const Bent = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="0 -70 210 210" {...{ stroke }}>
<LineDrawingWrapper viewBox="0 -70 210 210" {...{ className, stroke }}>
<Front stroke={strokeScale * stroke} />
<Back stroke={strokeScale * stroke} />
</LineDrawingWrapper>
@ -24,9 +26,11 @@ export const Bent = ({ className, stroke = 1 }) => (
/**
* A linedrawing component for the front of Bent
*
* @param {object} props - All React props
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const BentFront = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="0 -1 103 103" {...{ className, stroke }}>
@ -34,8 +38,14 @@ export const BentFront = ({ className, stroke = 1 }) => (
</LineDrawingWrapper>
)
/*
* React component for the back
/**
* A linedrawing component for the back of Bent
*
* @component
* @param {object} props - All component props
* @param {string} props.className - Any CSS classes to apply
* @param {number} props.stroke - The stroke width to apply
* @returns {JSX.Element}
*/
export const BentBack = ({
className = 'tw:w-full', // CSS classes to apply
@ -54,7 +64,7 @@ export const BentBack = ({
/*
* SVG elements for the front
*/
export const Front = ({ stroke }) => (
const Front = ({ stroke }) => (
<>
<path
key="stitches"

Some files were not shown because too many files have changed in this diff Show more