Merge branch 'chrismoran-bkt-develop' into develop
This commit is contained in:
commit
8e1e7fb314
|
@ -214,7 +214,7 @@ pie
|
|||
|
||||
## Related projects
|
||||
|
||||
- [Command Line Interface](https://github.com/mermaid-js/mermaid.cli)
|
||||
- [Command Line Interface](https://github.com/mermaid-js/mermaid-cli)
|
||||
- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
||||
- [HTTP Server](https://github.com/TomWright/mermaid-server)
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('Configuration', () => {
|
|||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{
|
||||
arrowMarkerAbsolute: true
|
||||
logLevel:0, arrowMarkerAbsolute: true,fontFamily: '"Noto Sans SC", sans-serif'
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -126,6 +126,29 @@ context('Sequence diagram', () => {
|
|||
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
A->>Bob: Hola
|
||||
Bob-->A: Pasten !
|
||||
`,
|
||||
{logLevel: 0}
|
||||
);
|
||||
});
|
||||
it('should wrap (inline) long actor descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
participant A as wrap:Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
A->>Bob: Hola
|
||||
Bob-->A: Pasten !
|
||||
`,
|
||||
{logLevel: 0}
|
||||
);
|
||||
});
|
||||
it('should wrap (directive) long actor descriptions', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {'config': {'wrapEnabled': true }}}%%
|
||||
sequenceDiagram
|
||||
participant A as Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
A->>Bob: Hola
|
||||
Bob-->A: Pasten !
|
||||
`,
|
||||
{}
|
||||
);
|
||||
|
@ -141,6 +164,17 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes wrapped (inline) left of actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note left of Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes right of actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
@ -152,6 +186,17 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes wrapped (inline) right of actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note right of Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes over actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
@ -163,6 +208,17 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render long notes wrapped (inline) over actor', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hola
|
||||
Note over Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages from an actor to the left to one to the right', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
@ -173,6 +229,16 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages wrapped (inline) from an actor to the left to one to the right', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob:wrap:Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
Bob->>Alice: I'm short though
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages from an actor to the right to one to the left', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
@ -183,6 +249,16 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render long messages wrapped (inline) from an actor to the right to one to the left', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
Bob->>Alice:wrap: Extremely utterly long line of longness which had preivously overflown the actor box as it is much longer than what it should be
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
||||
context('background rects', () => {
|
||||
it('should render a single and nested rects', () => {
|
||||
|
@ -216,6 +292,69 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render a single and nested opt with long test overflowing', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
participant A
|
||||
participant B
|
||||
participant C
|
||||
participant D
|
||||
participant E
|
||||
participant G
|
||||
|
||||
A ->>+ B: Task 1
|
||||
opt this is an opt with a long title that will overflow
|
||||
B ->>+ C: Task 2
|
||||
C -->>- B: Return
|
||||
end
|
||||
|
||||
A ->> D: Task 3
|
||||
opt this is another opt with a long title that will overflow
|
||||
D ->>+ E: Task 4
|
||||
opt this is a nested opt with a long title that will overflow
|
||||
E ->>+ G: Task 5
|
||||
G -->>- E: Return
|
||||
end
|
||||
E ->> E: Task 6
|
||||
end
|
||||
D -->> A: Complete
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render a single and nested opt with long test wrapping', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: { 'config': { 'wrapEnabled': true } } }%%
|
||||
sequenceDiagram
|
||||
participant A
|
||||
participant B
|
||||
participant C
|
||||
participant D
|
||||
participant E
|
||||
participant G
|
||||
|
||||
A ->>+ B: Task 1
|
||||
opt this is an opt with a long title that will overflow
|
||||
B ->>+ C: Task 2
|
||||
C -->>- B: Return
|
||||
end
|
||||
|
||||
A ->> D: Task 3
|
||||
opt this is another opt with a long title that will overflow
|
||||
D ->>+ E: Task 4
|
||||
opt this is a nested opt with a long title that will overflow
|
||||
E ->>+ G: Task 5
|
||||
G -->>- E: Return
|
||||
end
|
||||
E ->> E: Task 6
|
||||
end
|
||||
D -->> A: Complete
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render rect around and inside loops', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
@ -327,5 +466,69 @@ context('Sequence diagram', () => {
|
|||
{}
|
||||
);
|
||||
});
|
||||
it('should render dark theme from init directive and configure font size 24 font', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: {'theme': 'dark', 'config': {'fontSize': 24}}}%%
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
Alice->>John: John, can you hear me?
|
||||
John-->>Alice: Hi Alice, I can hear you!
|
||||
John-->>Alice: I feel great!
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render with wrapping enabled', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: { 'config': { 'wrapEnabled': true }}}%%
|
||||
sequenceDiagram
|
||||
participant A as Alice, the talkative one
|
||||
A->>John: Hello John, how are you today? I'm feeling quite verbose today.
|
||||
A->>John: John, can you hear me? If you are not available, we can talk later.
|
||||
John-->>A: Hi Alice, I can hear you! I was finishing up an important meeting.
|
||||
John-->>A: I feel great! I was not ignoring you. I am sorry you had to wait for a response.
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should render with an init directive', () => {
|
||||
imgSnapshotTest(
|
||||
`%%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrapEnabled": true }}}%%
|
||||
sequenceDiagram
|
||||
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
|
||||
Note left of Alice: Bob thinks
|
||||
Bob->>Alice: Fine!`,
|
||||
{}
|
||||
)
|
||||
});
|
||||
});
|
||||
context('directives', () => {
|
||||
it('should overide config with directive settings', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: { "sequence": { "mirrorActors": true }}}%%
|
||||
sequenceDiagram
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: config set to mirrorActors: false<br/>directive set to mirrorActors: true
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ logLevel:0, sequence: { mirrorActors: false, noteFontSize: 18, noteFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
it('should overide config with directive settings', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
%%{init: { "sequence": { "mirrorActors": false }}}%%
|
||||
sequenceDiagram
|
||||
%%{config: { "mirrorActors": false} }%%
|
||||
Alice->>Bob: I'm short
|
||||
note left of Alice: config set to mirrorActors: true<br/>directive set to mirrorActors: false
|
||||
Bob->>Alice: Short as well
|
||||
`,
|
||||
{ logLevel:0, sequence: { mirrorActors: true, noteFontSize: 18, noteFontFamily: 'Arial' } }
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,6 +21,14 @@
|
|||
<body>
|
||||
<h1>info below</h1>
|
||||
<div class="mermaid" style="width: 50%; height: 20%;">
|
||||
graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
</div>
|
||||
<div class="mermaid2" style="width: 50%; height: 20%;">
|
||||
flowchart TB
|
||||
subgraph 1
|
||||
A --> B;
|
||||
|
@ -64,13 +72,14 @@ flowchart TB
|
|||
// arrowMarkerAbsolute: true,
|
||||
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
|
||||
logLevel: 0,
|
||||
flowchart: { curve: 'linear', "htmlLabels": false },
|
||||
flowchart: { curve: 'linear',htmlLabels: false },
|
||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50, showSequenceNumbers: true },
|
||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||
fontFamily: '"arial", sans-serif',
|
||||
curve: 'linear',
|
||||
securityLevel: 'loose'
|
||||
securityLevel: 'loose',
|
||||
htmlLabels: false
|
||||
});
|
||||
function callback(){alert('It worked');}
|
||||
</script>
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
<style>
|
||||
body {
|
||||
/* font-family: 'Mansalva', cursive;*/
|
||||
font-family: 'Mansalva', cursive;
|
||||
font-family: '"Noto Sans SC", sans-serif';
|
||||
}
|
||||
.mermaid-main-font {
|
||||
font-family: '"Noto Sans SC", sans-serif';
|
||||
/* font-family: var(--mermaid-font-family); */
|
||||
}
|
||||
/* .mermaid-main-font {
|
||||
font-family: "trebuchet ms", verdana, arial;
|
||||
font-family: var(--mermaid-font-family);
|
||||
} */
|
||||
/* :root {
|
||||
--mermaid-font-family: '"trebuchet ms", verdana, arial';
|
||||
--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive;
|
||||
|
@ -27,7 +27,7 @@
|
|||
<script>
|
||||
// Notice startOnLoad=false
|
||||
// This prevents default handling in mermaid from render before the e2e logic is applied
|
||||
mermaid.initialize({
|
||||
window.mermaidDefault = {
|
||||
startOnLoad: false,
|
||||
useMaxWidth: true,
|
||||
// "themeCSS": ":root { --mermaid-font-family: \"trebuchet ms\", verdana, arial;}",
|
||||
|
@ -35,7 +35,8 @@
|
|||
// fontFamily: '"Comic Sans MS", "Comic Sans", cursive'
|
||||
// fontFamily: '"Mansalva", cursive',
|
||||
fontFamily: '"Noto Sans SC", sans-serif'
|
||||
});
|
||||
};
|
||||
mermaid.initialize(window.mermaidDefault);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -30,6 +30,11 @@ const contentLoaded = function() {
|
|||
div.innerHTML = graphObj.code;
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
|
||||
if (window.mermaidDefault.fontFamily) {
|
||||
graphObj.mermaid.fontFamily = window.mermaidDefault.fontFamily;
|
||||
}
|
||||
|
||||
global.mermaid.initialize(graphObj.mermaid);
|
||||
global.mermaid.init();
|
||||
}
|
||||
|
|
|
@ -832,7 +832,8 @@ function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "functi
|
|||
var config = {};
|
||||
|
||||
var setConf = function setConf(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
console.log('config::setConf', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
|
||||
var lvl1Keys = Object.keys(cnf);
|
||||
|
||||
for (var i = 0; i < lvl1Keys.length; i++) {
|
||||
|
@ -852,6 +853,8 @@ var setConf = function setConf(cnf) {
|
|||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('config::setConf done', config);
|
||||
};
|
||||
|
||||
var setConfig = function setConfig(conf) {
|
||||
|
@ -21478,7 +21481,8 @@ var render = function render(id, _txt, cb, container) {
|
|||
};
|
||||
|
||||
var setConf = function setConf(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
console.log('set conf ', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
|
||||
var lvl1Keys = Object.keys(cnf);
|
||||
|
||||
for (var i = 0; i < lvl1Keys.length; i++) {
|
||||
|
@ -21499,6 +21503,8 @@ var setConf = function setConf(cnf) {
|
|||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('set conf done', config);
|
||||
};
|
||||
|
||||
function reinitialize(options) {
|
||||
|
@ -21516,7 +21522,16 @@ function reinitialize(options) {
|
|||
}
|
||||
|
||||
function initialize(options) {
|
||||
var _config = config;
|
||||
console.log('initialize ', options, config, Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])());
|
||||
|
||||
var _config = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])(); // Set default options
|
||||
|
||||
|
||||
if (_typeof(options) === 'object') {
|
||||
setConf(_config);
|
||||
Object(_config__WEBPACK_IMPORTED_MODULE_3__["setConfig"])(_config);
|
||||
}
|
||||
|
||||
_logger__WEBPACK_IMPORTED_MODULE_4__["logger"].debug('Initializing mermaidAPI ', {
|
||||
version: _package_json__WEBPACK_IMPORTED_MODULE_2__.version,
|
||||
options: options,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -46615,7 +46615,8 @@ function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "functi
|
|||
var config = {};
|
||||
|
||||
var setConf = function setConf(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
console.log('config::setConf', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
|
||||
var lvl1Keys = Object.keys(cnf);
|
||||
|
||||
for (var i = 0; i < lvl1Keys.length; i++) {
|
||||
|
@ -46635,6 +46636,8 @@ var setConf = function setConf(cnf) {
|
|||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('config::setConf done', config);
|
||||
};
|
||||
|
||||
var setConfig = function setConfig(conf) {
|
||||
|
@ -67236,7 +67239,8 @@ var render = function render(id, _txt, cb, container) {
|
|||
};
|
||||
|
||||
var setConf = function setConf(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
console.log('set conf ', cnf); // Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
|
||||
var lvl1Keys = Object.keys(cnf);
|
||||
|
||||
for (var i = 0; i < lvl1Keys.length; i++) {
|
||||
|
@ -67257,6 +67261,8 @@ var setConf = function setConf(cnf) {
|
|||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
}
|
||||
|
||||
console.log('set conf done', config);
|
||||
};
|
||||
|
||||
function reinitialize(options) {
|
||||
|
@ -67274,7 +67280,16 @@ function reinitialize(options) {
|
|||
}
|
||||
|
||||
function initialize(options) {
|
||||
var _config = config;
|
||||
console.log('initialize ', options, config, Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])());
|
||||
|
||||
var _config = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getConfig"])(); // Set default options
|
||||
|
||||
|
||||
if (_typeof(options) === 'object') {
|
||||
setConf(_config);
|
||||
Object(_config__WEBPACK_IMPORTED_MODULE_3__["setConfig"])(_config);
|
||||
}
|
||||
|
||||
_logger__WEBPACK_IMPORTED_MODULE_4__["logger"].debug('Initializing mermaidAPI ', {
|
||||
version: _package_json__WEBPACK_IMPORTED_MODULE_2__.version,
|
||||
options: options,
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -34,7 +34,7 @@
|
|||
window.$docsify = {
|
||||
search: 'auto',
|
||||
name: 'mermaid',
|
||||
repo: 'https://github.com/knsv/mermaid',
|
||||
repo: 'https://github.com/mermaid-js/mermaid',
|
||||
loadSidebar: true,
|
||||
mergeNavbar: true,
|
||||
maxLevel: 4,
|
||||
|
|
|
@ -454,12 +454,11 @@ mermaidAPI.initialize({
|
|||
### Parameters
|
||||
|
||||
- `id` the id of the element to be rendered
|
||||
- `_txt`
|
||||
- `_txt` the graph definition
|
||||
- `cb` callback which is called after rendering is finished with the svg code as inparam.
|
||||
- `container` selector to element in which a div with the graph temporarily will be inserted. In one is
|
||||
provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
|
||||
completed.
|
||||
- `txt` the graph definition
|
||||
|
||||
##
|
||||
|
||||
|
|
|
@ -1,36 +1,19 @@
|
|||
let config = {};
|
||||
|
||||
const setConf = function(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
const lvl1Keys = Object.keys(cnf);
|
||||
for (let i = 0; i < lvl1Keys.length; i++) {
|
||||
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
|
||||
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]);
|
||||
|
||||
for (let j = 0; j < lvl2Keys.length; j++) {
|
||||
// logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
|
||||
if (typeof config[lvl1Keys[i]] === 'undefined') {
|
||||
config[lvl1Keys[i]] = {};
|
||||
}
|
||||
// logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
|
||||
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]];
|
||||
}
|
||||
} else {
|
||||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
}
|
||||
};
|
||||
import { assignWithDepth } from './utils';
|
||||
const config = {};
|
||||
|
||||
export const setConfig = conf => {
|
||||
setConf(conf);
|
||||
assignWithDepth(config, conf);
|
||||
};
|
||||
export const getConfig = () => config;
|
||||
|
||||
export const reset = conf => {
|
||||
Object.keys(config).forEach(key => delete config[key]);
|
||||
assignWithDepth(config, conf, { clobber: true });
|
||||
};
|
||||
|
||||
const configApi = {
|
||||
setConfig,
|
||||
getConfig
|
||||
// get conf() {
|
||||
// return config;
|
||||
// }
|
||||
getConfig,
|
||||
reset
|
||||
};
|
||||
export default configApi;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { logger } from '../../logger';
|
||||
import { getConfig, setConfig } from '../../config';
|
||||
import mermaidAPI from '../../mermaidAPI';
|
||||
|
||||
let prevActor = undefined;
|
||||
|
@ -10,7 +9,6 @@ let title = '';
|
|||
let titleWrapped = false;
|
||||
let sequenceNumbersEnabled = false;
|
||||
let wrapEnabled = false;
|
||||
let configUpdated = false;
|
||||
let currentDirective = {};
|
||||
|
||||
export const parseDirective = function(statement, context) {
|
||||
|
@ -46,18 +44,24 @@ const handleDirective = function(directive) {
|
|||
switch (directive.type) {
|
||||
case 'init':
|
||||
case 'initialize':
|
||||
['config'].forEach(prop => {
|
||||
if (typeof directive.args[prop] !== 'undefined') {
|
||||
directive.args.sequence = directive.args[prop];
|
||||
delete directive.args[prop];
|
||||
}
|
||||
});
|
||||
|
||||
mermaidAPI.initialize(directive.args);
|
||||
break;
|
||||
case 'config':
|
||||
updateConfig(directive.args);
|
||||
break;
|
||||
case 'wrap':
|
||||
case 'nowrap':
|
||||
wrapEnabled = directive.type === 'wrap';
|
||||
break;
|
||||
default:
|
||||
logger.warn(
|
||||
`Unrecognized directive: source: '%%{${directive.type}: ${directive.args}}%%`,
|
||||
`Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify(
|
||||
directive.args ? directive.args : {}
|
||||
)}}%%`,
|
||||
directive
|
||||
);
|
||||
break;
|
||||
|
@ -77,7 +81,7 @@ export const addActor = function(id, name, description) {
|
|||
actors[id] = {
|
||||
name: name,
|
||||
description: description.text,
|
||||
wrap: (description.wrap === null && autoWrap()) || !!description.wrap,
|
||||
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
|
||||
prevActor: prevActor
|
||||
};
|
||||
if (prevActor && actors[prevActor]) {
|
||||
|
@ -111,12 +115,17 @@ export const addMessage = function(idFrom, idTo, message, answer) {
|
|||
from: idFrom,
|
||||
to: idTo,
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
|
||||
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
|
||||
answer: answer
|
||||
});
|
||||
};
|
||||
|
||||
export const addSignal = function(idFrom, idTo, message = { text: null, wrap: null }, messageType) {
|
||||
export const addSignal = function(
|
||||
idFrom,
|
||||
idTo,
|
||||
message = { text: undefined, wrap: undefined },
|
||||
messageType
|
||||
) {
|
||||
logger.debug(
|
||||
'Adding message from=' +
|
||||
idFrom +
|
||||
|
@ -150,7 +159,7 @@ export const addSignal = function(idFrom, idTo, message = { text: null, wrap: nu
|
|||
from: idFrom,
|
||||
to: idTo,
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
|
||||
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
|
||||
type: messageType
|
||||
});
|
||||
return true;
|
||||
|
@ -180,12 +189,8 @@ export const enableSequenceNumbers = function() {
|
|||
};
|
||||
export const showSequenceNumbers = () => sequenceNumbersEnabled;
|
||||
|
||||
export const enableWrap = function() {
|
||||
wrapEnabled = true;
|
||||
};
|
||||
|
||||
export const disableWrap = function() {
|
||||
wrapEnabled = false;
|
||||
export const setWrap = function(wrapSetting) {
|
||||
wrapEnabled = wrapSetting;
|
||||
};
|
||||
|
||||
export const autoWrap = () => wrapEnabled;
|
||||
|
@ -193,12 +198,11 @@ export const autoWrap = () => wrapEnabled;
|
|||
export const clear = function() {
|
||||
actors = {};
|
||||
messages = [];
|
||||
configUpdated = false;
|
||||
};
|
||||
|
||||
export const parseMessage = function(str) {
|
||||
const _str = str.trim();
|
||||
return {
|
||||
const retVal = {
|
||||
text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(),
|
||||
wrap:
|
||||
_str.match(/^[:]?(?:no)?wrap:/) === null
|
||||
|
@ -209,6 +213,8 @@ export const parseMessage = function(str) {
|
|||
? false
|
||||
: autoWrap()
|
||||
};
|
||||
logger.debug(`ParseMessage[${str}] [${JSON.stringify(retVal, null, 2)}`);
|
||||
return retVal;
|
||||
};
|
||||
|
||||
export const LINETYPE = {
|
||||
|
@ -251,7 +257,7 @@ export const addNote = function(actor, placement, message) {
|
|||
actor: actor,
|
||||
placement: placement,
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap
|
||||
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap
|
||||
};
|
||||
|
||||
// Coerce actor into a [to, from, ...] array
|
||||
|
@ -262,7 +268,7 @@ export const addNote = function(actor, placement, message) {
|
|||
from: actors[0],
|
||||
to: actors[1],
|
||||
message: message.text,
|
||||
wrap: (message.wrap === null && autoWrap()) || !!message.wrap,
|
||||
wrap: (message.wrap === undefined && autoWrap()) || !!message.wrap,
|
||||
type: LINETYPE.NOTE,
|
||||
placement: placement
|
||||
});
|
||||
|
@ -270,20 +276,7 @@ export const addNote = function(actor, placement, message) {
|
|||
|
||||
export const setTitle = function(titleWrap) {
|
||||
title = titleWrap.text;
|
||||
titleWrapped = (titleWrap.wrap === null && autoWrap()) || !!titleWrap.wrap;
|
||||
};
|
||||
|
||||
export const updateConfig = function(config = getConfig()) {
|
||||
try {
|
||||
setConfig(config);
|
||||
configUpdated = true;
|
||||
} catch (error) {
|
||||
logger.error('Error: unable to parse config');
|
||||
}
|
||||
};
|
||||
|
||||
export const hasConfigChange = function() {
|
||||
return configUpdated;
|
||||
titleWrapped = (titleWrap.wrap === undefined && autoWrap()) || !!titleWrap.wrap;
|
||||
};
|
||||
|
||||
export const apply = function(param) {
|
||||
|
@ -355,20 +348,16 @@ export default {
|
|||
addActor,
|
||||
addMessage,
|
||||
addSignal,
|
||||
enableWrap,
|
||||
disableWrap,
|
||||
autoWrap,
|
||||
setWrap,
|
||||
enableSequenceNumbers,
|
||||
showSequenceNumbers,
|
||||
autoWrap,
|
||||
getMessages,
|
||||
getActors,
|
||||
getActor,
|
||||
getActorKeys,
|
||||
getTitle,
|
||||
parseDirective,
|
||||
hasConfigChange,
|
||||
getConfig,
|
||||
updateConfig,
|
||||
getTitleWrapped,
|
||||
clear,
|
||||
parseMessage,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-env jasmine */
|
||||
import { parser } from './parser/sequenceDiagram';
|
||||
import sequenceDb from './sequenceDb';
|
||||
import renderer, { calculateTextHeight, calculateTextWidth } from './sequenceRenderer';
|
||||
import renderer from './sequenceRenderer';
|
||||
import mermaidAPI from '../../mermaidAPI';
|
||||
|
||||
function addConf(conf, key, value) {
|
||||
|
@ -10,7 +10,6 @@ function addConf(conf, key, value) {
|
|||
}
|
||||
return conf;
|
||||
}
|
||||
|
||||
describe('when parsing a sequenceDiagram', function() {
|
||||
beforeEach(function() {
|
||||
parser.yy = sequenceDb;
|
||||
|
@ -774,6 +773,7 @@ end`;
|
|||
describe('when checking the bounds in a sequenceDiagram', function() {
|
||||
let conf;
|
||||
beforeEach(function() {
|
||||
mermaidAPI.reset();
|
||||
parser.yy = sequenceDb;
|
||||
parser.yy.clear();
|
||||
conf = {
|
||||
|
@ -788,10 +788,11 @@ describe('when checking the bounds in a sequenceDiagram', function() {
|
|||
boxTextMargin: 15,
|
||||
noteMargin: 25
|
||||
};
|
||||
renderer.setConf(conf);
|
||||
|
||||
mermaidAPI.initialize({ sequence: conf });
|
||||
renderer.bounds.init();
|
||||
});
|
||||
it('it should handle a simple bound call', function() {
|
||||
renderer.bounds.init();
|
||||
|
||||
renderer.bounds.insert(100, 100, 200, 200);
|
||||
|
||||
|
@ -802,7 +803,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
|
|||
expect(bounds.stopy).toBe(200);
|
||||
});
|
||||
it('it should handle an expanding bound', function() {
|
||||
renderer.bounds.init();
|
||||
|
||||
renderer.bounds.insert(100, 100, 200, 200);
|
||||
renderer.bounds.insert(25, 50, 300, 400);
|
||||
|
@ -814,7 +814,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
|
|||
expect(bounds.stopy).toBe(400);
|
||||
});
|
||||
it('it should handle inserts within the bound without changing the outer bounds', function() {
|
||||
renderer.bounds.init();
|
||||
|
||||
renderer.bounds.insert(100, 100, 200, 200);
|
||||
renderer.bounds.insert(25, 50, 300, 400);
|
||||
|
@ -827,7 +826,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
|
|||
expect(bounds.stopy).toBe(400);
|
||||
});
|
||||
it('it should handle a loop without expanding the area', function() {
|
||||
renderer.bounds.init();
|
||||
|
||||
renderer.bounds.insert(25, 50, 300, 400);
|
||||
renderer.bounds.verticalPos = 150;
|
||||
|
@ -850,7 +848,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
|
|||
expect(bounds.stopy).toBe(400);
|
||||
});
|
||||
it('it should handle multiple loops withtout expanding the bounds', function() {
|
||||
renderer.bounds.init();
|
||||
|
||||
renderer.bounds.insert(100, 100, 1000, 1000);
|
||||
renderer.bounds.verticalPos = 200;
|
||||
|
@ -883,7 +880,6 @@ describe('when checking the bounds in a sequenceDiagram', function() {
|
|||
expect(bounds.stopy).toBe(1000);
|
||||
});
|
||||
it('it should handle a loop that expands the area', function() {
|
||||
renderer.bounds.init();
|
||||
|
||||
renderer.bounds.insert(100, 100, 200, 200);
|
||||
renderer.bounds.verticalPos = 200;
|
||||
|
@ -910,6 +906,7 @@ describe('when checking the bounds in a sequenceDiagram', function() {
|
|||
describe('when rendering a sequenceDiagram', function() {
|
||||
let conf;
|
||||
beforeEach(function() {
|
||||
mermaidAPI.reset();
|
||||
parser.yy = sequenceDb;
|
||||
parser.yy.clear();
|
||||
|
||||
|
@ -923,19 +920,22 @@ describe('when rendering a sequenceDiagram', function() {
|
|||
boxMargin: 10,
|
||||
messageMargin: 40,
|
||||
boxTextMargin: 15,
|
||||
noteMargin: 25
|
||||
noteMargin: 25,
|
||||
wrapEnabled: false,
|
||||
mirrorActors: false
|
||||
};
|
||||
renderer.setConf(conf);
|
||||
mermaidAPI.initialize({ sequence: conf });
|
||||
renderer.bounds.init();
|
||||
});
|
||||
['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) {
|
||||
it(`
|
||||
it should handle one actor, when textPlacement is ${textPlacement}`, function() {
|
||||
renderer.setConf(addConf(conf, 'textPlacement', textPlacement));
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
participant Alice`;
|
||||
|
||||
mermaidAPI.initialize(addConf(conf, 'textPlacement', textPlacement));
|
||||
renderer.bounds.init();
|
||||
parser.parse(str);
|
||||
renderer.draw(str, 'tst');
|
||||
|
||||
|
@ -947,8 +947,6 @@ participant Alice`;
|
|||
});
|
||||
});
|
||||
it('it should handle same actor with different whitespace properly', function() {
|
||||
renderer.bounds.init();
|
||||
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
|
@ -962,7 +960,6 @@ participant Alice
|
|||
expect(Object.keys(actors)).toEqual(['Alice']);
|
||||
});
|
||||
it('it should handle one actor and a centered note', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
|
@ -980,7 +977,6 @@ Note over Alice: Alice thinks
|
|||
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10);
|
||||
});
|
||||
it('it should handle one actor and a note to the left', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
|
@ -997,7 +993,6 @@ Note left of Alice: Alice thinks`;
|
|||
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10);
|
||||
});
|
||||
it('it should handle one actor and a note to the right', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
|
@ -1014,7 +1009,6 @@ Note right of Alice: Alice thinks`;
|
|||
expect(bounds.stopy).toBe(conf.height + conf.boxMargin + 2 * conf.noteMargin + 10);
|
||||
});
|
||||
it('it should handle two actors', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?`;
|
||||
|
@ -1026,10 +1020,9 @@ Alice->Bob: Hello Bob, how are you?`;
|
|||
expect(bounds.startx).toBe(0);
|
||||
expect(bounds.starty).toBe(0);
|
||||
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
|
||||
expect(bounds.stopy).toBe(0 + conf.messageMargin + conf.height);
|
||||
expect(bounds.stopy).toBe(conf.messageMargin + conf.height);
|
||||
});
|
||||
it('it should handle two actors with init directive', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
%%{init: {'logLevel': 0}}%%
|
||||
sequenceDiagram
|
||||
|
@ -1044,10 +1037,9 @@ Alice->Bob: Hello Bob, how are you?`;
|
|||
expect(bounds.startx).toBe(0);
|
||||
expect(bounds.starty).toBe(0);
|
||||
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
|
||||
expect(bounds.stopy).toBe(0 + conf.messageMargin + conf.height);
|
||||
expect(bounds.stopy).toBe(conf.height + conf.messageMargin + (conf.mirrorActors ? 2 * conf.boxMargin + conf.height : 0));
|
||||
});
|
||||
it('it should handle two actors with init directive with multiline directive', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
%%{init: { 'logLevel': 0}}%%
|
||||
sequenceDiagram
|
||||
|
@ -1059,16 +1051,18 @@ Alice->Bob: Hello Bob, how are you?`;
|
|||
parser.parse(str);
|
||||
renderer.draw(str, 'tst');
|
||||
|
||||
const msgs = parser.yy.getMessages();
|
||||
const bounds = renderer.bounds.getBounds();
|
||||
const mermaid = mermaidAPI.getConfig();
|
||||
expect(mermaid.logLevel).toBe(0);
|
||||
expect(bounds.startx).toBe(0);
|
||||
expect(bounds.starty).toBe(0);
|
||||
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
|
||||
expect(bounds.stopy).toBe(0 + conf.messageMargin + conf.height);
|
||||
expect(bounds.stopy).toBe(conf.messageMargin + conf.height);
|
||||
expect(msgs.every(v => v.wrap)).toBe(true);
|
||||
|
||||
});
|
||||
it('it should handle two actors and two centered shared notes', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
|
@ -1088,7 +1082,6 @@ Note over Bob,Alice: Looks back
|
|||
);
|
||||
});
|
||||
it('it should draw two actors and two messages', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
|
@ -1104,7 +1097,6 @@ Bob->Alice: Fine!`;
|
|||
expect(bounds.stopy).toBe(0 + 2 * conf.messageMargin + conf.height);
|
||||
});
|
||||
it('it should draw two actors notes to the right', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
|
@ -1126,7 +1118,6 @@ Bob->Alice: Fine!`;
|
|||
);
|
||||
});
|
||||
it('it should draw two actors notes to the left', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
|
@ -1146,7 +1137,6 @@ Bob->Alice: Fine!`;
|
|||
);
|
||||
});
|
||||
it('it should draw two actors notes to the left with text wrapped (inline)', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->>Bob:wrap: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
|
||||
|
@ -1168,7 +1158,6 @@ Bob->>Alice: Fine!`;
|
|||
);
|
||||
});
|
||||
it('it should draw two actors notes to the left with text wrapped (directive)', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
%%{init: { 'theme': 'dark' } }%%
|
||||
sequenceDiagram
|
||||
|
@ -1194,7 +1183,6 @@ Bob->>Alice: Fine!`;
|
|||
);
|
||||
});
|
||||
it('it should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
%%{init:{'theme':'dark'}}%%
|
||||
sequenceDiagram
|
||||
|
@ -1202,6 +1190,7 @@ sequenceDiagram
|
|||
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
|
||||
Note left of Alice: Bob thinks
|
||||
Bob->>Alice: Fine!`;
|
||||
|
||||
parser.parse(str);
|
||||
renderer.draw(str, 'tst');
|
||||
|
||||
|
@ -1218,17 +1207,16 @@ Bob->>Alice: Fine!`;
|
|||
2 * conf.messageMargin + conf.height + conf.boxMargin + 10 + 2 * conf.noteMargin
|
||||
);
|
||||
});
|
||||
it('it should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', function() {
|
||||
renderer.bounds.init();
|
||||
it('it should draw two actors, notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', function() {
|
||||
const str = `
|
||||
%%{init: { "theme": "dark" }}%%
|
||||
%%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400, "wrapEnabled": true }}}%%
|
||||
sequenceDiagram
|
||||
%%{config: { "fontFamily": "Menlo", "fontSize": 18, "fontWeight": 400 } }%%
|
||||
%%{wrap}%%
|
||||
Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can!
|
||||
Note left of Alice: Bob thinks
|
||||
Bob->>Alice: Fine!`;
|
||||
|
||||
parser.parse(str);
|
||||
// renderer.setConf(mermaidAPI.getConfig().sequence);
|
||||
renderer.draw(str, 'tst');
|
||||
|
||||
const bounds = renderer.bounds.getBounds();
|
||||
|
@ -1237,9 +1225,9 @@ Bob->>Alice: Fine!`;
|
|||
expect(bounds.startx).toBe(-(conf.width / 2) - conf.actorMargin / 2);
|
||||
expect(bounds.starty).toBe(0);
|
||||
expect(mermaid.theme).toBe('dark');
|
||||
expect(mermaid.fontFamily).toBe('Menlo');
|
||||
expect(mermaid.fontSize).toBe(18);
|
||||
expect(mermaid.fontWeight).toBe(400);
|
||||
expect(mermaid.sequence.fontFamily).toBe('Menlo');
|
||||
expect(mermaid.sequence.fontSize).toBe(18);
|
||||
expect(mermaid.sequence.fontWeight).toBe(400);
|
||||
expect(msgs.every(v => v.wrap)).toBe(true);
|
||||
|
||||
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
|
||||
|
@ -1248,13 +1236,13 @@ Bob->>Alice: Fine!`;
|
|||
);
|
||||
});
|
||||
it('it should draw two loops', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
loop Cheers
|
||||
Bob->Alice: Fine!
|
||||
end`;
|
||||
|
||||
parser.parse(str);
|
||||
renderer.draw(str, 'tst');
|
||||
|
||||
|
@ -1268,7 +1256,6 @@ end`;
|
|||
);
|
||||
});
|
||||
it('it should draw background rect', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, are you alright?
|
||||
|
@ -1290,6 +1277,7 @@ end`;
|
|||
describe('when rendering a sequenceDiagram with actor mirror activated', function() {
|
||||
let conf;
|
||||
beforeEach(function() {
|
||||
mermaidAPI.reset();
|
||||
parser.yy = sequenceDb;
|
||||
parser.yy.clear();
|
||||
|
||||
|
@ -1309,11 +1297,12 @@ describe('when rendering a sequenceDiagram with actor mirror activated', functio
|
|||
// Prolongs the edge of the diagram downwards
|
||||
bottomMarginAdj: 1
|
||||
};
|
||||
renderer.setConf(conf);
|
||||
mermaidAPI.initialize({ sequence: conf });
|
||||
renderer.bounds.init();
|
||||
});
|
||||
['tspan', 'fo', 'old', undefined].forEach(function(textPlacement) {
|
||||
it('it should handle one actor, when textPlacement is' + textPlacement, function() {
|
||||
renderer.setConf(addConf(conf, 'textPlacement', textPlacement));
|
||||
mermaidAPI.initialize(addConf(conf, 'textPlacement', textPlacement));
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
|
@ -1334,6 +1323,7 @@ participant Alice`;
|
|||
describe('when rendering a sequenceDiagram with directives', function() {
|
||||
let conf;
|
||||
beforeEach(function() {
|
||||
mermaidAPI.reset();
|
||||
parser.yy = sequenceDb;
|
||||
parser.yy.clear();
|
||||
conf = {
|
||||
|
@ -1348,11 +1338,11 @@ describe('when rendering a sequenceDiagram with directives', function() {
|
|||
boxTextMargin: 15,
|
||||
noteMargin: 25
|
||||
};
|
||||
renderer.setConf(conf);
|
||||
mermaidAPI.initialize({ sequence: conf });
|
||||
renderer.bounds.init();
|
||||
});
|
||||
|
||||
it('it should handle one actor, when theme is dark and logLevel is 1', function() {
|
||||
renderer.bounds.init();
|
||||
it('it should handle one actor, when theme is dark and logLevel is 1 DX1', function() {
|
||||
const str = `
|
||||
%%{init: { "theme": "dark", "logLevel": 1 } }%%
|
||||
sequenceDiagram
|
||||
|
@ -1373,7 +1363,6 @@ participant Alice
|
|||
expect(bounds.stopy).toBe(2 * conf.height + 2 * conf.boxMargin);
|
||||
});
|
||||
it('it should handle one actor, when logLevel is 3', function() {
|
||||
renderer.bounds.init();
|
||||
const str = `
|
||||
%%{initialize: { "logLevel": 3 }}%%
|
||||
sequenceDiagram
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { select, selectAll } from 'd3';
|
||||
import svgDraw from './svgDraw';
|
||||
import svgDraw, { drawText } from './svgDraw';
|
||||
import { logger } from '../../logger';
|
||||
import { parser } from './parser/sequenceDiagram';
|
||||
import common from '../common/common';
|
||||
import sequenceDb from './sequenceDb';
|
||||
import { getConfig } from '../../config';
|
||||
import utils, { assignWithDepth } from '../../utils';
|
||||
|
||||
parser.yy = sequenceDb;
|
||||
|
||||
|
@ -79,9 +79,6 @@ export const bounds = {
|
|||
stopy: undefined
|
||||
};
|
||||
this.verticalPos = 0;
|
||||
if (parser.yy.hasConfigChange()) {
|
||||
setConf(getConfig());
|
||||
}
|
||||
},
|
||||
updateVal: function(obj, key, val, fun) {
|
||||
if (typeof obj[key] === 'undefined') {
|
||||
|
@ -153,15 +150,20 @@ export const bounds = {
|
|||
.lastIndexOf(message.from.actor);
|
||||
return this.activations.splice(lastActorActivationIdx, 1)[0];
|
||||
},
|
||||
newLoop: function(title, fill) {
|
||||
this.sequenceItems.push({
|
||||
createLoop: function(title = { message: undefined, wrap: false, width: undefined }, fill) {
|
||||
return {
|
||||
startx: undefined,
|
||||
starty: this.verticalPos,
|
||||
stopx: undefined,
|
||||
stopy: undefined,
|
||||
title: title,
|
||||
title: title.message,
|
||||
wrap: title.wrap,
|
||||
width: title.width,
|
||||
fill: fill
|
||||
});
|
||||
};
|
||||
},
|
||||
newLoop: function(title = { message: undefined, wrap: false, width: undefined }, fill) {
|
||||
this.sequenceItems.push(this.createLoop(title, fill));
|
||||
},
|
||||
endLoop: function() {
|
||||
return this.sequenceItems.pop();
|
||||
|
@ -186,59 +188,7 @@ export const bounds = {
|
|||
}
|
||||
};
|
||||
|
||||
const wrapLabel = (label, maxWidth, joinWith = '<br/>') => {
|
||||
if (common.lineBreakRegex.test(label)) {
|
||||
return label;
|
||||
}
|
||||
const words = label.split(' ');
|
||||
const completedLines = [];
|
||||
let nextLine = '';
|
||||
words.forEach((word, index) => {
|
||||
const wordLength = calculateTextWidth(`${word} `);
|
||||
const nextLineLength = calculateTextWidth(nextLine);
|
||||
if (wordLength > maxWidth) {
|
||||
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth);
|
||||
completedLines.push(nextLine, ...hyphenatedStrings);
|
||||
nextLine = remainingWord;
|
||||
} else if (nextLineLength + wordLength >= maxWidth) {
|
||||
completedLines.push(nextLine);
|
||||
nextLine = word;
|
||||
} else {
|
||||
nextLine = [nextLine, word].filter(Boolean).join(' ');
|
||||
}
|
||||
const currentWord = index + 1;
|
||||
const isLastWord = currentWord === words.length;
|
||||
if (isLastWord) {
|
||||
completedLines.push(nextLine);
|
||||
}
|
||||
});
|
||||
return completedLines.filter(line => line !== '').join(joinWith);
|
||||
};
|
||||
|
||||
const breakString = (word, maxWidth, hyphenCharacter = '-') => {
|
||||
const characters = word.split('');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
characters.forEach((character, index) => {
|
||||
const nextLine = `${currentLine}${character}`;
|
||||
const lineWidth = calculateTextWidth(nextLine);
|
||||
if (lineWidth >= maxWidth) {
|
||||
const currentCharacter = index + 1;
|
||||
const isLastLine = characters.length === currentCharacter;
|
||||
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
|
||||
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
|
||||
currentLine = '';
|
||||
} else {
|
||||
currentLine = nextLine;
|
||||
}
|
||||
});
|
||||
return { hyphenatedStrings: lines, remainingWord: currentLine };
|
||||
};
|
||||
|
||||
const _drawLongText = (text, x, y, g, width) => {
|
||||
let textHeight = 0;
|
||||
let prevTextHeight = 0;
|
||||
|
||||
const drawLongText = (text, x, y, g, width) => {
|
||||
const alignmentToAnchor = {
|
||||
left: 'start',
|
||||
start: 'start',
|
||||
|
@ -247,43 +197,45 @@ const _drawLongText = (text, x, y, g, width) => {
|
|||
right: 'end',
|
||||
end: 'end'
|
||||
};
|
||||
const lines = text.split(common.lineBreakRegex);
|
||||
for (const line of lines) {
|
||||
const textObj = svgDraw.getTextObj();
|
||||
const alignment = alignmentToAnchor[conf.noteAlign] || 'middle';
|
||||
|
||||
switch (alignment) {
|
||||
case 'start':
|
||||
textObj.x = x + conf.noteMargin;
|
||||
break;
|
||||
case 'middle':
|
||||
textObj.x = x + width / 2;
|
||||
break;
|
||||
case 'end':
|
||||
textObj.x = x + width - conf.noteMargin;
|
||||
break;
|
||||
}
|
||||
|
||||
textObj.y = y + textHeight;
|
||||
textObj.dy = '1em';
|
||||
textObj.text = line;
|
||||
textObj.class = 'noteText';
|
||||
|
||||
const textElem = svgDraw
|
||||
.drawText(g, textObj)
|
||||
.style('text-anchor', alignment)
|
||||
.style('font-size', conf.noteFontSize)
|
||||
.style('font-family', conf.noteFontFamily)
|
||||
.style('font-weight', conf.noteFontWeight)
|
||||
.attr('dominant-baseline', 'central')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
textElem.attr('y', y + (prevTextHeight + textHeight + 2 * conf.noteMargin) / 2);
|
||||
prevTextHeight = textHeight;
|
||||
const alignment = alignmentToAnchor[conf.noteAlign] || 'middle';
|
||||
const textObj = svgDraw.getTextObj();
|
||||
switch (alignment) {
|
||||
case 'start':
|
||||
textObj.x = x + conf.noteMargin;
|
||||
break;
|
||||
case 'middle':
|
||||
textObj.x = x + width / 2;
|
||||
break;
|
||||
case 'end':
|
||||
textObj.x = x + width - conf.noteMargin;
|
||||
break;
|
||||
}
|
||||
|
||||
return textHeight;
|
||||
textObj.y = y;
|
||||
textObj.dy = '1em';
|
||||
textObj.text = text;
|
||||
textObj.class = 'noteText';
|
||||
textObj.fontFamily = conf.noteFontFamily;
|
||||
textObj.fontSize = conf.noteFontSize;
|
||||
textObj.fontWeight = conf.noteFontWeight;
|
||||
textObj.anchor = alignment;
|
||||
textObj.textMargin = conf.noteMargin;
|
||||
textObj.valign = alignment;
|
||||
textObj.wrap = true;
|
||||
|
||||
let textElem = drawText(g, textObj);
|
||||
|
||||
if (!Array.isArray(textElem)) {
|
||||
textElem = [textElem];
|
||||
}
|
||||
|
||||
textElem.forEach(te => {
|
||||
te.attr('dominant-baseline', 'central').attr('alignment-baseline', 'central');
|
||||
});
|
||||
|
||||
return textElem
|
||||
.map(te => (te._groups || te)[0][0].getBBox().height)
|
||||
.reduce((acc, curr) => acc + curr);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -304,7 +256,7 @@ const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
|
|||
let g = elem.append('g');
|
||||
const rectElem = svgDraw.drawRect(g, rect);
|
||||
|
||||
const textHeight = _drawLongText(msg.message, startx, verticalPos, g, rect.width);
|
||||
const textHeight = drawLongText(msg.message, startx, verticalPos, g, rect.width);
|
||||
|
||||
bounds.insert(
|
||||
startx,
|
||||
|
@ -331,36 +283,15 @@ const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceInde
|
|||
const txtCenter = startx + (stopx - startx) / 2;
|
||||
|
||||
let textElems = [];
|
||||
/*
|
||||
let textHeight = 0;
|
||||
const breaklines = msg.message.split(common.lineBreakRegex);
|
||||
for (const breakline of breaklines) {
|
||||
let textElem = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', txtCenter)
|
||||
.attr('y', verticalPos + textHeight)
|
||||
.style('font-size', conf.messageFontSize)
|
||||
.style('font-family', conf.messageFontFamily)
|
||||
.style('font-weight', conf.messageFontWeight)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('class', 'messageText')
|
||||
.text(breakline.trim());
|
||||
textElems.push(textElem);
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
}
|
||||
|
||||
let totalOffset = textHeight;
|
||||
*/
|
||||
|
||||
let counterBreaklines = 0;
|
||||
let breaklineOffset = conf.messageFontSize + 4;
|
||||
let breaklineOffset = conf.messageFontSize;
|
||||
const breaklines = msg.message.split(common.lineBreakRegex);
|
||||
for (const breakline of breaklines) {
|
||||
textElems.push(
|
||||
g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', txtCenter)
|
||||
// .attr('y', verticalPos - breaklineVerticalOffset + counterBreaklines * breaklineOffset)
|
||||
.attr('y', verticalPos - 7 + counterBreaklines * breaklineOffset)
|
||||
.style('font-size', conf.messageFontSize)
|
||||
.style('font-family', conf.messageFontFamily)
|
||||
|
@ -500,32 +431,18 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
|
|||
// Draw the actors
|
||||
let prevWidth = 0;
|
||||
let prevMargin = 0;
|
||||
let maxActorHeight = conf.height;
|
||||
|
||||
for (let i = 0; i < actorKeys.length; i++) {
|
||||
const actor = actors[actorKeys[i]];
|
||||
|
||||
// Add some rendering data to the object
|
||||
actor.width = actor.width || calculateActorWidth(actor);
|
||||
actor.height = actor.wrap
|
||||
? calculateTextHeight(
|
||||
actor.message,
|
||||
conf.height,
|
||||
actor.width,
|
||||
conf.wrapPadding,
|
||||
actor.wrap,
|
||||
conf.actorFontSize
|
||||
)
|
||||
: conf.height;
|
||||
maxActorHeight = Math.max(maxActorHeight, actor.height);
|
||||
actor.margin = actor.margin || conf.actorMargin;
|
||||
actor.width = typeof actor.width === 'undefined' ? calculateActorWidth(actor) : actor.width;
|
||||
actor.height = conf.height;
|
||||
actor.margin = conf.actorMargin;
|
||||
|
||||
actor.x = prevWidth + prevMargin;
|
||||
actor.y = verticalPos;
|
||||
|
||||
if (actor.wrap) {
|
||||
actor.description = wrapLabel(actor.description, actor.width);
|
||||
}
|
||||
// Draw the box with the attached line
|
||||
svgDraw.drawActor(diagram, actor, conf);
|
||||
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
|
||||
|
@ -535,22 +452,17 @@ export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
|
|||
}
|
||||
|
||||
// Add a margin between the actor boxes and the first arrow
|
||||
bounds.bumpVerticalPos(maxActorHeight);
|
||||
bounds.bumpVerticalPos(conf.height);
|
||||
};
|
||||
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
keys.forEach(function(key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
assignWithDepth(conf, cnf);
|
||||
|
||||
if (cnf.fontFamily) {
|
||||
conf.actorFontFamily = conf.noteFontFamily = conf.messageFontFamily = cnf.fontFamily;
|
||||
}
|
||||
if (cnf.fontSize) {
|
||||
conf.actorFontSize = conf.noteFontSize = conf.messageFontSize = cnf.fontSize;
|
||||
// conf.height = cnf.fontSize * (65 / 14);
|
||||
}
|
||||
if (cnf.fontWeight) {
|
||||
conf.actorFontWeight = conf.noteFontWeight = conf.messageFontWeight = cnf.fontWeight;
|
||||
|
@ -596,100 +508,41 @@ const calculateActorWidth = function(actor) {
|
|||
? conf.width
|
||||
: Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(
|
||||
actor.description,
|
||||
conf.actorFontSize,
|
||||
conf.actorFontFamily,
|
||||
conf.actorFontWeight
|
||||
)
|
||||
utils.calculateTextWidth(actor.description, {
|
||||
fontSize: conf.actorFontSize,
|
||||
fontFamily: conf.actorFontFamily,
|
||||
fontWeight: conf.actorFontWeight,
|
||||
margin: conf.wrapPadding
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the text's height, taking into account the wrap value and
|
||||
* both the statically configured height, width, and the length of the text (in pixels).
|
||||
*
|
||||
* If the wrapped text text has greater height, we extend the height, so it's
|
||||
* value won't overflow.
|
||||
*
|
||||
* @return - The height for the given actor
|
||||
* @param message the text to measure
|
||||
* @param elementHeight the height of the default bounding box containing the text
|
||||
* @param elementWidth the width of the default bounding box containing the text
|
||||
* @param margin space above and below
|
||||
* @param wrap wrap the text based on: elementWidth - 2 * margin
|
||||
* @param fontSize
|
||||
*/
|
||||
export const calculateTextHeight = function(
|
||||
message,
|
||||
elementHeight,
|
||||
elementWidth,
|
||||
margin,
|
||||
wrap,
|
||||
fontSize
|
||||
) {
|
||||
if (!message) {
|
||||
return elementHeight;
|
||||
function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoopFn) {
|
||||
let heightAdjust = 0;
|
||||
bounds.bumpVerticalPos(preMargin);
|
||||
if (msg.message && msg.wrap && loopWidths[msg.message]) {
|
||||
let loopWidth = loopWidths[msg.message].width;
|
||||
let minSize =
|
||||
Math.round((3 * conf.fontSize) / 4) < 10
|
||||
? conf.fontSize
|
||||
: Math.round((3 * conf.fontSize) / 4);
|
||||
let textConf = {
|
||||
fontSize: minSize,
|
||||
fontFamily: conf.messageFontFamily,
|
||||
fontWeight: conf.messageFontWeight,
|
||||
margin: conf.wrapPadding
|
||||
};
|
||||
msg.message = msg.message
|
||||
? utils.wrapLabel(`[${msg.message}]`, loopWidth, textConf)
|
||||
: msg.message;
|
||||
heightAdjust = Math.max(
|
||||
0,
|
||||
utils.calculateTextHeight(msg.message, textConf) - (preMargin + postMargin)
|
||||
);
|
||||
}
|
||||
let lineHeightFactor = wrap
|
||||
? wrapLabel(message, elementWidth - 2 * margin).split(common.lineBreakRegex).length
|
||||
: 1;
|
||||
|
||||
return wrap ? Math.max(elementHeight, lineHeightFactor * fontSize) : elementHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the width of the given text, font size and family.
|
||||
*
|
||||
* @param text - The text to calculate the width of
|
||||
* @param fontSize - The font size of the given text
|
||||
* @param fontFamily - The font family (one, or more fonts) to render
|
||||
* @param fontWeight - The font weight (normal, bold, italics)
|
||||
*/
|
||||
export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeight) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
fontSize = fontSize ? fontSize : conf.actorFontSize;
|
||||
fontFamily = fontFamily ? fontFamily : conf.actorFontFamily;
|
||||
fontWeight = fontWeight ? fontWeight : conf.actorFontWeight;
|
||||
|
||||
// We can't really know if the user supplied font family will render on the user agent;
|
||||
// thus, we'll take the max width between the user supplied font family, and a default
|
||||
// of sans-serif.
|
||||
const fontFamilies = ['sans-serif', fontFamily];
|
||||
const lines = text.split(common.lineBreakRegex);
|
||||
let maxWidth = 0;
|
||||
|
||||
const body = select('body');
|
||||
// We don't want to leak DOM elements - if a removal operation isn't available
|
||||
// for any reason, do not continue.
|
||||
if (!body.remove) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const g = body.append('svg');
|
||||
|
||||
for (let line of lines) {
|
||||
for (let fontFamily of fontFamilies) {
|
||||
const textObj = svgDraw.getTextObj();
|
||||
textObj.text = line;
|
||||
const textElem = svgDraw
|
||||
.drawText(g, textObj)
|
||||
.style('font-size', fontSize)
|
||||
.style('font-weight', fontWeight)
|
||||
.style('font-family', fontFamily);
|
||||
|
||||
maxWidth = Math.max(maxWidth, (textElem._groups || textElem)[0][0].getBBox().width);
|
||||
}
|
||||
}
|
||||
|
||||
g.remove();
|
||||
|
||||
// Adds some padding, so the text won't sit exactly within the actor's borders
|
||||
return maxWidth + conf.wrapPadding * 2;
|
||||
};
|
||||
addLoopFn(msg);
|
||||
bounds.bumpVerticalPos(heightAdjust + postMargin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
|
@ -698,6 +551,7 @@ export const calculateTextWidth = function(text, fontSize, fontFamily, fontWeigh
|
|||
*/
|
||||
export const draw = function(text, id) {
|
||||
parser.yy.clear();
|
||||
parser.yy.setWrap(conf.wrapEnabled);
|
||||
parser.parse(text + '\n');
|
||||
|
||||
bounds.init();
|
||||
|
@ -714,15 +568,10 @@ export const draw = function(text, id) {
|
|||
const title = parser.yy.getTitle();
|
||||
|
||||
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages);
|
||||
const maxActorHeight = calculateActorMargins(actors, maxMessageWidthPerActor);
|
||||
conf.height = calculateActorMargins(actors, maxMessageWidthPerActor);
|
||||
|
||||
drawActors(diagram, actors, actorKeys, 0);
|
||||
|
||||
bounds.bumpVerticalPos(
|
||||
maxActorHeight > conf.height
|
||||
? Math.min(conf.boxMargin, Math.abs(maxActorHeight - conf.height))
|
||||
: 0
|
||||
);
|
||||
const loopWidths = calculateLoopMargins(messages, actors);
|
||||
|
||||
// The arrow head definition is attached to the svg once
|
||||
svgDraw.insertArrowHead(diagram);
|
||||
|
@ -752,6 +601,7 @@ export const draw = function(text, id) {
|
|||
let loopData,
|
||||
noteWidth,
|
||||
textWidth,
|
||||
textConf,
|
||||
shouldWrap = msg.wrap && msg.message && !common.lineBreakRegex.test(msg.message);
|
||||
|
||||
switch (msg.type) {
|
||||
|
@ -760,17 +610,18 @@ export const draw = function(text, id) {
|
|||
|
||||
startx = actors[msg.from].x;
|
||||
stopx = actors[msg.to].x;
|
||||
textWidth = calculateTextWidth(
|
||||
msg.message,
|
||||
conf.noteFontSize,
|
||||
conf.noteFontFamily,
|
||||
conf.noteFontWeight
|
||||
);
|
||||
textConf = {
|
||||
fontSize: conf.noteFontSize,
|
||||
fontFamily: conf.noteFontFamily,
|
||||
fontWeight: conf.noteFontWeight,
|
||||
margin: conf.wrapPadding
|
||||
};
|
||||
textWidth = utils.calculateTextWidth(msg.message, textConf);
|
||||
noteWidth = shouldWrap ? conf.width : Math.max(conf.width, textWidth);
|
||||
|
||||
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
|
||||
}
|
||||
drawNote(
|
||||
diagram,
|
||||
|
@ -781,7 +632,7 @@ export const draw = function(text, id) {
|
|||
);
|
||||
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
|
||||
}
|
||||
drawNote(
|
||||
diagram,
|
||||
|
@ -793,7 +644,7 @@ export const draw = function(text, id) {
|
|||
} else if (msg.to === msg.from) {
|
||||
// Single-actor over
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
|
||||
}
|
||||
drawNote(
|
||||
diagram,
|
||||
|
@ -807,7 +658,7 @@ export const draw = function(text, id) {
|
|||
forceWidth = Math.abs(startx - stopx) + conf.actorMargin / 2;
|
||||
if (shouldWrap) {
|
||||
noteWidth = forceWidth;
|
||||
msg.message = wrapLabel(msg.message, noteWidth);
|
||||
msg.message = utils.wrapLabel(msg.message, noteWidth, textConf);
|
||||
} else {
|
||||
noteWidth = Math.max(forceWidth, textWidth - 2 * conf.noteMargin);
|
||||
}
|
||||
|
@ -826,13 +677,16 @@ export const draw = function(text, id) {
|
|||
activeEnd(msg, bounds.getVerticalPos());
|
||||
break;
|
||||
case parser.yy.LINETYPE.LOOP_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
message => bounds.newLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.LOOP_END:
|
||||
loopData = bounds.endLoop();
|
||||
|
||||
svgDraw.drawLoop(diagram, loopData, 'loop', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
|
@ -841,51 +695,56 @@ export const draw = function(text, id) {
|
|||
bounds.newLoop(undefined, msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.RECT_END: {
|
||||
const rectData = bounds.endLoop();
|
||||
svgDraw.drawBackgroundRect(diagram, rectData);
|
||||
case parser.yy.LINETYPE.RECT_END:
|
||||
svgDraw.drawBackgroundRect(diagram, bounds.endLoop());
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
}
|
||||
case parser.yy.LINETYPE.OPT_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
message => bounds.newLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.OPT_END:
|
||||
loopData = bounds.endLoop();
|
||||
|
||||
svgDraw.drawLoop(diagram, loopData, 'opt', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
message => bounds.newLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_ELSE:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopData = bounds.addSectionToLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
|
||||
bounds.addSectionToLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_END:
|
||||
loopData = bounds.endLoop();
|
||||
|
||||
svgDraw.drawLoop(diagram, loopData, 'alt', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(msg.message, conf.boxMargin);
|
||||
}
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
conf.boxMargin,
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
message => bounds.newLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_AND:
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopData = bounds.addSectionToLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
adjustLoopHeightForWrap(loopWidths, msg, conf.boxMargin, conf.boxMargin, message =>
|
||||
bounds.addSectionToLoop(message)
|
||||
);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_END:
|
||||
loopData = bounds.endLoop();
|
||||
|
@ -902,13 +761,20 @@ export const draw = function(text, id) {
|
|||
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
|
||||
startx = fromBounds[fromIdx];
|
||||
stopx = toBounds[toIdx];
|
||||
textConf = {
|
||||
fontSize: conf.messageFontSize,
|
||||
fontFamily: conf.messageFontFamily,
|
||||
fontWeight: conf.messageFontWeight,
|
||||
margin: conf.wrapPadding
|
||||
};
|
||||
if (shouldWrap) {
|
||||
msg.message = wrapLabel(
|
||||
msg.message = utils.wrapLabel(
|
||||
msg.message,
|
||||
Math.max(
|
||||
Math.abs(stopx - startx) + conf.messageMargin * 2,
|
||||
conf.width + conf.messageMargin * 2
|
||||
)
|
||||
),
|
||||
textConf
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -972,6 +838,7 @@ export const draw = function(text, id) {
|
|||
diagram.attr('height', '100%');
|
||||
diagram.attr('width', '100%');
|
||||
diagram.attr('style', 'max-width:' + width + 'px;');
|
||||
// diagram.attr('style', 'max-width:100%;');
|
||||
} else {
|
||||
diagram.attr('height', height);
|
||||
diagram.attr('width', width);
|
||||
|
@ -1023,12 +890,17 @@ const getMaxMessageWidthPerActor = function(actors, messages) {
|
|||
const fontSize = isNote ? conf.noteFontSize : conf.messageFontSize;
|
||||
const fontFamily = isNote ? conf.noteFontFamily : conf.messageFontFamily;
|
||||
const fontWeight = isNote ? conf.noteFontWeight : conf.messageFontWeight;
|
||||
const messageWidth = calculateTextWidth(
|
||||
msg.wrap ? wrapLabel(msg.message, conf.width - conf.noteMargin) : msg.message,
|
||||
const textConf = { fontFamily, fontSize, fontWeight, margin: conf.wrapPadding };
|
||||
let wrappedMessage = msg.wrap
|
||||
? utils.wrapLabel(msg.message, conf.width - conf.noteMargin, textConf)
|
||||
: msg.message;
|
||||
const messageDimensions = utils.calculateTextDimensions(wrappedMessage, {
|
||||
fontSize,
|
||||
fontFamily,
|
||||
fontWeight
|
||||
);
|
||||
fontWeight,
|
||||
margin: conf.wrapPadding
|
||||
});
|
||||
const messageWidth = messageDimensions.width;
|
||||
|
||||
/*
|
||||
* The following scenarios should be supported:
|
||||
|
@ -1104,6 +976,13 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
|
|||
continue;
|
||||
}
|
||||
|
||||
const textConf = {
|
||||
fontSize: conf.actorFontSize,
|
||||
fontFamily: conf.actorFontFamily,
|
||||
fontWeight: conf.actorFontWeight,
|
||||
margin: conf.wrapPadding
|
||||
};
|
||||
|
||||
const nextActor = actors[actor.nextActor];
|
||||
|
||||
// No need to space out an actor that doesn't have a next link
|
||||
|
@ -1112,28 +991,14 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
|
|||
}
|
||||
|
||||
[actor, nextActor].forEach(function(act) {
|
||||
act.width = act.wrap
|
||||
? conf.width
|
||||
: Math.max(
|
||||
conf.width,
|
||||
calculateTextWidth(
|
||||
act.description,
|
||||
conf.actorFontSize,
|
||||
conf.actorFontFamily,
|
||||
conf.actorFontWeight
|
||||
)
|
||||
);
|
||||
if (act.wrap) {
|
||||
act.description = utils.wrapLabel(act.description, conf.width, textConf);
|
||||
}
|
||||
const actDims = utils.calculateTextDimensions(act.description, textConf);
|
||||
act.width = act.wrap ? conf.width : Math.max(conf.width, actDims.width);
|
||||
|
||||
act.height = act.wrap
|
||||
? calculateTextHeight(
|
||||
act.description,
|
||||
conf.height,
|
||||
actor.width,
|
||||
conf.actorMargin,
|
||||
act.wrap,
|
||||
conf.actorFontSize
|
||||
)
|
||||
: conf.height;
|
||||
act.height = act.wrap ? Math.max(actDims.height, conf.height) : conf.height;
|
||||
logger.debug(`Actor h:${act.height} ${actDims.height} d:${act.description}`);
|
||||
maxHeight = Math.max(maxHeight, act.height);
|
||||
});
|
||||
|
||||
|
@ -1142,11 +1007,62 @@ const calculateActorMargins = function(actors, actorToMessageWidth) {
|
|||
|
||||
actor.margin = Math.max(actorWidth, conf.actorMargin);
|
||||
}
|
||||
Object.keys(actors).forEach(function(key) {
|
||||
actors[key].height = maxHeight;
|
||||
});
|
||||
|
||||
return maxHeight;
|
||||
return Math.max(maxHeight, conf.height);
|
||||
};
|
||||
|
||||
const calculateLoopMargins = function(messages, actors) {
|
||||
const loops = {};
|
||||
const stack = [];
|
||||
let current;
|
||||
|
||||
messages.forEach(function(msg) {
|
||||
switch (msg.type) {
|
||||
case parser.yy.LINETYPE.LOOP_START:
|
||||
case parser.yy.LINETYPE.ALT_START:
|
||||
case parser.yy.LINETYPE.OPT_START:
|
||||
case parser.yy.LINETYPE.PAR_START:
|
||||
stack.push({
|
||||
msg: msg.message,
|
||||
from: Number.MAX_SAFE_INTEGER,
|
||||
to: Number.MIN_SAFE_INTEGER,
|
||||
width: 0
|
||||
});
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_ELSE:
|
||||
case parser.yy.LINETYPE.PAR_AND:
|
||||
if (msg.message) {
|
||||
current = stack.pop();
|
||||
loops[msg.message] = current;
|
||||
stack.push(current);
|
||||
}
|
||||
break;
|
||||
case parser.yy.LINETYPE.LOOP_END:
|
||||
case parser.yy.LINETYPE.ALT_END:
|
||||
case parser.yy.LINETYPE.OPT_END:
|
||||
case parser.yy.LINETYPE.PAR_END:
|
||||
current = stack.pop();
|
||||
loops[current.msg] = current;
|
||||
break;
|
||||
}
|
||||
if (msg.from && msg.to && stack.length > 0) {
|
||||
stack.forEach(stk => {
|
||||
current = stk;
|
||||
let from = actors[msg.from];
|
||||
let to = actors[msg.to];
|
||||
if (from.x < to.x) {
|
||||
current.from = Math.min(current.from, from.x);
|
||||
current.to = Math.max(current.to, to.x);
|
||||
} else {
|
||||
current.from = Math.min(current.from, to.x);
|
||||
current.to = Math.max(current.to, from.x);
|
||||
}
|
||||
current.width = Math.abs(current.from - current.to) - 20 + 2 * conf.wrapPadding;
|
||||
});
|
||||
}
|
||||
});
|
||||
logger.debug('LoopWidths:', { loops, actors });
|
||||
return loops;
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -19,27 +19,95 @@ export const drawRect = function(elem, rectData) {
|
|||
};
|
||||
|
||||
export const drawText = function(elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(common.lineBreakRegex, ' ');
|
||||
let prevTextHeight = 0,
|
||||
textHeight = 0;
|
||||
const lines = textData.wrap
|
||||
? textData.text.split(common.lineBreakRegex)
|
||||
: [textData.text.replace(common.lineBreakRegex, ' ')];
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
textElem.style('font-family', textData.fontFamily);
|
||||
textElem.style('font-size', textData.fontSize);
|
||||
textElem.style('font-weight', textData.fontWeight);
|
||||
textElem.attr('fill', textData.fill);
|
||||
if (typeof textData.class !== 'undefined') {
|
||||
textElem.attr('class', textData.class);
|
||||
let textElems = [];
|
||||
let dy = 0;
|
||||
let yfunc = () => textData.y;
|
||||
if (
|
||||
typeof textData.valign !== 'undefined' &&
|
||||
typeof textData.textMargin !== 'undefined' &&
|
||||
textData.textMargin > 0
|
||||
) {
|
||||
switch (textData.valign) {
|
||||
case 'top':
|
||||
case 'start':
|
||||
yfunc = () => textData.y + textData.textMargin;
|
||||
break;
|
||||
case 'middle':
|
||||
case 'center':
|
||||
yfunc = () => textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2;
|
||||
break;
|
||||
case 'bottom':
|
||||
case 'end':
|
||||
yfunc = () =>
|
||||
textData.y +
|
||||
(prevTextHeight + textHeight + 2 * textData.textMargin) -
|
||||
textData.textMargin;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
if (
|
||||
typeof textData.textMargin !== 'undefined' &&
|
||||
textData.textMargin === 0 &&
|
||||
typeof textData.fontSize !== 'undefined'
|
||||
) {
|
||||
dy = i * textData.fontSize;
|
||||
}
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', yfunc());
|
||||
if (typeof textData.anchor !== 'undefined') {
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
}
|
||||
if (typeof textData.fontFamily !== 'undefined') {
|
||||
textElem.style('font-family', textData.fontFamily);
|
||||
}
|
||||
if (typeof textData.fontSize !== 'undefined') {
|
||||
textElem.style('font-size', textData.fontSize);
|
||||
}
|
||||
if (typeof textData.fontWeight !== 'undefined') {
|
||||
textElem.style('font-weight', textData.fontWeight);
|
||||
}
|
||||
if (typeof textData.fill !== 'undefined') {
|
||||
textElem.attr('fill', textData.fill);
|
||||
}
|
||||
if (typeof textData.class !== 'undefined') {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
if (typeof textData.dy !== 'undefined') {
|
||||
textElem.attr('dy', textData.dy);
|
||||
} else if (dy !== 0) {
|
||||
textElem.attr('dy', dy);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x);
|
||||
if (typeof textData.fill !== 'undefined') {
|
||||
span.attr('fill', textData.fill);
|
||||
}
|
||||
span.text(line);
|
||||
|
||||
if (
|
||||
typeof textData.valign !== 'undefined' &&
|
||||
typeof textData.textMargin !== 'undefined' &&
|
||||
textData.textMargin > 0
|
||||
) {
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
prevTextHeight = textHeight;
|
||||
}
|
||||
|
||||
textElems.push(textElem);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.attr('fill', textData.fill);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
return textElems.length === 1 ? textElems[0] : textElems;
|
||||
};
|
||||
|
||||
export const drawLabel = function(elem, txtObject) {
|
||||
|
@ -72,7 +140,7 @@ export const drawLabel = function(elem, txtObject) {
|
|||
|
||||
txtObject.y = txtObject.y + txtObject.labelMargin;
|
||||
txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin;
|
||||
drawText(elem, txtObject);
|
||||
return drawText(elem, txtObject);
|
||||
};
|
||||
|
||||
let actorCnt = -1;
|
||||
|
@ -80,7 +148,7 @@ let actorCnt = -1;
|
|||
* Draws an actor in the diagram with the attaced line
|
||||
* @param elem - The diagram we'll draw to.
|
||||
* @param actor - The actor to draw.
|
||||
* @param conf - drawText implementation discriminator object
|
||||
* @param conf - utils.drawText implementation discriminator object
|
||||
*/
|
||||
export const drawActor = function(elem, actor, conf) {
|
||||
const center = actor.x + actor.width / 2;
|
||||
|
@ -185,33 +253,39 @@ export const drawLoop = function(elem, bounds, labelText, conf) {
|
|||
txt.fontWeight = conf.fontWeight;
|
||||
txt.class = 'labelText'; // Its size & position are fixed.
|
||||
|
||||
drawLabel(g, txt);
|
||||
|
||||
let labelElem = drawLabel(g, txt);
|
||||
let labelBoxWidth = (labelElem._groups || labelElem)[0][0].getBBox().width;
|
||||
txt = getTextObj();
|
||||
txt.text = '[ ' + bounds.title + ' ]';
|
||||
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2;
|
||||
txt.y = bounds.starty + 1.5 * conf.boxMargin;
|
||||
txt.text = bounds.title;
|
||||
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2 + labelBoxWidth;
|
||||
txt.y = bounds.starty + conf.boxMargin + conf.boxTextMargin;
|
||||
txt.anchor = 'middle';
|
||||
txt.class = 'loopText';
|
||||
txt.fontFamily = conf.fontFamily;
|
||||
txt.fontSize = minSize;
|
||||
txt.fontWeight = conf.fontWeight;
|
||||
txt.wrap = bounds.wrap;
|
||||
|
||||
let textElem = drawText(g, txt);
|
||||
let textHeight = (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
drawText(g, txt);
|
||||
|
||||
if (typeof bounds.sectionTitles !== 'undefined') {
|
||||
bounds.sectionTitles.forEach(function(item, idx) {
|
||||
if (item !== '') {
|
||||
txt.text = '[ ' + item + ' ]';
|
||||
if (item.message) {
|
||||
txt.text = item.message;
|
||||
txt.x = bounds.startx + (bounds.stopx - bounds.startx) / 2;
|
||||
txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin;
|
||||
textElem = drawText(g, txt);
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
txt.class = 'loopText';
|
||||
txt.anchor = 'middle';
|
||||
txt.fontFamily = conf.fontFamily;
|
||||
txt.fontSize = minSize;
|
||||
txt.fontWeight = conf.fontWeight;
|
||||
txt.wrap = bounds.wrap;
|
||||
drawText(g, txt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return textHeight + 4;
|
||||
return g;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -300,23 +374,23 @@ export const insertArrowCrossHead = function(elem) {
|
|||
};
|
||||
|
||||
export const getTextObj = function() {
|
||||
const txt = {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: undefined,
|
||||
'text-anchor': 'start',
|
||||
anchor: 'start',
|
||||
style: '#666',
|
||||
width: 100,
|
||||
height: 100,
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0
|
||||
ry: 0,
|
||||
valign: undefined
|
||||
};
|
||||
return txt;
|
||||
};
|
||||
|
||||
export const getNoteRect = function() {
|
||||
const rect = {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#EDF2AE',
|
||||
|
@ -327,7 +401,6 @@ export const getNoteRect = function() {
|
|||
rx: 0,
|
||||
ry: 0
|
||||
};
|
||||
return rect;
|
||||
};
|
||||
|
||||
const _drawTextCandidateFunc = (function() {
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
import decode from 'entity-decode/browser';
|
||||
import mermaidAPI from './mermaidAPI';
|
||||
import { logger } from './logger';
|
||||
import utils from './utils';
|
||||
|
||||
/**
|
||||
* ## init
|
||||
* Function that goes through the document to find the chart definitions in there and render them.
|
||||
|
@ -29,7 +31,7 @@ import { logger } from './logger';
|
|||
*/
|
||||
const init = function() {
|
||||
const conf = mermaidAPI.getConfig();
|
||||
logger.debug('Starting rendering diagrams');
|
||||
// console.log('Starting rendering diagrams (init) - mermaid.init');
|
||||
let nodes;
|
||||
if (arguments.length >= 2) {
|
||||
/*! sequence config was passed as #1 */
|
||||
|
@ -69,11 +71,11 @@ const init = function() {
|
|||
logger.debug('Start On Load before: ' + mermaid.startOnLoad);
|
||||
if (typeof mermaid.startOnLoad !== 'undefined') {
|
||||
logger.debug('Start On Load inner: ' + mermaid.startOnLoad);
|
||||
mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad });
|
||||
mermaidAPI.setConfig({ startOnLoad: mermaid.startOnLoad });
|
||||
}
|
||||
|
||||
if (typeof mermaid.ganttConfig !== 'undefined') {
|
||||
mermaidAPI.initialize({ gantt: mermaid.ganttConfig });
|
||||
mermaidAPI.setConfig({ gantt: mermaid.ganttConfig });
|
||||
}
|
||||
|
||||
let txt;
|
||||
|
@ -98,6 +100,11 @@ const init = function() {
|
|||
.trim()
|
||||
.replace(/<br\s*\/?>/gi, '<br/>');
|
||||
|
||||
const init = utils.detectInit(txt);
|
||||
if (init) {
|
||||
logger.debug('Detected early reinit: ', init);
|
||||
}
|
||||
|
||||
try {
|
||||
mermaidAPI.render(
|
||||
id,
|
||||
|
@ -122,6 +129,9 @@ const init = function() {
|
|||
};
|
||||
|
||||
const initialize = function(config) {
|
||||
mermaidAPI.reset();
|
||||
|
||||
// console.log('mermaid.initialize1', config);
|
||||
if (typeof config.mermaid !== 'undefined') {
|
||||
if (typeof config.mermaid.startOnLoad !== 'undefined') {
|
||||
mermaid.startOnLoad = config.mermaid.startOnLoad;
|
||||
|
@ -130,8 +140,9 @@ const initialize = function(config) {
|
|||
mermaid.htmlLabels = config.mermaid.htmlLabels;
|
||||
}
|
||||
}
|
||||
// console.log('Initializing mermaid 2', config);
|
||||
mermaidAPI.initialize(config);
|
||||
logger.debug('Initializing mermaid ');
|
||||
// logger.debug('Initializing mermaid 3', config);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,7 @@ import scope from 'scope-css';
|
|||
import pkg from '../package.json';
|
||||
import { setConfig, getConfig } from './config';
|
||||
import { logger, setLogLevel } from './logger';
|
||||
import utils from './utils';
|
||||
import utils, { assignWithDepth } from './utils';
|
||||
import flowRenderer from './diagrams/flowchart/flowRenderer';
|
||||
import flowRendererV2 from './diagrams/flowchart/flowRenderer-v2';
|
||||
import flowParser from './diagrams/flowchart/parser/flow';
|
||||
|
@ -49,6 +49,7 @@ import erRenderer from './diagrams/er/erRenderer';
|
|||
import journeyParser from './diagrams/user-journey/parser/journey';
|
||||
import journeyDb from './diagrams/user-journey/journeyDb';
|
||||
import journeyRenderer from './diagrams/user-journey/journeyRenderer';
|
||||
import configApi from './config';
|
||||
|
||||
const themes = {};
|
||||
for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
|
||||
|
@ -556,14 +557,17 @@ const config = {
|
|||
fontSize: 12
|
||||
}
|
||||
};
|
||||
export const defaultConfig = Object.freeze(assignWithDepth({}, config));
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
config.git.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
|
||||
setLogLevel(config.logLevel);
|
||||
setConfig(config);
|
||||
configApi.reset(config);
|
||||
|
||||
function parse(text) {
|
||||
const graphInit = utils.detectInit(text);
|
||||
if (graphInit) {
|
||||
reinitialize(graphInit);
|
||||
initialize(graphInit);
|
||||
logger.debug('Init ', graphInit);
|
||||
}
|
||||
const graphType = utils.detectType(text);
|
||||
|
@ -633,6 +637,7 @@ function parse(text) {
|
|||
};
|
||||
|
||||
parser.parse(text);
|
||||
return parser;
|
||||
}
|
||||
|
||||
export const encodeEntities = function(text) {
|
||||
|
@ -692,18 +697,24 @@ export const decodeEntities = function(text) {
|
|||
* });
|
||||
*```
|
||||
* @param id the id of the element to be rendered
|
||||
* @param txt the graph definition
|
||||
* @param _txt the graph definition
|
||||
* @param cb callback which is called after rendering is finished with the svg code as inparam.
|
||||
* @param container selector to element in which a div with the graph temporarily will be inserted. In one is
|
||||
* provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
|
||||
* completed.
|
||||
*/
|
||||
const render = function(id, _txt, cb, container) {
|
||||
const cnf = getConfig();
|
||||
// Check the maximum allowed text size
|
||||
let txt = _txt;
|
||||
if (_txt.length > config.maxTextSize) {
|
||||
if (_txt.length > cnf.maxTextSize) {
|
||||
txt = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
|
||||
}
|
||||
const graphInit = utils.detectInit(txt);
|
||||
if (graphInit) {
|
||||
initialize(graphInit);
|
||||
assignWithDepth(cnf, getConfig());
|
||||
}
|
||||
|
||||
if (typeof container !== 'undefined') {
|
||||
container.innerHTML = '';
|
||||
|
@ -711,7 +722,7 @@ const render = function(id, _txt, cb, container) {
|
|||
select(container)
|
||||
.append('div')
|
||||
.attr('id', 'd' + id)
|
||||
.attr('style', 'font-family: ' + config.fontFamily)
|
||||
.attr('style', 'font-family: ' + cnf.fontFamily)
|
||||
.append('svg')
|
||||
.attr('id', id)
|
||||
.attr('width', '100%')
|
||||
|
@ -741,10 +752,6 @@ const render = function(id, _txt, cb, container) {
|
|||
txt = encodeEntities(txt);
|
||||
|
||||
const element = select('#d' + id).node();
|
||||
const graphInit = utils.detectInit(txt);
|
||||
if (graphInit) {
|
||||
reinitialize(graphInit);
|
||||
}
|
||||
const graphType = utils.detectType(txt);
|
||||
|
||||
// insert inline style into svg
|
||||
|
@ -752,22 +759,22 @@ const render = function(id, _txt, cb, container) {
|
|||
const firstChild = svg.firstChild;
|
||||
|
||||
// pre-defined theme
|
||||
let style = themes[config.theme];
|
||||
let style = themes[cnf.theme];
|
||||
if (style === undefined) {
|
||||
style = '';
|
||||
}
|
||||
|
||||
// user provided theme CSS
|
||||
if (config.themeCSS !== undefined) {
|
||||
style += `\n${config.themeCSS}`;
|
||||
if (cnf.themeCSS !== undefined) {
|
||||
style += `\n${cnf.themeCSS}`;
|
||||
}
|
||||
// user provided theme CSS
|
||||
if (config.fontFamily !== undefined) {
|
||||
style += `\n:root { --mermaid-font-family: ${config.fontFamily}}`;
|
||||
if (cnf.fontFamily !== undefined) {
|
||||
style += `\n:root { --mermaid-font-family: ${cnf.fontFamily}}`;
|
||||
}
|
||||
// user provided theme CSS
|
||||
if (config.altFontFamily !== undefined) {
|
||||
style += `\n:root { --mermaid-alt-font-family: ${config.altFontFamily}}`;
|
||||
if (cnf.altFontFamily !== undefined) {
|
||||
style += `\n:root { --mermaid-alt-font-family: ${cnf.altFontFamily}}`;
|
||||
}
|
||||
|
||||
// classDef
|
||||
|
@ -800,74 +807,74 @@ const render = function(id, _txt, cb, container) {
|
|||
try {
|
||||
switch (graphType) {
|
||||
case 'git':
|
||||
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
gitGraphRenderer.setConf(config.git);
|
||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
gitGraphRenderer.setConf(cnf.git);
|
||||
gitGraphRenderer.draw(txt, id, false);
|
||||
break;
|
||||
case 'flowchart':
|
||||
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
flowRenderer.setConf(config.flowchart);
|
||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
flowRenderer.setConf(cnf.flowchart);
|
||||
flowRenderer.draw(txt, id, false);
|
||||
break;
|
||||
case 'flowchart-v2':
|
||||
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
flowRendererV2.setConf(config.flowchart);
|
||||
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
flowRendererV2.setConf(cnf.flowchart);
|
||||
flowRendererV2.draw(txt, id, false);
|
||||
break;
|
||||
case 'sequence':
|
||||
config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
if (config.sequenceDiagram) {
|
||||
cnf.sequence.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
if (cnf.sequenceDiagram) {
|
||||
// backwards compatibility
|
||||
sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram));
|
||||
sequenceRenderer.setConf(Object.assign(cnf.sequence, cnf.sequenceDiagram));
|
||||
console.error(
|
||||
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
|
||||
);
|
||||
} else {
|
||||
sequenceRenderer.setConf(config.sequence);
|
||||
sequenceRenderer.setConf(cnf.sequence);
|
||||
}
|
||||
sequenceRenderer.draw(txt, id);
|
||||
break;
|
||||
case 'gantt':
|
||||
config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
ganttRenderer.setConf(config.gantt);
|
||||
cnf.gantt.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
ganttRenderer.setConf(cnf.gantt);
|
||||
ganttRenderer.draw(txt, id);
|
||||
break;
|
||||
case 'class':
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
classRenderer.setConf(config.class);
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
classRenderer.setConf(cnf.class);
|
||||
classRenderer.draw(txt, id);
|
||||
break;
|
||||
case 'state':
|
||||
// config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
stateRenderer.setConf(config.state);
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
stateRenderer.setConf(cnf.state);
|
||||
stateRenderer.draw(txt, id);
|
||||
break;
|
||||
case 'stateDiagram':
|
||||
// config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
stateRendererV2.setConf(config.state);
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
stateRendererV2.setConf(cnf.state);
|
||||
stateRendererV2.draw(txt, id);
|
||||
break;
|
||||
case 'info':
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
infoRenderer.setConf(config.class);
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
infoRenderer.setConf(cnf.class);
|
||||
infoRenderer.draw(txt, id, pkg.version);
|
||||
break;
|
||||
case 'pie':
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
pieRenderer.setConf(config.class);
|
||||
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
|
||||
pieRenderer.setConf(cnf.class);
|
||||
pieRenderer.draw(txt, id, pkg.version);
|
||||
break;
|
||||
case 'er':
|
||||
erRenderer.setConf(config.er);
|
||||
erRenderer.setConf(cnf.er);
|
||||
erRenderer.draw(txt, id, pkg.version);
|
||||
break;
|
||||
case 'journey':
|
||||
journeyRenderer.setConf(config.journey);
|
||||
journeyRenderer.setConf(cnf.journey);
|
||||
journeyRenderer.draw(txt, id, pkg.version);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
errorRenderer.setConf(config.class);
|
||||
// errorRenderer.setConf(cnf.class);
|
||||
errorRenderer.draw(id, pkg.version);
|
||||
throw e;
|
||||
}
|
||||
|
@ -876,7 +883,7 @@ const render = function(id, _txt, cb, container) {
|
|||
.selectAll('foreignobject > *')
|
||||
.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
|
||||
// if (config.arrowMarkerAbsolute) {
|
||||
// if (cnf.arrowMarkerAbsolute) {
|
||||
// url =
|
||||
// window.location.protocol +
|
||||
// '//' +
|
||||
|
@ -889,8 +896,8 @@ const render = function(id, _txt, cb, container) {
|
|||
|
||||
// Fix for when the base tag is used
|
||||
let svgCode = select('#d' + id).node().innerHTML;
|
||||
|
||||
if (!config.arrowMarkerAbsolute || config.arrowMarkerAbsolute === 'false') {
|
||||
logger.debug('cnf.arrowMarkerAbsolute', cnf.arrowMarkerAbsolute);
|
||||
if (!cnf.arrowMarkerAbsolute || cnf.arrowMarkerAbsolute === 'false') {
|
||||
svgCode = svgCode.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#', 'g');
|
||||
}
|
||||
|
||||
|
@ -925,53 +932,36 @@ const render = function(id, _txt, cb, container) {
|
|||
return svgCode;
|
||||
};
|
||||
|
||||
const setConf = function(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
const lvl1Keys = Object.keys(cnf);
|
||||
for (let i = 0; i < lvl1Keys.length; i++) {
|
||||
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
|
||||
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]);
|
||||
|
||||
for (let j = 0; j < lvl2Keys.length; j++) {
|
||||
logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j]);
|
||||
if (typeof config[lvl1Keys[i]] === 'undefined') {
|
||||
config[lvl1Keys[i]] = {};
|
||||
}
|
||||
logger.debug(
|
||||
'Setting config: ' +
|
||||
lvl1Keys[i] +
|
||||
' ' +
|
||||
lvl2Keys[j] +
|
||||
' to ' +
|
||||
cnf[lvl1Keys[i]][lvl2Keys[j]]
|
||||
);
|
||||
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]];
|
||||
}
|
||||
} else {
|
||||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
function updateRendererConfigs(conf) {
|
||||
gitGraphRenderer.setConf(conf.git);
|
||||
flowRenderer.setConf(conf.flowchart);
|
||||
flowRendererV2.setConf(conf.flowchart);
|
||||
if (typeof conf['sequenceDiagram'] !== 'undefined') {
|
||||
sequenceRenderer.setConf(assignWithDepth(conf.sequence, conf['sequenceDiagram']));
|
||||
}
|
||||
};
|
||||
|
||||
function reinitialize(options) {
|
||||
if (typeof options === 'object') {
|
||||
setConf(options);
|
||||
}
|
||||
setConfig(config);
|
||||
setLogLevel(config.logLevel);
|
||||
logger.debug('RE-Initializing mermaidAPI ', { version: pkg.version, options, config });
|
||||
sequenceRenderer.setConf(conf.sequence);
|
||||
ganttRenderer.setConf(conf.gantt);
|
||||
classRenderer.setConf(conf.class);
|
||||
stateRenderer.setConf(conf.state);
|
||||
stateRendererV2.setConf(conf.state);
|
||||
infoRenderer.setConf(conf.class);
|
||||
pieRenderer.setConf(conf.class);
|
||||
erRenderer.setConf(conf.er);
|
||||
journeyRenderer.setConf(conf.journey);
|
||||
errorRenderer.setConf(conf.class);
|
||||
}
|
||||
|
||||
function initialize(options) {
|
||||
let _config = config;
|
||||
logger.debug('Initializing mermaidAPI ', { version: pkg.version, options, _config });
|
||||
// Update default config with options supplied at initialization
|
||||
console.log(`mermaidAPI.initialize: v${pkg.version}`);
|
||||
// Set default options
|
||||
if (typeof options === 'object') {
|
||||
_config = Object.assign(_config, options);
|
||||
setConf(_config);
|
||||
assignWithDepth(config, options);
|
||||
updateRendererConfigs(config);
|
||||
}
|
||||
setConfig(_config);
|
||||
setLogLevel(_config.logLevel);
|
||||
setConfig(config);
|
||||
|
||||
setLogLevel(config.logLevel);
|
||||
logger.debug('mermaidAPI.initialize: ', config);
|
||||
}
|
||||
|
||||
// function getConfig () {
|
||||
|
@ -979,13 +969,20 @@ function initialize(options) {
|
|||
// return config
|
||||
// }
|
||||
|
||||
const mermaidAPI = {
|
||||
const mermaidAPI = Object.freeze({
|
||||
render,
|
||||
parse,
|
||||
initialize,
|
||||
reinitialize,
|
||||
getConfig
|
||||
};
|
||||
getConfig,
|
||||
setConfig,
|
||||
reset: () => {
|
||||
// console.warn('reset');
|
||||
configApi.reset(defaultConfig);
|
||||
assignWithDepth(config, defaultConfig, { clobber: true });
|
||||
updateRendererConfigs(config);
|
||||
},
|
||||
defaultConfig
|
||||
});
|
||||
|
||||
export default mermaidAPI;
|
||||
/**
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
/* eslint-env jasmine */
|
||||
import mermaidAPI from './mermaidAPI';
|
||||
import { assignWithDepth } from './utils';
|
||||
|
||||
describe('when using mermaidAPI and ', function() {
|
||||
describe('doing initialize ', function() {
|
||||
beforeEach(function() {
|
||||
document.body.innerHTML = '';
|
||||
mermaidAPI.reset();
|
||||
});
|
||||
|
||||
it('should copy a literal into the configuration', function() {
|
||||
|
@ -26,13 +28,64 @@ describe('when using mermaidAPI and ', function() {
|
|||
};
|
||||
|
||||
mermaidAPI.initialize({ testObject: object });
|
||||
let config = mermaidAPI.getConfig();
|
||||
|
||||
console.log('1:', config);
|
||||
expect(config.testObject.test1).toBe(1);
|
||||
mermaidAPI.initialize({ testObject: { test3: true } });
|
||||
const config = mermaidAPI.getConfig();
|
||||
config = mermaidAPI.getConfig();
|
||||
console.log(config);
|
||||
|
||||
expect(config.testObject.test1).toBe(1);
|
||||
expect(config.testObject.test2).toBe(false);
|
||||
expect(config.testObject.test3).toBe(true);
|
||||
});
|
||||
it('should reset mermaid config to global defaults', function() {
|
||||
let config = {
|
||||
logLevel: 0
|
||||
};
|
||||
mermaidAPI.initialize(config);
|
||||
expect(mermaidAPI.getConfig().logLevel).toBe(0);
|
||||
mermaidAPI.reset();
|
||||
expect(mermaidAPI.getConfig()).toEqual(mermaidAPI.defaultConfig);
|
||||
});
|
||||
it('should prevent clobbering global defaults (direct)', function() {
|
||||
let config = assignWithDepth({}, mermaidAPI.defaultConfig);
|
||||
assignWithDepth(config, { logLevel: 0 });
|
||||
|
||||
let error = { message: '' };
|
||||
try {
|
||||
mermaidAPI['defaultConfig'] = config;
|
||||
} catch(e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toBe('Cannot assign to read only property \'defaultConfig\' of object \'#<Object>\'');
|
||||
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
|
||||
});
|
||||
it('should prevent changes to global defaults (direct)', function() {
|
||||
let error = { message: '' };
|
||||
try {
|
||||
mermaidAPI.defaultConfig.logLevel = 0;
|
||||
} catch(e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toBe('Cannot assign to read only property \'logLevel\' of object \'#<Object>\'');
|
||||
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
|
||||
});
|
||||
it('should prevent sneaky changes to global defaults (assignWithDepth)', function() {
|
||||
let config = {
|
||||
logLevel: 0
|
||||
};
|
||||
let error = { message: '' };
|
||||
try {
|
||||
assignWithDepth(mermaidAPI.defaultConfig, config);
|
||||
} catch(e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toBe('Cannot assign to read only property \'logLevel\' of object \'#<Object>\'');
|
||||
expect(mermaidAPI.defaultConfig['logLevel']).toBe(5);
|
||||
});
|
||||
|
||||
});
|
||||
describe('checking validity of input ', function() {
|
||||
it('it should throw for an invalid definiton', function() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
$mainBkg: #1f2020;
|
||||
$secondBkg: lighten(#1f2020, 16);
|
||||
$mainContrastColor: lightgrey;
|
||||
$secondBkg: lighten(#1f2020, 24);
|
||||
$mainContrastColor: rgba(226, 226, 226, .75);
|
||||
$darkTextColor: #323D47;
|
||||
$lineColor: $mainContrastColor;
|
||||
$border1: #81B1DB;
|
||||
$border2: rgba(255, 255, 255, 0.25);
|
||||
$lineColor: lighten($mainContrastColor, 24);
|
||||
$border1: #81b1db;
|
||||
$border2: darken(#81b1db, 32);
|
||||
$arrowheadColor: $mainContrastColor;
|
||||
|
||||
/* Flowchart variables */
|
||||
|
@ -31,10 +31,10 @@ $labelTextColor: $mainContrastColor;
|
|||
$loopTextColor: $mainContrastColor;
|
||||
$noteBorderColor: $border2;
|
||||
$noteBkgColor: #fff5ad;
|
||||
$noteTextColor: $mainBkg;
|
||||
$noteTextColor: #1f2020;
|
||||
$activationBorderColor: $border1;
|
||||
$activationBkgColor: $secondBkg;
|
||||
$sequenceNumberColor: white;
|
||||
$sequenceNumberColor: $mainContrastColor;
|
||||
|
||||
/* Gantt chart variables */
|
||||
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
}
|
||||
|
||||
/* Classes common for multiple diagrams */
|
||||
|
||||
body {
|
||||
background-color: $mainBkg;
|
||||
}
|
||||
.error-icon {
|
||||
fill: $errorBkgColor;
|
||||
}
|
||||
|
|
272
src/utils.js
272
src/utils.js
|
@ -9,10 +9,12 @@ import {
|
|||
curveNatural,
|
||||
curveStep,
|
||||
curveStepAfter,
|
||||
curveStepBefore
|
||||
curveStepBefore,
|
||||
select
|
||||
} from 'd3';
|
||||
import { logger } from './logger';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import common from './diagrams/common/common';
|
||||
|
||||
// Effectively an enum of the supported curve types, accessible by name
|
||||
const d3CurveTypes = {
|
||||
|
@ -34,7 +36,7 @@ const anyComment = /\s*%%.*\n/gm;
|
|||
|
||||
/**
|
||||
* @function detectInit
|
||||
* Detects the init config object from the text
|
||||
* Detects the init config object from the text and (re)initializes mermaid
|
||||
* ```mermaid
|
||||
* %%{init: {"theme": "debug", "logLevel": 1 }}%%
|
||||
* graph LR
|
||||
|
@ -60,17 +62,29 @@ const anyComment = /\s*%%.*\n/gm;
|
|||
* ```
|
||||
*
|
||||
* @param {string} text The text defining the graph
|
||||
* @returns {object} the json object representing the init to pass to mermaid.initialize()
|
||||
* @returns {object} the json object representing the init passed to mermaid.initialize()
|
||||
*/
|
||||
export const detectInit = function(text) {
|
||||
let inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
|
||||
let results = {};
|
||||
if (Array.isArray(inits)) {
|
||||
let args = inits.map(init => init.args);
|
||||
results = Object.assign(results, ...args);
|
||||
results = assignWithDepth(results, [...args]);
|
||||
} else {
|
||||
results = inits.args;
|
||||
}
|
||||
if (results) {
|
||||
let type = detectType(text);
|
||||
['config'].forEach(prop => {
|
||||
if (typeof results[prop] !== 'undefined') {
|
||||
if (type === 'flowchart-v2') {
|
||||
type = 'flowchart';
|
||||
}
|
||||
results[type] = results[prop];
|
||||
delete results[prop];
|
||||
}
|
||||
});
|
||||
}
|
||||
return results;
|
||||
};
|
||||
|
||||
|
@ -382,7 +396,257 @@ export const generateId = () => {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @function assignWithDepth
|
||||
* Extends the functionality of {@link ObjectConstructor.assign} with the ability to merge arbitrary-depth objects
|
||||
* For each key in src with path `k` (recursively) performs an Object.assign(dst[`k`], src[`k`]) with
|
||||
* a slight change from the typical handling of undefined for dst[`k`]: instead of raising an error,
|
||||
* dst[`k`] is auto-initialized to {} and effectively merged with src[`k`]
|
||||
* <p>
|
||||
* Additionally, dissimilar types will not clobber unless the config.clobber parameter === true. Example:
|
||||
* ```
|
||||
* let config_0 = { foo: { bar: 'bar' }, bar: 'foo' };
|
||||
* let config_1 = { foo: 'foo', bar: 'bar' };
|
||||
* let result = assignWithDepth(config_0, config_1);
|
||||
* console.log(result);
|
||||
* //-> result: { foo: { bar: 'bar' }, bar: 'bar' }
|
||||
* ```
|
||||
* <p>
|
||||
* Traditional Object.assign would have clobbered foo in config_0 with foo in config_1.
|
||||
* <p>
|
||||
* If src is a destructured array of objects and dst is not an array, assignWithDepth will apply each element of src to dst
|
||||
* in order.
|
||||
* @param dst:any - the destination of the merge
|
||||
* @param src:any - the source object(s) to merge into destination
|
||||
* @param config:{ depth: number, clobber: boolean } - depth: depth to traverse within src and dst for merging -
|
||||
* clobber: should dissimilar types clobber (default: { depth: 2, clobber: false })
|
||||
* @returns {*}
|
||||
*/
|
||||
export const assignWithDepth = function(dst, src, config) {
|
||||
const { depth, clobber } = Object.assign({ depth: 2, clobber: false }, config);
|
||||
if (Array.isArray(src) && !Array.isArray(dst)) {
|
||||
src.forEach(s => assignWithDepth(dst, s, config));
|
||||
return dst;
|
||||
}
|
||||
if (typeof dst === 'undefined' || depth <= 0) {
|
||||
if (dst !== undefined && dst !== null && typeof dst === 'object' && typeof src === 'object') {
|
||||
return Object.assign(dst, src);
|
||||
} else {
|
||||
return src;
|
||||
}
|
||||
}
|
||||
if (typeof src !== 'undefined' && typeof dst === 'object' && typeof src === 'object') {
|
||||
Object.keys(src).forEach(key => {
|
||||
if (
|
||||
typeof src[key] === 'object' &&
|
||||
(dst[key] === undefined || typeof dst[key] === 'object')
|
||||
) {
|
||||
if (dst[key] === undefined) {
|
||||
dst[key] = {};
|
||||
}
|
||||
dst[key] = assignWithDepth(dst[key], src[key], { depth: depth - 1, clobber });
|
||||
} else if (clobber || (typeof dst[key] !== 'object' && typeof src[key] !== 'object')) {
|
||||
dst[key] = src[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
return dst;
|
||||
};
|
||||
|
||||
export const getTextObj = function() {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: undefined,
|
||||
anchor: 'start',
|
||||
style: '#666',
|
||||
width: 100,
|
||||
height: 100,
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
valign: undefined
|
||||
};
|
||||
};
|
||||
|
||||
export const drawSimpleText = function(elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(common.lineBreakRegex, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
textElem.style('font-family', textData.fontFamily);
|
||||
textElem.style('font-size', textData.fontSize);
|
||||
textElem.style('font-weight', textData.fontWeight);
|
||||
textElem.attr('fill', textData.fill);
|
||||
if (typeof textData.class !== 'undefined') {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.attr('fill', textData.fill);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
};
|
||||
|
||||
export const wrapLabel = (label, maxWidth, config) => {
|
||||
config = Object.assign(
|
||||
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15, joinWith: '<br/>' },
|
||||
config
|
||||
);
|
||||
if (common.lineBreakRegex.test(label)) {
|
||||
return label;
|
||||
}
|
||||
const words = label.split(' ');
|
||||
const completedLines = [];
|
||||
let nextLine = '';
|
||||
words.forEach((word, index) => {
|
||||
const wordLength = calculateTextWidth(`${word} `, config);
|
||||
const nextLineLength = calculateTextWidth(nextLine, config);
|
||||
if (wordLength > maxWidth) {
|
||||
const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config);
|
||||
completedLines.push(nextLine, ...hyphenatedStrings);
|
||||
nextLine = remainingWord;
|
||||
} else if (nextLineLength + wordLength >= maxWidth) {
|
||||
completedLines.push(nextLine);
|
||||
nextLine = word;
|
||||
} else {
|
||||
nextLine = [nextLine, word].filter(Boolean).join(' ');
|
||||
}
|
||||
const currentWord = index + 1;
|
||||
const isLastWord = currentWord === words.length;
|
||||
if (isLastWord) {
|
||||
completedLines.push(nextLine);
|
||||
}
|
||||
});
|
||||
return completedLines.filter(line => line !== '').join(config.joinWith);
|
||||
};
|
||||
|
||||
const breakString = (word, maxWidth, hyphenCharacter = '-', config) => {
|
||||
config = Object.assign(
|
||||
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
|
||||
config
|
||||
);
|
||||
const characters = word.split('');
|
||||
const lines = [];
|
||||
let currentLine = '';
|
||||
characters.forEach((character, index) => {
|
||||
const nextLine = `${currentLine}${character}`;
|
||||
const lineWidth = calculateTextWidth(nextLine, config);
|
||||
if (lineWidth >= maxWidth) {
|
||||
const currentCharacter = index + 1;
|
||||
const isLastLine = characters.length === currentCharacter;
|
||||
const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
|
||||
lines.push(isLastLine ? nextLine : hyphenatedNextLine);
|
||||
currentLine = '';
|
||||
} else {
|
||||
currentLine = nextLine;
|
||||
}
|
||||
});
|
||||
return { hyphenatedStrings: lines, remainingWord: currentLine };
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the text's height, taking into account the wrap breaks and
|
||||
* both the statically configured height, width, and the length of the text (in pixels).
|
||||
*
|
||||
* If the wrapped text text has greater height, we extend the height, so it's
|
||||
* value won't overflow.
|
||||
*
|
||||
* @return - The height for the given text
|
||||
* @param text the text to measure
|
||||
* @param config - the config for fontSize, fontFamily, fontWeight, and margin all impacting the resulting size
|
||||
*/
|
||||
export const calculateTextHeight = function(text, config) {
|
||||
config = Object.assign(
|
||||
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
|
||||
config
|
||||
);
|
||||
return calculateTextDimensions(text, config).height;
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the width of the given text, font size and family.
|
||||
*
|
||||
* @return - The width for the given text
|
||||
* @param text - The text to calculate the width of
|
||||
* @param config - the config for fontSize, fontFamily, fontWeight, and margin all impacting the resulting size
|
||||
*/
|
||||
export const calculateTextWidth = function(text, config) {
|
||||
config = Object.assign(
|
||||
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
|
||||
config
|
||||
);
|
||||
return calculateTextDimensions(text, config).width;
|
||||
};
|
||||
|
||||
/**
|
||||
* This calculates the dimensions of the given text, font size, font family, font weight, and margins.
|
||||
*
|
||||
* @return - The width for the given text
|
||||
* @param text - The text to calculate the width of
|
||||
* @param config - the config for fontSize, fontFamily, fontWeight, and margin all impacting the resulting size
|
||||
*/
|
||||
export const calculateTextDimensions = function(text, config) {
|
||||
config = Object.assign(
|
||||
{ fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
|
||||
config
|
||||
);
|
||||
const { fontSize, fontFamily, fontWeight, margin } = config;
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We can't really know if the user supplied font family will render on the user agent;
|
||||
// thus, we'll take the max width between the user supplied font family, and a default
|
||||
// of sans-serif.
|
||||
const fontFamilies = ['sans-serif', fontFamily];
|
||||
const lines = text.split(common.lineBreakRegex);
|
||||
let maxWidth = 0,
|
||||
height = 0;
|
||||
|
||||
const body = select('body');
|
||||
// We don't want to leak DOM elements - if a removal operation isn't available
|
||||
// for any reason, do not continue.
|
||||
if (!body.remove) {
|
||||
return { width: 0, height: 0 };
|
||||
}
|
||||
|
||||
const g = body.append('svg');
|
||||
|
||||
for (let line of lines) {
|
||||
let cheight = 0;
|
||||
for (let fontFamily of fontFamilies) {
|
||||
const textObj = getTextObj();
|
||||
textObj.text = line;
|
||||
const textElem = drawSimpleText(g, textObj)
|
||||
.style('font-size', fontSize)
|
||||
.style('font-weight', fontWeight)
|
||||
.style('font-family', fontFamily);
|
||||
|
||||
let bBox = (textElem._groups || textElem)[0][0].getBBox();
|
||||
maxWidth = Math.max(maxWidth, bBox.width);
|
||||
cheight = Math.max(bBox.height, cheight);
|
||||
}
|
||||
height += cheight;
|
||||
}
|
||||
|
||||
g.remove();
|
||||
|
||||
// Adds some padding, so the text won't sit exactly within the actor's borders
|
||||
return { width: maxWidth + 2 * margin, height: height + 2 * margin };
|
||||
};
|
||||
|
||||
export default {
|
||||
assignWithDepth,
|
||||
wrapLabel,
|
||||
calculateTextHeight,
|
||||
calculateTextWidth,
|
||||
calculateTextDimensions,
|
||||
detectInit,
|
||||
detectDirective,
|
||||
detectType,
|
||||
|
|
|
@ -1,6 +1,74 @@
|
|||
/* eslint-env jasmine */
|
||||
import utils from './utils';
|
||||
|
||||
describe('when assignWithDepth: should merge objects within objects', function() {
|
||||
it('should handle simple, depth:1 types (identity)', function() {
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
let config_1 = { foo: 'bar', bar: 0 };
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_1);
|
||||
});
|
||||
it('should handle simple, depth:1 types (dst: undefined)', function() {
|
||||
let config_0 = undefined;
|
||||
let config_1 = { foo: 'bar', bar: 0 };
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_1);
|
||||
});
|
||||
it('should handle simple, depth:1 types (src: undefined)', function() {
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
let config_1 = undefined;
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_0);
|
||||
});
|
||||
it('should handle simple, depth:1 types (merge)', function() {
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
let config_1 = { foo: 'foo' };
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({ foo: 'foo', bar: 0});
|
||||
});
|
||||
it('should handle depth:2 types (dst: orphan)', function() {
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
let config_1 = { foo: 'bar' };
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_0);
|
||||
});
|
||||
it('should handle depth:2 types (dst: object, src: simple type)', function() {
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
let config_1 = { foo: 'foo', bar: 'should NOT clobber'};
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({ foo: 'foo', bar: { foo: 'bar' } } );
|
||||
});
|
||||
it('should handle depth:2 types (src: orphan)', function() {
|
||||
let config_0 = { foo: 'bar' };
|
||||
let config_1 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_1);
|
||||
});
|
||||
it('should handle depth:2 types (merge)', function() {
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' }, boofar: 1 };
|
||||
let config_1 = { foo: 'foo', bar: { bar: 0 }, foobar: 'foobar' };
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({ foo: "foo", bar: { foo: "bar", bar: 0 }, foobar: "foobar", boofar: 1 });
|
||||
});
|
||||
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 2)', function() {
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: 'this', willbe: 'clobbered' } } }, boofar: 1 };
|
||||
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } }, foobar: 'foobar' };
|
||||
let result = utils.assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } }, foobar: "foobar", boofar: 1 });
|
||||
});
|
||||
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 1)', function() {
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: '', willNotbe: 'present' }, bar: 'shouldNotBePresent' } }, boofar: 1 };
|
||||
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: 'foobar' };
|
||||
let result = utils.assignWithDepth(config_0, config_1, { depth: 1 });
|
||||
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: "foobar", boofar: 1 });
|
||||
});
|
||||
it('should handle depth:3 types (merge with no clobber because assignWithDepth::depth == 3)', function() {
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar', bar: { foo: { message: '', willbe: 'present' } } }, boofar: 1 };
|
||||
let config_1 = { foo: 'foo', bar: { foo: 'foo', bar: { foo: { message: 'this' } } }, foobar: 'foobar' };
|
||||
let result = utils.assignWithDepth(config_0, config_1, { depth: 3 });
|
||||
expect(result).toEqual({ foo: "foo", bar: { foo: 'foo', bar: { foo: { message: 'this', willbe: 'present' } } }, foobar: "foobar", boofar: 1 });
|
||||
});
|
||||
});
|
||||
describe('when detecting chart type ', function() {
|
||||
it('should handle a graph definition', function() {
|
||||
const str = 'graph TB\nbfs1:queue';
|
||||
|
@ -27,6 +95,16 @@ Alice->Bob: hi`;
|
|||
expect(type).toBe('sequence');
|
||||
expect(init).toEqual({logLevel:0,theme:"dark"});
|
||||
});
|
||||
it('should handle an init definition with config converted to the proper diagram configuration', function() {
|
||||
const str = `
|
||||
%%{init: { 'logLevel': 0, 'theme': 'dark', 'config': {'wrapEnabled': true} } }%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: hi`;
|
||||
const type = utils.detectType(str);
|
||||
const init = utils.detectInit(str);
|
||||
expect(type).toBe('sequence');
|
||||
expect(init).toEqual({logLevel:0,theme:"dark", sequence: { wrapEnabled: true }});
|
||||
});
|
||||
it('should handle a multiline init definition', function() {
|
||||
const str = `
|
||||
%%{
|
||||
|
|
Loading…
Reference in New Issue