GraphEditor now support mermaid

This commit is contained in:
Xuan Sang LE 2018-03-16 19:53:32 +01:00
parent 2f57c05ef3
commit 0ff98fee98
15 changed files with 59525 additions and 252 deletions

View File

@ -1,6 +1,8 @@
<afx-list-view class = {dropdown: opts.dropdown == "true"} style = "display:flex; flex-direction:column">
<div class = "list-container" ref = "container" style="flex:1;">
<div if = {opts.dropdown == "true"} ref = "current" style = {opts.width?"min-width:" + opts.width + "px;":""} onclick = {show_list}></div>
<div if = {opts.dropdown == "true"} ref = "current" onclick = {show_list}>
<afx-label ref = "drlabel"></afx-label>
</div>
<ul ref = "mlist" >
<li each={item,i in items } class={selected: parent._autoselect(item,i)} ondblclick = {parent._dbclick} onclick = {parent._select} oncontextmenu = {parent._select}>
<afx-label class = {item.class} color = {item.color} iconclass = {item.iconclass} icon = {item.icon} text = {item.text}></afx-label>
@ -119,9 +121,18 @@
if(opts.dropdown == "true")
{
self.root.observable.on("calibrate", function(){
var cl = function()
{
$(self.refs.container).css("width", $(self.root).width() + "px" )
$(self.refs.current).css("width", $(self.root).width() + "px" )
$(self.refs.mlist).css("width", $(self.root).width() + "px" )
}
cl()
self.root.observable.on("calibrate", function(){
cl()
})
self.root.observable.on("resize", function(){
cl()
})
$(document).click(function(event) {
if(!$(event.target).closest(self.refs.container).length) {
@ -180,7 +191,8 @@
if(opts.dropdown == "true")
{
$(self.refs.mlist).hide()
$(self.refs.current).html(it.text.__())
self.refs.drlabel.root.set("*",it)
}
if(self.onlistselect)

View File

@ -1,220 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class DotEditor extends this.OS.GUI.BaseApplication
constructor: ( args ) ->
super "DotEditor", args
main: () ->
me = @
@currfile = if @args and @args.length > 0 then @args[0].asFileHandler() else "Untitled".asFileHandler()
@currfile.dirty = false
@datarea = @find "datarea"
@preview = @find "preview"
@.editor = ace.edit @datarea
@.editor.setOptions {
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
fontSize: "9pt"
}
#@.editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> }
@.editor.getSession().setUseWrapMode true
@editor.session.setMode "ace/mode/dot"
@editor.setTheme "ace/theme/monokai"
@editor.on "input", () ->
if me.editormux
me.editormux = false
return false
if not me.currfile.dirty
me.currfile.dirty = true
@editormux = true
@editor.setValue DotEditor.dummygraph
@editor.container.addEventListener "keydown", (e) ->
me.renderSVG true if e.key is ";"
, true
@bindKey "CTRL-R", () -> me.renderSVG false
@bindKey "ALT-G", () -> me.export "SVG"
@bindKey "ALT-P", () -> me.export "PNG"
@bindKey "ALT-N", () -> me.actionFile "#{me.name}-New"
@bindKey "ALT-O", () -> me.actionFile "#{me.name}-Open"
@bindKey "CTRL-S", () -> me.actionFile "#{me.name}-Save"
@bindKey "ALT-W", () -> me.actionFile "#{me.name}-Saveas"
@on "hboxchange", () ->
me.editor.resize()
me.calibrate()
@on "focus", () -> me.editor.focus()
me.renderSVG true
(@find "btn-zoomin").set "onbtclick", (e) ->
me.pan.zoomIn() if me.pan
(@find "btn-zoomout").set "onbtclick", (e) ->
me.pan.zoomOut() if me.pan
(@find "btn-reset").set "onbtclick", (e) ->
me.pan.resetZoom() if me.pan
@open @currfile
menu: () ->
me = @
menu = [{
text: "__(File)",
child: [
{ text: "__(New)", dataid: "#{@name}-New", shortcut: "A-N" },
{ text: "__(Open)", dataid: "#{@name}-Open", shortcut: "A-O" },
{ text: "__(Save)", dataid: "#{@name}-Save", shortcut: "C-S" },
{ text: "__(Save as)",dataid: "#{@name}-Saveas" , shortcut: "A-W" },
{ text: "__(Render)", dataid: "#{@name}-Render", shortcut: "C-R" },
{
text: "__(Export as)",
child: [
{ text: "SVG", shortcut: "A-G" },
{ text: "PNG", shortcut: "A-P" }
],
onmenuselect: (e) -> me.export e.item.data.text
},
],
onmenuselect: (e) -> me.actionFile e.item.data.dataid
}]
menu
open: (file) ->
return if file.path is "Untitled"
me = @
file.dirty = false
file.read (d) ->
me.currfile = file
me.editormux = true
me.currfile.dirty = false
me.editor.setValue d
me.scheme.set "apptitle", "#{me.currfile.basename}"
me.renderSVG false
save: (file) ->
me = @
file.write "text/plain", (d) ->
return me.error __("Error saving file {0}", file.basename) if d.error
file.dirty = false
file.text = file.basename
me.scheme.set "apptitle", "#{me.currfile.basename}"
actionFile: (e) ->
me = @
saveas = () ->
me.openDialog "FileDiaLog", (d, n) ->
me.currfile.setPath "#{d}/#{n}"
me.save me.currfile
, __("Save as"), { file: me.currfile }
switch e
when "#{@name}-Open"
@openDialog "FileDiaLog", ( d, f ) ->
me.open "#{d}/#{f}".asFileHandler()
, __("Open file")
when "#{@name}-Save"
@currfile.cache = @editor.getValue()
return @save @currfile if @currfile.basename
saveas()
when "#{@name}-Saveas"
@currfile.cache = @editor.getValue()
saveas()
when "#{@name}-Render"
me.renderSVG false
when "#{@name}-New"
@currfile = "Untitled".asFileHandler()
@currfile.cache = ""
@currfile.dirty = false
@editormux = true
@editor.setValue("")
export: (t) ->
me = @
me.openDialog "PromptDialog", (s) ->
me._gui.openDialog "FileDiaLog", (d, n) ->
fp = "#{d}/#{n}".asFileHandler()
scale = Number(s)
try
switch t
when "SVG"
fp.cache = Viz me.editor.getValue(), { format: "svg", scale: scale}
fp.write "text/plain", (r) ->
return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error
me.notify __("File exported")
when "PNG"
content = Viz me.editor.getValue(), { format: "png-image-element", scale: scale}
content.onload = () ->
fp.cache = @src
fp.write "base64", (r) ->
return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error
me.notify __("File exported")
catch e
me.error __("Cannot export: {0}", e.message)
, __("Export as"), { file: me.currfile }
, "__(Scale)", { label: "__(Diagram scale)" }
renderSVG: (silent) ->
console.log "render svg"
try
result = Viz @editor.getValue(), { format: "svg", scale: 1 }
svg = $.parseHTML result
$(@preview).children("svg").remove()
$(@preview).append svg
svg = $(@preview).children("svg")[0]
@calibrate()
@pan = svgPanZoom svg, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.1
}
catch e
@error e.message unless silent
calibrate: () ->
svg = ($ @preview).children("svg")[0]
if svg
prs = [$(@preview).width(), $(@preview).height()]
$(svg).attr "width", prs[0] + "px"
$(svg).attr "height", prs[1] + "px"
cleanup: (evt) ->
return unless @currfile.dirty
me = @
evt.preventDefault()
@.openDialog "YesNoDialog", (d) ->
if d
me.currfile.dirty = false
me.quit()
, __("Quit"), { text: __("Quit without saving ?") }
DotEditor.dummygraph = """
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.
}
"""
DotEditor.dependencies = [
"ace/ace"
]
this.OS.register "DotEditor", DotEditor

View File

@ -1,14 +0,0 @@
<afx-app-window data-id="dot-edit-win" apptitle="__(Dot editor)" width="650" height="500">
<afx-hbox>
<div data-id="datarea"></div>
<afx-resizer data-width="5"></afx-resizer>
<div data-id="preview">
<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-zoomout" iconclass="fa fa-search-minus"></afx-button>
<afx-button data-id="btn-reset" iconclass="fa fa-square-o"></afx-button>
</div>
</div>
</afx-hbox>
</afx-app-window>

View File

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

View File

@ -0,0 +1,273 @@
/* Flowchart variables */
/* Sequence Diagram variables */
/* Gantt chart variables */
afx-app-window[data-id="graph_editor_win"] .mermaid .label {
color: #333;
}
afx-app-window[data-id="graph_editor_win"] .node rect,
afx-app-window[data-id="graph_editor_win"] .node circle,
afx-app-window[data-id="graph_editor_win"] .node ellipse,
afx-app-window[data-id="graph_editor_win"] .node polygon {
fill: #ECECFF;
stroke: #CCCCFF;
stroke-width: 1px;
}
afx-app-window[data-id="graph_editor_win"] .edgePath .path {
stroke: #333333;
}
afx-app-window[data-id="graph_editor_win"] .edgeLabel {
background-color: #e8e8e8;
}
afx-app-window[data-id="graph_editor_win"] .cluster rect {
fill: #ffffde !important;
rx: 4 !important;
stroke: #aaaa33 !important;
stroke-width: 1px !important;
}
afx-app-window[data-id="graph_editor_win"] .cluster text {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .actor {
stroke: #CCCCFF;
fill: #ECECFF;
}
afx-app-window[data-id="graph_editor_win"] text.actor {
fill: black;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .actor-line {
stroke: grey;
}
afx-app-window[data-id="graph_editor_win"] .messageLine0 {
stroke-width: 1.5;
stroke-dasharray: "2 2";
marker-end: "url(#arrowhead)";
stroke: #333;
}
afx-app-window[data-id="graph_editor_win"] .messageLine1 {
stroke-width: 1.5;
stroke-dasharray: "2 2";
stroke: #333;
}
afx-app-window[data-id="graph_editor_win"] #arrowhead {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] #crosshead path {
fill: #333 !important;
stroke: #333 !important;
}
afx-app-window[data-id="graph_editor_win"] .messageText {
fill: #333;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .labelBox {
stroke: #CCCCFF;
fill: #ECECFF;
}
afx-app-window[data-id="graph_editor_win"] .labelText {
fill: black;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .loopText {
fill: black;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .loopLine {
stroke-width: 2;
stroke-dasharray: "2 2";
marker-end: "url(#arrowhead)";
stroke: #CCCCFF;
}
afx-app-window[data-id="graph_editor_win"] .note {
stroke: #aaaa33;
fill: #fff5ad;
}
afx-app-window[data-id="graph_editor_win"] .noteText {
fill: black;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-size: 14px;
}
/** Section styling */
afx-app-window[data-id="graph_editor_win"] .section {
stroke: none;
opacity: 0.2;
}
afx-app-window[data-id="graph_editor_win"] .section0 {
fill: rgba(102, 102, 255, 0.49);
}
afx-app-window[data-id="graph_editor_win"] .section2 {
fill: #fff400;
}
afx-app-window[data-id="graph_editor_win"] .section1,
afx-app-window[data-id="graph_editor_win"] .section3 {
fill: white;
opacity: 0.2;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle0 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle1 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle2 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle3 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle {
text-anchor: start;
font-size: 11px;
text-height: 14px;
}
/* Grid and axis */
afx-app-window[data-id="graph_editor_win"] .grid .tick {
stroke: lightgrey;
opacity: 0.3;
shape-rendering: crispEdges;
}
afx-app-window[data-id="graph_editor_win"] .grid path {
stroke-width: 0;
}
/* Today line */
afx-app-window[data-id="graph_editor_win"] .today {
fill: none;
stroke: red;
stroke-width: 2px;
}
/* Task styling */
/* Default task */
afx-app-window[data-id="graph_editor_win"] .task {
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .taskText {
text-anchor: middle;
font-size: 11px;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutsideRight {
fill: black;
text-anchor: start;
font-size: 11px;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutsideLeft {
fill: black;
text-anchor: end;
font-size: 11px;
}
/* Specific task settings for the sections*/
afx-app-window[data-id="graph_editor_win"] .taskText0,
afx-app-window[data-id="graph_editor_win"] .taskText1,
afx-app-window[data-id="graph_editor_win"] .taskText2,
afx-app-window[data-id="graph_editor_win"] .taskText3 {
fill: white;
}
afx-app-window[data-id="graph_editor_win"] .task0,
afx-app-window[data-id="graph_editor_win"] .task1,
afx-app-window[data-id="graph_editor_win"] .task2,
afx-app-window[data-id="graph_editor_win"] .task3 {
fill: #8a90dd;
stroke: #534fbc;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutside0,
afx-app-window[data-id="graph_editor_win"] .taskTextOutside2 {
fill: black;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutside1,
afx-app-window[data-id="graph_editor_win"] .taskTextOutside3 {
fill: black;
}
/* Active task */
afx-app-window[data-id="graph_editor_win"] .active0,
afx-app-window[data-id="graph_editor_win"] .active1,
afx-app-window[data-id="graph_editor_win"] .active2,
afx-app-window[data-id="graph_editor_win"] .active3 {
fill: #bfc7ff;
stroke: #534fbc;
}
afx-app-window[data-id="graph_editor_win"] .activeText0,
afx-app-window[data-id="graph_editor_win"] .activeText1,
afx-app-window[data-id="graph_editor_win"] .activeText2,
afx-app-window[data-id="graph_editor_win"] .activeText3 {
fill: black !important;
}
/* Completed task */
afx-app-window[data-id="graph_editor_win"] .done0,
afx-app-window[data-id="graph_editor_win"] .done1,
afx-app-window[data-id="graph_editor_win"] .done2,
afx-app-window[data-id="graph_editor_win"] .done3 {
stroke: grey;
fill: lightgrey;
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .doneText0,
afx-app-window[data-id="graph_editor_win"] .doneText1,
afx-app-window[data-id="graph_editor_win"] .doneText2,
afx-app-window[data-id="graph_editor_win"] .doneText3 {
fill: black !important;
}
/* Tasks on the critical line */
afx-app-window[data-id="graph_editor_win"] .crit0,
afx-app-window[data-id="graph_editor_win"] .crit1,
afx-app-window[data-id="graph_editor_win"] .crit2,
afx-app-window[data-id="graph_editor_win"] .crit3 {
stroke: #ff8888;
fill: red;
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .activeCrit0,
afx-app-window[data-id="graph_editor_win"] .activeCrit1,
afx-app-window[data-id="graph_editor_win"] .activeCrit2,
afx-app-window[data-id="graph_editor_win"] .activeCrit3 {
stroke: #ff8888;
fill: #bfc7ff;
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .doneCrit0,
afx-app-window[data-id="graph_editor_win"] .doneCrit1,
afx-app-window[data-id="graph_editor_win"] .doneCrit2,
afx-app-window[data-id="graph_editor_win"] .doneCrit3 {
stroke: #ff8888;
fill: lightgrey;
stroke-width: 2;
cursor: pointer;
shape-rendering: crispEdges;
}
afx-app-window[data-id="graph_editor_win"] .doneCritText0,
afx-app-window[data-id="graph_editor_win"] .doneCritText1,
afx-app-window[data-id="graph_editor_win"] .doneCritText2,
afx-app-window[data-id="graph_editor_win"] .doneCritText3 {
fill: black !important;
}
afx-app-window[data-id="graph_editor_win"] .activeCritText0,
afx-app-window[data-id="graph_editor_win"] .activeCritText1,
afx-app-window[data-id="graph_editor_win"] .activeCritText2,
afx-app-window[data-id="graph_editor_win"] .activeCritText3 {
fill: black !important;
}
afx-app-window[data-id="graph_editor_win"] .titleText {
text-anchor: middle;
font-size: 18px;
fill: black;
}
/*
*/
afx-app-window[data-id="graph_editor_win"] .node text {
font-family: 'trebuchet ms', verdana, arial;
font-size: 14px;
}
afx-app-window[data-id="graph_editor_win"] div.mermaidTooltip {
position: absolute;
text-align: center;
max-width: 200px;
padding: 2px;
font-family: 'trebuchet ms', verdana, arial;
font-size: 12px;
background: #ffffde;
border: 1px solid #aaaa33;
border-radius: 2px;
pointer-events: none;
z-index: 100;
}

View File

@ -0,0 +1,353 @@
/* Flowchart variables */
/* Sequence Diagram variables */
/* Gantt chart variables */
afx-app-window[data-id="graph_editor_win"] .mermaid .label {
font-family: 'trebuchet ms', verdana, arial;
color: #333;
}
afx-app-window[data-id="graph_editor_win"] .node rect,
afx-app-window[data-id="graph_editor_win"] .node circle,
afx-app-window[data-id="graph_editor_win"] .node ellipse,
afx-app-window[data-id="graph_editor_win"] .node polygon {
fill: #cde498;
stroke: #13540c;
stroke-width: 1px;
}
afx-app-window[data-id="graph_editor_win"] .edgePath .path {
stroke: green;
stroke-width: 1.5px;
}
afx-app-window[data-id="graph_editor_win"] .edgeLabel {
background-color: #e8e8e8;
}
afx-app-window[data-id="graph_editor_win"] .cluster rect {
fill: #cdffb2 !important;
rx: 4 !important;
stroke: #6eaa49 !important;
stroke-width: 1px !important;
}
afx-app-window[data-id="graph_editor_win"] .cluster text {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .actor {
stroke: #13540c;
fill: #cde498;
}
afx-app-window[data-id="graph_editor_win"] text.actor {
fill: black;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .actor-line {
stroke: grey;
}
afx-app-window[data-id="graph_editor_win"] .messageLine0 {
stroke-width: 1.5;
stroke-dasharray: "2 2";
marker-end: "url(#arrowhead)";
stroke: #333;
}
afx-app-window[data-id="graph_editor_win"] .messageLine1 {
stroke-width: 1.5;
stroke-dasharray: "2 2";
stroke: #333;
}
afx-app-window[data-id="graph_editor_win"] #arrowhead {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] #crosshead path {
fill: #333 !important;
stroke: #333 !important;
}
afx-app-window[data-id="graph_editor_win"] .messageText {
fill: #333;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .labelBox {
stroke: #326932;
fill: #cde498;
}
afx-app-window[data-id="graph_editor_win"] .labelText {
fill: black;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .loopText {
fill: black;
stroke: none;
}
afx-app-window[data-id="graph_editor_win"] .loopLine {
stroke-width: 2;
stroke-dasharray: "2 2";
marker-end: "url(#arrowhead)";
stroke: #326932;
}
afx-app-window[data-id="graph_editor_win"] .note {
stroke: #6eaa49;
fill: #fff5ad;
}
afx-app-window[data-id="graph_editor_win"] .noteText {
fill: black;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-size: 14px;
}
/** Section styling */
afx-app-window[data-id="graph_editor_win"] .section {
stroke: none;
opacity: 0.2;
}
afx-app-window[data-id="graph_editor_win"] .section0 {
fill: #6eaa49;
}
afx-app-window[data-id="graph_editor_win"] .section2 {
fill: #6eaa49;
}
afx-app-window[data-id="graph_editor_win"] .section1,
afx-app-window[data-id="graph_editor_win"] .section3 {
fill: white;
opacity: 0.2;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle0 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle1 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle2 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle3 {
fill: #333;
}
afx-app-window[data-id="graph_editor_win"] .sectionTitle {
text-anchor: start;
font-size: 11px;
text-height: 14px;
}
/* Grid and axis */
afx-app-window[data-id="graph_editor_win"] .grid .tick {
stroke: lightgrey;
opacity: 0.3;
shape-rendering: crispEdges;
}
afx-app-window[data-id="graph_editor_win"] .grid path {
stroke-width: 0;
}
/* Today line */
afx-app-window[data-id="graph_editor_win"] .today {
fill: none;
stroke: red;
stroke-width: 2px;
}
/* Task styling */
/* Default task */
afx-app-window[data-id="graph_editor_win"] .task {
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .taskText {
text-anchor: middle;
font-size: 11px;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutsideRight {
fill: black;
text-anchor: start;
font-size: 11px;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutsideLeft {
fill: black;
text-anchor: end;
font-size: 11px;
}
/* Specific task settings for the sections*/
afx-app-window[data-id="graph_editor_win"] .taskText0,
afx-app-window[data-id="graph_editor_win"] .taskText1,
afx-app-window[data-id="graph_editor_win"] .taskText2,
afx-app-window[data-id="graph_editor_win"] .taskText3 {
fill: white;
}
afx-app-window[data-id="graph_editor_win"] .task0,
afx-app-window[data-id="graph_editor_win"] .task1,
afx-app-window[data-id="graph_editor_win"] .task2,
afx-app-window[data-id="graph_editor_win"] .task3 {
fill: #487e3a;
stroke: #13540c;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutside0,
afx-app-window[data-id="graph_editor_win"] .taskTextOutside2 {
fill: black;
}
afx-app-window[data-id="graph_editor_win"] .taskTextOutside1,
afx-app-window[data-id="graph_editor_win"] .taskTextOutside3 {
fill: black;
}
/* Active task */
afx-app-window[data-id="graph_editor_win"] .active0,
afx-app-window[data-id="graph_editor_win"] .active1,
afx-app-window[data-id="graph_editor_win"] .active2,
afx-app-window[data-id="graph_editor_win"] .active3 {
fill: #cde498;
stroke: #13540c;
}
afx-app-window[data-id="graph_editor_win"] .activeText0,
afx-app-window[data-id="graph_editor_win"] .activeText1,
afx-app-window[data-id="graph_editor_win"] .activeText2,
afx-app-window[data-id="graph_editor_win"] .activeText3 {
fill: black !important;
}
/* Completed task */
afx-app-window[data-id="graph_editor_win"] .done0,
afx-app-window[data-id="graph_editor_win"] .done1,
afx-app-window[data-id="graph_editor_win"] .done2,
afx-app-window[data-id="graph_editor_win"] .done3 {
stroke: grey;
fill: lightgrey;
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .doneText0,
afx-app-window[data-id="graph_editor_win"] .doneText1,
afx-app-window[data-id="graph_editor_win"] .doneText2,
afx-app-window[data-id="graph_editor_win"] .doneText3 {
fill: black !important;
}
/* Tasks on the critical line */
afx-app-window[data-id="graph_editor_win"] .crit0,
afx-app-window[data-id="graph_editor_win"] .crit1,
afx-app-window[data-id="graph_editor_win"] .crit2,
afx-app-window[data-id="graph_editor_win"] .crit3 {
stroke: #ff8888;
fill: red;
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .activeCrit0,
afx-app-window[data-id="graph_editor_win"] .activeCrit1,
afx-app-window[data-id="graph_editor_win"] .activeCrit2,
afx-app-window[data-id="graph_editor_win"] .activeCrit3 {
stroke: #ff8888;
fill: #cde498;
stroke-width: 2;
}
afx-app-window[data-id="graph_editor_win"] .doneCrit0,
afx-app-window[data-id="graph_editor_win"] .doneCrit1,
afx-app-window[data-id="graph_editor_win"] .doneCrit2,
afx-app-window[data-id="graph_editor_win"] .doneCrit3 {
stroke: #ff8888;
fill: lightgrey;
stroke-width: 2;
cursor: pointer;
shape-rendering: crispEdges;
}
afx-app-window[data-id="graph_editor_win"] .doneCritText0,
afx-app-window[data-id="graph_editor_win"] .doneCritText1,
afx-app-window[data-id="graph_editor_win"] .doneCritText2,
afx-app-window[data-id="graph_editor_win"] .doneCritText3 {
fill: black !important;
}
afx-app-window[data-id="graph_editor_win"] .activeCritText0,
afx-app-window[data-id="graph_editor_win"] .activeCritText1,
afx-app-window[data-id="graph_editor_win"] .activeCritText2,
afx-app-window[data-id="graph_editor_win"] .activeCritText3 {
fill: black !important;
}
afx-app-window[data-id="graph_editor_win"] .titleText {
text-anchor: middle;
font-size: 18px;
fill: black;
}
/*
*/
afx-app-window[data-id="graph_editor_win"] g.classGroup text {
fill: #13540c;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-size: 14px;
}
afx-app-window[data-id="graph_editor_win"] g.classGroup rect {
fill: #cde498;
stroke: #13540c;
}
afx-app-window[data-id="graph_editor_win"] g.classGroup line {
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] svg .classLabel .box {
stroke: none;
stroke-width: 0;
fill: #cde498;
opacity: 0.5;
}
afx-app-window[data-id="graph_editor_win"] svg .classLabel .label {
fill: #13540c;
}
afx-app-window[data-id="graph_editor_win"] .relation {
stroke: #13540c;
stroke-width: 1;
fill: none;
}
afx-app-window[data-id="graph_editor_win"] .composition {
fill: #13540c;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #compositionStart {
fill: #13540c;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #compositionEnd {
fill: #13540c;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] .aggregation {
fill: #cde498;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #aggregationStart {
fill: #cde498;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #aggregationEnd {
fill: #cde498;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #dependencyStart {
fill: #13540c;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #dependencyEnd {
fill: #13540c;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #extensionStart {
fill: #13540c;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] #extensionEnd {
fill: #13540c;
stroke: #13540c;
stroke-width: 1;
}
afx-app-window[data-id="graph_editor_win"] .node text {
font-family: 'trebuchet ms', verdana, arial;
font-size: 14px;
}
afx-app-window[data-id="graph_editor_win"] 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;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,280 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class GraphEditor extends this.OS.GUI.BaseApplication
constructor: ( args ) ->
super "GraphEditor", args
main: () ->
me = @
mermaidAPI.initialize { startOnLoad: false }
@currfile = if @args and @args.length > 0 then @args[0].asFileHandler() else "Untitled".asFileHandler()
@currfile.dirty = false
@datarea = @find "datarea"
@preview = @find "preview"
@renderlist = @find "render-list"
@btctn = @find "btn-container"
@.editor = ace.edit @datarea
@.editor.setOptions {
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
fontSize: "9pt"
}
#@.editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> }
@editor.getSession().setUseWrapMode true
@editor.session.setMode "ace/mode/dot"
@editor.setTheme "ace/theme/monokai"
@editor.on "input", () ->
if me.editormux
me.editormux = false
return false
if not me.currfile.dirty
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
me.editor.setValue text
me.renderSVG false
@editor.container.addEventListener "keydown", (e) ->
me.renderSVG true if e.keyCode is 13
, true
@bindKey "CTRL-R", () -> me.renderSVG false
@bindKey "ALT-G", () -> me.export "SVG"
@bindKey "ALT-P", () -> me.export "PNG"
@bindKey "ALT-N", () -> me.actionFile "#{me.name}-New"
@bindKey "ALT-O", () -> me.actionFile "#{me.name}-Open"
@bindKey "CTRL-S", () -> me.actionFile "#{me.name}-Save"
@bindKey "ALT-W", () -> me.actionFile "#{me.name}-Saveas"
#@bindKey "CTRL-M", () -> me.svgToCanvas(()->)
@on "hboxchange", () ->
me.editor.resize()
me.calibrate()
@on "focus", () -> me.editor.focus()
(@find "btn-zoomin").set "onbtclick", (e) ->
me.pan.zoomIn() if me.pan
(@find "btn-zoomout").set "onbtclick", (e) ->
me.pan.zoomOut() if me.pan
(@find "btn-reset").set "onbtclick", (e) ->
me.pan.resetZoom() if me.pan
@renderlist.set "items", [{ text: "Dot", selected: true }, { text: "Mermaid" } ]
@open @currfile
engine: () ->
sel = @renderlist.get "selected"
sel.text
menu: () ->
me = @
menu = [{
text: "__(File)",
child: [
{ text: "__(New)", dataid: "#{@name}-New", shortcut: "A-N" },
{ text: "__(Open)", dataid: "#{@name}-Open", shortcut: "A-O" },
{ text: "__(Save)", dataid: "#{@name}-Save", shortcut: "C-S" },
{ text: "__(Save as)",dataid: "#{@name}-Saveas" , shortcut: "A-W" },
{ text: "__(Render)", dataid: "#{@name}-Render", shortcut: "C-R" },
{
text: "__(Export as)",
child: [
{ text: "SVG", shortcut: "A-G" },
{ text: "PNG", shortcut: "A-P" }
],
onmenuselect: (e) -> me.export e.item.data.text
},
],
onmenuselect: (e) -> me.actionFile e.item.data.dataid
}]
menu
open: (file) ->
return if file.path is "Untitled"
me = @
file.dirty = false
file.read (d) ->
me.currfile = file
me.editormux = true
me.currfile.dirty = false
me.editor.setValue d
me.scheme.set "apptitle", "#{me.currfile.basename}"
me.renderlist.set "selected", if file.info.mime.match /.*graphviz/ then 0 else 1
#me.renderSVG false
save: (file) ->
me = @
file.write "text/plain", (d) ->
return me.error __("Error saving file {0}", file.basename) if d.error
file.dirty = false
file.text = file.basename
me.scheme.set "apptitle", "#{me.currfile.basename}"
actionFile: (e) ->
me = @
saveas = () ->
me.openDialog "FileDiaLog", (d, n) ->
me.currfile.setPath "#{d}/#{n}"
me.save me.currfile
, __("Save as"), { file: me.currfile }
switch e
when "#{@name}-Open"
@openDialog "FileDiaLog", ( d, f ) ->
me.open "#{d}/#{f}".asFileHandler()
, __("Open file")
when "#{@name}-Save"
@currfile.cache = @editor.getValue()
return @save @currfile if @currfile.basename
saveas()
when "#{@name}-Saveas"
@currfile.cache = @editor.getValue()
saveas()
when "#{@name}-Render"
me.renderSVG false
when "#{@name}-New"
@currfile = "Untitled".asFileHandler()
@currfile.cache = ""
@currfile.dirty = false
@editormux = true
@editor.setValue("")
export: (t) ->
me = @
me.openDialog "FileDiaLog", (d, n) ->
fp = "#{d}/#{n}".asFileHandler()
try
switch t
when "SVG"
fp.cache = me.svgtext()
fp.write "text/plain", (r) ->
return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error
me.notify __("File exported")
when "PNG"
# toDataURL("image/png")
me.svgToCanvas (canvas) ->
fp.cache = canvas.toDataURL "image/png"
console.log fp.cache
fp.write "base64", (r) ->
return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error
me.notify __("File exported")
catch e
me.error __("Cannot export: {0}", e.message)
, __("Export as"), { file: me.currfile }
renderSVG: (silent) ->
svg = undefined
me = @
try
rd = (obj) ->
$(me.preview).children("svg").remove()
$(me.preview).append obj
svg = $(me.preview).children("svg")[0]
$(svg).prepend($ "<defs>")
$($(svg).children("defs")[0]).html "<style type='text/css'>#{GraphEditor.css}</style>"
me.calibrate()
me.pan = svgPanZoom svg, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.1
}
if @engine() is "Dot"
result = Viz @editor.getValue(), { format: "svg", scale: 1 }
rd($.parseHTML result)
else
id = Math.floor(Math.random() * 100000) + 1
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: () ->
svg = $(@preview).children("svg")[0]
serializer = new XMLSerializer()
return serializer.serializeToString(svg)
svgToCanvas: (f) ->
me = @
img = new Image()
svgStr = @svgtext()
img.onload = () ->
canvas = me.find "offscreen"
canvas.width = img.width
canvas.height = img.height
canvas.getContext("2d").drawImage img, 0, 0, img.width, img.height
f(canvas)
img.src = 'data:image/svg+xml;base64,' + window.btoa(svgStr)
calibrate: () ->
svg = ($ @preview).children("svg")[0]
if svg
prs = [$(@preview).width(), $(@preview).height()]
$(svg).attr "width", prs[0] + "px"
$(svg).attr "height", prs[1] + "px"
cleanup: (evt) ->
return unless @currfile.dirty
me = @
evt.preventDefault()
@.openDialog "YesNoDialog", (d) ->
if d
me.currfile.dirty = false
me.quit()
, __("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 = """
graph TD;
A-->B;
A-->C;
B-->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 = [
"ace/ace"
]
this.OS.register "GraphEditor", GraphEditor

View File

@ -1,19 +1,19 @@
afx-app-window[data-id="dot-edit-win"] div[data-id="preview"]
afx-app-window[data-id="graph_editor_win"] div[data-id="preview"]
{
display: flex;
align-items: center;
justify-content: center;
}
afx-app-window[data-id="dot-edit-win"] afx-button button
afx-app-window[data-id="graph_editor_win"] afx-button button
{
border-radius: 0;
padding-top:2px;
padding-bottom: 2px;
}
afx-app-window[data-id="dot-edit-win"] afx-resizer{
afx-app-window[data-id="graph_editor_win"] afx-resizer{
background-color: transparent;
}
afx-app-window[data-id="dot-edit-win"] div[data-id="btn-container"]{
afx-app-window[data-id="graph_editor_win"] div[data-id="btn-container"]{
background-color: transparent;
position: absolute;
bottom:10px;

View File

@ -1,7 +1,7 @@
{
"app":"DotEditor",
"name":"Dot Graph Editor",
"description":"Create graph with dot language",
"app":"GraphEditor",
"name":"Graph Editor",
"description":"Create graph with dot language or mermaid",
"info":{
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com",

View File

@ -0,0 +1,18 @@
<afx-app-window data-id="graph_editor_win" apptitle="__(Graph editor)" width="650" height="500">
<afx-hbox>
<afx-vbox clas="section0">
<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>
<div data-id="preview">
<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-zoomout" iconclass="fa fa-search-minus"></afx-button>
<afx-button data-id="btn-reset" iconclass="fa fa-square-o"></afx-button>
</div>
</div>
<canvas data-id="offscreen" data-width='0' style="display:none;"></canvas>
</afx-hbox>
</afx-app-window>

View File

@ -1,12 +1,12 @@
<afx-app-window apptitle="" width="600" height="400" data-id="notepad">
<afx-hbox>
<afx-vbox data-width = "172" data-id = "sidebar" min-width="172">
<afx-vbox data-width = "155" data-id = "sidebar" min-width="155">
<afx-list-view data-id = "location" dropdown = "true" data-height= "30" width = "150"></afx-list-view>
<afx-file-view data-id = "fileview" view='tree' status = false></afx-file-view>
</afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-vbox>
<afx-tab-bar data-id="tabarea" data-height="26" closable = true></afx-tab-bar>
<afx-tab-bar data-id="tabarea" data-height="23" closable = true></afx-tab-bar>
<div data-id="datarea"></div>
<afx-hbox data-height="30" data-id="bottom-vbox">
<afx-label data-id="editorstat"></afx-label>

View File

@ -1,6 +1,6 @@
afx-list-view{
overflow:hidden;
padding: 5px;
/*padding: 5px;*/
display: block;
}
/*
@ -94,7 +94,7 @@ afx-list-view.dropdown > div.list-container > ul{
border:1px solid #a6a6a6;
box-shadow: 1px 1px 1px #9f9F9F;
border-radius: 3px;
padding:2px;
/*padding:2px;*/
border-top-left-radius: 0px;
z-index: 10;
}
@ -104,13 +104,17 @@ afx-list-view.dropdown > div.list-container > ul > li{
}
afx-list-view.dropdown div.list-container div{
padding:3px;
color: #414339;
padding-top:3px;
padding-bottom: 3px;
border:1px solid #a6a6a6;
border-radius: 3px;
padding-right:15px;
background-color: white;
height: 17px;
}
afx-list-view.dropdown div.list-container div > afx-label{
padding-left:3px;
}
afx-list-view.dropdown div.list-container div:before {
content: "\f107";
font-family: "FontAwesome";