mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 14:29:25 +02:00
Compare commits
16 Commits
ipsep-cola
...
848f69a75c
Author | SHA1 | Date | |
---|---|---|---|
![]() |
848f69a75c | ||
![]() |
99dbeba407 | ||
![]() |
d525acc05b | ||
![]() |
4915545429 | ||
![]() |
334fe87bc6 | ||
![]() |
283e7810d2 | ||
![]() |
237d01d510 | ||
![]() |
afeb761296 | ||
![]() |
3abcfbb8d2 | ||
![]() |
ee82694645 | ||
![]() |
012530e98e | ||
![]() |
a4a27611dd | ||
![]() |
5055ade44e | ||
![]() |
b61bec8faf | ||
![]() |
76d073b027 | ||
![]() |
cc476d59d1 |
5
.changeset/hungry-baths-glow.md
Normal file
5
.changeset/hungry-baths-glow.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`.
|
644
cypress/integration/rendering/sequencediagram-v2.spec.js
Normal file
644
cypress/integration/rendering/sequencediagram-v2.spec.js
Normal file
@@ -0,0 +1,644 @@
|
|||||||
|
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||||
|
|
||||||
|
const looks = ['classic'];
|
||||||
|
const participantTypes = [
|
||||||
|
'participant',
|
||||||
|
'actor',
|
||||||
|
'boundary',
|
||||||
|
'control',
|
||||||
|
'entity',
|
||||||
|
'database',
|
||||||
|
'collections',
|
||||||
|
'queue',
|
||||||
|
];
|
||||||
|
|
||||||
|
const interactionTypes = [
|
||||||
|
'->>', // Solid arrow with arrowhead
|
||||||
|
'-->>', // Dotted arrow with arrowhead
|
||||||
|
'->', // Solid arrow without arrowhead
|
||||||
|
'-->', // Dotted arrow without arrowhead
|
||||||
|
'-x', // Solid arrow with cross
|
||||||
|
'--x', // Dotted arrow with cross
|
||||||
|
'->>+', // Solid arrow with arrowhead (activate)
|
||||||
|
'-->>+', // Dotted arrow with arrowhead (activate)
|
||||||
|
];
|
||||||
|
|
||||||
|
const notePositions = ['left of', 'right of', 'over'];
|
||||||
|
|
||||||
|
looks.forEach((look) => {
|
||||||
|
describe(`Sequence Diagram Tests - ${look} look`, () => {
|
||||||
|
it('should render all participant types', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
participantTypes.forEach((type, index) => {
|
||||||
|
diagramCode += ` ${type} ${type}${index} as ${type} ${index}\n`;
|
||||||
|
});
|
||||||
|
// Add some basic interactions
|
||||||
|
for (let i = 0; i < participantTypes.length - 1; i++) {
|
||||||
|
diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`;
|
||||||
|
}
|
||||||
|
imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render all interaction types', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
// Create two participants
|
||||||
|
// Add all interaction types
|
||||||
|
diagramCode += ` participant A\n`;
|
||||||
|
diagramCode += ` participant B\n`;
|
||||||
|
interactionTypes.forEach((interaction, index) => {
|
||||||
|
diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`;
|
||||||
|
});
|
||||||
|
imgSnapshotTest(diagramCode, { look });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render participant creation and destruction', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
participantTypes.forEach((type, index) => {
|
||||||
|
diagramCode += ` ${type} A\n`;
|
||||||
|
diagramCode += ` ${type} B\n`;
|
||||||
|
diagramCode += ` create ${type} ${type}${index}\n`;
|
||||||
|
diagramCode += ` A ->> ${type}${index}: Hello ${type}\n`;
|
||||||
|
if (index % 2 === 0) {
|
||||||
|
diagramCode += ` destroy ${type}${index}\n`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
imgSnapshotTest(diagramCode, { look });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render notes in all positions', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
diagramCode += ` participant A\n`;
|
||||||
|
diagramCode += ` participant B\n`;
|
||||||
|
notePositions.forEach((position, index) => {
|
||||||
|
diagramCode += ` Note ${position} A: Note ${position} ${index}\n`;
|
||||||
|
});
|
||||||
|
diagramCode += ` A ->> B: Message with notes\n`;
|
||||||
|
imgSnapshotTest(diagramCode, { look });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render parallel interactions', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
participantTypes.slice(0, 4).forEach((type, index) => {
|
||||||
|
diagramCode += ` ${type} ${type}${index}\n`;
|
||||||
|
});
|
||||||
|
diagramCode += ` par Parallel actions\n`;
|
||||||
|
for (let i = 0; i < participantTypes.length - 1; i += 2) {
|
||||||
|
diagramCode += ` ${participantTypes[i]}${i} ->> ${participantTypes[i + 1]}${i + 1}: Message ${i}\n`;
|
||||||
|
if (i < participantTypes.length - 2) {
|
||||||
|
diagramCode += ` and\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diagramCode += ` end\n`;
|
||||||
|
imgSnapshotTest(diagramCode, { look });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render alternative flows', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
diagramCode += ` participant A\n`;
|
||||||
|
diagramCode += ` participant B\n`;
|
||||||
|
diagramCode += ` alt Successful case\n`;
|
||||||
|
diagramCode += ` A ->> B: Request\n`;
|
||||||
|
diagramCode += ` B -->> A: Success\n`;
|
||||||
|
diagramCode += ` else Failure case\n`;
|
||||||
|
diagramCode += ` A ->> B: Request\n`;
|
||||||
|
diagramCode += ` B --x A: Failure\n`;
|
||||||
|
diagramCode += ` end\n`;
|
||||||
|
imgSnapshotTest(diagramCode, { look });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render loops', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
participantTypes.slice(0, 3).forEach((type, index) => {
|
||||||
|
diagramCode += ` ${type} ${type}${index}\n`;
|
||||||
|
});
|
||||||
|
diagramCode += ` loop For each participant\n`;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Message ${i}\n`;
|
||||||
|
}
|
||||||
|
diagramCode += ` end\n`;
|
||||||
|
imgSnapshotTest(diagramCode, { look });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render boxes around groups', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
diagramCode += ` box Group 1\n`;
|
||||||
|
participantTypes.slice(0, 3).forEach((type, index) => {
|
||||||
|
diagramCode += ` ${type} ${type}${index}\n`;
|
||||||
|
});
|
||||||
|
diagramCode += ` end\n`;
|
||||||
|
diagramCode += ` box rgb(200,220,255) Group 2\n`;
|
||||||
|
participantTypes.slice(3, 6).forEach((type, index) => {
|
||||||
|
diagramCode += ` ${type} ${type}${index}\n`;
|
||||||
|
});
|
||||||
|
diagramCode += ` end\n`;
|
||||||
|
// Add some interactions
|
||||||
|
diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[3]}0: Cross-group message\n`;
|
||||||
|
imgSnapshotTest(diagramCode, { look });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with different font settings', () => {
|
||||||
|
let diagramCode = `sequenceDiagram\n`;
|
||||||
|
participantTypes.slice(0, 3).forEach((type, index) => {
|
||||||
|
diagramCode += ` ${type} ${type}${index}\n`;
|
||||||
|
});
|
||||||
|
diagramCode += ` ${participantTypes[0]}0 ->> ${participantTypes[1]}1: Regular message\n`;
|
||||||
|
diagramCode += ` Note right of ${participantTypes[1]}1: Regular note\n`;
|
||||||
|
imgSnapshotTest(diagramCode, {
|
||||||
|
look,
|
||||||
|
sequence: {
|
||||||
|
actorFontFamily: 'courier',
|
||||||
|
actorFontSize: 14,
|
||||||
|
messageFontFamily: 'Arial',
|
||||||
|
messageFontSize: 12,
|
||||||
|
noteFontFamily: 'times',
|
||||||
|
noteFontSize: 16,
|
||||||
|
noteAlign: 'left',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Additional tests for specific combinations
|
||||||
|
describe('Sequence Diagram Special Cases', () => {
|
||||||
|
it('should render complex sequence with all features', () => {
|
||||||
|
const diagramCode = `
|
||||||
|
sequenceDiagram
|
||||||
|
box rgb(200,220,255) Authentication
|
||||||
|
actor User
|
||||||
|
boundary LoginUI
|
||||||
|
control AuthService
|
||||||
|
database UserDB
|
||||||
|
end
|
||||||
|
|
||||||
|
box rgb(200,255,220) Order Processing
|
||||||
|
entity Order
|
||||||
|
queue OrderQueue
|
||||||
|
collections AuditLogs
|
||||||
|
end
|
||||||
|
|
||||||
|
User ->> LoginUI: Enter credentials
|
||||||
|
LoginUI ->> AuthService: Validate
|
||||||
|
AuthService ->> UserDB: Query user
|
||||||
|
UserDB -->> AuthService: User data
|
||||||
|
alt Valid credentials
|
||||||
|
AuthService -->> LoginUI: Success
|
||||||
|
LoginUI -->> User: Welcome
|
||||||
|
|
||||||
|
par Place order
|
||||||
|
User ->> Order: New order
|
||||||
|
Order ->> OrderQueue: Process
|
||||||
|
and
|
||||||
|
Order ->> AuditLogs: Record
|
||||||
|
end
|
||||||
|
|
||||||
|
loop Until confirmed
|
||||||
|
OrderQueue ->> Order: Update status
|
||||||
|
Order -->> User: Notification
|
||||||
|
end
|
||||||
|
else Invalid credentials
|
||||||
|
AuthService --x LoginUI: Failure
|
||||||
|
LoginUI --x User: Retry
|
||||||
|
end
|
||||||
|
`;
|
||||||
|
imgSnapshotTest(diagramCode, {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with wrapped messages and notes', () => {
|
||||||
|
const diagramCode = `
|
||||||
|
sequenceDiagram
|
||||||
|
participant A
|
||||||
|
participant B
|
||||||
|
|
||||||
|
A ->> B: This is a very long message that should wrap properly in the diagram rendering
|
||||||
|
Note over A,B: This is a very long note that should also wrap properly when rendered in the diagram
|
||||||
|
|
||||||
|
par Wrapped parallel
|
||||||
|
A ->> B: Parallel message 1<br>with explicit line break
|
||||||
|
and
|
||||||
|
B ->> A: Parallel message 2<br>with explicit line break
|
||||||
|
end
|
||||||
|
|
||||||
|
loop Wrapped loop
|
||||||
|
Note right of B: This is a long note<br>in a loop
|
||||||
|
A ->> B: Message in loop
|
||||||
|
end
|
||||||
|
`;
|
||||||
|
imgSnapshotTest(diagramCode, { sequence: { wrap: true } });
|
||||||
|
});
|
||||||
|
describe('Sequence Diagram Rendering with Different Participant Types', () => {
|
||||||
|
it('should render a sequence diagram with various participant types', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor User
|
||||||
|
participant AuthService as Authentication Service
|
||||||
|
boundary UI
|
||||||
|
control OrderController
|
||||||
|
entity Product
|
||||||
|
database MongoDB
|
||||||
|
collections Products
|
||||||
|
queue OrderQueue
|
||||||
|
User ->> UI: Login request
|
||||||
|
UI ->> AuthService: Validate credentials
|
||||||
|
AuthService -->> UI: Authentication token
|
||||||
|
UI ->> OrderController: Place order
|
||||||
|
OrderController ->> Product: Check availability
|
||||||
|
Product -->> OrderController: Available
|
||||||
|
OrderController ->> MongoDB: Save order
|
||||||
|
MongoDB -->> OrderController: Order saved
|
||||||
|
OrderController ->> OrderQueue: Process payment
|
||||||
|
OrderQueue -->> User: Order confirmation
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render participant creation and destruction with different types', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Customer
|
||||||
|
participant Frontend
|
||||||
|
boundary PaymentGateway
|
||||||
|
Customer ->> Frontend: Place order
|
||||||
|
Frontend ->> OrderProcessor: Process order
|
||||||
|
create database OrderDB
|
||||||
|
OrderProcessor ->> OrderDB: Save order
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle complex interactions between different participant types', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
box rgba(200,220,255,0.5) System Components
|
||||||
|
actor User
|
||||||
|
boundary WebUI
|
||||||
|
control API
|
||||||
|
entity BusinessLogic
|
||||||
|
database MainDB
|
||||||
|
end
|
||||||
|
box rgba(200,255,220,0.5) External Services
|
||||||
|
queue MessageQueue
|
||||||
|
database AuditLogs
|
||||||
|
end
|
||||||
|
|
||||||
|
User ->> WebUI: Submit request
|
||||||
|
WebUI ->> API: Process request
|
||||||
|
API ->> BusinessLogic: Execute business rules
|
||||||
|
BusinessLogic ->> MainDB: Query data
|
||||||
|
MainDB -->> BusinessLogic: Return results
|
||||||
|
BusinessLogic ->> MessageQueue: Publish event
|
||||||
|
MessageQueue -->> AuditLogs: Store audit trail
|
||||||
|
AuditLogs -->> API: Audit complete
|
||||||
|
API -->> WebUI: Return response
|
||||||
|
WebUI -->> User: Show results
|
||||||
|
`,
|
||||||
|
{ sequence: { useMaxWidth: false } }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render parallel processes with different participant types', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Customer
|
||||||
|
participant Frontend
|
||||||
|
boundary PaymentService
|
||||||
|
control InventoryManager
|
||||||
|
entity Order
|
||||||
|
database OrdersDB
|
||||||
|
queue NotificationQueue
|
||||||
|
|
||||||
|
Customer ->> Frontend: Place order
|
||||||
|
Frontend ->> Order: Create order
|
||||||
|
par Parallel Processing
|
||||||
|
Order ->> PaymentService: Process payment
|
||||||
|
and
|
||||||
|
Order ->> InventoryManager: Reserve items
|
||||||
|
end
|
||||||
|
PaymentService -->> Order: Payment confirmed
|
||||||
|
InventoryManager -->> Order: Items reserved
|
||||||
|
Order ->> OrdersDB: Save finalized order
|
||||||
|
OrdersDB -->> Order: Order saved
|
||||||
|
Order ->> NotificationQueue: Send confirmation
|
||||||
|
NotificationQueue -->> Customer: Order confirmation
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render different participant types with notes and loops', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Admin
|
||||||
|
participant Dashboard
|
||||||
|
boundary AuthService
|
||||||
|
control UserManager
|
||||||
|
entity UserProfile
|
||||||
|
database UserDB
|
||||||
|
database Logs
|
||||||
|
|
||||||
|
Admin ->> Dashboard: Open user management
|
||||||
|
loop Authentication check
|
||||||
|
Dashboard ->> AuthService: Verify admin rights
|
||||||
|
AuthService ->> Dashboard: Access granted
|
||||||
|
end
|
||||||
|
Dashboard ->> UserManager: List users
|
||||||
|
UserManager ->> UserDB: Query users
|
||||||
|
UserDB ->> UserManager: Return user data
|
||||||
|
Note right of UserDB: Encrypted data<br/>requires decryption
|
||||||
|
UserManager ->> UserProfile: Format profiles
|
||||||
|
UserProfile ->> UserManager: Formatted data
|
||||||
|
UserManager ->> Dashboard: Display users
|
||||||
|
Dashboard ->> Logs: Record access
|
||||||
|
Logs ->> Admin: Audit trail
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render different participant types with alternative flows', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Client
|
||||||
|
participant MobileApp
|
||||||
|
boundary CloudService
|
||||||
|
control DataProcessor
|
||||||
|
entity Transaction
|
||||||
|
database TransactionsDB
|
||||||
|
queue EventBus
|
||||||
|
|
||||||
|
Client ->> MobileApp: Initiate transaction
|
||||||
|
MobileApp ->> CloudService: Authenticate
|
||||||
|
alt Authentication successful
|
||||||
|
CloudService -->> MobileApp: Auth token
|
||||||
|
MobileApp ->> DataProcessor: Process data
|
||||||
|
DataProcessor ->> Transaction: Create transaction
|
||||||
|
Transaction ->> TransactionsDB: Save record
|
||||||
|
TransactionsDB -->> Transaction: Confirmation
|
||||||
|
Transaction ->> EventBus: Publish event
|
||||||
|
EventBus -->> Client: Notification
|
||||||
|
else Authentication failed
|
||||||
|
CloudService -->> MobileApp: Error
|
||||||
|
MobileApp -->> Client: Show error
|
||||||
|
end
|
||||||
|
`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render different participant types with wrapping text', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor LongNameUser as User With A Very<br/>Long Name
|
||||||
|
participant FE as Frontend Service<br/>With Long Name
|
||||||
|
boundary B as Boundary With<br/>Multiline Name
|
||||||
|
control C as Control With<br/>Multiline Name
|
||||||
|
entity E as Entity With<br/>Multiline Name
|
||||||
|
database DB as Database With<br/>Multiline Name
|
||||||
|
collections COL as Collections With<br/>Multiline Name
|
||||||
|
queue Q as Queue With<br/>Multiline Name
|
||||||
|
|
||||||
|
LongNameUser ->> FE: This is a very long message that should wrap properly in the diagram
|
||||||
|
FE ->> B: Another long message<br/>with explicit<br/>line breaks
|
||||||
|
B -->> FE: Response message that is also quite long and needs to wrap
|
||||||
|
FE ->> C: Process data
|
||||||
|
C ->> E: Validate
|
||||||
|
E -->> C: Validation result
|
||||||
|
C ->> DB: Save
|
||||||
|
DB -->> C: Save result
|
||||||
|
C ->> COL: Log
|
||||||
|
COL -->> Q: Forward
|
||||||
|
Q -->> LongNameUser: Final response with confirmation of all actions taken
|
||||||
|
`,
|
||||||
|
{ sequence: { wrap: true } }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => {
|
||||||
|
it('should render long notes left of boundary', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
boundary Alice
|
||||||
|
actor Bob
|
||||||
|
Alice->>Bob: Hola
|
||||||
|
Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
|
Bob->>Alice: I'm short though
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render wrapped long notes left of control', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
control Alice
|
||||||
|
actor Bob
|
||||||
|
Alice->>Bob: Hola
|
||||||
|
Note left of Alice:wrap: Extremely utterly long line of longness which had previously 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 entity', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
entity Alice
|
||||||
|
actor Bob
|
||||||
|
Alice->>Bob: Hola
|
||||||
|
Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
|
Bob->>Alice: I'm short though
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render wrapped long notes right of database', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
database Alice
|
||||||
|
actor Bob
|
||||||
|
Alice->>Bob: Hola
|
||||||
|
Note right of Alice:wrap: Extremely utterly long line of longness which had previously 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 collections', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
collections Alice
|
||||||
|
actor Bob
|
||||||
|
Alice->>Bob: Hola
|
||||||
|
Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
|
Bob->>Alice: I'm short though
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render wrapped long notes over queue', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
queue Alice
|
||||||
|
actor Bob
|
||||||
|
Alice->>Bob: Hola
|
||||||
|
Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
|
Bob->>Alice: I'm short though
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render notes over actor and boundary', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Alice
|
||||||
|
boundary Charlie
|
||||||
|
note over Alice: Some note
|
||||||
|
note over Charlie: Other note
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render long messages from database to collections', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
database Alice
|
||||||
|
collections Bob
|
||||||
|
Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
|
Bob->>Alice: I'm short though
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render wrapped long messages from control to entity', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
control Alice
|
||||||
|
entity Bob
|
||||||
|
Alice->>Bob:wrap: Extremely utterly long line of longness which had previously 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 queue to boundary', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
queue Alice
|
||||||
|
boundary Bob
|
||||||
|
Alice->>Bob: I'm short
|
||||||
|
Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render wrapped long messages from actor to database', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Alice
|
||||||
|
database Bob
|
||||||
|
Alice->>Bob: I'm short
|
||||||
|
Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
|
||||||
|
`,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('svg size', () => {
|
||||||
|
it('should render a sequence diagram when useMaxWidth is true (default)', () => {
|
||||||
|
renderGraph(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Alice
|
||||||
|
boundary Bob
|
||||||
|
control John as John<br/>Second Line
|
||||||
|
Alice ->> Bob: Hello Bob, how are you?
|
||||||
|
Bob-->>John: How about you John?
|
||||||
|
Bob--x Alice: I am good thanks!
|
||||||
|
Bob-x John: I am good thanks!
|
||||||
|
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
|
||||||
|
Bob-->Alice: Checking with John...
|
||||||
|
alt either this
|
||||||
|
Alice->>John: Yes
|
||||||
|
else or this
|
||||||
|
Alice->>John: No
|
||||||
|
else or this will happen
|
||||||
|
Alice->John: Maybe
|
||||||
|
end
|
||||||
|
par this happens in parallel
|
||||||
|
Alice -->> Bob: Parallel message 1
|
||||||
|
and
|
||||||
|
Alice -->> John: Parallel message 2
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
{ sequence: { useMaxWidth: true } }
|
||||||
|
);
|
||||||
|
cy.get('svg').should((svg) => {
|
||||||
|
expect(svg).to.have.attr('width', '100%');
|
||||||
|
const style = svg.attr('style');
|
||||||
|
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||||
|
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
||||||
|
expect(maxWidthValue).to.be.within(820 * 0.95, 820 * 1.05);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a sequence diagram when useMaxWidth is false', () => {
|
||||||
|
renderGraph(
|
||||||
|
`
|
||||||
|
sequenceDiagram
|
||||||
|
actor Alice
|
||||||
|
boundary Bob
|
||||||
|
control John as John<br/>Second Line
|
||||||
|
Alice ->> Bob: Hello Bob, how are you?
|
||||||
|
Bob-->>John: How about you John?
|
||||||
|
Bob--x Alice: I am good thanks!
|
||||||
|
Bob-x John: I am good thanks!
|
||||||
|
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
|
||||||
|
Bob-->Alice: Checking with John...
|
||||||
|
alt either this
|
||||||
|
Alice->>John: Yes
|
||||||
|
else or this
|
||||||
|
Alice->>John: No
|
||||||
|
else or this will happen
|
||||||
|
Alice->John: Maybe
|
||||||
|
end
|
||||||
|
par this happens in parallel
|
||||||
|
Alice -->> Bob: Parallel message 1
|
||||||
|
and
|
||||||
|
Alice -->> John: Parallel message 2
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
{ sequence: { useMaxWidth: false } }
|
||||||
|
);
|
||||||
|
cy.get('svg').should((svg) => {
|
||||||
|
const width = parseFloat(svg.attr('width'));
|
||||||
|
expect(width).to.be.within(820 * 0.95, 820 * 1.05);
|
||||||
|
expect(svg).to.not.have.attr('style');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -74,6 +74,222 @@ sequenceDiagram
|
|||||||
Bob->>Alice: Hi Alice
|
Bob->>Alice: Hi Alice
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Boundary
|
||||||
|
|
||||||
|
If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
boundary theBoundary
|
||||||
|
participant Bob
|
||||||
|
theBoundary->>Bob: Request from boundary
|
||||||
|
Bob->>theBoundary: Response to boundary
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
boundary theBoundary
|
||||||
|
participant Bob
|
||||||
|
theBoundary->>Bob: Request from boundary
|
||||||
|
Bob->>theBoundary: Response to boundary
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
boundary theBoundary
|
||||||
|
participant Bob
|
||||||
|
theBoundary->>Bob: Request from boundary
|
||||||
|
Bob->>theBoundary: Response to boundary
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
boundary theBoundary
|
||||||
|
participant Bob
|
||||||
|
theBoundary->>Bob: Request from boundary
|
||||||
|
Bob->>theBoundary: Response to boundary
|
||||||
|
```
|
||||||
|
|
||||||
|
### Control
|
||||||
|
|
||||||
|
If you want to use the control symbol for a participant, use the `control` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
control theControl
|
||||||
|
participant Alice
|
||||||
|
theControl->>Alice: Control request
|
||||||
|
Alice->>theControl: Control response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
control theControl
|
||||||
|
participant Alice
|
||||||
|
theControl->>Alice: Control request
|
||||||
|
Alice->>theControl: Control response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
control theControl
|
||||||
|
participant Alice
|
||||||
|
theControl->>Alice: Control request
|
||||||
|
Alice->>theControl: Control response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
control theControl
|
||||||
|
participant Alice
|
||||||
|
theControl->>Alice: Control request
|
||||||
|
Alice->>theControl: Control response
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entity
|
||||||
|
|
||||||
|
If you want to use the entity symbol for a participant, use the `entity` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
entity theEntity
|
||||||
|
participant Bob
|
||||||
|
theEntity->>Bob: Entity request
|
||||||
|
Bob->>theEntity: Entity response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
entity theEntity
|
||||||
|
participant Bob
|
||||||
|
theEntity->>Bob: Entity request
|
||||||
|
Bob->>theEntity: Entity response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
entity theEntity
|
||||||
|
participant Bob
|
||||||
|
theEntity->>Bob: Entity request
|
||||||
|
Bob->>theEntity: Entity response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
entity theEntity
|
||||||
|
participant Bob
|
||||||
|
theEntity->>Bob: Entity request
|
||||||
|
Bob->>theEntity: Entity response
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
If you want to use the database symbol for a participant, use the `database` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
database theDb
|
||||||
|
participant Alice
|
||||||
|
theDb->>Alice: DB query
|
||||||
|
Alice->>theDb: DB result
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
database theDb
|
||||||
|
participant Alice
|
||||||
|
theDb->>Alice: DB query
|
||||||
|
Alice->>theDb: DB result
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
database theDb
|
||||||
|
participant Alice
|
||||||
|
theDb->>Alice: DB query
|
||||||
|
Alice->>theDb: DB result
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
database theDb
|
||||||
|
participant Alice
|
||||||
|
theDb->>Alice: DB query
|
||||||
|
Alice->>theDb: DB result
|
||||||
|
```
|
||||||
|
|
||||||
|
### Collections
|
||||||
|
|
||||||
|
If you want to use the collections symbol for a participant, use the `collections` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
collections theCollection
|
||||||
|
participant Bob
|
||||||
|
theCollection->>Bob: Collections request
|
||||||
|
Bob->>theCollection: Collections response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
collections theCollection
|
||||||
|
participant Bob
|
||||||
|
theCollection->>Bob: Collections request
|
||||||
|
Bob->>theCollection: Collections response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
collections theCollection
|
||||||
|
participant Bob
|
||||||
|
theCollection->>Bob: Collections request
|
||||||
|
Bob->>theCollection: Collections response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
collections theCollection
|
||||||
|
participant Bob
|
||||||
|
theCollection->>Bob: Collections request
|
||||||
|
Bob->>theCollection: Collections response
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queue
|
||||||
|
|
||||||
|
If you want to use the queue symbol for a participant, use the `queue` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
queue theQueue
|
||||||
|
participant Alice
|
||||||
|
theQueue->>Alice: Queue message
|
||||||
|
Alice->>theQueue: Queue
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
queue theQueue
|
||||||
|
participant Alice
|
||||||
|
theQueue->>Alice: Queue message
|
||||||
|
Alice->>theQueue: Queue
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
queue theQueue
|
||||||
|
participant Alice
|
||||||
|
theQueue->>Alice: Queue message
|
||||||
|
Alice->>theQueue: Queue
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
queue theQueue
|
||||||
|
participant Alice
|
||||||
|
theQueue->>Alice: Queue message
|
||||||
|
Alice->>theQueue: Queue
|
||||||
|
```
|
||||||
|
|
||||||
### Aliases
|
### Aliases
|
||||||
|
|
||||||
The actor can have a convenient identifier and a descriptive label.
|
The actor can have a convenient identifier and a descriptive label.
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
%options case-insensitive
|
%options case-insensitive
|
||||||
|
|
||||||
|
|
||||||
// Special states for recognizing aliases
|
// Special states for recognizing aliases
|
||||||
// A special state for grabbing text up to the first comment/newline
|
// A special state for grabbing text up to the first comment/newline
|
||||||
%x ID ALIAS LINE
|
%x ID ALIAS LINE
|
||||||
@@ -31,6 +32,12 @@
|
|||||||
"box" { this.begin('LINE'); return 'box'; }
|
"box" { this.begin('LINE'); return 'box'; }
|
||||||
"participant" { this.begin('ID'); return 'participant'; }
|
"participant" { this.begin('ID'); return 'participant'; }
|
||||||
"actor" { this.begin('ID'); return 'participant_actor'; }
|
"actor" { this.begin('ID'); return 'participant_actor'; }
|
||||||
|
"boundary" { return yy.matchAsActorOrParticipant('boundary', 'participant_boundary', this._input, this); }
|
||||||
|
"control" { return yy.matchAsActorOrParticipant('control', 'participant_control', this._input, this); }
|
||||||
|
"entity" { return yy.matchAsActorOrParticipant('entity', 'participant_entity', this._input, this); }
|
||||||
|
"database" { return yy.matchAsActorOrParticipant('database', 'participant_database', this._input, this); }
|
||||||
|
"collections" { return yy.matchAsActorOrParticipant('collections', 'participant_collections', this._input, this); }
|
||||||
|
"queue" { return yy.matchAsActorOrParticipant('queue', 'participant_queue', this._input, this); }
|
||||||
"create" return 'create';
|
"create" return 'create';
|
||||||
"destroy" { this.begin('ID'); return 'destroy'; }
|
"destroy" { this.begin('ID'); return 'destroy'; }
|
||||||
<ID>[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
<ID>[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
|
||||||
@@ -231,6 +238,25 @@ participant_statement
|
|||||||
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
|
| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
|
||||||
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
|
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
|
||||||
|
|
||||||
|
| 'participant_boundary' actor 'AS' restOfLine 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
|
| 'participant_boundary' actor 'NEWLINE' {$2.draw='boundary'; $2.type='addParticipant'; $$=$2;}
|
||||||
|
|
||||||
|
| 'participant_control' actor 'AS' restOfLine 'NEWLINE' {$2.draw='control'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
|
| 'participant_control' actor 'NEWLINE' {$2.draw='control'; $2.type='addParticipant'; $$=$2;}
|
||||||
|
|
||||||
|
| 'participant_entity' actor 'AS' restOfLine 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
|
| 'participant_entity' actor 'NEWLINE' {$2.draw='entity'; $2.type='addParticipant'; $$=$2;}
|
||||||
|
|
||||||
|
| 'participant_database' actor 'AS' restOfLine 'NEWLINE' {$2.draw='database'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
|
| 'participant_database' actor 'NEWLINE' {$2.draw='database'; $2.type='addParticipant'; $$=$2;}
|
||||||
|
|
||||||
|
| 'participant_collections' actor 'AS' restOfLine 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
|
| 'participant_collections' actor 'NEWLINE' {$2.draw='collections'; $2.type='addParticipant'; $$=$2;}
|
||||||
|
|
||||||
|
| 'participant_queue' actor 'AS' restOfLine 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
|
||||||
|
| 'participant_queue' actor 'NEWLINE' {$2.draw='queue'; $2.type='addParticipant'; $$=$2;}
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|
||||||
note_statement
|
note_statement
|
||||||
|
@@ -75,6 +75,17 @@ const PLACEMENT = {
|
|||||||
OVER: 2,
|
OVER: 2,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
export const PARTICIPANT_TYPE = {
|
||||||
|
ACTOR: 'actor',
|
||||||
|
BOUNDARY: 'boundary',
|
||||||
|
COLLECTIONS: 'collections',
|
||||||
|
CONTROL: 'control',
|
||||||
|
DATABASE: 'database',
|
||||||
|
ENTITY: 'entity',
|
||||||
|
PARTICIPANT: 'participant',
|
||||||
|
QUEUE: 'queue',
|
||||||
|
} as const;
|
||||||
|
|
||||||
export class SequenceDB implements DiagramDB {
|
export class SequenceDB implements DiagramDB {
|
||||||
private readonly state = new ImperativeState<SequenceState>(() => ({
|
private readonly state = new ImperativeState<SequenceState>(() => ({
|
||||||
prevActor: undefined,
|
prevActor: undefined,
|
||||||
@@ -96,6 +107,7 @@ export class SequenceDB implements DiagramDB {
|
|||||||
this.apply = this.apply.bind(this);
|
this.apply = this.apply.bind(this);
|
||||||
this.parseBoxData = this.parseBoxData.bind(this);
|
this.parseBoxData = this.parseBoxData.bind(this);
|
||||||
this.parseMessage = this.parseMessage.bind(this);
|
this.parseMessage = this.parseMessage.bind(this);
|
||||||
|
this.matchAsActorOrParticipant = this.matchAsActorOrParticipant.bind(this);
|
||||||
|
|
||||||
this.clear();
|
this.clear();
|
||||||
|
|
||||||
@@ -330,6 +342,22 @@ export class SequenceDB implements DiagramDB {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public matchAsActorOrParticipant(
|
||||||
|
tokenName: string,
|
||||||
|
tokenType: string,
|
||||||
|
inputRemainder: string,
|
||||||
|
lexer: any
|
||||||
|
): string {
|
||||||
|
const arrowLike = /^\s*(->>|-->>|->|-->|<<->>|<<-->>|-x|--x|-\))/;
|
||||||
|
const colonLike = /^\s*:/;
|
||||||
|
|
||||||
|
if (arrowLike.test(inputRemainder) || colonLike.test(inputRemainder)) {
|
||||||
|
return 'ACTOR';
|
||||||
|
}
|
||||||
|
lexer.begin('ID'); // used the passed lexer
|
||||||
|
return tokenType;
|
||||||
|
}
|
||||||
|
|
||||||
// We expect the box statement to be color first then description
|
// We expect the box statement to be color first then description
|
||||||
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
|
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
|
||||||
// We extract first segment as color, the rest of the line is considered as text
|
// We extract first segment as color, the rest of the line is considered as text
|
||||||
|
@@ -2038,4 +2038,189 @@ Bob->>Alice:Got it!
|
|||||||
expect(messages[0].from).toBe('Alice');
|
expect(messages[0].from).toBe('Alice');
|
||||||
expect(messages[0].to).toBe('Bob');
|
expect(messages[0].to).toBe('Bob');
|
||||||
});
|
});
|
||||||
|
describe('when newly parsing messages ', () => {
|
||||||
|
it('should parse a message', async () => {
|
||||||
|
const actor1 = 'database';
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
database Alice
|
||||||
|
database Bob
|
||||||
|
Bob->>+Alice: Hi Alice
|
||||||
|
Alice->>+Bob: Hi Bob
|
||||||
|
`);
|
||||||
|
|
||||||
|
const messages = diagram.db.getMessages();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse a message', async () => {
|
||||||
|
const actor1 = 'database';
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
participant lead
|
||||||
|
queue dsa
|
||||||
|
API->>+Database: getUserb
|
||||||
|
Database-->>-API: userb
|
||||||
|
queue --> Database: hello
|
||||||
|
`);
|
||||||
|
|
||||||
|
const messages = diagram.db.getMessages();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('participant type parsing', () => {
|
||||||
|
it('should parse boundary participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
boundary B as Boundary Box
|
||||||
|
B->B: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('B').type).toBe('boundary');
|
||||||
|
expect(actors.get('B').description).toBe('Boundary Box');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse control participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
control C as Controller
|
||||||
|
C->C: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('C').type).toBe('control');
|
||||||
|
expect(actors.get('C').description).toBe('Controller');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse entity participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
entity E as Entity
|
||||||
|
E->E: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('E').type).toBe('entity');
|
||||||
|
expect(actors.get('E').description).toBe('Entity');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse database participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
database D as Database
|
||||||
|
D->D: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('D').type).toBe('database');
|
||||||
|
expect(actors.get('D').description).toBe('Database');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse collections participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
collections L as List
|
||||||
|
L->L: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('L').type).toBe('collections');
|
||||||
|
expect(actors.get('L').description).toBe('List');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse queue participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
queue Q as Jobs
|
||||||
|
Q->Q: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('Q').type).toBe('queue');
|
||||||
|
expect(actors.get('Q').description).toBe('Jobs');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('participant type parsing', () => {
|
||||||
|
it('should parse actor participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
queue A as ActorName
|
||||||
|
A->A: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('A').type).toBe('queue');
|
||||||
|
expect(actors.get('A').description).toBe('ActorName');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse participant participant', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
database P as PartName
|
||||||
|
P->P: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('P').type).toBe('database');
|
||||||
|
expect(actors.get('P').description).toBe('PartName');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse boundary using actor keyword', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
collections B as Boundary
|
||||||
|
B->B: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('B').type).toBe('collections');
|
||||||
|
expect(actors.get('B').description).toBe('Boundary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse control using participant keyword', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
control C as Controller
|
||||||
|
C->C: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('C').type).toBe('control');
|
||||||
|
expect(actors.get('C').description).toBe('Controller');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse entity using actor keyword', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
entity E as Entity
|
||||||
|
E->E: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('E').type).toBe('entity');
|
||||||
|
expect(actors.get('E').description).toBe('Entity');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse database using participant keyword', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
participant D as Database
|
||||||
|
D->D: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('D').type).toBe('participant');
|
||||||
|
expect(actors.get('D').description).toBe('Database');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse collections using actor keyword', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
actor L as List
|
||||||
|
L->L: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('L').type).toBe('actor');
|
||||||
|
expect(actors.get('L').description).toBe('List');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should parse queue using participant keyword', async () => {
|
||||||
|
const diagram = await Diagram.fromText(`
|
||||||
|
sequenceDiagram
|
||||||
|
participant Q as Jobs
|
||||||
|
Q->Q: test
|
||||||
|
`);
|
||||||
|
const actors = diagram.db.getActors();
|
||||||
|
expect(actors.get('Q').type).toBe('participant');
|
||||||
|
expect(actors.get('Q').description).toBe('Jobs');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth.js';
|
|||||||
import utils from '../../utils.js';
|
import utils from '../../utils.js';
|
||||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||||
import type { Diagram } from '../../Diagram.js';
|
import type { Diagram } from '../../Diagram.js';
|
||||||
|
import { PARTICIPANT_TYPE } from './sequenceDb.js';
|
||||||
|
|
||||||
let conf = {};
|
let conf = {};
|
||||||
|
|
||||||
@@ -724,11 +725,19 @@ function adjustCreatedDestroyedData(
|
|||||||
msgModel.startx = msgModel.startx - adjustment;
|
msgModel.startx = msgModel.startx - adjustment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const actorArray = [
|
||||||
|
PARTICIPANT_TYPE.ACTOR,
|
||||||
|
PARTICIPANT_TYPE.CONTROL,
|
||||||
|
PARTICIPANT_TYPE.ENTITY,
|
||||||
|
PARTICIPANT_TYPE.DATABASE,
|
||||||
|
];
|
||||||
|
|
||||||
// if it is a create message
|
// if it is a create message
|
||||||
if (createdActors.get(msg.to) == index) {
|
if (createdActors.get(msg.to) == index) {
|
||||||
const actor = actors.get(msg.to);
|
const actor = actors.get(msg.to);
|
||||||
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
|
const adjustment = actorArray.includes(actor.type)
|
||||||
|
? ACTOR_TYPE_WIDTH / 2 + 3
|
||||||
|
: actor.width / 2 + 3;
|
||||||
receiverAdjustment(actor, adjustment);
|
receiverAdjustment(actor, adjustment);
|
||||||
actor.starty = lineStartY - actor.height / 2;
|
actor.starty = lineStartY - actor.height / 2;
|
||||||
bounds.bumpVerticalPos(actor.height / 2);
|
bounds.bumpVerticalPos(actor.height / 2);
|
||||||
@@ -737,7 +746,7 @@ function adjustCreatedDestroyedData(
|
|||||||
else if (destroyedActors.get(msg.from) == index) {
|
else if (destroyedActors.get(msg.from) == index) {
|
||||||
const actor = actors.get(msg.from);
|
const actor = actors.get(msg.from);
|
||||||
if (conf.mirrorActors) {
|
if (conf.mirrorActors) {
|
||||||
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
|
const adjustment = actorArray.includes(actor.type) ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
|
||||||
senderAdjustment(actor, adjustment);
|
senderAdjustment(actor, adjustment);
|
||||||
}
|
}
|
||||||
actor.stopy = lineStartY - actor.height / 2;
|
actor.stopy = lineStartY - actor.height / 2;
|
||||||
@@ -747,7 +756,9 @@ function adjustCreatedDestroyedData(
|
|||||||
else if (destroyedActors.get(msg.to) == index) {
|
else if (destroyedActors.get(msg.to) == index) {
|
||||||
const actor = actors.get(msg.to);
|
const actor = actors.get(msg.to);
|
||||||
if (conf.mirrorActors) {
|
if (conf.mirrorActors) {
|
||||||
const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
|
const adjustment = actorArray.includes(actor.type)
|
||||||
|
? ACTOR_TYPE_WIDTH / 2 + 3
|
||||||
|
: actor.width / 2 + 3;
|
||||||
receiverAdjustment(actor, adjustment);
|
receiverAdjustment(actor, adjustment);
|
||||||
}
|
}
|
||||||
actor.stopy = lineStartY - actor.height / 2;
|
actor.stopy = lineStartY - actor.height / 2;
|
||||||
@@ -1065,10 +1076,11 @@ export const draw = async function (_text: string, id: string, _version: string,
|
|||||||
for (const box of bounds.models.boxes) {
|
for (const box of bounds.models.boxes) {
|
||||||
box.height = bounds.getVerticalPos() - box.y;
|
box.height = bounds.getVerticalPos() - box.y;
|
||||||
bounds.insert(box.x, box.y, box.x + box.width, box.height);
|
bounds.insert(box.x, box.y, box.x + box.width, box.height);
|
||||||
box.startx = box.x;
|
const boxPadding = conf.boxMargin * 2;
|
||||||
box.starty = box.y;
|
box.startx = box.x - boxPadding;
|
||||||
box.stopx = box.startx + box.width;
|
box.starty = box.y - boxPadding * 0.25;
|
||||||
box.stopy = box.starty + box.height;
|
box.stopx = box.startx + box.width + 2 * boxPadding;
|
||||||
|
box.stopy = box.starty + box.height + boxPadding * 0.75;
|
||||||
box.stroke = 'rgb(0,0,0, 0.5)';
|
box.stroke = 'rgb(0,0,0, 0.5)';
|
||||||
svgDraw.drawBox(diagram, box, conf);
|
svgDraw.drawBox(diagram, box, conf);
|
||||||
}
|
}
|
||||||
@@ -1333,6 +1345,9 @@ async function calculateActorMargins(
|
|||||||
return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0));
|
return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0));
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
const standardBoxPadding = conf.boxMargin * 8;
|
||||||
|
totalWidth += standardBoxPadding;
|
||||||
|
|
||||||
totalWidth -= 2 * conf.boxTextMargin;
|
totalWidth -= 2 * conf.boxTextMargin;
|
||||||
if (box.wrap) {
|
if (box.wrap) {
|
||||||
box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont);
|
box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont);
|
||||||
|
@@ -13,6 +13,11 @@ const getStyles = (options) =>
|
|||||||
stroke: ${options.actorLineColor};
|
stroke: ${options.actorLineColor};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.innerArc {
|
||||||
|
stroke-width: 1.5;
|
||||||
|
stroke-dasharray: none;
|
||||||
|
}
|
||||||
|
|
||||||
.messageLine0 {
|
.messageLine0 {
|
||||||
stroke-width: 1.5;
|
stroke-width: 1.5;
|
||||||
stroke-dasharray: none;
|
stroke-dasharray: none;
|
||||||
@@ -115,6 +120,7 @@ const getStyles = (options) =>
|
|||||||
fill: ${options.actorBkg};
|
fill: ${options.actorBkg};
|
||||||
stroke-width: 2px;
|
stroke-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default getStyles;
|
export default getStyles;
|
||||||
|
@@ -411,6 +411,608 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
|||||||
return height;
|
return height;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws an actor in the diagram with the attached line
|
||||||
|
*
|
||||||
|
* @param {any} elem - The diagram we'll draw to.
|
||||||
|
* @param {any} actor - The actor to draw.
|
||||||
|
* @param {any} conf - DrawText implementation discriminator object
|
||||||
|
* @param {boolean} isFooter - If the actor is the footer one
|
||||||
|
*/
|
||||||
|
const drawActorTypeCollections = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
|
const center = actor.x + actor.width / 2;
|
||||||
|
const centerY = actorY + actor.height;
|
||||||
|
|
||||||
|
const boxplusLineGroup = elem.append('g').lower();
|
||||||
|
var g = boxplusLineGroup;
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actorCnt++;
|
||||||
|
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
|
||||||
|
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
|
||||||
|
}
|
||||||
|
g.append('line')
|
||||||
|
.attr('id', 'actor' + actorCnt)
|
||||||
|
.attr('x1', center)
|
||||||
|
.attr('y1', centerY)
|
||||||
|
.attr('x2', center)
|
||||||
|
.attr('y2', 2000)
|
||||||
|
.attr('class', 'actor-line 200')
|
||||||
|
.attr('stroke-width', '0.5px')
|
||||||
|
.attr('stroke', '#999')
|
||||||
|
.attr('name', actor.name);
|
||||||
|
|
||||||
|
g = boxplusLineGroup.append('g');
|
||||||
|
actor.actorCnt = actorCnt;
|
||||||
|
|
||||||
|
if (actor.links != null) {
|
||||||
|
g.attr('id', 'root-' + actorCnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = svgDrawCommon.getNoteRect();
|
||||||
|
var cssclass = 'actor';
|
||||||
|
if (actor.properties?.class) {
|
||||||
|
cssclass = actor.properties.class;
|
||||||
|
} else {
|
||||||
|
rect.fill = '#eaeaea';
|
||||||
|
}
|
||||||
|
if (isFooter) {
|
||||||
|
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
|
||||||
|
} else {
|
||||||
|
cssclass += ` ${TOP_ACTOR_CLASS}`;
|
||||||
|
}
|
||||||
|
rect.x = actor.x;
|
||||||
|
rect.y = actorY;
|
||||||
|
rect.width = actor.width;
|
||||||
|
rect.height = actor.height;
|
||||||
|
rect.class = cssclass;
|
||||||
|
// rect.rx = 3;
|
||||||
|
// rect.ry = 3;
|
||||||
|
rect.name = actor.name;
|
||||||
|
|
||||||
|
// 🔹 DRAW STACKED RECTANGLES
|
||||||
|
const offset = 6;
|
||||||
|
const shadowRect = {
|
||||||
|
...rect,
|
||||||
|
x: rect.x + offset,
|
||||||
|
y: rect.y + (isFooter ? +offset : -offset),
|
||||||
|
class: 'actor',
|
||||||
|
};
|
||||||
|
drawRect(g, shadowRect);
|
||||||
|
const rectElem = drawRect(g, rect); // draw main rectangle on top
|
||||||
|
actor.rectData = rect;
|
||||||
|
|
||||||
|
if (actor.properties?.icon) {
|
||||||
|
const iconSrc = actor.properties.icon.trim();
|
||||||
|
if (iconSrc.charAt(0) === '@') {
|
||||||
|
svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
|
||||||
|
} else {
|
||||||
|
svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
|
||||||
|
actor.description,
|
||||||
|
g,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: `actor ${ACTOR_BOX_CLASS}` },
|
||||||
|
conf
|
||||||
|
);
|
||||||
|
|
||||||
|
let height = actor.height;
|
||||||
|
if (rectElem.node) {
|
||||||
|
const bounds = rectElem.node().getBBox();
|
||||||
|
actor.height = bounds.height;
|
||||||
|
height = bounds.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawActorTypeQueue = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
|
const center = actor.x + actor.width / 2;
|
||||||
|
const centerY = actorY + actor.height;
|
||||||
|
|
||||||
|
const boxplusLineGroup = elem.append('g').lower();
|
||||||
|
let g = boxplusLineGroup;
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actorCnt++;
|
||||||
|
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
|
||||||
|
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
|
||||||
|
}
|
||||||
|
g.append('line')
|
||||||
|
.attr('id', 'actor' + actorCnt)
|
||||||
|
.attr('x1', center)
|
||||||
|
.attr('y1', centerY)
|
||||||
|
.attr('x2', center)
|
||||||
|
.attr('y2', 2000)
|
||||||
|
.attr('class', 'actor-line 200')
|
||||||
|
.attr('stroke-width', '0.5px')
|
||||||
|
.attr('stroke', '#999')
|
||||||
|
.attr('name', actor.name);
|
||||||
|
|
||||||
|
g = boxplusLineGroup.append('g');
|
||||||
|
actor.actorCnt = actorCnt;
|
||||||
|
|
||||||
|
if (actor.links != null) {
|
||||||
|
g.attr('id', 'root-' + actorCnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = svgDrawCommon.getNoteRect();
|
||||||
|
let cssclass = 'actor';
|
||||||
|
if (actor.properties?.class) {
|
||||||
|
cssclass = actor.properties.class;
|
||||||
|
} else {
|
||||||
|
rect.fill = '#eaeaea';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFooter) {
|
||||||
|
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
|
||||||
|
} else {
|
||||||
|
cssclass += ` ${TOP_ACTOR_CLASS}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.x = actor.x;
|
||||||
|
rect.y = actorY;
|
||||||
|
rect.width = actor.width;
|
||||||
|
rect.height = actor.height;
|
||||||
|
rect.class = cssclass;
|
||||||
|
rect.name = actor.name;
|
||||||
|
|
||||||
|
// Cylinder dimensions
|
||||||
|
const ry = rect.height / 2;
|
||||||
|
const rx = ry / (2.5 + rect.height / 50);
|
||||||
|
|
||||||
|
// Cylinder base group
|
||||||
|
const cylinderGroup = g.append('g');
|
||||||
|
const cylinderArc = g.append('g');
|
||||||
|
|
||||||
|
// Main cylinder body
|
||||||
|
cylinderGroup
|
||||||
|
.append('path')
|
||||||
|
.attr(
|
||||||
|
'd',
|
||||||
|
`M ${rect.x},${rect.y + ry}
|
||||||
|
a ${rx},${ry} 0 0 0 0,${rect.height}
|
||||||
|
h ${rect.width - 2 * rx}
|
||||||
|
a ${rx},${ry} 0 0 0 0,-${rect.height}
|
||||||
|
Z
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.attr('class', cssclass);
|
||||||
|
cylinderArc
|
||||||
|
.append('path')
|
||||||
|
.attr(
|
||||||
|
'd',
|
||||||
|
`M ${rect.x},${rect.y + ry}
|
||||||
|
a ${rx},${ry} 0 0 0 0,${rect.height}`
|
||||||
|
)
|
||||||
|
.attr('stroke', '#666')
|
||||||
|
.attr('stroke-width', '1px')
|
||||||
|
.attr('class', cssclass);
|
||||||
|
|
||||||
|
cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`);
|
||||||
|
cylinderArc.attr('transform', `translate(${rect.width - rx}, ${-rect.height / 2})`);
|
||||||
|
|
||||||
|
actor.rectData = rect;
|
||||||
|
|
||||||
|
if (actor.properties?.icon) {
|
||||||
|
const iconSrc = actor.properties.icon.trim();
|
||||||
|
const iconX = rect.x + rect.width - 20;
|
||||||
|
const iconY = rect.y + 10;
|
||||||
|
if (iconSrc.charAt(0) === '@') {
|
||||||
|
svgDrawCommon.drawEmbeddedImage(g, iconX, iconY, iconSrc.substr(1));
|
||||||
|
} else {
|
||||||
|
svgDrawCommon.drawImage(g, iconX, iconY, iconSrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
|
||||||
|
actor.description,
|
||||||
|
g,
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: `actor ${ACTOR_BOX_CLASS}` },
|
||||||
|
conf
|
||||||
|
);
|
||||||
|
|
||||||
|
let height = actor.height;
|
||||||
|
const lastPath = cylinderGroup.select('path:last-child');
|
||||||
|
if (lastPath.node()) {
|
||||||
|
const bounds = lastPath.node().getBBox();
|
||||||
|
actor.height = bounds.height;
|
||||||
|
height = bounds.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawActorTypeControl = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
|
const center = actor.x + actor.width / 2;
|
||||||
|
const centerY = actorY + 75;
|
||||||
|
|
||||||
|
const line = elem.append('g').lower();
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actorCnt++;
|
||||||
|
line
|
||||||
|
.append('line')
|
||||||
|
.attr('id', 'actor' + actorCnt)
|
||||||
|
.attr('x1', center)
|
||||||
|
.attr('y1', centerY)
|
||||||
|
.attr('x2', center)
|
||||||
|
.attr('y2', 2000)
|
||||||
|
.attr('class', 'actor-line 200')
|
||||||
|
.attr('stroke-width', '0.5px')
|
||||||
|
.attr('stroke', '#999')
|
||||||
|
.attr('name', actor.name);
|
||||||
|
|
||||||
|
actor.actorCnt = actorCnt;
|
||||||
|
}
|
||||||
|
const actElem = elem.append('g');
|
||||||
|
let cssClass = ACTOR_MAN_FIGURE_CLASS;
|
||||||
|
if (isFooter) {
|
||||||
|
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
|
||||||
|
} else {
|
||||||
|
cssClass += ` ${TOP_ACTOR_CLASS}`;
|
||||||
|
}
|
||||||
|
actElem.attr('class', cssClass);
|
||||||
|
actElem.attr('name', actor.name);
|
||||||
|
|
||||||
|
const rect = svgDrawCommon.getNoteRect();
|
||||||
|
rect.x = actor.x;
|
||||||
|
rect.y = actorY;
|
||||||
|
rect.fill = '#eaeaea';
|
||||||
|
rect.width = actor.width;
|
||||||
|
rect.height = actor.height;
|
||||||
|
rect.class = 'actor';
|
||||||
|
|
||||||
|
const cx = actor.x + actor.width / 2;
|
||||||
|
const cy = actorY + 30;
|
||||||
|
const r = 18;
|
||||||
|
|
||||||
|
actElem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', 'filled-head-control')
|
||||||
|
.attr('refX', 15.5)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 20)
|
||||||
|
.attr('markerHeight', 28)
|
||||||
|
.attr('orient', '180')
|
||||||
|
.append('path')
|
||||||
|
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||||
|
|
||||||
|
// Draw the base circle
|
||||||
|
actElem
|
||||||
|
.append('circle')
|
||||||
|
.attr('cx', cx)
|
||||||
|
.attr('cy', cy)
|
||||||
|
.attr('r', r)
|
||||||
|
.attr('fill', '#eaeaf7')
|
||||||
|
.attr('stroke', '#666')
|
||||||
|
.attr('stroke-width', 1.2);
|
||||||
|
|
||||||
|
// Draw looping arrow as arc path
|
||||||
|
actElem
|
||||||
|
.append('path')
|
||||||
|
.attr('d', `M ${cx},${cy - r}`)
|
||||||
|
.attr('stroke-width', 1.5)
|
||||||
|
.attr('marker-end', 'url(#filled-head-control)');
|
||||||
|
|
||||||
|
const bounds = actElem.node().getBBox();
|
||||||
|
actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0);
|
||||||
|
|
||||||
|
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
|
||||||
|
actor.description,
|
||||||
|
actElem,
|
||||||
|
rect.x,
|
||||||
|
rect.y + 30,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
|
||||||
|
conf
|
||||||
|
);
|
||||||
|
|
||||||
|
return actor.height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawActorTypeEntity = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
|
const center = actor.x + actor.width / 2;
|
||||||
|
const centerY = actorY + 75;
|
||||||
|
|
||||||
|
const line = elem.append('g').lower();
|
||||||
|
|
||||||
|
const actElem = elem.append('g');
|
||||||
|
let cssClass = ACTOR_MAN_FIGURE_CLASS;
|
||||||
|
if (isFooter) {
|
||||||
|
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
|
||||||
|
} else {
|
||||||
|
cssClass += ` ${TOP_ACTOR_CLASS}`;
|
||||||
|
}
|
||||||
|
actElem.attr('class', cssClass);
|
||||||
|
actElem.attr('name', actor.name);
|
||||||
|
|
||||||
|
const rect = svgDrawCommon.getNoteRect();
|
||||||
|
rect.x = actor.x;
|
||||||
|
rect.y = actorY;
|
||||||
|
rect.fill = '#eaeaea';
|
||||||
|
rect.width = actor.width;
|
||||||
|
rect.height = actor.height;
|
||||||
|
rect.class = 'actor';
|
||||||
|
|
||||||
|
const cx = actor.x + actor.width / 2;
|
||||||
|
const cy = actorY + 25;
|
||||||
|
const r = 18;
|
||||||
|
|
||||||
|
actElem
|
||||||
|
.append('circle')
|
||||||
|
.attr('cx', cx)
|
||||||
|
.attr('cy', cy)
|
||||||
|
.attr('r', r)
|
||||||
|
.attr('width', actor.width)
|
||||||
|
.attr('height', actor.height);
|
||||||
|
|
||||||
|
actElem
|
||||||
|
.append('line')
|
||||||
|
.attr('x1', cx - r)
|
||||||
|
.attr('x2', cx + r)
|
||||||
|
.attr('y1', cy + r)
|
||||||
|
.attr('y2', cy + r)
|
||||||
|
.attr('stroke', '#333')
|
||||||
|
.attr('stroke-width', 2);
|
||||||
|
|
||||||
|
const bounds = actElem.node().getBBox();
|
||||||
|
actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actorCnt++;
|
||||||
|
line
|
||||||
|
.append('line')
|
||||||
|
.attr('id', 'actor' + actorCnt)
|
||||||
|
.attr('x1', center)
|
||||||
|
.attr('y1', centerY)
|
||||||
|
.attr('x2', center)
|
||||||
|
.attr('y2', 2000)
|
||||||
|
.attr('class', 'actor-line 200')
|
||||||
|
.attr('stroke-width', '0.5px')
|
||||||
|
.attr('stroke', '#999')
|
||||||
|
.attr('name', actor.name);
|
||||||
|
|
||||||
|
actor.actorCnt = actorCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
|
||||||
|
actor.description,
|
||||||
|
actElem,
|
||||||
|
rect.x,
|
||||||
|
rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY) / 2 + r + 2),
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
|
||||||
|
conf
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actElem.attr('transform', `translate(${0}, ${r / 2})`);
|
||||||
|
} else {
|
||||||
|
actElem.attr('transform', `translate(${0}, ${r / 2})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actor.height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawActorTypeDatabase = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
|
const center = actor.x + actor.width / 2;
|
||||||
|
const centerY = actorY + actor.height + 2 * conf.boxTextMargin;
|
||||||
|
|
||||||
|
const boxplusLineGroup = elem.append('g').lower();
|
||||||
|
let g = boxplusLineGroup;
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actorCnt++;
|
||||||
|
if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
|
||||||
|
g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
|
||||||
|
}
|
||||||
|
g.append('line')
|
||||||
|
.attr('id', 'actor' + actorCnt)
|
||||||
|
.attr('x1', center)
|
||||||
|
.attr('y1', centerY)
|
||||||
|
.attr('x2', center)
|
||||||
|
.attr('y2', 2000)
|
||||||
|
.attr('class', 'actor-line 200')
|
||||||
|
.attr('stroke-width', '0.5px')
|
||||||
|
.attr('stroke', '#999')
|
||||||
|
.attr('name', actor.name);
|
||||||
|
|
||||||
|
g = boxplusLineGroup.append('g');
|
||||||
|
actor.actorCnt = actorCnt;
|
||||||
|
|
||||||
|
if (actor.links != null) {
|
||||||
|
g.attr('id', 'root-' + actorCnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = svgDrawCommon.getNoteRect();
|
||||||
|
|
||||||
|
let cssclass = 'actor';
|
||||||
|
if (actor.properties?.class) {
|
||||||
|
cssclass = actor.properties.class;
|
||||||
|
} else {
|
||||||
|
rect.fill = '#eaeaea';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFooter) {
|
||||||
|
cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
|
||||||
|
} else {
|
||||||
|
cssclass += ` ${TOP_ACTOR_CLASS}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.x = actor.x;
|
||||||
|
rect.y = actorY;
|
||||||
|
rect.width = actor.width;
|
||||||
|
rect.height = actor.height;
|
||||||
|
rect.class = cssclass;
|
||||||
|
rect.name = actor.name;
|
||||||
|
|
||||||
|
// Cylinder dimensions
|
||||||
|
rect.x = actor.x;
|
||||||
|
rect.y = actorY;
|
||||||
|
const w = rect.width / 4;
|
||||||
|
const h = rect.height * 0.8;
|
||||||
|
const rx = w / 2;
|
||||||
|
const ry = rx / (2.5 + w / 50);
|
||||||
|
|
||||||
|
// Cylinder base group
|
||||||
|
const cylinderGroup = g.append('g');
|
||||||
|
|
||||||
|
const d = `
|
||||||
|
M ${rect.x},${rect.y + ry}
|
||||||
|
a ${rx},${ry} 0 0 0 ${w},0
|
||||||
|
a ${rx},${ry} 0 0 0 -${w},0
|
||||||
|
l 0,${h - 2 * ry}
|
||||||
|
a ${rx},${ry} 0 0 0 ${w},0
|
||||||
|
l 0,-${h - 2 * ry}
|
||||||
|
`;
|
||||||
|
// Draw the main cylinder body
|
||||||
|
cylinderGroup
|
||||||
|
.append('path')
|
||||||
|
.attr('d', d)
|
||||||
|
.attr('fill', '#eaeaea')
|
||||||
|
.attr('stroke', '#000')
|
||||||
|
.attr('stroke-width', 1)
|
||||||
|
.attr('class', cssclass);
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`);
|
||||||
|
} else {
|
||||||
|
cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`);
|
||||||
|
}
|
||||||
|
actor.rectData = rect;
|
||||||
|
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
|
||||||
|
actor.description,
|
||||||
|
g,
|
||||||
|
rect.x,
|
||||||
|
rect.y + (!isFooter ? rect.height / 2 : h / 2 + ry),
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: `actor ${ACTOR_BOX_CLASS}` },
|
||||||
|
conf
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastPath = cylinderGroup.select('path:last-child');
|
||||||
|
if (lastPath.node()) {
|
||||||
|
const bounds = lastPath.node().getBBox();
|
||||||
|
actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actor.height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawActorTypeBoundary = function (elem, actor, conf, isFooter) {
|
||||||
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
|
const center = actor.x + actor.width / 2;
|
||||||
|
const centerY = actorY + 80;
|
||||||
|
const radius = 30;
|
||||||
|
const line = elem.append('g').lower();
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actorCnt++;
|
||||||
|
line
|
||||||
|
.append('line')
|
||||||
|
.attr('id', 'actor' + actorCnt)
|
||||||
|
.attr('x1', center)
|
||||||
|
.attr('y1', centerY)
|
||||||
|
.attr('x2', center)
|
||||||
|
.attr('y2', 2000)
|
||||||
|
.attr('class', 'actor-line 200')
|
||||||
|
.attr('stroke-width', '0.5px')
|
||||||
|
.attr('stroke', '#999')
|
||||||
|
.attr('name', actor.name);
|
||||||
|
|
||||||
|
actor.actorCnt = actorCnt;
|
||||||
|
}
|
||||||
|
const actElem = elem.append('g');
|
||||||
|
let cssClass = ACTOR_MAN_FIGURE_CLASS;
|
||||||
|
if (isFooter) {
|
||||||
|
cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
|
||||||
|
} else {
|
||||||
|
cssClass += ` ${TOP_ACTOR_CLASS}`;
|
||||||
|
}
|
||||||
|
actElem.attr('class', cssClass);
|
||||||
|
actElem.attr('name', actor.name);
|
||||||
|
|
||||||
|
const rect = svgDrawCommon.getNoteRect();
|
||||||
|
rect.x = actor.x;
|
||||||
|
rect.y = actorY;
|
||||||
|
rect.fill = '#eaeaea';
|
||||||
|
rect.width = actor.width;
|
||||||
|
rect.height = actor.height;
|
||||||
|
rect.class = 'actor';
|
||||||
|
rect.rx = 3;
|
||||||
|
rect.ry = 3;
|
||||||
|
|
||||||
|
actElem
|
||||||
|
.append('line')
|
||||||
|
.attr('id', 'actor-man-torso' + actorCnt)
|
||||||
|
.attr('x1', actor.x + actor.width / 2 - radius * 2.5)
|
||||||
|
.attr('y1', actorY + 10)
|
||||||
|
.attr('x2', actor.x + actor.width / 2 - 15)
|
||||||
|
.attr('y2', actorY + 10);
|
||||||
|
|
||||||
|
actElem
|
||||||
|
.append('line')
|
||||||
|
.attr('id', 'actor-man-arms' + actorCnt)
|
||||||
|
.attr('x1', actor.x + actor.width / 2 - radius * 2.5)
|
||||||
|
.attr('y1', actorY + 0) // starting Y
|
||||||
|
.attr('x2', actor.x + actor.width / 2 - radius * 2.5)
|
||||||
|
.attr('y2', actorY + 20); // ending Y (26px long, adjust as needed)
|
||||||
|
|
||||||
|
actElem
|
||||||
|
.append('circle')
|
||||||
|
.attr('cx', actor.x + actor.width / 2)
|
||||||
|
.attr('cy', actorY + 10)
|
||||||
|
.attr('r', radius)
|
||||||
|
.attr('width', actor.width);
|
||||||
|
|
||||||
|
const bounds = actElem.node().getBBox();
|
||||||
|
actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
|
||||||
|
|
||||||
|
_drawTextCandidateFunc(conf, hasKatex(actor.description))(
|
||||||
|
actor.description,
|
||||||
|
actElem,
|
||||||
|
rect.x,
|
||||||
|
rect.y + (!isFooter ? radius / 2 : radius / 2),
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
{ class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
|
||||||
|
conf
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isFooter) {
|
||||||
|
actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
|
||||||
|
} else {
|
||||||
|
actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// actElem.attr('transform', `translate(${rect.width / 2}, ${actorY + rect.height / 2})`);
|
||||||
|
|
||||||
|
return actor.height;
|
||||||
|
};
|
||||||
|
|
||||||
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||||
const actorY = isFooter ? actor.stopy : actor.starty;
|
const actorY = isFooter ? actor.stopy : actor.starty;
|
||||||
const center = actor.x + actor.width / 2;
|
const center = actor.x + actor.width / 2;
|
||||||
@@ -512,6 +1114,18 @@ export const drawActor = async function (elem, actor, conf, isFooter) {
|
|||||||
return await drawActorTypeActor(elem, actor, conf, isFooter);
|
return await drawActorTypeActor(elem, actor, conf, isFooter);
|
||||||
case 'participant':
|
case 'participant':
|
||||||
return await drawActorTypeParticipant(elem, actor, conf, isFooter);
|
return await drawActorTypeParticipant(elem, actor, conf, isFooter);
|
||||||
|
case 'boundary':
|
||||||
|
return await drawActorTypeBoundary(elem, actor, conf, isFooter);
|
||||||
|
case 'control':
|
||||||
|
return await drawActorTypeControl(elem, actor, conf, isFooter);
|
||||||
|
case 'entity':
|
||||||
|
return await drawActorTypeEntity(elem, actor, conf, isFooter);
|
||||||
|
case 'database':
|
||||||
|
return await drawActorTypeDatabase(elem, actor, conf, isFooter);
|
||||||
|
case 'collections':
|
||||||
|
return await drawActorTypeCollections(elem, actor, conf, isFooter);
|
||||||
|
case 'queue':
|
||||||
|
return await drawActorTypeQueue(elem, actor, conf, isFooter);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -46,6 +46,126 @@ sequenceDiagram
|
|||||||
Bob->>Alice: Hi Alice
|
Bob->>Alice: Hi Alice
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Boundary
|
||||||
|
|
||||||
|
If you want to use the boundary symbol for a participant, use the `boundary` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
boundary theBoundary
|
||||||
|
participant Bob
|
||||||
|
theBoundary->>Bob: Request from boundary
|
||||||
|
Bob->>theBoundary: Response to boundary
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
boundary theBoundary
|
||||||
|
participant Bob
|
||||||
|
theBoundary->>Bob: Request from boundary
|
||||||
|
Bob->>theBoundary: Response to boundary
|
||||||
|
```
|
||||||
|
|
||||||
|
### Control
|
||||||
|
|
||||||
|
If you want to use the control symbol for a participant, use the `control` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
control theControl
|
||||||
|
participant Alice
|
||||||
|
theControl->>Alice: Control request
|
||||||
|
Alice->>theControl: Control response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
control theControl
|
||||||
|
participant Alice
|
||||||
|
theControl->>Alice: Control request
|
||||||
|
Alice->>theControl: Control response
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entity
|
||||||
|
|
||||||
|
If you want to use the entity symbol for a participant, use the `entity` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
entity theEntity
|
||||||
|
participant Bob
|
||||||
|
theEntity->>Bob: Entity request
|
||||||
|
Bob->>theEntity: Entity response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
entity theEntity
|
||||||
|
participant Bob
|
||||||
|
theEntity->>Bob: Entity request
|
||||||
|
Bob->>theEntity: Entity response
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
If you want to use the database symbol for a participant, use the `database` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
database theDb
|
||||||
|
participant Alice
|
||||||
|
theDb->>Alice: DB query
|
||||||
|
Alice->>theDb: DB result
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
database theDb
|
||||||
|
participant Alice
|
||||||
|
theDb->>Alice: DB query
|
||||||
|
Alice->>theDb: DB result
|
||||||
|
```
|
||||||
|
|
||||||
|
### Collections
|
||||||
|
|
||||||
|
If you want to use the collections symbol for a participant, use the `collections` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
collections theCollection
|
||||||
|
participant Bob
|
||||||
|
theCollection->>Bob: Collections request
|
||||||
|
Bob->>theCollection: Collections response
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
collections theCollection
|
||||||
|
participant Bob
|
||||||
|
theCollection->>Bob: Collections request
|
||||||
|
Bob->>theCollection: Collections response
|
||||||
|
```
|
||||||
|
|
||||||
|
### Queue
|
||||||
|
|
||||||
|
If you want to use the queue symbol for a participant, use the `queue` statement as shown below.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
sequenceDiagram
|
||||||
|
queue theQueue
|
||||||
|
participant Alice
|
||||||
|
theQueue->>Alice: Queue message
|
||||||
|
Alice->>theQueue: Queue
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
queue theQueue
|
||||||
|
participant Alice
|
||||||
|
theQueue->>Alice: Queue message
|
||||||
|
Alice->>theQueue: Queue
|
||||||
|
```
|
||||||
|
|
||||||
### Aliases
|
### Aliases
|
||||||
|
|
||||||
The actor can have a convenient identifier and a descriptive label.
|
The actor can have a convenient identifier and a descriptive label.
|
||||||
|
Reference in New Issue
Block a user