feat: Wrapped up work on measurements sets
This commit is contained in:
parent
770b608090
commit
dbe1a04552
23 changed files with 1292 additions and 359 deletions
|
@ -1,11 +1,19 @@
|
|||
// Dependencies
|
||||
import { measurements, isDegreeMeasurement, control as controlConfig } from '@freesewing/config'
|
||||
import {
|
||||
measurements,
|
||||
isDegreeMeasurement,
|
||||
control as controlConfig,
|
||||
urls,
|
||||
} from '@freesewing/config'
|
||||
import { measurements as measurementTranslations } from '@freesewing/i18n'
|
||||
import { measurements as designMeasurements } from '@freesewing/collection'
|
||||
import {
|
||||
cloudflareImageUrl,
|
||||
capitalize,
|
||||
formatMm,
|
||||
horFlexClasses,
|
||||
linkClasses,
|
||||
notEmpty,
|
||||
roundDistance,
|
||||
shortDate,
|
||||
timeAgo,
|
||||
|
@ -18,8 +26,10 @@ import React, { useState, useEffect, Fragment, 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 { Link as WebLink, AnchorLink } from '@freesewing/react/components/Link'
|
||||
import {
|
||||
BoolNoIcon,
|
||||
BoolYesIcon,
|
||||
CloneIcon,
|
||||
CuratedMeasurementsSetIcon,
|
||||
EditIcon,
|
||||
|
@ -36,54 +46,26 @@ import {
|
|||
// BoolNoIcon,
|
||||
} from '@freesewing/react/components/Icon'
|
||||
import { BookmarkButton, MsetCard } from '@freesewing/react/components/Account'
|
||||
import { ToggleInput } from '@freesewing/react/components/Input'
|
||||
import {
|
||||
DesignInput,
|
||||
MarkdownInput,
|
||||
ListInput,
|
||||
MeasieInput,
|
||||
PassiveImageInput,
|
||||
StringInput,
|
||||
ToggleInput,
|
||||
} from '@freesewing/react/components/Input'
|
||||
import { DisplayRow } from './shared.mjs'
|
||||
import Markdown from 'react-markdown'
|
||||
import { ModalWrapper } from '@freesewing/react/components/Modal'
|
||||
import { Json } from '@freesewing/react/components/Json'
|
||||
import { Yaml } from '@freesewing/react/components/Yaml'
|
||||
import { Popout } from '@freesewing/react/components/Popout'
|
||||
|
||||
//import { measurements as designMeasurements } from 'shared/prebuild/data/design-measurements.mjs'
|
||||
//import { freeSewingConfig as conf, controlLevels } from 'shared/config/freesewing.config.mjs'
|
||||
//import { isDegreeMeasurement } from 'config/measurements.mjs'
|
||||
//import {
|
||||
// shortDate,
|
||||
// cloudflareImageUrl,
|
||||
// formatMm,
|
||||
// hasRequiredMeasurements,
|
||||
// capitalize,
|
||||
// horFlexClasses,
|
||||
//} from 'shared/utils.mjs'
|
||||
//// Hooks
|
||||
//import { useState, useEffect, useContext } from 'react'
|
||||
//import { useTranslation } from 'next-i18next'
|
||||
//import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
//import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
//import { useRouter } from 'next/router'
|
||||
//// Context
|
||||
//import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs'
|
||||
//import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||
//// Components
|
||||
//import { Popout } from 'shared/components/popout/index.mjs'
|
||||
//import { BackToAccountButton } from './shared.mjs'
|
||||
//import { AnchorLink, PageLink, Link } from 'shared/components/link.mjs'
|
||||
//import { Json } from 'shared/components/json.mjs'
|
||||
//import { Yaml } from 'shared/components/yaml.mjs'
|
||||
//import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
|
||||
//import { Mdx } from 'shared/components/mdx/dynamic.mjs'
|
||||
//import Timeago from 'react-timeago'
|
||||
//import {
|
||||
// StringInput,
|
||||
// ToggleInput,
|
||||
// PassiveImageInput,
|
||||
// ListInput,
|
||||
// MarkdownInput,
|
||||
// MeasieInput,
|
||||
// DesignDropdown,
|
||||
// ns as inputNs,
|
||||
//} from 'shared/components/inputs.mjs'
|
||||
//import { BookmarkButton } from 'shared/components/bookmarks.mjs'
|
||||
//import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs'
|
||||
const t = (input) => {
|
||||
console.log('t called', input)
|
||||
return input
|
||||
}
|
||||
|
||||
/*
|
||||
* Component to show an individual measurements set
|
||||
|
@ -163,10 +145,8 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
}
|
||||
}, [id, publicOnly])
|
||||
|
||||
const filterMeasurements = () => {
|
||||
if (!filter) return measurements.map((m) => `measurements:${m}` + `|${m}`).sort()
|
||||
else return designMeasurements[filter].map((m) => `measurements:${m}` + `|${m}`).sort()
|
||||
}
|
||||
const filterMeasurements = () =>
|
||||
filter ? designMeasurements[filter].sort() : measurements.sort()
|
||||
|
||||
if (!id || !mset) return null
|
||||
|
||||
|
@ -192,7 +172,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
setLoadingStatus([true, 'Saving measurements set'])
|
||||
const [status, body] = await backend.updateSet(mset.id, data)
|
||||
if (status === 200 && body.result === 'success') {
|
||||
setMset(body.data.set)
|
||||
setMset(body.set)
|
||||
setEdit(false)
|
||||
setLoadingStatus([true, 'Nailed it', true, true])
|
||||
} else
|
||||
|
@ -218,10 +198,9 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
}
|
||||
delete data.img
|
||||
const [status, body] = await backend.createSet(data)
|
||||
if (status === 200 && body.result === 'success') {
|
||||
setMset(body.data.set)
|
||||
setEdit(false)
|
||||
setLoadingStatus([true, 'Nailed it', true, true])
|
||||
if (status === 201 && body.result === 'created') {
|
||||
setLoadingStatus([true, 'Loading newly created set', true, true])
|
||||
window.location = `/account/set/?id=${body.set.id}`
|
||||
} else setLoadingStatus([true, 'We failed to create this measurements set', true, false])
|
||||
}
|
||||
|
||||
|
@ -235,14 +214,14 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
{account.control > 2 && mset.public && mset.userId !== account.id ? (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<a
|
||||
className="badge badge-secondary font-bold badge-lg"
|
||||
href={`${conf.backend}/sets/${mset.id}.json`}
|
||||
className="daisy-badge daisy-badge-secondary font-bold daisy-badge-lg"
|
||||
href={`${urls.backend}/sets/${mset.id}.json`}
|
||||
>
|
||||
JSON
|
||||
</a>
|
||||
<a
|
||||
className="badge badge-success font-bold badge-lg"
|
||||
href={`${conf.backend}/sets/${mset.id}.yaml`}
|
||||
className="daisy-badge daisy-badge-success font-bold daisy-badge-lg"
|
||||
href={`${urls.backend}/sets/${mset.id}.yaml`}
|
||||
>
|
||||
YAML
|
||||
</a>
|
||||
|
@ -253,7 +232,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
{account.control > 3 && mset.userId === account.id ? (
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<button
|
||||
className="badge badge-secondary font-bold badge-lg"
|
||||
className="daisy-badge daisy-badge-secondary font-bold daisy-badge-lg"
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<ModalWrapper keepOpenOnClick>
|
||||
|
@ -265,7 +244,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
JSON
|
||||
</button>
|
||||
<button
|
||||
className="badge badge-success font-bold badge-lg"
|
||||
className="daisy-badge daisy-badge-success font-bold daisy-badge-lg text-neutral-content"
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<ModalWrapper keepOpenOnClick>
|
||||
|
@ -283,12 +262,12 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
{account.id && account.control > 2 && mset.public && mset.userId !== account.id ? (
|
||||
<button
|
||||
className="daisy-btn daisy-btn-primary"
|
||||
title={t('account:importSet')}
|
||||
title="Import measurements set"
|
||||
onClick={importSet}
|
||||
>
|
||||
<div className="flex flex-row gap-4 justify-between items-center w-full">
|
||||
<UploadIcon />
|
||||
{t('account:importSet')}
|
||||
Import measurements set
|
||||
</div>
|
||||
</button>
|
||||
) : null}
|
||||
|
@ -379,7 +358,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
return (
|
||||
<div className="max-w-2xl">
|
||||
{heading}
|
||||
<SuggestCset {...{ mset, setLoadingStatus, backend, t }} />
|
||||
<SuggestCset {...{ mset, setLoadingStatus, backend, Link }} />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
@ -466,56 +445,27 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
return (
|
||||
<div className="max-w-2xl">
|
||||
{heading}
|
||||
<ul className="list list-disc list-inside ml-4">
|
||||
{['measies', 'data'].map((s) => (
|
||||
<li key={s}>
|
||||
<AnchorLink id={s}>{s}</AnchorLink>
|
||||
</li>
|
||||
))}
|
||||
<ul className="list list-disc list-inside ml-4">
|
||||
<li>
|
||||
<AnchorLink id="name">Name</AnchorLink>
|
||||
</li>
|
||||
{account.control >= conf.account.sets.img ? (
|
||||
<li>
|
||||
<AnchorLink id="image">Image</AnchorLink>
|
||||
</li>
|
||||
) : null}
|
||||
{['public', 'units', 'notes'].map((id) =>
|
||||
account.control >= conf.account.sets[id] ? (
|
||||
<li key={id}>
|
||||
<AnchorLink id="units">{id}</AnchorLink>
|
||||
</li>
|
||||
) : null
|
||||
)}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<h2 id="measies">{t('measies')}</h2>
|
||||
<h2 id="measies">Measurements</h2>
|
||||
<div className="bg-secondary px-4 pt-1 pb-4 rounded-lg shadow bg-opacity-10">
|
||||
<DesignDropdown
|
||||
<DesignInput
|
||||
update={setFilter}
|
||||
label="Filter by design"
|
||||
current={filter}
|
||||
firstOption={<option value="">Clear filter</option>}
|
||||
/>
|
||||
</div>
|
||||
{filterMeasurements().map((mplus) => {
|
||||
const [translated, m] = mplus.split('|')
|
||||
|
||||
return (
|
||||
<MeasieInput
|
||||
id={`measie-${m}`}
|
||||
key={m}
|
||||
m={m}
|
||||
imperial={mset.imperial}
|
||||
label={translated}
|
||||
current={mset.measies[m]}
|
||||
original={mset.measies[m]}
|
||||
update={updateMeasies}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{filterMeasurements().map((m) => (
|
||||
<MeasieInput
|
||||
id={`measie-${m}`}
|
||||
key={m}
|
||||
m={m}
|
||||
imperial={mset.imperial}
|
||||
label={measurementTranslations[m]}
|
||||
current={mset.measies[m]}
|
||||
original={mset.measies[m]}
|
||||
update={updateMeasies}
|
||||
/>
|
||||
))}
|
||||
|
||||
<h2 id="data">Data</h2>
|
||||
|
||||
|
@ -533,7 +483,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
|
||||
{/* img: Control level determines whether or not to show this */}
|
||||
<span id="image"></span>
|
||||
{account.control >= conf.account.sets.img ? (
|
||||
{account.control >= controlConfig.account.sets.img ? (
|
||||
<PassiveImageInput
|
||||
id="set-img"
|
||||
label="Image"
|
||||
|
@ -545,7 +495,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
|
||||
{/* public: Control level determines whether or not to show this */}
|
||||
<span id="public"></span>
|
||||
{account.control >= conf.account.sets.public ? (
|
||||
{account.control >= controlConfig.account.sets.public ? (
|
||||
<ListInput
|
||||
id="set-public"
|
||||
label="Public"
|
||||
|
@ -581,7 +531,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
|
||||
{/* units: Control level determines whether or not to show this */}
|
||||
<span id="units"></span>
|
||||
{account.control >= conf.account.sets.units ? (
|
||||
{account.control >= controlConfig.account.sets.units ? (
|
||||
<>
|
||||
<ListInput
|
||||
id="set-units"
|
||||
|
@ -611,13 +561,15 @@ export const Set = ({ id, publicOnly = false, Link = false }) => {
|
|||
]}
|
||||
current={imperial}
|
||||
/>
|
||||
<span className="text-large text-warning">{t('unitsMustSave')}</span>
|
||||
<span className="text-large text-warning">
|
||||
Note: You must save after changing Units to have the change take effect on this page.
|
||||
</span>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{/* notes: Control level determines whether or not to show this */}
|
||||
<span id="notes"></span>
|
||||
{account.control >= conf.account.sets.notes ? (
|
||||
{account.control >= controlConfig.account.sets.notes ? (
|
||||
<MarkdownInput
|
||||
id="set-notes"
|
||||
label="Notes"
|
||||
|
@ -652,14 +604,137 @@ export const MeasurementValue = ({ val, m, imperial = false }) =>
|
|||
<span dangerouslySetInnerHTML={{ __html: formatMm(val, imperial) }}></span>
|
||||
)
|
||||
|
||||
/*
|
||||
/**
|
||||
* React component to suggest a measurements set for curation
|
||||
*
|
||||
* @param {object} props - All React props
|
||||
* @param {string} mset - The measurements set
|
||||
*/
|
||||
export const SuggestCset = ({ mset, Link }) => {
|
||||
// State
|
||||
const [height, setHeight] = useState('')
|
||||
const [img, setImg] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const [notes, setNotes] = useState('')
|
||||
const [submission, setSubmission] = useState(false)
|
||||
|
||||
console.log(mset)
|
||||
|
||||
// Hooks
|
||||
const backend = useBackend()
|
||||
|
||||
// Method to submit the form
|
||||
const suggestSet = async () => {
|
||||
setLoadingStatus([true, 'Contacting backend'])
|
||||
const result = await backend.suggestCset({ set: mset.id, height, img, name, notes })
|
||||
if (result.success && result.data.submission) {
|
||||
setSubmission(result.data.submission)
|
||||
setLoadingStatus([true, 'Nailed it', true, true])
|
||||
} else setLoadingStatus([true, 'An unexpected error occured. Please report this.', true, false])
|
||||
}
|
||||
|
||||
const missing = []
|
||||
for (const m of measurements) {
|
||||
if (typeof mset.measies[m] === 'undefined') missing.push(m)
|
||||
}
|
||||
|
||||
if (submission) {
|
||||
const url = `/curate/sets/suggested/${submission.id}`
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Thank you</h2>
|
||||
<p>Your submission has been registered and will be processed by one of our curators.</p>
|
||||
<p>
|
||||
It is available at: <Link href={url}>{url}</Link>
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>Suggest a measurements set for curation</h2>
|
||||
<h4 className="flex flex-row items-center gap-2">
|
||||
{missing.length > 0 ? <BoolNoIcon /> : <BoolYesIcon />}
|
||||
Measurements
|
||||
</h4>
|
||||
{missing.length > 0 ? (
|
||||
<>
|
||||
<p>
|
||||
To ensure curated measurements sets work for all designs, you need to provide a full set
|
||||
of measurements.
|
||||
</p>
|
||||
<p>Your measurements set is missing the following measurements:</p>
|
||||
<ul className="list list-inside list-disc ml-4">
|
||||
{missing.map((m) => (
|
||||
<li key={m}>{m}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<p>All measurements are available.</p>
|
||||
)}
|
||||
<h4 className="flex flex-row items-center gap-2">
|
||||
{name.length > 1 ? <BoolYesIcon /> : <BoolNoIcon />}
|
||||
Name
|
||||
</h4>
|
||||
<p>Each curated set has a name. You can suggest your own name or a pseudonym.</p>
|
||||
<StringInput label="Name" current={name} update={setName} valid={(val) => val.length > 1} />
|
||||
<h4 className="flex flex-row items-center gap-2">
|
||||
{height.length > 1 ? <BoolYesIcon /> : <BoolNoIcon />}
|
||||
Height
|
||||
</h4>
|
||||
<p>
|
||||
To allow organizing and presenting our curated sets in a structured way, we organize them by
|
||||
height.
|
||||
</p>
|
||||
<StringInput
|
||||
label="height"
|
||||
current={height}
|
||||
update={setHeight}
|
||||
valid={(val) => val.length > 1}
|
||||
/>
|
||||
<h4 className="flex flex-row items-center gap-2 mt-4">
|
||||
{img.length > 0 ? <BoolYesIcon /> : <BoolNoIcon />}
|
||||
Image
|
||||
</h4>
|
||||
<p>
|
||||
Finally, we need a picture. Please refer to the documentation to see what makes a good
|
||||
picture for a curated measurements set.
|
||||
<Link href="/docs/about/site/csets">Documentation</Link>
|
||||
</p>
|
||||
<PassiveImageInput
|
||||
label="Image"
|
||||
current={img}
|
||||
update={setImg}
|
||||
valid={(val) => val.length > 1}
|
||||
/>
|
||||
<h4 className="flex flex-row items-center gap-2 mt-4">
|
||||
<BoolYesIcon />
|
||||
Notes
|
||||
</h4>
|
||||
<p>If you would like to add any notes, you can do so here.</p>
|
||||
<Popout tip compact>
|
||||
This field supports markdown
|
||||
</Popout>
|
||||
<MarkdownInput label="Notes" current={notes} update={setNotes} valid={() => true} />
|
||||
<button
|
||||
className="daisy-btn daisy-btn-primary w-full mt-4"
|
||||
disabled={!(missing.length === 0 && height.length > 1 && img.length > 0)}
|
||||
onClick={suggestSet}
|
||||
>
|
||||
Suggest for curation
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const NewSet = () => {
|
||||
// Hooks
|
||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||
const backend = useBackend()
|
||||
const { t } = useTranslation(ns)
|
||||
const router = useRouter()
|
||||
const { account } = useAccount()
|
||||
const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext)
|
||||
|
||||
// State
|
||||
const [name, setName] = useState('')
|
||||
|
@ -669,39 +744,46 @@ export const NewSet = () => {
|
|||
|
||||
// Helper method to create a new set
|
||||
const createSet = async () => {
|
||||
setLoadingStatus([true, 'processingUpdate'])
|
||||
const result = await backend.createSet({ name, imperial })
|
||||
if (result.success) {
|
||||
setLoadingStatus([true, t('nailedIt'), true, true])
|
||||
router.push(`/account/set?id=${result.data.set.id}`)
|
||||
} else setLoadingStatus([true, 'backendError', true, false])
|
||||
setLoadingStatus([true, 'Storing new measurements set'])
|
||||
const [status, body] = await backend.createSet({ name, imperial })
|
||||
if (status === 201 && body.result === 'created') {
|
||||
setLoadingStatus([true, 'Nailed it', true, true])
|
||||
window.location = `/account/set?id=${body.set.id}`
|
||||
} else
|
||||
setLoadingStatus([
|
||||
true,
|
||||
'Failed to save the measurments set. Please report this.',
|
||||
true,
|
||||
false,
|
||||
])
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-xl">
|
||||
<h5>{t('name')}</h5>
|
||||
<p>{t('setNameDesc')}</p>
|
||||
<input
|
||||
autoFocus
|
||||
value={name}
|
||||
onChange={(evt) => setName(evt.target.value)}
|
||||
className="input w-full input-bordered flex flex-row"
|
||||
type="text"
|
||||
<h5>Name</h5>
|
||||
<p>Give this set of measurements a name. That will help tell them apart.</p>
|
||||
<StringInput
|
||||
id="new-set"
|
||||
label="Name"
|
||||
update={setName}
|
||||
current={name}
|
||||
valid={(val) => val && val.length > 0}
|
||||
placeholder={'Georg Cantor'}
|
||||
/>
|
||||
<div className="flex flex-row gap-2 items-center w-full mt-8 mb-2">
|
||||
<button
|
||||
className="btn btn-primary grow capitalize"
|
||||
className="daisy-btn daisy-btn-primary grow capitalize"
|
||||
disabled={name.length < 1}
|
||||
onClick={createSet}
|
||||
>
|
||||
{t('newSet')}
|
||||
New Measurements Set
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
export const SetCard = ({
|
||||
set,
|
||||
|
@ -950,122 +1032,5 @@ export const BookmarkedSetPicker = ({ design, clickHandler, t, size, href }) =>
|
|||
)
|
||||
}
|
||||
|
||||
const SuggestCset = ({ mset, backend, setLoadingStatus, t }) => {
|
||||
// State
|
||||
const [height, setHeight] = useState('')
|
||||
const [img, setImg] = useState('')
|
||||
const [name, setName] = useState('')
|
||||
const [notes, setNotes] = useState('')
|
||||
const [submission, setSubmission] = useState(false)
|
||||
|
||||
// Method to submit the form
|
||||
const suggestSet = async () => {
|
||||
setLoadingStatus([true, 'status:contactingBackend'])
|
||||
const result = await backend.suggestCset({ set: mset.id, height, img, name, notes })
|
||||
if (result.success && result.data.submission) {
|
||||
setSubmission(result.data.submission)
|
||||
setLoadingStatus([true, 'status:nailedIt', true, true])
|
||||
} else setLoadingStatus([true, 'backendError', true, false])
|
||||
}
|
||||
|
||||
const missing = []
|
||||
for (const m of measurements) {
|
||||
if (typeof mset.measies[m] === 'undefined') missing.push(m)
|
||||
}
|
||||
|
||||
if (submission) {
|
||||
const url = `/curate/sets/suggested/${submission.id}`
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{t('account:thankYouVeryMuch')}</h2>
|
||||
<p>{t('account:csetSuggestedMsg')}</p>
|
||||
<p>
|
||||
{t('account:itIsAvailableAt')}: <PageLink href={url} txt={url} />
|
||||
</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>{t('account:suggestCset')}</h2>
|
||||
<h4 className="flex flex-row items-center gap-2">
|
||||
{missing.length > 0 ? <BoolNoIcon /> : <BoolYesIcon />}
|
||||
{t('account:measurements')}
|
||||
</h4>
|
||||
{missing.length > 0 ? (
|
||||
<>
|
||||
<p>{t('account:csetAllMeasies')}</p>
|
||||
<p>{t('account:csetMissing')}:</p>
|
||||
<ul className="list list-inside list-disc ml-4">
|
||||
{missing.map((m) => (
|
||||
<li key={m}>{t(`measurements:${m}`)}</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : (
|
||||
<p>{t('account:allMeasiesAvailable')}</p>
|
||||
)}
|
||||
<h4 className="flex flex-row items-center gap-2">
|
||||
{name.length > 1 ? <BoolYesIcon /> : <BoolNoIcon />}
|
||||
{t('account:name')}
|
||||
</h4>
|
||||
<p>{t('account:csetNameMsg')}</p>
|
||||
<StringInput
|
||||
label={t('account:name')}
|
||||
current={name}
|
||||
update={setName}
|
||||
valid={(val) => val.length > 1}
|
||||
/>
|
||||
<h4 className="flex flex-row items-center gap-2">
|
||||
{height.length > 1 ? <BoolYesIcon /> : <BoolNoIcon />}
|
||||
{t('measurements:height')}
|
||||
</h4>
|
||||
<p>{t('account:csetHeightMsg1')}</p>
|
||||
<StringInput
|
||||
label={t('measurements:height')}
|
||||
current={height}
|
||||
update={setHeight}
|
||||
valid={(val) => val.length > 1}
|
||||
/>
|
||||
<h4 className="flex flex-row items-center gap-2 mt-4">
|
||||
{img.length > 0 ? <BoolYesIcon /> : <BoolNoIcon />}
|
||||
{t('account:img')}
|
||||
</h4>
|
||||
<p>
|
||||
{t('account:csetImgMsg')}:{' '}
|
||||
<PageLink href="/docs/about/site/csets">{t('account:docs')}</PageLink>
|
||||
</p>
|
||||
<PassiveImageInput
|
||||
label={t('account:img')}
|
||||
current={img}
|
||||
update={setImg}
|
||||
valid={(val) => val.length > 1}
|
||||
/>
|
||||
<h4 className="flex flex-row items-center gap-2 mt-4">
|
||||
<BoolYesIcon />
|
||||
{t('account:notes')}
|
||||
</h4>
|
||||
<p>{t('account:csetNotesMsg')}</p>
|
||||
<Popout tip compact>
|
||||
{t('account:mdSupport')}
|
||||
</Popout>
|
||||
<MarkdownInput
|
||||
label={t('account:notes')}
|
||||
current={notes}
|
||||
update={setNotes}
|
||||
valid={() => true}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-primary w-full mt-4"
|
||||
disabled={!(missing.length === 0 && height.length > 1 && img.length > 0)}
|
||||
onClick={suggestSet}
|
||||
>
|
||||
{t('account:suggestForCuration')}
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
*/
|
||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react'
|
|||
|
||||
import { Bookmarks, BookmarkButton } from './Bookmarks.mjs'
|
||||
import { Links } from './Links.mjs'
|
||||
import { Set } from './Set.mjs'
|
||||
import { Set, NewSet } from './Set.mjs'
|
||||
import { Sets, MsetCard } from './Sets.mjs'
|
||||
|
||||
export { Bookmarks, BookmarkButton, Links, Set, Sets, MsetCard }
|
||||
export { Bookmarks, BookmarkButton, Links, Set, NewSet, Sets, MsetCard }
|
||||
|
|
|
@ -1,26 +1,34 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useContext, useState } from 'react'
|
||||
import ReactDOMServer from 'react-dom/server'
|
||||
import { CopyIcon, OkIcon } from '@freesewing/react/components/Icon'
|
||||
import { CopyToClipboard as Copy } from 'react-copy-to-clipboard'
|
||||
import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus'
|
||||
|
||||
const strip = (html) =>
|
||||
typeof DOMParser === 'undefined'
|
||||
? html
|
||||
: new DOMParser().parseFromString(html, 'text/html').body.textContent || ''
|
||||
|
||||
const handleCopied = (setCopied) => {
|
||||
const handleCopied = (setCopied, setLoadingStatus, label) => {
|
||||
setCopied(true)
|
||||
setLoadingStatus([
|
||||
true,
|
||||
label ? `${label} copied to clipboard` : 'Copied to clipboard',
|
||||
true,
|
||||
true,
|
||||
])
|
||||
setTimeout(() => setCopied(false), 1000)
|
||||
}
|
||||
|
||||
export const CopyToClipboard = ({ content }) => {
|
||||
export const CopyToClipboard = ({ content, label = false }) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const { setLoadingStatus } = useContext(LoadingStatusContext)
|
||||
|
||||
const text =
|
||||
typeof content === 'string' ? content : strip(ReactDOMServer.renderToStaticMarkup(content))
|
||||
|
||||
return (
|
||||
<Copy text={text} onCopy={() => handleCopied(setCopied)}>
|
||||
<Copy text={text} onCopy={() => handleCopied(setCopied, setLoadingStatus, label)}>
|
||||
<button className={copied ? 'text-success' : ''}>
|
||||
{copied ? (
|
||||
<OkIcon className="w-5 h-5 text-success-content bg-success rounded-full p-1" stroke={4} />
|
||||
|
|
|
@ -1,25 +1,41 @@
|
|||
import React from 'react'
|
||||
import { CopyToClipboard } from '@freesewing/react/components/CopyToClipboard'
|
||||
|
||||
const names = {
|
||||
const defaultTitles = {
|
||||
js: 'Javascript',
|
||||
bash: 'Bash prompt',
|
||||
sh: 'Shell prompt',
|
||||
bash: 'Bash commands',
|
||||
sh: 'Shell commands',
|
||||
json: 'JSON',
|
||||
yaml: 'file.yaml',
|
||||
yaml: 'YAML',
|
||||
}
|
||||
|
||||
export const Highlight = (props) => {
|
||||
let language = 'txt'
|
||||
if (props.language) language = props.language
|
||||
if (props.children?.props?.className) {
|
||||
language = props.children.props.className.split('-').pop()
|
||||
/**
|
||||
* A React component to highlight code
|
||||
*
|
||||
* @params {object} props - All React props
|
||||
* @params {string} language - The language to highlight
|
||||
* @params {object} children - The React children
|
||||
* @params {bool} raw - Set this to true to not escape tags
|
||||
* @params {string} title - Title for the highlight
|
||||
* @params {string} copy - Content to copy to clipboard
|
||||
*/
|
||||
export const Highlight = ({
|
||||
language = 'txt',
|
||||
children,
|
||||
raw = false,
|
||||
title = false,
|
||||
copy = false,
|
||||
}) => {
|
||||
if (children?.props?.className) {
|
||||
language = children.props.className.split('-').pop()
|
||||
}
|
||||
|
||||
const preProps = {
|
||||
className: `language-${language} hljs text-base lg:text-lg whitespace-break-spaces overflow-scroll pr-4`,
|
||||
}
|
||||
if (props.raw) preProps.dangerouslySetInnerHTML = { __html: props.raw }
|
||||
if (raw) preProps.dangerouslySetInnerHTML = { __html: raw }
|
||||
|
||||
const label = title ? title : defaultTitles[language] ? defaultTitles[language] : language
|
||||
|
||||
return (
|
||||
<div className="hljs my-4">
|
||||
|
@ -31,10 +47,10 @@ export const Highlight = (props) => {
|
|||
px-4 py-1 mb-2 lg:text-sm
|
||||
`}
|
||||
>
|
||||
<span>{props.title ? props.title : names[language] ? names[language] : language}</span>
|
||||
<CopyToClipboard content={props.children} />
|
||||
<span>{label}</span>
|
||||
<CopyToClipboard content={copy ? copy : children} label={label} />
|
||||
</div>
|
||||
<pre {...preProps}>{props.children}</pre>
|
||||
<pre {...preProps}>{children}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,13 +14,11 @@ import React, { useState, useCallback, useContext } from 'react'
|
|||
import { useDropzone } from 'react-dropzone'
|
||||
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
||||
// Components
|
||||
//import { Mdx } from 'shared/components/mdx/dynamic.mjs'
|
||||
import { ResetIcon, DocsIcon, UploadIcon } from '@freesewing/react/components/Icon'
|
||||
import { ResetIcon, UploadIcon } 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'
|
||||
|
||||
export const ns = ['account', 'measurements', 'designs']
|
||||
import Markdown from 'react-markdown'
|
||||
|
||||
/*
|
||||
* Helper component to display a tab heading
|
||||
|
@ -32,8 +30,8 @@ export const _Tab = ({
|
|||
setActiveTab, // Method to set the active tab
|
||||
}) => (
|
||||
<button
|
||||
className={`text-lg font-bold capitalize tab tab-bordered grow
|
||||
${activeTab === id ? 'tab-active' : ''}`}
|
||||
className={`text-lg font-bold capitalize daisy-tab daisy-tab-bordered grow
|
||||
${activeTab === id ? 'daisy-tab-active' : ''}`}
|
||||
onClick={() => setActiveTab(id)}
|
||||
>
|
||||
{label ? label : id}
|
||||
|
@ -46,42 +44,16 @@ export const _Tab = ({
|
|||
export const FormControl = ({
|
||||
label, // the (top-left) label
|
||||
children, // Children to go inside the form control
|
||||
docs = false, // Optional top-right label
|
||||
labelBL = false, // Optional bottom-left label
|
||||
labelBR = false, // Optional bottom-right label
|
||||
forId = false, // ID of the for element we are wrapping
|
||||
}) => {
|
||||
const { setModal } = useContext(ModalContext)
|
||||
|
||||
if (labelBR && !labelBL) labelBL = <span></span>
|
||||
|
||||
const topLabelChildren = (
|
||||
<>
|
||||
<span className="daisy-label-text text-sm lg:text-base font-bold mb-1 text-inherit">
|
||||
{label}
|
||||
</span>
|
||||
{docs ? (
|
||||
<span className="daisy-label-text-alt">
|
||||
<button
|
||||
className="daisy-btn daisy-btn-ghost daisy-btn-sm daisy-btn-circle hover:daisy-btn-secondary"
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<ModalWrapper
|
||||
flex="col"
|
||||
justify="top lg:justify-center"
|
||||
slideFrom="right"
|
||||
keepOpenOnClick
|
||||
>
|
||||
<div className="markdown max-w-prose">{docs}</div>
|
||||
</ModalWrapper>
|
||||
)
|
||||
}
|
||||
>
|
||||
<DocsIcon />
|
||||
</button>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
<span className="daisy-label-text text-sm lg:text-base font-bold mb-1 text-inherit">
|
||||
{label}
|
||||
</span>
|
||||
)
|
||||
const bottomLabelChildren = (
|
||||
<>
|
||||
|
@ -149,7 +121,6 @@ export const NumberInput = ({
|
|||
current, // The current value
|
||||
original, // The original value
|
||||
placeholder, // The placeholder text
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
|
@ -157,7 +128,7 @@ export const NumberInput = ({
|
|||
min = 220,
|
||||
step = 1,
|
||||
}) => (
|
||||
<FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
|
||||
<FormControl {...{ label, labelBL, labelBR }} forId={id}>
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
|
@ -183,12 +154,11 @@ export const StringInput = ({
|
|||
current, // The current value
|
||||
original, // The original value
|
||||
placeholder, // The placeholder text
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
}) => (
|
||||
<FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
|
||||
<FormControl {...{ label, labelBL, labelBR }} forId={id}>
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
|
@ -220,7 +190,6 @@ export const MfaInput = ({
|
|||
valid={(val) => val.length > 4}
|
||||
{...{ update, current, id }}
|
||||
placeholder="MFA Code"
|
||||
docs={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -234,7 +203,6 @@ export const PasswordInput = ({
|
|||
valid, // Method that should return whether the value is valid or not
|
||||
current, // The current value
|
||||
placeholder = '¯\\_(ツ)_/¯', // The placeholder text
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
onKeyDown = false, // Optionall capture certain keys (like enter)
|
||||
}) => {
|
||||
|
@ -245,7 +213,6 @@ export const PasswordInput = ({
|
|||
return (
|
||||
<FormControl
|
||||
label={label}
|
||||
docs={docs}
|
||||
forId={id}
|
||||
labelBR={
|
||||
<button
|
||||
|
@ -281,12 +248,11 @@ export const EmailInput = ({
|
|||
current, // The current value
|
||||
original, // The original value
|
||||
placeholder, // The placeholder text
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
}) => (
|
||||
<FormControl {...{ label, docs, labelBL, labelBR }} forId={id}>
|
||||
<FormControl {...{ label, labelBL, labelBR }} forId={id}>
|
||||
<input
|
||||
id={id}
|
||||
type="email"
|
||||
|
@ -301,21 +267,20 @@ export const EmailInput = ({
|
|||
)
|
||||
|
||||
/*
|
||||
* Dropdown for designs
|
||||
* Input for designs
|
||||
*/
|
||||
export const DesignDropdown = ({
|
||||
export const DesignInput = ({
|
||||
label, // Label to use
|
||||
update, // onChange handler
|
||||
current, // The current value
|
||||
docs = false, // Docs to load, if any
|
||||
firstOption = null, // Any first option to add in addition to designs
|
||||
id = '', // An id to tie the input to the label
|
||||
}) => {
|
||||
return (
|
||||
<FormControl label={label} docs={docs} forId={id}>
|
||||
<FormControl label={label} forId={id}>
|
||||
<select
|
||||
id={id}
|
||||
className="select select-bordered w-full"
|
||||
className="daisy-select daisy-select-bordered w-full"
|
||||
onChange={(evt) => update(evt.target.value)}
|
||||
value={current}
|
||||
>
|
||||
|
@ -338,7 +303,6 @@ export const ImageInput = ({
|
|||
update, // The onChange handler
|
||||
current, // The current value
|
||||
original, // The original value
|
||||
docs = false, // Docs to load, if any
|
||||
active = false, // Whether or not to upload images
|
||||
imgType = 'showcase', // The image type
|
||||
imgSubid, // The image sub-id
|
||||
|
@ -383,7 +347,7 @@ export const ImageInput = ({
|
|||
|
||||
if (current)
|
||||
return (
|
||||
<FormControl label={label} docs={docs}>
|
||||
<FormControl label={label}>
|
||||
<div
|
||||
className="bg-base-100 w-full h-36 mb-2 mx-auto flex flex-col items-center text-center justify-center"
|
||||
style={{
|
||||
|
@ -396,7 +360,7 @@ export const ImageInput = ({
|
|||
}}
|
||||
>
|
||||
<button
|
||||
className="btn btn-neutral btn-circle opacity-50 hover:opacity-100"
|
||||
className="daisy-btn daisy-btn-neutral daisy-btn-circle opacity-50 hover:opacity-100"
|
||||
onClick={() => update(original)}
|
||||
>
|
||||
<ResetIcon />
|
||||
|
@ -406,7 +370,7 @@ export const ImageInput = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<FormControl label={label} docs={docs} forId={id}>
|
||||
<FormControl label={label} forId={id}>
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`
|
||||
|
@ -417,7 +381,7 @@ export const ImageInput = ({
|
|||
<input {...getInputProps()} />
|
||||
<p className="hidden lg:block p-0 m-0">Drag and drop and image here</p>
|
||||
<p className="hidden lg:block p-0 my-2">or</p>
|
||||
<button className={`btn btn-secondary btn-outline mt-4 px-8`}>
|
||||
<button className={`daisy-btn daisy-btn-secondary daisy-btn-outline mt-4 px-8`}>
|
||||
Select an image to use
|
||||
</button>
|
||||
</div>
|
||||
|
@ -433,7 +397,7 @@ export const ImageInput = ({
|
|||
/>
|
||||
{active && (
|
||||
<button
|
||||
className="btn btn-secondary ml-2 capitalize"
|
||||
className="daisy-btn daisy-btn-secondary ml-2 capitalize"
|
||||
disabled={!url || url.length < 1}
|
||||
onClick={() => upload(url, true)}
|
||||
>
|
||||
|
@ -463,9 +427,8 @@ export const ListInput = ({
|
|||
label, // The label
|
||||
list, // The list of items to present { val, label, desc }
|
||||
current, // The (value of the) current item
|
||||
docs = false, // Docs to load, if any
|
||||
}) => (
|
||||
<FormControl label={label} docs={docs}>
|
||||
<FormControl label={label}>
|
||||
{list.map((item, i) => (
|
||||
<ButtonFrame key={i} active={item.val === current} onClick={() => update(item.val)}>
|
||||
<div className="w-full flex flex-col gap-2">
|
||||
|
@ -489,15 +452,18 @@ export const MarkdownInput = ({
|
|||
current, // The current value (markdown)
|
||||
update, // The onChange handler
|
||||
placeholder, // The placeholder content
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
labelBL = false, // Bottom-Left label
|
||||
labelBR = false, // Bottom-Right label
|
||||
}) => (
|
||||
<FormControl {...{ label, labelBL, labelBR, docs }} forId={id}>
|
||||
<FormControl
|
||||
{...{ label, labelBR }}
|
||||
forId={id}
|
||||
labelBL={labelBL ? labelBL : 'This field supports markdown'}
|
||||
>
|
||||
<Tabs tabs={['edit', 'preview']}>
|
||||
<Tab key="edit">
|
||||
<div className="flex flex-row items-center mt-4">
|
||||
<div className="flex flex-row items-center">
|
||||
<textarea
|
||||
id={id}
|
||||
rows="5"
|
||||
|
@ -509,8 +475,8 @@ export const MarkdownInput = ({
|
|||
</div>
|
||||
</Tab>
|
||||
<Tab key="preview">
|
||||
<div className="flex flex-row items-center mt-4">
|
||||
<Mdx md={current} />
|
||||
<div className="flex flex-row items-center">
|
||||
<Markdown>{current}</Markdown>
|
||||
</div>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
@ -523,7 +489,6 @@ export const MeasieInput = ({
|
|||
original, // The original value
|
||||
update, // The onChange handler
|
||||
placeholder, // The placeholder content
|
||||
docs = false, // Docs to load, if any
|
||||
id = '', // An id to tie the input to the label
|
||||
}) => {
|
||||
const isDegree = isDegreeMeasurement(m)
|
||||
|
@ -553,14 +518,14 @@ export const MeasieInput = ({
|
|||
if (!m) return null
|
||||
|
||||
// Various visual indicators for validating the input
|
||||
let inputClasses = 'input-secondary'
|
||||
let inputClasses = 'daisy-input-secondary'
|
||||
let bottomLeftLabel = null
|
||||
if (valid === true) {
|
||||
inputClasses = 'input-success'
|
||||
inputClasses = 'daisy-input-success'
|
||||
const val = `${validatedVal}${isDegree ? '°' : imperial ? '"' : 'cm'}`
|
||||
bottomLeftLabel = <span className="font-medium text-base text-success -mt-2 block">{val}</span>
|
||||
} else if (valid === false) {
|
||||
inputClasses = 'input-error'
|
||||
inputClasses = 'daisy-input-error'
|
||||
bottomLeftLabel = (
|
||||
<span className="font-medium text-error text-base -mt-2 block">¯\_(ツ)_/¯</span>
|
||||
)
|
||||
|
@ -574,12 +539,7 @@ export const MeasieInput = ({
|
|||
* See: https://github.com/facebook/react/issues/16554
|
||||
*/
|
||||
return (
|
||||
<FormControl
|
||||
label={t(m) + (isDegree ? ' (°)' : '')}
|
||||
docs={docs}
|
||||
forId={id}
|
||||
labelBL={bottomLeftLabel}
|
||||
>
|
||||
<FormControl label={m + (isDegree ? ' (°)' : '')} forId={id} labelBL={bottomLeftLabel}>
|
||||
<input
|
||||
id={id}
|
||||
type="text"
|
||||
|
@ -588,7 +548,7 @@ export const MeasieInput = ({
|
|||
placeholder={placeholder}
|
||||
value={localVal}
|
||||
onChange={(evt) => localUpdate(evt.target.value)}
|
||||
className={`input w-full input-bordered ${inputClasses}`}
|
||||
className={`daisy-input w-full daisy-input-bordered ${inputClasses}`}
|
||||
/>
|
||||
</FormControl>
|
||||
)
|
||||
|
@ -628,7 +588,7 @@ export const FileInput = ({
|
|||
<FormControl label={label} isValid={valid(current)}>
|
||||
<div className="bg-base-100 w-full h-36 mb-2 mx-auto flex flex-col items-center text-center justify-center">
|
||||
<button
|
||||
className="btn btn-neutral btn-circle opacity-50 hover:opacity-100"
|
||||
className="daisy-btn daisy-btn-neutral daisy-btn-circle opacity-50 hover:opacity-100"
|
||||
onClick={() => update(original)}
|
||||
>
|
||||
<ResetIcon />
|
||||
|
@ -651,7 +611,9 @@ export const FileInput = ({
|
|||
>
|
||||
<input {...getInputProps()} />
|
||||
<p className="hidden lg:block p-0 m-0">Drag and drop your file here</p>
|
||||
<button className={`btn btn-secondary btn-outline mt-4 px-8`}>Browse...</button>
|
||||
<button className={`daisy-btn daisy-btn-secondary daisy-btn-outline mt-4 px-8`}>
|
||||
Browse...
|
||||
</button>
|
||||
</div>
|
||||
</FormControl>
|
||||
)
|
||||
|
|
|
@ -5,5 +5,7 @@ import hljs from 'highlight.js/lib/common'
|
|||
export const Json = (props) => {
|
||||
const code = props.js ? JSON.stringify(props.js, null, 2) : props.children
|
||||
|
||||
return <Highlight language="json" raw={hljs.highlight(code, { language: 'json' }).value} />
|
||||
return (
|
||||
<Highlight language="json" raw={hljs.highlight(code, { language: 'json' }).value} copy={code} />
|
||||
)
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@ export const ModalWrapper = ({
|
|||
flex = 'row',
|
||||
justify = 'center',
|
||||
items = 'center',
|
||||
bg = 'base-100 lg:bg-base-300',
|
||||
bgOpacity = '100 lg:bg-opacity-95',
|
||||
bg = 'neutral lg:neutral',
|
||||
bgOpacity = '100 lg:bg-opacity-70',
|
||||
bare = false,
|
||||
keepOpenOnClick = false,
|
||||
slideFrom = 'left',
|
||||
|
@ -68,9 +68,10 @@ export const ModalWrapper = ({
|
|||
<div
|
||||
className={`fixed top-0 left-0 m-0 p-0 shadow w-full h-screen
|
||||
transform-all duration-150 ${animation}
|
||||
bg-${bg} bg-opacity-${bgOpacity} z-40 hover:cursor-pointer
|
||||
flex flex-${flex} justify-${justify} items-${items} lg:p-12`}
|
||||
bg-${bg} bg-opacity-${bgOpacity} hover:cursor-pointer
|
||||
flex flex-${flex} justify-${justify} items-${items} lg:p-12 backdrop-blur-md`}
|
||||
onClick={close}
|
||||
style={{ zIndex: 250 }}
|
||||
>
|
||||
{bare ? (
|
||||
children
|
||||
|
|
|
@ -1,8 +1,104 @@
|
|||
import React from 'react'
|
||||
import { mergeProps } from './utils.mjs'
|
||||
import { Popout as SwizzledPopout } from './editor/swizzle/components/popout.mjs'
|
||||
import { CloseIcon } from './editor/swizzle/components/icons.mjs'
|
||||
import React, { useState } from 'react'
|
||||
import { CloseIcon } from '@freesewing/react/components/Icon'
|
||||
|
||||
const t = (id) => id
|
||||
const colors = {
|
||||
comment: 'secondary',
|
||||
error: 'error',
|
||||
fixme: 'warning',
|
||||
link: 'secondary',
|
||||
none: '',
|
||||
note: 'primary',
|
||||
related: 'info',
|
||||
tip: 'accent',
|
||||
tldr: 'info',
|
||||
warning: 'error',
|
||||
}
|
||||
|
||||
export const Popout = (props) => <SwizzledPopout {...mergeProps(props, { CloseIcon }, { t })} />
|
||||
/**
|
||||
* This popout component is a way to make some content stand out
|
||||
*
|
||||
* @param {object} props - All React props
|
||||
* @param {object} props.comment - Set this to make it a comment popout
|
||||
* @param {object} props.error - Set this to make it a error popout
|
||||
* @param {object} props.fixme - Set this to make it a fixme popout
|
||||
* @param {object} props.link - Set this to make it a link popout
|
||||
* @param {object} props.note - Set this to make it a note popout
|
||||
* @param {object} props.related - Set this to make it a related popout
|
||||
* @param {object} props.tip - Set this to make it a tip popout
|
||||
* @param {object} props.tldr - Set this to make it a tldr popout
|
||||
* @param {object} props.warning - Set this to make it a warning popout
|
||||
* @param {string} props.title - The popout title
|
||||
* @param {string} noP - Do not wrap the content in a p tag
|
||||
*/
|
||||
export const Popout = (props) => {
|
||||
// Make this hideable/dismissable
|
||||
const [hide, setHide] = useState(false)
|
||||
if (hide) return null
|
||||
|
||||
let type = 'none'
|
||||
for (const c in colors) {
|
||||
if (props[c]) type = c
|
||||
}
|
||||
const color = colors[type]
|
||||
const { className = '' } = props
|
||||
|
||||
return props.compact ? (
|
||||
<div
|
||||
className={`relative ${
|
||||
props.dense ? 'my-1' : 'my-8'
|
||||
} bg-${color} bg-opacity-5 -ml-4 -mr-4 sm:ml-0 sm:mr-0 ${className}`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
border-y-4 sm:border-0 sm:border-l-4 px-4
|
||||
shadow text-base border-${color}
|
||||
flex flex-row items-center
|
||||
`}
|
||||
>
|
||||
<div className={`font-bold uppercase text-${color}`}>
|
||||
{props.title || (
|
||||
<>
|
||||
<span>{type.toUpperCase()}</span>
|
||||
<span className="px-3">|</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="popout-content">{props.noP ? props.children : <p>{props.children}</p>}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={`relative my-8 bg-${color} bg-opacity-5 -ml-4 -mr-4 sm:ml-0 sm:mr-0 ${className}`}
|
||||
>
|
||||
<div
|
||||
className={`
|
||||
border-y-4 sm:border-0 sm:border-l-4 px-6 sm:px-8 py-4 sm:py-2
|
||||
shadow text-base border-${color}
|
||||
`}
|
||||
>
|
||||
<div className={`font-bold flex flex-row gap-1 items-end justify-between`}>
|
||||
<div>
|
||||
<span className={`font-bold uppercase text-${color}`}>
|
||||
{type === 'tldr' ? 'TL;DR' : type.toUpperCase()}
|
||||
</span>
|
||||
<span className={`font-normal text-base text-${color}`}>
|
||||
{type === 'comment' && (
|
||||
<>
|
||||
{' '}
|
||||
by <b>{props.by}</b>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
{props.hideable && (
|
||||
<button onClick={() => setHide(true)} className="hover:text-secondary" title="Close">
|
||||
<CloseIcon />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-1 first:mt-0 popout-content">{props.children}</div>
|
||||
{type === 'comment' && <div className={`font-bold italic text-${color}`}>{props.by}</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,16 +31,17 @@ export const Tabs = ({ tabs = '', active = 0, children, withModal = false }) =>
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="my-4">
|
||||
<div className="tabs">
|
||||
<div className="">
|
||||
<div className="daisy-tabs daisy-tabs-bordered" role="tablist">
|
||||
{tablist.map((title, tabId) => {
|
||||
const btnClasses = `text-lg font-bold capitalize tab h-auto tab-bordered grow py-2 ${
|
||||
activeTab === tabId ? 'tab-active' : ''
|
||||
const btnClasses = `text-lg font-bold capitalize daisy-tab h-auto daisy-tabs-bordered grow py-1 ${
|
||||
activeTab === tabId ? 'daisy-tab-active' : ''
|
||||
}`
|
||||
|
||||
return withModal && activeTab === tabId ? (
|
||||
<button
|
||||
key={tabId}
|
||||
role="tab"
|
||||
className={btnClasses}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
|
|
|
@ -10,6 +10,11 @@ export const Yaml = (props) => {
|
|||
else code = props.children
|
||||
|
||||
return (
|
||||
<Highlight {...props} language="yaml" raw={hljs.highlight(code, { language: 'yaml' }).value} />
|
||||
<Highlight
|
||||
{...props}
|
||||
language="yaml"
|
||||
raw={hljs.highlight(code, { language: 'yaml' }).value}
|
||||
copy={code}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue