2022-12-14 16:37:17 -06:00
|
|
|
import yaml from 'js-yaml'
|
2023-02-11 16:45:00 -06:00
|
|
|
import { defaultGist } from 'shared/components/workbench/gist.mjs'
|
|
|
|
import { validateGist } from './gist-validator.mjs'
|
2022-12-14 16:37:17 -06:00
|
|
|
import { useEffect, useState, useRef } from 'react'
|
2023-02-11 16:45:00 -06:00
|
|
|
import Popout from 'shared/components/popout.mjs'
|
2022-12-16 11:58:39 -06:00
|
|
|
import { useTranslation } from 'next-i18next'
|
2022-12-16 12:19:12 -06:00
|
|
|
import { capitalize } from '@freesewing/core'
|
2022-12-14 16:37:17 -06:00
|
|
|
|
2022-12-16 11:58:39 -06:00
|
|
|
/** a view for editing the gist as yaml */
|
2023-02-11 16:45:00 -06:00
|
|
|
export const EditYaml = (props) => {
|
2022-12-16 11:58:39 -06:00
|
|
|
let { gist, setGist, gistReady } = props
|
2022-12-14 16:37:17 -06:00
|
|
|
|
|
|
|
const inputRef = useRef(null)
|
2022-12-16 11:58:39 -06:00
|
|
|
// the gist parsed to yaml
|
2022-12-14 16:37:17 -06:00
|
|
|
const [gistAsYaml, setGistAsYaml] = useState(null)
|
2022-12-16 12:26:56 -06:00
|
|
|
// any errors as a json string
|
2022-12-15 19:27:05 -06:00
|
|
|
const [error, setError] = useState(null)
|
2022-12-16 11:58:39 -06:00
|
|
|
// success notifier
|
|
|
|
const [success, setSuccess] = useState(null)
|
|
|
|
|
2022-12-22 09:47:34 -06:00
|
|
|
const { t } = useTranslation(['workbench'])
|
2022-12-14 16:37:17 -06:00
|
|
|
|
2022-12-16 11:58:39 -06:00
|
|
|
// parse the current gist to yaml. this will also run when the gist gets set by input
|
2022-12-14 16:37:17 -06:00
|
|
|
useEffect(() => {
|
2022-12-16 11:58:39 -06:00
|
|
|
if (gistReady) {
|
|
|
|
// get everything but the design because it's a function and can't be serialized
|
2022-12-16 12:49:47 -06:00
|
|
|
// eslint-disable-next-line no-unused-vars
|
2022-12-22 09:47:34 -06:00
|
|
|
const { design, ...gistRest } = gist
|
2022-12-16 11:58:39 -06:00
|
|
|
setGistAsYaml(yaml.dump(gistRest))
|
|
|
|
}
|
|
|
|
}, [gist, gistReady])
|
|
|
|
|
|
|
|
// set the line numbers when the yaml changes
|
2022-12-14 16:37:17 -06:00
|
|
|
useEffect(() => {
|
2022-12-16 11:58:39 -06:00
|
|
|
if (gistAsYaml) {
|
|
|
|
inputRef.current.value = gistAsYaml
|
|
|
|
}
|
2022-12-14 16:37:17 -06:00
|
|
|
}, [gistAsYaml])
|
|
|
|
|
2022-12-16 11:58:39 -06:00
|
|
|
/** user-initiated save */
|
2022-12-15 19:27:05 -06:00
|
|
|
const onSave = () => {
|
2022-12-16 11:58:39 -06:00
|
|
|
// clear the errors
|
2022-12-15 19:27:05 -06:00
|
|
|
setError(null)
|
2022-12-16 11:58:39 -06:00
|
|
|
try {
|
|
|
|
// parse back to json
|
|
|
|
const editedAsJson = yaml.load(inputRef.current.value)
|
|
|
|
// make it backwards compatible so that people can paste in the yaml export from org
|
2022-12-22 15:07:09 -06:00
|
|
|
// the yaml export from org is missing some of the settings that are needed in the gist,
|
|
|
|
// and what it does have is under 'settings', so we merge that stuff with the existing gist view state
|
|
|
|
// and the default settings to make sure all necessary keys are accounted for,
|
|
|
|
// but we're not keeping stuff that was supposed to be cleared
|
2022-12-16 11:58:39 -06:00
|
|
|
const gistFromDefaults = { _state: gist._state }
|
2023-02-11 16:45:00 -06:00
|
|
|
for (const d in defaultGist) {
|
|
|
|
gistFromDefaults[d] = gist[d] === undefined ? defaultGist[d] : gist[d]
|
2022-12-16 11:58:39 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// merge it all up
|
|
|
|
const gistToCheck = {
|
|
|
|
...gistFromDefaults,
|
|
|
|
...(editedAsJson.settings ? editedAsJson.settings : editedAsJson),
|
|
|
|
}
|
|
|
|
|
|
|
|
// validate it
|
|
|
|
const validation = validateGist(gistToCheck, props.design)
|
|
|
|
// if it's not valid, show a warning about errors
|
|
|
|
if (!validation.valid) {
|
|
|
|
const newError = JSON.stringify(validation.errors, null, 2)
|
|
|
|
setError(newError)
|
|
|
|
}
|
|
|
|
|
|
|
|
// save regardless
|
|
|
|
setGist(gistToCheck)
|
|
|
|
setSuccess(true)
|
|
|
|
} catch (e) {
|
|
|
|
setError(e)
|
2022-12-14 16:37:17 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-16 12:19:12 -06:00
|
|
|
const designName = capitalize(props.design.designConfig.data.name.replace('@freesewing/', ''))
|
2022-12-14 16:37:17 -06:00
|
|
|
return (
|
|
|
|
<div className="max-w-screen-xl m-auto h-screen form-control">
|
2022-12-22 15:07:09 -06:00
|
|
|
<h2>{t('yamlEditViewTitleThing', { thing: designName })}</h2>
|
2022-12-15 19:27:05 -06:00
|
|
|
|
|
|
|
{error ? (
|
2022-12-16 11:58:39 -06:00
|
|
|
<Popout warning className="mb-4">
|
2022-12-22 15:07:09 -06:00
|
|
|
<h3> {t('yamlEditViewError')} </h3>
|
|
|
|
{success ? <p> {t('yamlEditViewErrorDesc')}: </p> : null}
|
2022-12-15 19:27:05 -06:00
|
|
|
<pre
|
|
|
|
className="language-json hljs text-base lg:text-lg whitespace-pre overflow-scroll pr-4"
|
|
|
|
dangerouslySetInnerHTML={{ __html: error }}
|
|
|
|
></pre>
|
|
|
|
</Popout>
|
|
|
|
) : null}
|
2022-12-16 11:58:39 -06:00
|
|
|
{success ? (
|
|
|
|
<div className="alert alert-success my-4">
|
|
|
|
<div>
|
|
|
|
<span>{t('success')}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : null}
|
2022-12-22 09:47:34 -06:00
|
|
|
<div id="editor" className="h-3/5 my-8">
|
|
|
|
<textarea
|
|
|
|
className="textarea textarea-primary w-full p-4 leading-7 text-lg h-full"
|
|
|
|
name="gistAsYaml"
|
|
|
|
aria-label="Configuration in YAML format"
|
|
|
|
ref={inputRef}
|
|
|
|
defaultValue={gistAsYaml}
|
|
|
|
/>
|
2022-12-14 16:37:17 -06:00
|
|
|
</div>
|
2022-12-15 19:27:05 -06:00
|
|
|
<button className="btn btn-primary" onClick={onSave}>
|
2022-12-14 16:37:17 -06:00
|
|
|
{' '}
|
|
|
|
Save{' '}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|