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": {
"@commitlint/cli": "^19.0.3",
"@commitlint/config-conventional": "^19.0.3",
"@eslint/css": "^0.8.1",
"@eslint/js": "^9.27.0",
"@eslint/json": "^0.12.0",
"@eslint/markdown": "^6.4.0",
"@nx/eslint": "20.2.1",
"all-contributors-cli": "^6.26.1",
"axios": "^1.5.1",
"chalk": "^4.1.0",
"cross-env": "^7.0.2",
"eslint": "^8.23.1",
"eslint": "^9.27.0",
"eslint-plugin-jsonc": "^2.4.0",
"eslint-plugin-markdown": "^5.0.0",
"eslint-plugin-mongo": "^1.0.5",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-yaml": "^0.5.0",
"execa": "^9.3.1",
"globals": "^16.2.0",
"husky": "^9.0.10",
"js-yaml": "^4.0.0",
"lerna": "^8.0.0",

View file

@ -1,49 +1,34 @@
// Dependencies
import { DateTime } from 'luxon'
import orderBy from 'lodash/orderBy.js'
import { capitalize, shortDate } from '@freesewing/utils'
import { shortDate } from '@freesewing/utils'
import { apikeyLevels } from '@freesewing/config'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useEffect, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import { useSelection } from '@freesewing/react/hooks/useSelection'
// Components
import { TableWrapper } from '@freesewing/react/components/Table'
import { Link as WebLink } from '@freesewing/react/components/Link'
import {
BoolNoIcon,
BoolYesIcon,
PlusIcon,
RightIcon,
TrashIcon,
} from '@freesewing/react/components/Icon'
import { PlusIcon, RightIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { Uuid } from '@freesewing/react/components/Uuid'
import { Popout } from '@freesewing/react/components/Popout'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { NumberCircle } from '@freesewing/react/components/Number'
import { StringInput, Fieldset, ListInput } from '@freesewing/react/components/Input'
import { DisplayRow } from './shared.mjs'
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
import { TimeAgo, TimeToGo } from '@freesewing/react/components/Time'
import { KeyVal } from '@freesewing/react/components/KeyVal'
const t = (input) => {
console.log('t called', input)
return input
}
const fields = {
id: 'Key',
name: 'Name',
calls: 'Calls',
level: 'Level',
level: 'Level',
createdAt: 'Created',
expiresAt: 'Expires',
}
@ -242,13 +227,6 @@ const NewApikey = ({ onCreate = false }) => {
} else setLoadingStatus([true, 'An error occured. Please report this', true, false])
}
const clear = () => {
setApikey(false)
setGenerate(false)
setName('')
setLevel(1)
}
return (
<div className="tw:w-full">
<h2>New API key {apikey ? `: ${apikey.name}` : ''}</h2>

View file

@ -7,7 +7,7 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Components
import { BookmarkIcon, LeftIcon, PlusIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { BookmarkIcon, PlusIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { Link as WebLink } from '@freesewing/react/components/Link'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { StringInput } from '@freesewing/react/components/Input'
@ -33,7 +33,7 @@ const types = {
export const Bookmarks = () => {
// Hooks & Context
const backend = useBackend()
const { setModal, clearModal } = useContext(ModalContext)
const { setModal } = useContext(ModalContext)
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
// State
@ -194,7 +194,7 @@ const NewBookmark = ({ onCreated = false }) => {
// This method will create the bookmark
const createBookmark = async () => {
setLoadingStatus([true, 'Processing update'])
const [status, body] = await backend.createBookmark({
const [status] = await backend.createBookmark({
title,
url,
type: 'custom',

View file

@ -1,17 +1,13 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
import { NoIcon, OkIcon, RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'

View file

@ -1,36 +1,15 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { linkClasses, navigate } from '@freesewing/utils'
import { navigate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
const strings = {
yes: {
title: 'Yes, in case it may help me',
desc:
'Allowing us to compare your measurments to a baseline or others measurements sets ' +
'allows us to detect potential problems in your measurements or patterns.',
},
no: {
title: 'No, never compare',
desc:
'We get it, comparison is the thief of joy. Just be aware that this limits our ability ' +
'to warn you about potential problems in your measurements sets or patterns.',
},
}
/**
* 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
const removeAccount = async () => {
setLoadingStatus([true, 'One moment please'])
const [status, body] = await backend.removeAccount()
const [status] = await backend.removeAccount()
if (status === 200) {
setLoadingStatus([true, 'All done, farewell', true, true])
setToken(null)

View file

@ -1,39 +1,16 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { controlDesc } from '@freesewing/config'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import React from 'react'
import { useControl } from '@freesewing/react/hooks/useControl'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { RightIcon, NoIcon, OkIcon, SaveIcon } from '@freesewing/react/components/Icon'
import { RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { ControlScore } from '@freesewing/react/components/Control'
import { IconButton } from '@freesewing/react/components/Button'
import { WelcomeIcons } from './shared.mjs'
const strings = {
1: {
title: 'Keep it as simple as possible',
desc:
'Allowing us to compare your measurments to a baseline or others measurements sets ' +
'allows us to detect potential problems in your measurements or patterns.',
},
2: {
title: 'No, never compare',
desc:
'We get it, comparison is the thief of joy. Just be aware that this limits our ability ' +
'to warn you about potential problems in your measurements sets or patterns.',
},
}
/**
* A component to manage the user's control/UX setting
*

View file

@ -1,15 +1,11 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { validateEmail, validateTld, getSearchParam } from '@freesewing/utils'
import { validateEmail, validateTld, getSearchParam, navigate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext, useEffect } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SaveIcon } from '@freesewing/react/components/Icon'
@ -22,11 +18,10 @@ import { Spinner } from '@freesewing/react/components/Spinner'
*
* @component
* @param {object} props - All component props
* @param {bool} [props.welcome = false] - Set to true to render the welcome/onboarding view
* @param {React.Component} props.Link - A framework specific Link component for client-side routing
* @returns {JSX.Element}
*/
export const Email = ({ welcome = false, Link = false }) => {
export const Email = ({ Link = false }) => {
if (!Link) Link = WebLink
// Hooks
@ -104,7 +99,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
const [check, setCheck] = useState()
// Hooks
const { setAccount, setToken } = useAccount()
const { setAccount } = useAccount()
const backend = useBackend()
// Context
@ -134,7 +129,7 @@ export const EmailChangeConfirmation = ({ onSuccess = false }) => {
})
// If it works, store account, which runs the onSuccess handler
if (body.result === 'success' && body.account) return storeAccount(body)
if (status === 200 && body.result === 'success' && body.account) return storeAccount(body)
// If we get here, we're not sure what's wrong
if (body.error) return setError(body.error)
return setError(true)

View file

@ -1,11 +1,8 @@
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { DownloadIcon } from '@freesewing/react/components/Icon'

View file

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

View file

@ -1,14 +1,12 @@
// Dependencies
import yaml from 'js-yaml'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SaveIcon } from '@freesewing/react/components/Icon'
import { FileInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { Yaml } from '@freesewing/react/components/Yaml'
@ -41,7 +39,7 @@ export const ImportSet = () => {
if (set.measurements || set.measies) {
const name = set.name || 'J. Doe'
setLoadingStatus([true, `Importing ${name}`])
const [status, body] = await backend.createSet({
const [status] = await backend.createSet({
name: set.name || 'J. Doe',
units: set.units || 'metric',
notes: set.notes || '',

View file

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

View file

@ -1,20 +1,15 @@
// Dependencies
import { horFlexClasses } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, LockIcon } from '@freesewing/react/components/Icon'
import { PasswordInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { NumberCircle } from '@freesewing/react/components/Number'
import { CopyToClipboardButton } from '@freesewing/react/components/Button'
/**

View file

@ -1,18 +1,15 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { linkClasses } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
import { NoIcon, OkIcon, RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'

View file

@ -1,15 +1,9 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
import { horFlexClasses } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { RightIcon, SaveIcon } from '@freesewing/react/components/Icon'
@ -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
* @returns {JSX.Element}
*/
export const Password = ({ welcome = false, Link = false }) => {
export const Password = ({ Link = false }) => {
if (!Link) Link = WebLink
// Hooks
const backend = useBackend()

View file

@ -1,25 +1,13 @@
// Dependencies
import orderBy from 'lodash/orderBy.js'
import {
cloudflareImageUrl,
capitalize,
shortDate,
horFlexClasses,
newPatternUrl,
patternUrlFromState,
} from '@freesewing/utils'
import { cloudflareImageUrl, horFlexClasses, patternUrlFromState } from '@freesewing/utils'
import { urls, control as controlConfig } from '@freesewing/config'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useEffect, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import { useSelection } from '@freesewing/react/hooks/useSelection'
// Components
import Markdown from 'react-markdown'
import {
@ -28,7 +16,7 @@ import {
PassiveImageInput,
ListInput,
} from '@freesewing/react/components/Input'
import { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link'
import { Link as WebLink } from '@freesewing/react/components/Link'
import {
BoolNoIcon,
BoolYesIcon,
@ -42,7 +30,6 @@ import {
ResetIcon,
UploadIcon,
} from '@freesewing/react/components/Icon'
import { DisplayRow } from './shared.mjs'
import { TimeAgo } from '@freesewing/react/components/Time'
import { Popout } from '@freesewing/react/components/Popout'
import { ModalWrapper } from '@freesewing/react/components/Modal'
@ -348,16 +335,7 @@ const BadgeLink = ({ label, href }) => (
/**
* Helper component to show the pattern title, image, and various buttons
*/
const PatternHeader = ({
pattern,
Link,
account,
setModal,
setEdit,
togglePublic,
save,
clone,
}) => (
const PatternHeader = ({ pattern, Link, account, setModal, setEdit, togglePublic, clone }) => (
<>
<h2>{pattern.name}</h2>
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-2 tw:text-sm tw:items-center tw:mb-2">

View file

@ -1,16 +1,12 @@
// Dependencies
import orderBy from 'lodash/orderBy.js'
import { capitalize, shortDate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useEffect, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
import { useSelection } from '@freesewing/react/hooks/useSelection'
// Components
import { TableWrapper } from '@freesewing/react/components/Table'
import { PatternCard } from '@freesewing/react/components/Account'
@ -23,11 +19,6 @@ import {
TrashIcon,
} 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
*
@ -97,13 +88,15 @@ export const Patterns = ({ Link = false }) => {
onClick={removeSelectedPatterns}
disabled={count < 1}
>
<TrashIcon /> {count} {t('patterns')}
<TrashIcon /> {count} Patterns
</button>
<Link
className="tw:daisy-btn tw:daisy-btn-primary tw:capitalize tw:w-full tw:md:w-auto tw:hover:text-primary-content"
href="/editor/"
>
<span className="tw:text-primary-content"><PlusIcon /></span>
<span className="tw:text-primary-content">
<PlusIcon />
</span>
<span className="tw:text-primary-content">Create a new pattern</span>
</Link>
</div>

View file

@ -1,15 +1,11 @@
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { ReloadIcon } from '@freesewing/react/components/Icon'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
/**

View file

@ -3,16 +3,12 @@ import { navigate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { BackIcon as ExitIcon, TrashIcon } from '@freesewing/react/components/Icon'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
import { ModalWrapper } from '@freesewing/react/components/Modal'

View file

@ -6,14 +6,13 @@ import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useContext } from 'react'
import React, { useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { BackIcon, NoIcon } from '@freesewing/react/components/Icon'
import { Popout } from '@freesewing/react/components/Popout'
import { IconButton } from '@freesewing/react/components/Button'
import { ModalWrapper } from '@freesewing/react/components/Modal'
@ -39,7 +38,7 @@ export const Restrict = ({ Link = false }) => {
// Helper method to restrict the account
const restrictAccount = async () => {
setLoadingStatus([true, 'Talking to the backend'])
const [status, body] = await backend.restrictAccount()
const [status] = await backend.restrictAccount()
if (status === 200) {
setLoadingStatus([true, 'Done. Consider yourself restrcited.', true, true])
signOut()

View file

@ -58,11 +58,6 @@ import { bundlePatternTranslations, draft, flattenFlags } from '../Editor/lib/in
import { Bonny } from '@freesewing/bonny'
import { MiniNote, MiniTip } from '../Mini/index.mjs'
const t = (input) => {
console.log('t called', input)
return input
}
/**
* Component to show an individual measurements set
*
@ -650,8 +645,8 @@ const SuggestCset = ({ mset, Link }) => {
const [name, setName] = useState('')
const [notes, setNotes] = useState('')
const [submission, setSubmission] = useState(false)
console.log(mset)
// Context
const { setLoadingStatus } = useContext(LoadingStatusContext)
// Hooks
const backend = useBackend()
@ -803,7 +798,7 @@ const RenderedCset = ({ mset, imperial }) => {
<strong>{formatMm(pattern.parts[0].front.points.head.y * -1, imperial)}</strong> high.
</p>
<p>Here is what the automated analysis found:</p>
{Object.entries(flattenFlags(flags)).map(([key, flag], i) => {
{Object.entries(flattenFlags(flags)).map(([key, flag]) => {
const desc = strings[flag.desc] || flag.desc
return (
@ -844,7 +839,7 @@ const RenderedCset = ({ mset, imperial }) => {
</li>
<li>
This preview is an <strong>approximation</strong>, not an exact representation. Bodies
have many variations that can't be captured with just a few measurements. We are
have many variations that can&apos;t be captured with just a few measurements. We are
missing some information, like how weight is distributed or your posture.
</li>
<li>
@ -881,7 +876,7 @@ export const NewSet = () => {
// Hooks
const backend = useBackend()
const { account } = useAccount()
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
const { setLoadingStatus } = useContext(LoadingStatusContext)
// State
const [name, setName] = useState('')

View file

@ -1,8 +1,7 @@
// Dependencies
import { measurements } from '@freesewing/config'
import { measurements as measurementsTranslations } from '@freesewing/i18n'
import { requiredMeasurements as designMeasurements } from '@freesewing/collection'
import { cloudflareImageUrl, capitalize, hasRequiredMeasurements } from '@freesewing/utils'
import { cloudflareImageUrl, hasRequiredMeasurements } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'

View file

@ -1,17 +1,13 @@
// Dependencies
import { welcomeSteps } from './shared.mjs'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SaveIcon, RightIcon } from '@freesewing/react/components/Icon'
import { RightIcon } from '@freesewing/react/components/Icon'
import { ListInput } from '@freesewing/react/components/Input'
import { NumberCircle } from '@freesewing/react/components/Number'
import { IconButton } from '@freesewing/react/components/Button'

View file

@ -57,10 +57,6 @@ export const Username = ({ welcome = false, Link = false }) => {
? '/welcome/' + welcomeSteps[account.control][5]
: '/docs/about/guide'
let btnClasses = 'daisy-btn mt-4 capitalize '
if (welcome) btnClasses += 'w-64 daisy-btn-secondary'
else btnClasses += 'w-full daisy-btn-primary'
return (
<div className="tw:w-full">
<StringInput

View file

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

View file

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

View file

@ -1,8 +1,7 @@
// Dependencies
import { uiRoles as roles } from '@freesewing/config'
import { userAvatarUrl } from '@freesewing/utils'
// Hooks
import React, { useState, useContext, useEffect } from 'react'
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Context
@ -14,7 +13,6 @@ import { Spinner } from '@freesewing/react/components/Spinner'
import { Link as WebLink } from '@freesewing/react/components/Link'
import { SearchIcon } from '@freesewing/react/components/Icon'
import { KeyVal } from '@freesewing/react/components/KeyVal'
import { Markdown } from '@freesewing/react/components/Markdown'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { AccountStatus, UserRole } from '@freesewing/react/components/Account'
@ -313,95 +311,3 @@ const ImpersonateButton = ({ userId }) => {
</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 { copyToClipboard } from '@freesewing/utils'
import ReactDOMServer from 'react-dom/server'
import { CopyIcon, OkIcon } from '@freesewing/react/components/Icon'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
const strip = (html) =>
typeof DOMParser === 'undefined'
? html
: new DOMParser().parseFromString(html, 'text/html').body.textContent || ''
const handleCopied = (content, setCopied, setLoadingStatus, label) => {
copyToClipboard(content)
setCopied(true)
@ -55,7 +49,7 @@ export const CopyToClipboardButton = ({ children, content, label = false, sup =
) : (
<CopyIcon className={`${style} tw:text-inherit tw:group-hover:text-secondary`} />
)}
{sup ? null : children}
{sup ? null : children}
</button>
)
}

View file

@ -4,32 +4,22 @@ import {
collection,
tags,
techniques,
designers,
developers,
examples,
measurements,
requiredMeasurements,
optionalMeasurements,
} from '@freesewing/collection'
import { capitalize, linkClasses, mutateObject } from '@freesewing/utils'
import { measurements as measurementsTranslations } from '@freesewing/i18n'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
import React, { useState, useContext, Fragment } from 'react'
import React, { useState, Fragment } from 'react'
import { useFilter } from '@freesewing/react/hooks/useFilter'
// Components
import { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link'
import { Link as WebLink } from '@freesewing/react/components/Link'
import {
CircleIcon,
CisFemaleIcon,
DocsIcon,
FilterIcon,
HeartIcon,
NewPatternIcon,
ResetIcon,
ShowcaseIcon,
@ -39,7 +29,6 @@ import {
lineDrawingsBack,
} from '@freesewing/react/components/LineDrawing'
import { IconButton } from '@freesewing/react/components/Button'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { KeyVal } from '@freesewing/react/components/KeyVal'
import { MissingLinedrawing } from '../LineDrawing/missing.mjs'
@ -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 }) => {
if (!Link) Link = WebLink
@ -374,10 +331,6 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
// State
const [back, setBack] = useState(false)
// Context
const { setModal, clearModal } = useContext(ModalContext)
const { setLoadingStatus } = useContext(LoadingStatusContext)
if (!design) return null
// Line drawings
@ -391,13 +344,6 @@ export const DesignInfo = ({ Link = false, design = false, noDocsLink = false })
: [about[design].design]
const tags = about[design].tags || []
const techniques = about[design].techniques || []
const colors = {
1: 'success',
2: 'success',
3: 'warning',
4: 'warning',
5: 'error',
}
const makeButton = (
<div className={`tw:grid tw:grid-cols-1 tw:gap-2 tw:mb-4`}>
@ -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.
* So instead, we handle this in React state
*/
const getProps = (isActive = false) => ({
const getProps = () => ({
className: `tw:p-0 tw:rounded-lg tw:bg-transparent tw:hover:cursor-pointer
tw:w-full tw:h-auto tw:content-start tw:text-left tw:list-none`,
})
const getSubProps = (isActive) => ({
const getSubProps = () => ({
className: `tw:p-0 tw:rounded-none tw:bg-transparent tw:w-full tw:h-auto
tw:content-start tw:text-left tw:list-none`,
})

View file

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

View file

@ -24,7 +24,6 @@ import {
ResetIcon,
RightIcon,
RocketIcon,
RotateIcon,
SaIcon,
SaveAsIcon,
SaveIcon,
@ -133,7 +132,7 @@ export const HeaderMenuTestViewDesignMeasurements = (props) => {
}
export const HeaderMenuDropdown = (props) => {
const { tooltip, toggle, open, setOpen, id, end = false } = props
const { toggle, open, setOpen, id } = props
const [localOpen, setLocalOpen] = useState(false)
useEffect(() => {
@ -460,10 +459,8 @@ export const HeaderMenuUndoIcons = (props) => {
}
export const HeaderMenuTestIcons = (props) => {
const { update, state, Design } = props
const { update } = props
const Button = HeaderMenuButton
const size = 'tw:w-5 tw:h-5'
const undos = state._?.undos && state._.undos.length > 0 ? state._.undos : false
return (
<div className="tw:flex tw:flex-row tw:flex-wrap tw:items-center tw:justify-center tw:px-0.5 tw:lg:px-1">
@ -660,11 +657,6 @@ export const HeaderMenuLayoutViewIcons = (props) => {
}
const pages = pattern.setStores[0].get('pages', {})
const format = state.ui.print?.pages?.size
? state.ui.print.pages.size
: settings.units === 'imperial'
? 'letter'
: 'a4'
const { cols, rows, count } = pages
const blank = cols * rows - count

View file

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

View file

@ -85,7 +85,7 @@ export const MovablePattern = ({
const sortedRenderProps = { ...renderProps, stacks: sortedStacks }
const Stack = ({ stackName, stack, settings, components, t }) => (
const Stack = ({ stackName, stack, settings, components }) => (
<MovableStack
{...{
stackName,
@ -397,7 +397,7 @@ function angle(pointA, pointB) {
const rectSize = 24
const Button = ({ onClickCb, transform, Icon, children, title = '' }) => {
const Button = ({ onClickCb, transform, Icon, title = '' }) => {
const _onClick = (event) => {
event.stopPropagation()
onClickCb(event)
@ -413,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 */
export const Buttons = ({ transform, flip, rotate, resetPart, rotate90, iconSize }) => {
return (

View file

@ -1,17 +1,9 @@
import React from 'react'
import { useDesignTranslation } from '@freesewing/react/hooks/useDesignTranslation'
import { ZoomContextProvider } from './ZoomablePattern.mjs'
import {
HeaderMenu,
HeaderMenuDraftViewDesignOptions,
HeaderMenuDraftViewCoreSettings,
HeaderMenuDraftViewUiPreferences,
HeaderMenuDraftViewFlags,
} from './HeaderMenu.mjs'
import { HeaderMenu } from './HeaderMenu.mjs'
import { DesignOptionsMenu } from './menus/DesignOptionsMenu.mjs'
import { CoreSettingsMenu } from './menus/CoreSettingsMenu.mjs'
import { UiPreferencesMenu } from './menus/UiPreferencesMenu.mjs'
import { Accordion } from './Accordion.mjs'
/**
* A layout for views that include a drafted pattern
@ -24,9 +16,7 @@ import { Accordion } from './Accordion.mjs'
* @param {object] pattern - The drafted pattern
*/
export const PatternLayout = (props) => {
const { menu = null, Design, pattern, update, config, state } = props
const i18n = useDesignTranslation(Design.designConfig.data.id)
const flags = props.pattern?.setStores?.[0]?.plugins?.['plugin-annotations']?.flags
const { Design, pattern, update, config, state } = props
return (
<ZoomContextProvider>

View file

@ -58,7 +58,7 @@ export const UserSetPicker = ({
href={config.hrefNewSet}
className="tw:daisy-btn tw:daisy-btn-accent tw:capitalize"
target="_BLANK"
rel="nofollow"
rel="noreferrer"
>
Create a new measurements set
</a>

View file

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

View file

@ -36,10 +36,8 @@ export const MenuItem = ({
allowOverride = false,
ux = 5,
state,
docs,
config,
Design,
i18n,
}) => {
// Local state - whether the override input should be shown
const [override, setOverride] = useState(false)

View file

@ -21,7 +21,7 @@ import {
MenuScaleSettingValue,
} from './Value.mjs'
import { MenuItemGroup, MenuItem } from './Container.mjs'
import { SettingsIcon } from '@freesewing/react/components/Icon'
import { SettingsIcon, TrashIcon } from '@freesewing/react/components/Icon'
/**
* The core settings menu

View file

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

View file

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

View file

@ -92,7 +92,7 @@ const SampleOptionButton = ({ name, i18n, update }) => (
</button>
)
const SampleMeasurementButton = ({ name, i18n, update }) => (
const SampleMeasurementButton = ({ name, update }) => (
<button
className={
'tw:daisy-btn tw:daisy-btn-outline tw:daisy-btn-sm tw:mx-2 ' +

View file

@ -19,7 +19,7 @@ export const UiPreferencesMenu = ({ update, state, Design }) => {
}
const values = {
aside: MenuListValue,
ux: (props) => <span>{state.ui.ux}/5</span>,
ux: () => <span>{state.ui.ux}/5</span>,
rotate: MenuListValue,
renderer: MenuListValue,
}

View file

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

View file

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

View file

@ -27,8 +27,8 @@ export const DraftErrorHandler = ({ failure, errors }) => {
.
</p>
<p>
If you believe your measurements are correct and/or if you'd like further assistance, you
can ask for help <Link href="https://forum.freesewing.eu">on our forum</Link>,{' '}
If you believe your measurements are correct and/or if you&apos;d like further assistance,
you can ask for help <Link href="https://forum.freesewing.eu">on our forum</Link>,{' '}
<Link href="https://discord.freesewing.org">our Discord server</Link>, or{' '}
<Link href="https://codeberg.org/freesewing/freesewing/issues">report an issue</Link>.
</p>

View file

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

View file

@ -23,10 +23,10 @@ import { EditIcon, CodeIcon, TipIcon, PrintIcon } from '@freesewing/react/compon
export const ExportView = (props) => {
const { config, state, update } = props
const { settings = {} } = state // Guard against undefined settings
const [link, setLink] = useState(false)
const [format, setFormat] = useState(false)
const setLink = useState(false)[1]
const setFormat = useState(false)[1]
const { protocol, hostname, port } = window.location
const { protocol, port } = window.location
const site =
(protocol === 'https:' && port === 443) || (protocol === 'http:' && port === 80)
? `${window.location.protocol}//${window.location.hostname}`
@ -102,6 +102,7 @@ export const ExportView = (props) => {
<H3>ISO paper sizes</H3>
{['a4', 'a3', 'a2', 'a1', 'a0'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
onClick={() => exportPattern({ ...exportProps, format })}
>
@ -114,6 +115,7 @@ export const ExportView = (props) => {
<H3>Other paper sizes</H3>
{['letter', 'legal', 'tabloid'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
onClick={() => exportPattern({ ...exportProps, format })}
>
@ -130,6 +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">
{['svg', 'pdf'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
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">
{['json', 'yaml'].map((format) => (
<button
key={format}
className={`${horFlexClasses} tw:daisy-btn tw:daisy-btn-primary tw:uppercase`}
onClick={() => exportPattern({ ...exportProps, format })}
>

View file

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

View file

@ -8,8 +8,8 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { RoleBlock } from '@freesewing/react/components/Role'
import { Popout } from '@freesewing/react/components/Popout'
import { StringInput } from '@freesewing/react/components/Input'
import { SaveAsIcon } from '@freesewing/react/components/Icon'
import { StringInput, MarkdownInput } from '@freesewing/react/components/Input'
import { SaveAsIcon, SaveIcon } from '@freesewing/react/components/Icon'
import { H1 } from '@freesewing/react/components/Heading'
import { Link, SuccessLink } from '@freesewing/react/components/Link'
import { HeaderMenu } from '../HeaderMenu.mjs'
@ -67,7 +67,8 @@ export const SaveView = ({ config, state, update }) => {
}
const savePattern = async () => {
setLoadingStatus([true, 'Saving pattern...'])
const loadingId = 'savePattern'
update.startLoading(loadingId)
const patternData = {
design: state.design,
settings,
@ -77,8 +78,9 @@ export const SaveView = ({ config, state, update }) => {
}
const result = await backend.updatePattern(saveAs.pattern, patternData)
if (result.success) {
update.stopLoading(loadingId)
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>
)}
<button
className={`${classeshorFlexNoSm} tw:btn tw:btn-primary tw:btn-lg tw:w-full tw:mt-2 tw:my-8`}
className={`tw:flex tw:flex-row tw:items-center tw:gap-2 tw:btn tw:btn-primary tw:btn-lg tw:w-full tw:mt-2 tw:my-8`}
onClick={savePattern}
>
<SaveIcon className="tw:h-8 tw:w-8" />
Save Patter #{saveAs.pattern}
Save Pattern #{saveAs.pattern}
</button>
</>
) : null}
@ -137,11 +139,7 @@ export const SaveView = ({ config, state, update }) => {
}
/>
{withNotes ? (
<Swizzled.components.MarkdownInput
label="Pattern notes"
current={notes}
update={setNotes}
/>
<MarkdownInput label="Pattern notes" current={notes} update={setNotes} />
) : null}
<div className="tw:flex tw:flex-row tw:gap-2 tw:mt-8">
<button

View file

@ -69,7 +69,6 @@ export const TestView = ({ Design, state, update, config }) => {
patternLocale={state.locale || 'en'}
rotate={state.ui.rotate}
strings={strings}
rotate={state.ui.rotate}
/>
)

View file

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

View file

@ -70,7 +70,7 @@ export const ViewPicker = ({ Design, update, state }) => {
)
}
const MainCard = ({ view, update, Design }) => {
const MainCard = ({ view, update }) => {
const Icon = viewIcons[view]
return (

View file

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

View file

@ -101,7 +101,7 @@ export function menuCoreSettingsStructure({
title: 'Seam Allowance Size',
about: (
<>
Controls the size of the pattern's seam allowance.
Controls the size of the pattern&apos;s seam allowance.
<CoreDocsLink item="sa" />
</>
),

View file

@ -9,6 +9,7 @@ const DesignDocsLink = ({ design, item }) => (
href={`/docs/designs/${design}/options/#${item.toLowerCase()}`}
className={`${linkClasses} tw:px-2`}
target="_BLANK"
rel="noreferrer"
>
Learn more
</a>
@ -62,14 +63,14 @@ export function menuDesignOptionsStructure(design, options, settings, asFullList
option.valueTitles = {}
option.choiceTitles = {}
option.choiceDescriptions = {}
for (const entry of option.list) {
option.list.forEach(() => {
option.choiceTitles.false = eno[`${option.name}No`]?.t || option.name
option.choiceDescriptions.false = eno[`${option.name}No`]?.d || 'No'
option.valueTitles.false = 'No'
option.choiceTitles.true = eno[`${option.name}Yes`]?.t || 'Yes'
option.choiceDescriptions.true = eno[`${option.name}Yes`]?.d || 'No'
option.valueTitles.true = 'Yes'
}
})
}
if (typeof option.menu === 'function')
option.menu = asFullList

View file

@ -2,7 +2,7 @@
import React from 'react'
import { defaultConfig } from '../config/index.mjs'
import { round, formatMm, randomLoadingMessage } from '@freesewing/utils'
import { formatDesignOptionValue, menuCoreSettingsStructure } from './index.mjs'
import { formatDesignOptionValue, menuCoreSettingsStructure, fractionToDecimal } from './index.mjs'
import { menuUiPreferencesStructure } from './ui-preferences.mjs'
import { i18n } from '@freesewing/collection'
import { i18n as pluginI18n } from '@freesewing/core-plugins'
@ -721,15 +721,15 @@ export function cloudImageUrl({ id = 'default-avatar', variant = 'public' }) {
/*
* Return something default so that people will actually change it
*/
if (!id || id === 'default-avatar') return config.cloudImageDflt
if (!id || id === 'default-avatar') return defaultConfig.cloudImageDflt
/*
* If the variant is invalid, set it to the smallest thumbnail so
* people don't load enourmous images by accident
*/
if (!config.cloudImageVariants.includes(variant)) variant = 'sq100'
if (!defaultConfig.cloudImageVariants.includes(variant)) variant = 'sq100'
return `${config.cloudImageUrl}${id}/${variant}`
return `${defaultConfig.cloudImageUrl}${id}/${variant}`
}
/**
* This method does nothing. It is used to disable certain methods

View file

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

View file

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

View file

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

View file

@ -10,7 +10,6 @@ import {
import { collection } from '@freesewing/collection'
import { measurements as measurementsTranslations } from '@freesewing/i18n'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useCallback, useContext } from 'react'
@ -19,7 +18,6 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { TrashIcon, ResetIcon, UploadIcon, HelpIcon } from '@freesewing/react/components/Icon'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { isDegreeMeasurement } from '@freesewing/config'
import { Tabs, Tab } from '@freesewing/react/components/Tab'
import Markdown from 'react-markdown'
@ -68,31 +66,50 @@ const HelpLink = ({ help, Link = false }) => {
* @returns {JSX.Element}
*/
export const Fieldset = ({
Link=false,
box=false,
Link = false,
box = false,
children,
label=false,
labelBL=false,
labelBR=false,
labelTR=false,
legend=false,
forId='',
help=false,
label = false,
labelBL = false,
labelBR = false,
labelTR = false,
legend = false,
forId = '',
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 className="tw:daisy-fieldset-legend tw:px-2 tw:pb-1">
{legend}<HelpLink {...{ help, Link }} />
{legend}
<HelpLink {...{ help, Link }} />
</legend>
) : null}
<div className="tw:flex tw:flex-row tw:justify-between tw:px-2">
{label ? <label className="tw:daisy-label" htmlFor={forId}>{label}</label> : null }
{labelTR ? <label className="tw:daisy-label" htmlFor={forId}>{labelTR}</label> : null }
{label ? (
<label className="tw:daisy-label" htmlFor={forId}>
{label}
</label>
) : null}
{labelTR ? (
<label className="tw:daisy-label" htmlFor={forId}>
{labelTR}
</label>
) : null}
</div>
{children}
<div className="tw:flex tw:flex-row tw:justify-between tw:px-2">
{labelBL ? <label className="tw:daisy-label" htmlFor={forId}>{labelBL}</label> : null }
{labelBR ? <label className="tw:daisy-label" htmlFor={forId}>{labelBR}</label> : null }
{labelBL ? (
<label className="tw:daisy-label" htmlFor={forId}>
{labelBL}
</label>
) : null}
{labelBR ? (
<label className="tw:daisy-label" htmlFor={forId}>
{labelBR}
</label>
) : null}
</div>
</fieldset>
)
@ -153,7 +170,7 @@ export const ButtonFrame = ({ active, children, dense, noBg, onClick }) => (
export const NumberInput = ({
box = false,
current,
help=false,
help = false,
inputMode = 'decimal',
label = false,
labelBL = false,
@ -212,7 +229,7 @@ export const NumberInput = ({
export const StringInput = ({
box = false,
current,
help=false,
help = false,
label = false,
labelBL = false,
labelBR = false,
@ -265,7 +282,7 @@ export const StringInput = ({
export const MfaInput = ({
box = false,
current,
help=false,
help = false,
label = false,
labelBL = false,
labelBR = false,
@ -273,12 +290,26 @@ export const MfaInput = ({
id = 'mfa',
inputMode = 'numeric',
legend = false,
placeholder="MFA Code",
placeholder = 'MFA Code',
update,
valid = (val) => val.length > 4,
}) => (
<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 = ({
box = false,
current,
help=false,
help = false,
label = false,
labelBL = false,
labelTR = false,
@ -712,7 +743,7 @@ export const MarkdownInput = ({
labelTR = false,
legend = false,
update,
placeholder='',
placeholder = '',
}) => (
<Fieldset {...{ box, help, label, labelTR, labelBL, labelBR, legend }} forId={id}>
<Tabs tabs={['edit', 'preview']}>
@ -743,7 +774,6 @@ export const MarkdownInput = ({
* @component
* @param {object} props - All component props
* @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} [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
@ -758,7 +788,6 @@ export const MarkdownInput = ({
*/
export const MeasurementInput = ({
box = false,
current,
help = false,
id = '',
imperial = false,
@ -768,7 +797,7 @@ export const MeasurementInput = ({
m,
update,
original,
placeholder='',
placeholder = '',
}) => {
const isDegree = isDegreeMeasurement(m)
const units = imperial ? 'imperial' : 'metric'
@ -876,7 +905,6 @@ export const MeasurementInput = ({
* @param {string} [props.legend = false] - The fieldset legend
* @param {function} props.update - The onChange handler
* @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}
*/
export const FileInput = ({
@ -892,7 +920,6 @@ export const FileInput = ({
legend = false,
update,
original,
valid = () => true,
}) => {
/*
* Ondrop handler
@ -974,8 +1001,6 @@ export const FileInput = ({
* @param {array} [props.list = [true, false] - An array of values to choose between
* @param {function} props.update - The onChange handler
* @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}
*/
export const ToggleInput = ({
@ -993,8 +1018,6 @@ export const ToggleInput = ({
list = [true, false],
update,
on = true,
original,
valid = () => true,
}) => (
<Fieldset {...{ box, help, labelTR, labelBL, labelBR, legend }} forId={id}>
<label className="tw:daisy-label">
@ -1009,8 +1032,7 @@ export const ToggleInput = ({
/>
{label
? `${label} (${current === on ? labels[0] : labels[1]})`
: `${current === on ? labels[0] : labels[1]}`
}
: `${current === on ? labels[0] : labels[1]}`}
</label>
</Fieldset>
)

View file

@ -23,7 +23,7 @@ export const KeyVal = ({
href = false,
onClick = false,
}) => {
const [copied, setCopied] = useState(false)
const setCopied = useState(false)[1]
const { setLoadingStatus } = useContext(LoadingStatusContext)
let colorClasses1 = primaryClasses1
@ -83,7 +83,6 @@ export const KeyVal = ({
const LinkKeyVal = ({
k,
val,
color = 'primary',
small = false,
href = false,
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 errorClasses2 = `tw:text-error tw:border-error`
const PrimarySpans = ({ small, k, val }) => (
<>
<span
className={`${sharedClasses} ${small ? 'tw:rounded-l' : 'tw:rounded-l-lg'} ${primaryClasses} ${small ? 'tw:text-xs' : ''}`}
>
{k}
</span>
<span
className={`${sharedClasses} ${small ? 'tw:rounded-r' : 'tw:rounded-r-lg'} ${primaryClasses} ${small ? 'tw:text-xs' : ''}`}
>
{val}
</span>
</>
)
const handleCopied = (setCopied, setLoadingStatus, label) => {
setCopied(true)
setLoadingStatus([

View file

@ -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 {JSX.Element} [props.children = []] - The component children to render inside the layout
* @param {array} [props.crumbs = []] - Data for the breadcrumbs, see Breadcrumbs
* @param {string} props.description - The page description
* @param {string} props.title - The page title
* @returns {JSX.Element}
*/
export const Layout = ({ children = [], crumbs = [], description, Link = false, title }) => {
export const Layout = ({ children = [], crumbs = [], Link = false, title }) => {
if (!Link) Link = DefaultLink
return (

View file

@ -48,7 +48,7 @@ export const AlbertFront = ({ className, stroke = 1 }) => (
* @returns {JSX.Element}
*/
export const AlbertBack = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="74 0 74 119" {...props}>
<LineDrawingWrapper viewBox="74 0 74 119" {...{ className, stroke }}>
<Back stroke={stroke * strokeScale} />
</LineDrawingWrapper>
)

View file

@ -17,7 +17,7 @@ const strokeScale = 0.7
* @returns {JSX.Element}
*/
export const Bent = ({ className, stroke = 1 }) => (
<LineDrawingWrapper viewBox="0 -70 210 210" {...{ stroke }}>
<LineDrawingWrapper viewBox="0 -70 210 210" {...{ className, stroke }}>
<Front stroke={strokeScale * stroke} />
<Back stroke={strokeScale * stroke} />
</LineDrawingWrapper>

View file

@ -1,5 +1,5 @@
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
@ -56,7 +56,7 @@ export const JaneBack = ({ className, stroke = 1 }) => (
/*
* SVG elements for the front
*/
const Front = ({ stroke }) => (
const Front = () => (
<>
<path
key="outline"
@ -69,7 +69,7 @@ const Front = ({ stroke }) => (
/*
* SVG elements for the back
*/
const Back = ({ stroke }) => (
const Back = () => (
<>
<path
key="outline"

View file

@ -52,22 +52,20 @@ const BaseLink = Link
* @param {object} props - All component props
* @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.target = undefined] - An optional link title
* @param {string} [props.title = false] - An optional link title
* @param {React.FC} [Link = undefined] - An optional framework-specific Link component
* @returns {JSX.Element}
*/
export const SuccessLink = ({
children,
href,
target,
title = false,
Link,
}) => (
<a href={href} className={linkClasses} title={title ? title : ''}>
<span className="tw:text-success-content tw:hover:text-success-content">{children}</span>
</a>
)
export const SuccessLink = ({ children, href, title = false, Link }) =>
Link ? (
<Link href={href} className={linkClasses} title={title ? title : ''}>
<span className="tw:text-success-content tw:hover:text-success-content">{children}</span>
</Link>
) : (
<a href={href} className={linkClasses} title={title ? title : ''}>
<span className="tw:text-success-content tw:hover:text-success-content">{children}</span>
</a>
)
/**
* 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
* @returns {JSX.Element}
*/
export const FreeSewingLogo = ({ className = 'tw:w-20 tw:h-20', stroke }) => {
const svgProps = {}
const strokes = { dark: '#000', light: 'var(--p)' }
return (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1 0 25 25" className={className}>
<defs>
<path id="react-logo" d={logoPath} />
</defs>
<use
xlinkHref="#react-logo"
fill="none"
strokeWidth="0.5"
style={{ stroke: stroke ? stroke : 'var(--color-base-100)'}}
/>
<use xlinkHref="#react-logo" fill="currentColor" stroke="none" />
</svg>
)
}
export const FreeSewingLogo = ({ className = 'tw:w-20 tw:h-20', stroke }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="1 0 25 25" className={className}>
<defs>
<path id="react-logo" d={logoPath} />
</defs>
<use
xlinkHref="#react-logo"
fill="none"
strokeWidth="0.5"
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
import React, { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { Link as WebLink } from '@freesewing/react/components/Link'
import { NoIcon, OkIcon, SaveIcon, RightIcon, WarningIcon } from '@freesewing/react/components/Icon'
import { OkIcon, WarningIcon } from '@freesewing/react/components/Icon'
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'
/**
@ -32,7 +29,6 @@ export const NewsletterSignup = ({ Link = false, noP = false, noTitle = false, n
if (!Link) Link = WebLink
// Hooks
const { account, setAccount } = useAccount()
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
@ -44,7 +40,7 @@ export const NewsletterSignup = ({ Link = false, noP = false, noTitle = false, n
// Helper method to handle subscription
const subscribe = async () => {
setLoadingStatus([true, 'Contacting backend'])
const [status, body] = unsubscribe
const [status] = unsubscribe
? await backend.newsletterStartUnsubscribe(email)
: await backend.newsletterSubscribe(email)
if (status === 200) {
@ -146,7 +142,7 @@ export const NewsletterUnsubscribe = ({ Link = false }) => {
// Helper method to handle subscription
const unsubscribe = async () => {
const [status, body] = await backend.newsletterUnsubscribe(ehash)
const [status] = await backend.newsletterUnsubscribe(ehash)
if (status === 204 || status === 404) setGone(true)
else setError(true)
}
@ -197,6 +193,4 @@ export const NewsletterUnsubscribe = ({ Link = false }) => {
</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
*

View file

@ -1,8 +1,7 @@
// Dependencies
import { linkClasses } from '@freesewing/utils'
// Hooks
import React, { useEffect, useState } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import React, { useState } from 'react'
/**
* A component to ask people to support FreeSewing financially
@ -211,7 +210,12 @@ export const Subscribe = ({
</button>
<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?
<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>
</a>
</p>

View file

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

View file

@ -1,5 +1,3 @@
// Dependencies
import { cloudflareImageUrl } from '@freesewing/utils'
// Components
import React, { forwardRef } from 'react'
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 { Circle as DefaultCircle } from './circle.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
@ -101,6 +98,7 @@ const Pattern = forwardRef((props, ref) => {
</Svg>
)
})
Pattern.displayName = 'Pattern'
export {
// utils

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,3 @@
// eslint-disable-next-line no-unused-vars
import React from 'react'
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
*

View file

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

View file

@ -1,5 +1,5 @@
// Dependencies
import { linkClasses, cloudflareImageUrl, getSearchParam } from '@freesewing/utils'
import { cloudflareImageUrl, getSearchParam } from '@freesewing/utils'
// Context
import { ModalContext } from '@freesewing/react/context/Modal'
// Hooks
@ -9,7 +9,6 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
// Components
import { ModalWrapper } from '@freesewing/react/components/Modal'
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 { KeyVal } from '@freesewing/react/components/KeyVal'
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
* @returns {JSX.Element}
*/
export const UserProfile = ({
Link = false,
setTitle = false,
uid = false,
fromUrl = false,
}) => {
export const UserProfile = ({ Link = false, setTitle = false, uid = false, fromUrl = false }) => {
if (!uid && !fromUrl)
return (
<MiniWarning>
@ -69,7 +63,7 @@ export const UserProfile = ({
const urlId = getSearchParam(fromUrl)
if (urlId && urlId !== ruid) setRuid(urlId)
}
if (ruid) loadProfileData(ruid, backend, setData)
if (ruid) loadProfileData(ruid, backend, setData, setTitle)
}, [uid, fromUrl, ruid])
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)
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 { Spinner } from '@freesewing/react/components/Spinner'
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'
const Wrap = ({ children }) => (
@ -104,7 +104,7 @@ const AccountStatusUnknown = ({ banner }) => (
</Wrap>
)
const RoleLacking = ({ t, requiredRole, role, banner }) => (
const RoleLacking = ({ requiredRole, role, banner }) => (
<Wrap>
{banner}
<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
* @returns {JSX.Element}
*/
export const RoleBlock = ({ children, role = "admin", Link = false }) => {
export const RoleBlock = ({ children, role = 'admin', Link = false }) => {
if (!Link) Link = DefaultLink
const requiredRole = role
@ -275,7 +275,6 @@ export const UserVisitorContent = ({ userContent = null, visitorContent = null }
const [ready, setReady] = useState(false)
const [error, setError] = useState(false)
const [refreshCount, setRefreshCount] = useState(0)
/*
* Avoid hydration errors
@ -300,14 +299,15 @@ export const UserVisitorContent = ({ userContent = null, visitorContent = null }
if (!account.bestBefore || account.bestBefore < Date.now()) verifyUser()
}
setReady(true)
}, [refreshCount])
const refresh = () => {
setRefreshCount(refreshCount + 1)
setError(false)
}
}, [])
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
}

View file

@ -1,11 +1,5 @@
// Utils
import {
linkClasses,
horFlexClasses,
horFlexClassesNoSm,
capitalize,
getSearchParam,
} from '@freesewing/utils'
import { horFlexClasses, horFlexClassesNoSm, getSearchParam, navigate } from '@freesewing/utils'
// Context
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
@ -21,13 +15,11 @@ import {
KeyIcon,
LockIcon,
WarningIcon,
GoogleIcon,
GitHubIcon,
FreeSewingIcon,
UserIcon,
} from '@freesewing/react/components/Icon'
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}
*/
export const SignIn = ({ onSuccess = false, silent = false }) => {
const { account, setAccount, setToken, seenUser, setSeenUser } = useAccount()
const { setAccount, setToken, seenUser, setSeenUser } = useAccount()
const backend = useBackend()
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 ${
signInFailed ? 'tw:daisy-btn-warning' : 'tw:daisy-btn-primary'
} tw:transition-colors tw:ease-in-out tw:duration-300 ${horFlexClassesNoSm}`
@ -412,7 +395,12 @@ export const SignInConfirmation = ({ onSuccess = false }) => {
}
// 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)
return error === 'signInFailed' ? (
<>

View file

@ -3,7 +3,6 @@ import { validateEmail, validateTld, getSearchParam } from '@freesewing/utils'
// Hooks
import React, { useState, useContext, useEffect } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'
// Context
@ -12,16 +11,7 @@ import { ModalContext } from '@freesewing/react/context/Modal'
// Components
import { Link } from '@freesewing/react/components/Link'
import {
LeftIcon,
RightIcon,
HelpIcon,
GoogleIcon,
GitHubIcon,
KeyIcon,
EmailIcon,
DownIcon,
} from '@freesewing/react/components/Icon'
import { LeftIcon, HelpIcon, KeyIcon, EmailIcon } from '@freesewing/react/components/Icon'
import { ModalWrapper } from '@freesewing/react/components/Modal'
import { EmailInput } from '@freesewing/react/components/Input'
import { IconButton } from '@freesewing/react/components/Button'
@ -42,7 +32,6 @@ export const SignUp = ({ embed = false }) => {
const [email, setEmail] = useState('')
const [emailValid, setEmailValid] = useState(false)
const [result, setResult] = useState(false)
const [showAll, setShowAll] = useState(false)
// Hooks
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
? ({ children }) => <h2 className="tw:text-inherit">{children}</h2>
: ({ children }) => <h1 className="tw:text-inherit">{children}</h1>
@ -200,10 +181,9 @@ export const SignUp = ({ embed = false }) => {
*
* @component
* @param {object} props - All component props
* @param {function} [props.onSuccess = false] - A method to run when the sign in is successful
* @returns {JSX.Element}
*/
export const SignUpConfirmation = ({ onSuccess = false }) => {
export const SignUpConfirmation = () => {
// State
const [id, setId] = useState()
const [error, setError] = useState(false)
@ -219,7 +199,12 @@ export const SignUpConfirmation = ({ onSuccess = false }) => {
}, [id, check])
// 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 (!id || !check)
return (

View file

@ -7,11 +7,7 @@ import { useBackend } from '@freesewing/react/hooks/useBackend'
import { Spinner } from '@freesewing/react/components/Spinner'
import { Link as WebLink } from '@freesewing/react/components/Link'
import { ChartWrapper } from '@freesewing/react/components/Echart'
const meta = {
title: 'FreeSewing by numbers',
description: 'Some high-level numbers about Freesewing',
}
import { Popout } from '@freesewing/react/components/Popout'
const option = {
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 (
<>
<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 (
<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) => {
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' : ''

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React from 'react'
import { shortUuid } from '@freesewing/utils'
import { Link as WebLink } from '@freesewing/react/components/Link'
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
* @returns {JSX.Element}
*/
export const Uuid = ({ uuid, href = false, label = "UUID", Link = false }) => {
const [full, setFull] = useState()
const short = shortUuid(uuid)
export const Uuid = ({ uuid, href = false, label = 'UUID', Link = false }) => {
if (!Link) Link = WebLink
if (href === false)

View file

@ -81,3 +81,5 @@ export const Xray = forwardRef((props, ref) => {
</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 log = (val) => console.log(val)
const rounder = rounded ? round : (val) => val
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 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
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
// Hooks
import React, { useState, useContext } from 'react'
import { useState, useContext } from 'react'
import { useAccount } from '@freesewing/react/hooks/useAccount'
import { useBackend } from '@freesewing/react/hooks/useBackend'

View file

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

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react'
import { useState } from 'react'
export const useSelection = (items) => {
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 unset from 'lodash/unset.js'

View file

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