Skip to content
14 changes: 11 additions & 3 deletions apps/typegpu-docs/src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import Editor, {
import type { editor } from 'monaco-editor';
import { entries, filter, fromEntries, isTruthy, map, pipe } from 'remeda';
import { SANDBOX_MODULES } from '../utils/examples/sandboxModules.ts';
import type { ExampleSrcFile } from '../utils/examples/types.ts';
import type {
ExampleCommonFile,
ExampleSrcFile,
} from '../utils/examples/types.ts';
import { tsCompilerOptions } from '../utils/liveEditor/embeddedTypeScript.ts';

function handleEditorWillMount(monaco: Monaco) {
Expand Down Expand Up @@ -56,7 +59,7 @@ function handleEditorOnMount(editor: editor.IStandaloneCodeEditor) {
}

type Props = {
file: ExampleSrcFile;
file: ExampleSrcFile | ExampleCommonFile;
shown: boolean;
};

Expand All @@ -68,6 +71,11 @@ const createCodeEditorComponent = (
(props: Props) => {
const { file, shown } = props;

// Monaco needs relative paths to work correctly and '../../common/file.ts' will not do
const path = 'common' in file
? `common/${file.path}`
: `${file.exampleKey.replace('--', '/')}/${file.path}`;

return (
<div
className={shown
Expand All @@ -77,7 +85,7 @@ const createCodeEditorComponent = (
<Editor
defaultLanguage={language}
value={file.content}
path={file.path}
path={path}
beforeMount={beforeMount}
onMount={onMount}
options={{
Expand Down
8 changes: 6 additions & 2 deletions apps/typegpu-docs/src/components/ExamplePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useAtomValue, useSetAtom } from 'jotai';
import { Suspense, useEffect, useRef } from 'react';
import { currentExampleAtom } from '../utils/examples/currentExampleAtom.ts';
import { examples } from '../examples/exampleContent.ts';
import { common, examples } from '../examples/exampleContent.ts';
import { ExampleNotFound } from './ExampleNotFound.tsx';
import { ExampleView } from './ExampleView.tsx';

Expand Down Expand Up @@ -68,7 +68,11 @@ function ExamplePage() {

if (currentExample in examples) {
return (
<ExampleView key={currentExample} example={examples[currentExample]} />
<ExampleView
key={currentExample}
example={examples[currentExample]}
common={common}
/>
);
}

Expand Down
38 changes: 34 additions & 4 deletions apps/typegpu-docs/src/components/ExampleView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { ExecutionCancelledError } from '../utils/examples/errors.ts';
import { exampleControlsAtom } from '../utils/examples/exampleControlAtom.ts';
import { executeExample } from '../utils/examples/exampleRunner.ts';
import type { ExampleState } from '../utils/examples/exampleState.ts';
import type { Example } from '../utils/examples/types.ts';
import type {
Example,
ExampleCommonFile,
ExampleSrcFile,
} from '../utils/examples/types.ts';
import { isGPUSupported } from '../utils/isGPUSupported.ts';
import { HtmlCodeEditor, TsCodeEditor } from './CodeEditor.tsx';
import { ControlPanel } from './ControlPanel.tsx';
Expand All @@ -20,6 +24,7 @@ import { openInStackBlitz } from './stackblitz/openInStackBlitz.ts';

type Props = {
example: Example;
common: ExampleCommonFile[];
isPlayground?: boolean;
};

Expand Down Expand Up @@ -66,8 +71,8 @@ function useExample(
}, [setSnackbarText, setExampleControlParams]);
}

export function ExampleView({ example }: Props) {
const { tsFiles, tsImport, htmlFile } = example;
export function ExampleView({ example, common }: Props) {
const { tsFiles: srcFiles, tsImport, htmlFile } = example;

const [snackbarText, setSnackbarText] = useAtom(currentSnackbarAtom);
const [currentFilePath, setCurrentFilePath] = useState<string>('index.ts');
Expand All @@ -76,6 +81,7 @@ export function ExampleView({ example }: Props) {
const codeEditorMobileShowing = useAtomValue(codeEditorShownMobileAtom);
const exampleHtmlRef = useRef<HTMLDivElement>(null);

const tsFiles = filterRelevantTsFiles(srcFiles, common);
const filePaths = tsFiles.map((file) => file.path);
const editorTabsList = [
'index.ts',
Expand Down Expand Up @@ -171,7 +177,7 @@ export function ExampleView({ example }: Props) {
</div>

<div className='absolute right-0 z-5 md:top-15 md:right-8'>
<Button onClick={() => openInStackBlitz(example)}>
<Button onClick={() => openInStackBlitz(example, common)}>
<span className='font-bold'>Edit on</span>
<img
src='https://developer.stackblitz.com/img/logo/stackblitz-logo-black_blue.svg'
Expand Down Expand Up @@ -290,3 +296,27 @@ function useResizableCanvas(exampleHtmlRef: RefObject<HTMLDivElement | null>) {
};
}, [exampleHtmlRef]);
}

/**
* NOTE: this function only filters common files used in src files.
* Common files used in other common files will not be included.
*/
function filterRelevantTsFiles(
srcFiles: ExampleSrcFile[],
commonFiles: ExampleCommonFile[],
) {
const tsFiles: (ExampleSrcFile | ExampleCommonFile)[] = [
...srcFiles,
];

for (const common of commonFiles) {
for (const src of srcFiles) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this change allow common file to reference each other, and show up in the tabs?

Suggested change
for (const src of srcFiles) {
for (const src of [...srcFiles, ...commonFiles]) {

Copy link
Contributor Author

@aleksanderkatan aleksanderkatan Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. Or, I guess, it would, but with a side effect. If common1.ts references common2.ts, then common2.ts would be included in every example, regardless of whether the example uses common1.ts or not. We would need to do a BFS or something.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Could you create an issue for this so we can do it in another PR?

if (src.content.includes(`common/${common.path}`)) {
tsFiles.push(common);
break;
}
}
}

return tsFiles;
}
36 changes: 24 additions & 12 deletions apps/typegpu-docs/src/components/stackblitz/openInStackBlitz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import pnpmWorkspace from '../../../../../pnpm-workspace.yaml?raw';
import typegpuDocsPackageJson from '../../../package.json' with {
type: 'json',
};
import type { Example } from '../../utils/examples/types.ts';
import type { Example, ExampleCommonFile } from '../../utils/examples/types.ts';
// biome-ignore lint/correctness/useImportExtensions: dude it's there
import index from './stackBlitzIndex.ts?raw';

Expand Down Expand Up @@ -51,17 +51,29 @@ if (pnpmWorkspaceYaml instanceof type.errors) {
throw new Error(pnpmWorkspaceYaml.summary);
}

export const openInStackBlitz = (example: Example) => {
const tsFiles = example.tsFiles.reduce(
(acc, file) => {
acc[`src/${file.path}`] = file.content.replaceAll(
'/TypeGPU',
'https://docs.swmansion.com/TypeGPU',
);
return acc;
},
{} as Record<string, string>,
);
export const openInStackBlitz = (
example: Example,
common: ExampleCommonFile[],
) => {
const tsFiles: Record<string, string> = {};

for (const file of example.tsFiles) {
tsFiles[`src/${file.path}`] = file.content;
}
for (const file of common) {
tsFiles[`src/common/${file.path}`] = file.content;
}

for (const key of Object.keys(tsFiles)) {
const content = tsFiles[key];
tsFiles[key] = content.replaceAll(
'/TypeGPU',
'https://docs.swmansion.com/TypeGPU',
).replaceAll(
'../../common',
'./common',
);
}

StackBlitzSDK.openProject(
{
Expand Down
22 changes: 19 additions & 3 deletions apps/typegpu-docs/src/examples/exampleContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import pathe from 'pathe';
import * as R from 'remeda';
import type {
Example,
ExampleCommonFile,
ExampleMetadata,
ExampleSrcFile,
ThumbnailPair,
Expand Down Expand Up @@ -75,8 +76,9 @@ const metaFiles = R.pipe(
R.mapKeys(pathToExampleKey),
);

const readonlyTsFiles = R.pipe(
import.meta.glob('./**/*.ts', {
const exampleTsFiles = R.pipe(
// './<category>/<example>/<file>.ts'
import.meta.glob('./*/*/*.ts', {
query: 'raw',
eager: true,
import: 'default',
Expand Down Expand Up @@ -127,7 +129,7 @@ export const examples = R.pipe(
({
key,
metadata: value,
tsFiles: readonlyTsFiles[key] ?? [],
tsFiles: exampleTsFiles[key] ?? [],
tsImport: () => noCacheImport(tsFilesImportFunctions[key]),
htmlFile: htmlFiles[key]?.[0] ?? '',
thumbnails: thumbnailFiles[key],
Expand All @@ -140,4 +142,18 @@ export const examplesByCategory = R.groupBy(
(example) => example.metadata.category,
);

export const common = R.pipe(
import.meta.glob('./common/*.ts', {
query: 'raw',
eager: true,
import: 'default',
}) as Record<string, string>,
R.mapValues((content: string, key: string): ExampleCommonFile => ({
common: true,
path: pathe.basename(key),
content,
})),
R.values(),
);

export const PLAYGROUND_KEY = 'playground__';
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ModelVertexOutput,
} from './schemas.ts';
import { loadModel } from './load-model.ts';
import { Camera, setupOrbitCamera } from './setup-orbit-camera.ts';
import { Camera, setupOrbitCamera } from '../../common/setup-orbit-camera.ts';

// setup
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
Expand Down Expand Up @@ -46,7 +46,7 @@ const exampleControlsUniform = root.createUniform(
p.initialControls,
);

export const vertexShader = tgpu['~unstable'].vertexFn({
const vertexShader = tgpu['~unstable'].vertexFn({
in: { ...ModelVertexInput.propTypes, instanceIndex: d.builtin.instanceIndex },
out: ModelVertexOutput,
})((input) => {
Expand All @@ -63,7 +63,7 @@ export const vertexShader = tgpu['~unstable'].vertexFn({
});

// see https://gist.github.com/chicio/d983fff6ff304bd55bebd6ff05a2f9dd
export const fragmentShader = tgpu['~unstable'].fragmentFn({
const fragmentShader = tgpu['~unstable'].fragmentFn({
in: ModelVertexOutput,
out: d.vec4f,
})((input) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/typegpu-docs/src/examples/simulation/gravity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
Time,
timeAccess,
} from './schemas.ts';
import { Camera, setupOrbitCamera } from './setup-orbit-camera.ts';
import { Camera, setupOrbitCamera } from '../../common/setup-orbit-camera.ts';

const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import tgpu, { type TgpuSampler } from 'typegpu';
import * as d from 'typegpu/data';
import { Camera } from './setup-orbit-camera.ts';
import { Camera } from '../../common/setup-orbit-camera.ts';

export type CelestialBody = d.Infer<typeof CelestialBody>;
export const CelestialBody = d.struct({
Expand Down
Loading