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/Echart": "./components/Echart/index.mjs"
|
||||||
"./components/Editor": "./components/Editor/index.mjs"
|
"./components/Editor": "./components/Editor/index.mjs"
|
||||||
"./components/Heading": "./components/Heading/index.mjs"
|
"./components/Heading": "./components/Heading/index.mjs"
|
||||||
|
"./components/Help": "./components/Help/index.mjs"
|
||||||
"./components/Highlight": "./components/Highlight/index.mjs"
|
"./components/Highlight": "./components/Highlight/index.mjs"
|
||||||
"./components/Icon": "./components/Icon/index.mjs"
|
"./components/Icon": "./components/Icon/index.mjs"
|
||||||
"./components/Input": "./components/Input/index.mjs"
|
"./components/Input": "./components/Input/index.mjs"
|
||||||
|
@ -124,6 +125,7 @@ packageJson:
|
||||||
"./hooks/useControl": "./hooks/useControl/index.mjs"
|
"./hooks/useControl": "./hooks/useControl/index.mjs"
|
||||||
"./hooks/useDesign": "./hooks/useDesign/index.mjs"
|
"./hooks/useDesign": "./hooks/useDesign/index.mjs"
|
||||||
"./hooks/useDesignTranslation": "./hooks/useDesignTranslation/index.mjs"
|
"./hooks/useDesignTranslation": "./hooks/useDesignTranslation/index.mjs"
|
||||||
|
"./hooks/useFilter": "./hooks/useFilter/index.mjs"
|
||||||
"./hooks/useSelection": "./hooks/useSelection/index.mjs"
|
"./hooks/useSelection": "./hooks/useSelection/index.mjs"
|
||||||
"./hooks/useStateObject": "./hooks/useStateObject/index.mjs"
|
"./hooks/useStateObject": "./hooks/useStateObject/index.mjs"
|
||||||
# Lib
|
# 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": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^19.0.3",
|
"@commitlint/cli": "^19.0.3",
|
||||||
"@commitlint/config-conventional": "^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",
|
"@nx/eslint": "20.2.1",
|
||||||
"all-contributors-cli": "^6.26.1",
|
"all-contributors-cli": "^6.26.1",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"eslint": "^8.23.1",
|
"eslint": "^9.27.0",
|
||||||
"eslint-plugin-jsonc": "^2.4.0",
|
"eslint-plugin-jsonc": "^2.4.0",
|
||||||
"eslint-plugin-markdown": "^5.0.0",
|
"eslint-plugin-markdown": "^5.0.0",
|
||||||
"eslint-plugin-mongo": "^1.0.5",
|
"eslint-plugin-mongo": "^1.0.5",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"eslint-plugin-yaml": "^0.5.0",
|
"eslint-plugin-yaml": "^0.5.0",
|
||||||
"execa": "^9.3.1",
|
"execa": "^9.3.1",
|
||||||
|
"globals": "^16.2.0",
|
||||||
"husky": "^9.0.10",
|
"husky": "^9.0.10",
|
||||||
"js-yaml": "^4.0.0",
|
"js-yaml": "^4.0.0",
|
||||||
"lerna": "^8.0.0",
|
"lerna": "^8.0.0",
|
||||||
|
@ -97,7 +103,6 @@
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.22.5",
|
"@babel/plugin-syntax-import-assertions": "^7.22.5",
|
||||||
"@babel/preset-react": "^7.22.15",
|
"@babel/preset-react": "^7.22.15",
|
||||||
"c8": "^10.1.2",
|
"c8": "^10.1.2",
|
||||||
"eslint-config-next": "^14.0.1",
|
|
||||||
"glob": "^11.0.1",
|
"glob": "^11.0.1",
|
||||||
"rehype-format": "^5.0.0",
|
"rehype-format": "^5.0.0",
|
||||||
"rehype-stringify": "^10.0.1",
|
"rehype-stringify": "^10.0.1",
|
||||||
|
|
|
@ -1,58 +1,45 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
import orderBy from 'lodash/orderBy.js'
|
import orderBy from 'lodash/orderBy.js'
|
||||||
import { capitalize, shortDate } from '@freesewing/utils'
|
import { shortDate } from '@freesewing/utils'
|
||||||
import { apikeyLevels } from '@freesewing/config'
|
import { apikeyLevels } from '@freesewing/config'
|
||||||
// Context
|
// Context
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useEffect, useContext } from 'react'
|
import React, { useState, useEffect, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
import { useSelection } from '@freesewing/react/hooks/useSelection'
|
import { useSelection } from '@freesewing/react/hooks/useSelection'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { TableWrapper } from '@freesewing/react/components/Table'
|
import { TableWrapper } from '@freesewing/react/components/Table'
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import {
|
import { PlusIcon, RightIcon, TrashIcon } from '@freesewing/react/components/Icon'
|
||||||
BoolNoIcon,
|
|
||||||
BoolYesIcon,
|
|
||||||
PlusIcon,
|
|
||||||
RightIcon,
|
|
||||||
TrashIcon,
|
|
||||||
} from '@freesewing/react/components/Icon'
|
|
||||||
import { Uuid } from '@freesewing/react/components/Uuid'
|
import { Uuid } from '@freesewing/react/components/Uuid'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
||||||
import { NumberCircle } from '@freesewing/react/components/Number'
|
import { NumberCircle } from '@freesewing/react/components/Number'
|
||||||
import { StringInput, FormControl, ListInput } from '@freesewing/react/components/Input'
|
import { StringInput, Fieldset, ListInput } from '@freesewing/react/components/Input'
|
||||||
import { DisplayRow } from './shared.mjs'
|
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
|
||||||
import { CopyToClipboardButton } from '@freesewing/react/components/CopyToClipboardButton'
|
|
||||||
import { TimeAgo, TimeToGo } from '@freesewing/react/components/Time'
|
import { TimeAgo, TimeToGo } from '@freesewing/react/components/Time'
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
||||||
|
|
||||||
const t = (input) => {
|
|
||||||
console.log('t called', input)
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields = {
|
const fields = {
|
||||||
id: 'Key',
|
id: 'Key',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
calls: 'Calls',
|
calls: 'Calls',
|
||||||
level: 'Level',
|
level: 'Level',
|
||||||
level: 'Level',
|
|
||||||
createdAt: 'Created',
|
createdAt: 'Created',
|
||||||
expiresAt: 'Expires',
|
expiresAt: 'Expires',
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Component for the account/apikeys page
|
* A component to mange the user's API keys
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {function} Link - A framework specific Link component for client-side routing
|
* @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 }) => {
|
export const Apikeys = ({ Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -240,13 +227,6 @@ const NewApikey = ({ onCreate = false }) => {
|
||||||
} else setLoadingStatus([true, 'An error occured. Please report this', true, false])
|
} else setLoadingStatus([true, 'An error occured. Please report this', true, false])
|
||||||
}
|
}
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
setApikey(false)
|
|
||||||
setGenerate(false)
|
|
||||||
setName('')
|
|
||||||
setLevel(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw:w-full">
|
<div className="tw:w-full">
|
||||||
<h2>New API key {apikey ? `: ${apikey.name}` : ''}</h2>
|
<h2>New API key {apikey ? `: ${apikey.name}` : ''}</h2>
|
||||||
|
@ -311,7 +291,7 @@ const ShowNewApikey = ({ apikey }) => (
|
||||||
<CopyToClipboardButton sup content={apikey.secret} label="API key secret" />
|
<CopyToClipboardButton sup content={apikey.secret} label="API key secret" />
|
||||||
</h6>
|
</h6>
|
||||||
<pre>{apikey.secret}</pre>
|
<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.
|
This is the only time you can see the key secret, make sure to copy it.
|
||||||
</Popout>
|
</Popout>
|
||||||
</div>
|
</div>
|
||||||
|
@ -331,7 +311,7 @@ const ExpiryPicker = ({ expires, setExpires }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="tw:flex tw:flex-row tw:gap-2 tw:items-center">
|
<div className="tw:flex tw:flex-row tw:gap-2 tw:items-center">
|
||||||
<FormControl
|
<Fieldset
|
||||||
label="Key Expiry"
|
label="Key Expiry"
|
||||||
labelBL={shortDate(expires)}
|
labelBL={shortDate(expires)}
|
||||||
labelBR={<TimeToGo iso={expires} />}
|
labelBR={<TimeToGo iso={expires} />}
|
||||||
|
@ -344,7 +324,7 @@ const ExpiryPicker = ({ expires, setExpires }) => {
|
||||||
className="tw:daisy-range tw:daisy-range-secondary tw:w-full"
|
className="tw:daisy-range tw:daisy-range-secondary tw:w-full"
|
||||||
onChange={update}
|
onChange={update}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</Fieldset>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,14 @@ import { PassiveImageInput } from '@freesewing/react/components/Input'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { WelcomeIcons } from './shared.mjs'
|
import { WelcomeIcons } from './shared.mjs'
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Component for the account/bio page
|
* Component to manage the user's Avatar
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @param {object} props - All component props
|
||||||
* @params {function} props.Link - A framework specific Link component for client-side routing
|
* @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 }) => {
|
export const Avatar = ({ welcome = false, Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -98,7 +100,7 @@ export const Avatar = ({ welcome = false, Link = false }) => {
|
||||||
<>
|
<>
|
||||||
<p className="tw:text-right">
|
<p className="tw:text-right">
|
||||||
<button
|
<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}
|
onClick={save}
|
||||||
>
|
>
|
||||||
<SaveIcon /> Save Avatar
|
<SaveIcon /> Save Avatar
|
||||||
|
|
|
@ -16,12 +16,14 @@ import { MarkdownInput } from '@freesewing/react/components/Input'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { WelcomeIcons } from './shared.mjs'
|
import { WelcomeIcons } from './shared.mjs'
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Component for the account/bio page
|
* Component to manage the user's Bio
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @param {object} props - All component props
|
||||||
* @params {function} props.Link - A framework specific Link component for client-side routing
|
* @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 }) => {
|
export const Bio = ({ welcome = false, Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
// Components
|
// 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 { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
||||||
import { StringInput } from '@freesewing/react/components/Input'
|
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 = () => {
|
export const Bookmarks = () => {
|
||||||
// Hooks & Context
|
// Hooks & Context
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
const { setModal, clearModal } = useContext(ModalContext)
|
const { setModal } = useContext(ModalContext)
|
||||||
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
|
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
|
||||||
|
|
||||||
// State
|
// State
|
||||||
|
@ -178,7 +181,7 @@ export const Bookmarks = () => {
|
||||||
* @param {object} props - All the React props
|
* @param {object} props - All the React props
|
||||||
* @param {function} onCreated - An optional method to call when the bookmark is created
|
* @param {function} onCreated - An optional method to call when the bookmark is created
|
||||||
*/
|
*/
|
||||||
export const NewBookmark = ({ onCreated = false }) => {
|
const NewBookmark = ({ onCreated = false }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||||
const { clearModal } = useContext(ModalContext)
|
const { clearModal } = useContext(ModalContext)
|
||||||
|
@ -191,7 +194,7 @@ export const NewBookmark = ({ onCreated = false }) => {
|
||||||
// This method will create the bookmark
|
// This method will create the bookmark
|
||||||
const createBookmark = async () => {
|
const createBookmark = async () => {
|
||||||
setLoadingStatus([true, 'Processing update'])
|
setLoadingStatus([true, 'Processing update'])
|
||||||
const [status, body] = await backend.createBookmark({
|
const [status] = await backend.createBookmark({
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
type: 'custom',
|
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
|
* @component
|
||||||
* @params {string} props.href - The bookmark href
|
* @param {object} props - All component props
|
||||||
* @params {string} props.title - The bookmark title
|
* @param {string} props.slug - The bookmark slug/href
|
||||||
* @params {string} props.type - The bookmark type
|
* @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 }) => {
|
export const BookmarkButton = ({ slug, type, title }) => {
|
||||||
const { setModal } = useContext(ModalContext)
|
const { setModal } = useContext(ModalContext)
|
||||||
|
@ -279,19 +284,20 @@ export const BookmarkButton = ({ slug, type, title }) => {
|
||||||
/*
|
/*
|
||||||
* A component to create a bookmark, preloaded with props
|
* A component to create a bookmark, preloaded with props
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {string} props.href - The bookmark href
|
* @param {object} props - All component props
|
||||||
* @params {string} props.title - The bookmark title
|
* @param {string} props.href - The bookmark href
|
||||||
* @params {string} props.type - The bookmark type
|
* @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 backend = useBackend()
|
||||||
const [name, setName] = useState(title)
|
const [name, setName] = useState(title)
|
||||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||||
const { setModal } = useContext(ModalContext)
|
const { setModal } = useContext(ModalContext)
|
||||||
|
|
||||||
const url = `/${slug}`
|
const url = slug.toLowerCase().slice(0, 4) === 'http' ? slug : `/${slug}`
|
||||||
|
|
||||||
const bookmark = async (evt) => {
|
const bookmark = async (evt) => {
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { welcomeSteps } from './shared.mjs'
|
import { welcomeSteps } from './shared.mjs'
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { NoIcon, OkIcon, RightIcon } from '@freesewing/react/components/Icon'
|
||||||
import { NoIcon, OkIcon, SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
|
|
||||||
import { ListInput } from '@freesewing/react/components/Input'
|
import { ListInput } from '@freesewing/react/components/Input'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { WelcomeIcons } from './shared.mjs'
|
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
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @params {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Compare = ({ welcome = false }) => {
|
export const Compare = ({ welcome = false }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
|
@ -1,42 +1,23 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { welcomeSteps } from './shared.mjs'
|
import { navigate } from '@freesewing/utils'
|
||||||
import { linkClasses, navigate } from '@freesewing/utils'
|
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
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'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
|
|
||||||
const strings = {
|
/**
|
||||||
yes: {
|
* A component to manage the user's consent setting
|
||||||
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
|
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {bool} props.signUp - Set to true to use this component on the initial signUp
|
* @param {object} props - All component props
|
||||||
* @param {function} props.Link - An optional framework-specific Link component
|
* @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 }) => {
|
export const Consent = ({ signUp = false, Link = false, title = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -77,7 +58,7 @@ export const Consent = ({ signUp = false, Link = false, title = false }) => {
|
||||||
// Helper method to remove the account
|
// Helper method to remove the account
|
||||||
const removeAccount = async () => {
|
const removeAccount = async () => {
|
||||||
setLoadingStatus([true, 'One moment please'])
|
setLoadingStatus([true, 'One moment please'])
|
||||||
const [status, body] = await backend.removeAccount()
|
const [status] = await backend.removeAccount()
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
setLoadingStatus([true, 'All done, farewell', true, true])
|
setLoadingStatus([true, 'All done, farewell', true, true])
|
||||||
setToken(null)
|
setToken(null)
|
||||||
|
|
|
@ -1,48 +1,27 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { welcomeSteps } from './shared.mjs'
|
import { welcomeSteps } from './shared.mjs'
|
||||||
import { controlDesc } from '@freesewing/config'
|
import { controlDesc } from '@freesewing/config'
|
||||||
|
|
||||||
// Context
|
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
|
||||||
import { useControl } from '@freesewing/react/hooks/useControl'
|
import { useControl } from '@freesewing/react/hooks/useControl'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { RightIcon } from '@freesewing/react/components/Icon'
|
||||||
import { RightIcon, NoIcon, OkIcon, SaveIcon } from '@freesewing/react/components/Icon'
|
|
||||||
import { ListInput } from '@freesewing/react/components/Input'
|
import { ListInput } from '@freesewing/react/components/Input'
|
||||||
import { ControlScore } from '@freesewing/react/components/Control'
|
import { ControlScore } from '@freesewing/react/components/Control'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { WelcomeIcons } from './shared.mjs'
|
import { WelcomeIcons } from './shared.mjs'
|
||||||
|
|
||||||
const strings = {
|
/**
|
||||||
1: {
|
* A component to manage the user's control/UX setting
|
||||||
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
|
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @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 }) => {
|
export const Control = ({ welcome = false }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const { control, updateControl } = useControl()
|
const { control, setControl } = useControl()
|
||||||
|
|
||||||
// Helper to get the link to the next onboarding step
|
// Helper to get the link to the next onboarding step
|
||||||
const nextHref = welcome
|
const nextHref = welcome
|
||||||
|
@ -67,7 +46,7 @@ export const Control = ({ welcome = false }) => {
|
||||||
desc: controlDesc[val].desc,
|
desc: controlDesc[val].desc,
|
||||||
}))}
|
}))}
|
||||||
current={control}
|
current={control}
|
||||||
update={updateControl}
|
update={setControl}
|
||||||
/>
|
/>
|
||||||
{welcome ? (
|
{welcome ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { welcomeSteps } from './shared.mjs'
|
import { validateEmail, validateTld, getSearchParam, navigate } from '@freesewing/utils'
|
||||||
import { validateEmail, validateTld, getSearchParam } from '@freesewing/utils'
|
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext, useEffect } from 'react'
|
import React, { useState, useContext, useEffect } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import { SaveIcon } from '@freesewing/react/components/Icon'
|
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 { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { Spinner } from '@freesewing/react/components/Spinner'
|
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
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @param {object} props - All component props
|
||||||
* @params {function} props.Link - A framework specific Link component for client-side routing
|
* @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
|
if (!Link) Link = WebLink
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
|
@ -53,7 +50,7 @@ export const Email = ({ welcome = false, Link = false }) => {
|
||||||
return (
|
return (
|
||||||
<div className="tw:w-full">
|
<div className="tw:w-full">
|
||||||
{changed ? (
|
{changed ? (
|
||||||
<Popout note>
|
<Popout type="note">
|
||||||
<h3>Please confirm this change</h3>
|
<h3>Please confirm this change</h3>
|
||||||
<p>
|
<p>
|
||||||
We have sent an E-mail to your new address to confirm this change. Please click the link
|
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 }) => {
|
export const EmailChangeConfirmation = ({ onSuccess = false }) => {
|
||||||
// State
|
// State
|
||||||
const [error, setError] = useState(false)
|
const [error, setError] = useState(false)
|
||||||
|
@ -94,7 +99,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
|
||||||
const [check, setCheck] = useState()
|
const [check, setCheck] = useState()
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
const { setAccount, setToken } = useAccount()
|
const { setAccount } = useAccount()
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
|
@ -124,7 +129,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// If it works, store account, which runs the onSuccess handler
|
// 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 we get here, we're not sure what's wrong
|
||||||
if (body.error) return setError(body.error)
|
if (body.error) return setError(body.error)
|
||||||
return setError(true)
|
return setError(true)
|
||||||
|
@ -150,7 +155,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
|
||||||
if (!id || !check)
|
if (!id || !check)
|
||||||
return (
|
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" />
|
<Spinner className="tw:w-8 tw:h-8 tw:m-auto tw:animate-spin" />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import { DownloadIcon } from '@freesewing/react/components/Icon'
|
import { DownloadIcon } from '@freesewing/react/components/Icon'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
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 = () => {
|
export const Export = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
|
@ -36,7 +36,7 @@ export const Export = () => {
|
||||||
return (
|
return (
|
||||||
<div className="tw:max-w-xl">
|
<div className="tw:max-w-xl">
|
||||||
{link ? (
|
{link ? (
|
||||||
<Popout link>
|
<Popout type="link">
|
||||||
<h5>Your data was exported and is available for download at the following location:</h5>
|
<h5>Your data was exported and is available for download at the following location:</h5>
|
||||||
<p className="tw:text-lg">
|
<p className="tw:text-lg">
|
||||||
<WebLink href={link}>{link}</WebLink>
|
<WebLink href={link}>{link}</WebLink>
|
||||||
|
|
|
@ -10,8 +10,11 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
import { SaveIcon } from '@freesewing/react/components/Icon'
|
import { SaveIcon } from '@freesewing/react/components/Icon'
|
||||||
import { StringInput } from '@freesewing/react/components/Input'
|
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 = () => {
|
export const Github = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
|
@ -1,22 +1,16 @@
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState } from 'react'
|
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
|
|
||||||
// Components
|
/**
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
* A component to display the current user's ID
|
||||||
|
|
||||||
/*
|
|
||||||
* Component to display a user ID
|
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {function} props.Link - A framework specific Link component for client-side routing
|
* @param {object} props - All component props
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const UserId = ({ Link = false }) => {
|
export const UserId = () => {
|
||||||
if (!Link) Link = WebLink
|
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
const { account } = useAccount()
|
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
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// 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 { FileInput } from '@freesewing/react/components/Input'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { Yaml } from '@freesewing/react/components/Yaml'
|
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
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @returns {JSX.Element}
|
||||||
* @params {function} props.Link - A framework specific Link component for client-side routing
|
|
||||||
*/
|
*/
|
||||||
export const ImportSet = () => {
|
export const ImportSet = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
|
@ -42,7 +39,7 @@ export const ImportSet = () => {
|
||||||
if (set.measurements || set.measies) {
|
if (set.measurements || set.measies) {
|
||||||
const name = set.name || 'J. Doe'
|
const name = set.name || 'J. Doe'
|
||||||
setLoadingStatus([true, `Importing ${name}`])
|
setLoadingStatus([true, `Importing ${name}`])
|
||||||
const [status, body] = await backend.createSet({
|
const [status] = await backend.createSet({
|
||||||
name: set.name || 'J. Doe',
|
name: set.name || 'J. Doe',
|
||||||
units: set.units || 'metric',
|
units: set.units || 'metric',
|
||||||
notes: set.notes || '',
|
notes: set.notes || '',
|
||||||
|
@ -77,7 +74,7 @@ export const ImportSet = () => {
|
||||||
multiple: false,
|
multiple: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Popout tip>
|
<Popout type="tip">
|
||||||
<p>
|
<p>
|
||||||
To import a measurement set, you should have a JSON or YAML file that has the following
|
To import a measurement set, you should have a JSON or YAML file that has the following
|
||||||
structure:
|
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 { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
// Components
|
// 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 { ControlScore } from '@freesewing/react/components/Control'
|
||||||
import {
|
import {
|
||||||
MeasurementsSetIcon,
|
MeasurementsSetIcon,
|
||||||
|
@ -93,13 +93,13 @@ const titles = {
|
||||||
|
|
||||||
const YesNo = ({ check }) => (check ? <BoolYesIcon /> : <BoolNoIcon />)
|
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
|
* @component
|
||||||
* @param {function} Link - A custom Link component, typically the Docusaurus one, but it's optional
|
* @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 }) => {
|
export const Links = ({ Link = false }) => {
|
||||||
// Use custom Link component if available
|
// Use custom Link component if available
|
||||||
|
|
|
@ -1,30 +1,26 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { welcomeSteps } from './shared.mjs'
|
|
||||||
import { horFlexClasses } from '@freesewing/utils'
|
import { horFlexClasses } from '@freesewing/utils'
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
|
||||||
import { NoIcon, LockIcon } from '@freesewing/react/components/Icon'
|
import { NoIcon, LockIcon } from '@freesewing/react/components/Icon'
|
||||||
import { PasswordInput } from '@freesewing/react/components/Input'
|
import { PasswordInput } from '@freesewing/react/components/Input'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { NumberCircle } from '@freesewing/react/components/Number'
|
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
|
||||||
import { CopyToClipboardButton } from '@freesewing/react/components/CopyToClipboardButton'
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Component for the account/security/password page
|
* A component to manage the user's MFA settings
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @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
|
// Hooks
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
const { account, setAccount } = useAccount()
|
const { account, setAccount } = useAccount()
|
||||||
|
@ -199,7 +195,7 @@ export const Mfa = ({ welcome = false, title = true }) => {
|
||||||
<LockIcon />
|
<LockIcon />
|
||||||
Set up Mult-Factor Authentication
|
Set up Mult-Factor Authentication
|
||||||
</button>
|
</button>
|
||||||
<Popout tip>
|
<Popout type="tip">
|
||||||
<h5>Please consider enabling Two-Factor Authentication</h5>
|
<h5>Please consider enabling Two-Factor Authentication</h5>
|
||||||
<p>
|
<p>
|
||||||
We do not enforce a password policy, but we do recommend you enable Multi-Factor
|
We do not enforce a password policy, but we do recommend you enable Multi-Factor
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { welcomeSteps } from './shared.mjs'
|
import { welcomeSteps } from './shared.mjs'
|
||||||
import { linkClasses } from '@freesewing/utils'
|
import { linkClasses } from '@freesewing/utils'
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
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 { ListInput } from '@freesewing/react/components/Input'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { WelcomeIcons } from './shared.mjs'
|
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
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @params {object} props - All Component props
|
||||||
* @param {function} props.Link - An optional framework-specific Link component
|
* @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 }) => {
|
export const Newsletter = ({ welcome = false, Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -109,7 +108,7 @@ export const Newsletter = ({ welcome = false, Link = false }) => {
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{welcome ? null : (
|
{welcome ? null : (
|
||||||
<Popout tip>
|
<Popout type="tip">
|
||||||
<h5>You can unsubscribe at any time with the link below</h5>
|
<h5>You can unsubscribe at any time with the link below</h5>
|
||||||
<p>
|
<p>
|
||||||
This unsubscribe link will also be included at the bottom of every newsletter we send
|
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
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import { RightIcon, SaveIcon } from '@freesewing/react/components/Icon'
|
import { RightIcon, SaveIcon } from '@freesewing/react/components/Icon'
|
||||||
import { PasswordInput } from '@freesewing/react/components/Input'
|
import { PasswordInput } from '@freesewing/react/components/Input'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
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
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @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
|
if (!Link) Link = WebLink
|
||||||
// Hooks
|
// Hooks
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
|
@ -60,7 +57,7 @@ export const Password = ({ welcome = false, Link = false }) => {
|
||||||
<SaveIcon /> Save
|
<SaveIcon /> Save
|
||||||
</button>
|
</button>
|
||||||
{!account.mfaEnabled && (
|
{!account.mfaEnabled && (
|
||||||
<Popout tip>
|
<Popout type="tip">
|
||||||
<h5>Please consider enabling Two-Factor Authentication</h5>
|
<h5>Please consider enabling Two-Factor Authentication</h5>
|
||||||
<p>
|
<p>
|
||||||
We do not enforce a password policy, but we do recommend you enable Two-Factor
|
We do not enforce a password policy, but we do recommend you enable Two-Factor
|
||||||
|
|
|
@ -1,25 +1,13 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import orderBy from 'lodash/orderBy.js'
|
import { cloudflareImageUrl, horFlexClasses, patternUrlFromState } from '@freesewing/utils'
|
||||||
import {
|
|
||||||
cloudflareImageUrl,
|
|
||||||
capitalize,
|
|
||||||
shortDate,
|
|
||||||
horFlexClasses,
|
|
||||||
newPatternUrl,
|
|
||||||
patternUrlFromState,
|
|
||||||
} from '@freesewing/utils'
|
|
||||||
import { urls, control as controlConfig } from '@freesewing/config'
|
import { urls, control as controlConfig } from '@freesewing/config'
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useEffect, useContext } from 'react'
|
import React, { useState, useEffect, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
import { useSelection } from '@freesewing/react/hooks/useSelection'
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Markdown from 'react-markdown'
|
import Markdown from 'react-markdown'
|
||||||
import {
|
import {
|
||||||
|
@ -28,7 +16,7 @@ import {
|
||||||
PassiveImageInput,
|
PassiveImageInput,
|
||||||
ListInput,
|
ListInput,
|
||||||
} from '@freesewing/react/components/Input'
|
} 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 {
|
import {
|
||||||
BoolNoIcon,
|
BoolNoIcon,
|
||||||
BoolYesIcon,
|
BoolYesIcon,
|
||||||
|
@ -42,12 +30,20 @@ import {
|
||||||
ResetIcon,
|
ResetIcon,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
} from '@freesewing/react/components/Icon'
|
} from '@freesewing/react/components/Icon'
|
||||||
import { DisplayRow } from './shared.mjs'
|
|
||||||
import { TimeAgo } from '@freesewing/react/components/Time'
|
import { TimeAgo } from '@freesewing/react/components/Time'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
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 }) => {
|
export const Pattern = ({ id, Link }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
// Hooks
|
// Hooks
|
||||||
|
@ -137,7 +133,7 @@ export const Pattern = ({ id, Link }) => {
|
||||||
return (
|
return (
|
||||||
<div className="tw:w-full">
|
<div className="tw:w-full">
|
||||||
{pattern.public ? (
|
{pattern.public ? (
|
||||||
<Popout note>
|
<Popout type="note">
|
||||||
<h5>This is the private view of your pattern</h5>
|
<h5>This is the private view of your pattern</h5>
|
||||||
<p>
|
<p>
|
||||||
Everyone can access the public view since this is a public pattern.
|
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 = ({
|
export const PatternCard = ({
|
||||||
pattern,
|
pattern,
|
||||||
href = false,
|
href = false,
|
||||||
|
@ -265,7 +276,7 @@ export const PatternCard = ({
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
const sizes = {
|
const sizes = {
|
||||||
lg: 96,
|
lg: 96,
|
||||||
md: 52,
|
md: 48,
|
||||||
sm: 36,
|
sm: 36,
|
||||||
xs: 20,
|
xs: 20,
|
||||||
}
|
}
|
||||||
|
@ -315,25 +326,16 @@ export const PatternCard = ({
|
||||||
const BadgeLink = ({ label, href }) => (
|
const BadgeLink = ({ label, href }) => (
|
||||||
<a
|
<a
|
||||||
href={href}
|
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>
|
</a>
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper component to show the pattern title, image, and various buttons
|
* Helper component to show the pattern title, image, and various buttons
|
||||||
*/
|
*/
|
||||||
const PatternHeader = ({
|
const PatternHeader = ({ pattern, Link, account, setModal, setEdit, togglePublic, clone }) => (
|
||||||
pattern,
|
|
||||||
Link,
|
|
||||||
account,
|
|
||||||
setModal,
|
|
||||||
setEdit,
|
|
||||||
togglePublic,
|
|
||||||
save,
|
|
||||||
clone,
|
|
||||||
}) => (
|
|
||||||
<>
|
<>
|
||||||
<h2>{pattern.name}</h2>
|
<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">
|
<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
|
// Dependencies
|
||||||
import orderBy from 'lodash/orderBy.js'
|
import orderBy from 'lodash/orderBy.js'
|
||||||
import { capitalize, shortDate } from '@freesewing/utils'
|
import { capitalize, shortDate } from '@freesewing/utils'
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useEffect, useContext } from 'react'
|
import React, { useState, useEffect, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
import { useSelection } from '@freesewing/react/hooks/useSelection'
|
import { useSelection } from '@freesewing/react/hooks/useSelection'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { TableWrapper } from '@freesewing/react/components/Table'
|
import { TableWrapper } from '@freesewing/react/components/Table'
|
||||||
import { PatternCard } from '@freesewing/react/components/Account'
|
import { PatternCard } from '@freesewing/react/components/Account'
|
||||||
|
@ -23,16 +19,13 @@ import {
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from '@freesewing/react/components/Icon'
|
} from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
const t = (input) => {
|
/**
|
||||||
console.log('t called', input)
|
* A component to display and manage the list of patterns in the user's account
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Component for the account/patterns page
|
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {function} Link - A framework specific Link component for client-side routing
|
* @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 }) => {
|
export const Patterns = ({ Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -95,14 +88,16 @@ export const Patterns = ({ Link = false }) => {
|
||||||
onClick={removeSelectedPatterns}
|
onClick={removeSelectedPatterns}
|
||||||
disabled={count < 1}
|
disabled={count < 1}
|
||||||
>
|
>
|
||||||
<TrashIcon /> {count} {t('patterns')}
|
<TrashIcon /> {count} Patterns
|
||||||
</button>
|
</button>
|
||||||
<Link
|
<Link
|
||||||
className="tw:daisy-btn tw:daisy-btn-primary tw:capitalize tw:w-full tw:md:w-auto tw:hover:text-primary-content"
|
className="tw:daisy-btn tw:daisy-btn-primary tw:capitalize tw:w-full tw:md:w-auto tw:hover:text-primary-content"
|
||||||
href="/editor/"
|
href="/editor/"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<span className="tw:text-primary-content">
|
||||||
Create a new pattern
|
<PlusIcon />
|
||||||
|
</span>
|
||||||
|
<span className="tw:text-primary-content">Create a new pattern</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<TableWrapper>
|
<TableWrapper>
|
||||||
|
|
|
@ -19,11 +19,52 @@ const labels = {
|
||||||
website: 'Website',
|
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" />
|
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" />
|
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" />
|
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" />
|
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" />
|
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" />
|
export const Website = () => <Platform platform="website" />
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
|
||||||
import { ReloadIcon } from '@freesewing/react/components/Icon'
|
import { ReloadIcon } from '@freesewing/react/components/Icon'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
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 = () => {
|
export const Reload = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
|
@ -3,21 +3,20 @@ import { navigate } from '@freesewing/utils'
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
|
||||||
import { BackIcon as ExitIcon, TrashIcon } from '@freesewing/react/components/Icon'
|
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 { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
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 = () => {
|
export const Remove = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
|
@ -6,19 +6,23 @@ import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import { BackIcon, NoIcon } from '@freesewing/react/components/Icon'
|
import { BackIcon, NoIcon } from '@freesewing/react/components/Icon'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
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 }) => {
|
export const Restrict = ({ Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -34,7 +38,7 @@ export const Restrict = ({ Link = false }) => {
|
||||||
// Helper method to restrict the account
|
// Helper method to restrict the account
|
||||||
const restrictAccount = async () => {
|
const restrictAccount = async () => {
|
||||||
setLoadingStatus([true, 'Talking to the backend'])
|
setLoadingStatus([true, 'Talking to the backend'])
|
||||||
const [status, body] = await backend.restrictAccount()
|
const [status] = await backend.restrictAccount()
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
setLoadingStatus([true, 'Done. Consider yourself restrcited.', true, true])
|
setLoadingStatus([true, 'Done. Consider yourself restrcited.', true, true])
|
||||||
signOut()
|
signOut()
|
||||||
|
@ -46,7 +50,7 @@ export const Restrict = ({ Link = false }) => {
|
||||||
<>
|
<>
|
||||||
<p>This button is red for a reason.</p>
|
<p>This button is red for a reason.</p>
|
||||||
<IconButton onClick={restrictAccount} color="error">
|
<IconButton onClick={restrictAccount} color="error">
|
||||||
<Nocon />
|
<NoIcon />
|
||||||
Remove your FreeSewing account
|
Remove your FreeSewing account
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
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 }) => {
|
export const UserRole = ({ role }) => {
|
||||||
if (role === 'user') return <RoleUser />
|
if (role === 'user') return <RoleUser />
|
||||||
if (role === 'admin') return <RoleAdmin />
|
if (role === 'admin') return <RoleAdmin />
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
shortDate,
|
shortDate,
|
||||||
timeAgo,
|
timeAgo,
|
||||||
} from '@freesewing/utils'
|
} from '@freesewing/utils'
|
||||||
|
import { modalMeasurementHelp } from '@freesewing/react/components/Help'
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
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 { Bonny } from '@freesewing/bonny'
|
||||||
import { MiniNote, MiniTip } from '../Mini/index.mjs'
|
import { MiniNote, MiniTip } from '../Mini/index.mjs'
|
||||||
|
|
||||||
const t = (input) => {
|
/**
|
||||||
console.log('t called', input)
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Component to show an individual measurements set
|
* Component to show an individual measurements set
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {number} id - The ID of the measurements set to load
|
* @param {object} props - All Component props
|
||||||
* @param {bool} publicOnly - If the set should be used with the backend.getPublicSet method
|
* @param {number} props.id - The ID of the measurements set to load
|
||||||
* @param {function} Link - An optional framework-specific Link component to use for client-side routing
|
* @param {bool} [props.publicOnly = false] - If the set should be used with the backend.getPublicSet method
|
||||||
* @param {object} measurementHelpProvider - A function that returns a url or action to show help for a specific measurement
|
* @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
|
if (!Link) Link = WebLink
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
|
@ -384,7 +380,7 @@ export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvi
|
||||||
return (
|
return (
|
||||||
<div className="tw:w-full">
|
<div className="tw:w-full">
|
||||||
{heading}
|
{heading}
|
||||||
<RenderedCSet {...{ mset, setLoadingStatus, backend, imperial }} />
|
<RenderedCset {...{ mset, setLoadingStatus, backend, imperial }} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -490,7 +486,7 @@ export const Set = ({ id, publicOnly = false, Link = false, measurementHelpProvi
|
||||||
current={mset.measies[m]}
|
current={mset.measies[m]}
|
||||||
original={mset.measies[m]}
|
original={mset.measies[m]}
|
||||||
update={updateMeasies}
|
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
|
* A (helper) component to display a measurements value
|
||||||
*
|
*
|
||||||
|
* @component
|
||||||
* @param {object} props - All React props
|
* @param {object} props - All React props
|
||||||
* @param {string} val - The value
|
* @param {string} val - The value
|
||||||
* @param {string} m - The measurement name
|
* @param {string} m - The measurement name
|
||||||
* @param {bool} imperial - True for imperial measurements, or metric by default
|
* @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) ? (
|
isDegreeMeasurement(m) ? (
|
||||||
<span>{val}°</span>
|
<span>{val}°</span>
|
||||||
) : (
|
) : (
|
||||||
<span dangerouslySetInnerHTML={{ __html: formatMm(val, imperial) }}></span>
|
<span dangerouslySetInnerHTML={{ __html: formatMm(val, imperial) }}></span>
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* React component to suggest a measurements set for curation
|
* React component to suggest a measurements set for curation
|
||||||
*
|
*
|
||||||
|
* @component
|
||||||
* @param {object} props - All React props
|
* @param {object} props - All React props
|
||||||
* @param {string} mset - The measurements set
|
* @param {string} mset - The measurements set
|
||||||
*/
|
*/
|
||||||
export const SuggestCset = ({ mset, Link }) => {
|
const SuggestCset = ({ mset, Link }) => {
|
||||||
// State
|
// State
|
||||||
const [height, setHeight] = useState('')
|
const [height, setHeight] = useState('')
|
||||||
const [img, setImg] = useState('')
|
const [img, setImg] = useState('')
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
const [notes, setNotes] = useState('')
|
const [notes, setNotes] = useState('')
|
||||||
const [submission, setSubmission] = useState(false)
|
const [submission, setSubmission] = useState(false)
|
||||||
|
// Context
|
||||||
console.log(mset)
|
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
|
@ -745,7 +743,7 @@ export const SuggestCset = ({ mset, Link }) => {
|
||||||
Notes
|
Notes
|
||||||
</h4>
|
</h4>
|
||||||
<p>If you would like to add any notes, you can do so here.</p>
|
<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
|
This field supports markdown
|
||||||
</Popout>
|
</Popout>
|
||||||
<MarkdownInput label="Notes" current={notes} update={setNotes} valid={() => true} />
|
<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
|
* React component to render a preview of a measurement set using the bonny pattern
|
||||||
*
|
*
|
||||||
|
* @component
|
||||||
* @param {object} props - All React props
|
* @param {object} props - All React props
|
||||||
* @param {string} mset - The measurements set
|
* @param {string} mset - The measurements set
|
||||||
*/
|
*/
|
||||||
export const RenderedCSet = ({ mset, imperial }) => {
|
const RenderedCset = ({ mset, imperial }) => {
|
||||||
const [previewVisible, setPreviewVisible] = useState(false)
|
const [previewVisible, setPreviewVisible] = useState(false)
|
||||||
|
|
||||||
const missing = []
|
const missing = []
|
||||||
|
@ -799,7 +798,7 @@ export const RenderedCSet = ({ mset, imperial }) => {
|
||||||
<strong>{formatMm(pattern.parts[0].front.points.head.y * -1, imperial)}</strong> high.
|
<strong>{formatMm(pattern.parts[0].front.points.head.y * -1, imperial)}</strong> high.
|
||||||
</p>
|
</p>
|
||||||
<p>Here is what the automated analysis found:</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
|
const desc = strings[flag.desc] || flag.desc
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -840,7 +839,7 @@ export const RenderedCSet = ({ mset, imperial }) => {
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
This preview is an <strong>approximation</strong>, not an exact representation. Bodies
|
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.
|
missing some information, like how weight is distributed or your posture.
|
||||||
</li>
|
</li>
|
||||||
<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 = () => {
|
export const NewSet = () => {
|
||||||
// Hooks
|
// Hooks
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
const { account } = useAccount()
|
const { account } = useAccount()
|
||||||
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
|
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [name, setName] = useState('')
|
const [name, setName] = useState('')
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { measurements } from '@freesewing/config'
|
|
||||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||||
import { requiredMeasurements as designMeasurements } from '@freesewing/collection'
|
import { requiredMeasurements as designMeasurements } from '@freesewing/collection'
|
||||||
import { cloudflareImageUrl, capitalize, hasRequiredMeasurements } from '@freesewing/utils'
|
import { cloudflareImageUrl, hasRequiredMeasurements } from '@freesewing/utils'
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
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 { ModalWrapper } from '@freesewing/react/components/Modal'
|
||||||
import { NewSet } from './Set.mjs'
|
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
|
* @component
|
||||||
* @param {function} Link - An optional framework-specific Link 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 }) => {
|
export const Sets = ({ Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
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
|
* This is a pure render component, you need to pass in the data.
|
||||||
* @param {function} Link - An optional framework-specific Link component
|
*
|
||||||
* @param {string} design - The designs for which to check required measurements
|
* @component
|
||||||
* @param {test} href - Where the set should link to
|
* @param {object} props - All component props
|
||||||
* @param {function} onClick - What to do when clicking on a set
|
* @param {React.Component} [props.Link = false] - An optional framework-specific Link component
|
||||||
* @param {object} set - The (data of the) measurements set
|
* @param {string} [props.design = false] - An optional design name to check for required measurements in this set
|
||||||
* @param {string} size - Size of the card
|
* @param {string} [props.href = false] - On optional href for the card to link to
|
||||||
* @param {bool} useA - Whether to use an A tag or not
|
* @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 = ({
|
export const MsetCard = ({
|
||||||
Link = false,
|
Link = false,
|
||||||
|
@ -183,7 +188,7 @@ export const MsetCard = ({
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
const sizes = {
|
const sizes = {
|
||||||
lg: 96,
|
lg: 96,
|
||||||
md: 52,
|
md: 48,
|
||||||
sm: 36,
|
sm: 36,
|
||||||
}
|
}
|
||||||
const s = sizes[size]
|
const s = sizes[size]
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
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 }) => {
|
export const AccountStatus = ({ status }) => {
|
||||||
if (status === 0) return <AccountInactive />
|
if (status === 0) return <AccountInactive />
|
||||||
if (status === 1) return <AccountActive />
|
if (status === 1) return <AccountActive />
|
||||||
|
|
|
@ -1,27 +1,24 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { welcomeSteps } from './shared.mjs'
|
import { welcomeSteps } from './shared.mjs'
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { RightIcon } from '@freesewing/react/components/Icon'
|
||||||
import { SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
|
|
||||||
import { ListInput } from '@freesewing/react/components/Input'
|
import { ListInput } from '@freesewing/react/components/Input'
|
||||||
import { NumberCircle } from '@freesewing/react/components/Number'
|
import { NumberCircle } from '@freesewing/react/components/Number'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { WelcomeIcons } from './shared.mjs'
|
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
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @param {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Units = ({ welcome = false }) => {
|
export const Units = ({ welcome = false }) => {
|
||||||
// Hooks
|
// Hooks
|
||||||
|
|
|
@ -16,12 +16,14 @@ import { StringInput } from '@freesewing/react/components/Input'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { WelcomeIcons } from './shared.mjs'
|
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
|
* @component
|
||||||
* @params {bool} props.welcome - Set to true to use this component on the welcome page
|
* @param {object} props - All component props
|
||||||
* @params {function} props.Link - A framework specific Link component for client-side routing
|
* @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 }) => {
|
export const Username = ({ welcome = false, Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -55,10 +57,6 @@ export const Username = ({ welcome = false, Link = false }) => {
|
||||||
? '/welcome/' + welcomeSteps[account.control][5]
|
? '/welcome/' + welcomeSteps[account.control][5]
|
||||||
: '/docs/about/guide'
|
: '/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 (
|
return (
|
||||||
<div className="tw:w-full">
|
<div className="tw:w-full">
|
||||||
<StringInput
|
<StringInput
|
||||||
|
|
|
@ -1,71 +1,69 @@
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import { AccountStatus } from './Status.mjs'
|
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 { Apikeys } from './Apikeys.mjs'
|
||||||
import { Username } from './Username.mjs'
|
|
||||||
import { Bio } from './Bio.mjs'
|
|
||||||
import { Avatar } from './Avatar.mjs'
|
import { Avatar } from './Avatar.mjs'
|
||||||
import { Email, EmailChangeConfirmation } from './Email.mjs'
|
import { Bio } from './Bio.mjs'
|
||||||
import { Github } from './Github.mjs'
|
import { BookmarkButton, Bookmarks } from './Bookmarks.mjs'
|
||||||
import { Instagram, Mastodon, Reddit, Twitch, Tiktok, Website } from './Platform.mjs'
|
|
||||||
import { Compare } from './Compare.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 { Consent } from './Consent.mjs'
|
||||||
import { Password } from './Password.mjs'
|
import { Control } from './Control.mjs'
|
||||||
import { Mfa } from './Mfa.mjs'
|
import { Email, EmailChangeConfirmation } from './Email.mjs'
|
||||||
import { ImportSet } from './Import.mjs'
|
|
||||||
import { Export } from './Export.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 { Reload } from './Reload.mjs'
|
||||||
import { Remove } from './Remove.mjs'
|
import { Remove } from './Remove.mjs'
|
||||||
import { Restrict } from './Restrict.mjs'
|
import { Restrict } from './Restrict.mjs'
|
||||||
|
import { Set, NewSet } from './Set.mjs'
|
||||||
|
import { Units } from './Units.mjs'
|
||||||
import { UserId } from './Id.mjs'
|
import { UserId } from './Id.mjs'
|
||||||
|
import { Username } from './Username.mjs'
|
||||||
import { UserRole } from './Role.mjs'
|
import { UserRole } from './Role.mjs'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AccountStatus,
|
AccountStatus,
|
||||||
Bookmarks,
|
|
||||||
BookmarkButton,
|
|
||||||
Links,
|
|
||||||
Set,
|
|
||||||
NewSet,
|
|
||||||
Sets,
|
|
||||||
MsetCard,
|
|
||||||
Patterns,
|
|
||||||
Pattern,
|
|
||||||
PatternCard,
|
|
||||||
Apikeys,
|
Apikeys,
|
||||||
Username,
|
|
||||||
Bio,
|
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Bio,
|
||||||
|
BookmarkButton,
|
||||||
|
Bookmarks,
|
||||||
|
Compare,
|
||||||
|
Consent,
|
||||||
|
Control,
|
||||||
Email,
|
Email,
|
||||||
EmailChangeConfirmation,
|
EmailChangeConfirmation,
|
||||||
Github,
|
|
||||||
Instagram,
|
|
||||||
Mastodon,
|
|
||||||
Reddit,
|
|
||||||
Twitch,
|
|
||||||
Tiktok,
|
|
||||||
Website,
|
|
||||||
Compare,
|
|
||||||
Control,
|
|
||||||
Units,
|
|
||||||
Newsletter,
|
|
||||||
Consent,
|
|
||||||
Password,
|
|
||||||
Mfa,
|
|
||||||
ImportSet,
|
|
||||||
Export,
|
Export,
|
||||||
|
Github,
|
||||||
|
ImportSet,
|
||||||
|
Instagram,
|
||||||
|
Links,
|
||||||
|
Mastodon,
|
||||||
|
Mfa,
|
||||||
|
MsetCard,
|
||||||
|
NewSet,
|
||||||
|
Newsletter,
|
||||||
|
Password,
|
||||||
|
Pattern,
|
||||||
|
PatternCard,
|
||||||
|
Patterns,
|
||||||
|
Reddit,
|
||||||
Reload,
|
Reload,
|
||||||
Remove,
|
Remove,
|
||||||
Restrict,
|
Restrict,
|
||||||
|
Set,
|
||||||
|
Sets,
|
||||||
|
Tiktok,
|
||||||
|
Twitch,
|
||||||
|
Units,
|
||||||
UserId,
|
UserId,
|
||||||
|
Username,
|
||||||
UserRole,
|
UserRole,
|
||||||
|
Website,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ import {
|
||||||
CompareIcon,
|
CompareIcon,
|
||||||
DocsIcon,
|
DocsIcon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
LeftIcon,
|
|
||||||
OkIcon,
|
|
||||||
NoIcon,
|
|
||||||
ShowcaseIcon,
|
ShowcaseIcon,
|
||||||
} from '@freesewing/react/components/Icon'
|
} from '@freesewing/react/components/Icon'
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { uiRoles as roles } from '@freesewing/config'
|
|
||||||
import { userAvatarUrl } from '@freesewing/utils'
|
import { userAvatarUrl } from '@freesewing/utils'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext, useEffect } from 'react'
|
import React, { useState, useContext } from 'react'
|
||||||
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
// Context
|
// Context
|
||||||
|
@ -14,11 +13,16 @@ import { Spinner } from '@freesewing/react/components/Spinner'
|
||||||
import { Link as WebLink } from '@freesewing/react/components/Link'
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import { SearchIcon } from '@freesewing/react/components/Icon'
|
import { SearchIcon } from '@freesewing/react/components/Icon'
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
||||||
import { Markdown } from '@freesewing/react/components/Markdown'
|
|
||||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
||||||
import { AccountStatus, UserRole } from '@freesewing/react/components/Account'
|
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 [subscribers, setSubscribers] = useState()
|
||||||
const [q, setQ] = useState()
|
const [q, setQ] = useState()
|
||||||
const [hits, setHits] = 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 }) => {
|
export const UserAdministration = ({ Link = false }) => {
|
||||||
const backend = useBackend()
|
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
|
if (!Link) Link = WebLink
|
||||||
|
|
||||||
return (
|
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 { setModal } = useContext(ModalContext)
|
||||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
|
@ -274,7 +286,7 @@ export const User = ({ user, Link }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ImpersonateButton = ({ userId }) => {
|
const ImpersonateButton = ({ userId }) => {
|
||||||
const backend = useBackend()
|
const backend = useBackend()
|
||||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||||
const { impersonate } = useAccount()
|
const { impersonate } = useAccount()
|
||||||
|
@ -299,95 +311,3 @@ export const ImpersonateButton = ({ userId }) => {
|
||||||
</button>
|
</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'
|
import React from 'react'
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The actual Breadcrumbs component
|
* A component to render breadcrumbs
|
||||||
*
|
*
|
||||||
* @param {object} props - All the React props
|
* This is a pure render component, you need to pass in the data.
|
||||||
* @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
|
* @component
|
||||||
* @param {text} title - The title of the current page
|
* @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 }) => {
|
export const Breadcrumbs = ({ crumbs = [], title, Link = false }) => {
|
||||||
if (Link === false) Link = RegularLink
|
if (Link === false) Link = RegularLink
|
||||||
|
@ -18,13 +22,20 @@ export const Breadcrumbs = ({ crumbs = [], title, Link = false }) => {
|
||||||
style={{ paddingLeft: 0 }}
|
style={{ paddingLeft: 0 }}
|
||||||
>
|
>
|
||||||
<li className="tw:inline">
|
<li className="tw:inline">
|
||||||
<Link href="/">Home</Link>
|
<Link href="/">
|
||||||
|
<b>Home</b>
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{crumbs.map((crumb, i) => (
|
{crumbs.map((crumb, i) => (
|
||||||
<li key={i} className="tw:inline">
|
<React.Fragment key={i}>
|
||||||
<Link href={crumb.href}>{crumb.label}</Link>
|
<li key={i} className="tw:inline">
|
||||||
</li>
|
<Link href={crumb.href}>{crumb.label}</Link>
|
||||||
|
</li>
|
||||||
|
<li key={i} className="tw:inline">
|
||||||
|
<Spacer />
|
||||||
|
</li>
|
||||||
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
<li className="tw:inline">{title}</li>
|
<li className="tw:inline">{title}</li>
|
||||||
</ul>
|
</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.
|
* A button with an icon and a label, something which we commonly use across our UI.
|
||||||
|
|
|
@ -1,36 +1,25 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { atomWithHash } from 'jotai-location'
|
|
||||||
import {
|
import {
|
||||||
about,
|
about,
|
||||||
collection,
|
collection,
|
||||||
tags,
|
tags,
|
||||||
techniques,
|
techniques,
|
||||||
designers,
|
|
||||||
developers,
|
|
||||||
examples,
|
examples,
|
||||||
measurements,
|
|
||||||
requiredMeasurements,
|
requiredMeasurements,
|
||||||
optionalMeasurements,
|
optionalMeasurements,
|
||||||
} from '@freesewing/collection'
|
} from '@freesewing/collection'
|
||||||
import { capitalize, linkClasses, mutateObject } from '@freesewing/utils'
|
import { capitalize, linkClasses, mutateObject } from '@freesewing/utils'
|
||||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||||
|
|
||||||
// Context
|
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
|
||||||
import { ModalContext } from '@freesewing/react/context/Modal'
|
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useContext, Fragment } from 'react'
|
import React, { useState, Fragment } from 'react'
|
||||||
import { useAtom } from 'jotai'
|
import { useFilter } from '@freesewing/react/hooks/useFilter'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link'
|
import { Link as WebLink } from '@freesewing/react/components/Link'
|
||||||
import {
|
import {
|
||||||
CircleIcon,
|
CircleIcon,
|
||||||
CisFemaleIcon,
|
CisFemaleIcon,
|
||||||
DocsIcon,
|
DocsIcon,
|
||||||
FilterIcon,
|
FilterIcon,
|
||||||
HeartIcon,
|
|
||||||
NewPatternIcon,
|
NewPatternIcon,
|
||||||
ResetIcon,
|
ResetIcon,
|
||||||
ShowcaseIcon,
|
ShowcaseIcon,
|
||||||
|
@ -40,23 +29,19 @@ import {
|
||||||
lineDrawingsBack,
|
lineDrawingsBack,
|
||||||
} from '@freesewing/react/components/LineDrawing'
|
} from '@freesewing/react/components/LineDrawing'
|
||||||
import { IconButton } from '@freesewing/react/components/Button'
|
import { IconButton } from '@freesewing/react/components/Button'
|
||||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
||||||
import { MissingLinedrawing } from '../LineDrawing/missing.mjs'
|
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
|
* @component
|
||||||
* @param {function} Link - An optional framework specific Link component for client-side routing
|
* @param {object} props - All component props
|
||||||
* @param {bool} editor - Set this to when loaded in the editor (this will make the display more dense)
|
* @param {React.Component} [props.Link = false] - A framework specific Link component for client-side routing
|
||||||
* @param {bool} onClick - Set this to trigger an onClick event, rather than using links
|
* @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 }) => {
|
export const Collection = ({ Link = false, linkTo = 'about', editor = false, onClick = false }) => {
|
||||||
if (!Link) Link = WebLink
|
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">
|
<div className="tw:flex tw:flex-row tw:gap-4 tw:items-center tw:justify-center tw:flex-wrap tw:my-2">
|
||||||
<button
|
<button
|
||||||
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
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.ld ? <CisFemaleIcon /> : <ShowcaseIcon />}
|
||||||
{filter.example ? 'Show Line Drawings' : 'Show Examples'}
|
{filter.ld ? 'Show Examples' : 'Show Line Drawings'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
||||||
onClick={() => setFilter({ example: 1 })}
|
onClick={() => setFilter({ ld: 1 })}
|
||||||
>
|
>
|
||||||
<ResetIcon />
|
<ResetIcon />
|
||||||
Clear Filter
|
Clear Filters
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
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">
|
<div className="tw:flex tw:flex-row tw:gap-4 tw:items-center tw:justify-center tw:flex-wrap tw:my-2">
|
||||||
<button
|
<button
|
||||||
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
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.ld ? <ShowcaseIcon /> : <CisFemaleIcon />}
|
||||||
{filter.example ? 'Show Line Drawings' : 'Show Examples'}
|
{filter.ld ? 'Show Examples' : 'Show Line Drawings'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline"
|
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}
|
key={d}
|
||||||
linkTo={linkTo}
|
linkTo={linkTo}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
lineDrawing={filter.example ? false : true}
|
lineDrawing={filter.ld ? true : false}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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 }) => {
|
const DesignCard = ({ name, lineDrawing = false, linkTo, Link, onClick }) => {
|
||||||
if (!Link) Link = WebLink
|
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'
|
'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
|
* @component
|
||||||
* @param {string} design - The name/id of the design
|
* @param {object} props - All component props
|
||||||
* @param {function} Link - An optional framework specific Link component for client-side routing
|
* @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 }) => {
|
export const DesignInfo = ({ Link = false, design = false, noDocsLink = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
|
@ -375,10 +331,6 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
|
||||||
// State
|
// State
|
||||||
const [back, setBack] = useState(false)
|
const [back, setBack] = useState(false)
|
||||||
|
|
||||||
// Context
|
|
||||||
const { setModal, clearModal } = useContext(ModalContext)
|
|
||||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
|
||||||
|
|
||||||
if (!design) return null
|
if (!design) return null
|
||||||
|
|
||||||
// Line drawings
|
// Line drawings
|
||||||
|
@ -392,13 +344,6 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
|
||||||
: [about[design].design]
|
: [about[design].design]
|
||||||
const tags = about[design].tags || []
|
const tags = about[design].tags || []
|
||||||
const techniques = about[design].techniques || []
|
const techniques = about[design].techniques || []
|
||||||
const colors = {
|
|
||||||
1: 'success',
|
|
||||||
2: 'success',
|
|
||||||
3: 'warning',
|
|
||||||
4: 'warning',
|
|
||||||
5: 'error',
|
|
||||||
}
|
|
||||||
|
|
||||||
const makeButton = (
|
const makeButton = (
|
||||||
<div className={`tw:grid tw:grid-cols-1 tw:gap-2 tw:mb-4`}>
|
<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"
|
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}"]}`}
|
href={`/designs/#filter={"example"%3Atrue%2C"tag"%3A["${tag}"]}`}
|
||||||
>
|
>
|
||||||
{tag}
|
<span className="tw:text-primary-content">{tag}</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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"
|
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}"]}`}
|
href={`/designs/#filter={"example"%3Atrue%2C"tag"%3A["${tech}"]}`}
|
||||||
>
|
>
|
||||||
{tech}
|
<span className="tw:text-accent-content">{tech}</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</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 { controlDesc } from '@freesewing/config'
|
||||||
import { BulletIcon } from '@freesewing/react/components/Icon'
|
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 ? (
|
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) => (
|
{Object.keys(controlDesc).map((score) => (
|
||||||
<BulletIcon
|
<BulletIcon
|
||||||
fill={control >= score ? true : false}
|
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 { Markdown } from '@freesewing/react/components/Markdown'
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
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 }) => {
|
export const CuratedSetLineup = ({ href = false, clickHandler = false, Link = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
// Hooks
|
// 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 }) => {
|
export const CuratedSet = ({ Link = false, id = false }) => {
|
||||||
if (!Link) Link = WebLink
|
if (!Link) Link = WebLink
|
||||||
// Hooks
|
// 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 { diffWords, diffJson } from 'diff'
|
||||||
import ReactDiffViewer from 'react-diff-viewer-continued'
|
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)
|
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 diffCheck = (from, to) => diffWords(from, to)
|
||||||
|
|
||||||
export const DiffViewer = (props) => <ReactDiffViewer {...props} />
|
export const DiffViewer = (props) => <ReactDiffViewer {...props} />
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
ChatIcon,
|
ChatIcon,
|
||||||
DesignIcon,
|
DesignIcon,
|
||||||
DocsIcon,
|
DocsIcon,
|
||||||
|
HelpIcon,
|
||||||
ShowcaseIcon,
|
ShowcaseIcon,
|
||||||
RssIcon,
|
RssIcon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
|
@ -16,16 +17,25 @@ import {
|
||||||
} from '@freesewing/react/components/Icon'
|
} from '@freesewing/react/components/Icon'
|
||||||
import { Layout as DefaultLayout } from '@freesewing/react/components/Layout'
|
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
|
* This sets up the various context providers before
|
||||||
* passing all props down to the InnerPageWrapper.
|
* passing all props down to the InnerPageWrapper.
|
||||||
* This is required because the context providers need to
|
* 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
|
* 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) => {
|
export const DocusaurusPage = (props) => {
|
||||||
const DocusaurusLayout = props.DocusaurusLayout
|
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
|
* 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
|
* This sets up the various context providers before
|
||||||
* passing all props down to the InnerPageWrapper.
|
* passing all props down to the InnerPageWrapper.
|
||||||
* This is required because the context providers need to
|
* This is required because the context providers need to
|
||||||
* be setup for the modal and loading state work we do in the InnerPageWrapper
|
* 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) => (
|
export const DocusaurusDoc = (props) => (
|
||||||
<ModalContextProvider>
|
<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) => {
|
export const NavbarItem = (props) => {
|
||||||
const { id, Default } = props
|
const { id, Default } = props
|
||||||
|
|
||||||
|
@ -153,4 +185,7 @@ const navbarItems = {
|
||||||
Link={props.Link}
|
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 ReactECharts from 'echarts-for-react'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-export of Apache Echarts
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
export const echarts = _echarts
|
export const echarts = _echarts
|
||||||
|
|
||||||
echarts.registerTheme('light', {
|
echarts.registerTheme('light', {
|
||||||
|
@ -12,6 +18,25 @@ echarts.registerTheme('dark', {
|
||||||
backgroundColor: 'transparent',
|
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 }) => {
|
export const ChartWrapper = ({ option = false, theme = 'light', h = 400 }) => {
|
||||||
return option ? (
|
return option ? (
|
||||||
<ReactECharts option={option} className="class_2" theme={theme} style={{ height: h }} />
|
<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.
|
* DaisyUI's accordion seems rather unreliable.
|
||||||
* So instead, we handle this in React state
|
* 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
|
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`,
|
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
|
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`,
|
tw:content-start tw:text-left tw:list-none`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import mustache from 'mustache'
|
import mustache from 'mustache'
|
||||||
import { flattenFlags, stripNamespace, bundlePatternTranslations } from '../lib/index.mjs'
|
import { flattenFlags } from '../lib/index.mjs'
|
||||||
import {
|
import {
|
||||||
ChatIcon,
|
ChatIcon,
|
||||||
ErrorIcon,
|
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)
|
const flagList = flattenFlags(flags)
|
||||||
|
|
||||||
if (Object.keys(flagList).length < 1) return null
|
if (Object.keys(flagList).length < 1) return null
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
FixmeIcon,
|
FixmeIcon,
|
||||||
FlagIcon,
|
FlagIcon,
|
||||||
|
MeasurementsIcon,
|
||||||
OptionsIcon,
|
OptionsIcon,
|
||||||
PaperlessIcon,
|
PaperlessIcon,
|
||||||
PrintIcon,
|
PrintIcon,
|
||||||
|
@ -24,7 +25,6 @@ import {
|
||||||
ResetIcon,
|
ResetIcon,
|
||||||
RightIcon,
|
RightIcon,
|
||||||
RocketIcon,
|
RocketIcon,
|
||||||
RotateIcon,
|
|
||||||
SaIcon,
|
SaIcon,
|
||||||
SaveAsIcon,
|
SaveAsIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
|
@ -53,6 +53,7 @@ const headerMenuIcons = {
|
||||||
settings: SettingsIcon,
|
settings: SettingsIcon,
|
||||||
ui: UiIcon,
|
ui: UiIcon,
|
||||||
layout: PrintIcon,
|
layout: PrintIcon,
|
||||||
|
measurements: MeasurementsIcon,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuIcon = (props) => {
|
export const HeaderMenuIcon = (props) => {
|
||||||
|
@ -122,7 +123,7 @@ export const HeaderMenuTestViewDesignMeasurements = (props) => {
|
||||||
tooltip="See how changes to a measurement influence the pattern being generated."
|
tooltip="See how changes to a measurement influence the pattern being generated."
|
||||||
toggle={
|
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>
|
<span className="tw:hidden tw:lg:inline">Test Measurements</span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ export const HeaderMenuTestViewDesignMeasurements = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuDropdown = (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)
|
const [localOpen, setLocalOpen] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -460,18 +461,16 @@ export const HeaderMenuUndoIcons = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuTestIcons = (props) => {
|
export const HeaderMenuTestIcons = (props) => {
|
||||||
const { update, state, Design } = props
|
const { update } = props
|
||||||
const Button = HeaderMenuButton
|
const Button = HeaderMenuButton
|
||||||
const size = 'tw:w-5 tw:h-5'
|
|
||||||
const undos = state._?.undos && state._.undos.length > 0 ? state._.undos : false
|
|
||||||
|
|
||||||
return (
|
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
|
<Button
|
||||||
updateHandler={() => update.settings('sample', undefined)}
|
updateHandler={() => update.settings('sample', undefined)}
|
||||||
tooltip="Clear the test so you can select another"
|
tooltip="Clear the test so you can select another"
|
||||||
>
|
>
|
||||||
Clear Test
|
<ResetIcon /> <span className="tw:hidden tw:lg:inline">Clear Test</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -531,7 +530,7 @@ export const HeaderMenuButton = ({
|
||||||
}) => (
|
}) => (
|
||||||
<Tooltip tip={tooltip}>
|
<Tooltip tip={tooltip}>
|
||||||
<button
|
<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}
|
onClick={updateHandler}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
|
@ -607,7 +606,7 @@ export const HeaderMenuViewMenu = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const HeaderMenuLayoutView = (props) => (
|
export const HeaderMenuLayoutView = (props) => (
|
||||||
<>
|
<div className="tw:flex tw:flex-row tw:items-center">
|
||||||
<HeaderMenuDropdown
|
<HeaderMenuDropdown
|
||||||
{...props}
|
{...props}
|
||||||
id="layoutOptions"
|
id="layoutOptions"
|
||||||
|
@ -622,7 +621,7 @@ export const HeaderMenuLayoutView = (props) => (
|
||||||
<LayoutSettingsMenu {...props} />
|
<LayoutSettingsMenu {...props} />
|
||||||
</HeaderMenuDropdown>
|
</HeaderMenuDropdown>
|
||||||
<HeaderMenuLayoutViewIcons {...props} />
|
<HeaderMenuLayoutViewIcons {...props} />
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
export const HeaderMenuLayoutViewIcons = (props) => {
|
export const HeaderMenuLayoutViewIcons = (props) => {
|
||||||
|
@ -660,11 +659,6 @@ export const HeaderMenuLayoutViewIcons = (props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const pages = pattern.setStores[0].get('pages', {})
|
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 { cols, rows, count } = pages
|
||||||
const blank = cols * rows - count
|
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
|
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 (
|
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: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">
|
<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 { MeasurementInput } from '@freesewing/react/components/Input'
|
||||||
|
import { modalMeasurementHelp } from '@freesewing/react/components/Help'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This MeasurementsEditor component allows inline-editing of the measurements
|
* 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 - The ViewWrapper state object
|
||||||
* @param {object} props.state.settings - The current settings
|
* @param {object} props.state.settings - The current settings
|
||||||
* @param {object} props.update - Helper object for updating the ViewWrapper state
|
* @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
|
* @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
|
* Helper method to handle state updates for measurements
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +34,7 @@ export const MeasurementsEditor = ({ Design, update, state, helpProvider = false
|
||||||
const { settings = {} } = state
|
const { settings = {} } = state
|
||||||
|
|
||||||
return (
|
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>
|
<h4>Required Measurements</h4>
|
||||||
{Object.keys(Design.patternConfig.measurements).length === 0 ? (
|
{Object.keys(Design.patternConfig.measurements).length === 0 ? (
|
||||||
<p>This design does not require any measurements.</p>
|
<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]}
|
original={settings.measurements?.[m]}
|
||||||
update={(m, newVal) => onUpdate(m, newVal)}
|
update={(m, newVal) => onUpdate(m, newVal)}
|
||||||
id={`edit-${m}`}
|
id={`edit-${m}`}
|
||||||
helpProvider={helpProvider}
|
label={measurementTranslations[m]}
|
||||||
|
help={() => modalMeasurementHelp(m, setModal)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<br />
|
<br />
|
||||||
|
@ -58,6 +67,7 @@ export const MeasurementsEditor = ({ Design, update, state, helpProvider = false
|
||||||
original={settings.measurements?.[m]}
|
original={settings.measurements?.[m]}
|
||||||
update={(m, newVal) => onUpdate(m, newVal)}
|
update={(m, newVal) => onUpdate(m, newVal)}
|
||||||
id={`edit-${m}`}
|
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 { ZoomablePattern } from './ZoomablePattern.mjs'
|
||||||
import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
|
import { generateStackTransform, getTransformedBounds } from '@freesewing/core'
|
||||||
import { getProps } from '@freesewing/react/components/Pattern'
|
import { getProps } from '@freesewing/react/components/Pattern'
|
||||||
import { FlipIcon, RotateIcon, ResetIcon } from '@freesewing/react/components/Icon'
|
|
||||||
import { drag } from 'd3-drag'
|
import { drag } from 'd3-drag'
|
||||||
import { select } from 'd3-selection'
|
import { select } from 'd3-selection'
|
||||||
//import { Buttons } from './transform-buttons.mjs'
|
//import { Buttons } from './transform-buttons.mjs'
|
||||||
|
@ -85,7 +84,7 @@ export const MovablePattern = ({
|
||||||
|
|
||||||
const sortedRenderProps = { ...renderProps, stacks: sortedStacks }
|
const sortedRenderProps = { ...renderProps, stacks: sortedStacks }
|
||||||
|
|
||||||
const Stack = ({ stackName, stack, settings, components, t }) => (
|
const Stack = ({ stackName, stack, settings, components }) => (
|
||||||
<MovableStack
|
<MovableStack
|
||||||
{...{
|
{...{
|
||||||
stackName,
|
stackName,
|
||||||
|
@ -397,7 +396,7 @@ function angle(pointA, pointB) {
|
||||||
|
|
||||||
const rectSize = 24
|
const rectSize = 24
|
||||||
|
|
||||||
const Button = ({ onClickCb, transform, Icon, children, title = '' }) => {
|
const Button = ({ onClickCb, transform, Icon, title = '' }) => {
|
||||||
const _onClick = (event) => {
|
const _onClick = (event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
onClickCb(event)
|
onClickCb(event)
|
||||||
|
@ -413,22 +412,24 @@ const Button = ({ onClickCb, transform, Icon, children, title = '' }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowButtonsToggle = ({ ui, update }) => {
|
const InnerFlipIcon = () => (
|
||||||
const hideButtons = (evt) => {
|
<path
|
||||||
update.ui('hideMovableButtons', !evt.target.checked)
|
strokeLinecap="round"
|
||||||
}
|
strokeLinejoin="round"
|
||||||
return (
|
d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5"
|
||||||
<label className="label cursor-pointer">
|
/>
|
||||||
<span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
|
)
|
||||||
<input
|
const InnerRotateIcon = ({ props }) => (
|
||||||
type="checkbox"
|
<path
|
||||||
className="toggle toggle-primary"
|
strokeLinecap="round"
|
||||||
checked={!ui.hideMovableButtons}
|
strokeLinejoin="round"
|
||||||
onChange={hideButtons}
|
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}
|
||||||
</label>
|
/>
|
||||||
)
|
)
|
||||||
}
|
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 */
|
/** buttons for manipulating the part */
|
||||||
export const Buttons = ({ transform, flip, rotate, resetPart, rotate90, iconSize }) => {
|
export const Buttons = ({ transform, flip, rotate, resetPart, rotate90, iconSize }) => {
|
||||||
|
@ -455,31 +456,31 @@ export const Buttons = ({ transform, flip, rotate, resetPart, rotate90, iconSize
|
||||||
<Button
|
<Button
|
||||||
onClickCb={resetPart}
|
onClickCb={resetPart}
|
||||||
transform={`translate(${rectSize / -2}, ${rectSize / -2})`}
|
transform={`translate(${rectSize / -2}, ${rectSize / -2})`}
|
||||||
Icon={() => <ResetIcon wrapped={0} />}
|
Icon={() => <InnerResetIcon />}
|
||||||
title="Reset part orientation"
|
title="Reset part orientation"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClickCb={() => rotate90()}
|
onClickCb={() => rotate90()}
|
||||||
transform={`translate(${rectSize * -2.7}, ${rectSize / -2})`}
|
transform={`translate(${rectSize * -2.7}, ${rectSize / -2})`}
|
||||||
Icon={() => <RotateIcon wrapped={0} style={{}} />}
|
Icon={() => <InnerRotateIcon />}
|
||||||
title="Rotate part clockwise"
|
title="Rotate part clockwise"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClickCb={() => flip('y')}
|
onClickCb={() => flip('y')}
|
||||||
transform={`rotate(90) translate(${rectSize / -2}, ${rectSize * -1.6})`}
|
transform={`rotate(90) translate(${rectSize / -2}, ${rectSize * -1.6})`}
|
||||||
Icon={() => <FlipIcon wrapped={0} />}
|
Icon={() => <InnerFlipIcon />}
|
||||||
title="Flip part top/bottom"
|
title="Flip part top/bottom"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClickCb={() => flip('x')}
|
onClickCb={() => flip('x')}
|
||||||
transform={`translate(${rectSize * -1.6}, ${rectSize / -2})`}
|
transform={`translate(${rectSize * -1.6}, ${rectSize / -2})`}
|
||||||
Icon={() => <FlipIcon style={{}} wrapped={0} />}
|
Icon={() => <InnerFlipIcon />}
|
||||||
title="Flip part left/right"
|
title="Flip part left/right"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClickCb={() => rotate90(-1)}
|
onClickCb={() => rotate90(-1)}
|
||||||
transform={`translate(${rectSize * 1.7}, ${rectSize / -2})`}
|
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"
|
title="Rotate part counter-clockwise"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
|
|
||||||
import { ZoomContextProvider } from './ZoomablePattern.mjs'
|
import { ZoomContextProvider } from './ZoomablePattern.mjs'
|
||||||
import {
|
import { HeaderMenu } from './HeaderMenu.mjs'
|
||||||
HeaderMenu,
|
|
||||||
HeaderMenuDraftViewDesignOptions,
|
|
||||||
HeaderMenuDraftViewCoreSettings,
|
|
||||||
HeaderMenuDraftViewUiPreferences,
|
|
||||||
HeaderMenuDraftViewFlags,
|
|
||||||
} from './HeaderMenu.mjs'
|
|
||||||
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
|
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
|
||||||
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
|
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
|
||||||
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.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
|
* A layout for views that include a drafted pattern
|
||||||
|
@ -24,9 +19,8 @@ import { Accordion } from './Accordion.mjs'
|
||||||
* @param {object] pattern - The drafted pattern
|
* @param {object] pattern - The drafted pattern
|
||||||
*/
|
*/
|
||||||
export const PatternLayout = (props) => {
|
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 i18n = useDesignTranslation(Design.designConfig.data.id)
|
||||||
const flags = props.pattern?.setStores?.[0]?.plugins?.['plugin-annotations']?.flags
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ZoomContextProvider>
|
<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">
|
<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}
|
{props.output}
|
||||||
</div>
|
</div>
|
||||||
{state.ui?.aside ? (
|
<PatternAsideMenu {...props} i18n={i18n} />
|
||||||
<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}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ZoomContextProvider>
|
</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 }) => (
|
export const SideMenuUl = ({ children }) => (
|
||||||
<ul
|
<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"
|
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)
|
if (!hasSets)
|
||||||
return (
|
return (
|
||||||
<div className="tw:w-full tw:max-w-3xl tw:mx-auto">
|
<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>
|
<h5> You do not (yet) have any of your own measurements sets</h5>
|
||||||
<p>
|
<p>
|
||||||
You can store your measurements as a measurements set, after which you can generate as
|
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}
|
href={config.hrefNewSet}
|
||||||
className="tw:daisy-btn tw:daisy-btn-accent tw:capitalize"
|
className="tw:daisy-btn tw:daisy-btn-accent tw:capitalize"
|
||||||
target="_BLANK"
|
target="_BLANK"
|
||||||
rel="nofollow"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
Create a new measurements set
|
Create a new measurements set
|
||||||
</a>
|
</a>
|
||||||
|
@ -88,7 +88,7 @@ export const UserSetPicker = ({
|
||||||
)}
|
)}
|
||||||
{lackingSets.length > 0 ? (
|
{lackingSets.length > 0 ? (
|
||||||
<div className="tw:my-4">
|
<div className="tw:my-4">
|
||||||
<Popout note>
|
<Popout type="note">
|
||||||
<h5>
|
<h5>
|
||||||
Some of your measurements sets lack the measurements required to generate this pattern
|
Some of your measurements sets lack the measurements required to generate this pattern
|
||||||
</h5>
|
</h5>
|
||||||
|
@ -174,7 +174,7 @@ export const BookmarkedSetPicker = ({
|
||||||
)}
|
)}
|
||||||
{lackingSets.length > 0 && (
|
{lackingSets.length > 0 && (
|
||||||
<div className="tw:my-4">
|
<div className="tw:my-4">
|
||||||
<Popout note>
|
<Popout type="note">
|
||||||
<h5>
|
<h5>
|
||||||
Some of these measurements sets lack the measurements required to generate this
|
Some of these measurements sets lack the measurements required to generate this
|
||||||
pattern
|
pattern
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { ZoomInIcon, ZoomOutIcon, RotateIcon } from '@freesewing/react/component
|
||||||
* A pattern you can pan and zoom
|
* A pattern you can pan and zoom
|
||||||
*/
|
*/
|
||||||
export const ZoomablePattern = forwardRef(function ZoomablePatternRef(props, ref) {
|
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)
|
const { onTransformed, zoomFunctions, setZoomFunctions } = useContext(ZoomContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,15 +1,27 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { menuValueWasChanged } from '../../lib/index.mjs'
|
import { menuValueWasChanged } from '../../lib/index.mjs'
|
||||||
import { designOptionType } from '@freesewing/utils'
|
import { designOptionType } from '@freesewing/utils'
|
||||||
|
import {
|
||||||
|
modalDesignOptionHelp,
|
||||||
|
modalCoreSettingHelp,
|
||||||
|
modalUiPreferenceHelp,
|
||||||
|
} from '@freesewing/react/components/Help'
|
||||||
|
// Context
|
||||||
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { useState, useMemo } from 'react'
|
import React, { useState, useMemo, useContext } from 'react'
|
||||||
// Components
|
// Components
|
||||||
import { SubAccordion } from '../Accordion.mjs'
|
import { SubAccordion } from '../Accordion.mjs'
|
||||||
import { EditIcon, GroupIcon, OptionsIcon, ResetIcon } from '@freesewing/react/components/Icon'
|
import {
|
||||||
import { FormControl } from '@freesewing/react/components/Input'
|
HelpIcon,
|
||||||
|
EditIcon,
|
||||||
|
GroupIcon,
|
||||||
|
OptionsIcon,
|
||||||
|
ResetIcon,
|
||||||
|
} from '@freesewing/react/components/Icon'
|
||||||
|
import { Fieldset } from '@freesewing/react/components/Input'
|
||||||
import { MiniTip } from '@freesewing/react/components/Mini'
|
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'
|
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 {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 {Boolean} allowOverride all a text input to be used to override the given input component
|
||||||
* @param {Number} ux the user-defined ux level
|
* @param {Number} ux the user-defined ux level
|
||||||
|
* @param {strign} type one of designOption, coreSetting, or uiPreference
|
||||||
*/
|
*/
|
||||||
export const MenuItem = ({
|
export const MenuItem = ({
|
||||||
name,
|
name,
|
||||||
|
@ -37,11 +50,11 @@ export const MenuItem = ({
|
||||||
allowOverride = false,
|
allowOverride = false,
|
||||||
ux = 5,
|
ux = 5,
|
||||||
state,
|
state,
|
||||||
docs,
|
|
||||||
config,
|
config,
|
||||||
Design,
|
Design,
|
||||||
i18n,
|
type,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { setModal } = useContext(ModalContext)
|
||||||
// Local state - whether the override input should be shown
|
// Local state - whether the override input should be shown
|
||||||
const [override, setOverride] = useState(false)
|
const [override, setOverride] = useState(false)
|
||||||
|
|
||||||
|
@ -72,6 +85,39 @@ export const MenuItem = ({
|
||||||
|
|
||||||
// get buttons for open and closed states
|
// get buttons for open and closed states
|
||||||
const buttons = []
|
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)
|
if (allowOverride)
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<button
|
<button
|
||||||
|
@ -106,7 +152,7 @@ export const MenuItem = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormControl
|
<Fieldset
|
||||||
label={false}
|
label={false}
|
||||||
id={config.name}
|
id={config.name}
|
||||||
labelBR={<div className="tw:flex tw:flex-row tw:items-center tw:gap-2">{buttons}</div>}
|
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} />
|
<Input {...drillProps} />
|
||||||
</FormControl>
|
</Fieldset>
|
||||||
{config.about ? <MiniTip>{config.about}</MiniTip> : null}
|
{config.about ? <MiniTip>{config.about}</MiniTip> : null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
MenuScaleSettingValue,
|
MenuScaleSettingValue,
|
||||||
} from './Value.mjs'
|
} from './Value.mjs'
|
||||||
import { MenuItemGroup, MenuItem } from './Container.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
|
* The core settings menu
|
||||||
|
@ -124,6 +124,7 @@ export const CoreSetting = ({ name, config, ux, updateHandler, current, passProp
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
type="coreSetting"
|
||||||
{...{
|
{...{
|
||||||
name,
|
name,
|
||||||
config,
|
config,
|
||||||
|
|
|
@ -121,6 +121,7 @@ export const DesignOption = ({ config, settings, ux, inputs, values, ...rest })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
type="designOption"
|
||||||
{...{
|
{...{
|
||||||
config,
|
config,
|
||||||
ux,
|
ux,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { OptionsIcon, SettingsIcon, UiIcon } from '@freesewing/react/components/
|
||||||
import { DesignOptionsMenu } from './DesignOptionsMenu.mjs'
|
import { DesignOptionsMenu } from './DesignOptionsMenu.mjs'
|
||||||
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
|
import { CoreSettingsMenu } from './CoreSettingsMenu.mjs'
|
||||||
import { UiPreferencesMenu } from './UiPreferencesMenu.mjs'
|
import { UiPreferencesMenu } from './UiPreferencesMenu.mjs'
|
||||||
import { FlagsAccordionEntries } from '../Flag.mjs'
|
import { FlagsAccordionEntries, FlagsAccordionTitle } from '../Flag.mjs'
|
||||||
import { Accordion } from '../Accordion.mjs'
|
import { Accordion } from '../Accordion.mjs'
|
||||||
|
|
||||||
export const DraftMenu = ({ Design, pattern, state, update, i18n }) => {
|
export const DraftMenu = ({ Design, pattern, state, update, i18n }) => {
|
||||||
|
|
|
@ -16,12 +16,7 @@ import { mergeOptions } from '@freesewing/core'
|
||||||
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
import { KeyVal } from '@freesewing/react/components/KeyVal'
|
||||||
|
|
||||||
/** A boolean version of {@see MenuListInput} that sets up the necessary configuration */
|
/** A boolean version of {@see MenuListInput} that sets up the necessary configuration */
|
||||||
export const MenuBoolInput = (props) => {
|
export const MenuBoolInput = (props) => <MenuListInput {...props} />
|
||||||
const { name, config } = props
|
|
||||||
//const boolConfig = useBoolConfig(name, config)
|
|
||||||
|
|
||||||
return <MenuListInput {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A placeholder for an input to handle constant values */
|
/** A placeholder for an input to handle constant values */
|
||||||
export const MenuConstantInput = ({
|
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
|
* An input for selecting and item from a list
|
||||||
* @param {String} options.name the name of the property this input changes
|
* @param {String} options.name the name of the property this input changes
|
||||||
|
@ -111,11 +71,9 @@ export const MenuListInput = ({
|
||||||
current,
|
current,
|
||||||
updateHandler,
|
updateHandler,
|
||||||
compact = false,
|
compact = false,
|
||||||
t,
|
|
||||||
changed,
|
changed,
|
||||||
design,
|
design,
|
||||||
isDesignOption = false,
|
isDesignOption = false,
|
||||||
i18n,
|
|
||||||
}) => {
|
}) => {
|
||||||
const handleChange = useSharedHandlers({
|
const handleChange = useSharedHandlers({
|
||||||
dflt: config.dflt,
|
dflt: config.dflt,
|
||||||
|
@ -125,7 +83,7 @@ export const MenuListInput = ({
|
||||||
})
|
})
|
||||||
|
|
||||||
return config.list.map((entry) => {
|
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 })
|
if (!title || !about) console.log('No title or about in', { name, config, design })
|
||||||
const sideBySide = config.sideBySide || about.length + title.length < 42
|
const sideBySide = config.sideBySide || about.length + title.length < 42
|
||||||
|
|
||||||
|
@ -286,7 +244,6 @@ export const MenuSliderInput = ({
|
||||||
setReset,
|
setReset,
|
||||||
children,
|
children,
|
||||||
changed,
|
changed,
|
||||||
i18n,
|
|
||||||
state,
|
state,
|
||||||
Design,
|
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'
|
import React, { useMemo } from 'react'
|
||||||
// Components
|
// Components
|
||||||
import { MenuButtonGroup } from './Container.mjs'
|
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
|
* The test design options menu
|
||||||
|
@ -87,12 +87,12 @@ const SampleOptionButton = ({ name, i18n, update }) => (
|
||||||
}
|
}
|
||||||
onClick={() => update.settings('sample', { type: 'option', option: name })}
|
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>
|
<span>{i18n.en?.o[name]?.t ?? name}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
const SampleMeasurementButton = ({ name, i18n, update }) => (
|
const SampleMeasurementButton = ({ name, update }) => (
|
||||||
<button
|
<button
|
||||||
className={
|
className={
|
||||||
'tw:daisy-btn tw:daisy-btn-outline tw:daisy-btn-sm tw:mx-2 ' +
|
'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 })}
|
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>
|
<span>{measurementsTranslations[name]}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const UiPreferencesMenu = ({ update, state, Design }) => {
|
||||||
}
|
}
|
||||||
const values = {
|
const values = {
|
||||||
aside: MenuListValue,
|
aside: MenuListValue,
|
||||||
ux: (props) => <span>{state.ui.ux}/5</span>,
|
ux: () => <span>{state.ui.ux}/5</span>,
|
||||||
rotate: MenuListValue,
|
rotate: MenuListValue,
|
||||||
renderer: MenuListValue,
|
renderer: MenuListValue,
|
||||||
}
|
}
|
||||||
|
@ -53,5 +53,11 @@ export const UiPreferencesMenu = ({ update, state, Design }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UiPreference = ({ name, ux, ...rest }) => (
|
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
|
* Displays the value for core's only setting
|
||||||
*
|
*
|
||||||
* @param {object} config - The option config
|
|
||||||
* @param {number} current - The current (count) value
|
* @param {number} current - The current (count) value
|
||||||
* @param {bool} changed - Whether or not the value is non-default
|
* @param {bool} changed - Whether or not the value is non-default
|
||||||
*/
|
*/
|
||||||
export const MenuOnlySettingValue = ({ current, config }) => (
|
export const MenuOnlySettingValue = ({ current }) => (
|
||||||
<MenuHighlightValue changed={current !== undefined}>
|
<MenuHighlightValue changed={current !== undefined}>
|
||||||
{current === undefined ? '-' : current.length}
|
{current === undefined ? '-' : current.length}
|
||||||
</MenuHighlightValue>
|
</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
|
* 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} props - All the props
|
||||||
* @param {Object} designs - Object holding all designs
|
|
||||||
* @param {Object} update - ViewWrapper state update object
|
* @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">
|
<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>
|
<h1>Choose a design from the FreeSewing collection</h1>
|
||||||
<Collection
|
<Collection
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { linkClasses, capitalize } from '@freesewing/utils'
|
import { linkClasses } from '@freesewing/utils'
|
||||||
|
// Context
|
||||||
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
// Components
|
// Components
|
||||||
import { H1, H5 } from '@freesewing/react/components/Heading'
|
import { H1, H2 } from '@freesewing/react/components/Heading'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { modalDocsHelp } from '@freesewing/react/components/Help'
|
||||||
import { HeaderMenu } from '../HeaderMenu.mjs'
|
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||||
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the docs view, it just shows content
|
* 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
|
* @param {Object} props.update - Helper object for updating the editor state
|
||||||
*/
|
*/
|
||||||
export const DocsView = ({ state, config, update }) => {
|
export const DocsView = ({ state, config, update }) => {
|
||||||
|
const { setModal, modalContent } = useContext(ModalContext)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderMenu state={state} {...{ config, update }} />
|
<HeaderMenu state={state} {...{ config, update }} />
|
||||||
<div className="tw:m-auto tw:mt-8 tw:max-w-2xl tw:px-4 tw:mb-8">
|
<div className="tw:m-auto tw:mt-8 tw:max-w-2xl tw:px-4 tw:mb-8">
|
||||||
<H1>Documentation</H1>
|
<H1>Documentation</H1>
|
||||||
{state?.design ? (
|
{state?.design ? (
|
||||||
<Popout link>
|
<>
|
||||||
<H5>Design Documentation</H5>
|
<H2>Design Documentation</H2>
|
||||||
<p className="tw:text-lg">
|
<Popout type="link" compact dense>
|
||||||
You can find documentation for the {capitalize(state.design)} design at:
|
<div className="tw:font-bold tw:py-1">
|
||||||
<br />
|
|
||||||
<b>
|
|
||||||
<a
|
<a
|
||||||
className={linkClasses}
|
className={linkClasses}
|
||||||
href={`https://freesewing.eu/docs/designs/${state.design}`}
|
href={`https://freesewing.eu/docs/designs/${state.design}`}
|
||||||
>{`FreeSewing.eu/docs/designs/${state.design}`}</a>
|
>{`FreeSewing.eu/docs/designs/${state.design}`}</a>
|
||||||
</b>
|
</div>
|
||||||
</p>
|
</Popout>
|
||||||
</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}
|
) : null}
|
||||||
<Popout link>
|
<H2>Editor Documentation</H2>
|
||||||
<H5>Understanding the FreeSewing Pattern Editor</H5>
|
<Popout type="link" compact dense>
|
||||||
<p className="tw:text-lg">
|
<div className="tw:font-bold tw:py-1">
|
||||||
Please refer to the pattern editor documentation at:
|
<a
|
||||||
<br />
|
className={linkClasses}
|
||||||
<b>
|
href="https://freesewing.eu/docs/editor"
|
||||||
<a
|
>{`FreeSewing.eu/docs/editor`}</a>
|
||||||
className={linkClasses}
|
</div>
|
||||||
href="https://freesewing.eu/docs/about/editor"
|
|
||||||
>{`FreeSewing.eu/docs/about/editor`}</a>
|
|
||||||
</b>
|
|
||||||
</p>
|
|
||||||
</Popout>
|
</Popout>
|
||||||
<Popout tip>
|
<button
|
||||||
<H5>
|
className="tw:daisy-btn tw:daisy-btn-secondary tw:daisy-btn-outline tw:mt-4"
|
||||||
Looking for info on how it <em>really</em> works?
|
onClick={() => modalDocsHelp(`docs/editor`, setModal)}
|
||||||
</H5>
|
>
|
||||||
<p>
|
Open without leaving the Editor
|
||||||
Documentation for developers and contributors is available at{' '}
|
</button>
|
||||||
<b>
|
<H2>Developer Documentation</H2>
|
||||||
<a className={linkClasses} href="https://freesewing.dev/">{`FreeSewing.dev`}</a>
|
<Popout type="link" compact>
|
||||||
</b>
|
<b>
|
||||||
</p>
|
<a className={linkClasses} href="https://freesewing.dev/">{`FreeSewing.dev`}</a>
|
||||||
|
</b>
|
||||||
</Popout>
|
</Popout>
|
||||||
</div>
|
</div>
|
||||||
|
{modalContent}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export const DraftErrorHandler = ({ failure, errors }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popout error>
|
<Popout type="error">
|
||||||
<p>
|
<p>
|
||||||
Sorry, there were problems drafting your pattern with the given measurements and options.
|
Sorry, there were problems drafting your pattern with the given measurements and options.
|
||||||
</p>
|
</p>
|
||||||
|
@ -27,8 +27,8 @@ export const DraftErrorHandler = ({ failure, errors }) => {
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you believe your measurements are correct and/or if you'd like further assistance, you
|
If you believe your measurements are correct and/or if you'd like further assistance,
|
||||||
can ask for help <Link href="https://forum.freesewing.eu">on our forum</Link>,{' '}
|
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://discord.freesewing.org">our Discord server</Link>, or{' '}
|
||||||
<Link href="https://codeberg.org/freesewing/freesewing/issues">report an issue</Link>.
|
<Link href="https://codeberg.org/freesewing/freesewing/issues">report an issue</Link>.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
import { bundlePatternTranslations, draft, missingMeasurements } from '../../lib/index.mjs'
|
import { bundlePatternTranslations, draft, missingMeasurements } from '../../lib/index.mjs'
|
||||||
import { colors, darkColors } from '@freesewing/plugin-theme'
|
import { colors, darkColors } from '@freesewing/plugin-theme'
|
||||||
|
// Context
|
||||||
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
// Hooks
|
// Hooks
|
||||||
import { useColorMode } from '@docusaurus/theme-common'
|
import { useColorMode } from '@docusaurus/theme-common'
|
||||||
// Components
|
// Components
|
||||||
|
@ -29,6 +31,7 @@ import { translateStrings } from '../../../Pattern/index.mjs'
|
||||||
* @return {function} DraftView - React component
|
* @return {function} DraftView - React component
|
||||||
*/
|
*/
|
||||||
export const DraftView = ({ Design, state, update, config, plugins = [], PluginOutput = Null }) => {
|
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
|
* 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}
|
rotate={state.ui.rotate}
|
||||||
update={update}
|
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
|
* @param {Object} props.update - Helper object for updating the editor state
|
||||||
*/
|
*/
|
||||||
export const EditSettingsView = (props) => {
|
export const EditSettingsView = (props) => {
|
||||||
const [settings, setSettings] = useState(props.state?.settings || {})
|
|
||||||
const { state, config, update } = props
|
const { state, config, update } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -96,7 +95,7 @@ export const PrimedSettingsEditor = (props) => {
|
||||||
setSettings(newSettings)
|
setSettings(newSettings)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// This is fine
|
console.log(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import React, { useState } from 'react'
|
||||||
// Components
|
// Components
|
||||||
import { H1, H2, H3, H5 } from '@freesewing/react/components/Heading'
|
import { H1, H2, H3, H5 } from '@freesewing/react/components/Heading'
|
||||||
import { HeaderMenu } from '../HeaderMenu.mjs'
|
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 { Highlight } from '@freesewing/react/components/Highlight'
|
||||||
import { EditIcon, CodeIcon, TipIcon, PrintIcon } from '@freesewing/react/components/Icon'
|
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) => {
|
export const ExportView = (props) => {
|
||||||
const { config, state, update } = props
|
const { config, state, update } = props
|
||||||
const { settings = {} } = state // Guard against undefined settings
|
const { settings = {} } = state // Guard against undefined settings
|
||||||
const [link, setLink] = useState(false)
|
const setLink = useState(false)[1]
|
||||||
const [format, setFormat] = useState(false)
|
const setFormat = useState(false)[1]
|
||||||
|
|
||||||
const { protocol, hostname, port } = window.location
|
const { protocol, port } = window.location
|
||||||
const site =
|
const site =
|
||||||
(protocol === 'https:' && port === 443) || (protocol === 'http:' && port === 80)
|
(protocol === 'https:' && port === 443) || (protocol === 'http:' && port === 80)
|
||||||
? `${window.location.protocol}//${window.location.hostname}`
|
? `${window.location.protocol}//${window.location.hostname}`
|
||||||
|
@ -58,10 +58,26 @@ export const ExportView = (props) => {
|
||||||
<H2>Share your pattern</H2>
|
<H2>Share your pattern</H2>
|
||||||
<p>If you merely want to share your pattern with others, you can copy these URLs:</p>
|
<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 ">
|
<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
|
Pattern and Measurements
|
||||||
</CopyToClipboardButton>
|
</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
|
Pattern only
|
||||||
</CopyToClipboardButton>
|
</CopyToClipboardButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -102,6 +118,7 @@ export const ExportView = (props) => {
|
||||||
<H3>ISO paper sizes</H3>
|
<H3>ISO paper sizes</H3>
|
||||||
{['a4', 'a3', 'a2', 'a1', 'a0'].map((format) => (
|
{['a4', 'a3', 'a2', 'a1', 'a0'].map((format) => (
|
||||||
<button
|
<button
|
||||||
|
key={format}
|
||||||
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
||||||
onClick={() => exportPattern({ ...exportProps, format })}
|
onClick={() => exportPattern({ ...exportProps, format })}
|
||||||
>
|
>
|
||||||
|
@ -114,6 +131,7 @@ export const ExportView = (props) => {
|
||||||
<H3>Other paper sizes</H3>
|
<H3>Other paper sizes</H3>
|
||||||
{['letter', 'legal', 'tabloid'].map((format) => (
|
{['letter', 'legal', 'tabloid'].map((format) => (
|
||||||
<button
|
<button
|
||||||
|
key={format}
|
||||||
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
||||||
onClick={() => exportPattern({ ...exportProps, format })}
|
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">
|
<div className="tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-2 tw:mt-2">
|
||||||
{['svg', 'pdf'].map((format) => (
|
{['svg', 'pdf'].map((format) => (
|
||||||
<button
|
<button
|
||||||
|
key={format}
|
||||||
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
||||||
onClick={() => exportPattern({ ...exportProps, format })}
|
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">
|
<div className="tw:grid tw:grid-cols-1 tw:lg:grid-cols-2 tw:gap-2 tw:mt-2">
|
||||||
{['json', 'yaml'].map((format) => (
|
{['json', 'yaml'].map((format) => (
|
||||||
<button
|
<button
|
||||||
|
key={format}
|
||||||
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
|
||||||
onClick={() => exportPattern({ ...exportProps, format })}
|
onClick={() => exportPattern({ ...exportProps, format })}
|
||||||
>
|
>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { DraftErrorHandler } from './DraftErrorHandler.mjs'
|
||||||
|
|
||||||
export const LayoutView = (props) => {
|
export const LayoutView = (props) => {
|
||||||
const { config, state, update, Design } = props
|
const { config, state, update, Design } = props
|
||||||
const { ui, settings } = state
|
const { settings } = state
|
||||||
const defaultSettings = defaultPrintSettings(settings?.units)
|
const defaultSettings = defaultPrintSettings(settings?.units)
|
||||||
|
|
||||||
// Settings for the tiler plugin
|
// Settings for the tiler plugin
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
import { designMeasurements } from '../../lib/index.mjs'
|
import { designMeasurements } from '../../lib/index.mjs'
|
||||||
import { capitalize, horFlexClasses as horFlexClasses } from '@freesewing/utils'
|
import { capitalize, horFlexClasses as horFlexClasses } from '@freesewing/utils'
|
||||||
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
import { measurements as measurementsTranslations } from '@freesewing/i18n'
|
||||||
|
// Context
|
||||||
|
import { ModalContext } from '@freesewing/react/context/Modal'
|
||||||
// Hooks
|
// Hooks
|
||||||
import React, { Fragment, useState, useEffect } from 'react'
|
import React, { Fragment, useState, useEffect, useContext } from 'react'
|
||||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
// Components
|
// Components
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
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 {Array} props.missingMeasurements - List of missing measurements for the current design
|
||||||
* @param {Object} props.state - The editor state object
|
* @param {Object} props.state - The editor state object
|
||||||
* @param {Object} props.update - Helper object for updating the editor state
|
* @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
|
* @return {Function} MeasurementsView - React component
|
||||||
*/
|
*/
|
||||||
export const MeasurementsView = ({
|
export const MeasurementsView = ({
|
||||||
|
@ -47,8 +48,8 @@ export const MeasurementsView = ({
|
||||||
state,
|
state,
|
||||||
update,
|
update,
|
||||||
design,
|
design,
|
||||||
measurementHelpProvider = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
|
const { modalContent } = useContext(ModalContext)
|
||||||
/*
|
/*
|
||||||
* If there is no view set, completing measurements will switch to the view picker
|
* 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.
|
* Which is a bit confusing. So in this case, set the view to measurements.
|
||||||
|
@ -170,10 +171,7 @@ export const MeasurementsView = ({
|
||||||
</div>
|
</div>
|
||||||
<p className="tw:text-left">You can manually set or override measurements below.</p>
|
<p className="tw:text-left">You can manually set or override measurements below.</p>
|
||||||
</Fragment>,
|
</Fragment>,
|
||||||
<MeasurementsEditor
|
<MeasurementsEditor key={2} {...{ Design, config, update, state }} />,
|
||||||
key={2}
|
|
||||||
{...{ Design, config, update, state, helpProvider: measurementHelpProvider }}
|
|
||||||
/>,
|
|
||||||
'edit',
|
'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">
|
<div className="tw:max-w-7xl tw:mt-8 tw:mx-auto tw:px-4 tw:mb-4">
|
||||||
<H1>Measurements</H1>
|
<H1>Measurements</H1>
|
||||||
{missingMeasurements && missingMeasurements.length > 0 ? (
|
{missingMeasurements && missingMeasurements.length > 0 ? (
|
||||||
<Popout note dense noP>
|
<Popout type="note" dense>
|
||||||
<h3>
|
<h3>
|
||||||
To generate this pattern, we need {missingMeasurements.length} additional measurement
|
To generate this pattern, we need {missingMeasurements.length} additional measurement
|
||||||
{missingMeasurements.length === 1 ? '' : 's'}:
|
{missingMeasurements.length === 1 ? '' : 's'}:
|
||||||
|
@ -198,7 +196,7 @@ export const MeasurementsView = ({
|
||||||
</ol>
|
</ol>
|
||||||
</Popout>
|
</Popout>
|
||||||
) : (
|
) : (
|
||||||
<Popout tip dense noP>
|
<Popout type="tip" dense>
|
||||||
<H5>We have all required measurements to draft this pattern</H5>
|
<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">
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-2 tw:mt-2">
|
||||||
<button
|
<button
|
||||||
|
@ -218,6 +216,7 @@ export const MeasurementsView = ({
|
||||||
)}
|
)}
|
||||||
{items.length > 1 ? <Accordion items={items} /> : items}
|
{items.length > 1 ? <Accordion items={items} /> : items}
|
||||||
</div>
|
</div>
|
||||||
|
{modalContent}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||||
// Components
|
// Components
|
||||||
import { RoleBlock } from '@freesewing/react/components/Role'
|
import { RoleBlock } from '@freesewing/react/components/Role'
|
||||||
import { Popout } from '@freesewing/react/components/Popout'
|
import { Popout } from '@freesewing/react/components/Popout'
|
||||||
import { StringInput } from '@freesewing/react/components/Input'
|
import { StringInput, MarkdownInput } from '@freesewing/react/components/Input'
|
||||||
import { SaveAsIcon } from '@freesewing/react/components/Icon'
|
import { SaveAsIcon, SaveIcon } from '@freesewing/react/components/Icon'
|
||||||
import { H1 } from '@freesewing/react/components/Heading'
|
import { H1 } from '@freesewing/react/components/Heading'
|
||||||
import { Link, SuccessLink } from '@freesewing/react/components/Link'
|
import { Link, SuccessLink } from '@freesewing/react/components/Link'
|
||||||
import { HeaderMenu } from '../HeaderMenu.mjs'
|
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||||
|
@ -67,7 +67,8 @@ export const SaveView = ({ config, state, update }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const savePattern = async () => {
|
const savePattern = async () => {
|
||||||
setLoadingStatus([true, 'Saving pattern...'])
|
const loadingId = 'savePattern'
|
||||||
|
update.startLoading(loadingId)
|
||||||
const patternData = {
|
const patternData = {
|
||||||
design: state.design,
|
design: state.design,
|
||||||
settings,
|
settings,
|
||||||
|
@ -77,17 +78,10 @@ export const SaveView = ({ config, state, update }) => {
|
||||||
}
|
}
|
||||||
const result = await backend.updatePattern(saveAs.pattern, patternData)
|
const result = await backend.updatePattern(saveAs.pattern, patternData)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
//setLoadingStatus([
|
update.stopLoading(loadingId)
|
||||||
// true,
|
|
||||||
// <>
|
|
||||||
// {t('status:patternSaved')} <small>[#{saveAs.pattern}]</small>
|
|
||||||
// </>,
|
|
||||||
// true,
|
|
||||||
// true,
|
|
||||||
//])
|
|
||||||
setSavedId(saveAs.pattern)
|
setSavedId(saveAs.pattern)
|
||||||
update.notify({ color: 'success', msg: 'boom' }, saveAs.pattern)
|
update.notifySuccess('Pattern saved', loadingId)
|
||||||
} //else setLoadingStatus([true, 'backendError', true, false])
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -98,17 +92,17 @@ export const SaveView = ({ config, state, update }) => {
|
||||||
<>
|
<>
|
||||||
<h2>Save Pattern</h2>
|
<h2>Save Pattern</h2>
|
||||||
{savedId && (
|
{savedId && (
|
||||||
<Popout link>
|
<Popout type="link">
|
||||||
<h5>Pattern Saved</h5>
|
<h5>Pattern Saved</h5>
|
||||||
See: <Link href={`/account/patterns/${savedId}`}>/account/patterns/{savedId}</Link>
|
See: <Link href={`/account/patterns/${savedId}`}>/account/patterns/{savedId}</Link>
|
||||||
</Popout>
|
</Popout>
|
||||||
)}
|
)}
|
||||||
<button
|
<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}
|
onClick={savePattern}
|
||||||
>
|
>
|
||||||
<SaveIcon className="tw:h-8 tw:w-8" />
|
<SaveIcon className="tw:h-8 tw:w-8" />
|
||||||
Save Patter #{saveAs.pattern}
|
Save Pattern #{saveAs.pattern}
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -145,11 +139,7 @@ export const SaveView = ({ config, state, update }) => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{withNotes ? (
|
{withNotes ? (
|
||||||
<Swizzled.components.MarkdownInput
|
<MarkdownInput label="Pattern notes" current={notes} update={setNotes} />
|
||||||
label="Pattern notes"
|
|
||||||
current={notes}
|
|
||||||
update={setNotes}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
<div className="tw:flex tw:flex-row tw:gap-2 tw:mt-8">
|
<div className="tw:flex tw:flex-row tw:gap-2 tw:mt-8">
|
||||||
<button
|
<button
|
||||||
|
@ -172,7 +162,7 @@ export const SaveView = ({ config, state, update }) => {
|
||||||
To access your saved patterns, go to:
|
To access your saved patterns, go to:
|
||||||
<b>
|
<b>
|
||||||
{' '}
|
{' '}
|
||||||
<Link href="/account/patterns">/account/patterns</Link>
|
<Link href="/account/data/patterns/">/account/data/patterns</Link>
|
||||||
</b>
|
</b>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { ZoomablePattern } from '../ZoomablePattern.mjs'
|
||||||
import { PatternLayout } from '../PatternLayout.mjs'
|
import { PatternLayout } from '../PatternLayout.mjs'
|
||||||
import { HeaderMenu } from '../HeaderMenu.mjs'
|
import { HeaderMenu } from '../HeaderMenu.mjs'
|
||||||
import { H1, H3, H4, H5 } from '@freesewing/react/components/Heading'
|
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
|
* 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'}
|
patternLocale={state.locale || 'en'}
|
||||||
rotate={state.ui.rotate}
|
rotate={state.ui.rotate}
|
||||||
strings={strings}
|
strings={strings}
|
||||||
rotate={state.ui.rotate}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,6 +88,8 @@ export const TestView = ({ Design, state, update, config }) => {
|
||||||
't',
|
't',
|
||||||
'ASC'
|
'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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -107,12 +109,13 @@ export const TestView = ({ Design, state, update, config }) => {
|
||||||
{trm.map(({ t, m }) => (
|
{trm.map(({ t, m }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
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={() =>
|
onClick={() =>
|
||||||
update.settings(['sample'], { type: 'measurement', measurement: m })
|
update.settings(['sample'], { type: 'measurement', measurement: m })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t}
|
<MeasurementsIcon className="tw:w-4 tw:h-4" />
|
||||||
|
<span>{t}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -123,12 +126,13 @@ export const TestView = ({ Design, state, update, config }) => {
|
||||||
{tom.map(({ t, m }) => (
|
{tom.map(({ t, m }) => (
|
||||||
<button
|
<button
|
||||||
key={m}
|
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={() =>
|
onClick={() =>
|
||||||
update.settings(['sample'], { type: 'measurement', measurement: m })
|
update.settings(['sample'], { type: 'measurement', measurement: m })
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t}
|
<MeasurementsIcon className="tw:w-4 tw:h-4" />
|
||||||
|
<span>{t}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -159,6 +163,8 @@ const SampleOptionsMenu = ({ Design, state, update }) => {
|
||||||
|
|
||||||
const SampleOptionsSubMenu = ({ structure, update, level = 1 }) => {
|
const SampleOptionsSubMenu = ({ structure, update, level = 1 }) => {
|
||||||
const output = []
|
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
|
* Show entries alphabetic, but force groups last, and advanced last among them
|
||||||
*/
|
*/
|
||||||
|
@ -176,10 +182,11 @@ const SampleOptionsSubMenu = ({ structure, update, level = 1 }) => {
|
||||||
output.push(
|
output.push(
|
||||||
<button
|
<button
|
||||||
key={name}
|
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 })}
|
onClick={() => update.settings(['sample'], { type: 'option', option: name })}
|
||||||
>
|
>
|
||||||
{struct.title}
|
<OptionsIcon className="tw:w-4 tw:h-4" />
|
||||||
|
<span>{struct.title}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import * as echarts from 'echarts'
|
|
||||||
import { timingPlugin } from '@freesewing/plugin-timing'
|
import { timingPlugin } from '@freesewing/plugin-timing'
|
||||||
// Components
|
// Components
|
||||||
import { ChartWrapper } from '@freesewing/react/components/Echart'
|
import { ChartWrapper } from '@freesewing/react/components/Echart'
|
||||||
|
@ -36,18 +35,6 @@ const TimingHeader = ({ timing, parts }) => {
|
||||||
) : null
|
) : 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) => {
|
const timeScore = (took) => {
|
||||||
if (took < 25) return 'Very Fast'
|
if (took < 25) return 'Very Fast'
|
||||||
if (took < 50) return 'Fast'
|
if (took < 50) return 'Fast'
|
||||||
|
@ -58,7 +45,6 @@ const timeScore = (took) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const option = (parts, data, took, setData) => ({
|
const option = (parts, data, took, setData) => ({
|
||||||
//color: colors.map((color) => resolveColor(color)),
|
|
||||||
title: {
|
title: {
|
||||||
text: `Timing of most recent draft: ${timeScore(took)}`,
|
text: `Timing of most recent draft: ${timeScore(took)}`,
|
||||||
left: 'center',
|
left: 'center',
|
||||||
|
@ -127,7 +113,7 @@ const option = (parts, data, took, setData) => ({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
yAxis: [{ type: 'value' }],
|
yAxis: [{ type: 'value' }],
|
||||||
series: parts.map((name, i) => ({
|
series: parts.map((name) => ({
|
||||||
name,
|
name,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
stack: 'Total',
|
stack: 'Total',
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const UndosView = ({ Design, update, state, config }) => {
|
||||||
<H1>Undo History</H1>
|
<H1>Undo History</H1>
|
||||||
<p className="tw:mb-4">Time-travel through your recent pattern changes.</p>
|
<p className="tw:mb-4">Time-travel through your recent pattern changes.</p>
|
||||||
{steps.length < 1 ? (
|
{steps.length < 1 ? (
|
||||||
<Popout note>
|
<Popout type="note">
|
||||||
<h4>Your undo history is currently empty</h4>
|
<h4>Your undo history is currently empty</h4>
|
||||||
<p>When you make changes to your pattern, they will show up here.</p>
|
<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>
|
<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 (
|
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._)}>
|
<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">
|
<div className="tw:flex tw:flex-col tw:font-medium tw:items-end tw:w-full tw:-mb-2">
|
||||||
<span className="tw:flex tw:flex-row tw:gap-2 tw:items-center">
|
<div className="tw:text-sm tw:-mt-2 tw:italic">
|
||||||
{data.fieldIcon || null}
|
<UndoStepTimeAgo step={step} />
|
||||||
{data.title}
|
</div>
|
||||||
</span>
|
<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">
|
<span className="tw:opacity-70 tw:flex tw:flex-row tw:gap-1 tw:items-center tw:text-base">
|
||||||
{data.icon || null} {data.menu}
|
{data.menu}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
<span>»</span>
|
||||||
<div className="tw:flex tw:flex-row tw:gap-1 tw:items-center tw:align-start tw:w-full">
|
<span className="tw:flex tw:flex-row tw:gap-2 tw:items-center">{data.title}</span>
|
||||||
{data.msg ? (
|
</div>
|
||||||
data.msg
|
<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>
|
<span className="">
|
||||||
<LeftIcon className="tw:w-4 tw:h-4 tw:text-secondary tw:shrink-0" stroke={4} />
|
{Array.isArray(data.newVal) ? data.newVal.join(', ') : data.newVal}
|
||||||
<span className="tw:line-through tw:decoration-1 tw:opacity-70">
|
</span>
|
||||||
{Array.isArray(data.oldVal) ? data.oldVal.join(', ') : data.oldVal}
|
<LeftIcon className="tw:w-4 tw:h-4 tw:text-secondary tw:shrink-0" stroke={4} />
|
||||||
</span>
|
<span className="tw:line-through tw:decoration-1 tw:opacity-70">
|
||||||
</>
|
{Array.isArray(data.oldVal) ? data.oldVal.join(', ') : data.oldVal}
|
||||||
)}
|
</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ButtonFrame>
|
</ButtonFrame>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -28,7 +28,7 @@ export const ViewPicker = ({ Design, update, state }) => {
|
||||||
.map((view) => (
|
.map((view) => (
|
||||||
<MainCard key={view} {...{ view, update, Design }} />
|
<MainCard key={view} {...{ view, update, Design }} />
|
||||||
))}
|
))}
|
||||||
<Popout note>
|
<Popout type="note">
|
||||||
<div className="tw:text-left">
|
<div className="tw:text-left">
|
||||||
<H5>pe:measurementsFreeViewsOnly.t:</H5>
|
<H5>pe:measurementsFreeViewsOnly.t:</H5>
|
||||||
<p>pe:measurementsFreeViewsOnly.d</p>
|
<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]
|
const Icon = viewIcons[view]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -37,6 +37,7 @@ export const useEditorState = (init = {}, setEphemeralState, config) => {
|
||||||
if (typeof data.s === 'object') setState(data.s)
|
if (typeof data.s === 'object') setState(data.s)
|
||||||
else setState(init)
|
else setState(init)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
setState(init)
|
setState(init)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,31 +46,6 @@ export const useEditorState = (init = {}, setEphemeralState, config) => {
|
||||||
return [state, setState, update]
|
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() {
|
function getHashData() {
|
||||||
if (!window) return false
|
if (!window) return false
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,18 @@ import { useAccount } from '../../hooks/useAccount/index.mjs'
|
||||||
* FreeSewing's pattern editor
|
* FreeSewing's pattern editor
|
||||||
*
|
*
|
||||||
* Editor is the high-level FreeSewing component
|
* 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,
|
* 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 - All React props
|
||||||
* @param {object} props.config - A configuration object for the editor
|
* @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.design = false] - A design name to preset the editor to use this design
|
||||||
* @param {object} props.preload - Any state to preload
|
* @param {object} [props.preload = {}] - Any state to preload
|
||||||
* @param {function} props.setTitle - A way to set the page title (optional)
|
* @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 (optional)
|
* @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 = ({
|
export const Editor = ({
|
||||||
config = {},
|
config = {},
|
||||||
|
@ -161,7 +163,7 @@ export const Editor = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Helper method to figure out what view to load
|
* Helper method to figure out what view to load
|
||||||
* based on the props passed in, and destructure
|
* based on the props passed in, and destructure
|
||||||
* the props we need for it.
|
* the props we need for it.
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import { defaultConfig as config } from '../config/index.mjs'
|
import { defaultConfig as config } from '../config/index.mjs'
|
||||||
import { measurementAsMm } from '@freesewing/utils'
|
import { measurementAsMm } from '@freesewing/utils'
|
||||||
import { linkClasses } from '@freesewing/utils'
|
|
||||||
/*
|
/*
|
||||||
* Components
|
* Components
|
||||||
* Note that these are only used as returns values
|
* Note that these are only used as returns values
|
||||||
|
@ -61,12 +61,6 @@ export function menuCoreSettingsSaboolHandler({ toggleSa }) {
|
||||||
return 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({
|
export function menuCoreSettingsStructure({
|
||||||
units = 'metric',
|
units = 'metric',
|
||||||
sabool = false,
|
sabool = false,
|
||||||
|
@ -77,12 +71,7 @@ export function menuCoreSettingsStructure({
|
||||||
sabool: {
|
sabool: {
|
||||||
dense: true,
|
dense: true,
|
||||||
title: 'Include seam allowance',
|
title: 'Include seam allowance',
|
||||||
about: (
|
about: <>Controls whether or not you want to include seam allowance on your pattern.</>,
|
||||||
<>
|
|
||||||
Controls whether or not you want to include seam allowance on your pattern.
|
|
||||||
<CoreDocsLink item="sabool" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
ux: config.uxLevels.core.sa,
|
ux: config.uxLevels.core.sa,
|
||||||
list: [0, 1],
|
list: [0, 1],
|
||||||
choiceTitles: {
|
choiceTitles: {
|
||||||
|
@ -99,12 +88,7 @@ export function menuCoreSettingsStructure({
|
||||||
samm: sabool
|
samm: sabool
|
||||||
? {
|
? {
|
||||||
title: 'Seam Allowance Size',
|
title: 'Seam Allowance Size',
|
||||||
about: (
|
about: <>Controls the size of the pattern's seam allowance.</>,
|
||||||
<>
|
|
||||||
Controls the size of the pattern's seam allowance.
|
|
||||||
<CoreDocsLink item="sa" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
ux: config.uxLevels.core.sa,
|
ux: config.uxLevels.core.sa,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: units === 'imperial' ? 25.4 : 25, // values are in mm
|
max: units === 'imperial' ? 25.4 : 25, // values are in mm
|
||||||
|
@ -115,12 +99,7 @@ export function menuCoreSettingsStructure({
|
||||||
units: {
|
units: {
|
||||||
dense: true,
|
dense: true,
|
||||||
title: 'Pattern units',
|
title: 'Pattern units',
|
||||||
about: (
|
about: <>Allows you to switch between metric and imperial units on the pattern.</>,
|
||||||
<>
|
|
||||||
Allows you to switch between metric and imperial units on the pattern.
|
|
||||||
<CoreDocsLink item="units" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
ux: config.uxLevels.core.units,
|
ux: config.uxLevels.core.units,
|
||||||
list: ['metric', 'imperial'],
|
list: ['metric', 'imperial'],
|
||||||
dflt: accountUnits,
|
dflt: accountUnits,
|
||||||
|
@ -141,7 +120,6 @@ export function menuCoreSettingsStructure({
|
||||||
<>
|
<>
|
||||||
Trees are awesome, and taping together sewing patterns is not much fun. Try our paperless
|
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.
|
mode to avoid the need to print out your pattern altogether.
|
||||||
<CoreDocsLink item="paperless" />
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
ux: config.uxLevels.core.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
|
Controls how detailed the pattern is; Either a complete pattern with all details, or a
|
||||||
basic outline of the pattern parts.
|
basic outline of the pattern parts.
|
||||||
<CoreDocsLink item="complete" />
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
ux: config.uxLevels.core.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
|
Controls efforts to save paper. Disable this to expand all pattern parts at the cost of
|
||||||
using more space & paper.
|
using more space & paper.
|
||||||
<CoreDocsLink item="expand" />
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
ux: config.uxLevels.core.expand,
|
ux: config.uxLevels.core.expand,
|
||||||
|
@ -206,12 +182,7 @@ export function menuCoreSettingsStructure({
|
||||||
only: {
|
only: {
|
||||||
dense: true,
|
dense: true,
|
||||||
title: 'Only included selected pattern parts',
|
title: 'Only included selected pattern parts',
|
||||||
about: (
|
about: <>Allows you to control what parts to include in your pattern.</>,
|
||||||
<>
|
|
||||||
Allows you to control what parts to include in your pattern.
|
|
||||||
<CoreDocsLink item="only" />
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
ux: config.uxLevels.core.only,
|
ux: config.uxLevels.core.only,
|
||||||
dflt: false,
|
dflt: false,
|
||||||
list: parts,
|
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
|
Allows you to control the scale of annotations on the pattern. This is most useful when
|
||||||
generating very small patterns, like for doll outfits.
|
generating very small patterns, like for doll outfits.
|
||||||
<CoreDocsLink item="scale" />
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
ux: config.uxLevels.core.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
|
Controls the gap between pattern parts, as well as the gap between the parts and the page
|
||||||
edge.
|
edge.
|
||||||
<CoreDocsLink item="margin" />
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
ux: config.uxLevels.core.margin,
|
ux: config.uxLevels.core.margin,
|
||||||
|
|
|
@ -2,17 +2,6 @@ import React from 'react'
|
||||||
import { mergeOptions } from '@freesewing/core'
|
import { mergeOptions } from '@freesewing/core'
|
||||||
import { designOptionType, set, orderBy } from '@freesewing/utils'
|
import { designOptionType, set, orderBy } from '@freesewing/utils'
|
||||||
import { i18n } from '@freesewing/collection'
|
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) {
|
export function menuDesignOptionsStructure(design, options, settings, asFullList = false) {
|
||||||
if (!options) return options
|
if (!options) return options
|
||||||
|
@ -23,12 +12,7 @@ export function menuDesignOptionsStructure(design, options, settings, asFullList
|
||||||
...option,
|
...option,
|
||||||
name,
|
name,
|
||||||
title: i18n[design]?.en?.o?.[name]?.t || name,
|
title: i18n[design]?.en?.o?.[name]?.t || name,
|
||||||
about: (
|
about: <span>{i18n[design]?.en?.o?.[name]?.d || name}</span>,
|
||||||
<span>
|
|
||||||
{i18n[design]?.en?.o?.[name]?.d || name}
|
|
||||||
<DesignDocsLink item={name} design={design} />
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
dense: true,
|
dense: true,
|
||||||
sideBySide: true,
|
sideBySide: true,
|
||||||
}
|
}
|
||||||
|
@ -62,14 +46,14 @@ export function menuDesignOptionsStructure(design, options, settings, asFullList
|
||||||
option.valueTitles = {}
|
option.valueTitles = {}
|
||||||
option.choiceTitles = {}
|
option.choiceTitles = {}
|
||||||
option.choiceDescriptions = {}
|
option.choiceDescriptions = {}
|
||||||
for (const entry of option.list) {
|
option.list.forEach(() => {
|
||||||
option.choiceTitles.false = eno[`${option.name}No`]?.t || option.name
|
option.choiceTitles.false = eno[`${option.name}No`]?.t || option.name
|
||||||
option.choiceDescriptions.false = eno[`${option.name}No`]?.d || 'No'
|
option.choiceDescriptions.false = eno[`${option.name}No`]?.d || 'No'
|
||||||
option.valueTitles.false = 'No'
|
option.valueTitles.false = 'No'
|
||||||
option.choiceTitles.true = eno[`${option.name}Yes`]?.t || 'Yes'
|
option.choiceTitles.true = eno[`${option.name}Yes`]?.t || 'Yes'
|
||||||
option.choiceDescriptions.true = eno[`${option.name}Yes`]?.d || 'No'
|
option.choiceDescriptions.true = eno[`${option.name}Yes`]?.d || 'No'
|
||||||
option.valueTitles.true = 'Yes'
|
option.valueTitles.true = 'Yes'
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
if (typeof option.menu === 'function')
|
if (typeof option.menu === 'function')
|
||||||
option.menu = asFullList
|
option.menu = asFullList
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { defaultConfig } from '../config/index.mjs'
|
import { defaultConfig } from '../config/index.mjs'
|
||||||
import { round, formatMm, randomLoadingMessage } from '@freesewing/utils'
|
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 { menuUiPreferencesStructure } from './ui-preferences.mjs'
|
||||||
import { i18n } from '@freesewing/collection'
|
import { i18n } from '@freesewing/collection'
|
||||||
import { i18n as pluginI18n } from '@freesewing/core-plugins'
|
import { i18n as pluginI18n } from '@freesewing/core-plugins'
|
||||||
|
@ -320,7 +320,7 @@ export function initialEditorState(preload = {}, config) {
|
||||||
return initial
|
return initial
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* round a value to the correct number of decimal places to display all supplied digits after multiplication
|
* 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
|
* this is a workaround for floating point errors
|
||||||
* examples:
|
* examples:
|
||||||
|
@ -721,15 +721,15 @@ export function cloudImageUrl({ id = 'default-avatar', variant = 'public' }) {
|
||||||
/*
|
/*
|
||||||
* Return something default so that people will actually change it
|
* 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
|
* If the variant is invalid, set it to the smallest thumbnail so
|
||||||
* people don't load enourmous images by accident
|
* 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
|
* This method does nothing. It is used to disable certain methods
|
||||||
|
@ -746,25 +746,6 @@ export function noop() {
|
||||||
export function notEmpty(value) {
|
export function notEmpty(value) {
|
||||||
return String(value).length > 0
|
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
|
* 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 { themePlugin } from '@freesewing/plugin-theme'
|
||||||
import { pluginI18n } from '@freesewing/plugin-i18n'
|
import { pluginI18n } from '@freesewing/plugin-i18n'
|
||||||
import { tilerPlugin } from './plugin-tiler.mjs'
|
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 mustache from 'mustache'
|
||||||
import he from 'he'
|
import he from 'he'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
|
|
|
@ -28,7 +28,6 @@ import {
|
||||||
menuValueWasChanged,
|
menuValueWasChanged,
|
||||||
noop,
|
noop,
|
||||||
notEmpty,
|
notEmpty,
|
||||||
nsMerge,
|
|
||||||
objUpdate,
|
objUpdate,
|
||||||
sample,
|
sample,
|
||||||
settingsValueIsCustom,
|
settingsValueIsCustom,
|
||||||
|
@ -91,7 +90,6 @@ export {
|
||||||
menuValueWasChanged,
|
menuValueWasChanged,
|
||||||
noop,
|
noop,
|
||||||
notEmpty,
|
notEmpty,
|
||||||
nsMerge,
|
|
||||||
objUpdate,
|
objUpdate,
|
||||||
sample,
|
sample,
|
||||||
settingsValueIsCustom,
|
settingsValueIsCustom,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { linkClasses } from '@freesewing/utils'
|
|
||||||
import {
|
import {
|
||||||
CoverPageIcon,
|
CoverPageIcon,
|
||||||
PageMarginIcon,
|
PageMarginIcon,
|
||||||
|
@ -9,13 +8,6 @@ import {
|
||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
} from '@freesewing/react/components/Icon'
|
} 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) => ({
|
const defaultPrintSettings = (units) => ({
|
||||||
size: units === 'imperial' ? 'letter' : 'a4',
|
size: units === 'imperial' ? 'letter' : 'a4',
|
||||||
orientation: 'portrait',
|
orientation: 'portrait',
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { defaultConfig } from '../config/index.mjs'
|
import { defaultConfig } from '../config/index.mjs'
|
||||||
import { linkClasses } from '@freesewing/utils'
|
|
||||||
import { AsideIcon, RotateIcon, RocketIcon, UxIcon } from '@freesewing/react/components/Icon'
|
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() {
|
export function menuUiPreferencesStructure() {
|
||||||
const uiUx = defaultConfig.uxLevels.ui
|
const uiUx = defaultConfig.uxLevels.ui
|
||||||
const uiPreferences = {
|
const uiPreferences = {
|
||||||
|
@ -19,7 +12,6 @@ export function menuUiPreferencesStructure() {
|
||||||
<span>
|
<span>
|
||||||
Uses the right side of the screen for the Design Options, Core Settings, and UI
|
Uses the right side of the screen for the Design Options, Core Settings, and UI
|
||||||
Preferences menus.
|
Preferences menus.
|
||||||
<UiDocsLink item="aside" />
|
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
ux: uiUx.aside,
|
ux: uiUx.aside,
|
||||||
|
@ -35,10 +27,7 @@ export function menuUiPreferencesStructure() {
|
||||||
dense: true,
|
dense: true,
|
||||||
title: 'User Experience',
|
title: 'User Experience',
|
||||||
about: (
|
about: (
|
||||||
<span>
|
<span>Controls the user experience, from keep it simple, to give me all the powers.</span>
|
||||||
Controls the user experience, from keep it simple, to give me all the powers.
|
|
||||||
<UiDocsLink item="control" />
|
|
||||||
</span>
|
|
||||||
),
|
),
|
||||||
ux: uiUx.ux,
|
ux: uiUx.ux,
|
||||||
emoji: '🖥️',
|
emoji: '🖥️',
|
||||||
|
@ -63,12 +52,7 @@ export function menuUiPreferencesStructure() {
|
||||||
rotate: {
|
rotate: {
|
||||||
dense: true,
|
dense: true,
|
||||||
title: 'Rotate Pattern',
|
title: 'Rotate Pattern',
|
||||||
about: (
|
about: <span>Allows you to rotate your pattern 90 degrees, handy for tall patterns.</span>,
|
||||||
<span>
|
|
||||||
Allows you to rotate your pattern 90 degrees, handy for tall patterns.
|
|
||||||
<UiDocsLink item="rotate" />
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
ux: uiUx.rotate,
|
ux: uiUx.rotate,
|
||||||
list: [0, 1],
|
list: [0, 1],
|
||||||
choiceTitles: {
|
choiceTitles: {
|
||||||
|
@ -81,12 +65,7 @@ export function menuUiPreferencesStructure() {
|
||||||
renderer: {
|
renderer: {
|
||||||
dense: true,
|
dense: true,
|
||||||
title: 'Pattern render engine',
|
title: 'Pattern render engine',
|
||||||
about: (
|
about: <span>Change the underlying method for rendering the pattern on screen.</span>,
|
||||||
<span>
|
|
||||||
Change the underlying method for rendering the pattern on screen.
|
|
||||||
<UiDocsLink item="renderer" />
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
ux: uiUx.renderer,
|
ux: uiUx.renderer,
|
||||||
list: ['react', 'svg'],
|
list: ['react', 'svg'],
|
||||||
choiceTitles: {
|
choiceTitles: {
|
||||||
|
|
|
@ -1,35 +1,119 @@
|
||||||
import React from 'react'
|
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 }) => (
|
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">
|
<h1 className="tw:text-5xl tw:pt-5 tw:pb-4 tw:font-thin tw:tracking-tighter tw:lg:text-6xl">
|
||||||
{children}
|
{children}
|
||||||
</h1>
|
</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 }) => (
|
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">
|
<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}
|
{children}
|
||||||
</h2>
|
</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 }) => (
|
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">
|
<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}
|
{children}
|
||||||
</h3>
|
</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 }) => (
|
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">
|
<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}
|
{children}
|
||||||
</h4>
|
</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 }) => (
|
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">
|
<h5 className="tw:text-lg tw:py-1 tw:font-semibold tw:m-0 tw:tracking-tight tw:lg:text-xl">
|
||||||
{children}
|
{children}
|
||||||
</h5>
|
</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 }) => (
|
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">
|
<h6 className="tw:text-base tw:py-1 tw:font-medium tw:italic tw:m-0 tw:tracking-tight tw:lg:text-lg">
|
||||||
{children}
|
{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 React from 'react'
|
||||||
import { CopyToClipboardButton } from '@freesewing/react/components/CopyToClipboardButton'
|
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
|
||||||
|
|
||||||
const defaultTitles = {
|
const defaultTitles = {
|
||||||
js: 'Javascript',
|
js: 'Javascript',
|
||||||
|
@ -12,13 +12,17 @@ const defaultTitles = {
|
||||||
/**
|
/**
|
||||||
* A React component to highlight code
|
* A React component to highlight code
|
||||||
*
|
*
|
||||||
* @params {object} props - All React props
|
* @component
|
||||||
* @params {string} language - The language to highlight
|
* @param {object} props - All React props
|
||||||
* @params {object} children - The React children
|
* @param {string} [props.language = 'txt'] - The language to highlight
|
||||||
* @params {bool} raw - Set this to true to not escape tags
|
* @param {JSX.Element} props.children - The React children (this is the code to highlight)
|
||||||
* @params {string} title - Title for the highlight
|
* @param {bool} [props.raw = false] - Set this to true to not escape tags
|
||||||
* @params {string} copy - Content to copy to clipboard
|
* @param {string} [props.title = false] - Title for the highlight. When
|
||||||
* @params {bool} noCopy - Do not add copy to clipboard
|
* 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 = ({
|
export const Highlight = ({
|
||||||
language = 'txt',
|
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 { Highlight } from '@freesewing/react/components/Highlight'
|
||||||
import hljs from 'highlight.js/lib/common'
|
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) => {
|
export const Json = (props) => {
|
||||||
const code = props.js ? JSON.stringify(props.js, null, 2) : props.children
|
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 { copyToClipboard } from '@freesewing/utils'
|
||||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
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 = ({
|
export const KeyVal = ({
|
||||||
k,
|
k,
|
||||||
val,
|
val,
|
||||||
|
@ -10,7 +23,7 @@ export const KeyVal = ({
|
||||||
href = false,
|
href = false,
|
||||||
onClick = false,
|
onClick = false,
|
||||||
}) => {
|
}) => {
|
||||||
const [copied, setCopied] = useState(false)
|
const setCopied = useState(false)[1]
|
||||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||||
|
|
||||||
let colorClasses1 = primaryClasses1
|
let colorClasses1 = primaryClasses1
|
||||||
|
@ -25,6 +38,7 @@ export const KeyVal = ({
|
||||||
if (color === 'secondary') colorClasses2 = secondaryClasses2
|
if (color === 'secondary') colorClasses2 = secondaryClasses2
|
||||||
else if (color === 'neutral') colorClasses2 = neutralClasses2
|
else if (color === 'neutral') colorClasses2 = neutralClasses2
|
||||||
else if (color === 'accent') colorClasses2 = accentClasses2
|
else if (color === 'accent') colorClasses2 = accentClasses2
|
||||||
|
else if (color === 'info') colorClasses2 = infoClasses2
|
||||||
else if (color === 'warning') colorClasses2 = warningClasses2
|
else if (color === 'warning') colorClasses2 = warningClasses2
|
||||||
else if (color === 'success') colorClasses2 = successClasses2
|
else if (color === 'success') colorClasses2 = successClasses2
|
||||||
else if (color === 'error') colorClasses2 = errorClasses2
|
else if (color === 'error') colorClasses2 = errorClasses2
|
||||||
|
@ -69,7 +83,6 @@ export const KeyVal = ({
|
||||||
const LinkKeyVal = ({
|
const LinkKeyVal = ({
|
||||||
k,
|
k,
|
||||||
val,
|
val,
|
||||||
color = 'primary',
|
|
||||||
small = false,
|
small = false,
|
||||||
href = false,
|
href = false,
|
||||||
colorClasses1,
|
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 errorClasses1 = `tw:text-error-content tw:bg-error tw:border-error`
|
||||||
const errorClasses2 = `tw:text-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) => {
|
const handleCopied = (setCopied, setLoadingStatus, label) => {
|
||||||
setCopied(true)
|
setCopied(true)
|
||||||
setLoadingStatus([
|
setLoadingStatus([
|
||||||
|
|
|
@ -2,17 +2,18 @@ import React from 'react'
|
||||||
import { Breadcrumbs } from '@freesewing/react/components/Breadcrumbs'
|
import { Breadcrumbs } from '@freesewing/react/components/Breadcrumbs'
|
||||||
import { Link as DefaultLink } from '@freesewing/react/components/Link'
|
import { Link as DefaultLink } from '@freesewing/react/components/Link'
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* This is the default layout, including title and breadcrumbs
|
* This is the default layout, including title and breadcrumbs
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {array} props.children - The content to go in the layout
|
* @param {object} props - All component props
|
||||||
* @param {array} props.crumbs - Data for the breadcrumbs
|
* @param {function} [props.Link = false] - An optional framework specific Link component
|
||||||
* @param {string} props.description - The page description
|
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout
|
||||||
* @param {function} props.Link - An optional framework specific Link component
|
* @param {array} [props.crumbs = []] - Data for the breadcrumbs, see Breadcrumbs
|
||||||
* @param {string} props.title - The page title
|
* @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
|
if (!Link) Link = DefaultLink
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -26,11 +27,13 @@ export const Layout = ({ children = [], crumbs = [], description, Link = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* This is the base layout
|
* This is the base layout
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {array} props.children - The content to go in the layout
|
* @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 }) => (
|
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">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The left column of the base layout
|
* The left column of the base layout
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {array} props.children - The content to go in the layout
|
* @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 = [] }) => (
|
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">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The right column of the base layout
|
* The right column of the base layout
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {array} props.children - The content to go in the layout
|
* @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 = [] }) => (
|
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">
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The main column for prose (text like docs and so on)
|
* The main column for prose (text like docs and so on)
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {array} props.children - The content to go in the layout
|
* @param {object} props - All component props
|
||||||
* @param {array} props.wide - Whether or not to use the wide view
|
* @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 }) => (
|
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`}>
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* The central column for wide content (no max-width)
|
* The central column for wide content (no max-width)
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {array} props.children - The content to go in the layout
|
* @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 = [] }) => (
|
export const BaseLayoutWide = ({ children = [] }) => (
|
||||||
<div className="tw:grow tw:w-full tw:m-auto tw:my-8 tw:grow">{children}</div>
|
<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
|
* A layout for pages that do their own title/layout, like the sign in page
|
||||||
*
|
*
|
||||||
* @param {object} props - All React props
|
* @component
|
||||||
* @param {array} props.children - The content to go in the layout
|
* @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 }) => {
|
export const NoTitleLayout = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,9 +10,11 @@ const strokeScale = 0.5
|
||||||
/**
|
/**
|
||||||
* A linedrawing component for Aaron
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Aaron = ({ className, stroke = 1 }) => (
|
export const Aaron = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="-2 -15 152 152" {...{ stroke, className }}>
|
<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
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const AaronFront = ({ className, stroke = 1 }) => (
|
export const AaronFront = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="-23 0 119 119" {...{ className }}>
|
<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
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const AaronBack = ({ className, stroke = 1 }) => (
|
export const AaronBack = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="51 0 119 119" {...{ className }}>
|
<LineDrawingWrapper viewBox="51 0 119 119" {...{ className }}>
|
||||||
|
@ -50,7 +56,7 @@ export const AaronBack = ({ className, stroke = 1 }) => (
|
||||||
/*
|
/*
|
||||||
* SVG elements for the front
|
* SVG elements for the front
|
||||||
*/
|
*/
|
||||||
export const Front = ({ stroke }) => (
|
const Front = ({ stroke }) => (
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
key="stitches"
|
key="stitches"
|
||||||
|
|
|
@ -10,9 +10,11 @@ const strokeScale = 0.5
|
||||||
/**
|
/**
|
||||||
* A linedrawing component for Albert
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Albert = ({ className, stroke = 1 }) => (
|
export const Albert = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="-10 -2 146 146" {...{ className }}>
|
<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
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const AlbertFront = ({ className, stroke = 1 }) => (
|
export const AlbertFront = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="-41 -2 145 145" {...{ className }}>
|
<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
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const AlbertBack = ({ className, stroke = 1 }) => (
|
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} />
|
<Back stroke={stroke * strokeScale} />
|
||||||
</LineDrawingWrapper>
|
</LineDrawingWrapper>
|
||||||
)
|
)
|
||||||
|
@ -50,7 +56,7 @@ export const AlbertBack = ({ className, stroke = 1 }) => (
|
||||||
/*
|
/*
|
||||||
* SVG elements for the front
|
* SVG elements for the front
|
||||||
*/
|
*/
|
||||||
export const Front = ({ stroke }) => (
|
const Front = ({ stroke }) => (
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
key="stitches"
|
key="stitches"
|
||||||
|
|
|
@ -10,9 +10,11 @@ const strokeScale = 0.4
|
||||||
/**
|
/**
|
||||||
* A linedrawing component for Bee
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Bee = ({ className, stroke = 1 }) => (
|
export const Bee = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="-20 0 120 120" {...{ stroke, className }}>
|
<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
|
export const BeeFront = Bee
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SVG elements for the front
|
* SVG elements for the front
|
||||||
*/
|
*/
|
||||||
export const Front = ({ stroke }) => (
|
const Front = ({ stroke }) => (
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
key="stitches"
|
key="stitches"
|
||||||
|
|
|
@ -10,9 +10,11 @@ const strokeScale = 0.5
|
||||||
/**
|
/**
|
||||||
* A linedrawing component for Bella
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Bella = ({ className, stroke = 1 }) => (
|
export const Bella = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="2 -30 155 155" {...{ stroke, className }}>
|
<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
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const BellaFront = ({ className, stroke = 1 }) => (
|
export const BellaFront = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="-4 2 89 86" {...{ className, stroke }}>
|
<LineDrawingWrapper viewBox="-4 2 89 86" {...{ className, stroke }}>
|
||||||
|
@ -34,8 +38,14 @@ export const BellaFront = ({ className, stroke = 1 }) => (
|
||||||
</LineDrawingWrapper>
|
</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 = ({
|
export const BellaBack = ({
|
||||||
className = 'tw:w-full', // CSS classes to apply
|
className = 'tw:w-full', // CSS classes to apply
|
||||||
|
@ -54,7 +64,7 @@ export const BellaBack = ({
|
||||||
/*
|
/*
|
||||||
* SVG elements for the front
|
* SVG elements for the front
|
||||||
*/
|
*/
|
||||||
export const Front = ({ stroke }) => (
|
const Front = ({ stroke }) => (
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
key="stitches"
|
key="stitches"
|
||||||
|
|
|
@ -10,9 +10,11 @@ const strokeScale = 1.2
|
||||||
/**
|
/**
|
||||||
* A linedrawing component for Benjamin
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Benjamin = ({ className, stroke = 1 }) => (
|
export const Benjamin = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="0 -44 138 138" {...{ className, stroke }}>
|
<LineDrawingWrapper viewBox="0 -44 138 138" {...{ className, stroke }}>
|
||||||
|
@ -20,15 +22,21 @@ export const Benjamin = ({ className, stroke = 1 }) => (
|
||||||
</LineDrawingWrapper>
|
</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
|
export const BenjaminFront = Benjamin
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SVG elements for the front
|
* SVG elements for the front
|
||||||
*/
|
*/
|
||||||
export const Front = ({ stroke }) => (
|
const Front = ({ stroke }) => (
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
key="folds"
|
key="folds"
|
||||||
|
|
|
@ -10,12 +10,14 @@ const strokeScale = 0.7
|
||||||
/**
|
/**
|
||||||
* A linedrawing component for Bent
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const Bent = ({ className, stroke = 1 }) => (
|
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} />
|
<Front stroke={strokeScale * stroke} />
|
||||||
<Back stroke={strokeScale * stroke} />
|
<Back stroke={strokeScale * stroke} />
|
||||||
</LineDrawingWrapper>
|
</LineDrawingWrapper>
|
||||||
|
@ -24,9 +26,11 @@ export const Bent = ({ className, stroke = 1 }) => (
|
||||||
/**
|
/**
|
||||||
* A linedrawing component for the front of Bent
|
* 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 {string} props.className - Any CSS classes to apply
|
||||||
* @param {number} props.stroke - The stroke width to apply
|
* @param {number} props.stroke - The stroke width to apply
|
||||||
|
* @returns {JSX.Element}
|
||||||
*/
|
*/
|
||||||
export const BentFront = ({ className, stroke = 1 }) => (
|
export const BentFront = ({ className, stroke = 1 }) => (
|
||||||
<LineDrawingWrapper viewBox="0 -1 103 103" {...{ className, stroke }}>
|
<LineDrawingWrapper viewBox="0 -1 103 103" {...{ className, stroke }}>
|
||||||
|
@ -34,8 +38,14 @@ export const BentFront = ({ className, stroke = 1 }) => (
|
||||||
</LineDrawingWrapper>
|
</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 = ({
|
export const BentBack = ({
|
||||||
className = 'tw:w-full', // CSS classes to apply
|
className = 'tw:w-full', // CSS classes to apply
|
||||||
|
@ -54,7 +64,7 @@ export const BentBack = ({
|
||||||
/*
|
/*
|
||||||
* SVG elements for the front
|
* SVG elements for the front
|
||||||
*/
|
*/
|
||||||
export const Front = ({ stroke }) => (
|
const Front = ({ stroke }) => (
|
||||||
<>
|
<>
|
||||||
<path
|
<path
|
||||||
key="stitches"
|
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