// Dependencies import { useState, useEffect, useContext } from 'react' import { useTranslation } from 'next-i18next' import { capitalize, shortDate, cloudflareImageUrl, horFlexClasses, newPatternUrl, } from 'shared/utils.mjs' import orderBy from 'lodash.orderby' import { freeSewingConfig as conf, controlLevels } from 'shared/config/freesewing.config.mjs' // Context import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' // Hooks import { useAccount } from 'shared/hooks/use-account.mjs' import { useBackend } from 'shared/hooks/use-backend.mjs' import { useRouter } from 'next/router' // Context import { ModalContext } from 'shared/context/modal-context.mjs' // Components import { PageLink, Link, AnchorLink } from 'shared/components/link.mjs' import { BackToAccountButton } from './shared.mjs' import { Popout } from 'shared/components/popout/index.mjs' import { StringInput, MarkdownInput, PassiveImageInput, ListInput, } from 'shared/components/inputs.mjs' import { OkIcon, NoIcon, TrashIcon, PlusIcon, CameraIcon, EditIcon, ResetIcon, RightIcon, UploadIcon, FreeSewingIcon, CloneIcon, BoolYesIcon, BoolNoIcon, LockIcon, PatternIcon, BookmarkIcon, } from 'shared/components/icons.mjs' import { DisplayRow } from './shared.mjs' import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' import { Mdx } from 'shared/components/mdx/dynamic.mjs' import Timeago from 'react-timeago' import { TableWrapper } from 'shared/components/wrappers/table.mjs' import { PatternReactPreview } from 'shared/components/pattern/preview.mjs' import { Lightbox } from 'shared/components/lightbox.mjs' import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' export const ns = ['account', 'patterns', 'status'] export const ShowPattern = ({ id }) => { // Hooks const { setLoadingStatus } = useContext(LoadingStatusContext) const backend = useBackend() const { t, i18n } = useTranslation(ns) const { account } = useAccount() // State const [pattern, setPattern] = useState() const [isOwn, setIsOwn] = useState(false) // Effect useEffect(() => { const getPattern = async () => { setLoadingStatus([true, t('backendLoadingStarted')]) let result try { result = await backend.getPattern(id) if (result.success) { setPattern(result.data.pattern) if (result.data.pattern.userId === account.userId) setIsOwn(true) setLoadingStatus([true, 'backendLoadingCompleted', true, true]) } else { result = await backend.getPublicPattern(id) if (result.success) { setPattern({ ...result.data, public: true }) setLoadingStatus([true, 'backendLoadingCompleted', true, true]) } else setLoadingStatus([true, 'backendError', true, false]) } } catch (err) { console.log(err) setLoadingStatus([true, 'backendError', true, false]) } } if (id) getPattern() }, [id]) const bookmarkPattern = async () => { setLoadingStatus([true, 'creatingBookmark']) const result = await backend.createBookmark({ type: 'pattern', title: pattern.name, url: `/patterns?id=${pattern.id}`, }) if (result.success) { const id = result.data.bookmark.id setLoadingStatus([ true, <> {t('status:bookmarkCreated')} [#{id}] , true, true, ]) } else setLoadingStatus([true, 'backendError', true, false]) } if (!pattern) return

loading

