diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index d4f34de70..88957b5fb 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import type * as d3 from 'd3'; +import type { SetRequired } from 'type-fest'; import type { Diagram } from '../Diagram.js'; import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js'; -import type * as d3 from 'd3'; export interface DiagramMetadata { title?: string; @@ -32,13 +33,29 @@ export interface DiagramDB { getDiagramTitle?: () => string; setAccTitle?: (title: string) => void; getAccTitle?: () => string; - setAccDescription?: (describetion: string) => void; + setAccDescription?: (description: string) => void; getAccDescription?: () => string; setDisplayMode?: (title: string) => void; bindFunctions?: (element: Element) => void; } +/** + * DiagramDB with fields that is required for all new diagrams. + */ +export type DiagramDBBase = { + getConfig: () => Required; +} & SetRequired< + DiagramDB, + | 'clear' + | 'getAccTitle' + | 'getDiagramTitle' + | 'getAccDescription' + | 'setAccDescription' + | 'setAccTitle' + | 'setDiagramTitle' +>; + // This is what is returned from getClasses(...) methods. // It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word. // It makes it clear we're working with a style class definition, even though defining the type is currently difficult. diff --git a/packages/mermaid/src/diagrams/packet/db.ts b/packages/mermaid/src/diagrams/packet/db.ts index 96648be5f..a40b71724 100644 --- a/packages/mermaid/src/diagrams/packet/db.ts +++ b/packages/mermaid/src/diagrams/packet/db.ts @@ -1,8 +1,17 @@ -import type { Block, PacketDB, PacketData, Row } from './types.js'; +import { getConfig as commonGetConfig } from '../../config.js'; import type { PacketDiagramConfig } from '../../config.type.js'; import DEFAULT_CONFIG from '../../defaultConfig.js'; -import { getConfig as commonGetConfig } from '../../config.js'; import { cleanAndMerge } from '../../utils.js'; +import { + clear as commonClear, + getAccDescription, + getAccTitle, + getDiagramTitle, + setAccDescription, + setAccTitle, + setDiagramTitle, +} from '../common/commonDb.js'; +import type { PacketDB, PacketData, Row } from './types.js'; const defaultPacketData: PacketData = { packet: [], @@ -10,9 +19,9 @@ const defaultPacketData: PacketData = { let data: PacketData = structuredClone(defaultPacketData); -export const DEFAULT_PACKET_CONFIG: Required = DEFAULT_CONFIG.packet; +const DEFAULT_PACKET_CONFIG: Required = DEFAULT_CONFIG.packet; -export const getConfig = (): Required => { +const getConfig = (): Required => { const config = cleanAndMerge({ ...DEFAULT_PACKET_CONFIG, ...commonGetConfig().packet, @@ -23,20 +32,28 @@ export const getConfig = (): Required => { return config; }; -export const getPacket = (): Row[] => data.packet; +const getPacket = (): Row[] => data.packet; -export const pushWord = (word: Row) => { +const pushWord = (word: Row) => { if (word.length > 0) { data.packet.push(word); } }; -export const clear = () => { +const clear = () => { + commonClear(); data = structuredClone(defaultPacketData); }; export const db: PacketDB = { + pushWord, getPacket, getConfig, clear, + setAccTitle, + getAccTitle, + setDiagramTitle, + getDiagramTitle, + getAccDescription, + setAccDescription, }; diff --git a/packages/mermaid/src/diagrams/packet/diagram.ts b/packages/mermaid/src/diagrams/packet/diagram.ts index ae9cad452..a73a77c05 100644 --- a/packages/mermaid/src/diagrams/packet/diagram.ts +++ b/packages/mermaid/src/diagrams/packet/diagram.ts @@ -1,6 +1,6 @@ import type { DiagramDefinition } from '../../diagram-api/types.js'; -import { parser } from './parser.js'; import { db } from './db.js'; +import { parser } from './parser.js'; import { renderer } from './renderer.js'; import { styles } from './styles.js'; diff --git a/packages/mermaid/src/diagrams/packet/packet.spec.ts b/packages/mermaid/src/diagrams/packet/packet.spec.ts index c4ccdba74..6ad24b75e 100644 --- a/packages/mermaid/src/diagrams/packet/packet.spec.ts +++ b/packages/mermaid/src/diagrams/packet/packet.spec.ts @@ -1,5 +1,8 @@ +import { db } from './db.js'; import { parser } from './parser.js'; -import { clear, getPacket } from './db.js'; + +const { clear, getPacket } = db; + describe('packet diagrams', () => { beforeEach(() => { clear(); diff --git a/packages/mermaid/src/diagrams/packet/parser.ts b/packages/mermaid/src/diagrams/packet/parser.ts index 91f0a8706..db1dcde09 100644 --- a/packages/mermaid/src/diagrams/packet/parser.ts +++ b/packages/mermaid/src/diagrams/packet/parser.ts @@ -1,17 +1,18 @@ import type { Packet } from 'mermaid-parser'; -import type { ParserDefinition } from '../../diagram-api/types.js'; import { parse } from 'mermaid-parser'; +import type { ParserDefinition } from '../../diagram-api/types.js'; import { log } from '../../logger.js'; +import { populateCommonDb } from '../common/populateCommonDb.js'; +import { db } from './db.js'; import type { Block, Row } from './types.js'; -import { clear, getConfig, getPacket, pushWord } from './db.js'; -const populate = ({ blocks }: { blocks: Block[] }) => { - clear(); +const populate = (ast: Packet) => { + populateCommonDb(ast, db); let lastByte = -1; let word: Row = []; let row = 1; - const { bitsPerRow } = getConfig(); - for (let { start, end, label } of blocks) { + const { bitsPerRow } = db.getConfig(); + for (let { start, end, label } of ast.blocks) { if (end && end < start) { throw new Error(`Packet block ${start} - ${end} is invalid. End must be greater than start.`); } @@ -25,11 +26,11 @@ const populate = ({ blocks }: { blocks: Block[] }) => { lastByte = end ?? start; log.debug(`Packet block ${start} - ${lastByte} with label ${label}`); - while (word.length <= bitsPerRow + 1 && getPacket().length < 10_000) { + while (word.length <= bitsPerRow + 1 && db.getPacket().length < 10_000) { const [block, nextBlock] = getNextFittingBlock({ start, end, label }, row, bitsPerRow); word.push(block); if (block.end + 1 === row * bitsPerRow) { - pushWord(word); + db.pushWord(word); word = []; row++; } @@ -39,7 +40,7 @@ const populate = ({ blocks }: { blocks: Block[] }) => { ({ start, end, label } = nextBlock); } } - pushWord(word); + db.pushWord(word); }; const getNextFittingBlock = ( diff --git a/packages/mermaid/src/diagrams/packet/renderer.ts b/packages/mermaid/src/diagrams/packet/renderer.ts index 172f4a202..d846de9e7 100644 --- a/packages/mermaid/src/diagrams/packet/renderer.ts +++ b/packages/mermaid/src/diagrams/packet/renderer.ts @@ -1,9 +1,9 @@ -import { configureSvgSize } from '../../setupGraphViewbox.js'; +import type { Diagram } from '../../Diagram.js'; +import type { PacketDiagramConfig } from '../../config.type.js'; import type { DrawDefinition, Group, SVG } from '../../diagram-api/types.js'; import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; +import { configureSvgSize } from '../../setupGraphViewbox.js'; import type { PacketDB, Row } from './types.js'; -import type { PacketDiagramConfig } from '../../config.type.js'; -import type { Diagram } from '../../Diagram.js'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => { @@ -11,16 +11,27 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => { const config = db.getConfig(); const { rowHeight, paddingY, bitWidth, bitsPerRow } = config; const words = db.getPacket(); - const svgHeight = (rowHeight + paddingY) * words.length + paddingY; + const title = db.getDiagramTitle(); + const totalRowHeight = rowHeight + paddingY; + const svgHeight = totalRowHeight * (words.length + 1) - (title ? 0 : rowHeight); const svgWidth = bitWidth * bitsPerRow + 2; - const svg: SVG = selectSvgElement(id); + configureSvgSize(svg, svgHeight, svgWidth, true); svg.attr('height', svgHeight + 'px'); for (const [row, packet] of words.entries()) { drawWord(svg, packet, row, config); } + + svg + .append('text') + .text(title) + .attr('x', svgWidth / 2) + .attr('y', svgHeight - rowHeight) + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle') + .attr('class', 'packetTitle'); }; const drawWord = ( @@ -41,14 +52,14 @@ const drawWord = ( .attr('y', wordY) .attr('width', width) .attr('height', rowHeight) - .attr('class', 'block'); + .attr('class', 'packetBlock'); // Block label group .append('text') .attr('x', blockX + width / 2) .attr('y', wordY + rowHeight / 2) - .attr('class', 'label') + .attr('class', 'packetLabel') .attr('dominant-baseline', 'middle') .attr('text-anchor', 'middle') .text(block.label); @@ -63,7 +74,7 @@ const drawWord = ( .append('text') .attr('x', blockX + (isSingleBlock ? width / 2 : 0)) .attr('y', bitNumberY) - .attr('class', 'byte start') + .attr('class', 'packetByte start') .attr('dominant-baseline', 'auto') .attr('text-anchor', isSingleBlock ? 'middle' : 'start') .text(block.start); @@ -74,7 +85,7 @@ const drawWord = ( .append('text') .attr('x', blockX + width) .attr('y', bitNumberY) - .attr('class', 'byte end') + .attr('class', 'packetByte end') .attr('dominant-baseline', 'auto') .attr('text-anchor', 'end') .text(block.end); diff --git a/packages/mermaid/src/diagrams/packet/styles.ts b/packages/mermaid/src/diagrams/packet/styles.ts index 28292a027..101328da7 100644 --- a/packages/mermaid/src/diagrams/packet/styles.ts +++ b/packages/mermaid/src/diagrams/packet/styles.ts @@ -5,20 +5,24 @@ import type { PacketStyleOptions } from './types.js'; export const styles: DiagramStylesProvider = (options: { packet?: PacketStyleOptions } = {}) => { log.debug({ options }); return ` - .byte { + .packetByte { font-size: ${options.packet?.byteFontSize ?? '10px'}; } - .byte.start { + .packetByte.start { fill: ${options.packet?.startByteColor ?? 'black'}; } - .byte.end { + .packetByte.end { fill: ${options.packet?.endByteColor ?? 'black'}; } - .label { + .packetLabel { fill: ${options.packet?.labelColor ?? 'black'}; font-size: ${options.packet?.labelFontSize ?? '12px'}; } - .block { + .packetTitle { + fill: ${options.packet?.titleColor ?? 'black'}; + font-size: ${options.packet?.titleFontSize ?? '14px'}; + } + .packetBlock { stroke: ${options.packet?.blockStrokeColor ?? 'black'}; stroke-width: ${options.packet?.blockStrokeWidth ?? '1'}; fill: ${options.packet?.blockFillColor ?? '#efefef'}; diff --git a/packages/mermaid/src/diagrams/packet/types.ts b/packages/mermaid/src/diagrams/packet/types.ts index 6dc92215a..0a4507076 100644 --- a/packages/mermaid/src/diagrams/packet/types.ts +++ b/packages/mermaid/src/diagrams/packet/types.ts @@ -1,15 +1,14 @@ import type { Packet } from 'mermaid-parser'; -import type { DiagramDB } from '../../diagram-api/types.js'; import type { PacketDiagramConfig } from '../../config.type.js'; +import type { DiagramDBBase } from '../../diagram-api/types.js'; export type ArrayElement = A extends readonly (infer T)[] ? T : never; export type Block = Pick, 'start' | 'end' | 'label'>; export type Row = Required[]; -export interface PacketDB extends DiagramDB { +export interface PacketDB extends DiagramDBBase { + pushWord: (word: Row) => void; getPacket: () => Row[]; - getConfig: () => Required; - clear: () => void; } export interface PacketStyleOptions { @@ -21,6 +20,8 @@ export interface PacketStyleOptions { blockStrokeColor?: string; blockStrokeWidth?: string; blockFillColor?: string; + titleColor?: string; + titleFontSize?: string; } export interface PacketData {