Skip to content

Commit

Permalink
Merge pull request #1 from ZenUml/zenuml-viewer
Browse files Browse the repository at this point in the history
Zenuml viewer
  • Loading branch information
MrCoder authored Sep 16, 2022
2 parents cc30623 + aabf0fd commit d51e34c
Show file tree
Hide file tree
Showing 16 changed files with 174 additions and 444 deletions.
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# Diagrams.net Integration for IntelliJ
# ZenUML Universal for JetBrains

[![Build Status (GitHub Workflow Build)](https://github.com/docToolchain/diragrams.net-intellij-plugin/workflows/Build/badge.svg?branch=main)](https://github.com/docToolchain/diragrams.net-intellij-plugin/actions?query=workflow%3ABuild+branch%3Amain)
[![JetBrains Plugins](https://img.shields.io/jetbrains/plugin/v/15635-diagrams-net-integration.svg)](https://plugins.jetbrains.com/plugin/15635-diagrams-net-integration)
[![Downloads](https://img.shields.io/jetbrains/plugin/d/15635-diagrams-net-integration.svg)](https://plugins.jetbrains.com/plugin/15635-diagrams-net-integration)

<!-- Plugin description -->
This unofficial extension integrates [diagrams.net](https://app.diagrams.net/) (formerly known as draw.io) directly into IntelliJ.
It supports diagram files with the extensions `.drawio.(svg|png|xml)` and `.dio.(svg|png|xml)`.
It also auto-detects editable PNGs and SVGs created with diagrams.net.
This official extension integrates [ZenUML](https://ZenUML.com/) directly into all JetBrains IDEs.
It supports ZenUML diagram files with the extensions `.z`, `.zen` and `.zenuml`.

The editor uses an offline version of diagrams.net by default, therefore it works without an internet connection and content stays local in your IDE.
The editor uses an offline version of ZenUML renderer, therefore it works without an internet connection and content stays local in your IDE.
<!-- Plugin description end -->

## About
Expand Down
3 changes: 0 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,9 @@ repositories {
}
dependencies {
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.21.0")
// mandatory dependencies for using Spock
testImplementation ("org.codehaus.groovy:groovy-all:3.0.12")
testImplementation ("org.spockframework:spock-core:2.2-groovy-4.0") {
exclude("org.codehaus.groovy", "groovy-xml")
}

}
// Configure gradle-intellij-plugin plugin.
// Read more: https://github.com/JetBrains/gradle-intellij-plugin
Expand Down
23 changes: 7 additions & 16 deletions src/docs/arc42/chapters/01_introduction_and_goals.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,33 @@
== Introduction and Goals

Good diagrams are very important for a good documentation.
https://www.diagrams.net/[diagrams.net] is open source, online, desktop and container deployable diagramming software which does an amazing job.
https://www.zenuml.com/[ZenUML] is open source, online, desktop and platform integrated diagramming software which does an amazing job.

One of the main features is that it is able to store the sources of the diagram within the meta data of a `.png` or `.svg` image.
That makes it ideal for the https://docs-as-co.de[Docs-as-Code approach] for documenting software.
Those images can be directly embedded in AsciiDoc documents but still remain editable.
This way, you never lose the source of your diagrams.

HINT: diagrams.net was formerly known as draw.io and many plugins, tools and docs still use this name.
One of the main features is that it is able to store the sources of the diagram as plain text.
That makes it ideal for documenting software.

=== Task Definition

The goal is to create a plugin for IntelliJ based IDEs which

* adds a recognizable icon to all diagrams.net files,
* opens diagrams.net files in the diagrams.net editor, and
* saves them in the same format as they were.
* adds a recognizable icon to all ZenUML files,
* opens ZenUML files with ZenUML viewer

Additional tasks might be added in the future.

=== Quality Goals

[cols="1,10"]
[cols="1,20"]
|===
a| 1
a|
It is expected that the diagrams.net files are under version control.
It is expected that the ZenUML files are under version control.
So, corrupted files will be easily recovered.
Nevertheless, the main quality goal is that the editor will be stable (no crashed will editing) and the load/save mechanism will not corrupt files.

a| 2
a|
Data protection is crucial.
The second quality goal is that no data leaves the local system.
If it is necessary to run diagrams.net on a server, it should be possible to set up your own server.

See also https://github.com/jgraph/security-privacy-legal/blob/master/Security/Data.md[data security statement of draw.io]

a| 3
a|
Expand Down
5 changes: 2 additions & 3 deletions src/docs/arc42/chapters/02_architecture_constraints.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
== Architecture Constraints

This plugin is intended for the IntelliJ based IDEs.
This bringst up some constraints.
This brings up some constraints.

* the plugin has to be written in a JVM based language
* the plugin has to run with the https://www.jetbrains.com/help/idea/installation-guide.html#requirements[minimum system requirements of IntelliJ]
** 2 GB of free RAM
** 2.5 GB disk space and another 1 GB for caches
** Monitor Resolution 1024x768
** 64-bit Operating System
* starting with IntelliJ 2020.2, the JavaFX based browser solution within IntelliJ will be deprecated and replaced with the Chromium Embedded Framework (CEF) in its Java based version (JCEF)footnote:[see the JetBraint Platform blog post from July 15, 2020: https://blog.jetbrains.com/platform/2020/07/javafx-and-jcef-in-the-intellij-platform/].
So, the architecture has to support JCEF.
* So, the architecture has to support JCEF.
* current IntelliJ ships with Java 11 as runtime.
So the system must run on Java 11 and higher.
8 changes: 4 additions & 4 deletions src/docs/arc42/chapters/03_system_scope_and_context.adoc
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
[[section-system-scope-and-context]]
== System Scope and Context

This plugin focuses only on IntelliJ and _editing_ of diagrams.

Other IDEs or modification of diagrams are out of scope.
This plugin must work on all JetBrains IDEs that share the same platform as Intellij IDEA.

=== Business Context

Expand All @@ -15,7 +13,9 @@ let's use the https://github.com/RicardoNiepel/C4-PlantUML[C4 PlantUML syntax] h

TODO

==== draw.io interface
==== ZenUML interface

TODO

The draw.io interface is messages based and described in the support article https://desk.draw.io/support/solutions/articles/16000042544-embed-mode[Embed Mode].

Expand Down
6 changes: 4 additions & 2 deletions src/docs/arc42/chapters/04_solution_strategy.adoc
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
[[section-solution-strategy]]
== Solution Strategy

diagrams.net is a web based diagramming tool which runs in different browsers.
ZenUML is a web based diagramming tool which runs in different browsers.
Since IntelliJ version 2020.01, JetBrains added the https://jetbrains.org/intellij/sdk/docs/reference_guide/jcef.html[JCEF - Java Chromium Embedded Framework] to its platform.

This enables us to use the powerful chromium browser to run diagrams.net within IntelliJ.

The diagrams.net documentation has good https://github.com/jgraph/drawio-integration[examples on how to embedd diagrams.not in a stand aline, local HTML page].
TODO: Think about how we should embed ZenUML viewer. Render directly on the page or embed an iFrame as diagrams.net does.

The diagrams.net documentation has good https://github.com/jgraph/drawio-integration[examples on how to embedd diagrams.net in a stand aline, local HTML page].
These where used as a starting point, together with the knowledge of Henning Dieterichs, the author of the draw.io plugin for VS Code.
He learned a lot about draw.io while writing his plugin and thus helped to get draw.io up and running as offline solution.

Expand Down
26 changes: 26 additions & 0 deletions src/docs/arc42/chapters/05_building_block_view.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[[section-solution-strategy]]
== Building Block View

This plugin has two blocks: the viewer and the plugin shell. In the plugin shell, it has glue layer (file type, icon, language, etc.), the web container and settings.

=== plugin.xml
In this file we defined 5 extensions and 1 action.

==== Extensions (5)
[options="header"]
|===
| Extension | Name | Implementation | Notes
| filetype | Diagrams.net Diagram | x.x.DiagramsNetFileType | define file extensions
| fileEditorProvider | NA | DiagramsEditorProvider |
| iconProvider | NA | DiagramsEditorIconProvider | load it from an SVG file
| applicationConfigurable | NA | x.x.DiagramsConfigurable | allow plugins to add pages to the Settings/Preferences dialog
| applicationService | NA | x.x.DiagramsApplicationSettings | also for settings?
|===

==== Actions (1)
[options="header"]
|===
| ID | Class | Notes
| DiagramsNetNewFile | DiagramCreateFileAction | Users can select SVG, PNG or XML
|===

Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
package de.docs_as_co.intellij.plugin.drawio

import com.intellij.openapi.vfs.VirtualFile
import org.w3c.dom.NodeList
import org.xml.sax.SAXParseException
import javax.imageio.ImageIO
import javax.imageio.ImageReader
import javax.imageio.metadata.IIOMetadataFormatImpl
import javax.imageio.metadata.IIOMetadataNode
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactory

class DiagramsFileUtil {
companion object {
Expand All @@ -22,69 +13,12 @@ class DiagramsFileUtil {
return false
}
//check for the right file extension
val extensions = arrayOf(".drawio", ".drawio.svg", ".drawio.png", ".dio", ".dio.svg", ".dio.png")
val extensions = arrayOf(".zen", ".z", ".zenuml")
// Short-circuit for well-known file names. Allows to start with an empty file and open it in the editor.
if (extensions.any { ext -> file.name.endsWith(ext) }) {
return true
}

// Detect editable SVG. Editable SVG will have an embedded diagrams.net diagram.
if (file.name.endsWith(".svg")) {
// prevent external content in SVGs. Even when working in a trusted project, resolving external context might slow down the UI
// https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#jaxp-documentbuilderfactory-saxparserfactory-and-dom4j
val factory = DocumentBuilderFactory.newInstance()
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);

val builder = factory.newDocumentBuilder()
// if if the attribute "content" of element "svg" starts with "<mxfile ", this is a diagrams.net file
file.inputStream.use {
try {
val doc = builder.parse(it)
val xPathfactory = XPathFactory.newInstance()
val xpath = xPathfactory.newXPath()
val expr = xpath.compile("/svg/@content")
val content = expr.evaluate(doc, XPathConstants.STRING)
if (content.toString().startsWith("<mxfile ")) {
return true
}
} catch (ignored: SAXParseException) {
// might happen if:
// * XML is invalid
return false;
}
}
}

// Detect editable PNG. Editable SVG will have an embedded diagrams.net diagram.
if (file.name.endsWith(".png")) {
file.inputStream.use {
ImageIO.createImageInputStream(it).use { input ->
val readers: Iterator<ImageReader> = ImageIO.getImageReaders(input)
if (readers.hasNext()) {
val reader = readers.next()
reader.input = input
val entries: NodeList = reader.getImageMetadata(0).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName).childNodes

// if we find a text node with an attribute mxfile, this is a image contains diagrams.net information
for (i in 0 until entries.length) {
val node = entries.item(i) as IIOMetadataNode
if (node.nodeName.equals("Text")) {
for (j in 0 until node.childNodes.length) {
if (node.childNodes.item(j).attributes.getNamedItem("keyword").nodeValue.equals("mxfile")) {
return true
}
}
}
}
}
}
}
}

return false
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package de.docs_as_co.intellij.plugin.drawio.editor

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.colors.EditorColorsListener
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.event.DocumentListener
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorLocation
import com.intellij.openapi.fileEditor.FileEditorState
Expand All @@ -12,16 +16,21 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.util.messages.MessageBusConnection
import com.intellij.util.ui.UIUtil
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
import de.docs_as_co.intellij.plugin.drawio.settings.DiagramsApplicationSettings
import de.docs_as_co.intellij.plugin.drawio.settings.DiagramsUiTheme
import de.docs_as_co.intellij.plugin.drawio.settings.ZenumlUniversalApplicationSettings
import java.beans.PropertyChangeListener
import java.io.BufferedReader
import javax.swing.JComponent


class DiagramsEditor(private val project: Project, private val file: VirtualFile) : FileEditor, EditorColorsListener, DumbAware,
DiagramsApplicationSettings.SettingsChangedListener {
class DiagramsEditor(project: Project, private val file: VirtualFile) : FileEditor, EditorColorsListener, DumbAware,
ZenumlUniversalApplicationSettings.SettingsChangedListener {
private val lifetimeDef = LifetimeDefinition()
private val lifetime = lifetimeDef.lifetime
private val userDataHolder = UserDataHolderBase()
Expand All @@ -33,20 +42,41 @@ class DiagramsEditor(private val project: Project, private val file: VirtualFile
override fun getFile() = file

private var view :DiagramsWebView
private var doc: Document


init {

//subscribe to changes of the theme
val settingsConnection = ApplicationManager.getApplication().messageBus.connect(this)
settingsConnection.subscribe(EditorColorsManager.TOPIC, this)
settingsConnection.subscribe(DiagramsApplicationSettings.SettingsChangedListener.TOPIC, this)

settingsConnection.subscribe(ZenumlUniversalApplicationSettings.SettingsChangedListener.TOPIC, this)
view = DiagramsWebView(lifetime, uiThemeFromConfig().key)
doc = FileDocumentManager.getInstance().getDocument(file)!!
val documentListener: DocumentListener = object : DocumentListener {
override fun documentChanged(event: DocumentEvent) {
// We can also use doc.text
view.loadXmlLike(event.document.text)
}
}
doc.addDocumentListener(documentListener)
// Listen to any file modification in the project.
val connection: MessageBusConnection = project.messageBus.connect(this)
connection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
override fun after(events: MutableList<out VFileEvent>) {
events.forEach {
if (it.file!!.name == getFile().name) {
val content = getFile().inputStream.bufferedReader().use(BufferedReader::readText)
view.loadXmlLike(content)
}
}
}
})
initView()
}

private fun uiThemeFromConfig(): DiagramsUiTheme {
var uiTheme = DiagramsApplicationSettings.instance.getDiagramsSettings().uiTheme
var uiTheme = ZenumlUniversalApplicationSettings.instance.getDiagramsSettings().uiTheme

if (uiTheme == DiagramsUiTheme.DEFAULT) {
//set theme according to IntelliJ-theme
Expand Down Expand Up @@ -100,7 +130,7 @@ class DiagramsEditor(private val project: Project, private val file: VirtualFile
}
}

override fun onSettingsChange(settings: DiagramsApplicationSettings) {
override fun onSettingsChange(settings: ZenumlUniversalApplicationSettings) {
view.reload(uiThemeFromConfig().key) {
initView()
}
Expand Down Expand Up @@ -129,8 +159,7 @@ class DiagramsEditor(private val project: Project, private val file: VirtualFile
return view.component
}

@Suppress("DialogTitleCapitalization")
override fun getName() = "diagrams.net editor"
override fun getName() = "ZenUML Viewer"

override fun setState(state: FileEditorState) {

Expand Down
10 changes: 5 additions & 5 deletions src/main/kotlin/de/docs_as_co/intellij/plugin/drawio/language.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import com.intellij.lang.Language
import com.intellij.openapi.fileTypes.LanguageFileType
import com.intellij.openapi.util.IconLoader

object DiagramsNet : Language("Diagrams.net")
object DiagramsNet : Language("ZenUML")

object DiagramsNetFileType : LanguageFileType(DiagramsNet) {
override fun getName() = "Diagrams.net Diagram"
override fun getDescription() = "Diagrams.net Diagram File"
override fun getDefaultExtension() = "drawio"
override fun getName() = "ZenUML Diagram"
override fun getDescription() = "ZenUML diagram file"
override fun getDefaultExtension() = "zen"
override fun getIcon() = DiagramsNetIcon.FILE
}

object DiagramsNetIcon {
val FILE = IconLoader.getIcon("/icons/diagrams.svg", this.javaClass)
val FILE = IconLoader.getIcon("/icons/zenuml-file.svg", this.javaClass)
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ class DiagramsConfigurable : SearchableConfigurable {
}

override fun isModified(): Boolean {
val settings = DiagramsApplicationSettings.instance
val settings = ZenumlUniversalApplicationSettings.instance
return !form.getDiagramsSettings().equals(settings.getDiagramsSettings())
}

override fun apply() {
val settings = DiagramsApplicationSettings.instance
val settings = ZenumlUniversalApplicationSettings.instance
settings.setDiagramsPreviewSettings(form.getDiagramsSettings())
}

override fun reset() {
val settings = DiagramsApplicationSettings.instance
val settings = ZenumlUniversalApplicationSettings.instance
form.setDiagramsPreviewSettings(settings.getDiagramsSettings())
}

Expand Down
Loading

0 comments on commit d51e34c

Please sign in to comment.