1
0
Fork 0

wip(org): More work on managing patterns

This commit is contained in:
joostdecock 2023-05-31 15:32:54 +02:00
parent cdc67a6086
commit cab7f5d2c5
15 changed files with 375 additions and 176 deletions

View file

@ -2,6 +2,7 @@ import { log } from '../utils/log.mjs'
import { capitalize } from '../utils/index.mjs' import { capitalize } from '../utils/index.mjs'
import { setPatternAvatar } from '../utils/sanity.mjs' import { setPatternAvatar } from '../utils/sanity.mjs'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import { SetModel } from './set.mjs'
export function PatternModel(tools) { export function PatternModel(tools) {
this.config = tools.config this.config = tools.config
@ -11,6 +12,7 @@ export function PatternModel(tools) {
this.rbac = tools.rbac this.rbac = tools.rbac
this.encryptedFields = ['data', 'img', 'name', 'notes', 'settings'] this.encryptedFields = ['data', 'img', 'name', 'notes', 'settings']
this.clear = {} // For holding decrypted data this.clear = {} // For holding decrypted data
this.Set = new SetModel(tools)
return this return this
} }
@ -218,8 +220,8 @@ PatternModel.prototype.reveal = async function () {
} }
if (this.record.cset.measies) this.record.cset.measies = JSON.parse(this.record.cset.measies) if (this.record.cset.measies) this.record.cset.measies = JSON.parse(this.record.cset.measies)
} }
if (this.record.set) { if (this.record?.set) {
if (this.record.set.measies) this.record.set.measies = JSON.parse(this.record.set.measies) this.record.set = this.Set.revealSet(this.record.set)
} }
return this return this

View file

@ -0,0 +1,77 @@
// Hooks
import { useEffect, useState } from 'react'
import { useAccount } from 'shared/hooks/use-account.mjs'
import { useBackend } from 'shared/hooks/use-backend.mjs'
import { useDesign } from 'shared/hooks/use-design.mjs'
// Dependencies
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
// Components
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
import { Workbench, ns as wbNs } from 'shared/components/workbench/index.mjs'
import { WorkbenchLayout } from 'site/components/layouts/workbench.mjs'
import { Null } from 'shared/components/null.mjs'
import { DynamicOrgDocs as DynamicDocs } from 'site/components/dynamic-org-docs.mjs'
// Translation namespaces used on this page
const namespaces = [...new Set(['aaron', ...wbNs, ...pageNs])]
const EditPatternPage = ({ page, id }) => {
// State
const [pattern, setPattern] = useState(false)
// Hooks
const { token } = useAccount()
const backend = useBackend(token)
const Design = useDesign(pattern?.design)
// Effect
useEffect(() => {
const getPattern = async () => {
const result = await backend.getPattern(id)
if (result.success) setPattern(result.data.pattern)
}
// Guard against loops as the backend object is recreated on each render
if (pattern === false) getPattern()
else if (pattern.id && pattern.id !== id) getPattern()
}, [id, pattern.id, backend])
const baseSettings = pattern
? {
...pattern.settings,
measurements: pattern.set ? pattern.set.measies : pattern.cset.measies,
}
: null
return (
<PageWrapper {...page} layout={WorkbenchLayout} header={Null}>
<Workbench
design={pattern.design}
from={{ type: 'pattern', data: pattern }}
{...{ Design, DynamicDocs, baseSettings }}
/>
</PageWrapper>
)
}
export default EditPatternPage
export async function getStaticProps({ locale, params }) {
return {
props: {
...(await serverSideTranslations(locale, namespaces)),
id: Number(params.id),
page: {
locale,
path: ['new', 'pattern', 'aaron', 'set', params.id],
title: '',
},
},
}
}
export async function getStaticPaths() {
return {
paths: [],
fallback: true,
}
}

View file

@ -25,7 +25,7 @@ img: Profile image
username: Username username: Username
compare: Metricset Comparison compare: Metricset Comparison
consent: Consent & Privacy consent: Consent & Privacy
control: Power versus Simplicity control: User Experience
imperial: Units imperial: Units
units: Units units: Units
apikeys: API Keys apikeys: API Keys
@ -97,7 +97,7 @@ control4d: Reveals all features, keeps handrails and safety checks.
control5t: Get out of my way control5t: Get out of my way
control5d: Reveals all features, removes all handrails and safety checks. control5d: Reveals all features, removes all handrails and safety checks.
controlShowMore: Show more options controlShowMore: Show more options
controlTitle: What do you prefer? controlTitle: Which user experience do you prefer?
# img # img
imgTitle: How about a picture? imgTitle: How about a picture?
imgDragAndDropImageHere: Drag and drop an image here imgDragAndDropImageHere: Drag and drop an image here

