graph editor

This commit is contained in:
lxsang 2020-06-07 00:51:01 +02:00
parent 073e48bcf7
commit b5a0e41886
22 changed files with 212 additions and 2512 deletions

View File

@ -1,24 +0,0 @@
# DrawIOWrapper
This is an example project, generated by AntOS Development Kit
## Howto
1. Open the project.apj file with AntOSDK (simply double Click on it)
2. Modify the UI in *assets/scheme.html*
3. Modify application code in *coffees/main.coffee*
4. Modify CSS style in *css/main.css*
5. Other files need to be copied: put in to assets
## Set up build target
Click **Menu> Build > Build Option** or simply hit **ALT-Y**
In the build options dialog, add or remove files that need to be
included into the build
Click **Save**
## Build application
* To build: **Menu > Build > Build** or **ALT-C**
* To build and run: **Menu > Build > Build and Run** or **CTRL-R**
* To release: **Menu > Build > Build release** or **ALT-P**

View File

@ -1,5 +0,0 @@
<afx-app-window apptitle="DrawIOWrapper" width="600" height="500" data-id="DrawIOWrapper">
<afx-hbox >
<iframe src="https://www.draw.io" ></iframe>
</afx-hbox>
</afx-app-window>

View File

@ -1,24 +0,0 @@
# DrawIOWrapper
This is an example project, generated by AntOS Development Kit
## Howto
1. Open the project.apj file with AntOSDK (simply double Click on it)
2. Modify the UI in *assets/scheme.html*
3. Modify application code in *coffees/main.coffee*
4. Modify CSS style in *css/main.css*
5. Other files need to be copied: put in to assets
## Set up build target
Click **Menu> Build > Build Option** or simply hit **ALT-Y**
In the build options dialog, add or remove files that need to be
included into the build
Click **Save**
## Build application
* To build: **Menu > Build > Build** or **ALT-C**
* To build and run: **Menu > Build > Build and Run** or **CTRL-R**
* To release: **Menu > Build > Build release** or **ALT-P**

View File

@ -1,15 +0,0 @@
(function() {
var DrawIOWrapper;
DrawIOWrapper = class DrawIOWrapper extends this.OS.GUI.BaseApplication {
constructor(args) {
super("DrawIOWrapper", args);
}
main() {}
};
this.OS.register("DrawIOWrapper", DrawIOWrapper);
}).call(this);

View File

@ -1,13 +0,0 @@
{
"app":"DrawIOWrapper",
"name":"DrawIOWrapper",
"description":"",
"info":{
"author": "",
"email": ""
},
"version":"0.0.1-a",
"category":"Other",
"iconclass":"fa fa-adn",
"mimes":["none"]
}

View File

@ -1,5 +0,0 @@
<afx-app-window apptitle="DrawIOWrapper" width="600" height="500" data-id="DrawIOWrapper">
<afx-hbox >
<iframe src="https://www.draw.io" ></iframe>
</afx-hbox>
</afx-app-window>

View File

@ -1,7 +0,0 @@
class DrawIOWrapper extends this.OS.GUI.BaseApplication
constructor: ( args ) ->
super "DrawIOWrapper", args
main: () ->
this.OS.register "DrawIOWrapper", DrawIOWrapper

View File

@ -1,13 +0,0 @@
{
"app":"DrawIOWrapper",
"name":"DrawIOWrapper",
"description":"",
"info":{
"author": "",
"email": ""
},
"version":"0.0.1-a",
"category":"Other",
"iconclass":"fa fa-adn",
"mimes":["none"]
}

View File

@ -1,8 +0,0 @@
{
"name": "DrawIOWrapper",
"root": "home://workspace/DrawIOWrapper",
"css": [],
"javascripts": [],
"coffees": ["coffees/main.coffee"],
"copies": ["assets/scheme.html", "package.json", "README.md"]
}

View File

@ -9,6 +9,6 @@
<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:block;" ></canvas>
</afx-hbox> </afx-hbox>
</afx-app-window> </afx-app-window>

View File

