wip(org): More work on managing patterns
This commit is contained in:
parent
cdc67a6086
commit
cab7f5d2c5
15 changed files with 375 additions and 176 deletions
|
@ -11,7 +11,7 @@ import {
|
|||
ClearIcon,
|
||||
CodeIcon,
|
||||
CutIcon,
|
||||
HelpIcon,
|
||||
FingerprintIcon,
|
||||
MenuIcon,
|
||||
OptionsIcon,
|
||||
PrintIcon,
|
||||
|
@ -20,6 +20,8 @@ import {
|
|||
import { Ribbon } from 'shared/components/ribbon.mjs'
|
||||
import Link from 'next/link'
|
||||
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
|
||||
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
|
||||
import { ControlSettings } from 'shared/components/account/control.mjs'
|
||||
|
||||
export const ns = ['workbench', 'sections']
|
||||
|
||||
|
@ -150,8 +152,19 @@ const NavIcons = ({ setModal, setView, view }) => {
|
|||
<ClearIcon className={iconSize} />
|
||||
</NavButton>
|
||||
<NavSpacer />
|
||||
<NavButton href="/account" label={t('workbench:help')} color={colors[9]}>
|
||||
<HelpIcon className={iconSize} />
|
||||
<NavButton
|
||||
label={t('workbench:control')}
|
||||
color={colors[9]}
|
||||
onClick={() =>
|
||||
setModal(
|
||||
<ModalWrapper>
|
||||
<ControlSettings noBack title />
|
||||
<div className="mb-3"></div>
|
||||
</ModalWrapper>
|
||||
)
|
||||
}
|
||||
>
|
||||
<FingerprintIcon className={iconSize} />
|
||||
</NavButton>
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -10,17 +10,12 @@ import { objUpdate } from 'shared/utils.mjs'
|
|||
// Components
|
||||
import { WorkbenchHeader } from './header.mjs'
|
||||
import { ErrorView } from 'shared/components/error/view.mjs'
|
||||
import { ModalSpinner } from 'shared/components/modal/spinner.mjs'
|
||||
// Views
|
||||
import { DraftView, ns as draftNs } from 'shared/components/workbench/views/draft/index.mjs'
|
||||
import { SaveView, ns as saveNs } from 'shared/components/workbench/views/save/index.mjs'
|
||||
|
||||
export const ns = ['workbench', ...draftNs, ...saveNs]
|
||||
|
||||
const loadDefaultSettings = ({ locale = 'en', units = 'metric' }) => ({
|
||||
units,
|
||||
locale,
|
||||
embed: true,
|
||||
})
|
||||
export const ns = ['account', 'workbench', ...draftNs, ...saveNs]
|
||||
|
||||
const defaultUi = {
|
||||
renderer: 'react',
|
||||
|
@ -28,31 +23,32 @@ const defaultUi = {
|
|||
|
||||
const draftViews = ['draft', 'test']
|
||||
|
||||
export const Workbench = ({ design, Design, set = false, DynamicDocs = false }) => {
|
||||
export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) => {
|
||||
// Hooks
|
||||
const { t, i18n } = useTranslation(ns)
|
||||
const { language } = i18n
|
||||
const { account } = useAccount()
|
||||
|
||||
const defaultSettings = loadDefaultSettings({
|
||||
units: account.imperial ? 'imperial' : 'metric',
|
||||
locale: language,
|
||||
})
|
||||
if (set) defaultSettings.measurements = set.measies
|
||||
|
||||
// State
|
||||
const [view, setView] = useView()
|
||||
const [settings, setSettings] = useState({ ...defaultSettings, embed: true })
|
||||
const [ui, setUi] = useState({ ...defaultUi })
|
||||
const [settings, setSettings] = useState({ ...baseSettings, embed: true })
|
||||
const [ui, setUi] = useState(defaultUi)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
// Effects
|
||||
// Effect
|
||||
useEffect(() => {
|
||||
if (set.measies) update.settings('measurements', set.measies)
|
||||
}, [set])
|
||||
// Force re-render when baseSettings changes. Required when they are loaded async.
|
||||
setSettings({ ...baseSettings, embed: true })
|
||||
}, [baseSettings])
|
||||
|
||||
// Don't bother without a set or Design
|
||||
if (!set || !Design) return null
|
||||
// Helper methods for settings/ui updates
|
||||
const update = {
|
||||
settings: (path, val) => setSettings(objUpdate({ ...settings }, path, val)),
|
||||
ui: (path, val) => setUi(objUpdate({ ...ui }, path, val)),
|
||||
}
|
||||
|
||||
// Don't bother without a Design
|
||||
if (!Design) return <ModalSpinner />
|
||||
|
||||
// Short-circuit errors early
|
||||
if (error)
|
||||
|
@ -62,13 +58,7 @@ export const Workbench = ({ design, Design, set = false, DynamicDocs = false })
|
|||
{error}
|
||||
</>
|
||||
)
|
||||
|
||||
// Helper methods for settings/ui updates
|
||||
const update = {
|
||||
settings: (path, val) => setSettings(objUpdate({ ...settings }, path, val)),
|
||||
ui: (path, val) => setUi(objUpdate({ ...ui }, path, val)),
|
||||
}
|
||||
|
||||
console.log(baseSettings)
|
||||
// Deal with each view
|
||||
const viewProps = {
|
||||
account,
|
||||
|
@ -108,7 +98,7 @@ export const Workbench = ({ design, Design, set = false, DynamicDocs = false })
|
|||
}
|
||||
|
||||
// Save view
|
||||
else if (view === 'save') viewContent = <SaveView {...viewProps} />
|
||||
else if (view === 'save') viewContent = <SaveView {...viewProps} from={from} />
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -146,7 +146,7 @@ export const Setting = ({
|
|||
|
||||
return (
|
||||
<Collapse
|
||||
color={changed ? 'accent' : 'secondary'}
|
||||
color={changed ? 'accent' : 'primary'}
|
||||
openTitle={<CoreTitle open {...titleProps} />}
|
||||
title={<CoreTitle {...titleProps} />}
|
||||
buttons={buttons}
|
||||
|
@ -167,26 +167,27 @@ export const CoreSettings = ({
|
|||
language,
|
||||
account,
|
||||
DynamicDocs,
|
||||
control,
|
||||
}) => {
|
||||
// FIXME: Update this namespace
|
||||
const { t } = useTranslation(['i18n', 'core-settings', design])
|
||||
const { setModal } = useContext(ModalContext)
|
||||
|
||||
// For the simplest experience, not core settings are shown at all
|
||||
if (control < 2) return null
|
||||
|
||||
const settingsConfig = loadSettingsConfig({
|
||||
language,
|
||||
control: account.control,
|
||||
control,
|
||||
sabool: settings.sabool,
|
||||
parts: patternConfig.draftOrder,
|
||||
})
|
||||
// Default control level is 2 (in case people are not logged in)
|
||||
const control = account.control || 2
|
||||
|
||||
const loadDocs = DynamicDocs
|
||||
? (evt, setting = false) => {
|
||||
evt.stopPropagation()
|
||||
let path = `site/draft/core-settings`
|
||||
if (setting) path += `/${setting}`
|
||||
console.log(path)
|
||||
setModal(
|
||||
<ModalWrapper>
|
||||
<div className="max-w-prose">
|
||||
|
@ -210,19 +211,20 @@ export const CoreSettings = ({
|
|||
)
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
bottom
|
||||
color="primary"
|
||||
title={
|
||||
<div className="w-full flex flex-row gap2 items-center justify-between">
|
||||
<span className="font-bold">{t('core-settings:coreSettings.t')}</span>
|
||||
<SettingsIcon className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
}
|
||||
openTitle={t('core-settings:coreSettings')}
|
||||
openButtons={openButtons}
|
||||
>
|
||||
<p>{t('core-settings:coreSettings.d')}</p>
|
||||
<>
|
||||
<div className="px-2 mt-8">
|
||||
{control > 4 ? (
|
||||
<div className="border-t border-solid border-base-300 pb-2 mx-36"></div>
|
||||
) : (
|
||||
<>
|
||||
<h5 className="flex flex-row gap-2 items-center">
|
||||
<SettingsIcon />
|
||||
<span>{t('core-settings:coreSettings')}</span>
|
||||
</h5>
|
||||
<p>{t('core-settings:coreSettings.d')}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{Object.keys(settingsConfig)
|
||||
.filter((name) => settingsConfig[name].control <= control)
|
||||
.map((name) => (
|
||||
|
@ -236,6 +238,6 @@ export const CoreSettings = ({
|
|||
units={settings.units}
|
||||
/>
|
||||
))}
|
||||
</Collapse>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -173,8 +173,14 @@ export const DesignOptionGroup = ({
|
|||
Option,
|
||||
t,
|
||||
loadDocs,
|
||||
topLevel = false,
|
||||
}) => (
|
||||
<Collapse bottom color="secondary" title={<GroupTitle {...{ group, t }} />} openTitle={t(group)}>
|
||||
<Collapse
|
||||
bottom
|
||||
color={topLevel ? 'primary' : 'secondary'}
|
||||
title={<GroupTitle {...{ group, t }} />}
|
||||
openTitle={t(group)}
|
||||
>
|
||||
{Object.entries(options).map(([option, type]) =>
|
||||
typeof type === 'string' ? (
|
||||
<Option
|
||||
|
@ -214,6 +220,7 @@ export const DesignOptions = ({
|
|||
account,
|
||||
Option = false,
|
||||
DynamicDocs = false,
|
||||
control,
|
||||
}) => {
|
||||
const { t } = useTranslation([design])
|
||||
const { setModal } = useContext(ModalContext)
|
||||
|
@ -239,17 +246,18 @@ export const DesignOptions = ({
|
|||
: false
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
bottom
|
||||
color="primary"
|
||||
title={
|
||||
<div className="w-full flex flex-row gap2 items-center justify-between">
|
||||
<span className="font-bold">{t('design-options:designOptions')}</span>
|
||||
<OptionsIcon className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
}
|
||||
openTitle={t('design-options:designOptions')}
|
||||
>
|
||||
<>
|
||||
<div className="px-2 mt-8">
|
||||
{control > 4 ? null : (
|
||||
<>
|
||||
<h5 className="flex flex-row gap-2 items-center">
|
||||
<OptionsIcon />
|
||||
<span>{t('design-options:designOptions')}</span>
|
||||
</h5>
|
||||
<p>{t('design-options:designOptions.d')}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{Object.entries(optionsMenu).map(([group, option]) =>
|
||||
typeof option === 'string' ? (
|
||||
<Option
|
||||
|
@ -265,9 +273,10 @@ export const DesignOptions = ({
|
|||
{...{ design, patternConfig, settings, update, group, Option, t, loadDocs }}
|
||||
options={option}
|
||||
key={group}
|
||||
topLevel
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Collapse>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,65 +1,35 @@
|
|||
import { XrayIcon, HelpIcon } from 'shared/components/icons.mjs'
|
||||
import { XrayIcon } from 'shared/components/icons.mjs'
|
||||
//import { ConsoleLog } from './log.mjs'
|
||||
//import { XrayReset } from './reset.mjs'
|
||||
//import { XrayList } from './list.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { Collapse } from 'shared/components/collapse.mjs'
|
||||
import { ChoiceButton } from 'shared/components/choice-button.mjs'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
||||
export const ns = ['xray']
|
||||
|
||||
export const XrayMenu = ({ design, update, settings, ui }) => {
|
||||
export const XrayMenu = ({ design, update, settings, ui, account, control }) => {
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
if (ui.renderer !== 'react' || control < 4) return null
|
||||
|
||||
const toggleXray = () => update.ui(['xray', 'enabled'], ui?.xray?.enabled ? false : true)
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
bottom
|
||||
color="primary"
|
||||
title={
|
||||
<div className="w-full flex flex-row gap2 items-center justify-between">
|
||||
<span className="font-bold">{t('xray:xrayPattern')}</span>
|
||||
<XrayIcon className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
}
|
||||
openTitle={t('xray:xrayPattern')}
|
||||
openButtons={[
|
||||
<button className="btn btn-xs btn-ghost px-0 z-10" onClick={(evt) => loadDocs(evt)}>
|
||||
<HelpIcon className="w-4 h-4" />
|
||||
</button>,
|
||||
]}
|
||||
>
|
||||
<>
|
||||
<div className="px-2 mt-8">
|
||||
{control > 4 ? (
|
||||
<div className="border-t border-solid border-base-300 pb-2 mx-36"></div>
|
||||
) : (
|
||||
<>
|
||||
<h5 className="flex flex-row gap-2 items-center">
|
||||
<XrayIcon />
|
||||
<span>{t('xray:xrayPattern')}</span>
|
||||
</h5>
|
||||
<p>{t('core-settings:coreSettings.d')}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Popout fixme>Implement X-Ray</Popout>
|
||||
<ChoiceButton
|
||||
title={t(`yes`)}
|
||||
color={ui?.xray?.enabled ? 'primary' : 'accent'}
|
||||
active={ui?.xray?.enabled}
|
||||
onClick={toggleXray}
|
||||
>
|
||||
{t(`xray:yes.t`)}
|
||||
</ChoiceButton>
|
||||
<ChoiceButton
|
||||
title={t(`no`)}
|
||||
color={ui?.xray?.enabled ? 'accent' : 'primary'}
|
||||
active={!ui?.xray?.enabled}
|
||||
onClick={toggleXray}
|
||||
>
|
||||
{t(`xray:no.t`)}
|
||||
</ChoiceButton>
|
||||
{ui?.xray?.enabled && (
|
||||
<>
|
||||
<p>xray here</p>
|
||||
</>
|
||||
)}
|
||||
</Collapse>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
//<ConsoleLog />
|
||||
//<XrayReset />
|
||||
//{settings.xray.parts &&
|
||||
// Object.keys(settings.xray.parts).map((partName) => (
|
||||
// <XrayList partName={partName} key={partName} />
|
||||
// ))}
|
||||
|
|
|
@ -20,14 +20,25 @@ export const DraftMenu = ({
|
|||
language,
|
||||
account,
|
||||
DynamicDocs,
|
||||
}) => (
|
||||
<nav className="grow mb-12">
|
||||
<DesignOptions
|
||||
{...{ design, patternConfig, settings, update, language, account, DynamicDocs }}
|
||||
/>
|
||||
{account.control === 1 ? null : (
|
||||
<CoreSettings {...{ patternConfig, settings, update, language, account, DynamicDocs }} />
|
||||
)}
|
||||
{ui.renderer === 'react' && <XrayMenu {...{ ui, update, DynamicDocs }} />}
|
||||
</nav>
|
||||
)
|
||||
}) => {
|
||||
// Default control level is 2 (in case people are not logged in)
|
||||
const control = account.control || 2
|
||||
const menuProps = {
|
||||
design,
|
||||
patternConfig,
|
||||
settings,
|
||||
update,
|
||||
language,
|
||||
account,
|
||||
DynamicDocs,
|
||||
control,
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="grow mb-12">
|
||||
<DesignOptions {...menuProps} />
|
||||
<CoreSettings {...menuProps} />
|
||||
<XrayMenu {...menuProps} ui={ui} />
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,17 +14,27 @@ import { Spinner } from 'shared/components/spinner.mjs'
|
|||
|
||||
export const ns = ['wbsave']
|
||||
|
||||
const whereAreWe = (router = false, design) => {
|
||||
const whereAreWe = (router = false, design, from) => {
|
||||
const info = {}
|
||||
if (router?.asPath) {
|
||||
const chunks = router.asPath.split('/')
|
||||
info.new = chunks[1] === 'new'
|
||||
info.design = chunks[3]
|
||||
info.from = chunks[4]
|
||||
info.fromId = chunks[5].split('#').shift()
|
||||
info.locale = router.locale
|
||||
if (from && from.type) {
|
||||
if (from.type === 'pattern') {
|
||||
// Editing an existing pattern
|
||||
info.edit = true
|
||||
info.patternId = from.data.id
|
||||
if (from.data.set) info.set = from.data.setId
|
||||
else if (from.data.cset) info.cset = from.data.csetId
|
||||
} else if (from.type === 'new') {
|
||||
// Creating a new pattern
|
||||
const chunks = router.asPath.split('/')
|
||||
info.design = chunks[3]
|
||||
info.from = chunks[4]
|
||||
info.fromId = chunks[5].split('#').shift()
|
||||
if (info.design !== design) throw 'Design passed to view does not match URL state'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.design !== design) throw 'Design passed to view does not match URL state'
|
||||
|
||||
return info
|
||||
}
|
||||
|
@ -101,7 +111,124 @@ const SaveNewPattern = ({
|
|||
)
|
||||
}
|
||||
|
||||
export const SaveView = ({ design, setView, settings, ui, update, language, DynamicDocs }) => {
|
||||
const SaveExistingPattern = ({
|
||||
t,
|
||||
design,
|
||||
settings,
|
||||
info,
|
||||
backend,
|
||||
loading,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
toast,
|
||||
router,
|
||||
from,
|
||||
}) => {
|
||||
// State
|
||||
const [name, setName] = useState(from.name ? from.name : defaultName(info))
|
||||
|
||||
const update = async (evt) => {
|
||||
evt.preventDefault()
|
||||
if (evt.target.value !== name) setName(evt.target.value)
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
startLoading()
|
||||
const data = {
|
||||
settings: {
|
||||
...settings,
|
||||
measurements: { ...settings.measurements },
|
||||
},
|
||||
}
|
||||
if (data.settings.measurements) delete data.settings.measurements
|
||||
if (data.settings.embed) delete data.settings.embed
|
||||
|
||||
const result = await backend.updatePattern(from.data.id, data)
|
||||
if (result.success) {
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
const saveAs = async () => {
|
||||
startLoading()
|
||||
const data = {
|
||||
data: {},
|
||||
design,
|
||||
name,
|
||||
settings: {
|
||||
...settings,
|
||||
measurements: { ...settings.measurements },
|
||||
},
|
||||
}
|
||||
if (data.settings.measurements) delete data.settings.measurements
|
||||
if (data.settings.embed) delete data.settings.embed
|
||||
if (info.set) data.set = info.set
|
||||
else if (info.cset) data.cset = info.cset
|
||||
else return toast.error(<span>¯\_(ツ)_/¯</span>)
|
||||
|
||||
const result = await backend.createPattern(data)
|
||||
if (result.success) {
|
||||
toast.for.settingsSaved()
|
||||
router.push(`/patterns/${result.data.pattern.id}`)
|
||||
} else toast.for.backendError()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="max-w-sm w-full">
|
||||
<h2>{t('savePattern')}</h2>
|
||||
<button className="btn mt-4 capitalize btn-primary w-full" onClick={save}>
|
||||
<span className="flex flex-row items-center gap-2">
|
||||
{loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
<span>{t('processing')}</span>
|
||||
</>
|
||||
) : (
|
||||
t('save')
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="max-w-sm w-full">
|
||||
<h2>{t('saveAsPattern')}</h2>
|
||||
<label className="font-bold">{t('wbsave:giveItAName')}</label>
|
||||
<input
|
||||
value={name}
|
||||
onChange={update}
|
||||
className="input w-full input-bordered flex flex-row"
|
||||
type="text"
|
||||
placeholder={from.data.name ? from.data.name : t('title')}
|
||||
/>
|
||||
<button className="btn mt-4 capitalize btn-primary w-full" onClick={saveAs}>
|
||||
<span className="flex flex-row items-center gap-2">
|
||||
{loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
<span>{t('processing')}</span>
|
||||
</>
|
||||
) : (
|
||||
t('save')
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const SaveView = ({
|
||||
design,
|
||||
setView,
|
||||
settings,
|
||||
ui,
|
||||
update,
|
||||
language,
|
||||
DynamicDocs,
|
||||
from = false,
|
||||
}) => {
|
||||
// Hooks
|
||||
const { t } = useTranslation(ns)
|
||||
const { token } = useAccount()
|
||||
|
@ -111,28 +238,27 @@ export const SaveView = ({ design, setView, settings, ui, update, language, Dyna
|
|||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
const info = whereAreWe(router, design)
|
||||
const info = whereAreWe(router, design, from)
|
||||
|
||||
const saveProps = {
|
||||
t,
|
||||
design,
|
||||
settings,
|
||||
info,
|
||||
backend,
|
||||
loading,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
toast,
|
||||
router,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-auto mt-24">
|
||||
<h1 className="max-w-6xl m-auto text-center">{t('wbsave:title')}</h1>
|
||||
<div className="px-4 lg:px-12 flex flex-row flex-wrap gap-4 lg:gap-8 justify-around">
|
||||
{info.new ? (
|
||||
<SaveNewPattern
|
||||
{...{
|
||||
t,
|
||||
design,
|
||||
settings,
|
||||
info,
|
||||
backend,
|
||||
loading,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
toast,
|
||||
router,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{info.new ? <SaveNewPattern {...saveProps} /> : null}
|
||||
{info.edit ? <SaveExistingPattern {...saveProps} from={from} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -20,3 +20,4 @@ showOnlyThisPart: Show only this pattern part
|
|||
partInfo: Pattern part info
|
||||
pathInfo: Path info
|
||||
part: Pattern part
|
||||
control: Experience
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue