// Dependencies
import { cloudflareImageUrl } from 'shared/utils.mjs'
// Context
import { ModalContext } from 'shared/context/modal-context.mjs'
// Hooks
import { useState, useCallback, useContext } from 'react'
import { useTranslation } from 'next-i18next'
import { useDropzone } from 'react-dropzone'
// Components
import { Popout } from 'shared/components/popout/index.mjs'
import Markdown from 'react-markdown'
import { ResetIcon, DocsIcon, HelpIcon } from 'shared/components/icons.mjs'
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { isDegreeMeasurement } from 'config/measurements.mjs'
import { measurementAsMm, formatMm, measurementAsUnits, parseDistanceInput } from 'shared/utils.mjs'
//import { Collapse } from 'shared/components/collapse.mjs'
//import { PlusIcon, EditIcon } from 'shared/components/icons.mjs'
//import { NumberInput } from 'shared/components/workbench/menus/shared/inputs.mjs'
//import { useState, useCallback } from 'react'
export const ns = ['account', 'measurements']
/*
* Helper component to display a tab heading
*/
export const Tab = ({
id, // The tab ID
label, // A label for the tab, if not set we'll use the ID
activeTab, // Which tab (id) is active
setActiveTab, // Method to set the active tab
}) => (
)
/*
* Helper component to wrap a form control with a label
*/
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
}) => {
const { setModal } = useContext(ModalContext)
return (
{children}
{labelBL || labelBR ? (
) : null}
)
}
/*
* Helper method to wrap content in a button
*/
export const ButtonFrame = ({
children, // Children of the button
onClick, // onClick handler
active, // Whether or not to render the button as active/selected
}) => (
)
/*
* Input for strings
*/
export const StringInput = ({
label, // Label to use
update, // onChange handler
valid, // Method that should return whether the value is valid or not
current, // The current value
original, // The original value
placeholder, // The placeholder text
docs = false, // Docs to load, if any
}) => {
const { setModal } = useContext(ModalContext)
const labelTR = docs ? (
) : (
false
)
return (
update(evt.target.value)}
className={`input w-full input-bordered ${
current === original
? 'input-secondary'
: valid(current)
? 'input-success'
: 'input-error'
}`}
/>
)
}
/*
* Input for an image that is passive (it does not upload the image)
*/
export const PassiveImageInput = ({
label, // The label
update, // The onChange handler
current, // The current value
original, // The original value
docs = false, // Docs to load, if any
}) => {
const { t } = useTranslation(ns)
const onDrop = useCallback(
(acceptedFiles) => {
const reader = new FileReader()
reader.onload = async () => {
update(reader.result)
}
acceptedFiles.forEach((file) => reader.readAsDataURL(file))
},
[current]
)
const { getRootProps, getInputProps } = useDropzone({ onDrop })
if (current)
return (
)
return (
{t('imgDragAndDropImageHere')}
{t('or')}
{t('or')}
update(evt.target.value)}
/>
)
}
/*
* Input for a list of things to pick from
*/
export const ListInput = ({
update, // the onChange handler
label, // The label
list, // The list of items to present { val, label, desc }
current, // The (value of the) current item
original, // The original value
docs = false, // Docs to load, if any
}) => (
{list.map((item, i) => (
update(item.val)}>
{item.label}
{item.desc}
))}
)
/*
* Input for markdown content
*/
export const MarkdownInput = ({
label, // The label
current, // The current value (markdown)
original, // The original value
update, // The onChange handler
placeholder, // The placeholder content
docs = false, // Docs to load, if any
}) => {
const { t } = useTranslation(ns)
const [activeTab, setActiveTab] = useState('edit')
return (
{['edit', 'preview'].map((tab) => (
))}
{activeTab === 'edit' ? (
)
}
const Mval = ({ m, val = false, imperial = false, className = '' }) =>
val ? (
isDegreeMeasurement(m) ? (
{val}°
) : (
)
) : null
const heightClasses = {
2: 'h-12',
4: 'h-10',
8: 'h-8',
16: 'h-6',
32: 'h-4',
}
export const MeasieInput = ({
imperial, // True for imperial, False for metric
m, // The measurement name
current, // The current value
original, // The original value
update, // The onChange handler
placeholder, // The placeholder content
docs = false, // Docs to load, if any
}) => {
const { t } = useTranslation(['measurements'])
const isDegree = isDegreeMeasurement(m)
const units = imperial ? 'imperial' : 'metric'
const [localVal, setLocalVal] = useState(
typeof original === 'undefined' ? original : measurementAsUnits(original, units)
)
const [validatedVal, setValidatedVal] = useState(measurementAsUnits(original, units))
const [val, setVal] = useState(() => {
const measie = current
if (!measie) return ''
if (isDegree) return measie
return measurementAsUnits(measie, units)
})
const [valid, setValid] = useState(null)
// Update onChange
const localUpdate = (newVal) => {
setLocalVal(newVal)
const parsedVal = parseDistanceInput(newVal, imperial)
if (parsedVal) {
update(m, isDegree ? parsedVal : measurementAsMm(parsedVal, units))
setValid(true)
setValidatedVal(parsedVal)
} else setValid(false)
}
if (!m) return null
// Various visual indicators for validating the input
let inputClasses = 'input-secondary'
let bottomLeftLabel = null
let bottomRightLabel = null
if (valid === true) {
inputClasses = 'input-success'
const val = `${validatedVal}${isDegree ? '°' : imperial ? '"' : 'cm'}`
bottomLeftLabel = (
{val}
)
} else if (valid === false) {
inputClasses = 'input-error'
bottomLeftLabel = (
¯\_(ツ)_/¯
)
}
/*
* I'm on the fence here about using a text input rather than number
* Obviously, number is the more correct option, but when the user enter
* text, it won't fire an onChange event and thus they can enter text and it
* will not be marked as invalid input.
* See: https://github.com/facebook/react/issues/16554
*/
return (
localUpdate(evt.target.value)}
className={`input w-full input-bordered ${inputClasses}`}
/>
)
}