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 { 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
|
||||||
|
|
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
|
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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
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,
|
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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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} />
|
|
||||||
// ))}
|
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue