Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions src/components/CodeEmbed/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { Icon } from "../Icon";
export const CodeEmbed = (props) => {
const { ref: liveRegionRef, announce } = useLiveRegion();
const [rendered, setRendered] = useState(false);

const initialCode = props.initialValue ?? "";
// Source code from Google Docs sometimes uses a unicode non-breaking space
// instead of a normal one, but these break the code frame, so we replace them here.
Expand All @@ -54,6 +55,9 @@ export const CodeEmbed = (props) => {
}

const codeFrameRef = useRef(null);
const editorRef = useRef(null);
const isScrollable = props.scrollable ?? false;
const [hasScrollbar, setHasScrollbar] = useState(false);

const updateOrReRun = () => {
if (codeString === previewCodeString) {
Expand All @@ -79,6 +83,18 @@ export const CodeEmbed = (props) => {
}
}, []);

// only observe scroll when scrollable
useEffect(() => {
if (!rendered || !isScrollable) return;
const scroller = editorRef.current?.querySelector(".cm-scroller");
if (!scroller) return;
const observer = new ResizeObserver(() => {
setHasScrollbar(scroller.scrollHeight > scroller.clientHeight);
});
observer.observe(scroller);
return () => observer.disconnect();
}, [rendered, isScrollable]);

if (!rendered) return <div className="code-placeholder" />;

return (
Expand Down Expand Up @@ -125,11 +141,12 @@ export const CodeEmbed = (props) => {
</div>
</div>
) : null}
<div className="code-editor-container relative w-full">
<div ref={editorRef} className="code-editor-container relative w-full">
<CodeMirror
value={codeString}
theme="light"
width="100%"
height={isScrollable ? "300px" : undefined}
minimalSetup={{
highlightSpecialChars: false,
history: false,
Expand All @@ -139,19 +156,27 @@ export const CodeEmbed = (props) => {
historyKeymap: true,
}}
basicSetup={{
lineNumbers: false,
foldGutter: false,
lineNumbers: hasScrollbar,
foldGutter: true,
autocompletion: false,
}}
indentWithTab={false}
extensions={[javascript(), EditorView.lineWrapping]}
extensions={[javascript(), EditorView.lineWrapping, EditorView.theme({
".cm-scroller": {
overflowY: isScrollable ? "auto" : "visible",
},
})]}
onChange={(val) => setCodeString(val)}
editable={props.editable}
onCreateEditor={(editorView) =>
(editorView.contentDOM.ariaLabel = "Code Editor")
}
/>
<div className="absolute right-0 top-0 flex flex-col gap-xs p-xs md:flex-row">
<div
className={`absolute top-0 flex flex-col gap-xs p-xs md:flex-row ${
isScrollable && hasScrollbar ? "right-[25px]" : "right-0"
}`}
>
<CopyCodeButton textToCopy={codeString || initialCode} />
<CircleButton
onClick={() => {
Expand All @@ -171,4 +196,4 @@ export const CodeEmbed = (props) => {
);
};

export default CodeEmbed;
export default CodeEmbed;
24 changes: 21 additions & 3 deletions src/components/EditableSketch/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,42 @@ import CodeEmbed from "../CodeEmbed";

interface Props {
code: string;
scrollable?: boolean;
cssCode?: string;
bodyCode?: string;
width?: number;
height?: number;
base?: string;
scripts?: string[];
}

const { props } = Astro;
const props = Astro.props as Props;

let previewWidth = props.width;
let previewHeight = props.height;
const match = /createCanvas\(\s*(\d+),\s*(\d+)\s*(?:,\s*(?:P2D|WEBGL)\s*)?\)/m.exec(props.code);
const match = /createCanvas\(\s*(\d+),\s*(\d+)\s*(?:,\s*(?:P2D|WEBGL|WEBGPU)\s*)?\)/m.exec(props.code);
if (match) {
previewWidth = previewWidth || parseFloat(match[1]);
previewHeight = previewHeight || parseFloat(match[2]);
}

const vertical = !previewWidth || previewWidth > 200
const scrollable = props.scrollable ?? false;

---

<CodeEmbed client:load initialValue={props.code.trim()} previewWidth={previewWidth} previewHeight={previewHeight} base={props.base} previewable editable allowSideBySide={!vertical} fullWidth={!vertical} scripts={props.scripts} />
<CodeEmbed
client:load
initialValue={props.code.trim()}
previewWidth={previewWidth}
previewHeight={previewHeight}
base={props.base}
previewable
editable
allowSideBySide={!vertical}
fullWidth={!vertical}
scripts={props.scripts}
cssCode={props.cssCode}
htmlBodyCode={props.bodyCode}
scrollable={scrollable}
/>
5 changes: 4 additions & 1 deletion src/components/SketchEmbed/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CodeFrame } from "../CodeEmbed/frame";

interface Props {
code: string;
scrollable?: boolean;
cssCode?: string;
bodyCode?: string;
width?: number | string;
Expand All @@ -15,17 +16,19 @@ interface Props {
const defaultHeight = 400,
defaultWidth = "100%";

const { code, cssCode, bodyCode, height, width, scripts, hideOffscreen } = Astro.props;
const { code, cssCode, bodyCode, height, width, scripts, hideOffscreen, scrollable = false } = Astro.props;
// A component that displays a full-width sketch without showing its code.

const frameProps = {
jsCode: code,
cssCode: cssCode,
htmlBodyCode: bodyCode,
scrollable: scrollable,
height: height || defaultHeight,
width: width || defaultWidth,
scripts,
};

---
{hideOffscreen ? (
<CodeFrame client:only {...frameProps} />
Expand Down
4 changes: 2 additions & 2 deletions src/content/tutorials/en/typography-2.0.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,7 @@ function draw() {
Try experimenting with different offsets to see what kind of effect you get! I've used 50ms here.
</Callout>

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

Try wiggling your mouse over the letters!

<EditableSketch scripts={['https://cdn.jsdelivr.net/npm/p5.woff2@0.0.3/lib/p5.woff2.js']} code={`
<EditableSketch scripts={['https://cdn.jsdelivr.net/npm/p5.woff2@0.0.3/lib/p5.woff2.js']} scrollable={true} code={`
let blockFont;
let cursiveFont;
let blockText;
Expand Down
7 changes: 7 additions & 0 deletions test/components/CodeEmbed.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ import { render, screen, act, cleanup, within } from "@testing-library/preact";
import { server } from "../mocks/server";
import { CodeEmbed } from "@/src/components/CodeEmbed";

class ResizeObserverMock {
observe() {}
unobserve() {}
disconnect() {}
}
(globalThis as any).ResizeObserver = ResizeObserverMock;

suite("CodeEmbed", () => {
// Start a mock server to intercept network requests
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
Expand Down