feat: scaffold

This commit is contained in:
Johannes Frohnmeyer 2024-05-04 13:09:22 +02:00
commit 8931e85658
Signed by: Johannes
GPG Key ID: E76429612C2929F4
26 changed files with 633 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

10
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
/checkstyle-idea.xml
/git_toolbox_prj.xml

18
.idea/gradle.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/share/java/gradle" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

6
.idea/kotlinc.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.22" />
</component>
</project>

25
.idea/misc.xml Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="MarkdownSettingsMigration">
<option name="stateVersion" value="1" />
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State />
<State>
<id>Checkstyle</id>
</State>
</expanded-state>
</profile-state>
</entry>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

12
.idea/vcs.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CommitMessageInspectionProfile">
<profile version="1.0">
<inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -0,0 +1,24 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Plugin" type="GradleRunConfiguration" factoryName="Gradle">
<log_file alias="idea.log" path="$PROJECT_DIR$/build/idea-sandbox/system/log/idea.log"/>
<ExternalSystemSettings>
<option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value=""/>
<option name="taskDescriptions">
<list/>
</option>
<option name="taskNames">
<list>
<option value="runIde"/>
</list>
</option>
<option name="vmOptions" value=""/>
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2"/>
</configuration>
</component>

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# S-DOM
## References
- [DOMjudge](https://www.domjudge.org/documentation)
- [DOMjudge API](https://www.domjudge.org/demoweb/api/doc)
- [SimpleCodeTester plugin](https://github.com/Mr-Pine/SimpleCodeTester-IntelliJ-Plugin)
- [Example Route](https://domjudge.iti.kit.edu/main/api/v4/contests/5/problems)

52
build.gradle.kts Normal file
View File

@ -0,0 +1,52 @@
plugins {
id("java")
id("org.jetbrains.kotlin.jvm") version "1.9.22"
id("org.jetbrains.intellij") version "1.17.2"
kotlin("plugin.serialization") version "1.9.22"
}
group = "io.gitlab.jfronny"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
// Configure Gradle IntelliJ Plugin
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {
version.set("2023.2.5")
type.set("IC") // Target IDE Platform
plugins.set(listOf(/* Plugin Dependencies */))
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
tasks {
// Set the JVM compatibility versions
withType<JavaCompile> {
sourceCompatibility = "17"
targetCompatibility = "17"
}
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}
patchPluginXml {
sinceBuild.set("232")
untilBuild.set("242.*")
}
signPlugin {
certificateChain.set(System.getenv("CERTIFICATE_CHAIN"))
privateKey.set(System.getenv("PRIVATE_KEY"))
password.set(System.getenv("PRIVATE_KEY_PASSWORD"))
}
publishPlugin {
token.set(System.getenv("PUBLISH_TOKEN"))
}
}

6
gradle.properties Normal file
View File

@ -0,0 +1,6 @@
# Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib
kotlin.stdlib.default.dependency=false
# Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html
org.gradle.configuration-cache=true
# Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html
org.gradle.caching=true

8
settings.gradle.kts Normal file
View File

@ -0,0 +1,8 @@
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
rootProject.name = "s-dom"

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.sdom
import com.intellij.credentialStore.CredentialAttributes
import com.intellij.credentialStore.Credentials
import com.intellij.credentialStore.generateServiceName
import com.intellij.ide.passwordSafe.PasswordSafe
import com.intellij.openapi.application.ApplicationManager
object SDCredentials {
private fun createCredentialAttributes(): CredentialAttributes {
return CredentialAttributes(generateServiceName("s-dom", "httpAuth"))
}
var credentials: Pair<String?, String?>
get() = PasswordSafe.instance.get(createCredentialAttributes())
.run { this?.userName to this?.getPasswordAsString() }
set(value) {
PasswordSafe.instance[createCredentialAttributes()] = Credentials(value.first, value.second)
}
fun logOut() {
PasswordSafe.instance.set(createCredentialAttributes(), null)
}
var url: String
get() = ApplicationManager.getApplication().getService(SDSettings::class.java).state.url ?: "https://domjudge.iti.kit.edu/main/api"
set(value) {
ApplicationManager.getApplication().getService(SDSettings::class.java).state.url = value
}
}

View File

@ -0,0 +1,11 @@
package io.gitlab.jfronny.sdom
import com.intellij.openapi.components.*
@Service
@State(name = "S-DOM", storages = [Storage("s-dom.xml")])
class SDSettings : SimplePersistentStateComponent<SDSettings.SDState>(SDState()) {
class SDState : BaseState() {
var url by string("https://domjudge.iti.kit.edu/main/api")
}
}

View File

@ -0,0 +1,66 @@
package io.gitlab.jfronny.sdom
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.LogLevel
import com.intellij.openapi.diagnostic.Logger
import io.gitlab.jfronny.sdom.model.SDLoginResult
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json
object SDom {
private val logger = Logger.getInstance(SDom.javaClass).apply { setLevel(LogLevel.DEBUG) }
private var propertiesComponent: PropertiesComponent? = null
private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
install(Auth) {
basic {
credentials {
BasicAuthCredentials(SDCredentials.credentials.first ?: "", SDCredentials.credentials.second ?: "")
}
}
}
}
private val loginListeners: MutableList<() -> Unit> = mutableListOf() //TODO add listener for refreshing tasks
private val logoutListeners: MutableList<() -> Unit> = mutableListOf()
fun registerLoginListener(listener: () -> Unit) {
loginListeners.add(listener)
}
fun registerLogoutListener(listener: () -> Unit) {
logoutListeners.add(listener)
}
suspend fun login(username: String, password: String, url: String) {
val fixedApi = url.trimEnd('/') + "/v4/"
val result: SDLoginResult = client.get(url = Url(fixedApi + "user")).body()
if (!result.enabled) throw Exception("User is not enabled")
SDCredentials.credentials = Pair(username, password)
SDCredentials.url = fixedApi
loginListeners.forEach { ApplicationManager.getApplication().invokeLater(it) }
logger.debug("Logged in as $username")
}
fun logout() {
SDCredentials.logOut()
logoutListeners.forEach { ApplicationManager.getApplication().invokeLater(it) }
}
val loggedIn: Boolean
get() = SDCredentials.credentials.first != null && SDCredentials.credentials.second != null
}

View File

@ -0,0 +1,30 @@
package io.gitlab.jfronny.sdom.actions
import com.intellij.notification.Notification
import com.intellij.notification.NotificationAction
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import io.gitlab.jfronny.sdom.SDom
import io.gitlab.jfronny.sdom.ui.SDLoginDialogWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class SDLoginAction(text: String) : NotificationAction(text) {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
constructor() : this("")
override fun actionPerformed(e: AnActionEvent, notification: Notification) = actionPerformed(e)
override fun actionPerformed(e: AnActionEvent) {
val dialogWrapper = SDLoginDialogWrapper()
if (dialogWrapper.showAndGet()) {
CoroutineScope(Job() + Dispatchers.IO).launch {
SDom.login(username = dialogWrapper.username, password = dialogWrapper.password, url = dialogWrapper.url)
}
}
}
}

View File

@ -0,0 +1,16 @@
package io.gitlab.jfronny.sdom.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareAction
import io.gitlab.jfronny.sdom.SDom
class SDLogoutAction : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) = SDom.logout()
override fun update(e: AnActionEvent) {
e.presentation.isVisible = SDom.loggedIn
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
}

View File

@ -0,0 +1,23 @@
package io.gitlab.jfronny.sdom.model
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class SDLoginResult(
@SerialName("last_login_time") val lastLoginTime: LocalDateTime,
@SerialName("last_api_login_time") val lastApiLoginTime: LocalDateTime,
@SerialName("first_login_time") val firstLoginTime: LocalDateTime,
val team: String,
@SerialName("team_id") val teamId: Int,
val roles: List<String>,
val type: String,
val id: String,
val username: String,
val name: String,
val email: String,
@SerialName("last_ip") val lastIp: String,
val ip: String,
val enabled: Boolean
)

View File

@ -0,0 +1,36 @@
package io.gitlab.jfronny.sdom.toolwindow
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.ui.DialogPanel
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.AlignY
import com.intellij.ui.dsl.builder.panel
import io.gitlab.jfronny.sdom.actions.SDLoginAction
fun SDToolWindow(): DialogPanel = panel {
row {
panel {
row {
label("You are not logged in. Please log in to use this tool.").align(Align.CENTER)
}
row {
button("Log In") {
SDLoginAction().actionPerformed(
AnActionEvent(
null,
DataContext.EMPTY_CONTEXT,
ActionPlaces.UNKNOWN,
Presentation(),
ActionManager.getInstance(),
0
)
)
}.align(Align.CENTER)
}
}.resizableColumn().align(AlignY.CENTER)
}.resizableRow()
}

View File

@ -0,0 +1,44 @@
package io.gitlab.jfronny.sdom.toolwindow
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import io.gitlab.jfronny.sdom.SDom
import io.gitlab.jfronny.sdom.ui.SDSubmitPanel
class SDToolWindowFactory : ToolWindowFactory, DumbAware {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val contentManager = toolWindow.contentManager
val loggedOutContent = contentManager.factory.createContent(loggedOutDialogPanel(), null, false).apply {
isCloseable = false
}
val submitContent = contentManager.factory.createContent(SDSubmitPanel(), "Submit", false).apply {
isCloseable = false
}
fun showSubmitContent() {
contentManager.addContent(submitContent)
contentManager.removeContent(loggedOutContent, true)
}
fun showLoggedOutContent() {
contentManager.removeContent(submitContent, true)
contentManager.addContent(loggedOutContent)
}
fun showResultContent() {
// TODO
}
SDom.registerLoginListener(::showSubmitContent)
SDom.registerLogoutListener(::showLoggedOutContent)
SDom.registerResultFlowListener(::showResultContent)
if (SDom.loggedIn) {
showSubmitContent()
} else {
contentManager.addContent(loggedOutContent)
}
}
}

View File

@ -0,0 +1,25 @@
package io.gitlab.jfronny.sdom.ui
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.openapi.actionSystem.ActionManager
import org.jetbrains.annotations.NonNls
import javax.swing.Box
import javax.swing.JComponent
class SDActionToolBar(orientation: ToolBarOrientation, place: @NonNls String = "???") {
private val actionGroup = ActionManager.getInstance()
.getAction("io.gitlab.jfronny.sdom.actions.SDToolbarActions") as ActionGroup
private val toolbar = ActionManager.getInstance().createActionToolbar(place, actionGroup, orientation.value)
fun setTargetComponent(component: JComponent) {
toolbar.targetComponent = component
}
val component: Box
get() = Box.createHorizontalBox().apply { add(toolbar.component) }
}
enum class ToolBarOrientation(val value: Boolean) {
HORIZONTAL(true), VERTICAL(false)
}

View File

@ -0,0 +1,35 @@
package io.gitlab.jfronny.sdom.ui
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.bindText
import com.intellij.ui.dsl.builder.panel
import io.gitlab.jfronny.sdom.SDCredentials
import javax.swing.JComponent
class SDLoginDialogWrapper : DialogWrapper(true) {
var username = ""
var password = ""
var url = ""
init {
title = "Log in to DOMjudge"
url = SDCredentials.url
init()
}
override fun createCenterPanel(): JComponent = panel {
row {
label("Username")
textField().align(AlignX.FILL).bindText(this@SDLoginDialogWrapper::username)
}
row {
label("Password")
passwordField().align(AlignX.FILL).bindText(this@SDLoginDialogWrapper::password)
}
row {
label("API URL")
textField().align(AlignX.FILL).bindText(this@SDLoginDialogWrapper::url)
}
}
}

View File

@ -0,0 +1,24 @@
package io.gitlab.jfronny.sdom.ui
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComponentContainer
import io.gitlab.jfronny.sdom.model.SDResult
import kotlinx.coroutines.flow.Flow
import javax.swing.JComponent
class SDResultPanel(
val project: Project,
resultFlow: Flow<Result<SDResult>>
) : ComponentContainer {
override fun dispose() {
TODO("Not yet implemented")
}
override fun getComponent(): JComponent {
TODO("Not yet implemented")
}
override fun getPreferredFocusableComponent(): JComponent {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,17 @@
package io.gitlab.jfronny.sdom.ui
import com.intellij.ui.components.JBLabel
import java.awt.BorderLayout
import javax.swing.JPanel
class SDSubmitPanel : JPanel(BorderLayout()) {
init {
val label = JBLabel("Nothing to see here (yet)")
val toolWindowPanel = SDToolWindowPanel(
topComponent = SDActionToolBar(ToolBarOrientation.HORIZONTAL).apply { setTargetComponent(label) }.component,
mainComponent = label
)
add(toolWindowPanel.getPanel())
isVisible = true
}
}

View File

@ -0,0 +1,21 @@
package io.gitlab.jfronny.sdom.ui
import com.intellij.util.ui.components.BorderLayoutPanel
import java.awt.BorderLayout
import javax.swing.JComponent
class SDToolWindowPanel(
private val topComponent: JComponent? = null,
private val leftComplement: JComponent? = null,
private val mainComponent: JComponent? = null,
private val rightComplement: JComponent? = null
) {
private val basePanel = BorderLayoutPanel(1, 1).apply {
mainComponent?.let { add(it, BorderLayout.CENTER) }
topComponent?.let { add(it, BorderLayout.NORTH) }
leftComplement?.let { add(it, BorderLayout.WEST) }
rightComplement?.let { add(it, BorderLayout.EAST) }
}
fun getPanel() = basePanel
}

View File

@ -0,0 +1,32 @@
<!-- Plugin Configuration File. Read more: https://plugins.jetbrains.com/docs/intellij/plugin-configuration-file.html -->
<idea-plugin>
<!-- Unique identifier of the plugin. It should be FQN. It cannot be changed between the plugin versions. -->
<id>io.gitlab.jfronny.s-dom</id>
<!-- Public plugin name should be written in Title Case.
Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-name -->
<name>S-dom</name>
<!-- A displayed Vendor name or Organization ID displayed on the Plugins Page. -->
<vendor email="projects.contact@frohnmeyer-wds.de" url="https://jfronny.gitlab.io">JFronny</vendor>
<!-- Description of the plugin displayed on the Plugin Page and IDE Plugin Manager.
Simple HTML elements (text formatting, paragraphs, and lists) can be added inside of <![CDATA[ ]]> tag.
Guidelines: https://plugins.jetbrains.com/docs/marketplace/plugin-overview-page.html#plugin-description -->
<description><![CDATA[
Simple plugin to upload potential solutions to a DOMjudge instance.<br>
Intended mostly for use in the context of the KITs "Basispraktikum zum ICPC-Programmierwettbewerb" course.<br>
]]></description>
<!-- Product and plugin compatibility requirements.
Read more: https://plugins.jetbrains.com/docs/intellij/plugin-compatibility.html -->
<depends>com.intellij.modules.platform</depends>
<!-- Extension points defined by the plugin.
Read more: https://plugins.jetbrains.com/docs/intellij/plugin-extension-points.html -->
<extensions defaultExtensionNs="com.intellij">
<toolWindow factoryClass="io.gitlab.jfronny.sdom.toolwindow.SDToolWindowFactory"
id="S-DOM" anchor="bottom" canCloseContents="true" icon="io.gitlab.jfronny.sdom.icons.ToolWindow" />
<notificationGroup id="sdom.notifications" displayType="BALLOON" />
</extensions>
</idea-plugin>

View File

@ -0,0 +1,12 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M32.0845 7.94025V4H24.0203V7.9896H16.029V4H7.91553V7.94025H4V36H16.0044V32.0045C16.0058 30.9457 16.4274 29.9308 17.1766 29.1826C17.9258 28.4345 18.9412 28.0143 20 28.0143C21.0588 28.0143 22.0743 28.4345 22.8234 29.1826C23.5726 29.9308 23.9942 30.9457 23.9956 32.0045V36H36V7.94025H32.0845Z"
fill="url(#paint0_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="2.94192" y1="4.89955" x2="37.7772" y2="39.7345"
gradientUnits="userSpaceOnUse">
<stop offset="0.15937" stop-color="#3BEA62"/>
<stop offset="0.5404" stop-color="#3C99CC"/>
<stop offset="0.93739" stop-color="#6B57FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 818 B