Skip to content

Commit 8dcc849

Browse files
authored
Merge pull request #1469 from processing/bigsketches
Add scrollable and loadable from file EditableSketches
2 parents 86f26f7 + edbd91b commit 8dcc849

5 files changed

Lines changed: 66 additions & 11 deletions

File tree

‎src/components/CodeEmbed/index.jsx‎

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Icon } from "../Icon";
2929
export const CodeEmbed = (props) => {
3030
const { ref: liveRegionRef, announce } = useLiveRegion();
3131
const [rendered, setRendered] = useState(false);
32+
3233
const initialCode = props.initialValue ?? "";
3334
// Source code from Google Docs sometimes uses a unicode non-breaking space
3435
// instead of a normal one, but these break the code frame, so we replace them here.
@@ -54,6 +55,9 @@ export const CodeEmbed = (props) => {
5455
}
5556

5657
const codeFrameRef = useRef(null);
58+
const editorRef = useRef(null);
59+
const isScrollable = props.scrollable ?? false;
60+
const [hasScrollbar, setHasScrollbar] = useState(false);
5761

5862
const updateOrReRun = () => {
5963
if (codeString === previewCodeString) {
@@ -79,6 +83,18 @@ export const CodeEmbed = (props) => {
7983
}
8084
}, []);
8185

86+
// only observe scroll when scrollable
87+
useEffect(() => {
88+
if (!rendered || !isScrollable) return;
89+
const scroller = editorRef.current?.querySelector(".cm-scroller");
90+
if (!scroller) return;
91+
const observer = new ResizeObserver(() => {
92+
setHasScrollbar(scroller.scrollHeight > scroller.clientHeight);
93+
});
94+
observer.observe(scroller);
95+
return () => observer.disconnect();
96+
}, [rendered, isScrollable]);
97+
8298
if (!rendered) return <div className="code-placeholder" />;
8399

84100
return (
@@ -92,6 +108,8 @@ export const CodeEmbed = (props) => {
92108
<div>
93109
<CodeFrame
94110
jsCode={previewCodeString}
111+
cssCode={props.cssCode}
112+
htmlBodyCode={props.htmlBodyCode}
95113
width={previewWidth}
96114
height={previewHeight}
97115
base={props.base}
@@ -125,11 +143,12 @@ export const CodeEmbed = (props) => {
125143
</div>
126144
</div>
127145
) : null}
128-
<div className="code-editor-container relative w-full">
146+
<div ref={editorRef} className="code-editor-container relative w-full">
129147
<CodeMirror
130148
value={codeString}
131149
theme="light"
132150
width="100%"
151+
height={isScrollable ? "300px" : undefined}
133152
minimalSetup={{
134153
highlightSpecialChars: false,
135154
history: false,
@@ -139,19 +158,27 @@ export const CodeEmbed = (props) => {
139158
historyKeymap: true,
140159
}}
141160
basicSetup={{
142-
lineNumbers: false,
143-
foldGutter: false,
161+
lineNumbers: hasScrollbar,
162+
foldGutter: isScrollable,
144163
autocompletion: false,
145164
}}
146165
indentWithTab={false}
147-
extensions={[javascript(), EditorView.lineWrapping]}
166+
extensions={[javascript(), EditorView.lineWrapping, EditorView.theme({
167+
".cm-scroller": {
168+
overflowY: isScrollable ? "auto" : "visible",
169+
},
170+
})]}
148171
onChange={(val) => setCodeString(val)}
149172
editable={props.editable}
150173
onCreateEditor={(editorView) =>
151174
(editorView.contentDOM.ariaLabel = "Code Editor")
152175
}
153176
/>
154-
<div className="absolute right-0 top-0 flex flex-col gap-xs p-xs md:flex-row">
177+
<div
178+
className={`absolute top-0 flex flex-col gap-xs p-xs md:flex-row ${
179+
isScrollable && hasScrollbar ? "right-[25px]" : "right-0"
180+
}`}
181+
>
155182
<CopyCodeButton textToCopy={codeString || initialCode} />
156183
<CircleButton
157184
onClick={() => {

‎src/components/EditableSketch/index.astro‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,42 @@ import CodeEmbed from "../CodeEmbed";
33
44
interface Props {
55
code: string;
6+
scrollable?: boolean;
7+
cssCode?: string;
8+
bodyCode?: string;
69
width?: number;
710
height?: number;
811
base?: string;
912
scripts?: string[];
1013
}
1114
12-
const { props } = Astro;
15+
const props = Astro.props as Props;
1316
1417
let previewWidth = props.width;
1518
let previewHeight = props.height;
16-
const match = /createCanvas\(\s*(\d+),\s*(\d+)\s*(?:,\s*(?:P2D|WEBGL)\s*)?\)/m.exec(props.code);
19+
const match = /createCanvas\(\s*(\d+),\s*(\d+)\s*(?:,\s*(?:P2D|WEBGL|WEBGPU)\s*)?\)/m.exec(props.code);
1720
if (match) {
1821
previewWidth = previewWidth || parseFloat(match[1]);
1922
previewHeight = previewHeight || parseFloat(match[2]);
2023
}
2124
2225
const vertical = !previewWidth || previewWidth > 200
26+
const scrollable = props.scrollable ?? false;
2327
2428
---
2529

26-
<CodeEmbed client:load initialValue={props.code.trim()} previewWidth={previewWidth} previewHeight={previewHeight} base={props.base} previewable editable allowSideBySide={!vertical} fullWidth={!vertical} scripts={props.scripts} />
30+
<CodeEmbed
31+
client:load
32+
initialValue={props.code.trim()}
33+
previewWidth={previewWidth}
34+
previewHeight={previewHeight}
35+
base={props.base}
36+
previewable
37+
editable
38+
allowSideBySide={!vertical}
39+
fullWidth={!vertical}
40+
scripts={props.scripts}
41+
cssCode={props.cssCode}
42+
htmlBodyCode={props.bodyCode}
43+
scrollable={scrollable}
44+
/>

‎src/components/SketchEmbed/index.astro‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { CodeFrame } from "../CodeEmbed/frame";
44
55
interface Props {
66
code: string;
7+
scrollable?: boolean;
78
cssCode?: string;
89
bodyCode?: string;
910
width?: number | string;
@@ -15,17 +16,19 @@ interface Props {
1516
const defaultHeight = 400,
1617
defaultWidth = "100%";
1718
18-
const { code, cssCode, bodyCode, height, width, scripts, hideOffscreen } = Astro.props;
19+
const { code, cssCode, bodyCode, height, width, scripts, hideOffscreen, scrollable = false } = Astro.props;
1920
// A component that displays a full-width sketch without showing its code.
2021
2122
const frameProps = {
2223
jsCode: code,
2324
cssCode: cssCode,
2425
htmlBodyCode: bodyCode,
26+
scrollable: scrollable,
2527
height: height || defaultHeight,
2628
width: width || defaultWidth,
2729
scripts,
2830
};
31+
2932
---
3033
{hideOffscreen ? (
3134
<CodeFrame client:only {...frameProps} />

‎src/content/tutorials/en/typography-2.0.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,7 +1348,7 @@ function draw() {
13481348
Try experimenting with different offsets to see what kind of effect you get! I've used 50ms here.
13491349
</Callout>
13501350

1351-
<EditableSketch scripts={['https://cdn.jsdelivr.net/npm/p5.woff2@0.0.3/lib/p5.woff2.js']} code={`
1351+
<EditableSketch scripts={['https://cdn.jsdelivr.net/npm/p5.woff2@0.0.3/lib/p5.woff2.js']} scrollable={true} code={`
13521352
let blockFont;
13531353
let cursiveFont;
13541354
let blockText;
@@ -1574,7 +1574,7 @@ letters.forEach((letter, i) => {
15741574

15751575
Try wiggling your mouse over the letters!
15761576

1577-
<EditableSketch scripts={['https://cdn.jsdelivr.net/npm/p5.woff2@0.0.3/lib/p5.woff2.js']} code={`
1577+
<EditableSketch scripts={['https://cdn.jsdelivr.net/npm/p5.woff2@0.0.3/lib/p5.woff2.js']} scrollable={true} code={`
15781578
let blockFont;
15791579
let cursiveFont;
15801580
let blockText;

‎test/components/CodeEmbed.test.tsx‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ import { render, screen, act, cleanup, within } from "@testing-library/preact";
1111
import { server } from "../mocks/server";
1212
import { CodeEmbed } from "@/src/components/CodeEmbed";
1313

14+
class ResizeObserverMock {
15+
observe() {}
16+
unobserve() {}
17+
disconnect() {}
18+
}
19+
(globalThis as any).ResizeObserver = ResizeObserverMock;
20+
1421
suite("CodeEmbed", () => {
1522
// Start a mock server to intercept network requests
1623
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));

0 commit comments

Comments
 (0)