View file

@ -13,7 +13,7 @@ import { ContinueButton } from 'shared/components/buttons/continue-button.mjs'
export const ns = ['account', 'toast'] export const ns = ['account', 'toast']
export const ControlSettings = ({ title = false, welcome = false }) => { export const ControlSettings = ({ title = false, welcome = false, noBack = false }) => {
// Context // Context
const { startLoading, stopLoading } = useContext(LoadingContext) const { startLoading, stopLoading } = useContext(LoadingContext)
@ -50,25 +50,16 @@ export const ControlSettings = ({ title = false, welcome = false }) => {
return ( return (
<div className="max-w-xl"> <div className="max-w-xl">
{title ? <h1 className="text-4xl">{t('controlTitle')}</h1> : null} {title ? <h1 className="text-4xl">{t('controlTitle')}</h1> : null}
{[1, 2, 3, 4, 5].map((val) => { {[1, 2, 3, 4, 5].map((val) => (
if (selection === 1 && val > 2) return null <Choice val={val} t={t} update={update} current={selection} key={val}>
if (selection === 2 && val > 3) return null <span className="block text-lg leading-5">{t(`control${val}t`)}</span>
if (selection === 3 && val > 4) return null {selection > 1 ? (
if (selection === 5 && val < 4) return null <span className="block text-normal font-light normal-case pt-1 leading-5">
else {t(`control${val}d`)}
return ( </span>
<Choice val={val} t={t} update={update} current={selection} key={val}> ) : null}
<span className="block text-lg leading-5"> </Choice>
{selection === 1 && val === 2 ? t('showMore') : t(`control${val}t`)} ))}
</span>
{selection > 1 ? (
<span className="block text-normal font-light normal-case pt-1 leading-5">
{t(`control${val}d`)}
</span>
) : null}
</Choice>
)
})}
{welcome ? ( {welcome ? (
<> <>
<ContinueButton btnProps={{ href: nextHref }} link /> <ContinueButton btnProps={{ href: nextHref }} link />
@ -86,7 +77,7 @@ export const ControlSettings = ({ title = false, welcome = false }) => {
</> </>
) : null} ) : null}
</> </>
) : ( ) : noBack ? null : (
<BackToAccountButton /> <BackToAccountButton />
)} )}
</div> </div>

View file

@ -625,7 +625,6 @@ export const Patterns = ({ standAlone = false }) => {
{t('createANewPattern')} {t('createANewPattern')}
</Link> </Link>
{standAlone ? null : <BackToAccountButton />} {standAlone ? null : <BackToAccountButton />}
<pre>{JSON.stringify(patterns, null, 2)}</pre>
</div> </div>
) )
} }

View file

