Skip to content

Commit 04c1bee

Browse files
authored
Resources preview (#7)
* Add resources preview popup in matched density * Add full zip file preview dialog * Update plugin compatibility from 203 to 221 * Update CHANGELOG.md
1 parent 9a2c492 commit 04c1bee

14 files changed

Lines changed: 386 additions & 20 deletions

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313

1414
### Fixed
1515

16+
## [1.2.0] - 2022-05-01
17+
### Added
18+
- Preview dialog showing all images in the selected ZIP file
19+
- Preview popup showing image in a matched density
20+
1621
## [1.1.1] - 2022-02-27
1722
### Added
1823
- Duplicated suffixes validation

gradle.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
pluginGroup = com.abeade.plugin.figma
55
pluginName = figma-import-plugin
66
# SemVer format -> https://semver.org
7-
pluginVersion = 1.1.1
7+
pluginVersion = 1.2.0
88

99
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
1010
# for insight into build numbers and IntelliJ Platform versions.
11-
pluginSinceBuild = 192.0
12-
pluginUntilBuild = 213.*
11+
pluginSinceBuild = 203.0
12+
pluginUntilBuild = 221.*
1313

1414
# IntelliJ Platform Properties -> https://github.com/JetBrains/gradle-intellij-plugin#intellij-platform-properties
1515
platformType = IC
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.abeade.plugin.figma
2+
3+
import com.abeade.plugin.figma.ui.FilePreviewDialog
4+
import com.abeade.plugin.figma.ui.FilePreviewItem
5+
import com.abeade.plugin.figma.utils.isValidEntry
6+
import com.intellij.openapi.ui.DialogWrapper
7+
import java.io.File
8+
import java.util.zip.ZipFile
9+
import javax.imageio.ImageIO
10+
import javax.swing.Action
11+
import javax.swing.BoxLayout
12+
import javax.swing.JComponent
13+
14+
class FilePreviewDialogWrapper(
15+
private val file: File
16+
) : DialogWrapper(true) {
17+
18+
init {
19+
init()
20+
title = file.name
21+
}
22+
23+
override fun createCenterPanel(): JComponent = FilePreviewDialog().apply {
24+
contentPanel.layout = BoxLayout(contentPanel, BoxLayout.X_AXIS)
25+
ZipFile(file).use { zipFile ->
26+
zipFile.entries().asSequence().filter { it.isValidEntry() }.sortedBy { it.size }.forEach { entry ->
27+
val preview = FilePreviewItem()
28+
try {
29+
val image = ImageIO.read(zipFile.getInputStream(entry))
30+
preview.quickDrawPanel.setImage(image)
31+
preview.labelDescription.text = "${entry.name} (${image.width} x ${image.height})"
32+
} catch (e: Exception) {
33+
preview.labelDescription.text = "${entry.name} (Failed to process file)"
34+
}
35+
contentPanel.add(preview.mainPanel)
36+
}
37+
}
38+
}.mainPanel
39+
40+
override fun createActions(): Array<Action> = arrayOf(okAction)
41+
}

src/main/kotlin/com/abeade/plugin/figma/ImportDialogWrapper.kt

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
package com.abeade.plugin.figma
22

33
import com.abeade.plugin.figma.ui.ImportDialog
4+
import com.abeade.plugin.figma.ui.PreviewPanel
45
import com.abeade.plugin.figma.ui.autocomplete.Autocomplete
56
import com.abeade.plugin.figma.utils.EMPTY
67
import com.abeade.plugin.figma.utils.containsAny
78
import com.abeade.plugin.figma.utils.findFirstOf
9+
import com.abeade.plugin.figma.utils.isValidEntry
810
import com.intellij.icons.AllIcons
911
import com.intellij.ide.util.PropertiesComponent
12+
import com.intellij.notification.Notification
13+
import com.intellij.notification.NotificationType
14+
import com.intellij.notification.Notifications
1015
import com.intellij.openapi.ui.DialogWrapper
1116
import com.intellij.openapi.ui.ValidationInfo
17+
import com.intellij.openapi.ui.popup.JBPopup
18+
import com.intellij.openapi.ui.popup.JBPopupFactory
1219
import com.intellij.ui.IdeBorderFactory
20+
import com.intellij.vcs.commit.NonModalCommitPanel.Companion.showAbove
1321
import java.awt.Desktop
22+
import java.awt.Dimension
1423
import java.awt.event.*
1524
import java.io.File
25+
import java.io.IOException
1626
import java.net.URI
1727
import java.util.zip.ZipFile
28+
import javax.imageio.ImageIO
1829
import javax.swing.*
1930
import javax.swing.event.DocumentEvent
2031
import javax.swing.event.DocumentListener
@@ -90,6 +101,27 @@ class ImportDialogWrapper(
90101
Desktop.getDesktop().browse(URI("https://github.com/abeade/figma-import-plugin"))
91102
}
92103
})
104+
ldpiIconViewLabel.icon = AllIcons.General.InspectionsEye
105+
mdpiIconViewLabel.icon = AllIcons.General.InspectionsEye
106+
hdpiIconViewLabel.icon = AllIcons.General.InspectionsEye
107+
xhdpiIconViewLabel.icon = AllIcons.General.InspectionsEye
108+
xxhdpiIconViewLabel.icon = AllIcons.General.InspectionsEye
109+
xxxhdpiIconViewLabel.icon = AllIcons.General.InspectionsEye
110+
val previewTooltipText = "Click here to view this image"
111+
ldpiIconViewLabel.toolTipText = previewTooltipText
112+
mdpiIconViewLabel.toolTipText = previewTooltipText
113+
hdpiIconViewLabel.toolTipText = previewTooltipText
114+
xhdpiIconViewLabel.toolTipText = previewTooltipText
115+
xxhdpiIconViewLabel.toolTipText = previewTooltipText
116+
xxxhdpiIconViewLabel.toolTipText = previewTooltipText
117+
previewLabel.icon = AllIcons.General.InspectionsEye
118+
previewLabel.toolTipText = "Click here to view the images in this file"
119+
previewLabel.isVisible = false
120+
previewLabel.addMouseListener(object : MouseAdapter() {
121+
override fun mouseClicked(e: MouseEvent?) {
122+
file?.let { FilePreviewDialogWrapper(it).show() }
123+
}
124+
})
93125
}
94126
updateLabels()
95127
return dialog.mainPanel
@@ -242,17 +274,25 @@ class ImportDialogWrapper(
242274
updateLabelField(dialog.xxxhdpiLabel, dialog.xxxhdpiField)
243275
zipFilesList?.let {
244276
val skip = propertiesComponent.isTrueValue(SKIP_KEY)
245-
updateIconField(dialog.ldpiIconLabel, dialog.ldpiField, it, skip, Density.LDPI)
246-
updateIconField(dialog.mdpiIconLabel, dialog.mdpiField, it, skip, Density.MDPI)
247-
updateIconField(dialog.hdpiIconLabel, dialog.hdpiField, it, skip, Density.HDPI)
248-
updateIconField(dialog.xhdpiIconLabel, dialog.xhdpiField, it, skip, Density.XHDPI)
249-
updateIconField(dialog.xxhdpiIconLabel, dialog.xxhdpiField, it, skip, Density.XXHDPI)
250-
updateIconField(dialog.xxxhdpiIconLabel, dialog.xxxhdpiField, it, skip, Density.XXXHDPI)
277+
updateIconField(dialog.ldpiIconLabel, dialog.ldpiIconViewLabel, dialog.ldpiField, it, skip, Density.LDPI)
278+
updateIconField(dialog.mdpiIconLabel, dialog.mdpiIconViewLabel, dialog.mdpiField, it, skip, Density.MDPI)
279+
updateIconField(dialog.hdpiIconLabel, dialog.hdpiIconViewLabel, dialog.hdpiField, it, skip, Density.HDPI)
280+
updateIconField(dialog.xhdpiIconLabel, dialog.xhdpiIconViewLabel, dialog.xhdpiField, it, skip, Density.XHDPI)
281+
updateIconField(dialog.xxhdpiIconLabel, dialog.xxhdpiIconViewLabel, dialog.xxhdpiField, it, skip, Density.XXHDPI)
282+
updateIconField(dialog.xxxhdpiIconLabel, dialog.xxxhdpiIconViewLabel, dialog.xxxhdpiField, it, skip, Density.XXXHDPI)
283+
} ?: run {
284+
dialog.ldpiIconViewLabel.isVisible = false
285+
dialog.mdpiIconViewLabel.isVisible = false
286+
dialog.hdpiIconViewLabel.isVisible = false
287+
dialog.xhdpiIconViewLabel.isVisible = false
288+
dialog.xxhdpiIconViewLabel.isVisible = false
289+
dialog.xxxhdpiIconViewLabel.isVisible = false
251290
}
252291
}
253292

254293
private fun updateIconField(
255294
iconLabel: JLabel,
295+
viewIconLabel: JLabel,
256296
field: JTextField,
257297
filesList: MutableList<String>,
258298
skipWhenNotExists: Boolean,
@@ -262,21 +302,67 @@ class ImportDialogWrapper(
262302
val suffix = field.text
263303
if (suffix.isBlank()) {
264304
iconLabel.icon = AllIcons.General.Warning
305+
viewIconLabel.isVisible = false
265306
iconLabel.toolTipText = "Empty suffix. Density will be skipped"
266307
} else if (!destinationExists && skipWhenNotExists) {
267308
iconLabel.icon = AllIcons.General.Warning
309+
viewIconLabel.isVisible = false
268310
iconLabel.toolTipText = "Density folder not found, resource will be skipped<br/>You can change this in plugin settings"
269311
} else {
270-
if (filesList.any { it.substringBeforeLast('.').endsWith(suffix) }) {
312+
val file = filesList.firstOrNull { it.substringBeforeLast('.').endsWith(suffix) }
313+
if (file != null) {
271314
iconLabel.icon = AllIcons.General.InspectionsOK
272315
iconLabel.toolTipText = "Resource found"
316+
viewIconLabel.isVisible = true
317+
viewIconLabel.mouseListeners.forEach { viewIconLabel.removeMouseListener(it) }
318+
viewIconLabel.addMouseListener(object : MouseAdapter() {
319+
override fun mouseClicked(e: MouseEvent?) {
320+
showPreviewPopup(file, viewIconLabel)
321+
}
322+
})
273323
} else {
274324
iconLabel.icon = AllIcons.General.Error
325+
viewIconLabel.isVisible = false
275326
iconLabel.toolTipText = "Resource not found"
276327
}
277328
}
278329
}
279330

331+
private fun showPreviewPopup(fileName: String, target: JComponent): JBPopup? =
332+
getBufferedImage(fileName)?.let { image ->
333+
val previewPanel = PreviewPanel()
334+
previewPanel.quickDrawPanel.setImage(image)
335+
previewPanel.labelSize.text = "${image.width} x ${image.height}"
336+
val popupBuilder = JBPopupFactory.getInstance().createComponentPopupBuilder(previewPanel.mainPanel, null)
337+
popupBuilder.createPopup().apply {
338+
size = Dimension(
339+
/* width = */ minOf(image.width, MAX_PREVIEW_SIZE) + WIDTH_PREVIEW_MARGIN,
340+
/* height = */ minOf(image.height, MAX_PREVIEW_SIZE) + HEIGHT_PREVIEW_MARGIN
341+
)
342+
showAbove(target)
343+
}
344+
}
345+
346+
private fun getBufferedImage(densityFile: String) = try {
347+
file?.let { selectedZip ->
348+
ZipFile(selectedZip).use { zipFile ->
349+
zipFile.entries().toList().find { it.name.endsWith(densityFile) }?.let { entry ->
350+
ImageIO.read(zipFile.getInputStream(entry))
351+
}
352+
}
353+
}
354+
} catch (e: IOException) {
355+
Notifications.Bus.notify(
356+
Notification(
357+
"Figma import",
358+
"Error loading preview image",
359+
"An error occurred processing loading image file $densityFile",
360+
NotificationType.ERROR
361+
)
362+
)
363+
null
364+
}
365+
280366
private fun Density.getFolder(): String {
281367
val preValue = dialog.qualifierBeforeField.text.sanitizeQualifier()
282368
val preModifier = if (preValue.isBlank()) "" else "$preValue$QUALIFIER_SEPARATOR"
@@ -301,12 +387,10 @@ class ImportDialogWrapper(
301387
if (result == JFileChooser.APPROVE_OPTION) {
302388
file = fileDialog.selectedFile
303389
dialog.fileField.text = fileDialog.selectedFile.toString()
390+
dialog.previewLabel.isVisible = true
304391
ZipFile(fileDialog.selectedFile).use { zipFile ->
305392
zipFilesList = zipFile.entries().asSequence()
306-
.filter {
307-
!it.isDirectory && (it.name.endsWith(".png", true) ||
308-
it.name.endsWith(".jpg", true))
309-
}
393+
.filter { it.isValidEntry() }
310394
.fold(mutableListOf()) { list, entry -> list.apply { add(File(entry.name).name) } }
311395
}
312396
zipFilesList?.let {
@@ -354,5 +438,9 @@ class ImportDialogWrapper(
354438

355439
private const val QUALIFIER_SEPARATOR = "-"
356440
private const val FOLDER_DRAWABLE = "drawable$QUALIFIER_SEPARATOR"
441+
442+
private const val MAX_PREVIEW_SIZE = 400
443+
private const val WIDTH_PREVIEW_MARGIN = 4
444+
private const val HEIGHT_PREVIEW_MARGIN = 26
357445
}
358446
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.abeade.plugin.figma.ui.FilePreviewDialog">
3+
<grid id="27dc6" binding="mainPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
4+
<constraints>
5+
<xy x="20" y="20" width="500" height="400"/>
6+
</constraints>
7+
<properties>
8+
<opaque value="false"/>
9+
<preferredSize width="800" height="500"/>
10+
</properties>
11+
<border type="none"/>
12+
<children>
13+
<scrollpane id="fd963">
14+
<constraints border-constraint="Center"/>
15+
<properties/>
16+
<border type="none"/>
17+
<children>
18+
<grid id="868c2" binding="contentPanel" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="1">
19+
<constraints/>
20+
<properties/>
21+
<border type="none"/>
22+
<children/>
23+
</grid>
24+
</children>
25+
</scrollpane>
26+
</children>
27+
</grid>
28+
</form>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.abeade.plugin.figma.ui
2+
3+
import javax.swing.JPanel
4+
5+
class FilePreviewDialog {
6+
lateinit var mainPanel: JPanel
7+
lateinit var contentPanel: JPanel
8+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.abeade.plugin.figma.ui.FilePreviewItem">
3+
<grid id="27dc6" binding="mainPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
4+
<margin top="0" left="0" bottom="0" right="0"/>
5+
<constraints>
6+
<xy x="20" y="20" width="500" height="400"/>
7+
</constraints>
8+
<properties/>
9+
<border type="bevel-lowered"/>
10+
<children>
11+
<component id="98c0f" class="javax.swing.JLabel" binding="labelDescription">
12+
<constraints>
13+
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
14+
</constraints>
15+
<properties>
16+
<text value=""/>
17+
</properties>
18+
</component>
19+
<grid id="2516f" layout-manager="BorderLayout" hgap="0" vgap="0">
20+
<constraints>
21+
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="1" fill="0" indent="0" use-parent-layout="false"/>
22+
</constraints>
23+
<properties/>
24+
<border type="bevel-raised"/>
25+
<children>
26+
<component id="e8df4" class="com.abeade.plugin.figma.ui.preview.QuickDrawPanel" binding="quickDrawPanel">
27+
<constraints border-constraint="Center"/>
28+
<properties/>
29+
</component>
30+
</children>
31+
</grid>
32+
</children>
33+
</grid>
34+
</form>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.abeade.plugin.figma.ui
2+
3+
import com.abeade.plugin.figma.ui.preview.QuickDrawPanel
4+
import javax.swing.JLabel
5+
import javax.swing.JPanel
6+
7+
class FilePreviewItem {
8+
lateinit var mainPanel: JPanel
9+
lateinit var quickDrawPanel: QuickDrawPanel
10+
lateinit var labelDescription: JLabel
11+
}

0 commit comments

Comments
 (0)