2018-09-29 11:19:26 +02:00
|
|
|
# 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/.
|
2020-06-07 00:51:01 +02:00
|
|
|
class GraphEditor extends this.OS.application.BaseApplication
|
2018-09-29 11:19:26 +02:00
|
|
|
constructor: ( args ) ->
|
|
|
|
super "GraphEditor", args
|
|
|
|
|
|
|
|
main: () ->
|
2020-06-07 00:51:01 +02:00
|
|
|
|
2018-09-29 11:19:26 +02:00
|
|
|
#mermaid.initialize { startOnLoad: false }
|
|
|
|
mermaid.initialize {
|
|
|
|
theme: 'forest'
|
|
|
|
}
|
2020-06-07 00:51:01 +02:00
|
|
|
@currfile = if @args and @args.length > 0 then @args[0].path.asFileHandle() else "Untitled".asFileHandle()
|
2018-09-29 11:19:26 +02:00
|
|
|
@currfile.dirty = false
|
|
|
|
@datarea = @find "datarea"
|
|
|
|
@preview = @find "preview"
|
|
|
|
@btctn = @find "btn-container"
|
2020-11-19 21:18:10 +01:00
|
|
|
ace.config.set("basePath", "#{window.location.pathname}/scripts/ace")
|
2020-06-07 00:51:01 +02:00
|
|
|
@editor = ace.edit @datarea
|
|
|
|
@editor.setOptions {
|
2018-09-29 11:19:26 +02:00
|
|
|
enableBasicAutocompletion: true,
|
|
|
|
enableLiveAutocompletion: true,
|
2020-06-07 00:51:01 +02:00
|
|
|
fontSize: "11pt"
|
2018-09-29 11:19:26 +02:00
|
|
|
}
|
|
|
|
@editor.getSession().setUseWrapMode true
|
|
|
|
@editor.session.setMode "ace/mode/text"
|
|
|
|
@editor.setTheme "ace/theme/monokai"
|
2020-06-07 00:51:01 +02:00
|
|
|
@editor.on "input", () =>
|
|
|
|
if @editormux
|
|
|
|
@editormux = false
|
2018-09-29 11:19:26 +02:00
|
|
|
return false
|
2020-06-07 00:51:01 +02:00
|
|
|
if not @currfile.dirty
|
|
|
|
@currfile.dirty = true
|
2018-09-29 11:19:26 +02:00
|
|
|
|
|
|
|
|
2020-06-07 00:51:01 +02:00
|
|
|
if not @currfile.basename
|
|
|
|
@editormux = true
|
|
|
|
@editor.setValue GraphEditor.dummymermaid
|
|
|
|
@renderSVG false
|
2018-09-29 11:19:26 +02:00
|
|
|
|
|
|
|
|
2020-06-07 00:51:01 +02:00
|
|
|
@editor.container.addEventListener "keydown", (e) =>
|
|
|
|
@renderSVG true if e.keyCode is 13
|
2018-09-29 11:19:26 +02:00
|
|
|
, true
|
|
|
|
|
2020-06-07 00:51:01 +02:00
|
|
|
@bindKey "CTRL-R", () => @renderSVG false
|
|
|
|
@bindKey "ALT-G", () => @export "SVG"
|
|
|
|
@bindKey "ALT-P", () => @export "PNG"
|
|
|
|
@bindKey "ALT-N", () => @actionFile "#{@name}-New"
|
|
|
|
@bindKey "ALT-O", () => @actionFile "#{@name}-Open"
|
|
|
|
@bindKey "CTRL-S", () => @actionFile "#{@name}-Save"
|
|
|
|
@bindKey "ALT-W", () => @actionFile "#{@name}-Saveas"
|
|
|
|
@bindKey "CTRL-M", () => @svgToCanvas(()->)
|
2018-09-29 11:19:26 +02:00
|
|
|
|
2020-06-07 00:51:01 +02:00
|
|
|
@on "hboxchange", () =>
|
|
|
|
@editor.resize()
|
|
|
|
@calibrate()
|
|
|
|
@on "focus", () => @editor.focus()
|
|
|
|
(@find "btn-zoomin").onbtclick = (e) =>
|
|
|
|
@pan.zoomIn() if @pan
|
|
|
|
(@find "btn-zoomout").onbtclick = (e) =>
|
|
|
|
@pan.zoomOut() if @pan
|
|
|
|
(@find "btn-reset").onbtclick = (e) =>
|
|
|
|
@pan.resetZoom() if @pan
|
2018-09-29 11:19:26 +02:00
|
|
|
|
|
|
|
@open @currfile
|
|
|
|
|
|
|
|
menu: () ->
|
2020-06-07 00:51:01 +02:00
|
|
|
|
2018-09-29 11:19:26 +02:00
|
|
|
menu = [{
|
|
|
|
text: "__(File)",
|
2020-06-07 00:51:01 +02:00
|
|
|
nodes: [
|
2018-09-29 11:19:26 +02:00
|
|
|
{ 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)",
|
2020-06-07 00:51:01 +02:00
|
|
|
nodes: [
|
2018-09-29 11:19:26 +02:00
|
|
|
{ text: "SVG", shortcut: "A-G" },
|
|
|
|
{ text: "PNG", shortcut: "A-P" }
|
|
|
|
],
|
2020-06-07 00:51:01 +02:00
|
|
|
onchildselect: (e) => @export e.data.item.data.text
|
2018-09-29 11:19:26 +02:00
|
|
|
},
|
|
|
|
],
|
2020-06-07 00:51:01 +02:00
|
|
|
onchildselect: (e) => @actionFile e.data.item.data.dataid
|
2018-09-29 11:19:26 +02:00
|
|
|
}]
|
|
|
|
menu
|
|
|
|
open: (file) ->
|
|
|
|
return if file.path is "Untitled"
|
2020-06-07 00:51:01 +02:00
|
|
|
|
2018-09-29 11:19:26 +02:00
|
|
|
file.dirty = false
|
2020-06-07 00:51:01 +02:00
|
|
|
file.read().then (d) =>
|
|
|
|
@currfile = file
|
|
|
|
@editormux = true
|
|
|
|
@currfile.dirty = false
|
|
|
|
@editor.setValue d
|
|
|
|
@scheme.apptitle = "#{@currfile.basename}"
|
|
|
|
@renderSVG false
|
|
|
|
.catch (e) => @error e.toString(), e
|
2018-09-29 11:19:26 +02:00
|
|
|
save: (file) ->
|
2020-06-07 00:51:01 +02:00
|
|
|
|
|
|
|
file.write "text/plain"
|
|
|
|
.then (d) =>
|
2018-09-29 11:19:26 +02:00
|
|
|
file.dirty = false
|
|
|
|
file.text = file.basename
|
2020-06-07 00:51:01 +02:00
|
|
|
@scheme.apptitle = "#{@currfile.basename}"
|
|
|
|
.catch (e) => @error e.toString(), e
|
2018-09-29 11:19:26 +02:00
|
|
|
|
|
|
|
actionFile: (e) ->
|
2020-06-07 00:51:01 +02:00
|
|
|
|
|
|
|
saveas = () =>
|
|
|
|
@openDialog "FileDialog",{title: __("Save as"), file: @currfile }
|
|
|
|
.then (d) =>
|
|
|
|
@currfile.setPath "#{d.file.path}/#{d.name}"
|
|
|
|
@save @currfile
|
|
|
|
.catch (e) => @error e.toString(), e
|
|
|
|
|
2018-09-29 11:19:26 +02:00
|
|
|
switch e
|
|
|
|
when "#{@name}-Open"
|
2020-06-07 00:51:01 +02:00
|
|
|
@openDialog "FileDialog", { title: __("Open file")}
|
|
|
|
.then ( d, f ) =>
|
|
|
|
@open d.file.path.asFileHandle()
|
|
|
|
.catch (e) => @error e.toString(), e
|
|
|
|
|
2018-09-29 11:19:26 +02:00
|
|
|
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"
|
2020-06-07 00:51:01 +02:00
|
|
|
@renderSVG false
|
2018-09-29 11:19:26 +02:00
|
|
|
when "#{@name}-New"
|
2020-06-07 00:51:01 +02:00
|
|
|
@currfile = "Untitled".asFileHandle()
|
2018-09-29 11:19:26 +02:00
|
|
|
@currfile.cache = ""
|
|
|
|
@currfile.dirty = false
|
|
|
|
@editormux = true
|
|
|
|
@editor.setValue("")
|
|
|
|
|
|
|
|
export: (t) ->
|
2020-06-07 00:51:01 +02:00
|
|
|
@openDialog "FileDialog", {title: __("Export as"), file: @currfile }
|
|
|
|
.then (d) =>
|
|
|
|
fp = "#{d.file.path}/#{d.name}".asFileHandle()
|
2018-09-29 11:19:26 +02:00
|
|
|
try
|
|
|
|
switch t
|
|
|
|
when "SVG"
|
2020-06-07 00:51:01 +02:00
|
|
|
fp.cache = @svgtext()
|
|
|
|
fp.write "text/plain"
|
|
|
|
.then (r) =>
|
|
|
|
@notify __("File exported")
|
|
|
|
.catch (e) => @error __("Cannot export to {0}: {1}", t, e.toString()), e
|
2018-09-29 11:19:26 +02:00
|
|
|
when "PNG"
|
|
|
|
# toDataURL("image/png")
|
2020-06-07 00:51:01 +02:00
|
|
|
@svgToCanvas (canvas) =>
|
2018-09-29 11:19:26 +02:00
|
|
|
try
|
|
|
|
fp.cache = canvas.toDataURL "image/png"
|
2020-06-07 00:51:01 +02:00
|
|
|
fp.write "base64"
|
|
|
|
.then (r) =>
|
|
|
|
@notify __("File exported")
|
|
|
|
.catch (e) => @error __("Cannot export to {0}: {1}", t, e.toString()), e
|
2018-09-29 11:19:26 +02:00
|
|
|
catch e
|
2020-06-07 00:51:01 +02:00
|
|
|
@error __("Cannot export to PNG in this browser: {0}", e.toString()), e
|
2018-09-29 11:19:26 +02:00
|
|
|
catch e
|
2020-06-07 00:51:01 +02:00
|
|
|
@error __("Cannot export: {0}", e.message), e
|
|
|
|
.catch (e) => @error e.toString(), e
|
2018-09-29 11:19:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
renderSVG: (silent) ->
|
2020-06-07 00:51:01 +02:00
|
|
|
|
2018-09-29 11:19:26 +02:00
|
|
|
id = Math.floor(Math.random() * 100000) + 1
|
|
|
|
#if silent
|
|
|
|
# mermaid.parseError = (e, h) ->
|
|
|
|
#else
|
|
|
|
# mermaid.parseError = (e, h) ->
|
2020-06-07 00:51:01 +02:00
|
|
|
# @error e
|
2018-09-29 11:19:26 +02:00
|
|
|
text = @editor.getValue()
|
|
|
|
try
|
|
|
|
mermaid.parse text
|
|
|
|
catch e
|
2020-06-07 00:51:01 +02:00
|
|
|
@error __("Syntax error: {0}", e.toString()), e if not silent
|
2018-09-29 11:19:26 +02:00
|
|
|
return
|
2020-06-07 00:51:01 +02:00
|
|
|
mermaid.render "c#{id}", text, (text, f) =>
|
|
|
|
@preview.innerHTML = text
|
|
|
|
$(@preview).append @btctn
|
|
|
|
@calibrate()
|
|
|
|
svg = $(@preview).children("svg")[0]
|
|
|
|
#$(svg).attr("style", "")
|
|
|
|
@pan = svgPanZoom svg, {
|
2018-09-29 11:19:26 +02:00
|
|
|
zoomEnabled: true,
|
|
|
|
controlIconsEnabled: false,
|
|
|
|
fit: true,
|
|
|
|
center: true,
|
|
|
|
minZoom: 0.1
|
|
|
|
}
|
|
|
|
#rd $($.parseHTML text).
|
2020-06-07 00:51:01 +02:00
|
|
|
, @preview
|
2018-09-29 11:19:26 +02:00
|
|
|
|
|
|
|
svgtext: () ->
|
|
|
|
svg = $(@preview).children("svg")[0]
|
2020-06-07 00:51:01 +02:00
|
|
|
$("g.label",svg).each (i) =>
|
2018-09-29 11:19:26 +02:00
|
|
|
$(@)
|
|
|
|
.css("font-family","Ubuntu")
|
|
|
|
.css("font-size", "13px")
|
2020-06-07 00:51:01 +02:00
|
|
|
$("text", svg).each (j) =>
|
2018-09-29 11:19:26 +02:00
|
|
|
$(@)
|
|
|
|
.css("font-family","Ubuntu")
|
|
|
|
.css("font-size", "13px")
|
|
|
|
serializer = new XMLSerializer()
|
|
|
|
return serializer.serializeToString(svg)
|
|
|
|
|
|
|
|
svgToCanvas: (f) ->
|
|
|
|
img = new Image()
|
2020-10-02 16:44:19 +02:00
|
|
|
img.crossOrigin="anonymous"
|
2018-09-29 11:19:26 +02:00
|
|
|
svgStr = @svgtext()
|
|
|
|
DOMURL = window.URL || window.webkitURL || window
|
|
|
|
svgBlob = new Blob [svgStr], { type: 'image/svg+xml;charset=utf-8' }
|
|
|
|
url = DOMURL.createObjectURL svgBlob
|
2020-06-07 00:51:01 +02:00
|
|
|
img.onload = () =>
|
|
|
|
canvas = @find "offscreen"
|
2018-09-29 11:19:26 +02:00
|
|
|
canvas.width = img.width
|
|
|
|
canvas.height = img.height
|
|
|
|
canvas.getContext("2d").drawImage img, 0, 0, img.width, img.height
|
|
|
|
f(canvas)
|
|
|
|
img.src = url
|
|
|
|
|
|
|
|
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
|
|
|
|
return unless @currfile.dirty
|
|
|
|
evt.preventDefault()
|
2020-06-07 00:51:01 +02:00
|
|
|
@ask {title: __("Quit"), text: __("Quit without saving ?") }
|
|
|
|
.then (d) =>
|
|
|
|
if d
|
|
|
|
@currfile.dirty = false
|
|
|
|
@quit()
|
2018-09-29 11:19:26 +02:00
|
|
|
|
|
|
|
GraphEditor.dummymermaid = """
|
2020-06-07 00:51:01 +02:00
|
|
|
classDiagram
|
|
|
|
Class01 <|-- AveryLongClass : Cool
|
|
|
|
Class03 *-- Class04
|
|
|
|
Class05 o-- Class06
|
|
|
|
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
|
2018-09-29 11:19:26 +02:00
|
|
|
"""
|
|
|
|
GraphEditor.dependencies = [
|
2020-05-23 20:09:01 +02:00
|
|
|
"os://scripts/ace/ace.js"
|
2018-09-29 11:19:26 +02:00
|
|
|
]
|
|
|
|
this.OS.register "GraphEditor", GraphEditor
|