// Dependencies import { useState, useEffect, useContext } from 'react' import { useTranslation } from 'next-i18next' import { capitalize, shortDate, cloudflareImageUrl, horFlexClasses } from 'shared/utils.mjs' 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 { StringInput, MarkdownInput, PassiveImageInput, ListInput, } from 'shared/components/inputs.mjs' import { OkIcon, NoIcon, TrashIcon, PlusIcon, CameraIcon, EditIcon, ResetIcon, UploadIcon, FreeSewingIcon, CloneIcon, BoolYesIcon, BoolNoIcon, } from 'shared/components/icons.mjs' import { DisplayRow } from './shared.mjs' import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' import Markdown from 'react-markdown' import Timeago from 'react-timeago' import { TableWrapper } from 'shared/components/wrappers/table.mjs' import { DynamicOrgDocs } from 'site/components/dynamic-org-docs.mjs' export const ns = ['account', 'patterns', 'status'] export const Pattern = ({ id, publicOnly = false }) => { // Hooks const { account, control } = useAccount() const { setLoadingStatus } = useContext(LoadingStatusContext) const backend = useBackend() const { t, i18n } = useTranslation(ns) // FIXME: implement a solution for loading docs dynamically const docs = {} for (const option of ['name', 'units', 'public', 'notes', 'image']) { docs[option] = } // 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]) } const getPublicPattern = async () => { setLoadingStatus([true, t('backendLoadingStarted')]) const result = await backend.getPublicPattern(id) if (result.success) { setPattern({ ...result.data, public: true, }) setName(result.data.name) setImage(result.data.image) setIsPublic(true) setNotes(result.data.notes) setLoadingStatus([true, 'backendLoadingCompleted', true, true]) } else setLoadingStatus([true, 'backendError', true, false]) } if (id) { if (publicOnly) getPublicPattern() else getPattern() } console.log(' in useeffect') }, [id, publicOnly]) 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 ([true, false].includes(isPublic) && isPublic !== pattern.public) data.public = isPublic if (notes || notes !== pattern.notes) data.notes = notes 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 && publicOnly && ( {t('updatePattern')} )} {account.username && ( {t('clonePattern')} )} {!publicOnly && ( <> {edit ? ( <> ) : ( <> {t('updatePattern')} )} )}
) if (!edit) return (
{heading} {pattern.name} {control >= controlLevels.sets.notes && ( {pattern.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} )}
) return (
{heading} {/* Name is always shown */} val && val.length > 0} /> {/* img: Control level determines whether or not to show this */} {account.control >= conf.account.sets.img ? ( val.length > 0} /> ) : 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} /> ) : 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 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) // 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]) } return (

{t('patternNew')}

{selCount ? ( ) : null} {patterns.map((pattern, i) => ( ))}
{t('account:img')} {t('account:name')} {t('account:design')} {t('account:createdAt')} {t('account:public')}
toggleSelect(pattern.id)} /> {shortDate(locale, pattern.createdAt, false)} {pattern.public ? : }
) }