1
0
Fork 0

feat(shared): implemented log view

This commit is contained in:
joostdecock 2023-06-10 20:33:34 +02:00
parent 0a8cedd23f
commit 9ffb4f5261
21 changed files with 316 additions and 279 deletions

View file

@ -22,7 +22,7 @@ import { ModalThemePicker, ns as themeNs } from 'shared/components/modal/theme-p
import { ModalLocalePicker, ns as localeNs } from 'shared/components/modal/locale-picker.mjs'
import { ModalMenu } from 'site/components/navigation/modal-menu.mjs'
import { NavButton, NavSpacer, colors } from 'shared/components/workbench/header.mjs'
import { NavButton, NavSpacer, colors } from 'shared/components/header.mjs'
export const ns = ['header', 'sections', ...themeNs, ...localeNs]

View file

@ -1,6 +1,4 @@
import get from 'lodash.get'
import { useTranslation } from 'next-i18next'
import orderBy from 'lodash.orderby'
import { freeSewingConfig as conf } from 'shared/config/freesewing.config.mjs'
import { useAccount } from 'shared/hooks/use-account.mjs'
import { designs } from 'shared/config/designs.mjs'
@ -104,43 +102,9 @@ const sitePages = (t = false, control = 99) => {
return pages
}
const createCrumbs = (path, nav) =>
path.map((crumb, i) => {
const entry = get(nav, path.slice(0, i + 1), { t: 'no-title', s: path.join('/') })
const val = { t: entry.t, s: entry.s }
if (entry.o) val.o = entry.o
return val
})
const createSections = (nav) => {
const sections = {}
for (const slug of Object.keys(nav)) {
const entry = nav[slug]
const val = { t: entry.t, s: entry.s }
if (entry.o) val.o = entry.o
if (!entry.h) sections[slug] = val
}
return orderBy(sections, ['o', 't'])
}
export const useNavigation = ({ path }) => {
const { t } = useTranslation(ns)
const { account } = useAccount()
const nav = sitePages(t, account?.control)
// Create crumbs array
const crumbs = createCrumbs(path, nav)
const sections = createSections(nav)
return {
crumbs,
sections,
slug: path.join('/'),
nav: path.length > 1 ? get(nav, path[0]) : path.length === 0 ? sections : nav[path[0]],
title: crumbs.length > 0 ? crumbs.slice(-1)[0].t : '',
siteNav: nav,
}
return sitePages(t, account.control)
}

View file