return ( <>
{pattern.name} {pattern.id} | {shortDate(i18n.language, pattern.createdAt, false)} | {shortDate(i18n.language, pattern.updatedAt, false)} {pattern.public ? : } {account.id ? ( ) : null} {t('clonePattern')} {isOwn ? ( <>

{t('account:ownPublicPattern')}

{t('account:privateView')}
) : null}

{t('account:notes')}

{isOwn ? 'is own' : 'is not own'} ) } export const Pattern = ({ id }) => { // Hooks const { account, control } = useAccount() const { setLoadingStatus } = useContext(LoadingStatusContext) const backend = useBackend() const { t, i18n } = useTranslation(ns) // Context const { setModal } = useContext(ModalContext) const [edit, setEdit] = useState(false) const [pattern, setPattern] = useState() // Set fields for editing const [name, setName] = useState(pattern?.name) const [image, setImage] = useState(pattern?.image) const [isPublic, setIsPublic] = useState(pattern?.public ? true : false) const [notes, setNotes] = useState(pattern?.notes || '') // Effect useEffect(() => { const getPattern = async () => { setLoadingStatus([true, t('backendLoadingStarted')]) const result = await backend.getPattern(id) if (result.success) { setPattern(result.data.pattern) setName(result.data.pattern.name) setImage(result.data.pattern.image) setIsPublic(result.data.pattern.public ? true : false) setNotes(result.data.pattern.notes) setLoadingStatus([true, 'backendLoadingCompleted', true, true]) } else setLoadingStatus([true, 'backendError', true, false]) } if (id) getPattern() }, [id]) const save = async () => { setLoadingStatus([true, 'gatheringInfo']) // Compile data const data = {} if (name || name !== pattern.name) data.name = name if (image || image !== pattern.image) data.img = image if (notes || notes !== pattern.notes) data.notes = notes if ([true, false].includes(isPublic) && isPublic !== pattern.public) data.public = isPublic setLoadingStatus([true, 'savingPattern']) const result = await backend.updatePattern(pattern.id, data) if (result.success) { setPattern(result.data.pattern) setEdit(false) setLoadingStatus([true, 'nailedIt', true, true]) } else setLoadingStatus([true, 'backendError', true, false]) } if (!pattern) return null const heading = ( <>
{account.control > 3 && pattern?.public ? (
JSON YAML
) : ( )} {pattern.userId === account.id && ( <> {edit ? ( <> ) : ( <> {t('updatePattern')} {t('clonePattern')} )} )}
) if (!edit) return (
{heading} {pattern.name} {control >= controlLevels.sets.notes && ( )} {control >= controlLevels.patterns.public && ( <> {pattern.public ? : } {pattern.public && ( )} )} {control >= controlLevels.sets.createdAt && ( | {shortDate(i18n.language, pattern.createdAt, false)} )} {control >= controlLevels.patterns.updatedAt && ( | {shortDate(i18n.language, pattern.updatedAt, false)} )} {control >= controlLevels.patterns.id && ( {pattern.id} )}

{t('account:ownPrivatePattern')}

{t('account:publicView')}
) return (
{heading} {/* Name is always shown */} val && val.length > 0} docs={} /> {/* img: Control level determines whether or not to show this */} {account.control >= conf.account.sets.img ? ( val.length > 0} docs={} /> ) : null} {/* public: Control level determines whether or not to show this */} {account.control >= conf.account.patterns.public ? ( {t('publicPattern')}
), desc: t('publicPatternDesc'), }, { val: false, label: (
{t('privatePattern')}
), desc: t('privatePatternDesc'), }, ]} current={isPublic} docs={} /> ) : null} {/* notes: Control level determines whether or not to show this */} {account.control >= conf.account.patterns.notes ? ( } /> ) : null} ) } export const PatternCard = ({ pattern, href = false, onClick = false, useA = false, size = 'md', }) => { const sizes = { lg: 96, md: 52, sm: 36, xs: 20, } const s = sizes[size] const wrapperProps = { className: `bg-base-300 w-full mb-2 mx-auto flex flex-col items-start text-center justify-center rounded shadow py-4 h-${s} w-${s}`, style: { backgroundImage: `url(${cloudflareImageUrl({ type: 'w1000', id: pattern.img })})`, backgroundSize: 'cover', backgroundRepeat: 'no-repeat', backgroundPosition: '50%', }, } if (pattern.img === 'default-avatar') wrapperProps.style.backgroundPosition = 'bottom right' const inner = null // Is it a button with an onClick handler? if (onClick) return ( ) // Returns a link to an internal page if (href && !useA) return ( {inner} ) // Returns a link to an external page if (href && useA) return ( {inner} ) // Returns a div return
{inner}
} // Component to show the sort header in the pattern table const SortButton = ({ field, label, order, orderAsc, updateOrder }) => ( ) // Component for the account/patterns page export const Patterns = () => { const router = useRouter() const { locale } = router // Hooks const backend = useBackend() const { t } = useTranslation(ns) const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext) // State const [patterns, setPatterns] = useState([]) const [selected, setSelected] = useState({}) const [refresh, setRefresh] = useState(0) const [order, setOrder] = useState('id') const [orderAsc, setOrderAsc] = useState(true) // Helper var to see how many are selected const selCount = Object.keys(selected).length // Effects useEffect(() => { const getPatterns = async () => { const result = await backend.getPatterns() if (result.success) setPatterns(result.data.patterns) } getPatterns() }, [refresh]) // Helper method to toggle single selection const toggleSelect = (id) => { const newSelected = { ...selected } if (newSelected[id]) delete newSelected[id] else newSelected[id] = 1 setSelected(newSelected) } // Helper method to toggle select all const toggleSelectAll = () => { if (selCount === patterns.length) setSelected({}) else { const newSelected = {} for (const pattern of patterns) newSelected[pattern.id] = 1 setSelected(newSelected) } } // Helper to delete one or more patterns const removeSelectedPatterns = async () => { let i = 0 for (const pattern in selected) { i++ await backend.removePattern(pattern) setLoadingStatus([ true, , ]) } setSelected({}) setRefresh(refresh + 1) setLoadingStatus([true, 'nailedIt', true, true]) } // Helper method to update the order state const updateOrder = (field) => { if (order !== field) { setOrder(field) setOrderAsc(true) } else setOrderAsc(!orderAsc) } return (

{t('patternNew')}

{orderBy(patterns, order, orderAsc ? 'asc' : 'desc').map((pattern, i) => ( ))}
{t('account:img')}
toggleSelect(pattern.id)} /> {pattern.id} {shortDate(locale, pattern.createdAt, false)} {pattern.public ? : }
) }