@zenuml/core 3.32.7 → 3.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,274 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ZenUML Compression Test</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ max-width: 1200px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ }
14
+ textarea {
15
+ width: 100%;
16
+ height: 200px;
17
+ margin: 10px 0;
18
+ font-family: monospace;
19
+ }
20
+ button {
21
+ background: #007bff;
22
+ color: white;
23
+ border: none;
24
+ padding: 10px 20px;
25
+ margin: 5px;
26
+ cursor: pointer;
27
+ border-radius: 4px;
28
+ }
29
+ button:hover {
30
+ background: #0056b3;
31
+ }
32
+ .output {
33
+ background: #f4f4f4;
34
+ padding: 10px;
35
+ margin: 10px 0;
36
+ border-radius: 4px;
37
+ word-break: break-all;
38
+ }
39
+ .error {
40
+ color: red;
41
+ background: #fee;
42
+ padding: 10px;
43
+ margin: 10px 0;
44
+ border-radius: 4px;
45
+ }
46
+ .success {
47
+ color: green;
48
+ background: #efe;
49
+ padding: 10px;
50
+ margin: 10px 0;
51
+ border-radius: 4px;
52
+ }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <h1>ZenUML Compression Test</h1>
57
+
58
+ <h2>Test URL Parameter Encoding/Decoding</h2>
59
+ <textarea id="zenUmlCode" placeholder="Enter ZenUML code here...">title Authentication Flow
60
+ RET ret = A.methodA() {
61
+ B.method() {
62
+ if (X) {
63
+ C.methodC() {
64
+ a = A.methodA() {
65
+ D.method()
66
+ }
67
+ }
68
+ }
69
+ while (Y) {
70
+ C.methodC() {
71
+ A.methodA()
72
+ }
73
+ }
74
+ }
75
+ }
76
+ </textarea>
77
+
78
+ <div>
79
+ <button onclick="encodeAndCompress()">Encode (Gzip + Base64)</button>
80
+ <button onclick="testDecode()">Test Decode</button>
81
+ <button onclick="generateUrl()">Generate URL</button>
82
+ <button onclick="clearAll()">Clear</button>
83
+ </div>
84
+
85
+ <h3>Encoded Output:</h3>
86
+ <div id="encodedOutput" class="output"></div>
87
+
88
+ <h3>URL:</h3>
89
+ <div id="urlOutput" class="output"></div>
90
+
91
+ <h3>Test Results:</h3>
92
+ <div id="testResults"></div>
93
+
94
+ <script type="module">
95
+ import pako from 'pako';
96
+
97
+ // Helper function to escape HTML special characters
98
+ function escapeHtml(unsafe) {
99
+ return unsafe
100
+ .replace(/&/g, "&amp;")
101
+ .replace(/</g, "&lt;")
102
+ .replace(/>/g, "&gt;")
103
+ .replace(/"/g, "&quot;")
104
+ .replace(/'/g, "&#039;");
105
+ }
106
+
107
+ // Compression and encoding functions
108
+ function compressAndEncode(text) {
109
+ try {
110
+ // Step 1: Compress with gzip
111
+ const compressed = pako.gzip(text);
112
+
113
+ // Step 2: Convert to base64
114
+ let binary = '';
115
+ const bytes = new Uint8Array(compressed);
116
+ for (let i = 0; i < bytes.byteLength; i++) {
117
+ binary += String.fromCharCode(bytes[i]);
118
+ }
119
+ const base64 = btoa(binary);
120
+
121
+ return base64;
122
+ } catch (error) {
123
+ console.error('Compression error:', error);
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ // Decompression and decoding functions
129
+ function decodeAndDecompress(base64String) {
130
+ try {
131
+ // Step 1: Decode base64
132
+ const binaryString = atob(base64String);
133
+
134
+ // Step 2: Convert to Uint8Array
135
+ const bytes = new Uint8Array(binaryString.length);
136
+ for (let i = 0; i < binaryString.length; i++) {
137
+ bytes[i] = binaryString.charCodeAt(i);
138
+ }
139
+
140
+ // Step 3: Decompress
141
+ const decompressed = pako.ungzip(bytes, { to: 'string' });
142
+
143
+ return decompressed;
144
+ } catch (error) {
145
+ console.error('Decompression error:', error);
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ function encodeAndCompress() {
151
+ const zenUmlCode = document.getElementById('zenUmlCode').value;
152
+ const resultsDiv = document.getElementById('testResults');
153
+ resultsDiv.innerHTML = '';
154
+
155
+ try {
156
+ const encoded = compressAndEncode(zenUmlCode);
157
+ document.getElementById('encodedOutput').textContent = encoded;
158
+
159
+ // Show compression ratio
160
+ const originalSize = new Blob([zenUmlCode]).size;
161
+ const compressedSize = new Blob([encoded]).size;
162
+ const ratio = ((originalSize - compressedSize) / originalSize * 100).toFixed(2);
163
+
164
+ resultsDiv.innerHTML = `
165
+ <div class="success">
166
+ Encoding successful!<br>
167
+ Original size: ${originalSize} bytes<br>
168
+ Encoded size: ${compressedSize} bytes<br>
169
+ Compression: ${ratio}% smaller
170
+ </div>
171
+ `;
172
+ } catch (error) {
173
+ resultsDiv.innerHTML = `<div class="error">Encoding failed: ${escapeHtml(error.message)}</div>`;
174
+ }
175
+ }
176
+
177
+ function testDecode() {
178
+ const encoded = document.getElementById('encodedOutput').textContent;
179
+ const resultsDiv = document.getElementById('testResults');
180
+
181
+ if (!encoded) {
182
+ resultsDiv.innerHTML = '<div class="error">No encoded data to decode. Please encode first.</div>';
183
+ return;
184
+ }
185
+
186
+ try {
187
+ const decoded = decodeAndDecompress(encoded);
188
+ const original = document.getElementById('zenUmlCode').value;
189
+
190
+ if (decoded === original) {
191
+ resultsDiv.innerHTML = `
192
+ <div class="success">
193
+ Decoding successful! The decoded content matches the original.<br>
194
+ <pre>${decoded}</pre>
195
+ </div>
196
+ `;
197
+ } else {
198
+ resultsDiv.innerHTML = `
199
+ <div class="error">
200
+ Decoding mismatch!<br>
201
+ Original length: ${original.length}<br>
202
+ Decoded length: ${decoded.length}
203
+ </div>
204
+ `;
205
+ }
206
+ } catch (error) {
207
+ resultsDiv.innerHTML = `<div class="error">Decoding failed: ${error.message}</div>`;
208
+ }
209
+ }
210
+
211
+ function generateUrl() {
212
+ const encoded = document.getElementById('encodedOutput').textContent;
213
+
214
+ if (!encoded) {
215
+ document.getElementById('urlOutput').innerHTML = '<span style="color: red;">Please encode first</span>';
216
+ return;
217
+ }
218
+
219
+ const baseUrl = window.location.origin + '/renderer.html';
220
+ const url = `${baseUrl}?code=${encodeURIComponent(encoded)}`;
221
+
222
+ document.getElementById('urlOutput').innerHTML = `
223
+ <a href="${url}" target="_blank">${url}</a><br>
224
+ <small>URL length: ${url.length} characters</small>
225
+ `;
226
+ }
227
+
228
+ function clearAll() {
229
+ document.getElementById('zenUmlCode').value = '';
230
+ document.getElementById('encodedOutput').textContent = '';
231
+ document.getElementById('urlOutput').textContent = '';
232
+ document.getElementById('testResults').innerHTML = '';
233
+ }
234
+
235
+ // Test some edge cases
236
+ function runTests() {
237
+ const testCases = [
238
+ { name: 'Empty string', input: '' },
239
+ { name: 'Simple text', input: 'Hello World' },
240
+ { name: 'Unicode', input: '你好世界 🌍' },
241
+ { name: 'Special characters', input: '!@#$%^&*()_+-=[]{}|;:,.<>?' }
242
+ ];
243
+
244
+ let results = '<h3>Automated Tests:</h3>';
245
+
246
+ for (const test of testCases) {
247
+ try {
248
+ const encoded = compressAndEncode(test.input);
249
+ const decoded = decodeAndDecompress(encoded);
250
+
251
+ if (decoded === test.input) {
252
+ results += `<div class="success">✓ ${test.name}: PASSED</div>`;
253
+ } else {
254
+ results += `<div class="error">✗ ${test.name}: FAILED - Mismatch</div>`;
255
+ }
256
+ } catch (error) {
257
+ results += `<div class="error">✗ ${test.name}: ERROR - ${error.message}</div>`;
258
+ }
259
+ }
260
+
261
+ document.getElementById('testResults').innerHTML = results;
262
+ }
263
+
264
+ // Make functions available globally for onclick handlers
265
+ window.encodeAndCompress = encodeAndCompress;
266
+ window.testDecode = testDecode;
267
+ window.generateUrl = generateUrl;
268
+ window.clearAll = clearAll;
269
+
270
+ // Run tests on page load
271
+ runTests();
272
+ </script>
273
+ </body>
274
+ </html>
@@ -0,0 +1,192 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>URL Parameter Test</title>
7
+ </head>
8
+ <body>
9
+ <h1>URL Parameter Extraction Test</h1>
10
+ <div id="test-results"></div>
11
+
12
+ <script>
13
+ // Utility function to escape HTML special characters
14
+ function escapeHTML(str) {
15
+ return str.replace(/[&<>"']/g, (char) => {
16
+ const escapeMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
17
+ return escapeMap[char];
18
+ });
19
+ }
20
+
21
+ // Copy the URL parameter extraction functions from renderer.html
22
+ function extractCodeFromURL() {
23
+ try {
24
+ console.log('Extracting code parameter from URL...');
25
+
26
+ // Get current URL
27
+ const currentUrl = new URL(window.location.href);
28
+ console.log('Current URL:', currentUrl.href);
29
+
30
+ // Extract the 'code' parameter
31
+ const codeParam = currentUrl.searchParams.get('code');
32
+
33
+ if (codeParam) {
34
+ console.log('Code parameter found:', codeParam.substring(0, 50) + '...');
35
+ return codeParam;
36
+ } else {
37
+ console.log('No code parameter found in URL');
38
+ return null;
39
+ }
40
+ } catch (error) {
41
+ console.error('Error extracting code from URL:', error);
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function hasCodeParameter() {
47
+ try {
48
+ const currentUrl = new URL(window.location.href);
49
+ return currentUrl.searchParams.has('code');
50
+ } catch (error) {
51
+ console.error('Error checking for code parameter:', error);
52
+ return false;
53
+ }
54
+ }
55
+
56
+ // Base64 decoding functions (copied from renderer.html)
57
+ function decodeBase64(encodedString) {
58
+ try {
59
+ console.log('Attempting to decode Base64 string...');
60
+
61
+ // Check if the string is valid Base64
62
+ if (!encodedString || typeof encodedString !== 'string') {
63
+ throw new Error('Invalid input: string is null, undefined, or not a string');
64
+ }
65
+
66
+ // Remove any whitespace
67
+ const cleanedString = encodedString.trim();
68
+
69
+ // Check if string looks like Base64 (basic validation)
70
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
71
+ if (!base64Regex.test(cleanedString)) {
72
+ throw new Error('Invalid Base64 format');
73
+ }
74
+
75
+ // Attempt to decode
76
+ const decodedString = atob(cleanedString);
77
+ console.log('Base64 decoding successful');
78
+
79
+ return decodedString;
80
+ } catch (error) {
81
+ console.error('Base64 decoding failed:', error);
82
+ throw new Error(`Base64 decoding failed: ${error.message}`);
83
+ }
84
+ }
85
+
86
+ function processURLCodeParameter(encodedCode) {
87
+ try {
88
+ console.log('Processing URL code parameter...');
89
+
90
+ if (!encodedCode) {
91
+ console.log('No encoded code provided');
92
+ return null;
93
+ }
94
+
95
+ // Try to decode as Base64
96
+ const decodedCode = decodeBase64(encodedCode);
97
+ console.log('Code parameter successfully decoded from Base64');
98
+
99
+ return decodedCode;
100
+ } catch (error) {
101
+ console.error('Failed to process URL code parameter:', error);
102
+
103
+ // Return the original code as fallback (might be plain text)
104
+ console.log('Falling back to using raw parameter value');
105
+ return encodedCode;
106
+ }
107
+ }
108
+
109
+ // Test the functions
110
+ function runTests() {
111
+ const resultsDiv = document.getElementById('test-results');
112
+ let results = '<h2>Test Results:</h2>';
113
+
114
+ // Test 1: Check current URL
115
+ const currentUrl = window.location.href;
116
+ results += `<p><strong>Current URL:</strong> ${escapeHTML(currentUrl)}</p>`;
117
+
118
+ // Test 2: Check if code parameter exists
119
+ const hasCode = hasCodeParameter();
120
+ results += `<p><strong>Has code parameter:</strong> ${hasCode}</p>`;
121
+
122
+ // Test 3: Extract code parameter
123
+ const codeParam = extractCodeFromURL();
124
+ results += `<p><strong>Extracted code parameter:</strong> ${escapeHTML(codeParam) || 'null'}</p>`;
125
+
126
+ // Test 4: Process URL code parameter (includes Base64 decoding)
127
+ if (codeParam) {
128
+ try {
129
+ const processedCode = processURLCodeParameter(codeParam);
130
+ results += `<p><strong>Processed code (after Base64 decoding):</strong></p>`;
131
+ results += `<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; white-space: pre-wrap;">${processedCode || 'null'}</pre>`;
132
+
133
+ // Check if it was actually Base64 decoded
134
+ if (processedCode !== codeParam) {
135
+ results += `<p style="color: green;"><strong>✓ Base64 decoding successful!</strong></p>`;
136
+ } else {
137
+ results += `<p style="color: orange;"><strong>⚠ Used raw parameter (not Base64 or decoding failed)</strong></p>`;
138
+ }
139
+ } catch (error) {
140
+ results += `<p style="color: red;"><strong>✗ Error processing code parameter:</strong> ${error.message}</p>`;
141
+ }
142
+ }
143
+
144
+ // Test 5: Show all URL parameters
145
+ const urlParams = new URLSearchParams(window.location.search);
146
+ results += '<p><strong>All URL parameters:</strong></p><ul>';
147
+ for (const [key, value] of urlParams) {
148
+ results += `<li>${escapeHTML(key)}: ${escapeHTML(value)}</li>`;
149
+ }
150
+ results += '</ul>';
151
+
152
+ resultsDiv.innerHTML = results;
153
+ }
154
+
155
+ // Run tests when page loads
156
+ window.addEventListener('load', runTests);
157
+ </script>
158
+
159
+ <h2>Test Instructions:</h2>
160
+ <p>To test URL parameter extraction and Base64 decoding, add parameters to the URL:</p>
161
+ <ul>
162
+ <li><a href="?code=test123">Test with simple code parameter</a></li>
163
+ <li><a href="?code=dGVzdCBjb2Rl">Test with Base64 code parameter ("test code")</a></li>
164
+ <li><a href="?code=SGVsbG8gV29ybGQ=">Test with Base64 code parameter ("Hello World")</a></li>
165
+ <li><a href="?code=QS5tZXRob2QoKSB7IEIucHJvY2VzcygpIH0=">Test with Base64 ZenUML code</a></li>
166
+ <li><a href="?code=test123&theme=dark">Test with multiple parameters</a></li>
167
+ <li><a href="?code=invalid@base64!">Test with invalid Base64 (should fallback)</a></li>
168
+ <li><a href="?other=value">Test with no code parameter</a></li>
169
+ </ul>
170
+
171
+ <h3>Base64 Encoding Helper:</h3>
172
+ <p>Enter text to encode as Base64:</p>
173
+ <input type="text" id="textInput" placeholder="Enter text to encode" style="width: 300px; padding: 5px;">
174
+ <button onclick="encodeText()" style="padding: 5px 10px;">Encode to Base64</button>
175
+ <p>Base64 result: <span id="base64Result" style="font-family: monospace; background: #f0f0f0; padding: 2px 4px;"></span></p>
176
+
177
+ <script>
178
+ function encodeText() {
179
+ const text = document.getElementById('textInput').value;
180
+ if (text) {
181
+ const encoded = btoa(text);
182
+ document.getElementById('base64Result').textContent = encoded;
183
+
184
+ // Create a test link
185
+ const testUrl = `?code=${encoded}`;
186
+ document.getElementById('base64Result').innerHTML =
187
+ `${encoded} <br><a href="${testUrl}" style="font-size: 12px;">Test this Base64 code</a>`;
188
+ }
189
+ }
190
+ </script>
191
+ </body>
192
+ </html>
package/vite.config.ts CHANGED
@@ -32,7 +32,7 @@ export default defineConfig(({ mode }) => ({
32
32
  base: mode === "gh-pages" ? "/zenuml-core/" : "/",
33
33
  build: {
34
34
  rollupOptions: {
35
- input: ["index.html", "embed.html", ...cypressHtmlFiles],
35
+ input: ["index.html", "embed.html", "renderer.html", "test-compression.html", ...cypressHtmlFiles],
36
36
  },
37
37
  },
38
38
  resolve: {
package/wrangler.toml ADDED
@@ -0,0 +1,12 @@
1
+ name = "zenuml-web-renderer"
2
+ compatibility_date = "2024-01-01"
3
+
4
+ # Use Workers Static Assets (recommended over Pages)
5
+ [assets]
6
+ directory = "dist"
7
+
8
+ [env.production]
9
+ name = "zenuml-web-renderer"
10
+
11
+ [env.staging]
12
+ name = "zenuml-web-renderer-staging"
@@ -1,121 +0,0 @@
1
- = Integration Guide
2
- :icons: font
3
-
4
- == Audience
5
- This document is written for those want to integrate ZenUML to their applications. If you are an end
6
- user, please read tutorials; if your would like to contribute, please read contributor's guide.
7
-
8
-
9
- == Integration
10
- An important goal of this project is to be easily integrated into other projects.
11
- There are mainly two ways to do this.
12
-
13
- === As a library
14
-
15
- ZenUML Core exposes a simple API for integration:
16
-
17
- [source, typescript]
18
- ----
19
- interface IZenUml {
20
- get code(): string | undefined;
21
- get theme(): string | undefined;
22
- // Resolve after rendering is finished.
23
- render: (code: string | undefined, theme: string | undefined) => Promise<IZenUml>
24
- }
25
-
26
- // client code
27
- import ZenUml from '@ZenUML/core'
28
- const zenUml = new ZenUml(el)
29
- await zenUml.render('A.method', {theme: 'theme-blue'})
30
- ----
31
-
32
- ==== When rendering is finished
33
- When rendering is finished, the `render` method will return a promise that resolves to the
34
- instance of `IZenUml`. The `html` properties of the instance will be updated to the rendered
35
- HTML.
36
-
37
- [NOTE]
38
- ====
39
- Vue's reactive system works against the `data`, `props` and `computed` properties of a component.
40
- This means a parent component is not necessarily be notified when a child component is `mounted` or `updated`.
41
- So this means we can not use the `mounted` or `updated` hook on `LifelineLayer` to emit `rendered` event.
42
- ====
43
-
44
- It is tricky to know when the rendering is finished. The `Lifeline` components used `$nextTick`
45
- in their `mounted` and `updated` hooks to set the top when a `creation` message is sent to it.
46
- We have to wait until all those `$nextTick` calls are finished.
47
-
48
- [NOTE]
49
- ====
50
- It seems that (to be confirmed) the `$nextTick` calls are queued and executed in ONE tick. This
51
- makes it easier to control the timing when to resolve the `render` promise. We only need to wait
52
- ONE `$nextTick` call to be finished.
53
- ====
54
-
55
- The diagram is rendered in the following steps:
56
-
57
- 1. The lifeline layer;
58
- 2. The message layer;
59
- 3. Update lifeline layer with `setTimeout` for `creation` messages.
60
-
61
- === As an iframe
62
-
63
- You can embed the ZenUML core renderer as an application within another app, where you store the diagram
64
- data in the host app. It takes around 5 minutes to get a basic example running.
65
-
66
- ==== Quick test
67
- To test this out open `https://embed.zenuml.com/embed.html`. In the developer console, type in the
68
- following command.
69
-
70
- [source,js]
71
- ----
72
- window.postMessage( {action: 'eval', args: { code: 'ZenUML.Hello' }})
73
- ----
74
- ==== The protocol
75
-
76
- The protocol is a simple JSON object with the following fields.
77
-
78
- [source,json]
79
- ----
80
- {
81
- "action": "eval",
82
- "args": {
83
- "code": "ZenUML.Hello",
84
- "style": "#diagram { background-color: red; }",
85
- "theme": "blue",
86
- "css": "https://github.com/abruzzi/zenuml-css-overrides/blob/master/zenuml-override.css"
87
- }
88
- }
89
- ----
90
-
91
- ==== Example
92
-
93
- [source,html]
94
- ----
95
- <iframe src="https://embed.zenuml.com/embed.html" id="zenuml-iframe"
96
- style="width: 100%; height: 100%; border: none;">
97
-
98
- </iframe>
99
- <script>
100
- const iframe = document.getElementById('zenuml-iframe');
101
- const message = {
102
- action: 'eval',
103
- args: {
104
- code: 'ZenUML.Hello',
105
- style: '#diagram { background-color: red; }',
106
- theme: 'blue',
107
- css: ''
108
- }
109
- };
110
- setTimeout(() => {
111
- iframe.contentWindow.postMessage(message, '*');
112
- }, 1000);
113
- </script>
114
- ----
115
-
116
- [source,js]
117
- ----
118
- document.getElementsByTagName('iframe')[0] // get iframe
119
- .contentWindow // get target window
120
- .postMessage({action: "eval", args: { code: 'A.m' }})
121
- ----