@ -3,7 +3,7 @@ import Link from 'next/link'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import { import {
CogIcon, CogIcon,
ControlIcon, FingerprintIcon as ControlIcon,
NewsletterIcon, NewsletterIcon,
UnitsIcon, UnitsIcon,
CompareIcon, CompareIcon,

View file

@ -0,0 +1,8 @@
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
import { Spinner } from 'shared/components/spinner.mjs'
export const ModalSpinner = ({ color = 'warning' }) => (
<ModalWrapper bare>
<Spinner className={`w-24 h-24 text-${color} animate-spin`} />
</ModalWrapper>
)

View file

@ -11,7 +11,7 @@ import {
ClearIcon, ClearIcon,
CodeIcon, CodeIcon,
CutIcon, CutIcon,
HelpIcon, FingerprintIcon,
MenuIcon, MenuIcon,
OptionsIcon, OptionsIcon,
PrintIcon, PrintIcon,
@ -20,6 +20,8 @@ import {
import { Ribbon } from 'shared/components/ribbon.mjs' import { Ribbon } from 'shared/components/ribbon.mjs'
import Link from 'next/link' import Link from 'next/link'
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs' 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'] export const ns = ['workbench', 'sections']
@ -150,8 +152,19 @@ const NavIcons = ({ setModal, setView, view }) => {
<ClearIcon className={iconSize} /> <ClearIcon className={iconSize} />
</NavButton> </NavButton>
<NavSpacer /> <NavSpacer />
<NavButton href="/account" label={t('workbench:help')} color={colors[9]}> <NavButton
<HelpIcon className={iconSize} /> label={t('workbench:control')}
color={colors[9]}
onClick={() =>
setModal(
<ModalWrapper>
<ControlSettings noBack title />
<div className="mb-3"></div>
</ModalWrapper>
)
}
>
<FingerprintIcon className={iconSize} />
</NavButton> </NavButton>
</> </>
) )

View file

@ -10,17 +10,12 @@ import { objUpdate } from 'shared/utils.mjs'
// Components // Components
import { WorkbenchHeader } from './header.mjs' import { WorkbenchHeader } from './header.mjs'
import { ErrorView } from 'shared/components/error/view.mjs' import { ErrorView } from 'shared/components/error/view.mjs'
import { ModalSpinner } from 'shared/components/modal/spinner.mjs'
// Views // Views
import { DraftView, ns as draftNs } from 'shared/components/workbench/views/draft/index.mjs' 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' import { SaveView, ns as saveNs } from 'shared/components/workbench/views/save/index.mjs'
export const ns = ['workbench', ...draftNs, ...saveNs] export const ns = ['account', 'workbench', ...draftNs, ...saveNs]
const loadDefaultSettings = ({ locale = 'en', units = 'metric' }) => ({
units,
locale,
embed: true,
})
const defaultUi = { const defaultUi = {
renderer: 'react', renderer: 'react',
@ -28,31 +23,32 @@ const defaultUi = {
const draftViews = ['draft', 'test'] const draftViews = ['draft', 'test']
export const Workbench = ({ design, Design, set = false, DynamicDocs = false }) => { export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) => {
// Hooks // Hooks
const { t, i18n } = useTranslation(ns) const { t, i18n } = useTranslation(ns)
const { language } = i18n const { language } = i18n
const { account } = useAccount() const { account } = useAccount()
const defaultSettings = loadDefaultSettings({
units: account.imperial ? 'imperial' : 'metric',
locale: language,
})
if (set) defaultSettings.measurements = set.measies
// State // State
const [view, setView] = useView() const [view, setView] = useView()
const [settings, setSettings] = useState({ ...defaultSettings, embed: true }) const [settings, setSettings] = useState({ ...baseSettings, embed: true })
const [ui, setUi] = useState({ ...defaultUi }) const [ui, setUi] = useState(defaultUi)
const [error, setError] = useState(false) const [error, setError] = useState(false)
// Effects // Effect
useEffect(() => { useEffect(() => {
if (set.measies) update.settings('measurements', set.measies) // Force re-render when baseSettings changes. Required when they are loaded async.
}, [set]) setSettings({ ...baseSettings, embed: true })
}, [baseSettings])
// Don't bother without a set or Design // Helper methods for settings/ui updates
if (!set || !Design) return null 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 // Short-circuit errors early
if (error) if (error)
@ -62,13 +58,7 @@ export const Workbench = ({ design, Design, set = false, DynamicDocs = false })
{error} {error}
</> </>
) )
console.log(baseSettings)
// Helper methods for settings/ui updates
const update = {
settings: (path, val) => setSettings(objUpdate({ ...settings }, path, val)),
ui: (path, val) => setUi(objUpdate({ ...ui }, path, val)),
}
// Deal with each view // Deal with each view
const viewProps = { const viewProps = {
account, account,
@ -108,7 +98,7 @@ export const Workbench = ({ design, Design, set = false, DynamicDocs = false })
} }
// Save view // Save view
else if (view === 'save') viewContent = <SaveView {...viewProps} /> else if (view === 'save') viewContent = <SaveView {...viewProps} from={from} />
return ( return (
<> <>

View file

@ -146,7 +146,7 @@ export const Setting = ({
return ( return (
<Collapse <Collapse
color={changed ? 'accent' : 'secondary'} color={changed ? 'accent' : 'primary'}
openTitle={<CoreTitle open {...titleProps} />} openTitle={<CoreTitle open {...titleProps} />}
title={<CoreTitle {...titleProps} />} title={<CoreTitle {...titleProps} />}
buttons={buttons} buttons={buttons}
@ -167,26 +167,27 @@ export const CoreSettings = ({
language, language,
account, account,
DynamicDocs, DynamicDocs,
control,
}) => { }) => {
// FIXME: Update this namespace // FIXME: Update this namespace
const { t } = useTranslation(['i18n', 'core-settings', design]) const { t } = useTranslation(['i18n', 'core-settings', design])
const { setModal } = useContext(ModalContext) const { setModal } = useContext(ModalContext)
// For the simplest experience, not core settings are shown at all
if (control < 2) return null
const settingsConfig = loadSettingsConfig({ const settingsConfig = loadSettingsConfig({
language, language,
control: account.control, control,
sabool: settings.sabool, sabool: settings.sabool,
parts: patternConfig.draftOrder, parts: patternConfig.draftOrder,
}) })
// Default control level is 2 (in case people are not logged in)
const control = account.control || 2
const loadDocs = DynamicDocs const loadDocs = DynamicDocs
? (evt, setting = false) => { ? (evt, setting = false) => {
evt.stopPropagation() evt.stopPropagation()
let path = `site/draft/core-settings` let path = `site/draft/core-settings`
if (setting) path += `/${setting}` if (setting) path += `/${setting}`
console.log(path)
setModal( setModal(
<ModalWrapper> <ModalWrapper>
<div className="max-w-prose"> <div className="max-w-prose">
@ -210,19 +211,20 @@ export const CoreSettings = ({
) )
return ( return (
<Collapse <>
bottom <div className="px-2 mt-8">
color="primary" {control > 4 ? (
title={ <div className="border-t border-solid border-base-300 pb-2 mx-36"></div>
<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" /> <h5 className="flex flex-row gap-2 items-center">
</div> <SettingsIcon />
} <span>{t('core-settings:coreSettings')}</span>
openTitle={t('core-settings:coreSettings')} </h5>
openButtons={openButtons} <p>{t('core-settings:coreSettings.d')}</p>
> </>
<p>{t('core-settings:coreSettings.d')}</p> )}
</div>
{Object.keys(settingsConfig) {Object.keys(settingsConfig)
.filter((name) => settingsConfig[name].control <= control) .filter((name) => settingsConfig[name].control <= control)
.map((name) => ( .map((name) => (
@ -236,6 +238,6 @@ export const CoreSettings = ({
units={settings.units} units={settings.units}
/> />
))} ))}
</Collapse> </>
) )
} }

View file

@ -173,8 +173,14 @@ export const DesignOptionGroup = ({
Option, Option,
t, t,
loadDocs, 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]) => {Object.entries(options).map(([option, type]) =>
typeof type === 'string' ? ( typeof type === 'string' ? (
<Option <Option
@ -214,6 +220,7 @@ export const DesignOptions = ({
account, account,
Option = false, Option = false,
DynamicDocs = false, DynamicDocs = false,
control,
}) => { }) => {
const { t } = useTranslation([design]) const { t } = useTranslation([design])
const { setModal } = useContext(ModalContext) const { setModal } = useContext(ModalContext)
@ -239,17 +246,18 @@ export const DesignOptions = ({
: false : false
return ( return (
<Collapse <>
bottom <div className="px-2 mt-8">
color="primary" {control > 4 ? null : (
title={ <>
<div className="w-full flex flex-row gap2 items-center justify-between"> <h5 className="flex flex-row gap-2 items-center">
<span className="font-bold">{t('design-options:designOptions')}</span> <OptionsIcon />
<OptionsIcon className="w-6 h-6 text-primary" /> <span>{t('design-options:designOptions')}</span>
</div> </h5>
} <p>{t('design-options:designOptions.d')}</p>
openTitle={t('design-options:designOptions')} </>
> )}
</div>
{Object.entries(optionsMenu).map(([group, option]) => {Object.entries(optionsMenu).map(([group, option]) =>
typeof option === 'string' ? ( typeof option === 'string' ? (
<Option <Option
@ -265,9 +273,10 @@ export const DesignOptions = ({
{...{ design, patternConfig, settings, update, group, Option, t, loadDocs }} {...{ design, patternConfig, settings, update, group, Option, t, loadDocs }}
options={option} options={option}
key={group} key={group}
topLevel
/> />
) )
)} )}
</Collapse> </>
) )
} }

View file

@ -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 { ConsoleLog } from './log.mjs'
//import { XrayReset } from './reset.mjs' //import { XrayReset } from './reset.mjs'
//import { XrayList } from './list.mjs' //import { XrayList } from './list.mjs'
import { useTranslation } from 'next-i18next' 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' import { Popout } from 'shared/components/popout.mjs'
export const ns = ['xray'] export const ns = ['xray']
export const XrayMenu = ({ design, update, settings, ui }) => { export const XrayMenu = ({ design, update, settings, ui, account, control }) => {
const { t } = useTranslation(ns) const { t } = useTranslation(ns)
if (ui.renderer !== 'react' || control < 4) return null
const toggleXray = () => update.ui(['xray', 'enabled'], ui?.xray?.enabled ? false : true) const toggleXray = () => update.ui(['xray', 'enabled'], ui?.xray?.enabled ? false : true)
return ( return (
<Collapse <>
bottom <div className="px-2 mt-8">
color="primary" {control > 4 ? (
title={ <div className="border-t border-solid border-base-300 pb-2 mx-36"></div>
<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" /> <h5 className="flex flex-row gap-2 items-center">
</div> <XrayIcon />
} <span>{t('xray:xrayPattern')}</span>
openTitle={t('xray:xrayPattern')} </h5>
openButtons={[ <p>{t('core-settings:coreSettings.d')}</p>
<button className="btn btn-xs btn-ghost px-0 z-10" onClick={(evt) => loadDocs(evt)}> </>
<HelpIcon className="w-4 h-4" /> )}
</button>, </div>
]}
>
<Popout fixme>Implement X-Ray</Popout> <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} />
// ))}

View file

@ -20,14 +20,25 @@ export const DraftMenu = ({
language, language,
account, account,
DynamicDocs, DynamicDocs,
}) => ( }) => {
<nav className="grow mb-12"> // Default control level is 2 (in case people are not logged in)
<DesignOptions const control = account.control || 2
{...{ design, patternConfig, settings, update, language, account, DynamicDocs }} const menuProps = {
/> design,
{account.control === 1 ? null : ( patternConfig,
<CoreSettings {...{ patternConfig, settings, update, language, account, DynamicDocs }} /> settings,
)} update,
{ui.renderer === 'react' && <XrayMenu {...{ ui, update, DynamicDocs }} />} language,
</nav> account,
) DynamicDocs,
control,
}
return (
<nav className="grow mb-12">
<DesignOptions {...menuProps} />
<CoreSettings {...menuProps} />
<XrayMenu {...menuProps} ui={ui} />
</nav>
)
}

View file

@ -14,17 +14,27 @@ import { Spinner } from 'shared/components/spinner.mjs'
export const ns = ['wbsave'] export const ns = ['wbsave']
const whereAreWe = (router = false, design) => { const whereAreWe = (router = false, design, from) => {
const info = {} const info = {}
if (router?.asPath) { 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 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 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 // Hooks
const { t } = useTranslation(ns) const { t } = useTranslation(ns)
const { token } = useAccount() const { token } = useAccount()
@ -111,28 +238,27 @@ export const SaveView = ({ design, setView, settings, ui, update, language, Dyna
// Context // Context
const { loading, startLoading, stopLoading } = useContext(LoadingContext) 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 ( return (
<div className="m-auto mt-24"> <div className="m-auto mt-24">
<h1 className="max-w-6xl m-auto text-center">{t('wbsave:title')}</h1> <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"> <div className="px-4 lg:px-12 flex flex-row flex-wrap gap-4 lg:gap-8 justify-around">
{info.new ? ( {info.new ? <SaveNewPattern {...saveProps} /> : null}
<SaveNewPattern {info.edit ? <SaveExistingPattern {...saveProps} from={from} /> : null}
{...{
t,
design,
settings,
info,
backend,
loading,
startLoading,
stopLoading,
toast,
router,
}}
/>
) : null}
</div> </div>
</div> </div>
) )

View file

@ -20,3 +20,4 @@ showOnlyThisPart: Show only this pattern part
partInfo: Pattern part info partInfo: Pattern part info
pathInfo: Path info pathInfo: Path info
part: Pattern part part: Pattern part
control: Experience