Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: module starter #35

Merged
merged 20 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 29 additions & 3 deletions .github/workflows/validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/[email protected]
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
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

}
28 changes: 28 additions & 0 deletions src/main/kotlin/com/vaadin/plugin/module/QuickStarterPanel.kt
Original file line number Diff line number Diff line change
@@ -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
}
}

}
120 changes: 120 additions & 0 deletions src/main/kotlin/com/vaadin/plugin/module/SkeletonStarterPanel.kt
Original file line number Diff line number Diff line change
@@ -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<JRadioButton, String>(),
"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("<i>Kotlin support uses a community add-on.</i>").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 = "<i>$frameworkName does not support all architectures.</i>"
}
}

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<JRadioButton, String>,
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
}
}

}
Original file line number Diff line number Diff line change
@@ -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())
}

}
50 changes: 50 additions & 0 deletions src/main/kotlin/com/vaadin/plugin/module/VaadinModuleBuilder.kt
Original file line number Diff line number Diff line change
@@ -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()) }
}

}
26 changes: 26 additions & 0 deletions src/main/kotlin/com/vaadin/plugin/module/VaadinModuleType.kt
Original file line number Diff line number Diff line change
@@ -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<VaadinModuleBuilder>(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)
}

}
59 changes: 59 additions & 0 deletions src/main/kotlin/com/vaadin/plugin/module/VaadinPanel.kt
Original file line number Diff line number Diff line change
@@ -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(
"<a href=\"https://vaadin.com/flow\">Flow framework</a> is the most productive" +
" choice, allowing 100% of the user<br>interface to be coded in server-side Java."
)
}
row {
text(
"<a href=\"https://hilla.dev/\">Hilla framework</a>, on the other hand, enables" +
" implementation of your user<br>interface with React while automatically connecting it to your" +
" Java backend."
)
}
row {
text("For more configuration options, visit <a href=\"https://start.vaadin.com\">start.vaadin.com</a>")
}
}.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
}

}
11 changes: 11 additions & 0 deletions src/main/kotlin/com/vaadin/plugin/starter/DownloadableModel.kt
Original file line number Diff line number Diff line change
@@ -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

}
Loading