mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-17 23:39:26 +02:00
fix: add imperativeState and replace sequenceDb global variables with it
This commit is contained in:
1
.npmrc
1
.npmrc
@@ -1,2 +1,3 @@
|
|||||||
|
registry=https://registry.npmjs.org
|
||||||
auto-install-peers=true
|
auto-install-peers=true
|
||||||
strict-peer-dependencies=false
|
strict-peer-dependencies=false
|
||||||
|
@@ -2,57 +2,60 @@ import * as configApi from '../../config.js';
|
|||||||
import { log } from '../../logger.js';
|
import { log } from '../../logger.js';
|
||||||
import { sanitizeText } from '../common/common.js';
|
import { sanitizeText } from '../common/common.js';
|
||||||
import {
|
import {
|
||||||
setAccTitle,
|
|
||||||
getAccTitle,
|
|
||||||
setDiagramTitle,
|
|
||||||
getDiagramTitle,
|
|
||||||
getAccDescription,
|
|
||||||
setAccDescription,
|
|
||||||
clear as commonClear,
|
clear as commonClear,
|
||||||
|
getAccDescription,
|
||||||
|
getAccTitle,
|
||||||
|
getDiagramTitle,
|
||||||
|
setAccDescription,
|
||||||
|
setAccTitle,
|
||||||
|
setDiagramTitle,
|
||||||
} from '../common/commonDb.js';
|
} from '../common/commonDb.js';
|
||||||
|
import { createImperativeState } from '../../utils/imperativeState.js';
|
||||||
|
|
||||||
let prevActor = undefined;
|
const state = createImperativeState(() => ({
|
||||||
let actors = {};
|
prevActor: undefined,
|
||||||
let createdActors = {};
|
actors: {},
|
||||||
let destroyedActors = {};
|
createdActors: {},
|
||||||
let boxes = [];
|
destroyedActors: {},
|
||||||
let messages = [];
|
boxes: [],
|
||||||
const notes = [];
|
messages: [],
|
||||||
let sequenceNumbersEnabled = false;
|
notes: [],
|
||||||
let wrapEnabled;
|
sequenceNumbersEnabled: false,
|
||||||
let currentBox = undefined;
|
wrapEnabled: undefined,
|
||||||
let lastCreated = undefined;
|
currentBox: undefined,
|
||||||
let lastDestroyed = undefined;
|
lastCreated: undefined,
|
||||||
|
lastDestroyed: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
export const addBox = function (data) {
|
export const addBox = function (data) {
|
||||||
boxes.push({
|
state.records.boxes.push({
|
||||||
name: data.text,
|
name: data.text,
|
||||||
wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap,
|
wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap,
|
||||||
fill: data.color,
|
fill: data.color,
|
||||||
actorKeys: [],
|
actorKeys: [],
|
||||||
});
|
});
|
||||||
currentBox = boxes.slice(-1)[0];
|
state.records.currentBox = state.records.boxes.slice(-1)[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addActor = function (id, name, description, type) {
|
export const addActor = function (id, name, description, type) {
|
||||||
let assignedBox = currentBox;
|
let assignedBox = state.records.currentBox;
|
||||||
const old = actors[id];
|
const old = state.records.actors[id];
|
||||||
if (old) {
|
if (old) {
|
||||||
// If already set and trying to set to a new one throw error
|
// If already set and trying to set to a new one throw error
|
||||||
if (currentBox && old.box && currentBox !== old.box) {
|
if (state.records.currentBox && old.box && state.records.currentBox !== old.box) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'A same participant should only be defined in one Box: ' +
|
'A same participant should only be defined in one Box: ' +
|
||||||
old.name +
|
old.name +
|
||||||
" can't be in '" +
|
" can't be in '" +
|
||||||
old.box.name +
|
old.box.name +
|
||||||
"' and in '" +
|
"' and in '" +
|
||||||
currentBox.name +
|
state.records.currentBox.name +
|
||||||
"' at the same time."
|
"' at the same time."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't change the box if already
|
// Don't change the box if already
|
||||||
assignedBox = old.box ? old.box : currentBox;
|
assignedBox = old.box ? old.box : state.records.currentBox;
|
||||||
old.box = assignedBox;
|
old.box = assignedBox;
|
||||||
|
|
||||||
// Don't allow description nulling
|
// Don't allow description nulling
|
||||||
@@ -69,36 +72,42 @@ export const addActor = function (id, name, description, type) {
|
|||||||
description = { text: name, wrap: null, type };
|
description = { text: name, wrap: null, type };
|
||||||
}
|
}
|
||||||
|
|
||||||
actors[id] = {
|
state.records.actors[id] = {
|
||||||
box: assignedBox,
|
box: assignedBox,
|
||||||
name: name,
|
name: name,
|
||||||
description: description.text,
|
description: description.text,
|
||||||
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
|
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
|
||||||
prevActor: prevActor,
|
prevActor: state.records.prevActor,
|
||||||
links: {},
|
links: {},
|
||||||
properties: {},
|
properties: {},
|
||||||
actorCnt: null,
|
actorCnt: null,
|
||||||
rectData: null,
|
rectData: null,
|
||||||
type: type || 'participant',
|
type: type || 'participant',
|
||||||
};
|
};
|
||||||
if (prevActor && actors[prevActor]) {
|
if (state.records.prevActor && state.records.actors[state.records.prevActor]) {
|
||||||
actors[prevActor].nextActor = id;
|
state.records.actors[state.records.prevActor].nextActor = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentBox) {
|
if (state.records.currentBox) {
|
||||||
currentBox.actorKeys.push(id);
|
state.records.currentBox.actorKeys.push(id);
|
||||||
}
|
}
|
||||||
prevActor = id;
|
state.records.prevActor = id;
|
||||||
};
|
};
|
||||||
|
|
||||||
const activationCount = (part) => {
|
const activationCount = (part) => {
|
||||||
let i;
|
let i;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
for (i = 0; i < messages.length; i++) {
|
for (i = 0; i < state.records.messages.length; i++) {
|
||||||
if (messages[i].type === LINETYPE.ACTIVE_START && messages[i].from.actor === part) {
|
if (
|
||||||
|
state.records.messages[i].type === LINETYPE.ACTIVE_START &&
|
||||||
|
state.records.messages[i].from.actor === part
|
||||||
|
) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
if (messages[i].type === LINETYPE.ACTIVE_END && messages[i].from.actor === part) {
|
if (
|
||||||
|
state.records.messages[i].type === LINETYPE.ACTIVE_END &&
|
||||||
|
state.records.messages[i].from.actor === part
|
||||||
|
) {
|
||||||
count--;
|
count--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +115,7 @@ const activationCount = (part) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const addMessage = function (idFrom, idTo, message, answer) {
|
export const addMessage = function (idFrom, idTo, message, answer) {
|
||||||
messages.push({
|
state.records.messages.push({
|
||||||
from: idFrom,
|
from: idFrom,
|
||||||
to: idTo,
|
to: idTo,
|
||||||
message: message.text,
|
message: message.text,
|
||||||
@@ -137,7 +146,7 @@ export const addSignal = function (
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
messages.push({
|
state.records.messages.push({
|
||||||
from: idFrom,
|
from: idFrom,
|
||||||
to: idTo,
|
to: idTo,
|
||||||
message: message.text,
|
message: message.text,
|
||||||
@@ -149,63 +158,58 @@ export const addSignal = function (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const hasAtLeastOneBox = function () {
|
export const hasAtLeastOneBox = function () {
|
||||||
return boxes.length > 0;
|
return state.records.boxes.length > 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hasAtLeastOneBoxWithTitle = function () {
|
export const hasAtLeastOneBoxWithTitle = function () {
|
||||||
return boxes.some((b) => b.name);
|
return state.records.boxes.some((b) => b.name);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMessages = function () {
|
export const getMessages = function () {
|
||||||
return messages;
|
return state.records.messages;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getBoxes = function () {
|
export const getBoxes = function () {
|
||||||
return boxes;
|
return state.records.boxes;
|
||||||
};
|
};
|
||||||
export const getActors = function () {
|
export const getActors = function () {
|
||||||
return actors;
|
return state.records.actors;
|
||||||
};
|
};
|
||||||
export const getCreatedActors = function () {
|
export const getCreatedActors = function () {
|
||||||
return createdActors;
|
return state.records.createdActors;
|
||||||
};
|
};
|
||||||
export const getDestroyedActors = function () {
|
export const getDestroyedActors = function () {
|
||||||
return destroyedActors;
|
return state.records.destroyedActors;
|
||||||
};
|
};
|
||||||
export const getActor = function (id) {
|
export const getActor = function (id) {
|
||||||
return actors[id];
|
return state.records.actors[id];
|
||||||
};
|
};
|
||||||
export const getActorKeys = function () {
|
export const getActorKeys = function () {
|
||||||
return Object.keys(actors);
|
return Object.keys(state.records.actors);
|
||||||
};
|
};
|
||||||
export const enableSequenceNumbers = function () {
|
export const enableSequenceNumbers = function () {
|
||||||
sequenceNumbersEnabled = true;
|
state.records.sequenceNumbersEnabled = true;
|
||||||
};
|
};
|
||||||
export const disableSequenceNumbers = function () {
|
export const disableSequenceNumbers = function () {
|
||||||
sequenceNumbersEnabled = false;
|
state.records.sequenceNumbersEnabled = false;
|
||||||
};
|
};
|
||||||
export const showSequenceNumbers = () => sequenceNumbersEnabled;
|
export const showSequenceNumbers = () => state.records.sequenceNumbersEnabled;
|
||||||
|
|
||||||
export const setWrap = function (wrapSetting) {
|
export const setWrap = function (wrapSetting) {
|
||||||
wrapEnabled = wrapSetting;
|
state.records.wrapEnabled = wrapSetting;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const autoWrap = () => {
|
export const autoWrap = () => {
|
||||||
// if setWrap has been called, use that value, otherwise use the value from the config
|
// if setWrap has been called, use that value, otherwise use the value from the config
|
||||||
// TODO: refactor, always use the config value let setWrap update the config value
|
// TODO: refactor, always use the config value let setWrap update the config value
|
||||||
if (wrapEnabled !== undefined) {
|
if (state.records.wrapEnabled !== undefined) {
|
||||||
return wrapEnabled;
|
return state.records.wrapEnabled;
|
||||||
}
|
}
|
||||||
return configApi.getConfig().sequence.wrap;
|
return configApi.getConfig().sequence.wrap;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const clear = function () {
|
export const clear = function () {
|
||||||
actors = {};
|
state.reset();
|
||||||
createdActors = {};
|
|
||||||
destroyedActors = {};
|
|
||||||
boxes = [];
|
|
||||||
messages = [];
|
|
||||||
sequenceNumbersEnabled = false;
|
|
||||||
commonClear();
|
commonClear();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -247,7 +251,7 @@ export const parseBoxData = function (str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const boxData = {
|
return {
|
||||||
color: color,
|
color: color,
|
||||||
text:
|
text:
|
||||||
title !== undefined
|
title !== undefined
|
||||||
@@ -262,7 +266,6 @@ export const parseBoxData = function (str) {
|
|||||||
: undefined
|
: undefined
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
return boxData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LINETYPE = {
|
export const LINETYPE = {
|
||||||
@@ -321,8 +324,8 @@ export const addNote = function (actor, placement, message) {
|
|||||||
// eslint-disable-next-line unicorn/prefer-spread
|
// eslint-disable-next-line unicorn/prefer-spread
|
||||||
const actors = [].concat(actor, actor);
|
const actors = [].concat(actor, actor);
|
||||||
|
|
||||||
notes.push(note);
|
state.records.notes.push(note);
|
||||||
messages.push({
|
state.records.messages.push({
|
||||||
from: actors[0],
|
from: actors[0],
|
||||||
to: actors[1],
|
to: actors[1],
|
||||||
message: message.text,
|
message: message.text,
|
||||||
@@ -414,7 +417,7 @@ function insertProperties(actor, properties) {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function boxEnd() {
|
function boxEnd() {
|
||||||
currentBox = undefined;
|
state.records.currentBox = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addDetails = function (actorId, text) {
|
export const addDetails = function (actorId, text) {
|
||||||
@@ -468,7 +471,7 @@ export const apply = function (param) {
|
|||||||
} else {
|
} else {
|
||||||
switch (param.type) {
|
switch (param.type) {
|
||||||
case 'sequenceIndex':
|
case 'sequenceIndex':
|
||||||
messages.push({
|
state.records.messages.push({
|
||||||
from: undefined,
|
from: undefined,
|
||||||
to: undefined,
|
to: undefined,
|
||||||
message: {
|
message: {
|
||||||
@@ -484,18 +487,18 @@ export const apply = function (param) {
|
|||||||
addActor(param.actor, param.actor, param.description, param.draw);
|
addActor(param.actor, param.actor, param.description, param.draw);
|
||||||
break;
|
break;
|
||||||
case 'createParticipant':
|
case 'createParticipant':
|
||||||
if (actors[param.actor]) {
|
if (state.records.actors[param.actor]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior"
|
"It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
lastCreated = param.actor;
|
state.records.lastCreated = param.actor;
|
||||||
addActor(param.actor, param.actor, param.description, param.draw);
|
addActor(param.actor, param.actor, param.description, param.draw);
|
||||||
createdActors[param.actor] = messages.length;
|
state.records.createdActors[param.actor] = state.records.messages.length;
|
||||||
break;
|
break;
|
||||||
case 'destroyParticipant':
|
case 'destroyParticipant':
|
||||||
lastDestroyed = param.actor;
|
state.records.lastDestroyed = param.actor;
|
||||||
destroyedActors[param.actor] = messages.length;
|
state.records.destroyedActors[param.actor] = state.records.messages.length;
|
||||||
break;
|
break;
|
||||||
case 'activeStart':
|
case 'activeStart':
|
||||||
addSignal(param.actor, undefined, undefined, param.signalType);
|
addSignal(param.actor, undefined, undefined, param.signalType);
|
||||||
@@ -519,25 +522,28 @@ export const apply = function (param) {
|
|||||||
addDetails(param.actor, param.text);
|
addDetails(param.actor, param.text);
|
||||||
break;
|
break;
|
||||||
case 'addMessage':
|
case 'addMessage':
|
||||||
if (lastCreated) {
|
if (state.records.lastCreated) {
|
||||||
if (param.to !== lastCreated) {
|
if (param.to !== state.records.lastCreated) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The created participant ' +
|
'The created participant ' +
|
||||||
lastCreated +
|
state.records.lastCreated +
|
||||||
' does not have an associated creating message after its declaration. Please check the sequence diagram.'
|
' does not have an associated creating message after its declaration. Please check the sequence diagram.'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
lastCreated = undefined;
|
state.records.lastCreated = undefined;
|
||||||
}
|
}
|
||||||
} else if (lastDestroyed) {
|
} else if (state.records.lastDestroyed) {
|
||||||
if (param.to !== lastDestroyed && param.from !== lastDestroyed) {
|
if (
|
||||||
|
param.to !== state.records.lastDestroyed &&
|
||||||
|
param.from !== state.records.lastDestroyed
|
||||||
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'The destroyed participant ' +
|
'The destroyed participant ' +
|
||||||
lastDestroyed +
|
state.records.lastDestroyed +
|
||||||
' does not have an associated destroying message after its declaration. Please check the sequence diagram.'
|
' does not have an associated destroying message after its declaration. Please check the sequence diagram.'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
lastDestroyed = undefined;
|
state.records.lastDestroyed = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addSignal(param.from, param.to, param.msg, param.signalType, param.activate);
|
addSignal(param.from, param.to, param.msg, param.signalType, param.activate);
|
||||||
|
78
packages/mermaid/src/utils/imperativeState.spec.ts
Normal file
78
packages/mermaid/src/utils/imperativeState.spec.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { createImperativeState, domain } from './imperativeState.js';
|
||||||
|
|
||||||
|
describe('domain.optional', () => {
|
||||||
|
it('should set undefined without args', () => {
|
||||||
|
expect(domain.optional()).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set identity with args', () => {
|
||||||
|
const value = {};
|
||||||
|
expect(domain.optional(value)).toEqual(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('domain.identity', () => {
|
||||||
|
it('should set identity', () => {
|
||||||
|
const value = {};
|
||||||
|
expect(domain.identity(value)).toEqual(value);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createImperativeState', () => {
|
||||||
|
it('should create state with values from initializer', () => {
|
||||||
|
const baz = {
|
||||||
|
flag: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = createImperativeState(() => ({
|
||||||
|
foo: domain.optional<number>(),
|
||||||
|
bar: domain.identity<string[]>([]),
|
||||||
|
baz,
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(state.records.foo).toBeUndefined();
|
||||||
|
expect(state.records.bar).toEqual([]);
|
||||||
|
expect(state.records.baz).toBe(baz);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update records', () => {
|
||||||
|
const state = createImperativeState(() => ({
|
||||||
|
foo: domain.optional<number>(),
|
||||||
|
bar: domain.identity<string[]>([]),
|
||||||
|
baz: {
|
||||||
|
flag: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
state.records.foo = 5;
|
||||||
|
state.records.bar = ['hello'];
|
||||||
|
state.records.baz.flag = true;
|
||||||
|
|
||||||
|
expect(state.records.foo).toEqual(5);
|
||||||
|
expect(state.records.bar).toEqual(['hello']);
|
||||||
|
expect(state.records.baz).toEqual({
|
||||||
|
flag: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset records', () => {
|
||||||
|
const state = createImperativeState(() => ({
|
||||||
|
foo: domain.optional<number>(),
|
||||||
|
bar: domain.identity<string[]>([]),
|
||||||
|
baz: {
|
||||||
|
flag: false,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
state.records.foo = 5;
|
||||||
|
state.records.bar = ['hello'];
|
||||||
|
state.records.baz.flag = true;
|
||||||
|
state.reset();
|
||||||
|
|
||||||
|
expect(state.records.foo).toBeUndefined();
|
||||||
|
expect(state.records.bar).toEqual([]);
|
||||||
|
expect(state.records.baz).toEqual({
|
||||||
|
flag: false,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
37
packages/mermaid/src/utils/imperativeState.ts
Normal file
37
packages/mermaid/src/utils/imperativeState.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
export const createImperativeState = <S extends Record<string, unknown>>(init: () => S) => {
|
||||||
|
const state = init();
|
||||||
|
|
||||||
|
return {
|
||||||
|
get records() {
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
reset: () => {
|
||||||
|
Object.keys(state).forEach((key) => {
|
||||||
|
delete state[key];
|
||||||
|
});
|
||||||
|
Object.entries(init()).forEach(([key, value]: [keyof S, any]) => {
|
||||||
|
state[key] = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const domain = {
|
||||||
|
optional: <V>(value?: V) => value,
|
||||||
|
identity: <V>(value: V) => value,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
const state = createImperativeState(() => ({
|
||||||
|
foo: domain.optional<string>(),
|
||||||
|
bar: domain.identity<number[]>([]),
|
||||||
|
baz: domain.optional(1),
|
||||||
|
}));
|
||||||
|
|
||||||
|
typeof state.records:
|
||||||
|
{
|
||||||
|
foo: string | undefined, // actual: undefined
|
||||||
|
bar: number[], // actual: []
|
||||||
|
baz: number | undefined, // actual: 1
|
||||||
|
}
|
||||||
|
*/
|
Reference in New Issue
Block a user