Skip to content

Commit 9403ff9

Browse files
authored
Merge pull request #767 from ZenUml/fix/png-export-issue
feat: Refactor PNG export and add sequence diagram
2 parents 2a24108 + fdb3a7f commit 9403ff9

File tree

4 files changed

+244
-47
lines changed

4 files changed

+244
-47
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# ZenUML Web Sequence - PNG Export and iframe Architecture Analysis
2+
3+
## 1. Overview
4+
5+
ZenUML Web Sequence uses an iframe architecture to render sequence diagrams and implements PNG export functionality through the `toBlob()` method provided by the @zenuml/core library. This document provides a detailed analysis of these two core mechanisms.
6+
7+
## 2. PNG Export Mechanism
8+
9+
### 2.1 Export Flow
10+
11+
The complete PNG export flow is as follows:
12+
13+
1. **User Triggers Export**
14+
* Click download button triggers `exportPngClickHandler()` (ContentWrap.jsx:540)
15+
* Or click copy button triggers `copyImageClickHandler()` (ContentWrap.jsx:559)
16+
17+
2. **Permission Check**
18+
```javascript
19+
if (!window.user) {
20+
this.props.onLogin();
21+
return;
22+
}
23+
```
24+
25+
3. **Get PNG Blob**
26+
```javascript
27+
async getPngBlob() {
28+
// Use the getPng method exposed by the iframe
29+
const pngDataUrl = await this.frame.contentWindow.getPng();
30+
if (!pngDataUrl) {
31+
throw new Error('Failed to get PNG from diagram');
32+
}
33+
34+
// Convert data URL to Blob
35+
const response = await fetch(pngDataUrl);
36+
return await response.blob();
37+
}
38+
```
39+
40+
4. **Process Export**
41+
* Download: Uses FileSaver.js's `saveAs(png, 'zenuml.png')`
42+
* Copy: Uses Clipboard API to write to clipboard
43+
44+
### 2.2 Key Technical Points
45+
46+
1. **Official API Method**: Uses the `getPng()` method provided by @zenuml/core library
47+
2. **Data URL to Blob Conversion**: The `getPng()` method returns a data URL which is converted to Blob using the Fetch API
48+
3. **Cross-iframe Access**: Access iframe's exposed methods through `this.frame.contentWindow`
49+
4. **No Vue Internals**: No longer relies on accessing Vue instance internals (`__vue__`), making it more stable and future-proof
50+
51+
### 2.3 Chrome Extension Special Handling
52+
53+
Chrome extension uses a different screenshot mechanism (takeScreenshot.js):
54+
- Uses Chrome tabs API for screenshots
55+
- Saves to filesystem through WriteFile.js
56+
57+
## 3. iframe Architecture Analysis
58+
59+
### 3.1 iframe Creation
60+
61+
The iframe is created in ContentWrap.jsx:
62+
```jsx
63+
<iframe
64+
ref={(el) => (this.frame = el)}
65+
frameBorder="0"
66+
id="demo-frame"
67+
allowFullScreen
68+
/>
69+
```
70+
71+
### 3.2 Content Loading Process
72+
73+
1. **HTML Template Generation** (computes.js:13-19)
74+
```javascript
75+
var code = `<main id="demo">
76+
<div id="diagram">
77+
<div id="mounting-point">
78+
<seq-diagram></seq-diagram>
79+
</div>
80+
</div>
81+
</main>`;
82+
```
83+
84+
2. **Complete HTML Assembly** (utils.js - getCompleteHtml)
85+
* Creates complete HTML document structure
86+
* Injects CSS styles
87+
* **Loads ZenUML Core library**: `<script src="' + getUrl(zenumlUrl) + '"></script>`
88+
* zenumlUrl is imported via Vite's URL import feature: `import zenumlUrl from '@zenuml/core/dist/zenuml?url'`
89+
90+
3. **JavaScript Initialization** (computes.js:170-192)
91+
```javascript
92+
window.addEventListener("load", function(event) {
93+
window.app = new window.zenuml.default('#mounting-point')
94+
// Expose getPng method to parent window
95+
window.getPng = async function() {
96+
if (window.app) {
97+
return await window.app.getPng();
98+
}
99+
return null;
100+
};
101+
});
102+
```
103+
104+
4. **Content Setting**
105+
* Prefers `srcdoc` attribute (modern browsers)
106+
* Falls back to `document.write` (older browsers)
107+
108+
### 3.3 Communication Mechanism
109+
110+
#### Parent Window to iframe
111+
112+
```javascript
113+
// ContentWrap.jsx
114+
targetWindow.postMessage({ code: this.cmCodes.js }, '*');
115+
```
116+
117+
#### iframe Listener
118+
119+
```javascript
120+
// computes.js
121+
window.addEventListener('message', (e) => {
122+
const code = e.data && e.data.code;
123+
const cursor = e.data && e.data.cursor;
124+
125+
if (code && app) {
126+
app.render(code, {
127+
enableMultiTheme: false,
128+
theme: "theme-default",
129+
onContentChange: (code) => {
130+
window.parent.postMessage({ code })
131+
},
132+
stickyOffset: Number(new URLSearchParams(window.location.search).get('stickyOffset') || 0)
133+
});
134+
}
135+
});
136+
```
137+
138+
### 3.4 Key Features
139+
140+
1. **Real-time Synchronization**: Changes in the editor are reflected in the preview in real-time via postMessage
141+
2. **Detached Window Support**: Supports opening preview in a separate window (detachPreview)
142+
3. **Cursor Synchronization**: Cursor position synchronization between editor and preview
143+
4. **Security Considerations**:
144+
* Uses srcdoc instead of data URL (more secure)
145+
* Communicates via postMessage API
146+
* Dynamically generates content to prevent XSS attacks
147+
148+
## 4. Architecture Advantages
149+
150+
1. **Isolation**: Complete isolation between editor UI and rendering engine
151+
2. **Flexibility**: Supports advanced features like detached windows and real-time preview
152+
3. **Maintainability**: Clear modular design
153+
4. **Security**: iframe provides a natural sandbox environment
154+
155+
## 5. Potential Improvements
156+
157+
1. **PNG Export Dependency**: Currently relies on accessing Vue instance's internal property (__vue__), which may fail when Vue version updates
158+
2. **Cross-origin Restrictions**: iframe communication uses `*` as target origin, could consider restricting to specific domains
159+
3. **Error Handling**: Could enhance error handling and user feedback when PNG export fails
160+
161+
## 6. Summary
162+
163+
ZenUML Web Sequence achieves effective isolation between the editor and rendering engine through its iframe architecture, while leveraging the toBlob() method provided by @zenuml/core for efficient PNG export functionality. This design ensures system modularity and security while providing a good user experience.
164+
165+
## 7. PNG Export Sequence Diagram
166+
167+
```mermaid
168+
sequenceDiagram
169+
actor User
170+
participant ContentWrap as Parent Window (ContentWrap.jsx)
171+
participant IFrame as IFrame
172+
participant ZenUMLCore as @zenuml/core
173+
174+
User->>ContentWrap: Clicks "Export PNG"
175+
ContentWrap->>ContentWrap: exportPngClickHandler()
176+
ContentWrap->>ContentWrap: getPngBlob()
177+
ContentWrap->>IFrame: calls contentWindow.getPng()
178+
IFrame->>ZenumlCore: calls app.getPng()
179+
ZenumlCore-->>IFrame: returns pngDataUrl
180+
IFrame-->>ContentWrap: returns pngDataUrl
181+
ContentWrap->>ContentWrap: fetch(pngDataUrl)
182+
ContentWrap->>ContentWrap: saveAs(blob, 'zenuml.png')
183+
ContentWrap-->>User: PNG file download
184+
```

0 commit comments

Comments
 (0)