1- import "./shapes/shapeMermaid" ;
1+ import { mermaid_plugin_defaults , mxShapeMermaid } from "./shapes/shapeMermaid" ;
22import "./palettes/mermaid/paletteMermaid" ;
33import mermaid from 'mermaid'
44
5+ import merge from 'deepmerge'
6+ import { diff , addedDiff , deletedDiff , updatedDiff , detailedDiff } from 'deep-object-diff'
7+ import isObject from 'is-object'
8+
59/**
610 * Constructs a new parse dialog.
711 */
@@ -14,6 +18,9 @@ var DialogMermaid = function (editorUi, shape) {
1418 var graph = editorUi . editor . graph ;
1519 graph . getModel ( ) . beginUpdate ( ) ;
1620 graph . labelChanged ( shape . state . cell , text ) ;
21+ // To replace valueChanged in mxShapeMermaid.prototype.paintVertexShape
22+ shape . updateImage ( ) ;
23+ shape . redraw ( ) ;
1724 graph . getModel ( ) . endUpdate ( ) ;
1825 editorUi . spinner . stop ( ) ;
1926
@@ -35,8 +42,16 @@ var DialogMermaid = function (editorUi, shape) {
3542 <div style="flex: 0 0 4em; display: flex; flex-direction: row; align-items: end">
3643 <pre id="plugin_mermaid_parserstatus" style="flex: 1; text-align: left; overflow-x: auto"></pre>
3744 <div id="plugin_mermaid_buttons" style="flex: initial; text-align: right; align-self: flex-end;">
38- <p style="margin-block: unset;">
39- <a target="_blank" href="https://mermaid-js.github.io/mermaid/#/./n00b-syntaxReference">[ Syntax ]</a>
45+ <p style="margin-block: unset; font-size: 90%">
46+ <br />Download as |
47+ <a id="plugin_mermaid_button_dl_svg" href="#">SVG</a> |
48+ <a id="plugin_mermaid_button_dl_png" href="#">PNG</a> |
49+ <br />Copy as |
50+ <span style="display: none;"><a id="plugin_mermaid_button_html" href="#">HTML</a> | </span>
51+ <span style="display: none;"><a id="plugin_mermaid_button_svg" href="#">SVG</a> | </span>
52+ <a id="plugin_mermaid_button_png" href="#">PNG</a> |
53+ <br />Help |
54+ <a target="_blank" href="https://mermaid-js.github.io/mermaid/#/./n00b-syntaxReference">Syntax</a> |
4055 </p><br /></div>
4156 </div>
4257 <div style="flex: 0 0 32px;"></div>
@@ -119,6 +134,81 @@ var DialogMermaid = function (editorUi, shape) {
119134 textarea . addEventListener ( 'input' , handleInput , false ) ;
120135 }
121136
137+ // Handle copy
138+ function generateCanvas ( callback , background = null ) {
139+ var svg = div . querySelector ( '#graph-div' ) ;
140+
141+ // https://stackoverflow.com/questions/60551658/saving-offscreencanvas-content-to-disk-as-png-in-electron
142+ // https://stackoverflow.com/questions/32230894/convert-very-large-svg-to-png-using-canvas
143+ //var svg_xml = (new XMLSerializer()).serializeToString(svg);
144+ //var blob = new Blob([svg_xml], {type:'image/svg+xml;charset=utf-8'});
145+ //var url = window.URL.createObjectURL(blob);
146+ var url = "data:image/svg+xml;base64," + btoa ( unescape ( encodeURIComponent ( div . querySelector ( '#graph-div' ) . outerHTML ) ) ) ;
147+
148+ var scale = 3 ;
149+ var img = new Image ( ) ;
150+ img . width = svg . getBBox ( ) . width * scale ;
151+ img . height = svg . getBBox ( ) . height * scale ;
152+ img . onload = ( ) => {
153+ var canvas = document . createElement ( 'canvas' ) ;
154+ var context = canvas . getContext ( '2d' ) ;
155+ canvas . width = svg . getBBox ( ) . width * scale ;
156+ canvas . height = svg . getBBox ( ) . height * scale ;
157+
158+ // Add a white background to cope with the transparent image problem getting black on windows...
159+ if ( background ) {
160+ context . fillStyle = background ;
161+ context . fillRect ( 0 , 0 , canvas . width , canvas . height ) ;
162+ }
163+
164+ context . drawImage ( img , svg . getBBox ( ) . x * scale , svg . getBBox ( ) . y * scale , svg . getBBox ( ) . width * scale , svg . getBBox ( ) . height * scale ) ;
165+ window . URL . revokeObjectURL ( url ) ;
166+
167+ callback ( canvas ) ;
168+ }
169+ img . src = url ;
170+ }
171+
172+ div . querySelector ( '#plugin_mermaid_button_dl_svg' ) . onclick = async function ( ) {
173+ var aDownloadLink = document . createElement ( 'a' ) ;
174+ aDownloadLink . download = 'image.svg' ;
175+ aDownloadLink . href = "data:image/svg+xml;base64," + btoa ( unescape ( encodeURIComponent ( div . querySelector ( '#graph-div' ) . outerHTML ) ) ) ;
176+ aDownloadLink . click ( ) ;
177+ }
178+
179+ div . querySelector ( '#plugin_mermaid_button_dl_png' ) . onclick = async function ( ) {
180+ generateCanvas ( function ( canvas ) {
181+ var aDownloadLink = document . createElement ( 'a' ) ;
182+ aDownloadLink . download = 'image.png' ;
183+ aDownloadLink . href = canvas . toDataURL ( ) ;
184+ aDownloadLink . click ( ) ;
185+ } ) ;
186+ }
187+
188+ div . querySelector ( '#plugin_mermaid_button_png' ) . onclick = async function ( ) {
189+ generateCanvas ( function ( canvas ) {
190+ canvas . toBlob ( function ( imgBlob ) {
191+ navigator . clipboard . write ( [ new ClipboardItem ( { [ imgBlob . type ] : imgBlob } ) ] ) ;
192+ } , 'image/png' ) ;
193+ } , 'white' ) ;
194+ }
195+
196+ // (hidden) Buggy - Oddly makes the whole electron stop working...
197+ div . querySelector ( '#plugin_mermaid_button_svg' ) . onclick = async function ( ) {
198+ var svg_xml = ( new XMLSerializer ( ) ) . serializeToString ( div . querySelector ( '#graph-div' ) ) ;
199+ var svg_blob = new Blob ( [ svg_xml ] , { type : 'image/svg+xml;charset=utf-8' } ) ;
200+ var clip_item = new ClipboardItem ( { 'image/svg+xml' : svg_blob } ) ;
201+ navigator . clipboard . write ( [ clip_item ] ) ;
202+ }
203+
204+ // (hidden) Tested, but not very usefull as not much destination applications support it... (Libreoffice Writer, with poor SVG render)
205+ div . querySelector ( '#plugin_mermaid_button_html' ) . onclick = async function ( ) {
206+ navigator . clipboard . write ( [ new ClipboardItem (
207+ { 'text/html' : new Blob ( [ "<img src='" + "data:image/svg+xml;base64," +
208+ btoa ( unescape ( encodeURIComponent ( div . querySelector ( '#graph-div' ) . outerHTML ) ) ) + "'>" ] , { type : 'text/html' } ) } ) ]
209+ ) ;
210+ }
211+
122212 var cancelBtn = mxUtils . button ( mxResources . get ( 'close' ) , function ( ) {
123213 win . destroy ( ) ;
124214 } ) ;
@@ -147,23 +237,177 @@ var DialogMermaid = function (editorUi, shape) {
147237} ;
148238
149239Draw . loadPlugin ( function ( ui ) {
240+
241+ // Build mermaid settings : by least order
242+ // - mermaid_plugin_defaults : this plugin defaults
243+ // - EditorUi.defaultMermaidConfig : drawio defaults mermaid
244+ // - Editor.config.defaultMermaidConfig : drawio config (from PreConfig and local configuration)
245+
246+ let mermaid_settings = { } ;
247+ mermaid_settings = merge ( mermaid_settings , mermaid_plugin_defaults ) ;
248+ try {
249+ mermaid_settings = merge ( mermaid_settings , window . EditorUi . defaultMermaidConfig ) ;
250+ } catch ( e ) {
251+ if ( ! e instanceof TypeError ) {
252+ throw e ;
253+ }
254+ }
255+ try {
256+ mermaid_settings = merge ( mermaid_settings , window . Editor . config . defaultMermaidConfig ) ;
257+ } catch ( e ) {
258+ if ( ! e instanceof TypeError ) {
259+ throw e ;
260+ }
261+ }
262+
263+ // Result is updated back in EditorUi.defaultMermaidConfig to have consistent settings with native mermaid
264+ // Note that the result will not be consistent if the diagram is updated in native mermaid without the plugin,
265+ // but no solution would be perfect until native mermaid allow some configuration...
266+ // As mermaid version are not the same between native mermaid and the plugin one, render may be different.
267+ window . EditorUi . defaultMermaidConfig = mermaid_settings ;
268+
269+ // Handle defaults
270+ Object . assign ( mermaid_plugin_defaults , mermaid_settings ) ;
271+ mxShapeMermaid . prototype . customProperties = mxShapeMermaid . prototype . buildCustomProperties ( mermaid_settings ) ;
272+
150273 // Adds custom sidebar entry
151274 ui . sidebar . addMermaidPalette ( ) ;
152275
153- ui . editor . graph . addListener ( mxEvent . DOUBLE_CLICK , function ( sender , evt ) {
154- var cell = evt . getProperty ( "cell" ) ;
276+ function isCellPluginMermaid ( cell ) {
155277 if ( ! cell ) {
156- return ;
278+ return false ;
157279 }
158280 if ( cell . style . indexOf ( "shape=mxgraph.mermaid.abstract.mermaid" ) < 0 ) {
159- return ;
281+ return false ;
282+ }
283+ return true ;
284+ }
285+
286+ function isCellNativeMermaid ( cell ) {
287+ if ( ! cell ) { return false ; }
288+ if ( mxUtils . isNode ( cell . value ) ) {
289+ if ( cell . getAttribute ( 'mermaidData' , '' ) != '' ) {
290+ return true ;
291+ }
160292 }
293+ return false ;
294+ }
161295
162- var shape = ui . editor . graph . view . states [ "map" ] [ cell . mxObjectId ] . shape ;
296+ ui . editor . graph . addListener ( mxEvent . DOUBLE_CLICK , function ( sender , evt ) {
297+ var cell = evt . getProperty ( "cell" ) ;
298+ if ( isCellPluginMermaid ( cell ) ) {
299+ var shape = ui . editor . graph . view . states [ "map" ] [ cell . mxObjectId ] . shape ;
163300
164- if ( shape ) {
165- var dlg = new DialogMermaid ( ui , shape ) ;
301+ if ( shape ) {
302+ var dlg = new DialogMermaid ( ui , shape ) ;
303+ }
304+ evt . consume ( ) ;
166305 }
167- evt . consume ( ) ;
168306 } ) ;
307+
308+ // Add convert menus
309+ mxResources . parse ( 'mermaidconvertfrom=Convert to Mermaid plugin shape...' ) ;
310+ mxResources . parse ( 'mermaidconvertto=Convert to native Mermaid shape...' ) ;
311+
312+ var uiCreatePopupMenu = ui . menus . createPopupMenu ;
313+ ui . menus . createPopupMenu = function ( menu , cell , evt )
314+ {
315+ uiCreatePopupMenu . apply ( this , arguments ) ;
316+
317+ var graph = ui . editor . graph ;
318+ var cell = graph . getSelectionCell ( ) ;
319+
320+ if ( isCellPluginMermaid ( cell ) ) {
321+ this . addMenuItems ( menu , [ '-' , 'mermaidconvertto' ] , null , evt ) ;
322+ }
323+
324+ if ( isCellNativeMermaid ( cell ) ) {
325+ this . addMenuItems ( menu , [ '-' , 'mermaidconvertfrom' ] , null , evt ) ;
326+ }
327+
328+ } ;
329+
330+ ui . actions . addAction ( 'mermaidconvertto' , function ( )
331+ {
332+ let graph = ui . editor . graph ;
333+ let cell = graph . getSelectionCell ( ) ;
334+ if ( ! isCellPluginMermaid ( cell ) ) return ;
335+
336+ graph . getModel ( ) . beginUpdate ( ) ;
337+ try
338+ {
339+ let state = graph . view . getState ( cell , true ) ;
340+ let mermaidData = JSON . stringify ( { data : graph . convertValueToString ( cell ) , config : state . shape . getRenderOptions ( ) /*getStyleOptions()*/ } , null , 2 )
341+ state . shape . redraw ( ) ;
342+ let image = state . shape . image . replace ( ";base64" , "" ) ; // ;base64 breaks the style
343+ graph . setCellStyle ( 'shape=image;noLabel=1;verticalAlign=top;imageAspect=1;' + 'image=' + image + ';' , [ cell ] ) ;
344+ graph . setAttributeForCell ( cell , 'mermaidData' , mermaidData ) ;
345+
346+ graph . view . getState ( cell , true ) . destroy ( ) ;
347+ graph . view . getState ( cell , true ) ;
348+ }
349+ finally
350+ {
351+ graph . getModel ( ) . endUpdate ( ) ;
352+ }
353+
354+ } ) ;
355+
356+
357+ ui . actions . addAction ( 'mermaidconvertfrom' , function ( )
358+ {
359+ let graph = ui . editor . graph ;
360+ let cell = graph . getSelectionCell ( ) ;
361+ if ( ! isCellNativeMermaid ( cell ) ) return ;
362+
363+ try {
364+
365+ graph . getModel ( ) . beginUpdate ( ) ;
366+
367+ var data = JSON . parse ( cell . getAttribute ( 'mermaidData' , '' ) ) ;
368+
369+ // Default style from paletteMermaid
370+ let style = 'shadow=0;dashed=0;align=left;strokeWidth=1;shape=mxgraph.mermaid.abstract.mermaid;labelBackgroundColor=#ffffff;noLabel=1;' ;
371+
372+ function addToStyle ( basestyle , value ) {
373+ if ( isObject ( value ) ) {
374+ for ( let key in value ) {
375+ addToStyle ( ( basestyle == '' ) ? key : basestyle + "_" + key , value [ key ] ) ;
376+ }
377+ } else {
378+ style += encodeURI ( basestyle ) + "=" + encodeURI ( value ) + ";" ;
379+ }
380+ }
381+
382+ let configDiff = diff ( mermaid_plugin_defaults , data . config ) ;
383+ addToStyle ( '' , configDiff ) ;
384+
385+ // cell.value = data.data;
386+ graph . setAttributeForCell ( cell , 'mermaidData' , "" ) ;
387+ graph . labelChanged ( cell , data . data ) ;
388+
389+ graph . setCellStyle ( style , [ cell ] ) ;
390+
391+ graph . view . getState ( cell , true ) . destroy ( ) ;
392+ graph . view . getState ( cell , true ) ;
393+
394+ }
395+ catch ( error )
396+ {
397+ console . error ( error ) ;
398+ }
399+ finally
400+ {
401+ graph . getModel ( ) . endUpdate ( ) ;
402+ }
403+
404+ } ) ;
405+
406+
407+
408+
409+
169410} ) ;
411+
412+
413+
0 commit comments