// __SDEFILE__ - This file is a dependency for the stand-alone environment
// Dependencies
import { useState, useEffect, useContext } from 'react'
import { useTranslation } from 'next-i18next'
import { freeSewingConfig as conf, controlLevels } from 'shared/config/freesewing.config.mjs'
import { siteConfig } from 'site/site.config.mjs'
import {
horFlexClasses,
objUpdate,
shortDate,
cloudflareImageUrl,
capitalize,
notEmpty,
} from 'shared/utils.mjs'
import { measurements } from 'config/measurements.mjs'
import { measurements as designMeasurements } from 'shared/prebuild/data/design-measurements.mjs'
import orderBy from 'lodash.orderby'
// Hooks
import { useAccount } from 'shared/hooks/use-account.mjs'
import { useBackend } from 'shared/hooks/use-backend.mjs'
// Context
import { ModalContext } from 'shared/context/modal-context.mjs'
import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs'
// Components
import { DisplayRow } from './account/shared.mjs'
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { Mdx } from 'shared/components/mdx/dynamic.mjs'
import Timeago from 'react-timeago'
import { MeasieVal } from './account/sets.mjs'
import {
TrashIcon,
CameraIcon,
UploadIcon,
OkIcon,
NoIcon,
BoolNoIcon,
BoolYesIcon,
} from 'shared/components/icons.mjs'
import { Link, PageLink } from 'shared/components/link.mjs'
import {
StringInput,
PassiveImageInput,
MarkdownInput,
MeasieInput,
DesignDropdown,
ListInput,
NumberInput,
ns as inputNs,
} from 'shared/components/inputs.mjs'
export const ns = ['account', 'patterns', 'status', 'measurements', 'sets', inputNs]
const SetLineup = ({ sets = [], href = false, onClick = false }) => (
1 ? 'justify-start px-8' : 'justify-center'
} overflow-x-scroll`}
style={{
backgroundImage: `url(/img/lineup-backdrop.svg)`,
width: 'auto',
backgroundSize: 'auto 100%',
backgroundRepeat: 'repeat-x',
}}
>
{sets.map((set) => {
const props = {
className: 'aspect-[1/3] w-auto h-96',
style: {
backgroundImage: `url(${cloudflareImageUrl({ id: `cset-${set.id}`, type: 'lineup' })})`,
width: 'auto',
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
},
}
if (onClick) props.onClick = () => onClick(set)
else if (typeof href === 'function') props.href = href(set.id)
if (onClick) return
else if (href) return
else return
})}
)
const ShowCuratedSet = ({ cset }) => {
const { control } = useAccount()
const { t, i18n } = useTranslation(ns)
const lang = i18n.language
const { setModal } = useContext(ModalContext)
if (!cset) return null
return (
<>
{cset[`name${capitalize(lang)}`]}
{control > 3 && (
<>
JSON
YAML
>
)}
setModal(
)
}
className={`btn btn-secondary btn-outline flex flex-row items-center justify-between w-48 grow-0`}
>
{t('showImage')}
{t('data')}
{cset.height}cm
{control >= controlLevels.sets.notes && (
)}
{control >= controlLevels.sets.createdAt && (
|
{shortDate(i18n.language, cset.createdAt, false)}
)}
{control >= controlLevels.sets.updatedAt && (
|
{shortDate(i18n.language, cset.updatedAt, false)}
)}
{control >= controlLevels.sets.id && {cset.id} }
{t('measies')}
{Object.entries(cset.measies).map(([m, val]) =>
val > 0 ? (
} key={m}>
{t(m)}
) : null
)}
>
)
}
export const CuratedSet = ({ id }) => {
// Hooks
const { setLoadingStatus } = useContext(LoadingStatusContext)
const backend = useBackend()
// State
const [cset, setCset] = useState()
// Effect
useEffect(() => {
const getCset = async () => {
setLoadingStatus([true, 'status:contactingBackend'])
const result = await backend.getCuratedSet(id)
if (result.success) {
setCset(result.data.curatedSet)
setLoadingStatus([true, 'status:dataLoaded', true, true])
} else setLoadingStatus([true, 'status:backendError', true, false])
}
if (id) getCset()
}, [id])
if (!id || !cset) return null
return (
)
}
// Picker version
export const CuratedSetPicker = (props) =>
// Component for the curated-sets page
export const CuratedSets = ({ href = false, clickHandler = false, published = true }) => {
// Hooks
const backend = useBackend()
const { setLoadingStatus } = useContext(LoadingStatusContext)
// State
const [sets, setSets] = useState([])
const [selected, setSelected] = useState(false)
// Effects
useEffect(() => {
const getSets = async () => {
setLoadingStatus([true, 'contactingBackend'])
const result = await backend.getCuratedSets()
if (result.success) {
const allSets = {}
for (const set of result.data.curatedSets) {
if (!published || set.published) allSets[set.id] = set
}
setSets(allSets)
setLoadingStatus([true, 'status:dataLoaded', true, true])
} else setLoadingStatus([true, 'status:backendError', true, false])
}
getSets()
}, [])
const lineupProps = {
sets: orderBy(sets, 'height', 'asc'),
}
if (typeof href === 'function') lineupProps.href = href
else lineupProps.onClick = clickHandler ? clickHandler : (set) => setSelected(set.id)
return (
{selected && }
)
}
// Component for the maintaining the list of curated-sets
export const CuratedSetsList = ({ href = false }) => {
// Hooks
const { t } = useTranslation(ns)
const backend = useBackend()
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
const [refresh, setRefresh] = useState(0)
// State
const [sets, setSets] = useState([])
const [selected, setSelected] = useState(false)
// Effects
useEffect(() => {
const getSets = async () => {
setLoadingStatus([true, 'contactingBackend'])
const result = await backend.getCuratedSets()
if (result.success) {
const allSets = []
for (const set of result.data.curatedSets) allSets.push(set)
setSets(allSets)
setLoadingStatus([true, 'status:dataLoaded', true, true])
} else setLoadingStatus([true, 'status:backendError', true, false])
}
getSets()
}, [refresh])
// Helper var to see how many are selected
const selCount = Object.keys(selected).length
// 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 === selected.length) setSelected({})
else {
const newSelected = {}
for (const set of selected) newSelected[set.id] = 1
setSelected(newSelected)
}
}
// Helper to delete one or more sets
const removeSelected = async () => {
let i = 0
for (const key in selected) {
i++
await backend.removeCuratedMeasurementsSet(key)
setLoadingStatus([
true,
,
])
}
setSelected({})
setRefresh(refresh + 1)
setLoadingStatus([true, 'nailedIt', true, true])
}
const lineupProps = {
sets: Object.values(sets),
}
if (typeof href === 'function') lineupProps.href = href
else lineupProps.onClick = setSelected
return (
{selCount ? (
{selCount} {t('curate:sets')}
) : null}
)
}
export const EditCuratedSet = ({ id }) => {
// Hooks
const { account } = useAccount()
const { setLoadingStatus } = useContext(LoadingStatusContext)
const backend = useBackend()
const { t } = useTranslation(ns)
const [filter, setFilter] = useState(false)
const [cset, setCset] = useState()
const [data, setData] = useState({})
// Effect
useEffect(() => {
const getCuratedSet = async () => {
setLoadingStatus([true, t('backendLoadingStarted')])
const result = await backend.getCuratedSet(id)
if (result.success) {
setCset(result.data.curatedSet)
const initData = {
img: result.data.curatedSet.img,
published: result.data.curatedSet.published,
measies: { ...result.data.curatedSet.measies },
}
for (const lang of siteConfig.languages) {
let k = `name${capitalize(lang)}`
initData[k] = result.data.curatedSet[k]
k = `notes${capitalize(lang)}`
initData[k] = result.data.curatedSet[k]
}
setData(initData)
setLoadingStatus([true, 'backendLoadingCompleted', true, true])
} else setLoadingStatus([true, 'backendError', true, false])
}
if (id) getCuratedSet()
}, [id])
const filterMeasurements = () => {
if (!filter) return measurements.map((m) => t(`measurements:${m}`) + `|${m}`).sort()
else return designMeasurements[filter].map((m) => t(`measurements:${m}`) + `|${m}`).sort()
}
if (!id || !cset) return nope {id}
//null
const updateData = (path, val) => setData(objUpdate({ ...data }, path, val))
const save = async () => {
setLoadingStatus([true, 'savingSet'])
const changes = { measies: {} }
for (const lang of siteConfig.languages) {
let k = `name${capitalize(lang)}`
if (data[k] !== cset[k]) changes[k] = data[k]
k = `notes${capitalize(lang)}`
if (data[k] !== cset[k]) changes[k] = data[k]
}
if (data.height !== cset.height) changes.height = Number(data.height)
if (data.img !== cset.img) changes.img = data.img
if (data.published !== cset.published) changes.published = data.published
for (const m in data.measies) {
if (data.measies[m] !== cset.measies[m]) changes.measies[m] = data.measies[m]
}
const result = await backend.updateCuratedSet(cset.id, changes)
if (result.success) {
setCset(result.data.curatedSet)
setLoadingStatus([true, 'nailedIt', true, true])
} else setLoadingStatus([true, 'backendError', true, false])
}
return (
updateData('published', val)}
list={[
{
val: true,
label: (
{t('curate:published')}
),
desc: t('curate:publishedDesc'),
},
{
val: false,
label: (
{t('curate:unpublished')}
),
desc: t('curate:unpublishedDesc'),
},
]}
current={data.published}
/>
updateData('height', val)}
current={Number(data.height)}
valid={notEmpty}
/>
{t('measies')}
{t('noFilter')}}
/>
{filterMeasurements().map((mplus) => {
const [translated, m] = mplus.split('|')
return (
updateData(['measies', m], val)}
/>
)
})}
{t('account:data')}
{t('account:name')}
{/* Name is always shown */}
{siteConfig.languages.map((lang) => {
const key = `name${capitalize(lang)}`
return (
updateData(key, val)}
current={data[key]}
valid={notEmpty}
/>
)
})}
{t('account:notes')}
{/* notes: Control level determines whether or not to show this */}
{siteConfig.languages.map((lang) => {
const key = `notes${capitalize(lang)}`
return (
updateData(key, val)}
current={data[key]}
placeholder={t('mdSupport')}
/>
)
})}
{/* img: Control level determines whether or not to show this */}
updateData('img', val)}
current={data.img}
valid={notEmpty}
/>
{t('saveThing', { thing: t('curate:curatedSet') })}
)
}