Skip to content

Commit 081b788

Browse files
committed
docs: add examples section
1 parent f8ecd4a commit 081b788

23 files changed

+1243
-1
lines changed

config/sidebar.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export default {
119119
{
120120
title: 'Gradients',
121121
to: '/features/gradients'
122+
},
123+
{
124+
title: 'Examples',
125+
to: '/examples'
122126
}
123127
]
124128
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"test:unit": "vitest",
1919
"prepack": "npx svelte-package",
2020
"release-next": "npm version prerelease --preid next && npm publish && git push && git push --tags && sleep 1 && npm dist-tag add svelteplot@$(npm view . version) next",
21-
"docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/"
21+
"docs": "npm run build && cd build && rsync --recursive . vis4.net:svelteplot/alpha0/",
22+
"screenshots": "node screenshot-examples.js"
2223
},
2324
"exports": {
2425
".": {
@@ -83,6 +84,7 @@
8384
"jsdom": "^26.1.0",
8485
"prettier": "^3.5.3",
8586
"prettier-plugin-svelte": "^3.4.0",
87+
"puppeteer": "^24.9.0",
8688
"remark-code-extra": "^1.0.1",
8789
"remark-code-frontmatter": "^1.0.0",
8890
"resize-observer-polyfill": "^1.5.1",

pnpm-lock.yaml

Lines changed: 622 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

screenshot-examples.js

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import fs from 'fs/promises';
2+
import path from 'path';
3+
import puppeteer from 'puppeteer';
4+
import { exec } from 'child_process';
5+
import { fileURLToPath } from 'url';
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
9+
// Configuration
10+
const EXAMPLES_DIR = path.join(__dirname, 'src', 'routes', 'examples');
11+
const OUTPUT_DIR = path.join(__dirname, 'static', 'examples');
12+
const SCREENSHOT_WIDTH = 600;
13+
const DEVICE_PIXEL_RATIO = 2;
14+
15+
// Start the development server and return server instance and local URL
16+
const startServer = () => {
17+
console.log('Starting development server...');
18+
const server = exec('pnpm dev');
19+
20+
// Wait for the server to start and extract the local URL
21+
return new Promise((resolve) => {
22+
let serverUrl = null;
23+
24+
server.stdout.on('data', (data) => {
25+
console.log(`Server: ${data}`);
26+
27+
// Extract the local URL using regex
28+
const localUrlMatch = data.toString().match(/Local:\s+(http:\/\/localhost:\d+\/)/i);
29+
if (localUrlMatch && localUrlMatch[1]) {
30+
serverUrl = localUrlMatch[1].trim();
31+
console.log(`Detected server URL: ${serverUrl}`);
32+
}
33+
34+
// Server is ready when we see this message and have a URL
35+
if ((data.includes('Local:') || data.includes('Server running at')) && serverUrl) {
36+
console.log('Server started successfully');
37+
resolve({ server, url: serverUrl });
38+
}
39+
});
40+
41+
server.stderr.on('data', (data) => {
42+
console.error(`Server error: ${data}`);
43+
});
44+
});
45+
};
46+
47+
// Recursively get all Svelte files (excluding layout, _index, [...key] files)
48+
const getSvelteFiles = async (dir) => {
49+
const entries = await fs.readdir(dir, { withFileTypes: true });
50+
51+
const files = await Promise.all(
52+
entries.map(async (entry) => {
53+
const fullPath = path.join(dir, entry.name);
54+
55+
if (entry.isDirectory() && !entry.name.startsWith('[')) {
56+
return getSvelteFiles(fullPath);
57+
} else if (
58+
entry.isFile() &&
59+
entry.name.endsWith('.svelte') &&
60+
!entry.name.startsWith('+') &&
61+
!entry.name.startsWith('_')
62+
) {
63+
return [fullPath];
64+
}
65+
66+
return [];
67+
})
68+
);
69+
70+
return files.flat();
71+
};
72+
73+
// Convert file path to URL path
74+
const filePathToUrlPath = (filePath) => {
75+
const relativePath = path.relative(EXAMPLES_DIR, filePath);
76+
return relativePath.replace('.svelte', '');
77+
};
78+
79+
// Create output directory if it doesn't exist
80+
const ensureDirectoryExists = async (dirPath) => {
81+
try {
82+
await fs.mkdir(dirPath, { recursive: true });
83+
} catch (error) {
84+
// Ignore if directory already exists
85+
if (error.code !== 'EEXIST') {
86+
throw error;
87+
}
88+
}
89+
};
90+
91+
// Take screenshot of a page in specific theme
92+
const takeScreenshot = async (page, urlPath, outputPath, isDarkMode = false) => {
93+
const themeSuffix = isDarkMode ? '.dark' : '';
94+
const finalOutputPath = outputPath.replace('.png', `${themeSuffix}.png`);
95+
96+
// Wait for the Plot component to be rendered
97+
await page.waitForSelector('.svelteplot', { timeout: 10000 });
98+
99+
// Toggle dark mode if needed
100+
if (isDarkMode) {
101+
// Toggle dark mode by setting the HTML class
102+
// SveltePress uses 'html.dark' for dark mode
103+
await page.evaluate(() => {
104+
document.documentElement.classList.add('dark');
105+
// Force any theme-aware components to re-render
106+
window.dispatchEvent(new Event('theme-change'));
107+
});
108+
109+
// Wait a bit for theme to apply
110+
await new Promise((resolve) => setTimeout(resolve, 300));
111+
}
112+
113+
// Get the Plot SVG element
114+
const elementHandle = await page.evaluateHandle(() =>
115+
document.querySelector('.svelteplot svg')
116+
);
117+
118+
// Take a screenshot of the element
119+
const boundingBox = await elementHandle.boundingBox();
120+
121+
if (!boundingBox) {
122+
console.error(
123+
`Could not get bounding box for example: ${urlPath} (${isDarkMode ? 'dark' : 'light'} mode)`
124+
);
125+
return false;
126+
}
127+
128+
// Take the screenshot
129+
await page.screenshot({
130+
path: finalOutputPath,
131+
clip: {
132+
x: boundingBox.x,
133+
y: boundingBox.y,
134+
width: boundingBox.width,
135+
height: boundingBox.height
136+
}
137+
});
138+
139+
console.log(`Saved screenshot to: ${finalOutputPath}`);
140+
return true;
141+
};
142+
143+
// Take screenshots of all example pages
144+
const screenshotExamples = async () => {
145+
// Start the server and get the URL
146+
const { server, url: serverUrl } = await startServer();
147+
148+
try {
149+
// Launch the browser
150+
console.log('Launching browser...');
151+
const browser = await puppeteer.launch({
152+
defaultViewport: {
153+
width: SCREENSHOT_WIDTH,
154+
height: 800,
155+
deviceScaleFactor: DEVICE_PIXEL_RATIO
156+
},
157+
// Launch Chrome in headless mode
158+
headless: 'new'
159+
});
160+
161+
// Get all example Svelte files
162+
const svelteFiles = await getSvelteFiles(EXAMPLES_DIR);
163+
console.log(`Found ${svelteFiles.length} example files to screenshot`);
164+
165+
// Process each file
166+
for (const filePath of svelteFiles) {
167+
const urlPath = filePathToUrlPath(filePath);
168+
const outputPath = path.join(OUTPUT_DIR, urlPath + '.png');
169+
const outputDir = path.dirname(outputPath);
170+
171+
console.log(`Processing: ${urlPath}`);
172+
173+
// Create output directory structure
174+
await ensureDirectoryExists(outputDir);
175+
176+
// Open the page
177+
const page = await browser.newPage();
178+
await page.goto(`${serverUrl}examples/${urlPath}`, {
179+
waitUntil: 'networkidle0',
180+
timeout: 60000
181+
});
182+
183+
// Take light mode screenshot
184+
await takeScreenshot(page, urlPath, outputPath, false);
185+
186+
// Take dark mode screenshot
187+
await takeScreenshot(page, urlPath, outputPath, true);
188+
189+
await page.close();
190+
}
191+
192+
// Close the browser
193+
await browser.close();
194+
195+
console.log('All screenshots completed successfully!');
196+
} catch (error) {
197+
console.error('Error:', error);
198+
} finally {
199+
// Kill the server
200+
server.kill();
201+
process.exit(0);
202+
}
203+
};
204+
205+
// Run the script
206+
screenshotExamples().catch(console.error);

src/routes/examples/+layout.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { loadDatasets, loadJSON } from '$lib/helpers/data.js';
2+
import type { PageLoad } from './$types.js';
3+
4+
export const ssr = true;
5+
6+
export const load: PageLoad = async ({ fetch }) => {
7+
return {
8+
data: {
9+
world: await loadJSON(fetch, 'countries-110m'),
10+
...(await loadDatasets(
11+
[
12+
'aapl',
13+
'beagle',
14+
'bls',
15+
'co2',
16+
'crimea',
17+
'driving',
18+
'riaa',
19+
'stateage',
20+
'tdf',
21+
'rightwing',
22+
'stocks',
23+
'sftemp'
24+
],
25+
fetch
26+
))
27+
}
28+
};
29+
};

src/routes/examples/+page.svelte

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script module>
2+
export const frontmatter = {
3+
title: 'Example plots',
4+
description: 'Some description'
5+
};
6+
</script>
7+
8+
<script>
9+
import { groupBy } from 'es-toolkit';
10+
11+
const pages = import.meta.glob('./**/*.svelte', { eager: true });
12+
const paths = groupBy(
13+
Object.keys(pages).filter((d) => !d.startsWith('./[group]')),
14+
(d) => d.split('/')[1]
15+
);
16+
</script>
17+
18+
<div class="column-container">
19+
{#each Object.entries(paths) as [group, groupPages] (group)}
20+
<div>
21+
<h3><a href="/examples/{group}">{pages[groupPages[0]].title}</a></h3>
22+
<ul>
23+
{#each groupPages.slice(1) as page (page)}
24+
<li>
25+
<a href={page.replace('./', 'examples/').replace('.svelte', '')}
26+
>{pages[page].title}</a>
27+
</li>
28+
{/each}
29+
</ul>
30+
</div>
31+
{/each}
32+
</div>
33+
34+
<style>
35+
.column-container {
36+
columns: 2;
37+
column-gap: 1rem;
38+
column-fill: balance;
39+
> div {
40+
padding-top: 1em;
41+
break-before: column;
42+
}
43+
h3 {
44+
break-before: avoid-column;
45+
}
46+
}
47+
</style>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<div class="theme-default--page-layout">
2+
<div class="content">
3+
<slot />
4+
</div>
5+
</div>
6+
7+
<style>
8+
.content {
9+
margin-left: auto;
10+
margin-right: auto;
11+
}
12+
</style>

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy