From 22956c93b6163139f7a842eb7ac35f1c6cabaa54 Mon Sep 17 00:00:00 2001 From: marcin Date: Wed, 24 Apr 2024 22:56:43 +0200 Subject: [PATCH] feat: module starter (#35) * module starter --- .github/workflows/validation.yml | 32 ++++- .../module/PostModuleCreatedActivity.kt | 25 ++++ .../vaadin/plugin/module/QuickStarterPanel.kt | 28 ++++ .../plugin/module/SkeletonStarterPanel.kt | 120 ++++++++++++++++++ .../plugin/module/VaadinCustomOptionsStep.kt | 18 +++ .../plugin/module/VaadinModuleBuilder.kt | 50 ++++++++ .../vaadin/plugin/module/VaadinModuleType.kt | 26 ++++ .../com/vaadin/plugin/module/VaadinPanel.kt | 59 +++++++++ .../plugin/starter/DownloadableModel.kt | 11 ++ .../plugin/starter/QuickStarterModel.kt | 34 +++++ .../com/vaadin/plugin/starter/StarterModel.kt | 38 ++++++ .../vaadin/plugin/starter/StarterSupport.kt | 104 +++++++++++++++ .../starter/StarterSupportMatrixElement.kt | 9 ++ .../vaadin/plugin/utils/VaadinProjectUtil.kt | 82 ++++++++++++ src/main/resources/META-INF/plugin.xml | 6 + src/main/resources/META-INF/pluginIcon.svg | 2 +- src/main/resources/icons/module.svg | 4 + 17 files changed, 644 insertions(+), 4 deletions(-) create mode 100644 src/main/kotlin/com/vaadin/plugin/module/PostModuleCreatedActivity.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/module/QuickStarterPanel.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/module/SkeletonStarterPanel.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/module/VaadinCustomOptionsStep.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/module/VaadinModuleBuilder.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/module/VaadinModuleType.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/module/VaadinPanel.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/starter/DownloadableModel.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/starter/QuickStarterModel.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/starter/StarterModel.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/starter/StarterSupport.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/starter/StarterSupportMatrixElement.kt create mode 100644 src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt create mode 100644 src/main/resources/icons/module.svg diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 31c46d61..c42e6b87 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -6,11 +6,11 @@ on: - main concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: "${{ github.workflow }}-${{ github.ref }}" cancel-in-progress: true jobs: - validate: + buildPlugin: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -23,4 +23,30 @@ jobs: - name: Setup Gradle uses: gradle/actions/setup-gradle@v3 - name: Build with Gradle - run: ./gradlew --no-configuration-cache build test + run: ./gradlew --no-configuration-cache buildPlugin + - uses: actions/upload-artifact@v4.3.3 + id: artifact + with: + name: distributions + path: build/distributions + - uses: mshick/add-pr-comment@v2 + with: + refresh-message-position: true + message: | + Artifact build on last commit: [distributions.zip](${{ steps.artifact.outputs.artifact-url }}). + For MacOS users: there is a zip inside this zip and Finder unzips them both at once. Use `unzip distributions.zip` from Terminal or [check solution for Archive Manager](https://apple.stackexchange.com/questions/443607/is-there-a-way-to-prevent-macoss-archive-utility-from-unarchiving-inner-zip-fil). + + runPluginVerifier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Build with Gradle + run: ./gradlew --no-configuration-cache runPluginVerifier diff --git a/src/main/kotlin/com/vaadin/plugin/module/PostModuleCreatedActivity.kt b/src/main/kotlin/com/vaadin/plugin/module/PostModuleCreatedActivity.kt new file mode 100644 index 00000000..8aaa3626 --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/module/PostModuleCreatedActivity.kt @@ -0,0 +1,25 @@ +package com.vaadin.plugin.module + +import com.intellij.notification.NotificationType +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.project.Project +import com.intellij.openapi.startup.ProjectActivity +import com.intellij.openapi.vfs.VfsUtil +import com.vaadin.plugin.utils.VaadinProjectUtil +import java.io.File + +class PostModuleCreatedActivity : ProjectActivity { + + override suspend fun execute(project: Project) { + project.getUserData(VaadinProjectUtil.PROJECT_DOWNLOADED_PROP_KEY)?.afterChange { + VaadinProjectUtil.notify("Vaadin project created", NotificationType.INFORMATION, project) + VfsUtil.findFileByIoFile(File(project.basePath, "README.md"), true)?.let { + val descriptor = OpenFileDescriptor(project, it) + descriptor.setUsePreviewTab(true) + FileEditorManager.getInstance(project).openEditor(descriptor, true) + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vaadin/plugin/module/QuickStarterPanel.kt b/src/main/kotlin/com/vaadin/plugin/module/QuickStarterPanel.kt new file mode 100644 index 00000000..c94b857a --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/module/QuickStarterPanel.kt @@ -0,0 +1,28 @@ +package com.vaadin.plugin.module + +import com.intellij.ui.dsl.builder.panel +import com.vaadin.plugin.starter.QuickStarterModel + +class QuickStarterPanel { + + val model = QuickStarterModel("Flow (Java)", false, "Stable") + + val root = panel { + row("Example views") { + segmentedButton(setOf("Flow (Java)", "Hilla (React)", "None")) { it }.whenItemSelected { + model.views = it + }.selectedItem = model.views + } + row("Use authentication") { + checkBox("").onChanged { + model.authentication = it.isSelected + }.component.isSelected = model.authentication + } + row("Version") { + segmentedButton(setOf("Stable", "Prerelease")) { it }.whenItemSelected { + model.version = it + }.selectedItem = model.version + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vaadin/plugin/module/SkeletonStarterPanel.kt b/src/main/kotlin/com/vaadin/plugin/module/SkeletonStarterPanel.kt new file mode 100644 index 00000000..0a98770f --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/module/SkeletonStarterPanel.kt @@ -0,0 +1,120 @@ +package com.vaadin.plugin.module + +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.dsl.builder.TopGap +import com.intellij.ui.dsl.builder.bind +import com.intellij.ui.dsl.builder.panel +import com.jetbrains.rd.util.first +import com.vaadin.plugin.starter.StarterModel +import com.vaadin.plugin.starter.StarterSupport +import javax.swing.JEditorPane +import javax.swing.JRadioButton + +class SkeletonStarterPanel { + + private val all = mapOf( + "frameworks" to HashMap(), + "languages" to HashMap(), + "buildTools" to HashMap(), + "architectures" to HashMap(), + ) + + private var kotlinInfo: JEditorPane? = null + private var notAllArchitecturesSupportedMessage: JEditorPane? = null + + val model = StarterModel( + StarterSupport.frameworks.keys.first(), + StarterSupport.languages.keys.first(), + StarterSupport.buildTools.keys.first(), + StarterSupport.architectures.keys.first() + ) + + val root: DialogPanel = panel { + buttonsGroup { + row("Framework") { + for (el in StarterSupport.frameworks.entries) { + val r = radioButton(el.value, el.key).onChanged { refreshSupport() } + all["frameworks"]!![r.component] = el.key + } + } + }.bind(model::framework) + buttonsGroup { + row("Language") { + for (el in StarterSupport.languages) { + val r = radioButton(el.value, el.key).onChanged { refreshSupport() } + all["languages"]!![r.component] = el.key + } + }.topGap(TopGap.SMALL) + row("") { + kotlinInfo = text("Kotlin support uses a community add-on.").component + } + }.bind(model::language) + buttonsGroup { + row("Build tool") { + for (el in StarterSupport.buildTools) { + val r = radioButton(el.value, el.key).onChanged { refreshSupport() } + all["buildTools"]!![r.component] = el.key + } + }.topGap(TopGap.SMALL) + }.bind(model::buildTool) + buttonsGroup { + row("Architecture") { + for (el in StarterSupport.architectures.entries) { + val r = radioButton(el.value, el.key).onChanged { refreshSupport() } + all["architectures"]!![r.component] = el.key + } + } + row("") { + notAllArchitecturesSupportedMessage = text("").component + } + }.bind(model::architecture) + } + + /** + * Enable / disable radio buttons depending on support matrix + */ + private fun refreshSupport() { + // apply model updates + root?.apply() ?: null + refreshGroup(all["frameworks"]!!, StarterSupport::isSupportedFramework) + refreshGroup(all["languages"]!!, StarterSupport::isSupportedLanguage) + refreshGroup(all["buildTools"]!!, StarterSupport::isSupportedBuildTool) + refreshGroup(all["architectures"]!!, StarterSupport::isSupportedArchitecture) + refreshKotlinMessage() + refreshArchitecturesSupportedMessage() + } + + private fun refreshArchitecturesSupportedMessage() { + if (StarterSupport.supportsAllArchitectures(model)) { + notAllArchitecturesSupportedMessage?.isVisible = false + } else { + notAllArchitecturesSupportedMessage?.isVisible = true + val frameworkName = StarterSupport.frameworks[model.framework] + notAllArchitecturesSupportedMessage?.text = "$frameworkName does not support all architectures." + } + } + + private fun refreshKotlinMessage() { + kotlinInfo!!.isVisible = model.language == "kotlin" + } + + /** + * Checks all JRadioButtons in given group if they should be disabled, fallbacks to first enabled + */ + private fun refreshGroup( + group: HashMap, + supportCheck: (model: StarterModel, framework: String) -> Boolean + ) { + var selectFirstEnabled = false + group.forEach { + it.key.isEnabled = supportCheck(model, it.value) + if (!it.key.isEnabled && it.key.isSelected) { + selectFirstEnabled = true + } + } + if (selectFirstEnabled) { + group.filterKeys { it.isEnabled }.first().key.isSelected = true + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vaadin/plugin/module/VaadinCustomOptionsStep.kt b/src/main/kotlin/com/vaadin/plugin/module/VaadinCustomOptionsStep.kt new file mode 100644 index 00000000..e47c9076 --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/module/VaadinCustomOptionsStep.kt @@ -0,0 +1,18 @@ +package com.vaadin.plugin.module + +import com.intellij.ide.util.projectWizard.ModuleWizardStep +import javax.swing.JComponent + +class VaadinCustomOptionsStep(private val builder: VaadinModuleBuilder) : ModuleWizardStep() { + + private val panel = VaadinPanel() + + override fun getComponent(): JComponent { + return panel.getComponent() + } + + override fun updateDataModel() { + builder.setModel(panel.getModel()) + } + +} diff --git a/src/main/kotlin/com/vaadin/plugin/module/VaadinModuleBuilder.kt b/src/main/kotlin/com/vaadin/plugin/module/VaadinModuleBuilder.kt new file mode 100644 index 00000000..f56b463a --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/module/VaadinModuleBuilder.kt @@ -0,0 +1,50 @@ +package com.vaadin.plugin.module + +import com.intellij.ide.util.projectWizard.ModuleBuilder +import com.intellij.ide.util.projectWizard.ModuleWizardStep +import com.intellij.ide.util.projectWizard.WizardContext +import com.intellij.openapi.Disposable +import com.intellij.openapi.module.ModifiableModuleModel +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleType +import com.intellij.openapi.observable.properties.PropertyGraph +import com.intellij.openapi.project.ProjectType +import com.vaadin.plugin.starter.DownloadableModel +import com.vaadin.plugin.utils.VaadinProjectUtil + +class VaadinModuleBuilder : ModuleBuilder() { + + private val propertyGraph = PropertyGraph() + + private val projectDownloadedProperty = propertyGraph.property(false) + + private var model: DownloadableModel? = null + + override fun getBuilderId(): String { + return "vaadin" + } + + override fun getModuleType(): ModuleType<*> { + return VaadinModuleType("VaadinModule") + } + + override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?): ModuleWizardStep { + return VaadinCustomOptionsStep(this) + } + + fun setModel(model: DownloadableModel) { + this.model = model + } + + override fun createModule(moduleModel: ModifiableModuleModel): Module { + val project = moduleModel.project + project.putUserData(VaadinProjectUtil.PROJECT_DOWNLOADED_PROP_KEY, projectDownloadedProperty) + VaadinProjectUtil.downloadAndExtract(project, this.model!!.getDownloadLink(project)) + return super.createModule(moduleModel) + } + + override fun getProjectType(): ProjectType? { + return this.model?.let { ProjectType.create(it.getProjectType()) } + } + +} diff --git a/src/main/kotlin/com/vaadin/plugin/module/VaadinModuleType.kt b/src/main/kotlin/com/vaadin/plugin/module/VaadinModuleType.kt new file mode 100644 index 00000000..69c841b2 --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/module/VaadinModuleType.kt @@ -0,0 +1,26 @@ +package com.vaadin.plugin.module + +import com.intellij.openapi.module.ModuleType +import com.intellij.openapi.util.IconLoader +import org.jetbrains.annotations.NonNls +import javax.swing.Icon + +class VaadinModuleType(id: @NonNls String) : ModuleType(id) { + + override fun createModuleBuilder(): VaadinModuleBuilder { + return VaadinModuleBuilder() + } + + override fun getName(): String { + return "Vaadin" + } + + override fun getDescription(): String { + return "Create Vaadin application" + } + + override fun getNodeIcon(isOpened: Boolean): Icon { + return IconLoader.getIcon("/META-INF/pluginIcon.svg", javaClass.classLoader) + } + +} diff --git a/src/main/kotlin/com/vaadin/plugin/module/VaadinPanel.kt b/src/main/kotlin/com/vaadin/plugin/module/VaadinPanel.kt new file mode 100644 index 00000000..93a01105 --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/module/VaadinPanel.kt @@ -0,0 +1,59 @@ +package com.vaadin.plugin.module + +import com.intellij.ide.wizard.withVisualPadding +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.dsl.builder.CollapsibleRow +import com.intellij.ui.dsl.builder.panel +import com.vaadin.plugin.starter.DownloadableModel + +class VaadinPanel { + + private var dialogPanel: DialogPanel? = null + + private var quickStarterGroup: CollapsibleRow? = null + private var skeletonStarterGroup: CollapsibleRow? = null + + private val quickStarterPanel = QuickStarterPanel() + private val skeletonStarterPanel = SkeletonStarterPanel() + + init { + dialogPanel = panel { + quickStarterGroup = collapsibleGroup("Project Settings") { + row {}.cell(quickStarterPanel.root) + } + + skeletonStarterGroup = collapsibleGroup("Hello World Projects") { + row {}.cell(skeletonStarterPanel.root) + } + row { + text( + "Flow framework is the most productive" + + " choice, allowing 100% of the user
interface to be coded in server-side Java." + ) + } + row { + text( + "Hilla framework, on the other hand, enables" + + " implementation of your user
interface with React while automatically connecting it to your" + + " Java backend." + ) + } + row { + text("For more configuration options, visit start.vaadin.com") + } + }.withVisualPadding(true) + + quickStarterGroup!!.expanded = true + quickStarterGroup!!.addExpandedListener { if (it) skeletonStarterGroup!!.expanded = false } + skeletonStarterGroup!!.addExpandedListener { if (it) quickStarterGroup!!.expanded = false } + } + + fun getComponent(): DialogPanel { + return dialogPanel!! + } + + fun getModel(): DownloadableModel { + return if (quickStarterGroup!!.expanded) quickStarterPanel.model else skeletonStarterPanel.model + } + +} diff --git a/src/main/kotlin/com/vaadin/plugin/starter/DownloadableModel.kt b/src/main/kotlin/com/vaadin/plugin/starter/DownloadableModel.kt new file mode 100644 index 00000000..da213abf --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/starter/DownloadableModel.kt @@ -0,0 +1,11 @@ +package com.vaadin.plugin.starter + +import com.intellij.openapi.project.Project + +interface DownloadableModel { + + fun getDownloadLink(project: Project): String + + fun getProjectType(): String + +} \ No newline at end of file diff --git a/src/main/kotlin/com/vaadin/plugin/starter/QuickStarterModel.kt b/src/main/kotlin/com/vaadin/plugin/starter/QuickStarterModel.kt new file mode 100644 index 00000000..ecdb0ea8 --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/starter/QuickStarterModel.kt @@ -0,0 +1,34 @@ +package com.vaadin.plugin.starter + +import com.intellij.openapi.components.BaseState +import com.intellij.openapi.project.Project + +class QuickStarterModel( + var views: String, + var authentication: Boolean, + var version: String +) : BaseState(), DownloadableModel { + + override fun getDownloadLink(project: Project): String { + var preset = if (views.contains("Flow")) { + "default" + } else if (views.contains("Hilla")) { + "react" + } else { + "empty" + } + + if (authentication) { + preset += "&preset=partial-auth" + } + if (version === "Prerelease") { + preset += "&preset=partial-prerelease" + } + + return "https://start.vaadin.com/dl?preset=${preset}&projectName=${project.name}" + } + + override fun getProjectType(): String { + return "maven" + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/vaadin/plugin/starter/StarterModel.kt b/src/main/kotlin/com/vaadin/plugin/starter/StarterModel.kt new file mode 100644 index 00000000..1ad9f2ae --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/starter/StarterModel.kt @@ -0,0 +1,38 @@ +package com.vaadin.plugin.starter + +import com.intellij.openapi.project.Project + +class StarterModel( + var framework: String, // vaadin version / hilla-lit / hilla-react + var language: String, // 17 / kotlin + var buildTool: String, // maven / gradle + var architecture: String // springboot / servlet / ... +) : DownloadableModel { + + override fun getDownloadLink(project: Project): String { + var key: String + if (framework.contains("hilla")) { + key = framework + if (buildTool == "gradle") { + key += "-gradle" + } + return StarterSupport.downloadLinks[key]!! + } + + if (language == "kotlin") { + key = "kotlin" + } else if (buildTool == "gradle") { + key = "gradle-$architecture" + } else { + key = architecture + } + + val link = StarterSupport.downloadLinks[key] ?: "#" + return link.replace("", framework) + } + + override fun getProjectType(): String { + return buildTool + } + +} diff --git a/src/main/kotlin/com/vaadin/plugin/starter/StarterSupport.kt b/src/main/kotlin/com/vaadin/plugin/starter/StarterSupport.kt new file mode 100644 index 00000000..35c42a7c --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/starter/StarterSupport.kt @@ -0,0 +1,104 @@ +package com.vaadin.plugin.starter + +class StarterSupport { + + companion object { + + val frameworks = linkedMapOf( + "24" to "Flow / Java", + "hilla-react" to "Hilla / React" + ) + + val languages = linkedMapOf( + "17" to "Java", + "kotlin" to "Kotlin" + ) + + val architectures = linkedMapOf( + "springboot" to "Spring Boot", + "quarkus" to "Quarkus", + "jakartaee" to "Jakarta EE", + "servlet" to "Servlet" + ) + + val buildTools = linkedMapOf( + "maven" to "Maven", + "gradle" to "Gradle" + ) + + val downloadLinks = mapOf( + "hilla-react" to "https://github.com/vaadin/skeleton-starter-hilla-react/archive/master.zip", + "hilla-react-gradle" to "https://github.com/vaadin/skeleton-starter-hilla-react-gradle/archive/master.zip", + "kotlin" to "https://github.com/vaadin/skeleton-starter-kotlin-spring/archive/master.zip", + "gradle-servlet" to "https://github.com/vaadin/base-starter-gradle/archive/v.zip", + "gradle-springboot" to "https://github.com/vaadin/base-starter-spring-gradle/archive/v.zip", + "springboot" to "https://github.com/vaadin/skeleton-starter-flow-spring/archive/v.zip", + "quarkus" to "https://github.com/vaadin/base-starter-flow-quarkus/archive/v.zip", + "jakartaee" to "https://github.com/vaadin/skeleton-starter-flow-cdi/archive/v.zip", + "servlet" to "https://github.com/vaadin/skeleton-starter-flow/archive/v.zip", + ) + + private val supportMatrix = arrayOf( + StarterSupportMatrixElement( + "24", + languages.keys, + setOf("springboot", "quarkus", "jakartaee", "servlet"), + buildTools.keys, + 17 + ), + StarterSupportMatrixElement( + "hilla-react", + setOf("17"), + setOf("springboot"), + buildTools.keys, + 17 + ), + ) + + + fun isSupportedFramework(model: StarterModel, framework: String): Boolean { + val foundSupport = getSupport(framework) ?: return false + return try { + (Integer.parseInt(model.language) >= foundSupport.javaMinVersion + && foundSupport.architectures.contains(model.architecture)) + } catch (e: NumberFormatException) { + true // kotlin + } + } + + fun isSupportedLanguage(model: StarterModel, language: String): Boolean { + val foundSupport = getSupport(model.framework) ?: return false + return foundSupport.languages.contains(language) + } + + fun isSupportedArchitecture(model: StarterModel, architecture: String): Boolean { + val foundSupport = getSupport(model.framework) ?: return false + if (model.buildTool == "gradle") { + return foundSupport.architectures.contains(architecture) && setOf("springboot", "servlet").contains( + architecture + ) + } else if (model.language == "kotlin") { + return architecture == "springboot" + } + return foundSupport.architectures.contains(architecture) + } + + fun isSupportedBuildTool(model: StarterModel, buildTool: String): Boolean { + val foundSupport = getSupport(model.framework) ?: return false + if (model.language == "kotlin") { + return foundSupport.buildTools.contains(buildTool) && buildTool == "maven" + } + return foundSupport.buildTools.contains(buildTool) + } + + fun supportsAllArchitectures(model: StarterModel): Boolean { + return getSupport(model.framework)?.architectures?.containsAll(architectures.keys)!! + } + + private fun getSupport(framework: String): StarterSupportMatrixElement? { + return supportMatrix.find { it.framework == framework } + } + + } + +} diff --git a/src/main/kotlin/com/vaadin/plugin/starter/StarterSupportMatrixElement.kt b/src/main/kotlin/com/vaadin/plugin/starter/StarterSupportMatrixElement.kt new file mode 100644 index 00000000..a537654d --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/starter/StarterSupportMatrixElement.kt @@ -0,0 +1,9 @@ +package com.vaadin.plugin.starter + +data class StarterSupportMatrixElement( + val framework: String, + val languages: Collection, + val architectures: Collection, + val buildTools: Collection, + val javaMinVersion: Int +) diff --git a/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt b/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt new file mode 100644 index 00000000..c5636d0b --- /dev/null +++ b/src/main/kotlin/com/vaadin/plugin/utils/VaadinProjectUtil.kt @@ -0,0 +1,82 @@ +package com.vaadin.plugin.utils + +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.notification.Notifications +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.observable.properties.GraphProperty +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.util.download.DownloadableFileService +import com.intellij.util.io.ZipUtil +import java.io.File +import java.io.IOException +import java.nio.file.Path +import java.util.* +import java.util.zip.ZipFile + +class VaadinProjectUtil { + + companion object { + + private val LOG: Logger = Logger.getInstance(VaadinProjectUtil::class.java) + + val PROJECT_DOWNLOADED_PROP_KEY = Key>("vaadin_project_downloaded") + + private const val NOTIFICATION_GROUP = "Vaadin" + + fun downloadAndExtract(project: Project, url: String) { + val filename = "project.zip" + LOG.info("Downloading $filename") + val basePath: String = project.basePath!! + val downloadedFile = File(basePath, filename) + LOG.info("File saved to $downloadedFile") + val description = DownloadableFileService.getInstance().createFileDescription(url, filename) + val downloader = + DownloadableFileService.getInstance().createDownloader(listOf(description), "Vaadin Starter Project") + + downloader.downloadWithBackgroundProgress(basePath, project).thenApply { + LOG.info("Extracting $downloadedFile") + ZipUtil.extract(downloadedFile.toPath(), Path.of(basePath), null) + // move contents from single zip directory + getZipRootFolder(downloadedFile)?.let { + LOG.info("Zip contains single directory $it, moving to $basePath") + FileUtil.copyDirContent(File(basePath, it), File(basePath)) + FileUtil.delete(File(basePath, it)) + } + FileUtil.delete(downloadedFile) + LOG.info("$downloadedFile deleted") + VirtualFileManager.getInstance().syncRefresh() + (project.getUserData(PROJECT_DOWNLOADED_PROP_KEY) as GraphProperty).set(true) + } + } + + @Throws(IOException::class) + fun getZipRootFolder(zip: File): String? { + ZipFile(zip).use { zipFile -> + val en = zipFile.entries() + while (en.hasMoreElements()) { + val zipEntry = en.nextElement() + // we do not necessarily get a separate entry for the subdirectory when the file + // in the ZIP archive is placed in a subdirectory, so we need to check if the slash + // is found anywhere in the path + val indexOf = zipEntry.name.indexOf('/') + if (indexOf >= 0) { + return zipEntry.name.substring(0, indexOf) + } + } + return null + } + } + + fun notify(content: String, type: NotificationType, project: Project?) { + Notifications.Bus.notify( + Notification(NOTIFICATION_GROUP, content, type), project + ) + } + + } + +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5ac79788..b6e28d65 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -34,6 +34,7 @@ + @@ -41,6 +42,11 @@ + + + + + diff --git a/src/main/resources/META-INF/pluginIcon.svg b/src/main/resources/META-INF/pluginIcon.svg index 53c00cc9..e19da390 100644 --- a/src/main/resources/META-INF/pluginIcon.svg +++ b/src/main/resources/META-INF/pluginIcon.svg @@ -1,4 +1,4 @@ - + diff --git a/src/main/resources/icons/module.svg b/src/main/resources/icons/module.svg new file mode 100644 index 00000000..e19da390 --- /dev/null +++ b/src/main/resources/icons/module.svg @@ -0,0 +1,4 @@ + + + +