@ -7,6 +7,7 @@ const colors = {
note: 'primary',
tip: 'accent',
warning: 'error',
error: 'error',
fixme: 'warning',
link: 'secondary',
related: 'info',

View file

@ -19,6 +19,7 @@ import { CutView, ns as cutNs } from 'shared/components/workbench/views/cut/inde
import { EditView, ns as editNs } from './views/edit/index.mjs'
import { TestView, ns as testNs } from 'shared/components/workbench/views/test/index.mjs'
import { ExportView, ns as exportNs } from 'shared/components/workbench/views/exporting/index.mjs'
import { LogView, ns as logNs } from 'shared/components/workbench/views/logs/index.mjs'
export const ns = [
'account',
@ -30,6 +31,7 @@ export const ns = [
...editNs,
...testNs,
...exportNs,
...logNs,
]
const defaultUi = {
@ -43,6 +45,7 @@ const views = {
export: ExportView,
edit: EditView,
test: TestView,
logs: LogView,
}
const draftViews = ['draft']
@ -87,6 +90,7 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
const viewProps = {
account,
design,
view,
setView,
update,
settings,
@ -127,6 +131,9 @@ export const Workbench = ({ design, Design, baseSettings, DynamicDocs, from }) =
// Draft the pattern or die trying
try {
pattern.draft()
const errors = [...pattern.store.logs.error]
for (const store of pattern.setStores) errors.push(...store.logs.error)
if (errors.length > 0) setView('logs')
} catch (error) {
console.log(error)
setError(<ErrorView>{JSON.stringify(error)}</ErrorView>)

View file

@ -1,48 +0,0 @@
//import { Chevron } from 'shared/components/navigation/primary'
export const XrayAttributes = ({ attr = false, t }) => {
return null
// FIXME
/*
if (!attr || !attr.list || Object.keys(attr.list).length < 1) return null
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
Attributes
</SumDiv>
<Chevron />
</Summary>
<Ul>
{Object.keys(attr.list).map((at) => (
<Li key={at}>
<Details>
<Summary>
<SumDiv>
<Deg />
{at}
</SumDiv>
<Chevron />
</Summary>
<Ul>
{attr.list[at].map((val) => (
<Li key={val}>
<NoSumDiv>
<Deg />
<span>{val === true ? t('app.yes') : val}</span>
</NoSumDiv>
</Li>
))}
</Ul>
</Details>
</Li>
))}
</Ul>
</Details>
</Li>
)
*/
}

View file

@ -1,4 +1,4 @@
export const loadSettingsConfig = (settings) => {
export const loadSettingsConfig = (settings, view) => {
const uiSettings = {
control: {
control: 1, // Show when control > 0
@ -34,6 +34,30 @@ export const loadSettingsConfig = (settings) => {
dflt: 0,
emoji: '🔬',
},
view: {
control: 3,
list: ['draft', 'test', 'print', 'cut', 'save', 'export', 'logs'],
dflt: view,
emoji: '👀',
choiceTitles: {
draft: 'draft',
test: 'test',
print: 'print',
cut: 'cut',
save: 'save',
export: 'export',
logs: 'logs',
},
valueTitles: {
draft: 'draft',
test: 'test',
print: 'print',
cut: 'cut',
save: 'save',
export: 'export',
logs: 'logs',
},
},
}
uiSettings.control.list.forEach(

View file

@ -13,13 +13,13 @@ const UiSetting = ({ name, control, ...rest }) => (
<MenuItem
{...rest}
name={name}
allowToggle={name !== 'control' && control > 3}
allowToggle={!['control', 'view'].includes(name) && control > 3}
control={control}
/>
)
export const UiSettings = ({ update, ui, control, language, DynamicDocs }) => {
const settingsConfig = loadSettingsConfig(ui)
export const UiSettings = ({ update, ui, control, language, DynamicDocs, view, setView }) => {
const settingsConfig = loadSettingsConfig(ui, view)
return (
<WorkbenchMenu
@ -37,6 +37,7 @@ export const UiSettings = ({ update, ui, control, language, DynamicDocs }) => {
name: 'uiSettings',
ns,
updateFunc: update.ui,
passProps: { view, setView },
}}
/>
)

View file

@ -15,8 +15,22 @@ export const ControlSettingInput = (props) => {
)
}
const ViewInput = (props) => {
const { selection, update } = useControlState()
props.config.dflt = props.view
return (
<ListInput
{...props}
updateFunc={(path, newVal) => props.setView(newVal)}
current={props.view}
/>
)
}
export const inputs = {
renderer: ListInput,
inspect: BoolInput,
control: ControlSettingInput,
view: ViewInput,
}

View file

@ -1,38 +0,0 @@
//import { Chevron } from 'shared/components/navigation/primary.mjs'
export const ConsoleLog = (props) => null
// FIXME
/*
(
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span>console.log()</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{['designConfig', 'patternConfig', 'gist', 'draft', 'renderProps'].map((it) => (
<Li key={it}>
<SumButton
onClick={() => {
if (it === 'designConfig') return console.log(props.design.designConfig)
if (it === 'patternConfig') return console.log(props.design.patternConfig)
if (it === 'renderProps') return console.log(props.draft.getRenderProps())
return console.log(props[it])
}}
>
<SumDiv>
<Deg />
<span>{it}</span>
</SumDiv>
</SumButton>
</Li>
))}
</Ul>
</Details>
</Li>
)
*/

View file

@ -1,71 +0,0 @@
//import { Chevron } from 'shared/components/navigation/primary'
//import { XrayPoint } from './point'
//
//const MoveLine = ({ op }) => <XrayPoint point={op.to} />
//const Curve = ({ op }) => null
/*
['cp1', 'cp2', 'to'].map((pnt) => (
<Li key={pnt}>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{pnt}</span>
</SumDiv>
<Chevron />
</Summary>
<XrayPoint point={op[pnt]} />
</Details>
</Li>
))
*/
//const XrayPathOp = ({ op }) => null
/*
* (
<Li>
{op.type === 'close' ? (
<NoSumDiv>
<Deg />
<span className="font-bold">{op.type}</span>
</NoSumDiv>
) : (
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">{op.type}</span>
<span>To</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>{op.type === 'curve' ? <Curve op={op} /> : <MoveLine op={op} />}</Ul>
</Details>
)}
</Li>
)
*/
export const XrayPathOps = ({ ops = false }) => null
/*
* {
if (!ops || ops.length < 1) return null
return (
<Li>
<Details>
<Summary>
<SumDiv>
<Deg />
<span className="font-bold">path.ops</span>
</SumDiv>
<Chevron />
</Summary>
<Ul>
{ops.map((op) => (
<XrayPathOp op={op} key={op} />
))}
</Ul>
</Details>
</Li>
)
}
*/

View file

@ -1,35 +0,0 @@
//import { formatMm } from 'shared/utils.mjs'
//import { XrayAttributes } from './attributes.mjs'
//import { XrayPathOps } from './path-ops.mjs'
export const XrayPath = ({ pathName, partName, draft, units }) => null
/*{
const path = draft?.parts?.[partName]?.paths?.[pathName]
if (!path) return null
return (
<Ul>
<XrayAttributes attr={path.attributes} />
<Li>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">path.render =</span>
<span>{JSON.stringify(path.render)}</span>
</NoSumDiv>
</Li>
<Li>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">path.length() =</span>
<span
dangerouslySetInnerHTML={{
__html: formatMm(path.length(), units),
}}
/>
</NoSumDiv>
</Li>
<XrayPathOps ops={path.ops} />
</Ul>
)
}
*/

View file

@ -1,23 +0,0 @@
//import { round } from 'shared/utils'
//import { XrayAttributes } from './attributes'
export const XrayPoint = ({ pointName, partName, draft, t }) => null
/*{
const point = draft?.parts?.[partName]?.points?.[pointName]
return point ? (
<Ul>
{['x', 'y'].map((coord) => (
<Li key={coord}>
<NoSumDiv>
<Deg />
<span className="font-bold mr-2">{coord} =</span>
<span>{round(point[coord])}</span>
</NoSumDiv>
</Li>
))}
<XrayAttributes attr={point.attributes} t={t} />
</Ul>
) : null
}
*/

View file

@ -1,18 +0,0 @@
//import { useTranslation } from 'next-i18next'
export const XrayReset = (props) => null
/*{
const { t } = useTranslation(['app'])
return (
<Li>
<SumButton onClick={() => props.updateGist(['_state', 'xray'], { enabled: true })}>
<SumDiv>
<Deg />
<span>{t(`reset`)}</span>
</SumDiv>
</SumButton>
</Li>
)
}
*/

View file

@ -16,3 +16,28 @@ inspectYes.t: Enable the inspector
inspectYes.d: With the pattern inspector enabled and the React rendering engine selected, we will add interactivity to the pattern to allow you to inspect the various elements that make up the pattern.
no: No
yes: Yes
draft: Draft
test: Test
print: Print layout
cut: Cut Layout
save: Save
export: Export
edit: Edit
draft.t: Draft your pattern
draft.d: Launches FreeSewing flagship pattern editor, where you can tweak your pattern to your heart's desire
test.t: Test your pattern
test.d: See how your pattern adapts to changes in options, or measurements
print.t: Print Layout
print.d: Allows you to arrange your pattern pieces so you can printing your pattern on as little pages as possible
cut.t: Cutting layout
cut.d: Allows you to arrange your pattern pieces so you can determine exactly how much fabric you need to make it.
save.t: Save your pattern
save.d: Save the current pattern to your FreeSewing account
export.t: Export your pattern
export.d: Allows you to export this pattern to a variety of formats
logs.t: Pattern logs
logs.d: Allows you to browse the pattern logs to see what exactly happened while drafting this pattern
edit.t: Hand-edit the pattern settings
edit.d: This allows you to hand-edit the pattern settings, giving you full control over how your pattern will be drafted
view: View
view.d: These are the various views you can pick from. Includes those views available via the navigation bar or menu, and some additional ones

View file

@ -5,4 +5,5 @@ export const values = {
renderer: ListValue,
inspect: ListValue,
control: ({ control }) => <Difficulty score={control} color="primary" />,
view: ListValue,
}

View file

@ -1,4 +1,5 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import { useTranslation } from 'next-i18next'
import { PanZoomPattern as ShowPattern } from 'shared/components/workbench/pan-zoom-pattern.mjs'
import { InspectorPattern } from './inspector/pattern.mjs'
import { DraftMenu, ns as menuNs } from './menu.mjs'
@ -16,12 +17,15 @@ export const DraftView = ({
language,
account,
DynamicDocs,
setView,
view,
}) => {
// State for inspector
const [inspect, setInspect] = useState({
show: {},
reveal: {},
})
const inspector = {
show: (data) => {
const newInspect = { ...inspect }
@ -83,6 +87,8 @@ export const DraftView = ({
DynamicDocs,
inspector,
renderProps,
view,
setView,
}}
/>
</div>

View file

@ -22,6 +22,8 @@ export const DraftMenu = ({
DynamicDocs,
inspector = false,
renderProps,
view,
setView,
}) => {
const control = account.control
const menuProps = {
@ -40,7 +42,7 @@ export const DraftMenu = ({
{ui.inspect ? <Inspector {...menuProps} {...{ ui, inspector, renderProps }} /> : null}
<DesignOptions {...menuProps} />
<CoreSettings {...menuProps} />
<UiSettings {...menuProps} ui={ui} />
<UiSettings {...menuProps} {...{ ui, view, setView }} />
</nav>
)
}

View file

@ -0,0 +1,118 @@
import { useState } from 'react'
import { useTranslation } from 'next-i18next'
import { NoIcon as ErrorIcon } from 'shared/components/icons.mjs'
import { WebLink } from 'shared/components/web-link.mjs'
const knownVars = [
'sa',
'Path',
'Point',
'Snippet',
'paths',
'points',
'snippets',
'absoluteOptions',
'complete',
'measurements',
'options',
'paperless',
'sa',
'scale',
'context',
'getId',
'hide',
'log',
'macro',
'setHidden',
'store',
'unhide',
'units',
'utils',
'Bezier',
'part',
]
export const analyzeDraftLogLine = ({ type, line, t }) => {
if (type === 'error' && line.stack) return <DraftError err={line} t={t} />
return null
}
const Code = ({ children }) => <code className="font-mono font-bold px-1 rounded">{children}</code>
const ShowStackButton = ({
setDetails,
details,
t,
txt = 'click here to show the stack trace',
}) => (
<button className="text-secondary font-medium" onClick={() => setDetails(!details)}>
{txt}
</button>
)
const NotDestructured = ({ missing, err, setDetails, details, t }) => (
<div>
This most likely means that <Code>{missing}</Code> was not desctructured as a draft method
parameter.
<br />
See{' '}
<WebLink
href="https://freesewing.dev/reference/api/part/draft"
txt="the draft method documentation"
/>{' '}
or <ShowStackButton {...{ setDetails, details, t }} />.
</div>
)
const DesignsVarUndefined = ({ missing, err, t }) => (
<div key={missing}>
We were unable to draft this pattern because <Code>{missing}</Code> is undefined in{' '}
<Code>{err.stack.split('\n')[0].split('/designs/').pop()}</Code>
</div>
)
const OtherVarUndefined = ({ missing, err, t }) => (
<div key={missing}>
The <Code>{missing}</Code> variable is undefined.
<br />
Check <Code>{stack[0]}</Code> for details or <ShowStackButton {...{ setDetails, details, t }} />
.
</div>
)
const DraftError = ({ err, t }) => {
const [details, setDetails] = useState(false)
const data = []
const stack = err.stack.split('\n')
// Leave this here, it's intentional. We log the error to you can inpect it.
console.log(err)
const msg = err.toString()
if (err.name === 'ReferenceError') {
if (err.message.includes('is not defined')) {
const missing = err.message.split(' ').shift()
if (stack[0].includes('/designs/')) {
data.push(<DesignsVarUndefined {...{ err, missing, t }} />)
if (knownVars.includes(missing))
data.push(<NotDestructured key="nd" {...{ details, setDetails, err, missing, t }} />)
} else data.push(<OtherVarUndefined {...{ err, missing, t }} />)
}
}
return (
<>
{data}
{details ? (
<>
<h6>{t('stackTrace')}</h6>
<ol className="list list-inside list-decimal text-sm font-mono">
{stack.map((line, i) => (
<li key={i}>{line}</li>
))}
</ol>
</>
) : null}
</>
)
}

View file

@ -0,0 +1,95 @@
import { useTranslation } from 'next-i18next'
import { analyzeDraftLogLine } from './errors.mjs'
export const ns = ['logs']
const colors = {
error: 'error',
warning: 'warning',
info: 'secondary',
debug: 'base',
}
const DraftLogEntry = ({ type, line, t }) => {
// Some lines are arrays, handle each entry individually
if (Array.isArray(line))
return line.reverse().map((l, key) => <DraftLogEntry line={l} {...{ t, type, key }} />)
let title = 'Unsure how to treat this error'
const data = []
// Simple error string
if (typeof line === 'string') title = line
if (typeof line === 'object') {
data.push(analyzeDraftLogLine({ type, line, t }))
title = line.toString()
}
return (
<div className={`relative my-2 bg-${colors[type]} bg-opacity-5 -ml-4 -mr-4 sm:ml-0 sm:mr-0`}>
<div
className={`
lg:border-0 lg:border-l-4 px-4
shadow text-base border-${colors[type]}
flex flex-col items-start
`}
>
<div className={`flex flex-row gap-1 py-1`}>
<span className={`hidden lg:block font-bold uppercase text-${colors[type]}`}>{type}</span>
<span className={`hidden lg:block font-bold text-${colors[type]}`}>|</span>
<span className="font-medium px-2 lg:px-0">{title}</span>
</div>
<div className="popout-content pl-2">{data}</div>
</div>
</div>
)
}
const DraftLogs = ({ type, t, lines = [] }) =>
lines.length > 0 ? (
<>
<h3>{t(type)}</h3>
{lines.reverse().map((line, key) => (
<DraftLogEntry {...{ type, key, line, t }} />
))}
</>
) : null
const extractLogs = (pattern) => {
const logs = {}
for (const type of ['error', 'warning', 'info', 'debug']) {
logs[type] = [...pattern.store.logs[type]]
for (const store of pattern.setStores) logs[type].push(...store.logs[type])
}
return logs
}
export const LogView = ({
pattern,
patternConfig,
settings,
ui,
update,
language,
account,
DynamicDocs,
}) => {
const { t } = useTranslation(ns)
try {
pattern.draft(settings)
} catch (err) {
console.log(err)
}
const logs = extractLogs(pattern)
return (
<div className="max-w-4xl mx-auto px-4 pb-8">
<h1>{t('logs')}</h1>
{Object.entries(logs).map(([type, lines], key) => (
<DraftLogs {...{ type, lines, t, key }} />
))}
</div>
)
}

View file

@ -0,0 +1,11 @@
logs: Logs
error: Error messages
warning: Warning messages
info: Info messages
debug: Debug messages
seeLinkOrClick: See {{ link }} or { click }
notDesctructured: This most likely means that { var } was not desctructured as a draft method parameter.
designVarUndefined: We were unable to draft this pattern because { var } is undefined in { file }
otherVarUndefined: The { var } variable is undefined.
checkForDetailsOrClick: Check { file } for details or { click }.
stacktrace: Stack trace

View file

@ -255,8 +255,9 @@ export const nsMerge = (...args) => {
const ns = new Set()
for (const arg of args) {
if (typeof arg === 'string') ns.add(arg)
else if (Array.isArray(arg)) ns.add(nsMerge(...arg))
else console.log('Unexpected namespect type:', { arg })
else if (Array.isArray(arg)) {
for (const el of nsMerge(...arg)) ns.add(el)
} else console.log('Unexpected namespect type:', { arg })
}
return [...ns]