fix graph editor

This commit is contained in:
Xuan Sang LE 2018-03-17 20:50:51 +01:00
parent 0ff98fee98
commit 7133bdcbab
4 changed files with 53573 additions and 46748 deletions

View File

@ -1,6 +1,6 @@
coffee_files = main.coffee coffee_files = main.coffee
jsfiles = libs/viz-lite.js libs/svg-pan-zoom.js libs/mermaidAPI.js jsfiles = libs/svg-pan-zoom.js libs/mermaidAPI.js
cssfiles = main.css cssfiles = main.css
copyfiles = scheme.html package.json copyfiles = scheme.html package.json

File diff suppressed because one or more lines are too long

View File

@ -21,22 +21,24 @@ class GraphEditor extends this.OS.GUI.BaseApplication
main: () -> main: () ->
me = @ me = @
mermaidAPI.initialize { startOnLoad: false } #mermaid.initialize { startOnLoad: false }
mermaid.initialize {
theme: 'forest'
}
@currfile = if @args and @args.length > 0 then @args[0].asFileHandler() else "Untitled".asFileHandler() @currfile = if @args and @args.length > 0 then @args[0].asFileHandler() else "Untitled".asFileHandler()
@currfile.dirty = false @currfile.dirty = false
@datarea = @find "datarea" @datarea = @find "datarea"
@preview = @find "preview" @preview = @find "preview"
@renderlist = @find "render-list"
@btctn = @find "btn-container" @btctn = @find "btn-container"
@.editor = ace.edit @datarea @.editor = ace.edit @datarea
@.editor.setOptions { @.editor.setOptions {
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
enableLiveAutocompletion: true, enableLiveAutocompletion: true,
fontSize: "9pt" fontSize: "10pt"
} }
#@.editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> } #@.editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> }
@editor.getSession().setUseWrapMode true @editor.getSession().setUseWrapMode true
@editor.session.setMode "ace/mode/dot" @editor.session.setMode "ace/mode/text"
@editor.setTheme "ace/theme/monokai" @editor.setTheme "ace/theme/monokai"
@editor.on "input", () -> @editor.on "input", () ->
if me.editormux if me.editormux
@ -44,16 +46,11 @@ class GraphEditor extends this.OS.GUI.BaseApplication
return false return false
if not me.currfile.dirty if not me.currfile.dirty
me.currfile.dirty = true me.currfile.dirty = true
@renderlist.set "onlistselect", (e) ->
text = ""
if me.engine() is "Dot"
text = GraphEditor.dummydot
else
text = GraphEditor.dummymermaid
if not me.currfile.basename
me.editormux = true if not me.currfile.basename
me.editor.setValue text me.editormux = true
me.editor.setValue GraphEditor.dummymermaid
me.renderSVG false me.renderSVG false
@ -68,7 +65,7 @@ class GraphEditor extends this.OS.GUI.BaseApplication
@bindKey "ALT-O", () -> me.actionFile "#{me.name}-Open" @bindKey "ALT-O", () -> me.actionFile "#{me.name}-Open"
@bindKey "CTRL-S", () -> me.actionFile "#{me.name}-Save" @bindKey "CTRL-S", () -> me.actionFile "#{me.name}-Save"
@bindKey "ALT-W", () -> me.actionFile "#{me.name}-Saveas" @bindKey "ALT-W", () -> me.actionFile "#{me.name}-Saveas"
#@bindKey "CTRL-M", () -> me.svgToCanvas(()->) @bindKey "CTRL-M", () -> me.svgToCanvas(()->)
@on "hboxchange", () -> @on "hboxchange", () ->
me.editor.resize() me.editor.resize()
@ -81,11 +78,8 @@ class GraphEditor extends this.OS.GUI.BaseApplication
(@find "btn-reset").set "onbtclick", (e) -> (@find "btn-reset").set "onbtclick", (e) ->
me.pan.resetZoom() if me.pan me.pan.resetZoom() if me.pan
@renderlist.set "items", [{ text: "Dot", selected: true }, { text: "Mermaid" } ]
@open @currfile @open @currfile
engine: () ->
sel = @renderlist.get "selected"
sel.text
menu: () -> menu: () ->
me = @ me = @
menu = [{ menu = [{
@ -118,7 +112,6 @@ class GraphEditor extends this.OS.GUI.BaseApplication
me.currfile.dirty = false me.currfile.dirty = false
me.editor.setValue d me.editor.setValue d
me.scheme.set "apptitle", "#{me.currfile.basename}" me.scheme.set "apptitle", "#{me.currfile.basename}"
me.renderlist.set "selected", if file.info.mime.match /.*graphviz/ then 0 else 1
#me.renderSVG false #me.renderSVG false
save: (file) -> save: (file) ->
me = @ me = @
@ -170,56 +163,47 @@ class GraphEditor extends this.OS.GUI.BaseApplication
when "PNG" when "PNG"
# toDataURL("image/png") # toDataURL("image/png")
me.svgToCanvas (canvas) -> me.svgToCanvas (canvas) ->
fp.cache = canvas.toDataURL "image/png" try
console.log fp.cache fp.cache = canvas.toDataURL "image/png"
fp.write "base64", (r) -> fp.write "base64", (r) ->
return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error
me.notify __("File exported") me.notify __("File exported")
catch e
me.error __("Cannot export to PNG in this browser: {0}", e.message)
catch e catch e
me.error __("Cannot export: {0}", e.message) me.error __("Cannot export: {0}", e.message)
, __("Export as"), { file: me.currfile } , __("Export as"), { file: me.currfile }
renderSVG: (silent) -> renderSVG: (silent) ->
svg = undefined
me = @ me = @
try id = Math.floor(Math.random() * 100000) + 1
rd = (obj) -> if silent
$(me.preview).children("svg").remove() mermaid.parseError = (e, h) ->
$(me.preview).append obj else
svg = $(me.preview).children("svg")[0] mermaid.parseError = (e, h) ->
$(svg).prepend($ "<defs>") me.error e
$($(svg).children("defs")[0]).html "<style type='text/css'>#{GraphEditor.css}</style>" mermaid.render "c#{id}", @editor.getValue(), (text, f) ->
me.calibrate() me.preview.innerHTML = text
me.pan = svgPanZoom svg, { $(me.preview).append me.btctn
zoomEnabled: true, me.calibrate()
controlIconsEnabled: false, svg = $(me.preview).children("svg")[0]
fit: true, $(svg).attr("style", "")
center: true, me.pan = svgPanZoom svg, {
minZoom: 0.1 zoomEnabled: true,
} controlIconsEnabled: false,
fit: true,
if @engine() is "Dot" center: true,
result = Viz @editor.getValue(), { format: "svg", scale: 1 } minZoom: 0.1
rd($.parseHTML result) }
else #rd $($.parseHTML text).
id = Math.floor(Math.random() * 100000) + 1 , me.preview
if silent
mermaidAPI.parseError = (e, h) ->
else
mermaidAPI.parseError = (e, h) ->
me.error e
mermaidAPI.render "c#{id}", @editor.getValue(), (text, f) ->
$(me.preview).append me.btctn
rd $($.parseHTML text).attr("style", "")
, me.preview
catch e
@error e.message unless silent
svgtext: () -> svgtext: () ->
svg = $(@preview).children("svg")[0] svg = $(@preview).children("svg")[0]
serializer = new XMLSerializer() serializer = new XMLSerializer()
return serializer.serializeToString(svg) return serializer.serializeToString(svg)
svgToCanvas: (f) -> svgToCanvas: (f) ->
me = @ me = @
img = new Image() img = new Image()
@ -230,7 +214,8 @@ class GraphEditor extends this.OS.GUI.BaseApplication
canvas.height = img.height canvas.height = img.height
canvas.getContext("2d").drawImage img, 0, 0, img.width, img.height canvas.getContext("2d").drawImage img, 0, 0, img.width, img.height
f(canvas) f(canvas)
img.src = 'data:image/svg+xml;base64,' + window.btoa(svgStr) img.src = 'data:image/svg+xml;base64,' + btoa(svgStr)
calibrate: () -> calibrate: () ->
svg = ($ @preview).children("svg")[0] svg = ($ @preview).children("svg")[0]
if svg if svg
@ -248,20 +233,6 @@ class GraphEditor extends this.OS.GUI.BaseApplication
me.quit() me.quit()
, __("Quit"), { text: __("Quit without saving ?") } , __("Quit"), { text: __("Quit without saving ?") }
GraphEditor.dummydot = """
graph graphname {
// This attribute applies to the graph itself
size="2";
// The label attribute can be used to change the label of a node
a [label="Foo"];
// Here, the node shape is changed.
b [shape=box];
// These edges both have different line properties
a -- b -- c [color=blue];
b -- d [style=dotted];
// [style=invis] hides a node.
}
"""
GraphEditor.dummymermaid = """ GraphEditor.dummymermaid = """
graph TD; graph TD;
A-->B; A-->B;
@ -269,11 +240,6 @@ graph TD;
B-->D; B-->D;
C-->D; C-->D;
""" """
GraphEditor.css = """
<defs><style type="text/css">
/* <![CDATA[ */.mermaid .label{font-family:'trebuchet ms',verdana,arial;color:#333}.node rect,.node circle,.node ellipse,.node polygon{fill:#cde498;stroke:#13540c;stroke-width:1px}.edgePath .path{stroke:green;stroke-width:1.5px}.edgeLabel{background-color:#e8e8e8}.cluster rect{fill:#cdffb2 !important;rx:4 !important;stroke:#6eaa49 !important;stroke-width:1px !important}.cluster text{fill:#333}.actor{stroke:#13540c;fill:#cde498}text.actor{fill:black;stroke:none}.actor-line{stroke:grey}.messageLine0{stroke-width:1.5;stroke-dasharray:"2 2";marker-end:"url(#arrowhead)";stroke:#333}.messageLine1{stroke-width:1.5;stroke-dasharray:"2 2";stroke:#333}#arrowhead{fill:#333}#crosshead path{fill:#333 !important;stroke:#333 !important}.messageText{fill:#333;stroke:none}.labelBox{stroke:#326932;fill:#cde498}.labelText{fill:black;stroke:none}.loopText{fill:black;stroke:none}.loopLine{stroke-width:2;stroke-dasharray:"2 2";marker-end:"url(#arrowhead)";stroke:#326932}.note{stroke:#6eaa49;fill:#fff5ad}.noteText{fill:black;stroke:none;font-family:'trebuchet ms',verdana,arial;font-size:14px}.section{stroke:none;opacity:.2}.section0{fill:#6eaa49}.section2{fill:#6eaa49}.section1,.section3{fill:white;opacity:.2}.sectionTitle0{fill:#333}.sectionTitle1{fill:#333}.sectionTitle2{fill:#333}.sectionTitle3{fill:#333}.sectionTitle{text-anchor:start;font-size:11px;text-height:14px}.grid .tick{stroke:lightgrey;opacity:.3;shape-rendering:crispEdges}.grid path{stroke-width:0}.today{fill:none;stroke:red;stroke-width:2px}.task{stroke-width:2}.taskText{text-anchor:middle;font-size:11px}.taskTextOutsideRight{fill:black;text-anchor:start;font-size:11px}.taskTextOutsideLeft{fill:black;text-anchor:end;font-size:11px}.taskText0,.taskText1,.taskText2,.taskText3{fill:white}.task0,.task1,.task2,.task3{fill:#487e3a;stroke:#13540c}.taskTextOutside0,.taskTextOutside2{fill:black}.taskTextOutside1,.taskTextOutside3{fill:black}.active0,.active1,.active2,.active3{fill:#cde498;stroke:#13540c}.activeText0,.activeText1,.activeText2,.activeText3{fill:black !important}.done0,.done1,.done2,.done3{stroke:grey;fill:lightgrey;stroke-width:2}.doneText0,.doneText1,.doneText2,.doneText3{fill:black !important}.crit0,.crit1,.crit2,.crit3{stroke:#f88;fill:red;stroke-width:2}.activeCrit0,.activeCrit1,.activeCrit2,.activeCrit3{stroke:#f88;fill:#cde498;stroke-width:2}.doneCrit0,.doneCrit1,.doneCrit2,.doneCrit3{stroke:#f88;fill:lightgrey;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}.doneCritText0,.doneCritText1,.doneCritText2,.doneCritText3{fill:black !important}.activeCritText0,.activeCritText1,.activeCritText2,.activeCritText3{fill:black !important}.titleText{text-anchor:middle;font-size:18px;fill:black}g.classGroup text{fill:#13540c;stroke:none;font-family:'trebuchet ms',verdana,arial;font-size:14px}g.classGroup rect{fill:#cde498;stroke:#13540c}g.classGroup line{stroke:#13540c;stroke-width:1}svg .classLabel .box{stroke:none;stroke-width:0;fill:#cde498;opacity:.5}svg .classLabel .label{fill:#13540c}.relation{stroke:#13540c;stroke-width:1;fill:none}.composition{fill:#13540c;stroke:#13540c;stroke-width:1}#compositionStart{fill:#13540c;stroke:#13540c;stroke-width:1}#compositionEnd{fill:#13540c;stroke:#13540c;stroke-width:1}.aggregation{fill:#cde498;stroke:#13540c;stroke-width:1}#aggregationStart{fill:#cde498;stroke:#13540c;stroke-width:1}#aggregationEnd{fill:#cde498;stroke:#13540c;stroke-width:1}#dependencyStart{fill:#13540c;stroke:#13540c;stroke-width:1}#dependencyEnd{fill:#13540c;stroke:#13540c;stroke-width:1}#extensionStart{fill:#13540c;stroke:#13540c;stroke-width:1}#extensionEnd{fill:#13540c;stroke:#13540c;stroke-width:1}.node text{font-family:'trebuchet ms',verdana,arial;font-size:14px}div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms',verdana,arial;font-size:12px;background:#cdffb2;border:1px solid #6eaa49;border-radius:2px;pointer-events:none;z-index:100} /* ]]> */
</style></defs>
"""
GraphEditor.dependencies = [ GraphEditor.dependencies = [
"ace/ace" "ace/ace"
] ]

View File

@ -1,18 +1,14 @@
<afx-app-window data-id="graph_editor_win" apptitle="__(Graph editor)" width="650" height="500"> <afx-app-window data-id="graph_editor_win" apptitle="__(Graph editor)" width="650" height="500">
<afx-hbox> <afx-hbox>
<afx-vbox clas="section0"> <div data-id="datarea"></div>
<afx-list-view data-height="25" dropdown="true" data-id="render-list"></afx-list-view>
<div data-id="datarea"></div>
</afx-vbox>
<afx-resizer data-width="5" ></afx-resizer> <afx-resizer data-width="5" ></afx-resizer>
<div data-id="preview"> <div data-id="preview">
<div data-height="25" data-id="btn-container"> <div data-height="25" data-id="btn-container">
<afx-button data-id="btn-zoomin" iconclass="fa fa-search-plus"></afx-button> <afx-button data-id="btn-zoomin" iconclass="fa fa-search-plus"></afx-button>
<afx-button data-id="btn-zoomout" iconclass="fa fa-search-minus"></afx-button> <afx-button data-id="btn-zoomout" iconclass="fa fa-search-minus"></afx-button>
<afx-button data-id="btn-reset" iconclass="fa fa-square-o"></afx-button> <afx-button data-id="btn-reset" iconclass="fa fa-square-o"></afx-button>
</div> </div>
</div> </div>
<canvas data-id="offscreen" data-width='0' style="display:none;"></canvas> <canvas data-id="offscreen" data-width="0" style="display:none" ></canvas>
</afx-hbox> </afx-hbox>
</afx-app-window> </afx-app-window>