Skip to content

Commit 36b4414

Browse files
authored
Merge pull request #25 from Quantus-Network/feat/update-launch-and-add-new-weekly-update
Update launch page + add new weekly update + better script for generating pdf
2 parents ef49fbe + 14bcbf4 commit 36b4414

28 files changed

Lines changed: 1920 additions & 335 deletions

website/docker-compose.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
services:
2+
browserless:
3+
image: browserless/chrome:latest
4+
ports:
5+
- "33000:3000"
6+
environment:
7+
CONNECTION_TIMEOUT: "600000"
8+
MAX_CONCURRENT_SESSIONS: "5"
9+
shm_size: "2gb"
10+
extra_hosts:
11+
- "host.docker.internal:host-gateway"
12+
restart: "no"

website/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"scripts": {
66
"dev": "astro dev",
77
"build": "astro build",
8-
"build:pdf": "node scripts/generate-whitepaper-pdfs.js",
9-
"build:full": "astro build && node scripts/generate-whitepaper-pdfs.js",
8+
"build:pdf": "node scripts/run-whitepaper-pdf-with-browserless.mjs",
9+
"build:full": "astro build && node scripts/run-whitepaper-pdf-with-browserless.mjs",
1010
"preview": "astro preview",
1111
"astro": "astro",
1212
"format:check": "prettier --check .",
113 KB
Loading
210 KB
Loading

website/scripts/generate-whitepaper-pdfs.js

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import fs from "node:fs";
22
import path from "node:path";
3+
import process from "node:process";
34
import { spawn } from "node:child_process";
5+
import * as puppeteer from "puppeteer-core";
46

57
const OUTPUT_BASE = path.join("./public", "whitepaper", "pdf");
68
const PORT = 4322;
79
const BASE_URL = `http://host.docker.internal:${PORT}`;
810

11+
const BROWSERLESS_WS = process.env.BROWSERLESS_WS ?? "ws://127.0.0.1:33000";
12+
913
const SUPPORTED_LOCALES = [
1014
"en-US",
1115
"zh-CN",
@@ -19,6 +23,55 @@ const SUPPORTED_LOCALES = [
1923
];
2024
const DEFAULT_LOCALE = "en-US";
2125

26+
/** Time with no network activity before considering the page idle (ms). */
27+
const NETWORK_IDLE_MS = 500;
28+
/** Max time to wait for network idle after scrolling (ms). */
29+
const NETWORK_IDLE_TIMEOUT_MS = 90_000;
30+
/** Pause between scroll steps so lazy observers can fire (ms). */
31+
const SCROLL_STEP_PAUSE_MS = 75;
32+
33+
/**
34+
* Scrolls the full document to trigger lazy-loaded images, waits for network
35+
* quiet, then resolves when images have loaded (or failed).
36+
*/
37+
async function waitForImagesAndNetworkIdle(page) {
38+
await page.evaluate(async (stepPause) => {
39+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
40+
const step = Math.max(240, Math.floor(window.innerHeight * 0.9));
41+
const maxY = document.documentElement.scrollHeight;
42+
for (let y = 0; y < maxY; y += step) {
43+
window.scrollTo(0, y);
44+
await sleep(stepPause);
45+
}
46+
window.scrollTo(0, maxY);
47+
await sleep(stepPause);
48+
window.scrollTo(0, 0);
49+
await sleep(100);
50+
}, SCROLL_STEP_PAUSE_MS);
51+
52+
await page.waitForNetworkIdle({
53+
idleTime: NETWORK_IDLE_MS,
54+
timeout: NETWORK_IDLE_TIMEOUT_MS,
55+
});
56+
57+
await page.evaluate(async () => {
58+
const imgs = Array.from(document.images);
59+
await Promise.all(
60+
imgs.map((img) => {
61+
if (img.complete && img.naturalWidth > 0) {
62+
return img.decode?.().catch(() => {}) ?? Promise.resolve();
63+
}
64+
return new Promise((resolve) => {
65+
const done = () => resolve();
66+
img.addEventListener("load", done, { once: true });
67+
img.addEventListener("error", done, { once: true });
68+
setTimeout(done, 15_000);
69+
}).then(() => img.decode?.().catch(() => {}) ?? Promise.resolve());
70+
}),
71+
);
72+
});
73+
}
74+
2275
function discoverVersions() {
2376
const contentDir = "./src/contents/whitepapers";
2477
const versions = new Map();
@@ -98,25 +151,20 @@ async function generatePdf(puppeteer, locale, version) {
98151

99152
console.log(` Generating PDF: ${locale}/v${version} -> ${outputFile}`);
100153

101-
let browser;
102-
try {
103-
browser = await puppeteer.connect({
104-
browserWSEndpoint: `ws://127.0.0.1:33000`,
105-
});
106-
} catch (err) {
107-
console.warn(`\nCould not connect to browser: ${err.message}`);
108-
console.log("Skipping PDF generation.");
109-
return;
110-
}
154+
const browser = await puppeteer.connect({
155+
browserWSEndpoint: BROWSERLESS_WS,
156+
});
111157

112158
const page = await browser.newPage();
113159

114160
try {
115161
await page.goto(url, {
116162
waitUntil: "networkidle0",
117-
timeout: 30000,
163+
timeout: 60_000,
118164
});
119165

166+
await waitForImagesAndNetworkIdle(page);
167+
120168
await page.pdf({
121169
path: outputFile,
122170
format: "A4",
@@ -153,11 +201,11 @@ async function generatePdf(puppeteer, locale, version) {
153201
` Warning: Failed to generate PDF for ${locale}/v${version}: ${err.message}`,
154202
);
155203
} finally {
156-
await browser.close();
204+
await browser?.close();
157205
}
158206
}
159207

160-
async function main() {
208+
export async function main() {
161209
console.log("Whitepaper PDF Generation");
162210
console.log("=".repeat(50));
163211

@@ -172,17 +220,6 @@ async function main() {
172220
console.log(` ${locale}: ${versions.map((v) => `v${v}`).join(", ")}`);
173221
}
174222

175-
let puppeteer;
176-
try {
177-
puppeteer = await import("puppeteer");
178-
} catch {
179-
console.warn("\nPuppeteer not available. Skipping PDF generation.");
180-
console.log(
181-
"To enable PDF generation, run: npx puppeteer browsers install chrome",
182-
);
183-
return;
184-
}
185-
186223
console.log("\nStarting preview server...");
187224
const server = await startPreviewServer();
188225

@@ -198,10 +235,7 @@ async function main() {
198235
console.log("\nAll PDFs generated successfully.");
199236
} catch (err) {
200237
console.error("PDF generation failed:", err.message);
201-
console.log("Build completed but PDF generation was skipped.");
202238
}
203239

204240
server.kill();
205241
}
206-
207-
main();
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { execSync } from "node:child_process";
2+
import net from "node:net";
3+
import path from "node:path";
4+
import { fileURLToPath } from "node:url";
5+
6+
const websiteRoot = path.join(
7+
path.dirname(fileURLToPath(import.meta.url)),
8+
"..",
9+
);
10+
const runner = process.argv[2] ?? "podman";
11+
12+
function waitForPort(port, host = "127.0.0.1", timeoutMs = 120_000) {
13+
const deadline = Date.now() + timeoutMs;
14+
return new Promise((resolve, reject) => {
15+
const tryOnce = () => {
16+
const socket = net.createConnection({ port, host }, () => {
17+
socket.end();
18+
resolve();
19+
});
20+
socket.on("error", () => {
21+
socket.destroy();
22+
if (Date.now() >= deadline) {
23+
reject(
24+
new Error(
25+
`Timed out waiting for ${host}:${port} (is Browserless running?)`,
26+
),
27+
);
28+
return;
29+
}
30+
setTimeout(tryOnce, 300);
31+
});
32+
};
33+
tryOnce();
34+
});
35+
}
36+
37+
function runCompose(args) {
38+
execSync(`${runner} compose ${args}`, {
39+
cwd: websiteRoot,
40+
stdio: "inherit",
41+
env: process.env,
42+
});
43+
}
44+
45+
function stopBrowserless() {
46+
runCompose("stop browserless");
47+
}
48+
49+
async function run() {
50+
runCompose("up -d browserless");
51+
52+
try {
53+
await waitForPort(33000);
54+
const { main } = await import("./generate-whitepaper-pdfs.js");
55+
await main();
56+
} finally {
57+
stopBrowserless();
58+
}
59+
}
60+
61+
run().catch((err) => {
62+
console.error(err);
63+
process.exitCode = 1;
64+
});

website/src/components/features/launch/AllocationChart.astro

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,22 @@
1616
plugins: [ChartDataLabels],
1717
data: {
1818
labels: [
19-
t("launch.tge.table.private_sale"),
20-
t("launch.tge.table.public_sale"),
21-
t("launch.tge.table.dex_liquidity"),
22-
t("launch.mining.table.miners"),
23-
t("launch.mining.table.community"),
24-
t("launch.mining.table.team"),
19+
t("launch.chart.miners"),
20+
t("launch.chart.private"),
21+
t("launch.chart.company"),
22+
t("launch.chart.public"),
23+
t("launch.chart.liquidity"),
2524
],
2625
datasets: [
2726
{
2827
label: "Launch Allocation",
29-
data: [15, 10, 5, 35, 25, 10],
28+
data: [50, 15, 15, 10, 10],
3029
backgroundColor: [
30+
"#00C2A8",
3131
"#0000FF",
32+
"#7C3AED",
3233
"#ED4CCE",
3334
"#FFE91F",
34-
"#00C2A8",
35-
"#FF6B35",
36-
"#7C3AED",
3735
],
3836
borderColor: "#000000",
3937
borderWidth: 2,
@@ -48,17 +46,21 @@
4846
enabled: false,
4947
},
5048
datalabels: {
49+
textShadowBlur: 10,
50+
textShadowColor: "#000000",
5151
formatter: (value, context) => {
5252
const datapoints = context.chart.data.datasets[0]
5353
.data as number[];
54+
const label = context.chart?.data.labels?.[context.dataIndex];
55+
5456
const total = datapoints.reduce((acc, val) => acc + val, 0);
5557
const percentage = ((value / total) * 100).toFixed(1) + "%";
56-
return percentage;
58+
return `${label}\n ${percentage}`;
5759
},
5860
color: "#fff",
5961
},
6062
legend: {
61-
display: true,
63+
display: false,
6264
},
6365
},
6466
},

0 commit comments

Comments
 (0)