diff --git a/packages/pintora-core/src/index.ts b/packages/pintora-core/src/index.ts index c8ce862e..56de5265 100644 --- a/packages/pintora-core/src/index.ts +++ b/packages/pintora-core/src/index.ts @@ -5,8 +5,10 @@ import { logger, setLogLevel } from './logger' import configApi from './config' export * from './util' -import { encodeForUrl, decodeCodeInUrl, makeMark, calculateTextDimensions } from './util' +import { encodeForUrl, decodeCodeInUrl, makeMark, calculateTextDimensions, parseColor } from './util' import { symbolRegistry, SymbolDef, SymbolStyleAttrs } from './symbol-registry' +import { StyleParam, interpreteStyles } from './style-engine' +import * as styleEngine from './style-engine' export { logger, @@ -15,6 +17,8 @@ export { symbolRegistry, SymbolDef, SymbolStyleAttrs, + StyleParam, + interpreteStyles, } type DrawOptions = { @@ -24,6 +28,8 @@ type DrawOptions = { const pintora = { configApi, diagramRegistry, + symbolRegistry, + styleEngine, parseAndDraw(text: string, opts: DrawOptions) { const { onError } = opts const diagram = diagramRegistry.detectDiagram(text) @@ -48,8 +54,8 @@ const pintora = { decodeCodeInUrl, makeMark, calculateTextDimensions, + parseColor, }, - symbolRegistry, } export default pintora diff --git a/packages/pintora-core/src/style-engine.ts b/packages/pintora-core/src/style-engine.ts new file mode 100644 index 00000000..c3bd02c6 --- /dev/null +++ b/packages/pintora-core/src/style-engine.ts @@ -0,0 +1,62 @@ +/** + * This module evaluate custom style params to real style configs + */ +import { parseColor } from './util/color' + +export type StyleValueType = 'color' | 'fontSize' + +export type StyleMeta = { + valueType: StyleValueType | StyleValueType[] +} + +export type StyleParam = { + key: K + value: string +} + +type StyleEvaluateResult = { valid: true; value: T } | { valid: false } + +interface StyleValueTypeMap { + color: string + fontSize: number +} + +const styleValueEvaluators: { [K in StyleValueType]: (p: StyleParam) => StyleEvaluateResult } = { + color({ value }) { + const parsed = parseColor(value) + return { value: parsed.color, valid: true } + }, + fontSize({ value }) { + const parsed = parseInt(value) + if (isNaN(parsed)) return { valid: false } + return { value: parsed, valid: true } + }, +} + +type StyleRuleMap = Record + +/** + * Generate style config from style params + */ +export function interpreteStyles( + ruleMap: T, + params: StyleParam[], +): { [K in keyof T]: T[K]['valueType'] } { + const out: any = {} + params.forEach(param => { + const meta = ruleMap[param.key] + if (!meta) return + const valueTypes = Array.isArray(meta.valueType) ? meta.valueType : [meta.valueType] + for (const valueType of valueTypes) { + const evaluator = styleValueEvaluators[valueType] + if (!evaluator) continue + const result = evaluator(param) + if (result.valid) { + out[param.key] = result.value + return + } + } + }) + // console.log('interpreteStyles', out) + return out +} diff --git a/packages/pintora-diagrams/src/util/color.ts b/packages/pintora-core/src/util/color.ts similarity index 100% rename from packages/pintora-diagrams/src/util/color.ts rename to packages/pintora-core/src/util/color.ts diff --git a/packages/pintora-core/src/util/index.ts b/packages/pintora-core/src/util/index.ts index 607b541f..858b1576 100644 --- a/packages/pintora-core/src/util/index.ts +++ b/packages/pintora-core/src/util/index.ts @@ -4,3 +4,4 @@ export * from './geometry' export { IFont, calculateTextDimensions } from './text-metric' export { encodeForUrl, decodeCodeInUrl } from './encode' export { makeMark } from './mark' +export { parseColor } from './color' diff --git a/packages/pintora-diagrams/src/sequence/__tests__/sequence-parser.spec.js b/packages/pintora-diagrams/src/sequence/__tests__/sequence-parser.spec.js index 4b191a4e..ef7b53a7 100644 --- a/packages/pintora-diagrams/src/sequence/__tests__/sequence-parser.spec.js +++ b/packages/pintora-diagrams/src/sequence/__tests__/sequence-parser.spec.js @@ -431,4 +431,25 @@ sequenceDiagram }, ]) }) + + it('can parse style clause', () => { + const example = stripStartEmptyLines(` +sequenceDiagram + @style noteTextColor #00bbaa + @style messageFontSize 20 + `) + parse(example) + const ir = db.getDiagramIR() + // console.log(JSON.stringify(ir, null, 2)) + expect(ir.styleParams).toMatchObject([ + { + key: 'noteTextColor', + value: '#00bbaa', + }, + { + key: 'messageFontSize', + value: '20', + }, + ]) + }) }) diff --git a/packages/pintora-diagrams/src/sequence/artist.ts b/packages/pintora-diagrams/src/sequence/artist.ts index c811016f..d6bd658d 100644 --- a/packages/pintora-diagrams/src/sequence/artist.ts +++ b/packages/pintora-diagrams/src/sequence/artist.ts @@ -16,7 +16,7 @@ import { makeid, configApi, } from '@pintora/core' -import { db, SequenceDiagramIR, LINETYPE, Message, PLACEMENT, WrappedText, GroupAttrs } from './db' +import { db, SequenceDiagramIR, LINETYPE, Message, PLACEMENT, WrappedText } from './db' import { SequenceConf, getConf } from './config' import { getBaseNote, drawArrowTo, drawCrossTo, getBaseText, makeMark, makeLoopLabelBox } from './artist-util' import { ITheme } from '../util/themes/base' @@ -49,8 +49,8 @@ const GROUP_LABEL_MAP = { const sequenceArtist: IDiagramArtist = { draw(ir, config?) { - console.log('[draw]', ir) - conf = getConf() + // console.log('[draw]', ir) + conf = getConf(ir.styleParams) theme = (configApi.getConfig() as DiagramsConf).themeConfig.themeVariables model.init() logger.debug(`C:${JSON.stringify(conf, null, 2)}`) @@ -71,7 +71,7 @@ const sequenceArtist: IDiagramArtist = { calcLoopMinWidths(ir.messages) const maxMessageWidthPerActor = getMaxMessageWidthPerActor(ir) model.maxMessageWidthPerActor = maxMessageWidthPerActor - conf.actorHeight = calculateActorMargins(actors, maxMessageWidthPerActor) + model.actorHeight = calculateActorMargins(actors, maxMessageWidthPerActor) drawActors(rootMark, ir, { verticalPos: 0 }) const loopWidths = calculateLoopBounds(messages) @@ -295,6 +295,8 @@ class Model { /** backgrounds for groups like loop and opt */ groupBgs: Rect[] + actorHeight: number + private onBoundsFinishCbs: Array init() { @@ -312,6 +314,7 @@ class Model { this.loopMinWidths = {} this.onBoundsFinishCbs = [] this.groupBgs = [] + this.actorHeight = conf.actorHeight } clear() { this.activations = [] @@ -903,7 +906,7 @@ export const drawActors = function ( // Add some rendering data to the object safeAssign(attrs, { width: attrs.width || conf.actorWidth, - height: Math.max(attrs.height || 0, conf.actorHeight), + height: Math.max(attrs.height || 0, model.actorHeight), margin: attrs.margin || conf.actorMargin, x: prevWidth + prevMargin, y: verticalPos, @@ -971,7 +974,7 @@ export const drawActors = function ( } // Add a margin between the actor boxes and the first arrow - model.bumpVerticalPos(conf.actorHeight) + model.bumpVerticalPos(model.actorHeight) } function drawActivationTo(mark: Group, data: ActivationData) { diff --git a/packages/pintora-diagrams/src/sequence/config.ts b/packages/pintora-diagrams/src/sequence/config.ts index 3c75d2a7..fa38fe6c 100644 --- a/packages/pintora-diagrams/src/sequence/config.ts +++ b/packages/pintora-diagrams/src/sequence/config.ts @@ -2,6 +2,7 @@ import { MarkAttrs } from '@pintora/core' import { PALETTE } from '../util/theme' import { configApi, safeAssign } from '@pintora/core' import { DiagramsConf } from '../type' +import { interpreteStyles, StyleParam } from '../util/style' export type SequenceConf = { noteWidth: number @@ -84,13 +85,16 @@ export const defaultConfig: SequenceConf = { showSequenceNumbers: false, } -export const conf: SequenceConf = { - ...defaultConfig, -} +export const SEQUENCE_STYLE_RULES = { + noteTextColor: { valueType: 'color' }, + messageTextColor: { valueType: 'color' }, + messageFontSize: { valueType: 'fontSize' }, +} as const -export function getConf() { +export function getConf(styleParams: StyleParam[]) { const globalConfig: DiagramsConf = configApi.getConfig() const t = globalConfig.themeConfig.themeVariables + const conf = {...defaultConfig} safeAssign(conf, { actorBackground: t.primaryColor, actorBorderColor: t.primaryBorderColor, @@ -103,5 +107,6 @@ export function getConf() { dividerTextColor: t.secondaryTextColor, }) Object.assign(conf, globalConfig.sequence || {}) + Object.assign(conf, interpreteStyles(SEQUENCE_STYLE_RULES, styleParams)) return conf } diff --git a/packages/pintora-diagrams/src/sequence/db.ts b/packages/pintora-diagrams/src/sequence/db.ts index 7ad22d34..abfe256c 100644 --- a/packages/pintora-diagrams/src/sequence/db.ts +++ b/packages/pintora-diagrams/src/sequence/db.ts @@ -1,5 +1,5 @@ -import { Group, logger, OrNull } from '@pintora/core' -import { parseColor } from '../util/color' +import { logger, OrNull, parseColor } from '@pintora/core' +import { StyleParam } from '../util/style' export interface WrappedText { text: string @@ -91,6 +91,7 @@ export type SequenceDiagramIR = { title: string showSequenceNumbers: boolean // titleWrapped: boolean + styleParams: StyleParam[] } class SequenceDB { @@ -102,6 +103,7 @@ class SequenceDB { titleWrapped: boolean = false wrapEnabled = false showSequenceNumbers = false + styleParams: SequenceDiagramIR['styleParams'] = [] addActor(id: string, name: string, description: ParsedDescription) { // Don't allow description nulling @@ -237,6 +239,10 @@ class SequenceDB { return message } + addStyle(sp: StyleParam) { + this.styleParams.push(sp) + } + getActor(id: string) { return this.actors[id] } @@ -252,6 +258,7 @@ class SequenceDB { this.actors = {} this.title = '' this.showSequenceNumbers = false + this.styleParams = [] } getDiagramIR(): SequenceDiagramIR { @@ -261,6 +268,7 @@ class SequenceDB { actors: this.actors, title: this.title, showSequenceNumbers: this.showSequenceNumbers, + styleParams: this.styleParams, } } @@ -306,6 +314,9 @@ class SequenceDB { case 'addDivider': db.addSignalWithoutActor({ text: param.text, wrap: false }, param.signalType) break + case 'addStyle': + db.addStyle({ key: param.key, value: param.value }) + break } } } @@ -339,6 +350,9 @@ export function enableSequenceNumbers() { db.showSequenceNumbers = true; } +/** + * action param that will be handled by `apply` + */ type ApplyParam = | { type: 'addActor' @@ -382,6 +396,11 @@ type ApplyParam = signalType: LINETYPE text: string } + | { + type: 'addStyle' + key: string + value: string + } export { db diff --git a/packages/pintora-diagrams/src/sequence/parser/sequenceDiagram.ne b/packages/pintora-diagrams/src/sequence/parser/sequenceDiagram.ne index 9c423e7d..fb29044c 100644 --- a/packages/pintora-diagrams/src/sequence/parser/sequenceDiagram.ne +++ b/packages/pintora-diagrams/src/sequence/parser/sequenceDiagram.ne @@ -1,6 +1,6 @@ @{% import * as moo from 'moo' -import { tv, textToCaseInsensitiveRegex, VALID_TEXT_REGEXP } from '../../util/parser-shared' +import { tv, textToCaseInsensitiveRegex, VALID_TEXT_REGEXP, COLOR_REGEXP } from '../../util/parser-shared' let lexer = moo.states({ main: { @@ -44,6 +44,7 @@ export function setYY(v) { @lexer lexer @builtin "string.ne" @builtin "whitespace.ne" +@include "../../util/parser-grammars/style.ne" start -> __ start {% (d) => d[1] %} | "sequenceDiagram" document __:? {% @@ -148,6 +149,7 @@ statement -> return { type: 'addDivider', text, signalType: yy.LINETYPE.DIVIDER } } %} + | styleClause _ %NEWLINE words -> (%WORD | %SPACE):+ {% function(d) { @@ -271,5 +273,3 @@ par_sections -> ]) } %} - -color -> %COLOR {% (d) => tv(d[0]) %} diff --git a/packages/pintora-diagrams/src/util/parser-grammars/style.ne b/packages/pintora-diagrams/src/util/parser-grammars/style.ne new file mode 100644 index 00000000..790f712f --- /dev/null +++ b/packages/pintora-diagrams/src/util/parser-grammars/style.ne @@ -0,0 +1,19 @@ +@{% +export const COLOR = /#[a-zA-Z0-9]+/ +export const STYLE = /@style/ +%} + +color -> %COLOR {% (d) => tv(d[0]) %} + +styleClause -> + %STYLE __ [a-zA-Z0-9]:+ __ [^ \n]:+ {% + function(d) { + // console.log('[styleClause]', d[2], JSON.stringify(d[4])) + const key = d[2].map(v => tv(v)).join('') + let value = d[4] + if (typeof value !== 'string') value = value.map(v => tv(v)).join('') + // console.log('[styleClause] result', key, value) + return { type: 'addStyle', key, value } + } + %} + diff --git a/packages/pintora-diagrams/src/util/parser-shared.ts b/packages/pintora-diagrams/src/util/parser-shared.ts index 6c073387..f79c37d0 100644 --- a/packages/pintora-diagrams/src/util/parser-shared.ts +++ b/packages/pintora-diagrams/src/util/parser-shared.ts @@ -16,8 +16,12 @@ export function textToCaseInsensitiveRegex(text) { /** token value */ export function tv(token) { - return token.value + if (token && ('value' in token)) return token.value + return token } /** CJK friendly text pattern */ export const VALID_TEXT_REGEXP = /(?:[a-zA-Z0-9_]\p{Unified_Ideograph})+/ + +/** hex color */ +export const COLOR_REGEXP = /#[a-zA-Z0-9]+/ diff --git a/packages/pintora-diagrams/src/util/style.ts b/packages/pintora-diagrams/src/util/style.ts new file mode 100644 index 00000000..fea978dd --- /dev/null +++ b/packages/pintora-diagrams/src/util/style.ts @@ -0,0 +1,6 @@ +import { StyleParam, interpreteStyles } from '@pintora/core' + +export { + StyleParam, + interpreteStyles, +}