1
0
Fork 0

feat[react]: Linting with eslint 9

This commit is contained in:
joostdecock 2025-05-30 11:29:55 +02:00
parent 14eab04d5b
commit f69093b0dc
99 changed files with 1260 additions and 956 deletions

24
eslint.config.mjs Normal file
View file

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

1050
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -1,49 +1,34 @@
// 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, Fieldset, 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/Button'
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',
} }
@ -242,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>

View file

@ -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'
@ -33,7 +33,7 @@ const types = {
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
@ -194,7 +194,7 @@ 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',

View file

@ -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'

View file

@ -1,36 +1,15 @@
// 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: {
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.',
},
}
/** /**
* A component to manage the user's consent setting * A component to manage the user's consent setting
* *
@ -79,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)

View file

@ -1,39 +1,16 @@
// 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: {
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.',
},
}
/** /**
* A component to manage the user's control/UX setting * A component to manage the user's control/UX setting
* *

View file

@ -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'
@ -22,11 +18,10 @@ import { Spinner } from '@freesewing/react/components/Spinner'
* *
* @component * @component
* @param {object} props - All component props * @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 * @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element} * @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
@ -104,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
@ -134,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)

View file

@ -1,11 +1,8 @@
// 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'

View file

@ -1,24 +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 user's ID * A component to display the current user's ID
* *
* @component * @component
* @param {object} props - All component props * @param {object} props - All component props
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element} * @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
} }

View file

@ -1,14 +1,12 @@
// 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'
@ -41,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 || '',

View file

@ -93,8 +93,6 @@ const titles = {
const YesNo = ({ check }) => (check ? <BoolYesIcon /> : <BoolNoIcon />) const YesNo = ({ check }) => (check ? <BoolYesIcon /> : <BoolNoIcon />)
const t = (input) => input
/** /**
* A component to manage the user's Instagram handle in their account data * A component to manage the user's Instagram handle in their account data
* *

View file

@ -1,20 +1,15 @@
// Dependencies // Dependencies
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/Button'
/** /**

View file

@ -1,18 +1,15 @@
// 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'

View file

@ -1,15 +1,9 @@
// 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'
@ -25,7 +19,7 @@ import { Popout } from '@freesewing/react/components/Popout'
* @param {React.Component} 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} * @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()

View file

@ -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,7 +30,6 @@ 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'
@ -348,16 +335,7 @@ const BadgeLink = ({ label, href }) => (
/** /**
* 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">

View file

@ -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,11 +19,6 @@ import {
TrashIcon, TrashIcon,
} from '@freesewing/react/components/Icon' } from '@freesewing/react/components/Icon'
const t = (input) => {
console.log('t called', input)
return input
}
/** /**
* A component to display and manage the list of patterns in the user's account * A component to display and manage the list of patterns in the user's account
* *
@ -97,13 +88,15 @@ 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/"
> >
<span className="tw:text-primary-content"><PlusIcon /></span> <span className="tw:text-primary-content">
<PlusIcon />
</span>
<span className="tw:text-primary-content">Create a new pattern</span> <span className="tw:text-primary-content">Create a new pattern</span>
</Link> </Link>
</div> </div>

View file

@ -1,15 +1,11 @@
// 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'
/** /**

View file

@ -3,16 +3,12 @@ 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'

View file

@ -6,14 +6,13 @@ 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'
@ -39,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()

View file

@ -58,11 +58,6 @@ 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
* *
@ -650,8 +645,8 @@ const SuggestCset = ({ mset, Link }) => {
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()
@ -803,7 +798,7 @@ 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 (
@ -844,7 +839,7 @@ 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&apos;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>
@ -881,7 +876,7 @@ 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('')

View file

@ -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'

View file

@ -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 { 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'

View file

@ -57,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

View file

@ -1,5 +1,3 @@
import React from 'react'
import { AccountStatus } from './Status.mjs' import { AccountStatus } from './Status.mjs'
import { Apikeys } from './Apikeys.mjs' import { Apikeys } from './Apikeys.mjs'
import { Avatar } from './Avatar.mjs' import { Avatar } from './Avatar.mjs'

View file

@ -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'

View file

@ -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,7 +13,6 @@ 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'
@ -313,95 +311,3 @@ const ImpersonateButton = ({ userId }) => {
</button> </button>
) )
} }
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>
)
const ManageUser = ({ userId }) => {
// Hooks
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
const { account } = useAccount()
const { role } = account
// State
const [user, setUser] = useState({})
const [patterns, setPatterns] = useState({})
const [sets, setSets] = useState({})
// Effect
useEffect(() => {
const loadUser = async () => {
const result = await backend.adminLoadUser(userId)
if (result.success) {
setUser(result.data.user)
setPatterns(result.data.patterns)
setSets(result.data.sets)
}
}
loadUser()
}, [userId])
const updateUser = async (data) => {
setLoadingStatus([true, 'status:contactingBackend'])
const result = await backend.adminUpdateUser({ id: userId, data })
if (result.success) {
setLoadingStatus([true, 'status:settingsSaved', true, true])
setUser(result.data.user)
} else setLoadingStatus([true, 'status:backendError', true, false])
}
return user.id ? (
<div className="my-8">
<ShowUser
user={user}
button={role === 'admin' ? <ImpersonateButton userId={user.id} /> : null}
/>
{role === 'admin' ? (
<div className="flex flex-row flex-wrap gap-2 my-2">
{roles.map((role) => (
<button
key={role}
className="btn btn-primary btn-outline btn-sm"
onClick={() => updateUser({ role })}
disabled={role === user.role}
>
Assign {role} role
</button>
))}
</div>
) : null}
<div className="flex flex-row flex-wrap gap-2 my-2 mb-4">
{user.mfaEnabled && (
<button
className="btn btn-warning btn-outline btn-sm"
onClick={() => updateUser({ mfaEnabled: false })}
>
Disable MFA
</button>
)}
{Object.keys(freeSewingConfig.statuses).map((status) => (
<button
key={status}
className="btn btn-warning btn-outline btn-sm"
onClick={() => updateUser({ status })}
disabled={Number(status) === user.status}
>
Set {freeSewingConfig.statuses[status].name.toUpperCase()} status
</button>
))}
</div>
<Tabs tabs="Account, Patterns, Sets">
<Tab tabId="Account">{user.id ? <Json js={user} /> : null}</Tab>
<Tab tabId="Patterns">{patterns ? <Json js={patterns} /> : null}</Tab>
<Tab id="Sets">{sets ? <Json js={sets} /> : null}</Tab>
</Tabs>
</div>
) : (
<Loading />
)
}

View file

@ -1,14 +1,8 @@
import React, { useContext, useState } from 'react' import React, { useContext, useState } from 'react'
import { copyToClipboard } from '@freesewing/utils' import { copyToClipboard } from '@freesewing/utils'
import ReactDOMServer from 'react-dom/server'
import { CopyIcon, OkIcon } from '@freesewing/react/components/Icon' import { CopyIcon, OkIcon } from '@freesewing/react/components/Icon'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' 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) => { const handleCopied = (content, setCopied, setLoadingStatus, label) => {
copyToClipboard(content) copyToClipboard(content)
setCopied(true) setCopied(true)
@ -55,7 +49,7 @@ export const CopyToClipboardButton = ({ children, content, label = false, sup =
) : ( ) : (
<CopyIcon className={`${style} tw:text-inherit tw:group-hover:text-secondary`} /> <CopyIcon className={`${style} tw:text-inherit tw:group-hover:text-secondary`} />
)} )}
{sup ? null : children} {sup ? null : children}
</button> </button>
) )
} }

View file

@ -4,32 +4,22 @@ import {
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 { useFilter } from '@freesewing/react/hooks/useFilter' 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,
@ -39,7 +29,6 @@ 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'
@ -239,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
@ -374,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
@ -391,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`}>
@ -554,21 +500,3 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
</> </>
) )
} }
const SharingIsCaring = ({ design }) => (
<>
<h2>
Use <b>#FreeSewing{capitalize(design)}</b> to facilitate discovery
</h2>
<p>
Please use the{' '}
<b>
<code>#FreeSewing{capitalize(design)}</code>
</b>{' '}
hashtag when discussing FreeSewing&apos;s <b>{capitalize(design)}</b> pattern online.
<br />
Doing so can help others discover your post, which really is a win-win.
</p>
<p>If you like, you can copy the hashtag below:</p>
</>
)

View file

@ -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`,
}) })

View file

@ -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

View file

@ -24,7 +24,6 @@ import {
ResetIcon, ResetIcon,
RightIcon, RightIcon,
RocketIcon, RocketIcon,
RotateIcon,
SaIcon, SaIcon,
SaveAsIcon, SaveAsIcon,
SaveIcon, SaveIcon,
@ -133,7 +132,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,10 +459,8 @@ 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">
@ -660,11 +657,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

View file

@ -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">

View file

@ -85,7 +85,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 +397,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,23 +413,6 @@ const Button = ({ onClickCb, transform, Icon, children, title = '' }) => {
) )
} }
export const ShowButtonsToggle = ({ ui, update }) => {
const hideButtons = (evt) => {
update.ui('hideMovableButtons', !evt.target.checked)
}
return (
<label className="label cursor-pointer">
<span className="label-text text-lg mr-2">{t('showMovableButtons')}</span>
<input
type="checkbox"
className="toggle toggle-primary"
checked={!ui.hideMovableButtons}
onChange={hideButtons}
/>
</label>
)
}
/** 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 }) => {
return ( return (

View file

@ -1,17 +1,9 @@
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'
/** /**
* A layout for views that include a drafted pattern * A layout for views that include a drafted pattern
@ -24,9 +16,7 @@ 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, state } = props
const i18n = useDesignTranslation(Design.designConfig.data.id)
const flags = props.pattern?.setStores?.[0]?.plugins?.['plugin-annotations']?.flags
return ( return (
<ZoomContextProvider> <ZoomContextProvider>

View file

@ -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>

View file

@ -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 (

View file

@ -36,10 +36,8 @@ export const MenuItem = ({
allowOverride = false, allowOverride = false,
ux = 5, ux = 5,
state, state,
docs,
config, config,
Design, Design,
i18n,
}) => { }) => {
// 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)

View file

@ -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

View file

@ -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 }) => {

View file

@ -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,
}) => { }) => {

View file

@ -92,7 +92,7 @@ const SampleOptionButton = ({ name, i18n, update }) => (
</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 ' +

View file

@ -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,
} }

View file

@ -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>

View file

@ -5,10 +5,9 @@ import { Collection } from '@freesewing/react/components/Collection'
* The designs view is loaded if and only if no design name is passed to the editor * 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

View file

@ -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&apos;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>

View file

@ -23,7 +23,6 @@ import { yaml as yamlLang } from '@codemirror/lang-yaml'
* @param {Object} props.update - Helper object for updating the editor state * @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)
} }
} }

View file

@ -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}`
@ -102,6 +102,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 +115,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 +132,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 +146,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 })}
> >

View file

@ -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

View file

@ -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,8 +78,9 @@ 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) {
update.stopLoading(loadingId)
setSavedId(saveAs.pattern) setSavedId(saveAs.pattern)
update.notify({ color: 'success', msg: 'boom' }, saveAs.pattern) update.notifySuccess('Pattern saved', loadingId)
} }
} }
@ -96,11 +98,11 @@ export const SaveView = ({ config, state, update }) => {
</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}
@ -137,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

View file

@ -69,7 +69,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}
/> />
) )

View file

@ -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',

View file

@ -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 (

View file

@ -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

View file

@ -101,7 +101,7 @@ export function menuCoreSettingsStructure({
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&apos;s seam allowance.
<CoreDocsLink item="sa" /> <CoreDocsLink item="sa" />
</> </>
), ),

View file

@ -9,6 +9,7 @@ const DesignDocsLink = ({ design, item }) => (
href={`/docs/designs/${design}/options/#${item.toLowerCase()}`} href={`/docs/designs/${design}/options/#${item.toLowerCase()}`}
className={`${linkClasses} tw:px-2`} className={`${linkClasses} tw:px-2`}
target="_BLANK" target="_BLANK"
rel="noreferrer"
> >
Learn more Learn more
</a> </a>
@ -62,14 +63,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

View file

@ -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'
@ -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

View file

@ -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'

View file

@ -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',

View file

@ -27,7 +27,6 @@ export const IconWrapper = ({
fill = false, fill = false,
fillOpacity = 1, fillOpacity = 1,
dasharray = null, dasharray = null,
wrapped = true,
}) => ( }) => (
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View file

@ -10,7 +10,6 @@ import {
import { collection } from '@freesewing/collection' import { collection } from '@freesewing/collection'
import { measurements as measurementsTranslations } from '@freesewing/i18n' import { measurements as measurementsTranslations } from '@freesewing/i18n'
// Context // Context
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, useCallback, useContext } from 'react' import React, { useState, useCallback, useContext } from 'react'
@ -19,7 +18,6 @@ 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 { TrashIcon, ResetIcon, UploadIcon, HelpIcon } from '@freesewing/react/components/Icon' import { TrashIcon, ResetIcon, UploadIcon, HelpIcon } from '@freesewing/react/components/Icon'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { isDegreeMeasurement } from '@freesewing/config' import { isDegreeMeasurement } from '@freesewing/config'
import { Tabs, Tab } from '@freesewing/react/components/Tab' import { Tabs, Tab } from '@freesewing/react/components/Tab'
import Markdown from 'react-markdown' import Markdown from 'react-markdown'
@ -68,31 +66,50 @@ const HelpLink = ({ help, Link = false }) => {
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const Fieldset = ({ export const Fieldset = ({
Link=false, Link = false,
box=false, box = false,
children, children,
label=false, label = false,
labelBL=false, labelBL = false,
labelBR=false, labelBR = false,
labelTR=false, labelTR = false,
legend=false, legend = false,
forId='', forId = '',
help=false, help = false,
}) => ( }) => (
<fieldset className={`tw:daisy-fieldset tw:w-full tw:mt-2 ${box ? 'tw:bg-base-200 tw:border-base-300 tw:rounded-box tw:border tw:p-4' : ''}`}> <fieldset
className={`tw:daisy-fieldset tw:w-full tw:mt-2 ${box ? 'tw:bg-base-200 tw:border-base-300 tw:rounded-box tw:border tw:p-4' : ''}`}
>
{legend ? ( {legend ? (
<legend className="tw:daisy-fieldset-legend tw:px-2 tw:pb-1"> <legend className="tw:daisy-fieldset-legend tw:px-2 tw:pb-1">
{legend}<HelpLink {...{ help, Link }} /> {legend}
<HelpLink {...{ help, Link }} />
</legend> </legend>
) : null} ) : null}
<div className="tw:flex tw:flex-row tw:justify-between tw:px-2"> <div className="tw:flex tw:flex-row tw:justify-between tw:px-2">
{label ? <label className="tw:daisy-label" htmlFor={forId}>{label}</label> : null } {label ? (
{labelTR ? <label className="tw:daisy-label" htmlFor={forId}>{labelTR}</label> : null } <label className="tw:daisy-label" htmlFor={forId}>
{label}
</label>
) : null}
{labelTR ? (
<label className="tw:daisy-label" htmlFor={forId}>
{labelTR}
</label>
) : null}
</div> </div>
{children} {children}
<div className="tw:flex tw:flex-row tw:justify-between tw:px-2"> <div className="tw:flex tw:flex-row tw:justify-between tw:px-2">
{labelBL ? <label className="tw:daisy-label" htmlFor={forId}>{labelBL}</label> : null } {labelBL ? (
{labelBR ? <label className="tw:daisy-label" htmlFor={forId}>{labelBR}</label> : null } <label className="tw:daisy-label" htmlFor={forId}>
{labelBL}
</label>
) : null}
{labelBR ? (
<label className="tw:daisy-label" htmlFor={forId}>
{labelBR}
</label>
) : null}
</div> </div>
</fieldset> </fieldset>
) )
@ -153,7 +170,7 @@ export const ButtonFrame = ({ active, children, dense, noBg, onClick }) => (
export const NumberInput = ({ export const NumberInput = ({
box = false, box = false,
current, current,
help=false, help = false,
inputMode = 'decimal', inputMode = 'decimal',
label = false, label = false,
labelBL = false, labelBL = false,
@ -212,7 +229,7 @@ export const NumberInput = ({
export const StringInput = ({ export const StringInput = ({
box = false, box = false,
current, current,
help=false, help = false,
label = false, label = false,
labelBL = false, labelBL = false,
labelBR = false, labelBR = false,
@ -265,7 +282,7 @@ export const StringInput = ({
export const MfaInput = ({ export const MfaInput = ({
box = false, box = false,
current, current,
help=false, help = false,
label = false, label = false,
labelBL = false, labelBL = false,
labelBR = false, labelBR = false,
@ -273,12 +290,26 @@ export const MfaInput = ({
id = 'mfa', id = 'mfa',
inputMode = 'numeric', inputMode = 'numeric',
legend = false, legend = false,
placeholder="MFA Code", placeholder = 'MFA Code',
update, update,
valid = (val) => val.length > 4, valid = (val) => val.length > 4,
}) => ( }) => (
<NumberInput <NumberInput
{...{ box, current, help, label, labelBL, labelBR, labelTR, id, inputMode, legend, placeholder, update, valid }} {...{
box,
current,
help,
label,
labelBL,
labelBR,
labelTR,
id,
inputMode,
legend,
placeholder,
update,
valid,
}}
/> />
) )
@ -304,7 +335,7 @@ export const MfaInput = ({
export const PasswordInput = ({ export const PasswordInput = ({
box = false, box = false,
current, current,
help=false, help = false,
label = false, label = false,
labelBL = false, labelBL = false,
labelTR = false, labelTR = false,
@ -712,7 +743,7 @@ export const MarkdownInput = ({
labelTR = false, labelTR = false,
legend = false, legend = false,
update, update,
placeholder='', placeholder = '',
}) => ( }) => (
<Fieldset {...{ box, help, label, labelTR, labelBL, labelBR, legend }} forId={id}> <Fieldset {...{ box, help, label, labelTR, labelBL, labelBR, legend }} forId={id}>
<Tabs tabs={['edit', 'preview']}> <Tabs tabs={['edit', 'preview']}>
@ -743,7 +774,6 @@ export const MarkdownInput = ({
* @component * @component
* @param {object} props - All component props * @param {object} props - All component props
* @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset
* @param {number} props.current - The current value, to manage the state of this input
* @param {string|function} [props.props.help = false] - An optional URL/method to link/show help or docs * @param {string|function} [props.props.help = false] - An optional URL/method to link/show help or docs
* @param {string} [props.id = ''] - Id of the HTML element to link the fieldset labels * @param {string} [props.id = ''] - Id of the HTML element to link the fieldset labels
* @param {boolean} [props.imperial = false] - Set this to true to render imperial units * @param {boolean} [props.imperial = false] - Set this to true to render imperial units
@ -758,7 +788,6 @@ export const MarkdownInput = ({
*/ */
export const MeasurementInput = ({ export const MeasurementInput = ({
box = false, box = false,
current,
help = false, help = false,
id = '', id = '',
imperial = false, imperial = false,
@ -768,7 +797,7 @@ export const MeasurementInput = ({
m, m,
update, update,
original, original,
placeholder='', placeholder = '',
}) => { }) => {
const isDegree = isDegreeMeasurement(m) const isDegree = isDegreeMeasurement(m)
const units = imperial ? 'imperial' : 'metric' const units = imperial ? 'imperial' : 'metric'
@ -876,7 +905,6 @@ export const MeasurementInput = ({
* @param {string} [props.legend = false] - The fieldset legend * @param {string} [props.legend = false] - The fieldset legend
* @param {function} props.update - The onChange handler * @param {function} props.update - The onChange handler
* @param {number} props.original - The original value, which allows a reset * @param {number} props.original - The original value, which allows a reset
* @param {function} [props.valid = () => true] - A function that should return whether the value is valid or not
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const FileInput = ({ export const FileInput = ({
@ -892,7 +920,6 @@ export const FileInput = ({
legend = false, legend = false,
update, update,
original, original,
valid = () => true,
}) => { }) => {
/* /*
* Ondrop handler * Ondrop handler
@ -974,8 +1001,6 @@ export const FileInput = ({
* @param {array} [props.list = [true, false] - An array of values to choose between * @param {array} [props.list = [true, false] - An array of values to choose between
* @param {function} props.update - The onChange handler * @param {function} props.update - The onChange handler
* @param {any} [props.on = true] - The value that should show the toggle in the 'on' state * @param {any} [props.on = true] - The value that should show the toggle in the 'on' state
* @param {number} props.original - The original value, which allows a reset
* @param {function} [props.valid = () => true] - A function that should return whether the value is valid or not
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const ToggleInput = ({ export const ToggleInput = ({
@ -993,8 +1018,6 @@ export const ToggleInput = ({
list = [true, false], list = [true, false],
update, update,
on = true, on = true,
original,
valid = () => true,
}) => ( }) => (
<Fieldset {...{ box, help, labelTR, labelBL, labelBR, legend }} forId={id}> <Fieldset {...{ box, help, labelTR, labelBL, labelBR, legend }} forId={id}>
<label className="tw:daisy-label"> <label className="tw:daisy-label">
@ -1009,8 +1032,7 @@ export const ToggleInput = ({
/> />
{label {label
? `${label} (${current === on ? labels[0] : labels[1]})` ? `${label} (${current === on ? labels[0] : labels[1]})`
: `${current === on ? labels[0] : labels[1]}` : `${current === on ? labels[0] : labels[1]}`}
}
</label> </label>
</Fieldset> </Fieldset>
) )

View file

@ -23,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
@ -83,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,
@ -134,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([

View file

@ -10,11 +10,10 @@ import { Link as DefaultLink } from '@freesewing/react/components/Link'
* @param {function} [props.Link = false] - An optional framework specific Link component * @param {function} [props.Link = false] - An optional framework specific Link component
* @param {JSX.Element} [props.children = []] - The component children to render inside the layout * @param {JSX.Element} [props.children = []] - The component children to render inside the layout
* @param {array} [props.crumbs = []] - Data for the breadcrumbs, see Breadcrumbs * @param {array} [props.crumbs = []] - Data for the breadcrumbs, see Breadcrumbs
* @param {string} props.description - The page description
* @param {string} props.title - The page title * @param {string} props.title - The page title
* @returns {JSX.Element} * @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 (

View file

@ -48,7 +48,7 @@ export const AlbertFront = ({ className, stroke = 1 }) => (
* @returns {JSX.Element} * @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>
) )

View file

@ -17,7 +17,7 @@ const strokeScale = 0.7
* @returns {JSX.Element} * @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>

View file

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import { LineDrawingWrapper, thin, dashed } from './shared.mjs' import { LineDrawingWrapper } from './shared.mjs'
/* /*
* This strokeScale factor is used to normalize the stroke across * This strokeScale factor is used to normalize the stroke across
@ -56,7 +56,7 @@ export const JaneBack = ({ className, stroke = 1 }) => (
/* /*
* SVG elements for the front * SVG elements for the front
*/ */
const Front = ({ stroke }) => ( const Front = () => (
<> <>
<path <path
key="outline" key="outline"
@ -69,7 +69,7 @@ const Front = ({ stroke }) => (
/* /*
* SVG elements for the back * SVG elements for the back
*/ */
const Back = ({ stroke }) => ( const Back = () => (
<> <>
<path <path
key="outline" key="outline"

View file

@ -52,22 +52,20 @@ const BaseLink = Link
* @param {object} props - All component props * @param {object} props - All component props
* @param {JSX.Element} props.children - The component children, will be rendered inside the link * @param {JSX.Element} props.children - The component children, will be rendered inside the link
* @param {string} props.href - The URL to link to * @param {string} props.href - The URL to link to
* @param {string} [props.target = undefined] - An optional link title
* @param {string} [props.title = false] - An optional link title * @param {string} [props.title = false] - An optional link title
* @param {React.FC} [Link = undefined] - An optional framework-specific Link component * @param {React.FC} [Link = undefined] - An optional framework-specific Link component
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const SuccessLink = ({ export const SuccessLink = ({ children, href, title = false, Link }) =>
children, Link ? (
href, <Link href={href} className={linkClasses} title={title ? title : ''}>
target, <span className="tw:text-success-content tw:hover:text-success-content">{children}</span>
title = false, </Link>
Link, ) : (
}) => ( <a href={href} className={linkClasses} title={title ? title : ''}>
<a href={href} className={linkClasses} title={title ? title : ''}> <span className="tw:text-success-content tw:hover:text-success-content">{children}</span>
<span className="tw:text-success-content tw:hover:text-success-content">{children}</span> </a>
</a> )
)
/** /**
* A link styled as a card * A link styled as a card

View file

@ -10,24 +10,17 @@ import { logoPath } from '@freesewing/config'
* @param {string} [props.stroke = undefined] - Set this explicitly to use a different stroke color * @param {string} [props.stroke = undefined] - Set this explicitly to use a different stroke color
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const FreeSewingLogo = ({ className = 'tw:w-20 tw:h-20', stroke }) => { export const FreeSewingLogo = ({ className = 'tw:w-20 tw:h-20', stroke }) => (
const svgProps = {} <svg xmlns="http://www.w3.org/2000/svg" viewBox="1 0 25 25" className={className}>
const strokes = { dark: '#000', light: 'var(--p)' } <defs>
<path id="react-logo" d={logoPath} />
return ( </defs>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1 0 25 25" className={className}> <use
<defs> xlinkHref="#react-logo"
<path id="react-logo" d={logoPath} /> fill="none"
</defs> strokeWidth="0.5"
<use style={{ stroke: stroke ? stroke : 'var(--color-base-100)' }}
xlinkHref="#react-logo" />
fill="none" <use xlinkHref="#react-logo" fill="currentColor" stroke="none" />
strokeWidth="0.5" </svg>
style={{ stroke: stroke ? stroke : 'var(--color-base-100)'}} )
/>
<use xlinkHref="#react-logo" fill="currentColor" stroke="none" />
</svg>
)
}

View file

@ -6,15 +6,12 @@ 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 { NoIcon, OkIcon, SaveIcon, RightIcon, WarningIcon } from '@freesewing/react/components/Icon' import { OkIcon, WarningIcon } from '@freesewing/react/components/Icon'
import { EmailInput } from '@freesewing/react/components/Input' import { EmailInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
import { MiniTip } from '@freesewing/react/components/Mini' import { MiniTip } from '@freesewing/react/components/Mini'
/** /**
@ -32,7 +29,6 @@ export const NewsletterSignup = ({ Link = false, noP = false, noTitle = false, n
if (!Link) Link = WebLink if (!Link) Link = WebLink
// Hooks // Hooks
const { account, setAccount } = useAccount()
const backend = useBackend() const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext) const { setLoadingStatus } = useContext(LoadingStatusContext)
@ -44,7 +40,7 @@ export const NewsletterSignup = ({ Link = false, noP = false, noTitle = false, n
// Helper method to handle subscription // Helper method to handle subscription
const subscribe = async () => { const subscribe = async () => {
setLoadingStatus([true, 'Contacting backend']) setLoadingStatus([true, 'Contacting backend'])
const [status, body] = unsubscribe const [status] = unsubscribe
? await backend.newsletterStartUnsubscribe(email) ? await backend.newsletterStartUnsubscribe(email)
: await backend.newsletterSubscribe(email) : await backend.newsletterSubscribe(email)
if (status === 200) { if (status === 200) {
@ -146,7 +142,7 @@ export const NewsletterUnsubscribe = ({ Link = false }) => {
// Helper method to handle subscription // Helper method to handle subscription
const unsubscribe = async () => { const unsubscribe = async () => {
const [status, body] = await backend.newsletterUnsubscribe(ehash) const [status] = await backend.newsletterUnsubscribe(ehash)
if (status === 204 || status === 404) setGone(true) if (status === 204 || status === 404) setGone(true)
else setError(true) else setError(true)
} }
@ -197,6 +193,4 @@ export const NewsletterUnsubscribe = ({ Link = false }) => {
</MiniTip> </MiniTip>
</> </>
) )
return <p>Unsubscribe here {ehash}</p>
} }

View file

@ -1,5 +1,3 @@
import React from 'react'
/** /**
* Sometimes, you just want a component that does nothing * Sometimes, you just want a component that does nothing
* *

View file

@ -1,8 +1,7 @@
// Dependencies // Dependencies
import { linkClasses } from '@freesewing/utils' import { linkClasses } from '@freesewing/utils'
// Hooks // Hooks
import React, { useEffect, useState } from 'react' import React, { useState } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
/** /**
* A component to ask people to support FreeSewing financially * A component to ask people to support FreeSewing financially
@ -211,7 +210,12 @@ export const Subscribe = ({
</button> </button>
<p className="tw:text-center tw:text-sm tw:text-neutral-content tw:mt-2 tw:opacity-80"> <p className="tw:text-center tw:text-sm tw:text-neutral-content tw:mt-2 tw:opacity-80">
Don&apos;t have a PayPal account? Don&apos;t have a PayPal account?
<a href="https://ko-fi.com/freesewing" target="_BLANK" className={linkClasses}> <a
href="https://ko-fi.com/freesewing"
target="_BLANK"
rel="noreferrer"
className={linkClasses}
>
<b className="tw:text-neutral-content tw:pl-2">Ko-fi.com/FreeSewing</b> <b className="tw:text-neutral-content tw:pl-2">Ko-fi.com/FreeSewing</b>
</a> </a>
</p> </p>

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React from 'react' import React from 'react'
import sanitize from 'html-react-parser' import sanitize from 'html-react-parser'

View file

@ -1,5 +1,3 @@
// Dependencies
import { cloudflareImageUrl } from '@freesewing/utils'
// Components // Components
import React, { forwardRef } from 'react' import React, { forwardRef } from 'react'
import { Svg as DefaultSvg } from './svg.mjs' import { Svg as DefaultSvg } from './svg.mjs'
@ -14,7 +12,6 @@ import { Grid as DefaultGrid } from './grid.mjs'
import { Text as DefaultText, TextOnPath as DefaultTextOnPath } from './text.mjs' import { Text as DefaultText, TextOnPath as DefaultTextOnPath } from './text.mjs'
import { Circle as DefaultCircle } from './circle.mjs' import { Circle as DefaultCircle } from './circle.mjs'
import { getId, getProps, withinPartBounds, translateStrings } from './utils.mjs' import { getId, getProps, withinPartBounds, translateStrings } from './utils.mjs'
import { Link as WebLink } from '@freesewing/react/components/Link'
/** /**
* Default Pattern components that you can override * Default Pattern components that you can override
@ -101,6 +98,7 @@ const Pattern = forwardRef((props, ref) => {
</Svg> </Svg>
) )
}) })
Pattern.displayName = 'Pattern'
export { export {
// utils // utils

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React, { forwardRef } from 'react' import React, { forwardRef } from 'react'
import { getId, getProps } from './utils.mjs' import { getId, getProps } from './utils.mjs'

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React from 'react' import React from 'react'
import { getId, getProps } from './utils.mjs' import { getId, getProps } from './utils.mjs'

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React from 'react' import React from 'react'
import { withinPartBounds } from './utils.mjs' import { withinPartBounds } from './utils.mjs'

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React from 'react' import React from 'react'
import { getProps } from './utils.mjs' import { getProps } from './utils.mjs'

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React from 'react' import React from 'react'
import { forwardRef } from 'react' import { forwardRef } from 'react'

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React from 'react' import React from 'react'
import { translateStrings } from './utils.mjs' import { translateStrings } from './utils.mjs'

View file

@ -1,5 +1,3 @@
import React from 'react'
/** /**
* A method to generated an ID for an object part of a FreeSewing pattern * A method to generated an ID for an object part of a FreeSewing pattern
* *

View file

@ -7,44 +7,44 @@ import { CloseIcon } from '@freesewing/react/components/Icon'
*/ */
const types = { const types = {
comment: { comment: {
bg: 'tw:bg-success/5', bg: 'tw:bg-success/5',
border: 'tw:border-success', border: 'tw:border-success',
text: 'tw:text-success', text: 'tw:text-success',
}, },
error: { error: {
bg: 'tw:bg-error/5', bg: 'tw:bg-error/5',
border: 'tw:border-error', border: 'tw:border-error',
text: 'tw:text-error', text: 'tw:text-error',
}, },
fixme: { fixme: {
bg: 'tw:bg-neutral/5', bg: 'tw:bg-neutral/5',
border: 'tw:border-neutral', border: 'tw:border-neutral',
text: 'tw:text-error', text: 'tw:text-error',
}, },
link: { link: {
bg: 'tw:bg-secondary/5', bg: 'tw:bg-secondary/5',
border: 'tw:border-secondary', border: 'tw:border-secondary',
text: 'tw:text-secondary', text: 'tw:text-secondary',
}, },
note: { note: {
bg: 'tw:bg-primary/5', bg: 'tw:bg-primary/5',
border: 'tw:border-primary', border: 'tw:border-primary',
text: 'tw:text-primary', text: 'tw:text-primary',
}, },
related: { related: {
bg: 'tw:bg-info/5', bg: 'tw:bg-info/5',
border: 'tw:border-info', border: 'tw:border-info',
text: 'tw:text-info', text: 'tw:text-info',
}, },
tip: { tip: {
bg: 'tw:bg-accent/5', bg: 'tw:bg-accent/5',
border: 'tw:border-accent', border: 'tw:border-accent',
text: 'tw:text-accent', text: 'tw:text-accent',
}, },
warning: { warning: {
bg: 'tw:bg-warning/5', bg: 'tw:bg-warning/5',
border: 'tw:border-warning', border: 'tw:border-warning',
text: 'tw:text-warning', text: 'tw:text-warning',
}, },
} }
@ -68,7 +68,7 @@ export const Popout = ({
compact = false, compact = false,
dense = false, dense = false,
hideable = false, hideable = false,
type = "note", type = 'note',
title = false, title = false,
}) => { }) => {
// Make this hideable/dismissable // Make this hideable/dismissable
@ -76,15 +76,17 @@ export const Popout = ({
if (hide) return null if (hide) return null
return compact return compact ? (
? <CompactPopout {...{ by, compact, dense, hideable, type, title, setHide }}>{children}</CompactPopout> <CompactPopout {...{ by, compact, dense, hideable, type, title, setHide }}>
: <RegularPopout {...{ by, hideable, type, title, setHide }}>{children}</RegularPopout> {children}
</CompactPopout>
) : (
<RegularPopout {...{ by, hideable, type, title, setHide }}>{children}</RegularPopout>
)
} }
const RegularPopout = ({ by, children, compact, hideable, type, title, setHide }) => ( const RegularPopout = ({ by, children, hideable, type, title, setHide }) => (
<div <div className={`tw:relative tw:my-8 tw:-ml-4 tw:-mr-4 tw:sm:ml-0 tw:sm:mr-0 ${types[type].bg}`}>
className={`tw:relative tw:my-8 tw:-ml-4 tw:-mr-4 tw:sm:ml-0 tw:sm:mr-0 ${types[type].bg}`}
>
<div <div
className={` className={`
tw:border-y-4 tw:border-x-0 tw:sm:border-0 tw:sm:border-l-4 tw:px-6 tw:sm:px-8 tw:py-4 tw:sm:py-2 tw:border-y-4 tw:border-x-0 tw:sm:border-0 tw:sm:border-l-4 tw:px-6 tw:sm:px-8 tw:py-4 tw:sm:py-2
@ -98,7 +100,6 @@ const RegularPopout = ({ by, children, compact, hideable, type, title, setHide }
)} )}
</div> </div>
</div> </div>
) )
const CompactPopout = ({ by, children, compact, dense, hideable, type, title, setHide }) => ( const CompactPopout = ({ by, children, compact, dense, hideable, type, title, setHide }) => (
@ -122,20 +123,27 @@ const CompactPopout = ({ by, children, compact, dense, hideable, type, title, se
) )
const PopoutTitle = ({ by, compact, hideable, setHide, title, type }) => ( const PopoutTitle = ({ by, compact, hideable, setHide, title, type }) => (
<div <div className={`tw:font-bold tw:flex tw:flex-row tw:gap-1 tw:items-end tw:justify-between`}>
className={`tw:font-bold tw:flex tw:flex-row tw:gap-1 tw:items-end tw:justify-between`}
>
<div> <div>
<span className={`tw:font-bold tw:uppercase ${types[type].text}`}> <span className={`tw:font-bold tw:uppercase ${types[type].text}`}>
{title ? title : types[type].title ? types[type].title : type.toUpperCase()} {title ? title : types[type].title ? types[type].title : type.toUpperCase()}
{compact ? <span className="tw:px-2">|</span> : null} {compact ? <span className="tw:px-2">|</span> : null}
</span> </span>
{(type === 'comment' && by) && <span className={`tw:font-normal tw:text-base tw:pr-2 ${types[type].text}`}> by <b>{by}</b></span>} {type === 'comment' && by && (
<span className={`tw:font-normal tw:text-base tw:pr-2 ${types[type].text}`}>
{' '}
by <b>{by}</b>
</span>
)}
</div> </div>
{hideable && ( {hideable && (
<button onClick={() => setHide(true)} className={`${types[type].text} tw:hover:text-neutral tw:hover:cursor-pointer`} title="Close"> <button
onClick={() => setHide(true)}
className={`${types[type].text} tw:hover:text-neutral tw:hover:cursor-pointer`}
title="Close"
>
<CloseIcon /> <CloseIcon />
</button> </button>
)} )}
</div> </div>
) )

View file

@ -1,5 +1,5 @@
// Dependencies // Dependencies
import { linkClasses, cloudflareImageUrl, getSearchParam } from '@freesewing/utils' import { cloudflareImageUrl, getSearchParam } from '@freesewing/utils'
// Context // Context
import { ModalContext } from '@freesewing/react/context/Modal' import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks // Hooks
@ -9,7 +9,6 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components // Components
import { ModalWrapper } from '@freesewing/react/components/Modal' import { ModalWrapper } from '@freesewing/react/components/Modal'
import { Link as WebLink } from '@freesewing/react/components/Link' import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon, RightIcon, WarningIcon } from '@freesewing/react/components/Icon'
import { MiniWarning } from '@freesewing/react/components/Mini' import { MiniWarning } from '@freesewing/react/components/Mini'
import { KeyVal } from '@freesewing/react/components/KeyVal' import { KeyVal } from '@freesewing/react/components/KeyVal'
import Markdown from 'react-markdown' import Markdown from 'react-markdown'
@ -40,12 +39,7 @@ export const OwnProfile = (props) => {
* @param {number} [props.uid = false] - The user ID for which to show the profile * @param {number} [props.uid = false] - The user ID for which to show the profile
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const UserProfile = ({ export const UserProfile = ({ Link = false, setTitle = false, uid = false, fromUrl = false }) => {
Link = false,
setTitle = false,
uid = false,
fromUrl = false,
}) => {
if (!uid && !fromUrl) if (!uid && !fromUrl)
return ( return (
<MiniWarning> <MiniWarning>
@ -69,7 +63,7 @@ export const UserProfile = ({
const urlId = getSearchParam(fromUrl) const urlId = getSearchParam(fromUrl)
if (urlId && urlId !== ruid) setRuid(urlId) if (urlId && urlId !== ruid) setRuid(urlId)
} }
if (ruid) loadProfileData(ruid, backend, setData) if (ruid) loadProfileData(ruid, backend, setData, setTitle)
}, [uid, fromUrl, ruid]) }, [uid, fromUrl, ruid])
return ( return (
@ -121,7 +115,10 @@ export const Avatar = ({ ihash }) => {
) )
} }
async function loadProfileData(uid, backend, setData) { async function loadProfileData(uid, backend, setData, setTitle = false) {
const [status, body] = await backend.getUserProfile(uid) const [status, body] = await backend.getUserProfile(uid)
if (status === 200 && body.result === 'success' && body.profile) setData(body.profile) if (status === 200 && body.result === 'success' && body.profile) {
setData(body.profile)
if (typeof setTitle === 'function' && body.profile.username) setTitle(body.profile.username)
}
} }

View file

@ -9,7 +9,7 @@ import { Link as DefaultLink } from '@freesewing/react/components/Link'
import { LockIcon, PlusIcon } from '@freesewing/react/components/Icon' import { LockIcon, PlusIcon } from '@freesewing/react/components/Icon'
import { Spinner } from '@freesewing/react/components/Spinner' import { Spinner } from '@freesewing/react/components/Spinner'
import { Popout } from '@freesewing/react/components/Popout' import { Popout } from '@freesewing/react/components/Popout'
import { H1, H2, H3 } from '@freesewing/react/components/Heading' import { H3 } from '@freesewing/react/components/Heading'
import { Consent } from '@freesewing/react/components/Account' import { Consent } from '@freesewing/react/components/Account'
const Wrap = ({ children }) => ( const Wrap = ({ children }) => (
@ -104,7 +104,7 @@ const AccountStatusUnknown = ({ banner }) => (
</Wrap> </Wrap>
) )
const RoleLacking = ({ t, requiredRole, role, banner }) => ( const RoleLacking = ({ requiredRole, role, banner }) => (
<Wrap> <Wrap>
{banner} {banner}
<H3>You lack the required role to access this content</H3> <H3>You lack the required role to access this content</H3>
@ -174,7 +174,7 @@ const ConsentLacking = ({ banner, refresh }) => {
* @param {JSX.Element} props.children - The component children, will be rendered if props.js is not set * @param {JSX.Element} props.children - The component children, will be rendered if props.js is not set
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const RoleBlock = ({ children, role = "admin", Link = false }) => { export const RoleBlock = ({ children, role = 'admin', Link = false }) => {
if (!Link) Link = DefaultLink if (!Link) Link = DefaultLink
const requiredRole = role const requiredRole = role
@ -275,7 +275,6 @@ export const UserVisitorContent = ({ userContent = null, visitorContent = null }
const [ready, setReady] = useState(false) const [ready, setReady] = useState(false)
const [error, setError] = useState(false) const [error, setError] = useState(false)
const [refreshCount, setRefreshCount] = useState(0)
/* /*
* Avoid hydration errors * Avoid hydration errors
@ -300,14 +299,15 @@ export const UserVisitorContent = ({ userContent = null, visitorContent = null }
if (!account.bestBefore || account.bestBefore < Date.now()) verifyUser() if (!account.bestBefore || account.bestBefore < Date.now()) verifyUser()
} }
setReady(true) setReady(true)
}, [refreshCount]) }, [])
const refresh = () => {
setRefreshCount(refreshCount + 1)
setError(false)
}
if (!ready) return <Spinner /> if (!ready) return <Spinner />
if (error)
return (
<Popout type="error" title="Something went wrong" compact>
This is unexpected. You may want to report this.
</Popout>
)
return token && account.username ? userContent : visitorContent return token && account.username ? userContent : visitorContent
} }

View file

@ -1,11 +1,5 @@
// Utils // Utils
import { import { horFlexClasses, horFlexClassesNoSm, getSearchParam, navigate } from '@freesewing/utils'
linkClasses,
horFlexClasses,
horFlexClassesNoSm,
capitalize,
getSearchParam,
} from '@freesewing/utils'
// Context // Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks // Hooks
@ -21,13 +15,11 @@ import {
KeyIcon, KeyIcon,
LockIcon, LockIcon,
WarningIcon, WarningIcon,
GoogleIcon,
GitHubIcon,
FreeSewingIcon, FreeSewingIcon,
UserIcon, UserIcon,
} from '@freesewing/react/components/Icon' } from '@freesewing/react/components/Icon'
import { MfaInput, StringInput, PasswordInput } from '@freesewing/react/components/Input' import { MfaInput, StringInput, PasswordInput } from '@freesewing/react/components/Input'
import { H1, H2, H3, H4 } from '@freesewing/react/components/Heading' import { H1 } from '@freesewing/react/components/Heading'
/* /*
* *
@ -44,7 +36,7 @@ import { H1, H2, H3, H4 } from '@freesewing/react/components/Heading'
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const SignIn = ({ onSuccess = false, silent = false }) => { export const SignIn = ({ onSuccess = false, silent = false }) => {
const { account, setAccount, setToken, seenUser, setSeenUser } = useAccount() const { setAccount, setToken, seenUser, setSeenUser } = useAccount()
const backend = useBackend() const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext) const { setLoadingStatus } = useContext(LoadingStatusContext)
@ -149,15 +141,6 @@ export const SignIn = ({ onSuccess = false, silent = false }) => {
} }
} }
const initOauth = async (provider) => {
setLoadingStatus([true, 'Contacting the FreeSewing backend'])
const [status, body] = await backend.oauthInit(provider.toLowerCase())
if (status === 200 && body.result === 'success') {
setLoadingStatus([true, `Contacting ${capitalize(provider)}`])
window.location.href = body.authUrl
}
}
const btnClasses = `tw:daisy-btn tw:capitalize tw:w-full tw:mt-4 ${ const btnClasses = `tw:daisy-btn tw:capitalize tw:w-full tw:mt-4 ${
signInFailed ? 'tw:daisy-btn-warning' : 'tw:daisy-btn-primary' signInFailed ? 'tw:daisy-btn-warning' : 'tw:daisy-btn-primary'
} tw:transition-colors tw:ease-in-out tw:duration-300 ${horFlexClassesNoSm}` } tw:transition-colors tw:ease-in-out tw:duration-300 ${horFlexClassesNoSm}`
@ -412,7 +395,12 @@ export const SignInConfirmation = ({ onSuccess = false }) => {
} }
// Short-circuit errors // Short-circuit errors
if (error === 'noId') return <Popout type="error" title="Invalid Sign In URL">You seem to have arrived on this page in a way that is not supported</Popout> if (error === 'noId')
return (
<Popout type="error" title="Invalid Sign In URL">
You seem to have arrived on this page in a way that is not supported
</Popout>
)
if (error && mfa) if (error && mfa)
return error === 'signInFailed' ? ( return error === 'signInFailed' ? (
<> <>

View file

@ -3,7 +3,6 @@ import { validateEmail, validateTld, getSearchParam } from '@freesewing/utils'
// Hooks // Hooks
import React, { useState, useContext, useEffect } from 'react' import React, { useState, useContext, useEffect } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend' import { useBackend } from '@freesewing/react/hooks/useBackend'
// Context // Context
@ -12,16 +11,7 @@ import { ModalContext } from '@freesewing/react/context/Modal'
// Components // Components
import { Link } from '@freesewing/react/components/Link' import { Link } from '@freesewing/react/components/Link'
import { import { LeftIcon, HelpIcon, KeyIcon, EmailIcon } from '@freesewing/react/components/Icon'
LeftIcon,
RightIcon,
HelpIcon,
GoogleIcon,
GitHubIcon,
KeyIcon,
EmailIcon,
DownIcon,
} from '@freesewing/react/components/Icon'
import { ModalWrapper } from '@freesewing/react/components/Modal' import { ModalWrapper } from '@freesewing/react/components/Modal'
import { EmailInput } from '@freesewing/react/components/Input' import { EmailInput } from '@freesewing/react/components/Input'
import { IconButton } from '@freesewing/react/components/Button' import { IconButton } from '@freesewing/react/components/Button'
@ -42,7 +32,6 @@ export const SignUp = ({ embed = false }) => {
const [email, setEmail] = useState('') const [email, setEmail] = useState('')
const [emailValid, setEmailValid] = useState(false) const [emailValid, setEmailValid] = useState(false)
const [result, setResult] = useState(false) const [result, setResult] = useState(false)
const [showAll, setShowAll] = useState(false)
// Hooks // Hooks
const backend = useBackend() const backend = useBackend()
@ -93,14 +82,6 @@ export const SignUp = ({ embed = false }) => {
} }
} }
const initOauth = async (provider) => {
setLoadingStatus([true, 'Contacting the backend'])
const [status, body] = await backend.oauthInit(provider.toLowerCase())
if (status === 200 && body.result === 'success') {
setLoadingStatus([true, `Contacting ${provider}`])
window.location.href = body.authUrl
}
}
const Heading = embed const Heading = embed
? ({ children }) => <h2 className="tw:text-inherit">{children}</h2> ? ({ children }) => <h2 className="tw:text-inherit">{children}</h2>
: ({ children }) => <h1 className="tw:text-inherit">{children}</h1> : ({ children }) => <h1 className="tw:text-inherit">{children}</h1>
@ -200,10 +181,9 @@ export const SignUp = ({ embed = false }) => {
* *
* @component * @component
* @param {object} props - All component props * @param {object} props - All component props
* @param {function} [props.onSuccess = false] - A method to run when the sign in is successful
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const SignUpConfirmation = ({ onSuccess = false }) => { export const SignUpConfirmation = () => {
// State // State
const [id, setId] = useState() const [id, setId] = useState()
const [error, setError] = useState(false) const [error, setError] = useState(false)
@ -219,7 +199,12 @@ export const SignUpConfirmation = ({ onSuccess = false }) => {
}, [id, check]) }, [id, check])
// Short-circuit errors // Short-circuit errors
if (error === 'noId') return <Popout type="error" title="Invalid Sign Up URL">You seem to have arrived on this page in a way that is not supported</Popout> if (error === 'noId')
return (
<Popout type="error" title="Invalid Sign Up URL">
You seem to have arrived on this page in a way that is not supported
</Popout>
)
// If we do not (yet) have the data, show a loader // If we do not (yet) have the data, show a loader
if (!id || !check) if (!id || !check)
return ( return (

View file

@ -7,11 +7,7 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
import { Spinner } from '@freesewing/react/components/Spinner' 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 { ChartWrapper } from '@freesewing/react/components/Echart' import { ChartWrapper } from '@freesewing/react/components/Echart'
import { Popout } from '@freesewing/react/components/Popout'
const meta = {
title: 'FreeSewing by numbers',
description: 'Some high-level numbers about Freesewing',
}
const option = { const option = {
tooltip: { tooltip: {
@ -104,6 +100,13 @@ export const Stats = ({ Link = false }) => {
}, },
] ]
if (error)
return (
<Popout type="error" title="Something went wrong" compact>
This is unexpected. You may want to report this.
</Popout>
)
return ( return (
<> <>
<div className="tw:max-w-7xl tw:mx-auto tw:my-12 tw:px-4"> <div className="tw:max-w-7xl tw:mx-auto tw:my-12 tw:px-4">

View file

@ -37,7 +37,7 @@ export const Tabs = ({ tabs = '', active = 0, children, withModal = false }) =>
return ( return (
<div className="tw:border tw:border-base-300 tw:rounded-lg tw:pt-2"> <div className="tw:border tw:border-base-300 tw:rounded-lg tw:pt-2">
<div role="tablist" className="tw:daisy-tabs tw:daisy-tabs-border" role="tablist"> <div className="tw:daisy-tabs tw:daisy-tabs-border" role="tablist">
{tablist.map((title, tabId) => { {tablist.map((title, tabId) => {
const btnClasses = `tw:text-lg tw:font-bold tw:capitalize tw:daisy-tab tw:h-auto tw:grow tw:py-1 ${ const btnClasses = `tw:text-lg tw:font-bold tw:capitalize tw:daisy-tab tw:h-auto tw:grow tw:py-1 ${
activeTab === tabId ? 'tw:daisy-tab-active' : '' activeTab === tabId ? 'tw:daisy-tab-active' : ''

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react' import React from 'react'
import { shortUuid } from '@freesewing/utils' import { shortUuid } from '@freesewing/utils'
import { Link as WebLink } from '@freesewing/react/components/Link' import { Link as WebLink } from '@freesewing/react/components/Link'
import { CopyToClipboardButton } from '@freesewing/react/components/Button' import { CopyToClipboardButton } from '@freesewing/react/components/Button'
@ -14,9 +14,7 @@ import { CopyToClipboardButton } from '@freesewing/react/components/Button'
* @param {string} [props.label = false] - An optional label to pass to the CopyToClipboardButton * @param {string} [props.label = false] - An optional label to pass to the CopyToClipboardButton
* @returns {JSX.Element} * @returns {JSX.Element}
*/ */
export const Uuid = ({ uuid, href = false, label = "UUID", Link = false }) => { export const Uuid = ({ uuid, href = false, label = 'UUID', Link = false }) => {
const [full, setFull] = useState()
const short = shortUuid(uuid)
if (!Link) Link = WebLink if (!Link) Link = WebLink
if (href === false) if (href === false)

View file

@ -81,3 +81,5 @@ export const Xray = forwardRef((props, ref) => {
</Svg> </Svg>
) )
}) })
Xray.displayName = 'Xray'

View file

@ -96,9 +96,8 @@ export const PathXray = ({
) )
} }
const PathXrayInfo = ({ path, pathName, stackName, part }) => { const PathXrayInfo = ({ path, pathName, stackName }) => {
const [rounded, setRounded] = useState(true) const [rounded, setRounded] = useState(true)
const log = (val) => console.log(val)
const rounder = rounded ? round : (val) => val const rounder = rounded ? round : (val) => val
return ( return (

View file

@ -71,7 +71,7 @@ export const PointXray = ({
) )
} }
const PointXrayInfo = ({ point, pointName, stackName, part }) => { const PointXrayInfo = ({ point, pointName, stackName }) => {
const [rounded, setRounded] = useState(true) const [rounded, setRounded] = useState(true)
const rounder = rounded ? round : (val) => val const rounder = rounded ? round : (val) => val

View file

@ -0,0 +1,30 @@
import js from '@eslint/js'
import globals from 'globals'
import pluginReact from 'eslint-plugin-react'
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 } },
},
{
plugins: pluginReact.configs.flat.recommended.plugins,
languageOptions: pluginReact.configs.flat.recommended.languageOptions,
rules: {
...pluginReact.configs.flat.recommended.rules,
// Maybe one day someone wants to do this, but we use jsdoc for now
'react/prop-types': 0,
},
settings: {
react: {
version: 'detect',
},
},
},
])

View file

@ -1,8 +1,7 @@
// 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 { 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'

View file

@ -1,8 +1,6 @@
import React from 'react'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { atomWithHash } from 'jotai-location' import { atomWithHash } from 'jotai-location'
const filterAtom = atomWithHash('filter', { }) const filterAtom = atomWithHash('filter', {})
export const useFilter = () => useAtom(filterAtom) export const useFilter = () => useAtom(filterAtom)

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react' import { useState } from 'react'
export const useSelection = (items) => { export const useSelection = (items) => {
const [selection, setSelection] = useState({}) const [selection, setSelection] = useState({})

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react' import { useState } from 'react'
import set from 'lodash/set.js' import set from 'lodash/set.js'
import unset from 'lodash/unset.js' import unset from 'lodash/unset.js'

View file

@ -68,9 +68,11 @@ async function withoutBody(method = 'GET', url, headers = {}, raw = false, log =
try { try {
body = raw ? await response.text() : await response.json() body = raw ? await response.text() : await response.json()
} catch (err) { } catch (err) {
console.log(err)
try { try {
body = await response.text() body = await response.text()
} catch (err) { } catch (err) {
console.log(err)
body = false body = false
} }
} }
@ -125,9 +127,11 @@ async function withBody(method = 'POST', url, data, headers, raw = false, log =
try { try {
body = raw ? await response.text() : await response.json() body = raw ? await response.text() : await response.json()
} catch (err) { } catch (err) {
console.log(err)
try { try {
body = await response.text() body = await response.text()
} catch (err) { } catch (err) {
console.log(err)
body = false body = false
} }
} }

View file

@ -1,14 +0,0 @@
import React from 'react'
const html = (
<>
<button className="tw:btn-ghost">
<span className="tw:px-1 tw:text-sm tw:font-medium tw:whitespace-nowrap tw:border-2 tw:border-solid tw:rounded-l-lg tw:text-primary-content tw:bg-primary tw:border-primary ">
Code
</span>
<span className="tw:px-1 tw:text-sm tw:font-medium tw:whitespace-nowrap tw:border-2 tw:border-solid tw:rounded-r-lg tw:text-primary tw:bg-base-100 tw:border-primary ">
Joost De Cock
</span>
</button>
</>
)