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:
commit
94d3f4c09f
457 changed files with 17818 additions and 8053 deletions
|
@ -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
24
eslint.config.mjs
Normal 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
4277
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ? (
|
||||
<>
|
||||
|
|
|
@ -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" />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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/"
|
||||
>
|
||||
<PlusIcon />
|
||||
Create a new pattern
|
||||
<span className="tw:text-primary-content">
|
||||
<PlusIcon />
|
||||
</span>
|
||||
<span className="tw:text-primary-content">Create a new pattern</span>
|
||||
</Link>
|
||||
</div>
|
||||
<TableWrapper>
|
||||
|
|
|
@ -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" />
|
||||
|
||||
/*
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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'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('')
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -8,9 +8,6 @@ import {
|
|||
CompareIcon,
|
||||
DocsIcon,
|
||||
UserIcon,
|
||||
LeftIcon,
|
||||
OkIcon,
|
||||
NoIcon,
|
||||
ShowcaseIcon,
|
||||
} from '@freesewing/react/components/Icon'
|
||||
|
||||
|
|
|
@ -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 />
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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) => (
|
||||
<li key={i} className="tw:inline">
|
||||
<Link href={crumb.href}>{crumb.label}</Link>
|
||||
</li>
|
||||
<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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'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>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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} />
|
||||
|
|
|
@ -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} />
|
||||
),
|
||||
}
|
||||
|
|
|
@ -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 }} />
|
||||
|
|
|
@ -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`,
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
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"
|
||||
/>
|
||||
)
|
||||
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>
|
||||
|
|
|
@ -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,30 +33,66 @@ 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>
|
||||
<SideMenuUl>
|
||||
<DesignOptionsMenu {...props} />
|
||||
</SideMenuUl>
|
||||
<h5>Core Settings</h5>
|
||||
<SideMenuUl>
|
||||
<CoreSettingsMenu {...props} />
|
||||
</SideMenuUl>
|
||||
<h5>UI Preferences</h5>
|
||||
<SideMenuUl>
|
||||
<UiPreferencesMenu {...props} />
|
||||
</SideMenuUl>
|
||||
</div>
|
||||
) : null}
|
||||
<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>
|
||||
<h5>Core Settings</h5>
|
||||
<SideMenuUl>
|
||||
<CoreSettingsMenu {...props} />
|
||||
</SideMenuUl>
|
||||
<h5>UI Preferences</h5>
|
||||
<SideMenuUl>
|
||||
<UiPreferencesMenu {...props} />
|
||||
</SideMenuUl>
|
||||
</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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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}
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -121,6 +121,7 @@ export const DesignOption = ({ config, settings, ux, inputs, values, ...rest })
|
|||
|
||||
return (
|
||||
<MenuItem
|
||||
type="designOption"
|
||||
{...{
|
||||
config,
|
||||
ux,
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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,
|
||||
}) => {
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
]}
|
||||
/>
|
||||
)
|
||||
*/
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
</Popout>
|
||||
</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>
|
||||
<a
|
||||
className={linkClasses}
|
||||
href="https://freesewing.eu/docs/about/editor"
|
||||
>{`FreeSewing.eu/docs/about/editor`}</a>
|
||||
</b>
|
||||
</p>
|
||||
<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/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{' '}
|
||||
<b>
|
||||
<a className={linkClasses} href="https://freesewing.dev/">{`FreeSewing.dev`}</a>
|
||||
</b>
|
||||
</p>
|
||||
<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>
|
||||
</Popout>
|
||||
</div>
|
||||
{modalContent}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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'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>
|
||||
|
|
|
@ -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}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 })}
|
||||
>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,33 +108,33 @@ 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>
|
||||
<span className="tw:opacity-70 tw:flex tw:flex-row tw:gap-1 tw:items-center tw:text-base">
|
||||
{data.icon || null} {data.menu}
|
||||
</span>
|
||||
</div>
|
||||
<div className="tw:flex tw:flex-row tw:gap-1 tw:items-center tw:align-start tw:w-full">
|
||||
{data.msg ? (
|
||||
data.msg
|
||||
) : (
|
||||
<>
|
||||
<span className="">
|
||||
{Array.isArray(data.newVal) ? data.newVal.join(', ') : data.newVal}
|
||||
</span>
|
||||
<LeftIcon className="tw:w-4 tw:h-4 tw:text-secondary tw:shrink-0" stroke={4} />
|
||||
<span className="tw:line-through tw:decoration-1 tw:opacity-70">
|
||||
{Array.isArray(data.oldVal) ? data.oldVal.join(', ') : data.oldVal}
|
||||
</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.menu}
|
||||
</span>
|
||||
<span>»</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 ? (
|
||||
data.msg
|
||||
) : (
|
||||
<>
|
||||
<span className="">
|
||||
{Array.isArray(data.newVal) ? data.newVal.join(', ') : data.newVal}
|
||||
</span>
|
||||
<LeftIcon className="tw:w-4 tw:h-4 tw:text-secondary tw:shrink-0" stroke={4} />
|
||||
<span className="tw:line-through tw:decoration-1 tw:opacity-70">
|
||||
{Array.isArray(data.oldVal) ? data.oldVal.join(', ') : data.oldVal}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ButtonFrame>
|
||||
</>
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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'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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -28,7 +28,6 @@ import {
|
|||
menuValueWasChanged,
|
||||
noop,
|
||||
notEmpty,
|
||||
nsMerge,
|
||||
objUpdate,
|
||||
sample,
|
||||
settingsValueIsCustom,
|
||||
|
@ -91,7 +90,6 @@ export {
|
|||
menuValueWasChanged,
|
||||
noop,
|
||||
notEmpty,
|
||||
nsMerge,
|
||||
objUpdate,
|
||||
sample,
|
||||
settingsValueIsCustom,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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}
|
||||
|
|
154
packages/react/components/Help/index.mjs
Normal file
154
packages/react/components/Help/index.mjs
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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
|
@ -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
|
||||
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue