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
|
@ -2,6 +2,7 @@ import { log } from '../utils/log.mjs'
|
|||
import { capitalize } from '../utils/index.mjs'
|
||||
import { setPatternAvatar } from '../utils/sanity.mjs'
|
||||
import yaml from 'js-yaml'
|
||||
import { SetModel } from './set.mjs'
|
||||
|
||||
export function PatternModel(tools) {
|
||||
this.config = tools.config
|
||||
|
@ -11,6 +12,7 @@ export function PatternModel(tools) {
|
|||
this.rbac = tools.rbac
|
||||
this.encryptedFields = ['data', 'img', 'name', 'notes', 'settings']
|
||||
this.clear = {} // For holding decrypted data
|
||||
this.Set = new SetModel(tools)
|
||||
|
||||
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.set) {
|
||||
if (this.record.set.measies) this.record.set.measies = JSON.parse(this.record.set.measies)
|
||||
if (this.record?.set) {
|
||||
this.record.set = this.Set.revealSet(this.record.set)
|
||||
}
|
||||
|
||||
return this
|
||||
|
|
77
sites/org/pages/patterns/[id]/edit.mjs
Normal file
77
sites/org/pages/patterns/[id]/edit.mjs
Normal 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,
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ img: Profile image
|
|||
username: Username
|
||||
compare: Metricset Comparison
|
||||
consent: Consent & Privacy
|
||||
control: Power versus Simplicity
|
||||
control: User Experience
|
||||
imperial: Units
|
||||
units: Units
|
||||
apikeys: API Keys
|
||||
|
@ -97,7 +97,7 @@ control4d: Reveals all features, keeps handrails and safety checks.
|
|||
control5t: Get out of my way
|
||||
control5d: Reveals all features, removes all handrails and safety checks.
|
||||
controlShowMore: Show more options
|
||||
controlTitle: What do you prefer?
|
||||
controlTitle: Which user experience do you prefer?
|
||||
# img
|
||||
imgTitle: How about a picture?
|
||||
imgDragAndDropImageHere: Drag and drop an image here
|
||||
|
|
|
@ -13,7 +13,7 @@ import { ContinueButton } from 'shared/components/buttons/continue-button.mjs'
|
|||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const ControlSettings = ({ title = false, welcome = false }) => {
|
||||
export const ControlSettings = ({ title = false, welcome = false, noBack = false }) => {
|
||||
// Context
|
||||
const { startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
|
@ -50,25 +50,16 @@ export const ControlSettings = ({ title = false, welcome = false }) => {
|
|||
return (
|
||||
<div className="max-w-xl">
|
||||
{title ? <h1 className="text-4xl">{t('controlTitle')}</h1> : null}
|
||||
{[1, 2, 3, 4, 5].map((val) => {
|
||||
if (selection === 1 && val > 2) return null
|
||||
if (selection === 2 && val > 3) return null
|
||||
if (selection === 3 && val > 4) return null
|
||||
if (selection === 5 && val < 4) return null
|
||||
else
|
||||
return (
|
||||
{[1, 2, 3, 4, 5].map((val) => (
|
||||
<Choice val={val} t={t} update={update} current={selection} key={val}>
|
||||
<span className="block text-lg leading-5">
|
||||
{selection === 1 && val === 2 ? t('showMore') : t(`control${val}t`)}
|
||||
</span>
|
||||
<span className="block text-lg leading-5">{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 ? (
|
||||
<>
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
|
@ -86,7 +77,7 @@ export const ControlSettings = ({ title = false, welcome = false }) => {
|
|||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
) : noBack ? null : (
|
||||
<BackToAccountButton />
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -625,7 +625,6 @@ export const Patterns = ({ standAlone = false }) => {
|
|||
{t('createANewPattern')}
|
||||
</Link>
|
||||
{standAlone ? null : <BackToAccountButton />}
|
||||
<pre>{JSON.stringify(patterns, null, 2)}</pre>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import Link from 'next/link'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import {
|
||||
CogIcon,
|
||||
ControlIcon,
|
||||
FingerprintIcon as ControlIcon,
|
||||
NewsletterIcon,
|
||||
UnitsIcon,
|
||||
CompareIcon,
|
||||
|
|
8
sites/shared/components/modal/spinner.mjs
Normal file
8
sites/shared/components/modal/spinner.mjs
Normal 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>
|
||||
)
|
|
@ -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}
|
||||
>
|
||||
<>
|
||||
<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 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>
|
||||
}
|
||||
openTitle={t('design-options:designOptions')}
|
||||
>
|
||||
{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>,
|
||||
]}
|
||||
>
|
||||
<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>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</Collapse>
|
||||
</div>
|
||||
<Popout fixme>Implement X-Ray</Popout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
//<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,
|
||||
}) => (
|
||||
}) => {
|
||||
// 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
|
||||
{...{ 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 }} />}
|
||||
<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) {
|
||||
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.new = chunks[1] === 'new'
|
||||
info.design = chunks[3]
|
||||
info.from = chunks[4]
|
||||
info.fromId = chunks[5].split('#').shift()
|
||||
info.locale = router.locale
|
||||
}
|
||||
if (info.design !== design) throw 'Design passed to view does not match URL state'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
@ -101,25 +111,7 @@ const SaveNewPattern = ({
|
|||
)
|
||||
}
|
||||
|
||||
export const SaveView = ({ design, setView, settings, ui, update, language, DynamicDocs }) => {
|
||||
// Hooks
|
||||
const { t } = useTranslation(ns)
|
||||
const { token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
const info = whereAreWe(router, design)
|
||||
|
||||
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
|
||||
{...{
|
||||
const SaveExistingPattern = ({
|
||||
t,
|
||||
design,
|
||||
settings,
|
||||
|
@ -130,9 +122,143 @@ export const SaveView = ({ design, setView, settings, ui, update, language, Dyna
|
|||
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')}
|
||||
/>
|
||||
) : null}
|
||||
<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()
|
||||
const backend = useBackend(token)
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
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 {...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