Skip to content

Commit 141729b

Browse files
committed
optimized dev demo; useEditorTheme overwrites; add keyword dense;
1 parent c8ef29a commit 141729b

File tree

10 files changed

+342
-59
lines changed

10 files changed

+342
-59
lines changed

packages/demo/public/index.html

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,22 @@
1010
<!--<link rel="manifest" href="<%= htmlWebpackPlugin.options.PUBLIC_URL %>/manifest.json"/>-->
1111
<title>React App</title>
1212
<style>
13-
html,
14-
body {
13+
body, html {
14+
height: 100%;
1515
margin: 0;
16-
box-sizing: border-box;
16+
padding: 0;
17+
}
18+
19+
#root {
20+
overflow: auto;
21+
height: 100%;
22+
display: flex;
23+
flex-direction: column
24+
}
25+
26+
body {
27+
display: flex;
28+
flex-direction: column
1729
}
1830

1931
*, ::before, ::after {

packages/demo/src/components/CustomCodeMirror.tsx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@ import { useEditorTheme } from '@ui-schema/material-code/useEditorTheme'
1616
import { useHighlightStyle } from '@ui-schema/material-code/useHighlightStyle'
1717
import { CodeMirrorComponentProps, CodeMirror, CodeMirrorProps } from '@ui-schema/kit-codemirror/CodeMirror'
1818
import { useExtension } from '@ui-schema/kit-codemirror/useExtension'
19+
import { MuiCodeMirrorStyleProps } from '@ui-schema/material-code'
1920

20-
export const CustomCodeMirror: React.FC<CodeMirrorComponentProps> = (
21+
export const CustomCodeMirror: React.FC<CodeMirrorComponentProps & MuiCodeMirrorStyleProps> = (
2122
{
2223
// values we want to override in this component
2324
value, extensions, effects,
25+
dense, variant,
2426
// everything else is just passed down
2527
...props
2628
},
2729
) => {
2830
const {onChange} = props
29-
const theme = useEditorTheme(typeof onChange === 'undefined')
31+
const theme = useEditorTheme(typeof onChange === 'undefined', dense, variant)
3032
const highlightStyle = useHighlightStyle()
3133
const {init: initHighlightExt, effects: effectsHighlightExt} = useExtension(
3234
() => syntaxHighlighting(highlightStyle || defaultHighlightStyle, {fallback: true}),
@@ -36,7 +38,6 @@ export const CustomCodeMirror: React.FC<CodeMirrorComponentProps> = (
3638
() => theme,
3739
[theme],
3840
)
39-
const themeCompartment = React.useRef<Compartment>(new Compartment())
4041
const effectsRef = React.useRef<((editor: EditorView) => void)[]>(effects || [])
4142

4243
const extensionsAll: Extension[] = React.useMemo(() => [
@@ -70,23 +71,16 @@ export const CustomCodeMirror: React.FC<CodeMirrorComponentProps> = (
7071
]),
7172
initHighlightExt(),
7273
initThemeExt(),
73-
// themeCompartment.current.of(themeRef.current),// only initial theme here to not re-create extensions
7474
...(extensions || []),
7575
], [extensions, initHighlightExt, initThemeExt])
7676

77+
// attach parent plugin effects first
7778
React.useMemo(() => {
7879
if(!effects) return
7980
effectsRef.current.push(...effects)
8081
}, [effects])
81-
React.useMemo(() => {
82-
effectsRef.current.push(
83-
function updateTheme(editor) {
84-
editor.dispatch({
85-
effects: themeCompartment.current.reconfigure(theme),
86-
})
87-
},
88-
)
89-
}, [theme])
82+
83+
// attach each plugin effect separately (thus only the one which changes get reconfigured)
9084
React.useMemo(() => {
9185
effectsRef.current.push(...effectsHighlightExt)
9286
}, [effectsHighlightExt])

packages/demo/src/components/Layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { PageDemoWidget } from '../pages/PageDemoWidget'
55
import { PageDemoLangSelectable } from '../pages/PageDemoLangSelectable'
66
import { PageDemoComponent } from '../pages/PageDemoComponent'
77
import { Button } from '@mui/material'
8+
import { PageDemoComponentMui } from '../pages/PageDemoComponentMui'
89

910
export const Layout: React.ComponentType<{ setTheme: React.Dispatch<React.SetStateAction<'dark' | 'light'>> }> = ({setTheme}) => {
1011
const scrollWrapper = React.useRef<HTMLDivElement | null>(null)
@@ -14,6 +15,7 @@ export const Layout: React.ComponentType<{ setTheme: React.Dispatch<React.SetSta
1415
style={{
1516
display: 'flex',
1617
flexDirection: 'column',
18+
flexGrow: 1,
1719
maxHeight: '100%',
1820
position: 'relative',
1921
overflow: 'auto',
@@ -23,6 +25,7 @@ export const Layout: React.ComponentType<{ setTheme: React.Dispatch<React.SetSta
2325
<Routes>
2426
<Route path={'/'} element={<PageHome/>}/>
2527
<Route path={'/component'} element={<PageDemoComponent/>}/>
28+
<Route path={'/component-mui'} element={<PageDemoComponentMui/>}/>
2629
<Route path={'/widget'} element={<PageDemoWidget/>}/>
2730
<Route path={'/selectable'} element={<PageDemoLangSelectable/>}/>
2831
</Routes>

packages/demo/src/components/Nav.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export const Nav: React.FC<{}> = () => {
1111
return <Box style={{display: 'flex', flexDirection: 'column', flexShrink: 0}}>
1212
<MuiList>
1313
<ListItemButton onClick={() => navigate('/component')} selected={'/component' === location.pathname}>
14-
<ListItemText primary={'Component'}/>
14+
<ListItemText primary={'Component Plain'}/>
15+
</ListItemButton>
16+
<ListItemButton onClick={() => navigate('/component-mui')} selected={'/component-mui' === location.pathname}>
17+
<ListItemText primary={'Component MUI'}/>
1518
</ListItemButton>
1619
<ListItemButton onClick={() => navigate('/widget')} selected={'/widget' === location.pathname}>
1720
<ListItemText primary={'Widget'}/>
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import React from 'react'
2+
import Container from '@mui/material/Container'
3+
import Box from '@mui/material/Box'
4+
import Link from '@mui/material/Link'
5+
import Typography from '@mui/material/Typography'
6+
import { Nav } from '../components/Nav'
7+
import { CodeMirrorOnChange } from '@ui-schema/kit-codemirror/useCodeMirror'
8+
import { CodeMirrorComponentProps, CodeMirror, CodeMirrorProps } from '@ui-schema/kit-codemirror/CodeMirror'
9+
import {
10+
lineNumbers, highlightActiveLineGutter, highlightSpecialChars,
11+
drawSelection, dropCursor,
12+
rectangularSelection, highlightActiveLine, keymap, EditorView,
13+
// crosshairCursor,
14+
} from '@codemirror/view'
15+
import { foldGutter, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language'
16+
import { history, defaultKeymap, historyKeymap, indentWithTab } from '@codemirror/commands'
17+
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
18+
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete'
19+
import { lintKeymap } from '@codemirror/lint'
20+
import { Compartment, EditorState, Extension } from '@codemirror/state'
21+
import { json } from '@codemirror/lang-json'
22+
import { javascript } from '@codemirror/lang-javascript'
23+
import { html } from '@codemirror/lang-html'
24+
import { css } from '@codemirror/lang-css'
25+
import { MuiCodeMirrorStyleProps, useEditorTheme, useHighlightStyle } from '@ui-schema/material-code'
26+
import { useExtension } from '@ui-schema/kit-codemirror'
27+
28+
export const PageDemoComponentMui: React.ComponentType = () => {
29+
return <>
30+
<Container maxWidth={'md'} fixed style={{display: 'flex', flexGrow: 1, overflow: 'auto'}}>
31+
<Nav/>
32+
<Box mx={2} py={1} style={{display: 'flex', flexDirection: 'column', overflow: 'auto', flexGrow: 1}}>
33+
<Box mb={2}>
34+
<Typography variant={'h1'} gutterBottom>Component</Typography>
35+
<Typography variant={'body1'}>Plain React components demo, no UI-Schema widgets, using Material-UI styling with <code>@mui</code>.</Typography>
36+
<Typography variant={'body1'}>Page filling style, where the scrollable area is the CodeMirror Editor, use <code>variant: &qout;embed&qout;</code> to deactivate outlined/borders-radius.</Typography>
37+
<Typography variant={'body1'}>
38+
<Link
39+
href={'https://github.com/ui-schema/react-codemirror/blob/main/packages/demo/src/pages/PageDemoComponentMui.tsx'}
40+
target={'_blank'} rel={'noopener noreferrer'}
41+
>demo source</Link>
42+
</Typography>
43+
</Box>
44+
<Box
45+
mb={2}
46+
style={{
47+
display: 'flex',
48+
flexDirection: 'column',
49+
overflow: 'auto',
50+
flexGrow: 1,
51+
}}
52+
>
53+
<Typography variant={'h2'} gutterBottom>Content Editor</Typography>
54+
<DemoComponent/>
55+
</Box>
56+
</Box>
57+
</Container>
58+
</>
59+
}
60+
61+
export const CustomCodeMirror: React.FC<CodeMirrorComponentProps & MuiCodeMirrorStyleProps> = (
62+
{
63+
value,
64+
onChange,
65+
// MUI style props
66+
dense, variant,
67+
// make this a reusable `CodeMirror` component
68+
// otherwise use `Pick<CodeMirrorComponentProps, 'value' | 'onChange'>` as props
69+
extensions,
70+
effects,
71+
...props
72+
},
73+
) => {
74+
const [format] = React.useState('html')
75+
76+
// using a "direct plugin integration with reconfigure support"
77+
const theme = useEditorTheme(typeof onChange === 'undefined', dense, variant)
78+
const themeRef = React.useRef<Extension>(theme)
79+
const themeCompartment = React.useRef<Compartment>(new Compartment())
80+
81+
// using the `useExtension` hook to help with compartment plugins:
82+
const highlightStyle = useHighlightStyle()
83+
const {init: initHighlightExt, effects: effectsHighlightExt} = useExtension(
84+
() => syntaxHighlighting(highlightStyle || defaultHighlightStyle, {fallback: true}),
85+
[highlightStyle],
86+
)
87+
88+
const effectsRef = React.useRef<((editor: EditorView) => void)[]>(effects || [])
89+
90+
const extensionsAll = React.useMemo(() => [
91+
lineNumbers(),
92+
highlightActiveLineGutter(),
93+
highlightSpecialChars(),
94+
history(),
95+
foldGutter(),
96+
drawSelection(),
97+
dropCursor(),
98+
EditorState.allowMultipleSelections.of(true),
99+
indentOnInput(),
100+
syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
101+
bracketMatching(),
102+
closeBrackets(),
103+
autocompletion(),
104+
rectangularSelection(),
105+
// crosshairCursor(),
106+
highlightActiveLine(),
107+
highlightSelectionMatches(),
108+
new Compartment().of(EditorState.tabSize.of(4)),
109+
keymap.of([
110+
...closeBracketsKeymap,
111+
...defaultKeymap,
112+
...searchKeymap,
113+
...historyKeymap,
114+
...foldKeymap,
115+
...completionKeymap,
116+
...lintKeymap,
117+
indentWithTab,
118+
]),
119+
themeCompartment.current.of(themeRef.current),// only initial theme here to not re-create extensions
120+
initHighlightExt(),
121+
...(format === 'json' ? [json()] : []),
122+
...(format === 'javascript' ? [javascript()] : []),
123+
...(format === 'html' ? [html()] : []),
124+
...(format === 'css' ? [css()] : []),
125+
...(extensions || []),
126+
], [extensions, format, initHighlightExt])
127+
128+
// attach parent plugin effects first
129+
React.useMemo(() => {
130+
if(!effects) return
131+
effectsRef.current.push(...effects)
132+
}, [effects])
133+
134+
// attach each plugin effect separately (thus only the one which changes get reconfigured)
135+
React.useMemo(() => {
136+
// without `useExtension` you get direct access to the current `editor` inside of the effect
137+
// to otherwise access `editor`, you can't use the component `CodeMirror` but must use the hook `useCodeMirror`
138+
effectsRef.current.push(
139+
function updateTheme(editor) {
140+
editor.dispatch({
141+
effects: themeCompartment.current.reconfigure(theme),
142+
})
143+
},
144+
)
145+
}, [theme])
146+
147+
React.useMemo(() => {
148+
effectsRef.current.push(...effectsHighlightExt)
149+
}, [effectsHighlightExt])
150+
151+
const onViewLifecycle: CodeMirrorProps['onViewLifecycle'] = React.useCallback((view) => {
152+
console.log('on-view-lifecycle', view)
153+
}, [])
154+
155+
return <CodeMirror
156+
value={value || ''}
157+
extensions={extensionsAll}
158+
onChange={onChange}
159+
onViewLifecycle={onViewLifecycle}
160+
effects={effectsRef.current.splice(0, effectsRef.current.length)}
161+
{...props}
162+
/>
163+
}
164+
165+
const initialHtml = `<!DOCTYPE html>
166+
<html lang="en">
167+
<head>
168+
<meta charset="utf-8"/>
169+
<title>React App</title>
170+
<style>
171+
body, html {
172+
height: 100%;
173+
margin: 0;
174+
padding: 0;
175+
}
176+
</style>
177+
</head>
178+
<body>
179+
<h1>Lorem ipsum</h1>
180+
<p>Dolor sit amet, consectutor adipisci.</p>
181+
</body>
182+
</html>`
183+
184+
const DemoComponent = () => {
185+
const [variant] = React.useState('standard')
186+
const [value, setValue] = React.useState(initialHtml)
187+
188+
const onChange: CodeMirrorOnChange = React.useCallback((editor, newValue) => {
189+
if(!editor.docChanged || typeof newValue !== 'string') {
190+
return
191+
}
192+
setValue(newValue)
193+
}, [setValue])
194+
195+
return <React.Fragment>
196+
<CustomCodeMirror
197+
value={value}
198+
onChange={onChange}
199+
style={{
200+
display: 'flex',
201+
flexGrow: 1,
202+
overflow: 'auto',
203+
...(variant === 'standard' ? {
204+
padding: 2,// otherwise outline won't work (or use some hacky negative margin tricks etc.)
205+
} : {}),
206+
}}
207+
/>
208+
</React.Fragment>
209+
}

packages/demo/src/pages/PageDemoWidget.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ const schema = createOrderedMap({
5858
format: 'json',
5959
title: 'JSON',
6060
},
61+
json_dense: {
62+
type: 'string',
63+
widget: 'Code',
64+
format: 'json',
65+
title: 'JSON [view dense]',
66+
view: {
67+
dense: true,
68+
},
69+
},
6170
no_title: {
6271
type: 'string',
6372
widget: 'Code',

packages/kit-codemirror/src/CodeMirror/CodeMirror.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export interface CodeMirrorComponentProps {
1010
extensions?: Extension[]
1111
classNamesContent?: string[]
1212
effects?: ((editor: EditorView) => void)[]
13+
style?: React.CSSProperties
1314
}
1415

1516
export interface CodeMirrorProps extends CodeMirrorComponentProps {
@@ -18,7 +19,6 @@ export interface CodeMirrorProps extends CodeMirrorComponentProps {
1819
// - when editor was created, will be called with `undefined` after the editor is destroyed OR on unmount
1920
onViewLifecycle?: (editor: EditorView | undefined) => void
2021
className?: string
21-
style?: React.CSSProperties
2222
}
2323

2424
export const CodeMirror: React.FC<CodeMirrorProps> = (

packages/material-code/src/WidgetCode/WidgetCode.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import { CodeMirrorComponentProps } from '@ui-schema/kit-codemirror/CodeMirror'
44
import { CodeMirrorOnChange } from '@ui-schema/kit-codemirror/useCodeMirror'
55
import { Extension } from '@codemirror/state'
66
import { CodeBar, CodeBarProps } from '@ui-schema/material-code/CodeBar'
7-
import { Box } from '@mui/material'
7+
import Box from '@mui/material/Box'
8+
import { TextFieldProps } from '@mui/material/TextField'
89
import FormLabel from '@mui/material/FormLabel'
910
import { ValidityHelperText } from '@ui-schema/ds-material/Component/LocaleHelperText'
1011

12+
export interface MuiCodeMirrorStyleProps {
13+
dense?: boolean
14+
variant?: TextFieldProps['variant'] | 'embed'
15+
}
16+
1117
export interface WidgetCodeProps {
12-
CodeMirror: React.FC<CodeMirrorComponentProps>
18+
CodeMirror: React.FC<CodeMirrorComponentProps & MuiCodeMirrorStyleProps>
1319
extensions?: Extension[]
1420
barBegin?: React.ReactElement
1521
barContent?: React.ReactElement
@@ -71,6 +77,7 @@ export const WidgetCode: React.ComponentType<WidgetProps & WithScalarValue & Wid
7177
onChange={readOnly ? undefined : handleOnChange}
7278
extensions={extensions}
7379
classNamesContent={classNamesContent}
80+
dense={schema.getIn(['view', 'dense']) as boolean}
7481
/>
7582

7683
<ValidityHelperText errors={errors} showValidity={showValidity} schema={schema}/>

packages/material-code/src/WidgetCodeSelectable/WidgetCodeSelectable.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export const WidgetCodeSelectable: React.ComponentType<WidgetProps & WithScalarV
8484
onChange={readOnly ? undefined : handleOnChange}
8585
extensions={extensions}
8686
classNamesContent={classNamesContent}
87+
dense={schema.getIn(['view', 'dense']) as boolean}
8788
/>
8889

8990
<ValidityHelperText errors={errors} showValidity={showValidity} schema={schema}/>

0 commit comments

Comments
 (0)