@ -4,6 +4,7 @@ afx-app-window[data-id="graph_editor_win"] div[data-id="preview"]
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: white;
} }
afx-app-window[data-id="graph_editor_win"] afx-button button afx-app-window[data-id="graph_editor_win"] afx-button button
{ {

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@
"email": "xsang.le@gmail.com", "email": "xsang.le@gmail.com",
"licences": "GPLv3" "licences": "GPLv3"
}, },
"version":"0.0.4-a", "version":"0.0.5-a",
"category":"Office", "category":"Office",
"iconclass": "fa fa-sitemap", "iconclass": "fa fa-sitemap",
"mimes":["text/.*graphviz"] "mimes":["text/.*graphviz"]

View File

@ -9,6 +9,6 @@
<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:block;" ></canvas>
</afx-hbox> </afx-hbox>
</afx-app-window> </afx-app-window>

Binary file not shown.

View File

@ -15,76 +15,76 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/. #along with this program. If not, see https://www.gnu.org/licenses/.
class GraphEditor extends this.OS.GUI.BaseApplication class GraphEditor extends this.OS.application.BaseApplication
constructor: ( args ) -> constructor: ( args ) ->
super "GraphEditor", args super "GraphEditor", args
main: () -> main: () ->
me = @
#mermaid.initialize { startOnLoad: false } #mermaid.initialize { startOnLoad: false }
mermaid.initialize { mermaid.initialize {
theme: 'forest' 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].path.asFileHandle() else "Untitled".asFileHandle()
@currfile.dirty = false @currfile.dirty = false
@datarea = @find "datarea" @datarea = @find "datarea"
@preview = @find "preview" @preview = @find "preview"
@btctn = @find "btn-container" @btctn = @find "btn-container"
@.editor = ace.edit @datarea ace.config.set("basePath", "/scripts/ace")
@.editor.setOptions { @editor = ace.edit @datarea
@editor.setOptions {
enableBasicAutocompletion: true, enableBasicAutocompletion: true,
enableLiveAutocompletion: true, enableLiveAutocompletion: true,
fontSize: "10pt" fontSize: "11pt"
} }
#@.editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> }
@editor.getSession().setUseWrapMode true @editor.getSession().setUseWrapMode true
@editor.session.setMode "ace/mode/text" @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 @editormux
me.editormux = false @editormux = false
return false return false
if not me.currfile.dirty if not @currfile.dirty
me.currfile.dirty = true @currfile.dirty = true
if not me.currfile.basename if not @currfile.basename
me.editormux = true @editormux = true
me.editor.setValue GraphEditor.dummymermaid @editor.setValue GraphEditor.dummymermaid
me.renderSVG false @renderSVG false
@editor.container.addEventListener "keydown", (e) -> @editor.container.addEventListener "keydown", (e) =>
me.renderSVG true if e.keyCode is 13 @renderSVG true if e.keyCode is 13
, true , true
@bindKey "CTRL-R", () -> me.renderSVG false @bindKey "CTRL-R", () => @renderSVG false
@bindKey "ALT-G", () -> me.export "SVG" @bindKey "ALT-G", () => @export "SVG"
@bindKey "ALT-P", () -> me.export "PNG" @bindKey "ALT-P", () => @export "PNG"
@bindKey "ALT-N", () -> me.actionFile "#{me.name}-New" @bindKey "ALT-N", () => @actionFile "#{@name}-New"
@bindKey "ALT-O", () -> me.actionFile "#{me.name}-Open" @bindKey "ALT-O", () => @actionFile "#{@name}-Open"
@bindKey "CTRL-S", () -> me.actionFile "#{me.name}-Save" @bindKey "CTRL-S", () => @actionFile "#{@name}-Save"
@bindKey "ALT-W", () -> me.actionFile "#{me.name}-Saveas" @bindKey "ALT-W", () => @actionFile "#{@name}-Saveas"
@bindKey "CTRL-M", () -> me.svgToCanvas(()->) @bindKey "CTRL-M", () => @svgToCanvas(()->)
@on "hboxchange", () -> @on "hboxchange", () =>
me.editor.resize() @editor.resize()
me.calibrate() @calibrate()
@on "focus", () -> me.editor.focus() @on "focus", () => @editor.focus()
(@find "btn-zoomin").set "onbtclick", (e) -> (@find "btn-zoomin").onbtclick = (e) =>
me.pan.zoomIn() if me.pan @pan.zoomIn() if @pan
(@find "btn-zoomout").set "onbtclick", (e) -> (@find "btn-zoomout").onbtclick = (e) =>
me.pan.zoomOut() if me.pan @pan.zoomOut() if @pan
(@find "btn-reset").set "onbtclick", (e) -> (@find "btn-reset").onbtclick = (e) =>
me.pan.resetZoom() if me.pan @pan.resetZoom() if @pan
@open @currfile @open @currfile
menu: () -> menu: () ->
me = @
menu = [{ menu = [{
text: "__(File)", text: "__(File)",
child: [ nodes: [
{ text: "__(New)", dataid: "#{@name}-New", shortcut: "A-N" }, { text: "__(New)", dataid: "#{@name}-New", shortcut: "A-N" },
{ text: "__(Open)", dataid: "#{@name}-Open", shortcut: "A-O" }, { text: "__(Open)", dataid: "#{@name}-Open", shortcut: "A-O" },
{ text: "__(Save)", dataid: "#{@name}-Save", shortcut: "C-S" }, { text: "__(Save)", dataid: "#{@name}-Save", shortcut: "C-S" },
@ -92,47 +92,53 @@ class GraphEditor extends this.OS.GUI.BaseApplication
{ text: "__(Render)", dataid: "#{@name}-Render", shortcut: "C-R" }, { text: "__(Render)", dataid: "#{@name}-Render", shortcut: "C-R" },
{ {
text: "__(Export as)", text: "__(Export as)",
child: [ nodes: [
{ text: "SVG", shortcut: "A-G" }, { text: "SVG", shortcut: "A-G" },
{ text: "PNG", shortcut: "A-P" } { text: "PNG", shortcut: "A-P" }
], ],
onmenuselect: (e) -> me.export e.item.data.text onchildselect: (e) => @export e.data.item.data.text
}, },
], ],
onmenuselect: (e) -> me.actionFile e.item.data.dataid onchildselect: (e) => @actionFile e.data.item.data.dataid
}] }]
menu menu
open: (file) -> open: (file) ->
return if file.path is "Untitled" return if file.path is "Untitled"
me = @
file.dirty = false file.dirty = false
file.read (d) -> file.read().then (d) =>
me.currfile = file @currfile = file
me.editormux = true @editormux = true
me.currfile.dirty = false @currfile.dirty = false
me.editor.setValue d @editor.setValue d
me.scheme.set "apptitle", "#{me.currfile.basename}" @scheme.apptitle = "#{@currfile.basename}"
me.renderSVG false @renderSVG false
.catch (e) => @error e.toString(), e
save: (file) -> save: (file) ->
me = @
file.write "text/plain", (d) -> file.write "text/plain"
return me.error __("Error saving file {0}", file.basename) if d.error .then (d) =>
file.dirty = false file.dirty = false
file.text = file.basename file.text = file.basename
me.scheme.set "apptitle", "#{me.currfile.basename}" @scheme.apptitle = "#{@currfile.basename}"
.catch (e) => @error e.toString(), e
actionFile: (e) -> actionFile: (e) ->
me = @
saveas = () -> saveas = () =>
me.openDialog "FileDiaLog", (d, n) -> @openDialog "FileDialog",{title: __("Save as"), file: @currfile }
me.currfile.setPath "#{d}/#{n}" .then (d) =>
me.save me.currfile @currfile.setPath "#{d.file.path}/#{d.name}"
, __("Save as"), { file: me.currfile } @save @currfile
.catch (e) => @error e.toString(), e
switch e switch e
when "#{@name}-Open" when "#{@name}-Open"
@openDialog "FileDiaLog", ( d, f ) -> @openDialog "FileDialog", { title: __("Open file")}
me.open "#{d}/#{f}".asFileHandler() .then ( d, f ) =>
, __("Open file") @open d.file.path.asFileHandle()
.catch (e) => @error e.toString(), e
when "#{@name}-Save" when "#{@name}-Save"
@currfile.cache = @editor.getValue() @currfile.cache = @editor.getValue()
return @save @currfile if @currfile.basename return @save @currfile if @currfile.basename
@ -141,61 +147,63 @@ class GraphEditor extends this.OS.GUI.BaseApplication
@currfile.cache = @editor.getValue() @currfile.cache = @editor.getValue()
saveas() saveas()
when "#{@name}-Render" when "#{@name}-Render"
me.renderSVG false @renderSVG false
when "#{@name}-New" when "#{@name}-New"
@currfile = "Untitled".asFileHandler() @currfile = "Untitled".asFileHandle()
@currfile.cache = "" @currfile.cache = ""
@currfile.dirty = false @currfile.dirty = false
@editormux = true @editormux = true
@editor.setValue("") @editor.setValue("")
export: (t) -> export: (t) ->
me = @ @openDialog "FileDialog", {title: __("Export as"), file: @currfile }
me.openDialog "FileDiaLog", (d, n) -> .then (d) =>
fp = "#{d}/#{n}".asFileHandler() fp = "#{d.file.path}/#{d.name}".asFileHandle()
try try
switch t switch t
when "SVG" when "SVG"
fp.cache = me.svgtext() fp.cache = @svgtext()
fp.write "text/plain", (r) -> fp.write "text/plain"
return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error .then (r) =>
me.notify __("File exported") @notify __("File exported")
.catch (e) => @error __("Cannot export to {0}: {1}", t, e.toString()), e
when "PNG" when "PNG"
# toDataURL("image/png") # toDataURL("image/png")
me.svgToCanvas (canvas) -> @svgToCanvas (canvas) =>
try try
fp.cache = canvas.toDataURL "image/png" fp.cache = canvas.toDataURL "image/png"
fp.write "base64", (r) -> fp.write "base64"
return me.error __("Cannot export to {0}: {1}", t, r.error) if r.error .then (r) =>
me.notify __("File exported") @notify __("File exported")
.catch (e) => @error __("Cannot export to {0}: {1}", t, e.toString()), e
catch e catch e
me.error __("Cannot export to PNG in this browser: {0}", e.message) @error __("Cannot export to PNG in this browser: {0}", e.toString()), e
catch e catch e
me.error __("Cannot export: {0}", e.message) @error __("Cannot export: {0}", e.message), e
, __("Export as"), { file: me.currfile } .catch (e) => @error e.toString(), e
renderSVG: (silent) -> renderSVG: (silent) ->
me = @
id = Math.floor(Math.random() * 100000) + 1 id = Math.floor(Math.random() * 100000) + 1
#if silent #if silent
# mermaid.parseError = (e, h) -> # mermaid.parseError = (e, h) ->
#else #else
# mermaid.parseError = (e, h) -> # mermaid.parseError = (e, h) ->
# me.error e # @error e
text = @editor.getValue() text = @editor.getValue()
try try
mermaid.parse text mermaid.parse text
catch e catch e
me.error __("Syntax error: {0}", e.str) if not silent @error __("Syntax error: {0}", e.toString()), e if not silent
return return
mermaid.render "c#{id}", text, (text, f) -> mermaid.render "c#{id}", text, (text, f) =>
me.preview.innerHTML = text @preview.innerHTML = text
$(me.preview).append me.btctn $(@preview).append @btctn
me.calibrate() @calibrate()
svg = $(me.preview).children("svg")[0] svg = $(@preview).children("svg")[0]
$(svg).attr("style", "") #$(svg).attr("style", "")
me.pan = svgPanZoom svg, { @pan = svgPanZoom svg, {
zoomEnabled: true, zoomEnabled: true,
controlIconsEnabled: false, controlIconsEnabled: false,
fit: true, fit: true,
@ -203,15 +211,15 @@ class GraphEditor extends this.OS.GUI.BaseApplication
minZoom: 0.1 minZoom: 0.1
} }
#rd $($.parseHTML text). #rd $($.parseHTML text).
, me.preview , @preview
svgtext: () -> svgtext: () ->
svg = $(@preview).children("svg")[0] svg = $(@preview).children("svg")[0]
$("g.label",svg).each (i) -> $("g.label",svg).each (i) =>
$(@) $(@)
.css("font-family","Ubuntu") .css("font-family","Ubuntu")
.css("font-size", "13px") .css("font-size", "13px")
$("text", svg).each (j) -> $("text", svg).each (j) =>
$(@) $(@)
.css("font-family","Ubuntu") .css("font-family","Ubuntu")
.css("font-size", "13px") .css("font-size", "13px")
@ -219,14 +227,13 @@ class GraphEditor extends this.OS.GUI.BaseApplication
return serializer.serializeToString(svg) return serializer.serializeToString(svg)
svgToCanvas: (f) -> svgToCanvas: (f) ->
me = @
img = new Image() img = new Image()
svgStr = @svgtext() svgStr = @svgtext()
DOMURL = window.URL || window.webkitURL || window DOMURL = window.URL || window.webkitURL || window
svgBlob = new Blob [svgStr], { type: 'image/svg+xml;charset=utf-8' } svgBlob = new Blob [svgStr], { type: 'image/svg+xml;charset=utf-8' }
url = DOMURL.createObjectURL svgBlob url = DOMURL.createObjectURL svgBlob
img.onload = () -> img.onload = () =>
canvas = me.find "offscreen" canvas = @find "offscreen"
canvas.width = img.width canvas.width = img.width
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
@ -243,20 +250,28 @@ class GraphEditor extends this.OS.GUI.BaseApplication
cleanup: (evt) -> cleanup: (evt) ->
return unless @currfile return unless @currfile
return unless @currfile.dirty return unless @currfile.dirty
me = @
evt.preventDefault() evt.preventDefault()
@.openDialog "YesNoDialog", (d) -> @ask {title: __("Quit"), text: __("Quit without saving ?") }
.then (d) =>
if d if d
me.currfile.dirty = false @currfile.dirty = false
me.quit() @quit()
, __("Quit"), { text: __("Quit without saving ?") }
GraphEditor.dummymermaid = """ GraphEditor.dummymermaid = """
graph TD; classDiagram
A-->B; Class01 <|-- AveryLongClass : Cool
A-->C; Class03 *-- Class04
B-->D; Class05 o-- Class06
C-->D; Class07 .. Class08
Class09 --> C2 : Where am i?
Class09 --* C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 <--> C2: Cool label
""" """
GraphEditor.dependencies = [ GraphEditor.dependencies = [
"os://scripts/ace/ace.js" "os://scripts/ace/ace.js"

View File

@ -3,6 +3,7 @@ afx-app-window[data-id="graph_editor_win"] div[data-id="preview"]
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: white;
} }
afx-app-window[data-id="graph_editor_win"] afx-button button afx-app-window[data-id="graph_editor_win"] afx-button button
{ {

42
GraphEditor/javascripts/mermaid.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@
"email": "xsang.le@gmail.com", "email": "xsang.le@gmail.com",
"licences": "GPLv3" "licences": "GPLv3"
}, },
"version":"0.0.4-a", "version":"0.0.5-a",
"category":"Office", "category":"Office",
"iconclass": "fa fa-sitemap", "iconclass": "fa fa-sitemap",
"mimes":["text/.*graphviz"] "mimes":["text/.*graphviz"]

View File

@ -2,7 +2,7 @@
"name": "GraphEditor", "name": "GraphEditor",
"root": "home://workspace/antosdk-apps/GraphEditor", "root": "home://workspace/antosdk-apps/GraphEditor",
"css": ["css/main.css"], "css": ["css/main.css"],
"javascripts": ["javascripts/svg-pan-zoom.js", "javascripts/mermaidAPI.min.js"], "javascripts": ["javascripts/svg-pan-zoom.js", "javascripts/mermaid.min.js"],
"coffees": ["coffees/main.coffee"], "coffees": ["coffees/main.coffee"],
"copies": ["assets/scheme.html", "package.json", "README.md"] "copies": ["assets/scheme.html", "package.json", "README.md"]
} }

View File

@ -44,6 +44,15 @@
"version": "0.0.2-a", "version": "0.0.2-a",
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/Booklet/build/release/Booklet.zip" "download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/Booklet/build/release/Booklet.zip"
}, },
{
"pkgname": "GraphEditor",
"name": "Graph Editor",
"description": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/GraphEditor/README.md",
"category": "Office",
"author": "Xuan Sang LE",
"version": "0.0.5-a",
"download": "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/GraphEditor/build/release/GraphEditor.zip"
},
{ {
"pkgname": "LuaPlayground", "pkgname": "LuaPlayground",
"name": "LuaPlayground", "name": "LuaPlayground",