Skip to content

Commit

Permalink
refactor(plugin): Save, auto-save now use IntelliJ's built-in mechanism
Browse files Browse the repository at this point in the history
This commit introduces kotlin coroutines, since this my hello
world in the coroutine world this likely contains mistakes.
  • Loading branch information
bric3 committed Aug 11, 2021
1 parent 1ca3886 commit 6d0a527
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 97 deletions.
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

## [Unreleased]
### Added
- SVG and PNG exports are now saved with an embedded scene (always on) (#11)
- SVG and PNG exports are now saved with an embedded scene (always on) ([#11](https://github.com/bric3/excalidraw-jetbrains-plugin/issues/11))
- SVG and PNG files an embedded scene are recognized and can be loaded in the editor ([#12](https://github.com/bric3/excalidraw-jetbrains-plugin/issues/12))
- Modified excalidraw files marker if _Mark Modified (*)_ is ticked (in _Preferences | Editor | General | Editor Tabs_)

### Changed
- The Excalidraw webapp is now written in typescript
- The Excalidraw webapp now inline sourcemaps to enable
debugging within the JCEF DevTools, this was impossible due to a bug in chrominium
debugging within the JCEF DevTools, this was impossible due to a bug in chromium
(see [#7](https://github.com/bric3/excalidraw-jetbrains-plugin/issues/7))
- Refactored how editor are auto-saved, now using IntelliJ's built-in mechanism.

## [0.2.0-eap] - 2021-07-01
### Added
Expand Down
3 changes: 2 additions & 1 deletion excalidraw-assets/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ class ExcalidrawApiBridge {
fetch('/fs/' + fileToFetch)
.then(response => response.blob())
.then(async image => {
// TODO continuous saving disabled for now, eventually reenable
// TODO continuous saving disabled for now,
// as the plugin uses IntelliJ's auto-saving mechanism instead.
this.continuousSavingEnabled = false

switch (image.type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.github.bric3.excalidraw.SaveOptions
import com.github.bric3.excalidraw.asyncWrite
import com.github.bric3.excalidraw.files.ExcalidrawImageType
import com.github.bric3.excalidraw.findEditor
import com.github.bric3.excalidraw.logWithThread
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications
Expand All @@ -14,6 +15,11 @@ import com.intellij.openapi.fileChooser.FileChooserFactory
import com.intellij.openapi.fileChooser.FileSaverDescriptor
import com.intellij.openapi.fileChooser.FileSaverDialog
import com.intellij.openapi.project.Project
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

abstract class ExportAction(val type: ExcalidrawImageType) : AnAction() {
private val logger = thisLogger()
Expand All @@ -35,11 +41,12 @@ abstract class ExportAction(val type: ExcalidrawImageType) : AnAction() {
val saveFileDialog: FileSaverDialog =
FileChooserFactory.getInstance().createSaveFileDialog(descriptor, null as Project?)

val destination = saveFileDialog.save(excalidrawEditor.file.parent,
"${excalidrawEditor.file.nameWithoutExtension}.${type.extension}"
val destination = saveFileDialog.save(
excalidrawEditor.file.parent,
"${excalidrawEditor.file.nameWithoutExtension}.${type.extension}"
)
if (destination != null) {
logger.debug("Export ${type.name} to destination : ${destination.file}")
logWithThread("Export ${type.name} to destination : ${destination.file}")
if (destination.file.extension != type.extension) {
Notifications.Bus.notify(
Notification(
Expand All @@ -52,10 +59,17 @@ abstract class ExportAction(val type: ExcalidrawImageType) : AnAction() {
)
}

excalidrawEditor.viewController.saveAs(type, saveOptions).then { payload ->
asyncWrite({ destination.getVirtualFile(true)!! },
type,
convertToByteArray(payload))
runBlocking {
GlobalScope.launch {
withContext(Dispatchers.Default) {
val payload = excalidrawEditor.viewController.saveAsCoroutines(type, saveOptions)
asyncWrite(
{ destination.getVirtualFile(true)!! },
type,
convertToByteArray(payload)
)
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.github.bric3.excalidraw.SaveOptions
import com.github.bric3.excalidraw.asyncWrite
import com.github.bric3.excalidraw.files.EXCALIDRAW_IMAGE_TYPE
import com.github.bric3.excalidraw.files.ExcalidrawImageType
import com.github.bric3.excalidraw.logWithThread
import com.github.bric3.excalidraw.support.ExcalidrawColorScheme
import com.intellij.AppTopics
import com.intellij.notification.Notification
Expand All @@ -17,8 +18,6 @@ import com.intellij.openapi.editor.colors.EditorColorsScheme
import com.intellij.openapi.fileEditor.FileDocumentManagerListener
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorLocation
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.fileEditor.FileEditorState
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
Expand All @@ -34,6 +33,9 @@ import com.intellij.ui.jcef.JBCefApp
import com.intellij.util.ui.UIUtil
import com.jetbrains.rd.util.lifetime.LifetimeDefinition
import com.jetbrains.rd.util.reactive.adviseNotNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.awt.BorderLayout
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
Expand Down Expand Up @@ -72,18 +74,26 @@ class ExcalidrawEditor(
busConnection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, object : FileDocumentManagerListener {
override fun beforeAllDocumentsSaving() {
// This is the manual or auto save action of IntelliJ
println("before all documents saving") // TODO remove
logWithThread("ExcalidrawEditor::beforeAllDocumentsSaving")
saveEditor()
// AWT-EventQueue-0 : 26 : ExcalidrawEditor::beforeAllDocumentsSaving
// AWT-EventQueue-0 : 26 : ExcalidrawEditor::saveEditor
// AWT-EventQueue-0 : 26 : ExcalidrawWebViewController::saveAs
// AWT-AppKit : 23 : CefMessageRouterHandlerAdapter::onQuery
// AWT-AppKit : 23 : ExcalidrawEditor::saveEditor.then write promise
// AWT-AppKit : 23 : ExcalidrawEditor::saveEditor.then write done promise
// AWT-EventQueue-0 : 26 : asyncWrite
}
})

busConnection.subscribe(
FileEditorManagerListener.Before.FILE_EDITOR_MANAGER,
object : FileEditorManagerListener.Before {
override fun beforeFileClosed(source: FileEditorManager, file: VirtualFile) {
println("before closing ${file.name}") // TODO remove
}
})
// // Before file close
// busConnection.subscribe(
// FileEditorManagerListener.Before.FILE_EDITOR_MANAGER,
// object : FileEditorManagerListener.Before {
// override fun beforeFileClosed(source: FileEditorManager, file: VirtualFile) {
// logWithThread("ExcalidrawEditor::beforeFileClosed ${file.name}")
// }
// })

busConnection.subscribe(VirtualFileManager.VFS_CHANGES, object : BulkFileListener {
override fun after(events: MutableList<out VFileEvent>) {
Expand Down Expand Up @@ -155,68 +165,50 @@ class ExcalidrawEditor(

// https://github.com/JetBrains/rd/blob/211/rd-kt/rd-core/src/commonMain/kotlin/com/jetbrains/rd/util/reactive/Interfaces.kt#L17
viewController.excalidrawPayload.adviseNotNull(lifetime) { content ->
logger.debug("content to save to $file")
logWithThread("content to save to $file")
if (!file.isWritable) {
return@adviseNotNull
}
modified = true
ApplicationManager.getApplication().invokeLater {
ApplicationManager.getApplication().runWriteAction {
propertyChangeSupport.firePropertyChange(FileEditor.PROP_MODIFIED, false, true)
}
toggleModifiedStatus(true)
}
}

private fun toggleModifiedStatus(newModificationStatus: Boolean) {
val oldModificationStatus = modified
if (oldModificationStatus == newModificationStatus) {
return
}
modified = newModificationStatus
ApplicationManager.getApplication().invokeLater {
ApplicationManager.getApplication().runWriteAction {
propertyChangeSupport.firePropertyChange(FileEditor.PROP_MODIFIED,
oldModificationStatus,
newModificationStatus)
}
// val (type, b) = when {
// file.name.endsWith(".svg") -> {
// TODO("Continuous saving to SVG is not yet supported")
//// view.saveAsSvg().then{ data: String ->
//// file.setBinaryContent(data.toByteArray(charset("utf-8"))
//// }
// }
// file.name.endsWith(".png") -> {
// TODO("Continuous saving to PNG is not yet supported")
//// view.saveAsPng().then { data: ByteArray ->
//// file.setBinaryContent(data)
//// }
// }
// else -> {
// Pair(
// ExcalidrawImageType.EXCALIDRAW,
// content.toByteArray(UTF_8)
// )
// }
// }

// replaced by IntelliJ's auto-save feature
// ApplicationManager.getApplication().invokeLater {
// ApplicationManager.getApplication().runWriteAction {
// try {
// file.getOutputStream(file).use { stream ->
// with(stream) {
// write(b)
// }
// }
// } catch (e: IOException) {
// notifyAboutWriteError(type, file, e)
// } catch (e: IllegalArgumentException) {
// notifyAboutWriteError(type, file, e)
// }
// }
// }
}
}

private fun saveEditor() {
// TODO add scene version in file user data
saveCoroutines()
}

private fun saveCoroutines() {
logWithThread("ExcalidrawEditor::saveCoroutines")
if (!file.isWritable) {
logger.debug("bailing out save, file non writable")
logWithThread("bailing out save, file non writable")
return
}
logger.debug("starts saving editor")
val saveOptions = getUserData(SaveOptions.SAVE_OPTIONS_KEY) ?: SaveOptions()
logWithThread("starts saving editor")
val saveOptions = getUserData(SaveOptions.SAVE_OPTIONS_KEY) ?: SaveOptions()
val type = file.getUserData(EXCALIDRAW_IMAGE_TYPE)
?: throw IllegalStateException("Excalidraw should have been identified")

viewController.saveAs(type, saveOptions).then { payload ->
logger.debug("Got payload")

// wrap in runBlocking ?

GlobalScope.launch(Dispatchers.Default) {
val payload = viewController.saveAsCoroutines(type, saveOptions)
logWithThread("received a payload!! : ${payload.substring(0, 10)}...")
val byteArrayPayload = when (type) {
ExcalidrawImageType.EXCALIDRAW, ExcalidrawImageType.SVG -> payload.toByteArray(UTF_8)
ExcalidrawImageType.PNG -> Base64.getDecoder().decode(payload.substringAfter("data:image/png;base64,"))
Expand All @@ -225,17 +217,11 @@ class ExcalidrawEditor(
{ file },
type,
byteArrayPayload
)
}.then {
modified = false
logger.debug("File ${file.name} saved")
).then {
logWithThread("File ${file.name} saved")
toggleModifiedStatus(false)
}
}

// runBlocking {
// withTimeout(1000) {
// promise.await()
// }
// }
}

@Override
Expand Down Expand Up @@ -263,28 +249,42 @@ class ExcalidrawEditor(
}

override fun addPropertyChangeListener(listener: PropertyChangeListener) {
println("addPropertyChangeListener")
propertyChangeSupport.addPropertyChangeListener(listener)
}

override fun removePropertyChangeListener(listener: PropertyChangeListener) {
println("removePropertyChangeListener")
propertyChangeSupport.removePropertyChangeListener(listener)
}

override fun deselectNotify() {
// if closing the editor it's preceded by
// com.intellij.openapi.fileEditor.FileEditorManagerListener.Before.beforeFileClosed
// changing (and current editor gets deselected) editor triggers
println("deselectNotify")
logWithThread("ExcalidrawEditor::deselectNotify")
saveEditor()

// deselectNotify
// AWT-EventQueue-0 : 26 : ExcalidrawEditor::saveEditor
// AWT-EventQueue-0 : 26 : ExcalidrawWebViewController::saveAs
// AWT-AppKit : 23 : CefMessageRouterHandlerAdapter::onQuery
// AWT-AppKit : 23 : ExcalidrawEditor::saveEditor.then write promise
// AWT-AppKit : 23 : ExcalidrawEditor::saveEditor.then write done promise
// AWT-EventQueue-0 : 26 : asyncWrite

// AWT-EventQueue-0 : 26 : ExcalidrawEditor::beforeFileClosed random.excalidraw
// AWT-EventQueue-0 : 26 : ExcalidrawEditor::deselectNotify
// AWT-EventQueue-0 : 26 : ExcalidrawEditor::saveEditor
// AWT-EventQueue-0 : 26 : ExcalidrawWebViewController::saveAs
// AWT-EventQueue-0 : 26 : LoadableJCEFHtmlPanel::dispose
// AWT-EventQueue-0 : 26 : ExcalidrawEditor::dispose
}

override fun getCurrentLocation(): FileEditorLocation? {
return null
}

override fun dispose() {
logWithThread("ExcalidrawEditor::dispose")
lifetimeDef.terminate(true)
}

Expand Down
Loading

0 comments on commit 6d0a527

Please sign in to comment.