missing files
This commit is contained in:
203
src/app/registers/[hex]/opengraph-image.tsx
Normal file
203
src/app/registers/[hex]/opengraph-image.tsx
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import { ImageResponse } from 'next/og';
|
||||||
|
import { getRegisters } from '@/services/register.service';
|
||||||
|
|
||||||
|
export const runtime = 'nodejs';
|
||||||
|
|
||||||
|
export const size = {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const contentType = 'image/png';
|
||||||
|
|
||||||
|
const buildRegisterSummaryLines = (register: { description: string; text: string; modes: { text: string }[] }) => {
|
||||||
|
const isInfoLine = (line: string) =>
|
||||||
|
line.length > 0 &&
|
||||||
|
!line.startsWith('//') &&
|
||||||
|
!line.startsWith('(R') &&
|
||||||
|
!line.startsWith('(W') &&
|
||||||
|
!line.startsWith('(R/W') &&
|
||||||
|
!line.startsWith('*') &&
|
||||||
|
!/^bits?\s+\d/i.test(line);
|
||||||
|
|
||||||
|
const normalizeLines = (raw: string) => {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const rawLines = raw.split('\n');
|
||||||
|
for (const rawLine of rawLines) {
|
||||||
|
const trimmed = rawLine.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
if (lines.length > 0 && lines[lines.length - 1] !== '') {
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isInfoLine(trimmed)) {
|
||||||
|
lines.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
};
|
||||||
|
|
||||||
|
const textLines = normalizeLines(register.text);
|
||||||
|
const modeLines = register.modes.flatMap(mode => normalizeLines(mode.text));
|
||||||
|
const descriptionLines = normalizeLines(register.description);
|
||||||
|
|
||||||
|
const combined: string[] = [];
|
||||||
|
const appendBlock = (block: string[]) => {
|
||||||
|
if (block.length === 0) return;
|
||||||
|
if (combined.length > 0 && combined[combined.length - 1] !== '') {
|
||||||
|
combined.push('');
|
||||||
|
}
|
||||||
|
combined.push(...block);
|
||||||
|
};
|
||||||
|
|
||||||
|
appendBlock(textLines);
|
||||||
|
appendBlock(modeLines);
|
||||||
|
appendBlock(descriptionLines);
|
||||||
|
|
||||||
|
const deduped: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const line of combined) {
|
||||||
|
if (!line) {
|
||||||
|
if (deduped.length > 0 && deduped[deduped.length - 1] !== '') {
|
||||||
|
deduped.push('');
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (seen.has(line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
seen.add(line);
|
||||||
|
deduped.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deduped.length > 0 ? deduped : ['Spectrum Next register details and bit-level behavior.'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const splitLongWord = (word: string, maxLineLength: number) => {
|
||||||
|
if (word.length <= maxLineLength) return [word];
|
||||||
|
const chunks: string[] = [];
|
||||||
|
for (let idx = 0; idx < word.length; idx += maxLineLength - 1) {
|
||||||
|
chunks.push(`${word.slice(idx, idx + maxLineLength - 1)}-`);
|
||||||
|
}
|
||||||
|
const last = chunks[chunks.length - 1];
|
||||||
|
chunks[chunks.length - 1] = last.endsWith('-') ? last.slice(0, -1) : last;
|
||||||
|
return chunks;
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapText = (text: string, maxLineLength: number) => {
|
||||||
|
const words = text
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.flatMap(word => splitLongWord(word, maxLineLength));
|
||||||
|
const lines: string[] = [];
|
||||||
|
let current = '';
|
||||||
|
|
||||||
|
for (const word of words) {
|
||||||
|
const next = current ? `${current} ${word}` : word;
|
||||||
|
if (next.length > maxLineLength && current) {
|
||||||
|
lines.push(current);
|
||||||
|
current = word;
|
||||||
|
} else {
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
lines.push(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
};
|
||||||
|
|
||||||
|
const wrapTextLines = (sourceLines: string[], maxLineLength: number, maxLines: number) => {
|
||||||
|
const output: string[] = [];
|
||||||
|
for (const line of sourceLines) {
|
||||||
|
if (output.length >= maxLines) break;
|
||||||
|
if (!line) {
|
||||||
|
if (output.length > 0 && output[output.length - 1] !== '') {
|
||||||
|
output.push('');
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const wrapped = wrapText(line, maxLineLength);
|
||||||
|
for (const wrappedLine of wrapped) {
|
||||||
|
if (output.length >= maxLines) break;
|
||||||
|
output.push(wrappedLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output.slice(0, maxLines);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function Image({ params }: { params: Promise<{ hex: string }> }) {
|
||||||
|
const { hex } = await params;
|
||||||
|
const targetHex = decodeURIComponent(hex).toLowerCase();
|
||||||
|
const registers = await getRegisters();
|
||||||
|
const register = registers.find(r => r.hex_address.toLowerCase() === targetHex);
|
||||||
|
|
||||||
|
const title = register ? `${register.hex_address} ${register.name}` : 'Spectrum Next Register';
|
||||||
|
const summaryLinesSource = register
|
||||||
|
? buildRegisterSummaryLines(register)
|
||||||
|
: ['Register details not found.'];
|
||||||
|
const decAddress = register ? `Dec ${register.dec_address}` : '';
|
||||||
|
const titleLines = wrapTextLines([title], 32, 2);
|
||||||
|
const summaryLines = wrapTextLines(summaryLinesSource, 54, 6);
|
||||||
|
|
||||||
|
return new ImageResponse(
|
||||||
|
(
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '1200px',
|
||||||
|
height: '630px',
|
||||||
|
display: 'flex',
|
||||||
|
background: 'linear-gradient(135deg, #3f1f6e 0%, #593196 55%, #7a4cc4 100%)',
|
||||||
|
color: '#f3f2ed',
|
||||||
|
fontFamily: 'Arial, sans-serif',
|
||||||
|
padding: '64px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '24px', width: '100%' }}>
|
||||||
|
<div style={{ fontSize: '28px', letterSpacing: '2px', textTransform: 'uppercase', color: '#e8dcff' }}>
|
||||||
|
Spectrum Next Explorer
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '6px',
|
||||||
|
fontSize: '68px',
|
||||||
|
fontWeight: 700,
|
||||||
|
lineHeight: 1.05,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{titleLines.map(line => (
|
||||||
|
<div key={line}>{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '6px',
|
||||||
|
fontSize: '32px',
|
||||||
|
lineHeight: 1.35,
|
||||||
|
color: '#f7f1ff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{summaryLines.map(line => (
|
||||||
|
<div key={line}>{line}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 'auto', fontSize: '26px', color: '#dacbff' }}>
|
||||||
|
{decAddress}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user