mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[space] Extract Space plugin sources from Community
GitOrigin-RevId: e872d745c76e5ebcb66cdd9ac0cf228bdead4998
This commit is contained in:
committed by
intellij-monorepo-bot
parent
eb4c713239
commit
a2ddafffd7
Generated
-5
@@ -51,11 +51,6 @@
|
||||
<option name="name" value="36870030-7a21-4052-b93a-010aa4c33366" />
|
||||
<option name="url" value="https://cache-redirector.jetbrains.com/download.jetbrains.com/teamcity-repository" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="JetBrainsSpacePublicMaven" />
|
||||
<option name="name" value="JetBrains Space Public Maven" />
|
||||
<option name="url" value="https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/public/p/space/maven" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="caeb020e-2153-45d1-b752-f56d66968d08" />
|
||||
<option name="name" value="caeb020e-2153-45d1-b752-f56d66968d08" />
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jackson-datatype-joda" type="repository">
|
||||
<properties maven-id="com.fasterxml.jackson.datatype:jackson-datatype-joda:2.12.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.fasterxml.jackson.core:jackson-annotations" />
|
||||
<dependency maven-id="com.fasterxml.jackson.core:jackson-core" />
|
||||
<dependency maven-id="com.fasterxml.jackson.core:jackson-databind" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/datatype/jackson-datatype-joda/2.12.2/jackson-datatype-joda-2.12.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/joda-time/joda-time/2.10.8/joda-time-2.10.8.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-33
@@ -1,33 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="ktor-websockets" type="repository">
|
||||
<properties maven-id="io.ktor:ktor-websockets:1.5.0">
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-jdk8" />
|
||||
<dependency maven-id="org.slf4j:slf4j-api" />
|
||||
<dependency maven-id="org.jetbrains:annotations" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-reflect" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk7" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="com.typesafe:config" />
|
||||
<dependency maven-id="io.ktor:ktor-http-cio-jvm" />
|
||||
<dependency maven-id="io.ktor:ktor-http-jvm" />
|
||||
<dependency maven-id="io.ktor:ktor-io-jvm" />
|
||||
<dependency maven-id="io.ktor:ktor-network" />
|
||||
<dependency maven-id="io.ktor:ktor-server-core" />
|
||||
<dependency maven-id="io.ktor:ktor-utils-jvm" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:atomicfu" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-websockets/1.5.0/ktor-websockets-1.5.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-websockets/1.5.0/ktor-websockets-1.5.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
-43
@@ -1,43 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="space-idea-sdk" type="repository">
|
||||
<properties maven-id="com.jetbrains:space-idea-sdk:1.1.66161-pre">
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-reflect" />
|
||||
<dependency maven-id="com.fasterxml.jackson.core:jackson-core" />
|
||||
<dependency maven-id="com.fasterxml.jackson.core:jackson-databind" />
|
||||
<dependency maven-id="com.fasterxml.jackson.dataformat:jackson-dataformat-xml" />
|
||||
<dependency maven-id="com.fasterxml.jackson.module:jackson-module-kotlin" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core" />
|
||||
<dependency maven-id="org.jetbrains:kotlin-css" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk7" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-slf4j" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-io-jvm" />
|
||||
<dependency maven-id="commons-io:commons-io" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-jdk8" />
|
||||
<dependency maven-id="com.squareup.okhttp3:okhttp" />
|
||||
<dependency maven-id="io.ktor:ktor-client-core" />
|
||||
<dependency maven-id="io.ktor:ktor-client-apache" />
|
||||
<dependency maven-id="io.ktor:ktor-http-jvm" />
|
||||
<dependency maven-id="joda-time:joda-time" />
|
||||
<dependency maven-id="org.reflections:reflections" />
|
||||
<dependency maven-id="ch.qos.logback:logback-classic" />
|
||||
<dependency maven-id="org.apache.logging.log4j:log4j-to-slf4j" />
|
||||
<dependency maven-id="com.fasterxml.jackson.datatype:jackson-datatype-joda" />
|
||||
<dependency maven-id="com.google.guava:guava" />
|
||||
<dependency maven-id="org.slf4j:jul-to-slf4j" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
|
||||
<dependency maven-id="org.apache.ivy:ivy" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-scripting-dependencies" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-scripting-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-scripting-jvm" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/jetbrains/space-idea-sdk/1.1.66161-pre/space-idea-sdk-1.1.66161-pre.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-2
@@ -530,7 +530,6 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/community-guitests/intellij.idea.community.guiTests.iml" filepath="$PROJECT_DIR$/community-guitests/intellij.idea.community.guiTests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intellij.idea.community.main.iml" filepath="$PROJECT_DIR$/intellij.idea.community.main.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intellij.idea.community.main.android.iml" filepath="$PROJECT_DIR$/intellij.idea.community.main.android.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intellij.idea.community.main.space.iml" filepath="$PROJECT_DIR$/intellij.idea.community.main.space.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/community-resources/intellij.idea.community.resources.iml" filepath="$PROJECT_DIR$/community-resources/intellij.idea.community.resources.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/build/launch/intellij.idea.tools.launch.iml" filepath="$PROJECT_DIR$/build/launch/intellij.idea.tools.launch.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/java/openapi/intellij.java.iml" filepath="$PROJECT_DIR$/java/openapi/intellij.java.iml" />
|
||||
@@ -819,7 +818,6 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/settings-repository/intellij.settingsRepository.iml" filepath="$PROJECT_DIR$/plugins/settings-repository/intellij.settingsRepository.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/settings-repository/intellij.settingsRepository.tests.iml" filepath="$PROJECT_DIR$/plugins/settings-repository/intellij.settingsRepository.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/sh/intellij.sh.iml" filepath="$PROJECT_DIR$/plugins/sh/intellij.sh.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/space/intellij.space.iml" filepath="$PROJECT_DIR$/plugins/space/intellij.space.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/spellchecker/intellij.spellchecker.iml" filepath="$PROJECT_DIR$/spellchecker/intellij.spellchecker.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/stats-collector/intellij.statsCollector.iml" filepath="$PROJECT_DIR$/plugins/stats-collector/intellij.statsCollector.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/stats-collector/intellij.statsCollector.tests.iml" filepath="$PROJECT_DIR$/plugins/stats-collector/intellij.statsCollector.tests.iml" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.intellij.build
|
||||
|
||||
import groovy.transform.CompileStatic
|
||||
@@ -85,7 +85,6 @@ abstract class BaseIdeaProperties extends JetBrainsProductProperties {
|
||||
"intellij.webp",
|
||||
"intellij.grazie",
|
||||
"intellij.featuresTrainer",
|
||||
"intellij.space",
|
||||
"intellij.lombok"
|
||||
]
|
||||
|
||||
|
||||
@@ -142,7 +142,6 @@
|
||||
<orderEntry type="module" module-name="intellij.filePrediction" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.textmate" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.webp" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.space" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.packageSearch" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.packageSearch.tests" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.emojipicker" scope="RUNTIME" />
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module relativePaths="true" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="intellij.idea.community.main" scope="RUNTIME" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="KotlinPlugin" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
-13
@@ -336,9 +336,6 @@ class CommunityLibraryLicenses {
|
||||
new LibraryLicense(name: "Jackson", libraryName: "jackson", url: "https://github.com/FasterXML/jackson").apache(),
|
||||
new LibraryLicense(name: "Jackson Databind", libraryName: "jackson-databind",
|
||||
url: "https://github.com/FasterXML/jackson-databind").apache(),
|
||||
new LibraryLicense(name: "Jackson Datatype Joda",
|
||||
libraryName: "jackson-datatype-joda",
|
||||
url: "https://github.com/FasterXML/jackson-datatype-joda").apache(),
|
||||
new LibraryLicense(name: "Jackson Module Kotlin", libraryName: "jackson-module-kotlin",
|
||||
url: "https://github.com/FasterXML/jackson-module-kotlin").apache(),
|
||||
new LibraryLicense(name: "Jakarta ORO", libraryName: "OroMatcher",
|
||||
@@ -449,15 +446,6 @@ class CommunityLibraryLicenses {
|
||||
new LibraryLicense(name: "Kryo", libraryName: "Kryo",
|
||||
url: "https://github.com/EsotericSoftware/kryo",
|
||||
licenseUrl: "https://github.com/EsotericSoftware/kryo/blob/master/LICENSE.md").newBsd(),
|
||||
new LibraryLicense(name: "ktor-client-apache",
|
||||
libraryName: "space-ktor-client-apache",
|
||||
url: "https://github.com/ktorio/ktor").apache(),
|
||||
new LibraryLicense(name: "ktor-server-netty",
|
||||
libraryName: "space-ktor-server-netty",
|
||||
url: "https://github.com/ktorio/ktor").apache(),
|
||||
new LibraryLicense(name: "ktor-websockets",
|
||||
libraryName: "ktor-websockets",
|
||||
url: "https://github.com/ktorio/ktor").apache(),
|
||||
new LibraryLicense(name: "kXML2", libraryName: "kxml2", license: "BSD", url: "http://sourceforge.net/projects/kxml/"),
|
||||
new LibraryLicense(name: "Language Tool", libraryName: "org.languagetool:languagetool-core:5.2",
|
||||
license: "LGPL 2.1",
|
||||
@@ -693,7 +681,6 @@ class CommunityLibraryLicenses {
|
||||
jetbrainsLibrary("rd-framework"),
|
||||
jetbrainsLibrary("rd-swing"),
|
||||
jetbrainsLibrary("rd-text"),
|
||||
jetbrainsLibrary("space-idea-sdk"),
|
||||
jetbrainsLibrary("tcServiceMessages"),
|
||||
jetbrainsLibrary("tips-idea-ce"),
|
||||
jetbrainsLibrary("tips-pycharm-community"),
|
||||
|
||||
-4
@@ -268,10 +268,6 @@ final class CommunityRepositoryModules {
|
||||
bundlingRestrictions.includeInEapOnly = true
|
||||
},
|
||||
plugin("intellij.jps.cache"),
|
||||
plugin("intellij.space") {
|
||||
withProjectLibrary("space-idea-sdk")
|
||||
withProjectLibrary("jackson-datatype-joda")
|
||||
},
|
||||
plugin("intellij.lombok") {
|
||||
withModule("intellij.lombok.generated")
|
||||
},
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src/test/kotlin" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="module" module-name="intellij.vcs.git" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs.log" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs.log.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.externalSystem" />
|
||||
<orderEntry type="module" module-name="intellij.platform.execution.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide" />
|
||||
<orderEntry type="module" module-name="intellij.platform.diff.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.statistics" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit4" level="project" />
|
||||
<orderEntry type="library" name="Guava" level="project" />
|
||||
<orderEntry type="library" name="kotlin-stdlib-jdk8" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-coroutines-jdk8" level="project" />
|
||||
<orderEntry type="library" name="kotlin-reflect" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="kotlin-test" level="project" />
|
||||
<orderEntry type="library" name="miglayout-swing" level="project" />
|
||||
<orderEntry type="module-library">
|
||||
<library name="kotlinx-coroutines-slf4j" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.3.4" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-slf4j/1.3.4/kotlinx-coroutines-slf4j-1.3.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-slf4j/1.3.4/kotlinx-coroutines-slf4j-1.3.4-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-slf4j/1.3.4/kotlinx-coroutines-slf4j-1.3.4-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="ktor-websockets" level="project" />
|
||||
<orderEntry type="module-library">
|
||||
<library name="space-ktor-client-apache" type="repository">
|
||||
<properties maven-id="io.ktor:ktor-client-apache:1.5.2">
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="org.slf4j:slf4j-api" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm" />
|
||||
<dependency maven-id="io.ktor:ktor-http-jvm" />
|
||||
<dependency maven-id="io.ktor:ktor-http-cio-jvm" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="org.apache.httpcomponents:httpclient" />
|
||||
<dependency maven-id="org.apache.httpcomponents:httpcore" />
|
||||
<dependency maven-id="commons-logging:commons-logging" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-client-apache/1.5.2/ktor-client-apache-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-client-core-jvm/1.5.2/ktor-client-core-jvm-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpasyncclient/4.1.4/httpasyncclient-4.1.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore-nio/4.4.13/httpcore-nio-4.4.13.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-client-apache/1.5.2/ktor-client-apache-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-client-core-jvm/1.5.2/ktor-client-core-jvm-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpasyncclient/4.1.4/httpasyncclient-4.1.4-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore-nio/4.4.13/httpcore-nio-4.4.13-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library" scope="PROVIDED">
|
||||
<library name="kotlin-scripting" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:scripting:212-1.4.21-release-IJ22">
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/scripting/212-1.4.21-release-IJ22/scripting-212-1.4.21-release-IJ22.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/212-1.4.21-release-IJ22/kotlin-scripting-common-212-1.4.21-release-IJ22.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library" scope="PROVIDED">
|
||||
<library name="kotlin-scripting-common" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-scripting-common:1.4.21" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/1.4.21/kotlin-scripting-common-1.4.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.4.21/kotlin-stdlib-1.4.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.4.21/kotlin-stdlib-common-1.4.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.7/kotlinx-coroutines-core-1.3.7.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" exported="" name="space-idea-sdk" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="jackson" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="jackson-databind" level="project" />
|
||||
<orderEntry type="library" scope="RUNTIME" name="jackson-datatype-joda" level="project" />
|
||||
<orderEntry type="module-library" scope="RUNTIME">
|
||||
<library name="okhttp3" type="repository">
|
||||
<properties maven-id="com.squareup.okhttp3:okhttp:3.12.10" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okhttp3/okhttp/3.12.10/okhttp-3.12.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okio/okio/1.15.0/okio-1.15.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okhttp3/okhttp/3.12.10/okhttp-3.12.10-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/squareup/okio/okio/1.15.0/okio-1.15.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="Slf4j" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.vcs.codeReview" />
|
||||
<orderEntry type="module-library">
|
||||
<library name="org.apache.ivy" type="repository">
|
||||
<properties maven-id="org.apache.ivy:ivy:2.5.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/ivy/ivy/2.5.0/ivy-2.5.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apache/ivy/ivy/2.5.0/ivy-2.5.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.ui" />
|
||||
<orderEntry type="library" name="intellij-markdown" level="project" />
|
||||
<orderEntry type="module-library">
|
||||
<library name="kotlin-scripting-dependencies" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-scripting-dependencies:1.4.21">
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-scripting-common" />
|
||||
<dependency maven-id="org.jetbrains:annotations" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-dependencies/1.4.21/kotlin-scripting-dependencies-1.4.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library" scope="PROVIDED">
|
||||
<library name="kotlin-scripting-jvm" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-scripting-jvm:1.4.21">
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-script-runtime" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-scripting-common" />
|
||||
<dependency maven-id="org.jetbrains:annotations" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.4.21/kotlin-scripting-jvm-1.4.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.4.21/kotlin-scripting-jvm-1.4.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="module-library">
|
||||
<library name="space-ktor-server-netty" type="repository">
|
||||
<properties maven-id="io.ktor:ktor-server-netty:1.5.2">
|
||||
<exclude>
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk7" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8" />
|
||||
<dependency maven-id="org.slf4j:slf4j-api" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-jdk8" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-reflect" />
|
||||
<dependency maven-id="org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm" />
|
||||
<dependency maven-id="io.netty:netty-codec-http2" />
|
||||
<dependency maven-id="org.jetbrains.kotlin:kotlin-stdlib-common" />
|
||||
<dependency maven-id="io.netty:netty-buffer" />
|
||||
<dependency maven-id="io.netty:netty-common" />
|
||||
<dependency maven-id="io.netty:netty-resolver" />
|
||||
<dependency maven-id="io.netty:netty-transport" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-netty/1.5.2/ktor-server-netty-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-host-common/1.5.2/ktor-server-host-common-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-core/1.5.2/ktor-server-core-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-utils-jvm/1.5.2/ktor-utils-jvm-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-io-jvm/1.5.2/ktor-io-jvm-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-http-jvm/1.5.2/ktor-http-jvm-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/typesafe/config/1.3.1/config-1.3.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-http-cio-jvm/1.5.2/ktor-http-cio-jvm-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-network-jvm/1.5.2/ktor-network-jvm-1.5.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/alpn/alpn-api/1.1.3.v20160715/alpn-api-1.1.3.v20160715.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-kqueue/4.1.54.Final/netty-transport-native-kqueue-4.1.54.Final.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-unix-common/4.1.54.Final/netty-transport-native-unix-common-4.1.54.Final.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-epoll/4.1.54.Final/netty-transport-native-epoll-4.1.54.Final.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-netty/1.5.2/ktor-server-netty-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-host-common/1.5.2/ktor-server-host-common-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-server-core/1.5.2/ktor-server-core-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-utils-jvm/1.5.2/ktor-utils-jvm-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-io-jvm/1.5.2/ktor-io-jvm-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-http-jvm/1.5.2/ktor-http-jvm-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/typesafe/config/1.3.1/config-1.3.1-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-http-cio-jvm/1.5.2/ktor-http-cio-jvm-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/ktor/ktor-network-jvm/1.5.2/ktor-network-jvm-1.5.2-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/jetty/alpn/alpn-api/1.1.3.v20160715/alpn-api-1.1.3.v20160715-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-kqueue/4.1.54.Final/netty-transport-native-kqueue-4.1.54.Final-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-unix-common/4.1.54.Final/netty-transport-native-unix-common-4.1.54.Final-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/netty/netty-transport-native-epoll/4.1.54.Final/netty-transport-native-epoll-4.1.54.Final-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="gson" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.util.io" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space
|
||||
|
||||
import circlet.platform.client.KCircletClient
|
||||
|
||||
interface SpaceVmWithClient {
|
||||
val client: KCircletClient
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import icons.SpaceIcons
|
||||
|
||||
object SpaceActionUtils {
|
||||
fun showIconInActionSearch(e: AnActionEvent) {
|
||||
e.presentation.icon = if (e.place == ActionPlaces.ACTION_SEARCH) SpaceIcons.Main else null
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.settings.SpaceSettingsPanel
|
||||
|
||||
class SpaceLoginAction : DumbAwareAction() {
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = SpaceWorkspaceComponent.getInstance().workspace.value == null
|
||||
SpaceActionUtils.showIconInActionSearch(e)
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
SpaceSettingsPanel.openSettings(e.project)
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.actions
|
||||
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.ide.ui.fullRow
|
||||
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.promo.*
|
||||
import com.intellij.space.settings.SpaceLoginState
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.LoginComponents.errorText
|
||||
import com.intellij.space.ui.LoginComponents.loginButton
|
||||
import com.intellij.space.ui.LoginComponents.organizationUrlTextField
|
||||
import com.intellij.ui.ComponentUtil
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.components.BrowserLink
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import com.intellij.ui.layout.*
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal fun prettyBorder() = JBUI.Borders.empty(16, 20, 4, 20)
|
||||
|
||||
internal fun buildLoginPanelWithPromo(
|
||||
state: SpaceLoginState.Disconnected,
|
||||
statsExplorePlace: SpaceStatsCounterCollector.ExplorePlace,
|
||||
statsLoginPlace: SpaceStatsCounterCollector.LoginPlace,
|
||||
packParent: () -> Unit,
|
||||
loginAction: (String) -> Unit
|
||||
): JComponent {
|
||||
val wrapper = Wrapper().apply {
|
||||
val loginPanel = loginPanel(statsLoginPlace, state, loginAction).apply {
|
||||
border = prettyBorder()
|
||||
}
|
||||
val promoPanel = promoPanel(statsExplorePlace) {
|
||||
SpaceStatsCounterCollector.ADV_LOG_IN_LINK.log()
|
||||
setContent(loginPanel)
|
||||
packParent()
|
||||
repaint()
|
||||
}.apply {
|
||||
border = prettyBorder()
|
||||
}
|
||||
|
||||
val content = if (state.server.isNotBlank() || state.error?.isNotBlank() == true) loginPanel else promoPanel
|
||||
setContent(content)
|
||||
repaint()
|
||||
}
|
||||
val borderLayoutPanel = BorderLayoutPanel().addToCenter(wrapper)
|
||||
toolbarPromoBanner()?.let { borderLayoutPanel.addToTop(it) }
|
||||
return borderLayoutPanel
|
||||
}
|
||||
|
||||
private fun promoPanel(statsExplorePlace: SpaceStatsCounterCollector.ExplorePlace, loginAction: () -> Unit) = panel {
|
||||
fullRow { createSpaceByJetbrainsLabel()() }
|
||||
fullRow { promoText(if (SystemInfo.isMac) 60 else 52)() }
|
||||
fullRow {
|
||||
JButton(SpaceBundle.message("space.promo.explore.space.button")).apply {
|
||||
addActionListener {
|
||||
SpaceStatsCounterCollector.EXPLORE_SPACE.log(statsExplorePlace)
|
||||
BrowserUtil.browse(EXPLORE_SPACE_PROMO_URL)
|
||||
}
|
||||
}.apply {
|
||||
ComponentUtil.putClientProperty(this, DarculaButtonUI.DEFAULT_STYLE_KEY, true)
|
||||
}()
|
||||
}
|
||||
fullRow {
|
||||
grayTextLabel(SpaceBundle.message("login.already.have.account.label"))()
|
||||
loginLabel(loginAction)()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loginPanel(
|
||||
statsLoginPlace: SpaceStatsCounterCollector.LoginPlace,
|
||||
state: SpaceLoginState.Disconnected,
|
||||
loginAction: (String) -> Unit
|
||||
) = panel {
|
||||
val serverField = organizationUrlTextField(state.server)
|
||||
val loginButton = loginButton(statsLoginPlace, default = true) {
|
||||
it.isEnabled = false
|
||||
loginAction(serverField.text.trim())
|
||||
}
|
||||
|
||||
fullRow { label(SpaceBundle.message("login.panel.organization.url.label")) }
|
||||
fullRow { serverField() }
|
||||
fullRow { loginButton() }
|
||||
|
||||
state.error?.let {
|
||||
val errorText = errorText(it)
|
||||
fullRow { errorText() }
|
||||
}
|
||||
|
||||
fullRow {
|
||||
grayTextLabel(SpaceBundle.message("login.dont.have.account.label"))()
|
||||
singUpLabel()()
|
||||
}
|
||||
}
|
||||
|
||||
private fun grayTextLabel(@NlsContexts.Label text: String) = JBLabel(text).apply {
|
||||
foreground = SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES.fgColor
|
||||
}
|
||||
|
||||
private fun loginLabel(loginAction: () -> Unit) = ActionLink(SpaceBundle.message("login.panel.log.in.button")) {
|
||||
loginAction()
|
||||
}
|
||||
|
||||
private fun singUpLabel(): BrowserLink = BrowserLink(SpaceBundle.message("login.sign.up"), SIGN_UP_SPACE_URL).apply {
|
||||
addActionListener {
|
||||
SpaceStatsCounterCollector.SIGN_UP_LINK.log()
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.actions
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
|
||||
class SpaceLogoutAction : DumbAwareAction() {
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = SpaceWorkspaceComponent.getInstance().workspace.value != null
|
||||
SpaceActionUtils.showIconInActionSearch(e)
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
SpaceWorkspaceComponent.getInstance().signOut(SpaceStatsCounterCollector.LogoutPlace.ACTION)
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.actions
|
||||
|
||||
import circlet.client.api.englishFullName
|
||||
import circlet.platform.client.ConnectionStatus
|
||||
import circlet.workspaces.Workspace
|
||||
import com.intellij.openapi.actionSystem.ActionPlaces
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.RightAlignedToolbarAction
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.space.components.SpaceUserAvatarProvider
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.settings.SpaceLoginState
|
||||
import com.intellij.space.settings.SpaceSettings
|
||||
import com.intellij.space.settings.SpaceSettingsPanel
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.*
|
||||
import com.intellij.space.ui.LoginComponents.buildConnectingPanel
|
||||
import com.intellij.space.utils.SpaceUrls
|
||||
import com.intellij.space.vcs.SpaceProjectContext
|
||||
import com.intellij.space.vcs.clone.SpaceCloneAction
|
||||
import com.intellij.space.vcs.review.SpaceShowReviewsAction
|
||||
import com.intellij.ui.AnimatedIcon
|
||||
import com.intellij.ui.awt.RelativePoint
|
||||
import com.intellij.ui.components.panels.Wrapper
|
||||
import com.intellij.util.ui.cloneDialog.VcsCloneDialogUiSpec
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import runtime.reactive.view
|
||||
import java.awt.Component
|
||||
import java.awt.Point
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JComponent
|
||||
|
||||
class SpaceMainToolBarAction : DumbAwareAction(), RightAlignedToolbarAction {
|
||||
private val settings = SpaceSettings.getInstance()
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val isOnNavBar = e.place == ActionPlaces.NAVIGATION_BAR_TOOLBAR
|
||||
val isOnMainBar = e.place == ActionPlaces.MAIN_TOOLBAR
|
||||
if (!isOnNavBar && !isOnMainBar) {
|
||||
e.presentation.isEnabledAndVisible = false
|
||||
return
|
||||
}
|
||||
e.presentation.isEnabledAndVisible = true
|
||||
|
||||
val space = SpaceWorkspaceComponent.getInstance()
|
||||
val avatars = SpaceUserAvatarProvider.getInstance().avatars.value
|
||||
val isOnline = space.workspace.value?.client?.connectionStatus?.value is ConnectionStatus.Connected
|
||||
val isConnecting = space.loginState.value is SpaceLoginState.Connecting
|
||||
e.presentation.icon = when {
|
||||
isOnline -> avatars.online
|
||||
isConnecting -> AnimatedIcon.Default.INSTANCE
|
||||
else -> avatars.offline
|
||||
}
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val component = e.inputEvent.component
|
||||
val space = SpaceWorkspaceComponent.getInstance()
|
||||
val workspace = space.workspace.value
|
||||
SpaceStatsCounterCollector.OPEN_MAIN_TOOLBAR_POPUP.log(SpaceStatsCounterCollector.LoginState.convert(space.loginState.value))
|
||||
if (workspace != null) {
|
||||
buildMenu(workspace, SpaceUserAvatarProvider.getInstance().avatars.value.circle, e.project!!)
|
||||
.showUnderneathOf(component)
|
||||
}
|
||||
else {
|
||||
val wrapper = Wrapper()
|
||||
val popup = JBPopupFactory.getInstance().createComponentPopupBuilder(wrapper, wrapper)
|
||||
.setRequestFocus(true)
|
||||
.setFocusable(true)
|
||||
.createPopup()
|
||||
space.loginState.view(space.lifetime) { _: Lifetime, st: SpaceLoginState ->
|
||||
val view = createView(st, component) { popup.pack(true, true) }
|
||||
if (view == null) {
|
||||
popup.cancel()
|
||||
return@view
|
||||
}
|
||||
|
||||
wrapper.setContent(view)
|
||||
wrapper.repaint()
|
||||
if (st is SpaceLoginState.Disconnected) {
|
||||
popup.pack(true, true)
|
||||
}
|
||||
}
|
||||
popup.show(RelativePoint(component, Point(-wrapper.preferredSize.width + component.width, component.height)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createView(st: SpaceLoginState, component: Component, packPopup: () -> Unit): JComponent? {
|
||||
return when (st) {
|
||||
is SpaceLoginState.Connected -> null
|
||||
|
||||
is SpaceLoginState.Connecting -> buildConnectingPanel(st, SpaceStatsCounterCollector.LoginPlace.MAIN_TOOLBAR, prettyBorder()) {
|
||||
st.cancel()
|
||||
}
|
||||
|
||||
is SpaceLoginState.Disconnected -> buildLoginPanelWithPromo(
|
||||
st,
|
||||
SpaceStatsCounterCollector.ExplorePlace.MAIN_TOOLBAR,
|
||||
SpaceStatsCounterCollector.LoginPlace.MAIN_TOOLBAR,
|
||||
packPopup
|
||||
) { serverName ->
|
||||
val space = SpaceWorkspaceComponent.getInstance()
|
||||
space.signInManually(serverName, space.lifetime, component)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildMenu(workspace: Workspace, icon: Icon, project: Project): AccountsMenuListPopup {
|
||||
val host = settings.serverSettings.server
|
||||
val serverUrl = cleanupUrl(host)
|
||||
val menuItems: MutableList<AccountMenuItem> = mutableListOf()
|
||||
menuItems += AccountMenuItem.Account(
|
||||
workspace.me.value.englishFullName(), // NON-NLS
|
||||
serverUrl,
|
||||
resizeIcon(icon, VcsCloneDialogUiSpec.Components.popupMenuAvatarSize),
|
||||
listOf(browseAction(SpaceBundle.message("main.toolbar.open.server", serverUrl), host, true)))
|
||||
menuItems += AccountMenuItem.Action(SpaceBundle.message("action.com.intellij.space.vcs.clone.SpaceCloneAction.text"),
|
||||
{ SpaceCloneAction.runClone(project) },
|
||||
showSeparatorAbove = true)
|
||||
val projectContext = SpaceProjectContext.getInstance(project)
|
||||
val context = projectContext.currentContext
|
||||
if (context.isAssociatedWithSpaceRepository) {
|
||||
val descriptions = context.reposInProject.keys
|
||||
menuItems += AccountMenuItem.Action(SpaceBundle.message("action.show.code.reviews.text"), {
|
||||
SpaceShowReviewsAction.showCodeReviews(project)
|
||||
})
|
||||
if (descriptions.size > 1) {
|
||||
menuItems += AccountMenuItem.Group(SpaceBundle.message("open.in.browser.group.checklists"), descriptions.map {
|
||||
val checklistsUrl = SpaceUrls.checklists(it.key)
|
||||
browseAction(SpaceBundle.message("open.in.browser.open.for.project.action", it.project.name), checklistsUrl)
|
||||
}.toList())
|
||||
|
||||
menuItems += AccountMenuItem.Group(SpaceBundle.message("open.in.browser.group.issues"), descriptions.map {
|
||||
val issuesUrl = SpaceUrls.issues(it.key)
|
||||
browseAction(SpaceBundle.message("open.in.browser.open.for.project.action", it.project.name), issuesUrl)
|
||||
}.toList())
|
||||
}
|
||||
else if (descriptions.isNotEmpty()) {
|
||||
val projectKey = descriptions.first().key
|
||||
menuItems += browseAction(SpaceBundle.message("main.toolbar.checklists.action"), SpaceUrls.checklists(projectKey))
|
||||
menuItems += browseAction(SpaceBundle.message("main.toolbar.issues.action"), SpaceUrls.issues(projectKey))
|
||||
}
|
||||
}
|
||||
menuItems += AccountMenuItem.Action(SpaceBundle.message("main.toolbar.settings.action"),
|
||||
{ SpaceSettingsPanel.openSettings(project) },
|
||||
showSeparatorAbove = true)
|
||||
menuItems += AccountMenuItem.Action(SpaceBundle.message("main.toolbar.log.out.action"), {
|
||||
SpaceWorkspaceComponent.getInstance().signOut(SpaceStatsCounterCollector.LogoutPlace.MAIN_TOOLBAR)
|
||||
})
|
||||
|
||||
return AccountsMenuListPopup(project, AccountMenuPopupStep(menuItems))
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.actions.internal
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import libraries.klogging.logger
|
||||
|
||||
internal class SpaceReceivedPacketsAction : DumbAwareAction() {
|
||||
companion object {
|
||||
private val LOG = logger<SpaceReceivedPacketsAction>()
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
val presentation = e.presentation
|
||||
val workspace = SpaceWorkspaceComponent.getInstance().workspace.value
|
||||
if (workspace == null) {
|
||||
presentation.isEnabledAndVisible = false
|
||||
return
|
||||
}
|
||||
|
||||
presentation.isEnabledAndVisible = true
|
||||
if (workspace.client.logReceivedPackets) {
|
||||
presentation.text = SpaceBundle.message("action.internal.stop.log.packets.text")
|
||||
}
|
||||
else {
|
||||
presentation.text = SpaceBundle.message("action.internal.start.log.packets.text")
|
||||
}
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val client = SpaceWorkspaceComponent.getInstance().workspace.value?.client ?: return
|
||||
if (client.logReceivedPackets) {
|
||||
LOG.info { "Logging packets is stopped" }
|
||||
client.logReceivedPackets = false
|
||||
}
|
||||
else {
|
||||
LOG.info { "Logging packets is started" }
|
||||
client.logReceivedPackets = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.auth
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.messages.SpaceBundleExtensions
|
||||
import com.intellij.space.settings.SpaceSettingsPanel
|
||||
import com.intellij.space.utils.notify
|
||||
|
||||
internal object SpaceAuthNotifier {
|
||||
fun authFailed() {
|
||||
val tryAgainAction = object : DumbAwareAction(SpaceBundleExtensions.messagePointer("auth.notification.failed.login.again.action")) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
SpaceSettingsPanel.openSettings(null)
|
||||
}
|
||||
}
|
||||
notify(SpaceBundle.message("auth.notification.failed.message"), listOf(tryAgainAction))
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.auth
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.request.uri
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import java.io.IOException
|
||||
|
||||
private val ioDispatcher = AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher()
|
||||
|
||||
internal suspend fun startRedirectHandling(lifetime: Lifetime, server: String, ports: Collection<Int>): SpaceRedirectHandlingInfo? {
|
||||
val info = CompletableDeferred<SpaceRedirectHandlingInfo?>()
|
||||
launch(lifetime, ioDispatcher) {
|
||||
for (port in ports.distinct().shuffled()) {
|
||||
try {
|
||||
val redirectUrl = startAuthServerAsync(lifetime, server, port)
|
||||
info.complete(SpaceRedirectHandlingInfo(port, redirectUrl))
|
||||
break
|
||||
}
|
||||
catch (e: IOException) {
|
||||
// check next port
|
||||
}
|
||||
}
|
||||
info.complete(null)
|
||||
}
|
||||
return info.await()
|
||||
}
|
||||
|
||||
private fun startAuthServerAsync(lifetime: Lifetime, serverUrl: String, port: Int): Deferred<String> {
|
||||
val redirectUrl = CompletableDeferred<String>()
|
||||
val server = embeddedServer(Netty, port = port, host = "localhost") {
|
||||
routing {
|
||||
get("/auth") {
|
||||
call.respondText(
|
||||
createAuthPage(serverUrl),
|
||||
contentType = ContentType.Text.Html
|
||||
)
|
||||
redirectUrl.complete(call.request.uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
server.start(wait = false)
|
||||
|
||||
fun stopServer() {
|
||||
ApplicationManager.getApplication().executeOnPooledThread(Runnable {
|
||||
server.stop(1000, 1000)
|
||||
})
|
||||
}
|
||||
lifetime.addOrCallImmediately {
|
||||
stopServer()
|
||||
}
|
||||
redirectUrl.invokeOnCompletion {
|
||||
stopServer()
|
||||
}
|
||||
|
||||
return redirectUrl
|
||||
}
|
||||
|
||||
internal data class SpaceRedirectHandlingInfo(val port: Int, val redirectUrl: Deferred<String>)
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.auth
|
||||
|
||||
import com.intellij.ide.plugins.PluginManagerCore
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.text.HtmlChunk.*
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.ui.AppUIUtil
|
||||
import java.io.File
|
||||
|
||||
internal fun createAuthPage(server: String): String = html()
|
||||
.child(
|
||||
head()
|
||||
.child(styleTag(loadAuthResourceFileContent("spaceAuthPage.css")))
|
||||
)
|
||||
.child(
|
||||
body().child(
|
||||
div().attr("class", "central-div")
|
||||
.child(
|
||||
div().attr("class", "central-div-header")
|
||||
.child(
|
||||
div().attr("class", "central-div-header-icon").addRaw(loadAuthResourceFileContent("spaceAuthIcon.svg"))
|
||||
)
|
||||
.child(
|
||||
div().attr("class", "central-div-header-text").addText(SpaceBundle.message("auth.page.jetbrains.space.header.text"))
|
||||
)
|
||||
)
|
||||
.child(
|
||||
div().attr("class", "central-div-content")
|
||||
.child(
|
||||
div().attr("class", "central-div-content-icon").addRaw(loadProductSvg() ?: "")
|
||||
)
|
||||
.child(
|
||||
div().attr("class", "central-div-content-text").addText(SpaceBundle.message("auth.page.content.text"))
|
||||
)
|
||||
)
|
||||
.child(
|
||||
link(server, SpaceBundle.message("auth.page.go.to.space.link")).attr("class", "central-div-content-link")
|
||||
)
|
||||
)
|
||||
)
|
||||
.toString()
|
||||
|
||||
@NlsSafe
|
||||
private fun loadAuthResourceFileContent(fileName: String): String {
|
||||
val plugin = PluginManagerCore.getPlugin(PluginId.getId("com.jetbrains.space"))
|
||||
?: throw IllegalStateException("Couldn't find space plugin descriptor")
|
||||
val loader = plugin.pluginClassLoader
|
||||
return loader.getResourceAsStream("auth/$fileName")!!.bufferedReader(Charsets.UTF_8).readText()
|
||||
}
|
||||
|
||||
@NlsSafe
|
||||
private fun loadProductSvg(): String? {
|
||||
AppUIUtil.findIcon().takeIf { it?.endsWith(".svg") ?: false }?.let { iconPath ->
|
||||
val iconFile = File(iconPath)
|
||||
if (iconFile.exists() && iconFile.isFile) {
|
||||
return iconFile.readText(Charsets.UTF_8)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.editor
|
||||
|
||||
import com.intellij.diff.util.FileEditorBase
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorPolicy
|
||||
import com.intellij.openapi.fileEditor.FileEditorProvider
|
||||
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.space.chat.ui.SpaceChatPanel
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import libraries.coroutines.extra.LifetimeSource
|
||||
import javax.swing.JComponent
|
||||
|
||||
private class SpaceChatEditor(private val project: Project, private val spaceChatFile: SpaceChatFile) : FileEditorBase() {
|
||||
private val editorLifetime = LifetimeSource()
|
||||
|
||||
init {
|
||||
Disposer.register(this, Disposable { editorLifetime.terminate() })
|
||||
}
|
||||
|
||||
val component by lazy {
|
||||
val headerDetails = spaceChatFile.headerDetailsBuilder(editorLifetime)
|
||||
SpaceChatPanel(project, editorLifetime, this, spaceChatFile.channelsVm, spaceChatFile.chatRecord, headerDetails)
|
||||
}
|
||||
|
||||
override fun getComponent(): JComponent = component
|
||||
|
||||
override fun getPreferredFocusedComponent(): JComponent? = null
|
||||
|
||||
override fun getName(): String = SpaceBundle.message("chat.editor.name")
|
||||
|
||||
override fun getFile() = spaceChatFile
|
||||
}
|
||||
|
||||
internal class SpaceChatEditorProvider : FileEditorProvider, DumbAware {
|
||||
override fun accept(project: Project, file: VirtualFile): Boolean = file is SpaceChatFile
|
||||
|
||||
override fun createEditor(project: Project, file: VirtualFile): FileEditor = SpaceChatEditor(project, file as SpaceChatFile)
|
||||
|
||||
override fun getEditorTypeId(): String = "SpaceChatEditor"
|
||||
|
||||
override fun getPolicy(): FileEditorPolicy = FileEditorPolicy.HIDE_DEFAULT_EDITOR
|
||||
|
||||
override fun disposeEditor(editor: FileEditor) {
|
||||
if (editor.file?.getUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN) != true) {
|
||||
Disposer.dispose(editor)
|
||||
}
|
||||
|
||||
super.disposeEditor(editor)
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.editor
|
||||
|
||||
import circlet.client.api.M2ChannelRecord
|
||||
import circlet.m2.ChannelsVm
|
||||
import circlet.platform.api.Ref
|
||||
import com.intellij.ide.FileIconProvider
|
||||
import com.intellij.ide.actions.SplitAction
|
||||
import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFilePathWrapper
|
||||
import com.intellij.space.chat.model.api.SpaceChatHeaderDetails
|
||||
import com.intellij.space.editor.SpaceChatComplexPathVirtualFileSystem
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.testFramework.LightVirtualFile
|
||||
import icons.SpaceIcons
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import javax.swing.Icon
|
||||
|
||||
/**
|
||||
* @param id is used only for equals and hashcode to avoid multiple editor tabs the same [SpaceChatFile]
|
||||
*/
|
||||
internal class SpaceChatFile(
|
||||
private val sessionId: String,
|
||||
private val projectHash: String,
|
||||
@NlsSafe val id: String,
|
||||
@NlsSafe path: String,
|
||||
@NlsContexts.TabTitle val displayName: String,
|
||||
@NlsContexts.Tooltip val tabTooltip: String,
|
||||
val channelsVm: ChannelsVm,
|
||||
val chatRecord: Ref<M2ChannelRecord>,
|
||||
val headerDetailsBuilder: (Lifetime) -> SpaceChatHeaderDetails
|
||||
) : LightVirtualFile(path, SpaceChatFileType.instance, ""), VirtualFilePathWrapper {
|
||||
|
||||
init {
|
||||
putUserData(SplitAction.FORBID_TAB_SPLIT, true)
|
||||
isWritable = false
|
||||
}
|
||||
|
||||
override fun getFileSystem() = SpaceChatComplexPathVirtualFileSystem.getInstance()
|
||||
|
||||
override fun getPath(): String = try {
|
||||
fileSystem.getPath(sessionId, projectHash, id)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
name
|
||||
}
|
||||
|
||||
override fun enforcePresentableName(): Boolean = true
|
||||
|
||||
override fun getPresentablePath(): String = name
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SpaceChatFile
|
||||
|
||||
if (id != other.id) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = id.hashCode()
|
||||
}
|
||||
|
||||
internal class SpaceChatFileTabTitleProvider : EditorTabTitleProvider {
|
||||
override fun getEditorTabTitle(project: Project, file: VirtualFile): String? = (file as? SpaceChatFile)?.displayName
|
||||
|
||||
override fun getEditorTabTooltipText(project: Project, virtualFile: VirtualFile): String? = (virtualFile as? SpaceChatFile)?.tabTooltip
|
||||
}
|
||||
|
||||
internal class SpaceChatIconProvider : FileIconProvider {
|
||||
override fun getIcon(file: VirtualFile, flags: Int, project: Project?): Icon? {
|
||||
if (file !is SpaceChatFile) {
|
||||
return null
|
||||
}
|
||||
return SpaceIcons.Main
|
||||
}
|
||||
}
|
||||
|
||||
private class SpaceChatFileType private constructor(): FileType {
|
||||
|
||||
override fun getName(): String = "SpaceChat"
|
||||
|
||||
override fun getDescription(): String = SpaceBundle.message("filetype.chat.description")
|
||||
|
||||
override fun getDefaultExtension(): String = ""
|
||||
|
||||
override fun getIcon(): Icon = SpaceIcons.Main
|
||||
|
||||
override fun isBinary(): Boolean = true
|
||||
|
||||
override fun isReadOnly(): Boolean = true
|
||||
|
||||
companion object {
|
||||
val instance = SpaceChatFileType()
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.markdown
|
||||
|
||||
import org.intellij.markdown.IElementType
|
||||
import org.intellij.markdown.MarkdownElementTypes
|
||||
import org.intellij.markdown.MarkdownTokenTypes
|
||||
import org.intellij.markdown.flavours.gfm.GFMElementTypes
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
import org.intellij.markdown.flavours.gfm.GFMTokenTypes
|
||||
import org.intellij.markdown.flavours.gfm.StrikeThroughParser
|
||||
import org.intellij.markdown.html.GeneratingProvider
|
||||
import org.intellij.markdown.html.HtmlGenerator
|
||||
import org.intellij.markdown.html.SimpleInlineTagProvider
|
||||
import org.intellij.markdown.parser.LinkMap
|
||||
import org.intellij.markdown.parser.MarkdownParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.RangesListBuilder
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParser
|
||||
import org.intellij.markdown.parser.sequentialparsers.SequentialParserManager
|
||||
import org.intellij.markdown.parser.sequentialparsers.TokensCache
|
||||
import org.intellij.markdown.parser.sequentialparsers.impl.*
|
||||
import org.jetbrains.annotations.Contract
|
||||
import java.net.URI
|
||||
|
||||
@Contract(pure = true)
|
||||
internal fun convertToHtml(markdownText: String, server: String? = null): String {
|
||||
val flavour = SpaceFlavourDescriptor()
|
||||
val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(markdownText)
|
||||
val providers = flavour.createHtmlGeneratingProviders(
|
||||
linkMap = LinkMap.buildLinkMap(parsedTree, markdownText),
|
||||
baseURI = server?.let { URI(it) }
|
||||
)
|
||||
val htmlText = HtmlGenerator(markdownText, parsedTree, providers, false).generateHtml()
|
||||
return htmlText.removePrefix("<body>").removeSuffix("</body>")
|
||||
}
|
||||
|
||||
private class SpaceFlavourDescriptor : GFMFlavourDescriptor() {
|
||||
override val sequentialParserManager = object : SequentialParserManager() {
|
||||
override fun getParserSequence(): List<SequentialParser> = listOf(
|
||||
AutolinkParser(listOf(MarkdownTokenTypes.AUTOLINK, GFMTokenTypes.GFM_AUTOLINK)),
|
||||
BacktickParser(),
|
||||
ImageParser(),
|
||||
InlineLinkParser(),
|
||||
ReferenceLinkParser(),
|
||||
LineBreaksParser(), // difference with super
|
||||
StrikeThroughParser(),
|
||||
EmphStrongParser()
|
||||
)
|
||||
}
|
||||
|
||||
override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map<IElementType, GeneratingProvider> {
|
||||
val parentProviders = super.createHtmlGeneratingProviders(linkMap, baseURI).toMutableMap()
|
||||
parentProviders[MarkdownElementTypes.EMPH] = SimpleInlineTagProvider("i", 1, -1)
|
||||
parentProviders[MarkdownElementTypes.STRONG] = SimpleInlineTagProvider("b", 2, -2)
|
||||
parentProviders[GFMElementTypes.STRIKETHROUGH] = SimpleInlineTagProvider("strike", 2, -2)
|
||||
return parentProviders
|
||||
}
|
||||
}
|
||||
|
||||
private class LineBreaksParser : SequentialParser {
|
||||
override fun parse(tokens: TokensCache, rangesToGlue: List<IntRange>): SequentialParser.ParsingResult {
|
||||
val result = SequentialParser.ParsingResultBuilder()
|
||||
val delegateIndices = RangesListBuilder()
|
||||
var iterator = tokens.RangesListIterator(rangesToGlue)
|
||||
|
||||
while (iterator.type != null) {
|
||||
if (iterator.type == MarkdownTokenTypes.EOL) {
|
||||
result.withNode(SequentialParser.Node(iterator.index..iterator.index + 1, MarkdownTokenTypes.HARD_LINE_BREAK))
|
||||
}
|
||||
else {
|
||||
delegateIndices.put(iterator.index)
|
||||
}
|
||||
|
||||
iterator = iterator.advance()
|
||||
}
|
||||
|
||||
return result.withFurtherProcessing(delegateIndices.get())
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.markdown
|
||||
|
||||
import circlet.client.api.UnfurlAttachment
|
||||
import circlet.client.api.UnfurlDetailsDateTime
|
||||
import circlet.client.api.UnfurlDetailsProfile
|
||||
import circlet.client.api.fullName
|
||||
import circlet.platform.client.resolve
|
||||
import com.intellij.openapi.util.text.HtmlChunk.link
|
||||
import com.intellij.space.utils.formatPrettyDateTime
|
||||
import platform.common.Unfurl
|
||||
|
||||
internal fun processUnfurls(markdown: String, unfurls: List<UnfurlAttachment>): String {
|
||||
var result = markdown
|
||||
unfurls.map { it.unfurl }.forEach { unfurl ->
|
||||
result = result.replace(unfurl.link, unfurl.createNewLink())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun Unfurl.createNewLink(): String = when (val unfurlDetails = details) {
|
||||
is UnfurlDetailsProfile -> link(link, unfurlDetails.profile.resolve().name.fullName()).toString() // NON-NLS
|
||||
is UnfurlDetailsDateTime -> unfurlDetails.utcMilliseconds.formatPrettyDateTime()
|
||||
else -> link
|
||||
}
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.api
|
||||
|
||||
internal interface SpaceChatHeaderDetails
|
||||
@@ -1,74 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.api
|
||||
|
||||
import circlet.client.api.AttachmentInfo
|
||||
import circlet.client.api.CPrincipal
|
||||
import circlet.client.api.ChannelItemRecord
|
||||
import circlet.client.api.M2ChannelRecord
|
||||
import circlet.m2.M2MessageEditingVm
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import circlet.m2.threads.M2ThreadPreviewVm
|
||||
import circlet.platform.api.KDateTime
|
||||
import circlet.platform.api.Ref
|
||||
import circlet.platform.api.TID
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.util.ui.codereview.timeline.TimelineItem
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import runtime.reactive.MutableProperty
|
||||
import runtime.reactive.Property
|
||||
|
||||
internal interface SpaceChatItem : TimelineItem {
|
||||
val id: TID
|
||||
|
||||
val chat: M2ChannelVm
|
||||
|
||||
val author: CPrincipal
|
||||
|
||||
val text: @NlsSafe String
|
||||
|
||||
val created: KDateTime
|
||||
|
||||
val type: SpaceChatItemType
|
||||
|
||||
val thread: Ref<M2ChannelRecord>?
|
||||
|
||||
/**
|
||||
* hack to prevent threads collapsing when SpaceChatItem is recreated
|
||||
*/
|
||||
val loadingThread: MutableProperty<CompletableDeferred<M2ChannelVm>?>
|
||||
|
||||
val delivered: Boolean
|
||||
|
||||
val link: String?
|
||||
|
||||
val canDelete: Boolean
|
||||
|
||||
val isEdited: Boolean
|
||||
|
||||
val editingVm: Property<M2MessageEditingVm?>
|
||||
|
||||
val isEditing: Property<Boolean>
|
||||
|
||||
val canEdit: Boolean
|
||||
|
||||
val startThreadVm: SpaceChatStartThreadVm
|
||||
|
||||
val pending: Boolean?
|
||||
|
||||
val attachments: List<AttachmentInfo>
|
||||
|
||||
val additionalFeatures: Set<SpaceChatItemAdditionalFeature>
|
||||
|
||||
val projectedItem: Property<ChannelItemRecord>?
|
||||
|
||||
val threadPreview: M2ThreadPreviewVm?
|
||||
|
||||
fun startEditing()
|
||||
|
||||
fun stopEditing()
|
||||
|
||||
suspend fun delete()
|
||||
|
||||
suspend fun loadThread(lifetime: Lifetime): M2ChannelVm?
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.api
|
||||
|
||||
import runtime.reactive.Property
|
||||
|
||||
internal sealed class SpaceChatItemAdditionalFeature {
|
||||
class ShowResolvedState(val resolved: Property<Boolean>) : SpaceChatItemAdditionalFeature()
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.api
|
||||
|
||||
import circlet.client.api.M2ItemContentDetails
|
||||
import circlet.client.api.M2TextItemContent
|
||||
import circlet.client.api.TD_MemberProfile
|
||||
import circlet.client.api.mc.ChatMessage
|
||||
import circlet.client.api.mc.toMessage
|
||||
import circlet.code.api.*
|
||||
import circlet.platform.api.Ref
|
||||
import com.intellij.space.chat.model.api.SpaceChatItemType.*
|
||||
import com.intellij.space.chat.model.impl.toMCMessageContent
|
||||
|
||||
internal fun M2ItemContentDetails?.toType(): SpaceChatItemType = when (this) {
|
||||
is CodeDiscussionAddedFeedEvent -> CodeDiscussion(codeDiscussion)
|
||||
is M2TextItemContent -> SimpleText
|
||||
is ReviewCompletionStateChangedEvent -> ReviewCompletionStateChanged
|
||||
is ReviewerChangedEvent -> ReviewerChanged(changeType, uid)
|
||||
is MergeRequestMergedEvent -> MergeRequestMerged(sourceBranch, targetBranch)
|
||||
is MergeRequestBranchDeletedEvent -> MergeRequestBranchDeleted(branch)
|
||||
is ReviewTitleChangedEvent -> ReviewTitleChanged(oldTitle, newTitle)
|
||||
is circlet.client.api.mc.MCMessage -> when (val chatMessage = toMessage()) {
|
||||
is ChatMessage.Text -> SimpleText
|
||||
is ChatMessage.Block -> MCMessage(chatMessage.toMCMessageContent())
|
||||
}
|
||||
null -> Deleted
|
||||
else -> Unknown
|
||||
}
|
||||
|
||||
internal sealed class SpaceChatItemType {
|
||||
class CodeDiscussion(val discussion: Ref<CodeDiscussionRecord>) : SpaceChatItemType()
|
||||
|
||||
object SimpleText : SpaceChatItemType()
|
||||
|
||||
object ReviewCompletionStateChanged : SpaceChatItemType()
|
||||
|
||||
class ReviewerChanged(val changeType: ReviewerChangedType, val uid: Ref<TD_MemberProfile>) : SpaceChatItemType()
|
||||
|
||||
class MergeRequestMerged(val sourceBranch: String, val targetBranch: String) : SpaceChatItemType()
|
||||
|
||||
class MergeRequestBranchDeleted(val branch: String) : SpaceChatItemType()
|
||||
|
||||
class ReviewTitleChanged(val oldTitle: String, val newTitle: String) : SpaceChatItemType()
|
||||
|
||||
class MCMessage(val content: SpaceMCMessageContent) : SpaceChatItemType()
|
||||
|
||||
object Deleted : SpaceChatItemType()
|
||||
|
||||
object Unknown : SpaceChatItemType()
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.api
|
||||
|
||||
import runtime.reactive.Property
|
||||
|
||||
internal interface SpaceChatStartThreadVm {
|
||||
val canStartThread: Property<Boolean>
|
||||
|
||||
val isWritingFirstMessage: Property<Boolean>
|
||||
|
||||
fun startWritingFirstMessage()
|
||||
|
||||
fun stopWritingFirstMessage()
|
||||
|
||||
suspend fun startThread(message: String)
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.api
|
||||
|
||||
import circlet.client.api.mc.MessageOutline
|
||||
import circlet.client.api.mc.MessageStyle
|
||||
import javax.swing.Icon
|
||||
|
||||
internal interface SpaceMCMessageContent {
|
||||
val outline: MessageOutline?
|
||||
|
||||
val sections: List<SpaceMCMessageSection>
|
||||
|
||||
val style: MessageStyle
|
||||
}
|
||||
|
||||
internal sealed class SpaceMCMessageSection {
|
||||
class Section(val header: String?, val elements: List<SpaceMCMessageElement>, val footer: String?) : SpaceMCMessageSection()
|
||||
object Divider : SpaceMCMessageSection()
|
||||
}
|
||||
|
||||
internal sealed class SpaceMCMessageElement {
|
||||
class Text(val icon: Icon?, val content: String) : SpaceMCMessageElement()
|
||||
object MessageDivider : SpaceMCMessageElement()
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.impl
|
||||
|
||||
import circlet.m2.M2MessageVm
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import circlet.platform.client.property
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.chat.model.api.SpaceChatItemAdditionalFeature
|
||||
import com.intellij.space.chat.model.api.toType
|
||||
import com.intellij.space.chat.ui.awaitFullLoad
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import runtime.reactive.mutableProperty
|
||||
|
||||
internal class SpaceChatItemImpl private constructor(
|
||||
private val messageVm: M2MessageVm,
|
||||
override val link: String?,
|
||||
override val additionalFeatures: Set<SpaceChatItemAdditionalFeature> = setOf()
|
||||
) : SpaceChatItem {
|
||||
private val record = messageVm.message
|
||||
|
||||
override val id = record.id
|
||||
|
||||
override val thread = messageVm.message.thread
|
||||
|
||||
override val loadingThread = mutableProperty<CompletableDeferred<M2ChannelVm>?>(null)
|
||||
|
||||
override val threadPreview = messageVm.thread
|
||||
|
||||
override val chat = messageVm.channelVm
|
||||
|
||||
override val author = record.author
|
||||
|
||||
override val created = record.created
|
||||
|
||||
override val type = record.details.toType()
|
||||
|
||||
override val delivered = messageVm.delivered
|
||||
|
||||
override val canDelete = messageVm.canDelete
|
||||
|
||||
override val text = record.text
|
||||
|
||||
override val editingVm = messageVm.editingVm
|
||||
|
||||
override val isEditing = messageVm.isEditing
|
||||
|
||||
override val canEdit = messageVm.canEdit
|
||||
|
||||
override val isEdited = record.edited != null
|
||||
|
||||
override val startThreadVm = SpaceChatStartThreadVmImpl(messageVm, threadPreview, loadingThread)
|
||||
|
||||
override val pending = record.pending
|
||||
|
||||
override val attachments = record.attachments ?: listOf()
|
||||
|
||||
override val projectedItem = record.projectedItem?.property()
|
||||
|
||||
override fun startEditing() {
|
||||
messageVm.startEditing()
|
||||
}
|
||||
|
||||
override fun stopEditing() {
|
||||
messageVm.stopEditing()
|
||||
}
|
||||
|
||||
override suspend fun delete() {
|
||||
chat.deleteCurrentOrOriginalMessage(id)
|
||||
}
|
||||
|
||||
override suspend fun loadThread(lifetime: Lifetime): M2ChannelVm? {
|
||||
threadPreview ?: return null
|
||||
val loadingThreadDeferred = CompletableDeferred<M2ChannelVm>()
|
||||
loadingThread.value = loadingThreadDeferred
|
||||
return chat.channelsVm.thread(lifetime, chat, threadPreview, null).also {
|
||||
it.awaitFullLoad(lifetime)
|
||||
loadingThreadDeferred.complete(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (other !is SpaceChatItemImpl) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (record != other.record) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = record.hashCode()
|
||||
|
||||
companion object {
|
||||
internal fun M2MessageVm.convertToChatItem(
|
||||
link: String?,
|
||||
additionalFeatures: Set<SpaceChatItemAdditionalFeature> = setOf()
|
||||
): SpaceChatItem = SpaceChatItemImpl(this, link, additionalFeatures = additionalFeatures)
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.impl
|
||||
|
||||
import circlet.code.api.CodeReviewState
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.space.chat.model.api.SpaceChatHeaderDetails
|
||||
import com.intellij.space.vcs.SpaceProjectInfo
|
||||
import org.jetbrains.annotations.Nls
|
||||
import runtime.reactive.Property
|
||||
|
||||
internal class SpaceChatReviewHeaderDetails(
|
||||
val spaceProject: SpaceProjectInfo,
|
||||
val state: Property<CodeReviewState>,
|
||||
@Nls val title: Property<String>,
|
||||
@NlsSafe val reviewKey: String?,
|
||||
) : SpaceChatHeaderDetails
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.impl
|
||||
|
||||
import circlet.m2.M2MessageVm
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import circlet.m2.threads.M2ThreadPreviewVm
|
||||
import com.intellij.space.chat.model.api.SpaceChatStartThreadVm
|
||||
import com.intellij.space.chat.ui.awaitFullLoad
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import runtime.reactive.MutableProperty
|
||||
import runtime.reactive.mutableProperty
|
||||
import runtime.reactive.property
|
||||
import runtime.reactive.property.map
|
||||
|
||||
internal class SpaceChatStartThreadVmImpl(
|
||||
private val messageVm: M2MessageVm,
|
||||
private val threadPreview: M2ThreadPreviewVm?,
|
||||
private val loadingThread: MutableProperty<CompletableDeferred<M2ChannelVm>?>
|
||||
) : SpaceChatStartThreadVm {
|
||||
private val lifetime = messageVm.lifetime
|
||||
|
||||
override val isWritingFirstMessage = mutableProperty(false)
|
||||
|
||||
override val canStartThread = lifetime.map(
|
||||
messageVm.canOpenThread,
|
||||
isWritingFirstMessage,
|
||||
threadPreview?.messageCount ?: property(null)
|
||||
) { canOpenThread, writingFirstMessage, messagesCount ->
|
||||
canOpenThread && !writingFirstMessage && (messagesCount == null || messagesCount == 0)
|
||||
}
|
||||
|
||||
override fun startWritingFirstMessage() {
|
||||
isWritingFirstMessage.value = true
|
||||
|
||||
}
|
||||
|
||||
override fun stopWritingFirstMessage() {
|
||||
isWritingFirstMessage.value = false
|
||||
}
|
||||
|
||||
override suspend fun startThread(message: String) {
|
||||
val threadChannelDeferred = CompletableDeferred<M2ChannelVm>()
|
||||
loadingThread.value = threadChannelDeferred
|
||||
|
||||
val newThreadPreview = threadPreview ?: messageVm.makeThreadPreview()
|
||||
|
||||
val channelsVm = messageVm.channelsVm
|
||||
val viewerLifetime = messageVm.lifetime
|
||||
|
||||
val threadChannel = channelsVm.thread(viewerLifetime, messageVm.channelVm, newThreadPreview, null)
|
||||
threadChannel.awaitFullLoad(viewerLifetime)
|
||||
threadChannel.sendMessage(message)
|
||||
threadChannelDeferred.complete(threadChannel)
|
||||
stopWritingFirstMessage()
|
||||
}
|
||||
}
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.model.impl
|
||||
|
||||
import circlet.client.api.mc.*
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.space.chat.model.api.SpaceMCMessageContent
|
||||
import com.intellij.space.chat.model.api.SpaceMCMessageElement
|
||||
import com.intellij.space.chat.model.api.SpaceMCMessageSection
|
||||
import javax.swing.Icon
|
||||
|
||||
internal fun ChatMessage.Block.toMCMessageContent(): SpaceMCMessageContent =
|
||||
SpaceMCMessageContentImpl(outline, sections.mapNotNull { it.convert() }, style ?: MessageStyle.PRIMARY)
|
||||
|
||||
private fun MessageSectionElement.convert(): SpaceMCMessageSection? = when (this) {
|
||||
is MessageSection -> SpaceMCMessageSection.Section(header, elements.mapNotNull { it.convert() }, footer)
|
||||
is MessageDivider -> SpaceMCMessageSection.Divider
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun MessageElement.convert(): SpaceMCMessageElement? = when (this) {
|
||||
is MessageText -> {
|
||||
val icon = (accessory as? MessageIcon)?.icon?.toIcon()
|
||||
SpaceMCMessageElement.Text(icon, content)
|
||||
}
|
||||
is MessageDivider -> SpaceMCMessageElement.MessageDivider
|
||||
else -> null
|
||||
}
|
||||
|
||||
private fun ApiIcon.toIcon(): Icon? = when (icon) {
|
||||
"commits", "commits-small" -> AllIcons.Vcs.CommitNode
|
||||
else -> null
|
||||
}
|
||||
|
||||
private class SpaceMCMessageContentImpl(
|
||||
override val outline: MessageOutline?,
|
||||
override val sections: List<SpaceMCMessageSection>,
|
||||
override val style: MessageStyle
|
||||
) : SpaceMCMessageContent
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal abstract class HoverableJPanel : JPanel(null) {
|
||||
abstract fun hoverStateChanged(isHovered: Boolean)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import com.intellij.util.ui.JBValue
|
||||
|
||||
internal enum class SpaceChatAvatarType(val size: JBValue) {
|
||||
MAIN_CHAT(JBValue.UIInteger("space.chat.avatar.size", 30)),
|
||||
THREAD(JBValue.UIInteger("space.chat.thread.avatar.size", 20))
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import circlet.client.api.M2ChannelRecord
|
||||
import circlet.client.api.TD_MemberProfile
|
||||
import circlet.client.api.fullName
|
||||
import circlet.m2.ChannelsVm
|
||||
import circlet.m2.M2MessageVm
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import circlet.platform.api.Ref
|
||||
import circlet.platform.api.isTemporary
|
||||
import com.intellij.ide.plugins.newui.VerticalLayout
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.text.HtmlChunk
|
||||
import com.intellij.space.chat.model.impl.SpaceChatItemImpl.Companion.convertToChatItem
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.SpaceAvatarProvider
|
||||
import com.intellij.space.utils.SpaceUrls
|
||||
import com.intellij.ui.ColorUtil
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.codereview.timeline.TimelineComponent
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.delay
|
||||
import org.jetbrains.annotations.Nls
|
||||
import runtime.reactive.awaitTrue
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal class SpaceChatContentPanel(
|
||||
private val project: Project,
|
||||
private val lifetime: Lifetime,
|
||||
parent: Disposable,
|
||||
channelsVm: ChannelsVm,
|
||||
chatRecord: Ref<M2ChannelRecord>
|
||||
) : SpaceChatContentPanelBase(lifetime, parent, channelsVm, chatRecord) {
|
||||
private val server = channelsVm.client.server
|
||||
|
||||
override fun onChatLoad(chatVm: M2ChannelVm) {
|
||||
val itemsListModel = SpaceChatItemListModel()
|
||||
val contentPanel = createContentPanel(itemsListModel, chatVm)
|
||||
chatVm.mvms.forEach(lifetime) { messagesViewModel ->
|
||||
val chatItems = messagesViewModel.messages.map { messageViewModel ->
|
||||
messageViewModel.convertToChatItem(messageViewModel.getLink())
|
||||
}
|
||||
itemsListModel.messageListUpdated(chatItems)
|
||||
stopLoadingContent(contentPanel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createContentPanel(model: SpaceChatItemListModel, chatVM: M2ChannelVm): JPanel {
|
||||
val avatarSize = SpaceChatAvatarType.MAIN_CHAT.size
|
||||
val avatarProvider = SpaceAvatarProvider(lifetime, this, avatarSize)
|
||||
val itemComponentFactory = SpaceChatItemComponentFactory(project, lifetime, server, avatarProvider)
|
||||
val timeline = TimelineComponent(model, itemComponentFactory, offset = 0).apply {
|
||||
border = JBUI.Borders.emptyBottom(10)
|
||||
}
|
||||
|
||||
return JPanel(VerticalLayout(0)).apply {
|
||||
isOpaque = false
|
||||
add(timeline, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(createNewMessageField(chatVM, SpaceChatAvatarType.MAIN_CHAT, SpaceStatsCounterCollector.SendMessagePlace.MAIN_CHAT),
|
||||
VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun M2ChannelVm.awaitFullLoad(lifetime: Lifetime) {
|
||||
ready.awaitTrue(lifetime)
|
||||
val messages = mvms.prop
|
||||
while (messages.value.hasPrev) {
|
||||
// TODO: remove this temporary fix when it will be fixed in platform
|
||||
delay(1)
|
||||
loadPrev()
|
||||
}
|
||||
while (messages.value.hasNext) {
|
||||
delay(1)
|
||||
loadNext()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun M2MessageVm.getLink(): String? =
|
||||
if (canCopyLink && !message.isTemporary()) {
|
||||
SpaceUrls.message(channelVm.key.value, message)
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
|
||||
@Nls
|
||||
internal fun TD_MemberProfile.link(): HtmlChunk =
|
||||
HtmlChunk.link(SpaceUrls.member(username), name.fullName()) // NON-NLS
|
||||
|
||||
@NlsSafe
|
||||
internal fun getGrayTextHtml(@Nls text: String): String =
|
||||
HtmlChunk.span("color: ${ColorUtil.toHtmlColor(UIUtil.getContextHelpForeground())}").addRaw(text).toString()
|
||||
@@ -1,92 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import circlet.client.api.M2ChannelRecord
|
||||
import circlet.m2.ChannelsVm
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import circlet.platform.api.Ref
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.ui.ComponentUtil
|
||||
import com.intellij.ui.components.JBLoadingPanel
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import org.jetbrains.annotations.Nls
|
||||
import runtime.Ui
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Dimension
|
||||
import java.awt.event.MouseEvent
|
||||
import java.awt.event.MouseMotionAdapter
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal abstract class SpaceChatContentPanelBase(
|
||||
private val lifetime: Lifetime,
|
||||
parent: Disposable,
|
||||
private val channelsVm: ChannelsVm,
|
||||
private val chatRecord: Ref<M2ChannelRecord>
|
||||
) : BorderLayoutPanel() {
|
||||
private val loadingPanel = object : JBLoadingPanel(BorderLayout(), parent) {
|
||||
init {
|
||||
startLoading()
|
||||
isOpaque = false
|
||||
}
|
||||
|
||||
override fun getPreferredSize(): Dimension {
|
||||
val size = super.getPreferredSize()
|
||||
return Dimension(size.width, size.height.takeIf { it != 0 } ?: JBUI.scale(80))
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
isOpaque = true
|
||||
background = EditorColorsManager.getInstance().globalScheme.defaultBackground
|
||||
|
||||
addToCenter(loadingPanel)
|
||||
|
||||
addHoveringSupport()
|
||||
launch(lifetime, Ui) {
|
||||
val chatVM = loadChatVM()
|
||||
onChatLoad(chatVM)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addHoveringSupport() {
|
||||
var lastHoveredMessagePanel: HoverableJPanel? = null
|
||||
addMouseMotionListener(object : MouseMotionAdapter() {
|
||||
override fun mouseMoved(e: MouseEvent?) {
|
||||
e ?: return
|
||||
val component = UIUtil.getDeepestComponentAt(this@SpaceChatContentPanelBase, e.x, e.y) ?: return
|
||||
val messageComponent = ComponentUtil.getParentOfType(HoverableJPanel::class.java, component)
|
||||
if (messageComponent != lastHoveredMessagePanel) {
|
||||
lastHoveredMessagePanel?.hoverStateChanged(false)
|
||||
lastHoveredMessagePanel = messageComponent
|
||||
messageComponent?.hoverStateChanged(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private suspend fun loadChatVM(): M2ChannelVm = channelsVm.channel(lifetime, chatRecord).also {
|
||||
it.awaitFullLoad(lifetime)
|
||||
}
|
||||
|
||||
protected abstract fun onChatLoad(chatVm: M2ChannelVm)
|
||||
|
||||
@RequiresEdt
|
||||
protected fun stopLoadingContent(contentComponent: JComponent) {
|
||||
if (loadingPanel.isLoading) {
|
||||
loadingPanel.add(contentComponent, BorderLayout.CENTER)
|
||||
loadingPanel.stopLoading()
|
||||
loadingPanel.revalidate()
|
||||
loadingPanel.repaint()
|
||||
}
|
||||
}
|
||||
|
||||
fun setLoadingText(@Nls text: String) {
|
||||
loadingPanel.setLoadingText(text)
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.util.ui.codereview.SingleValueModel
|
||||
import com.intellij.util.ui.codereview.SingleValueModelImpl
|
||||
import com.intellij.util.ui.codereview.ToggleableContainer
|
||||
import com.intellij.util.ui.codereview.timeline.comment.SubmittableTextField
|
||||
import com.intellij.util.ui.codereview.timeline.comment.SubmittableTextFieldModelBase
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import runtime.Ui
|
||||
import runtime.reactive.awaitLoaded
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal class SpaceChatEditableComponent(
|
||||
project: Project,
|
||||
lifetime: Lifetime,
|
||||
content: JComponent,
|
||||
message: SpaceChatItem
|
||||
) : BorderLayoutPanel() {
|
||||
val editingModel: SingleValueModel<Boolean> = SingleValueModelImpl(false)
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
val submittableModel = object : SubmittableTextFieldModelBase("") {
|
||||
override fun submit() {
|
||||
val editingVm = message.editingVm.value
|
||||
val newText = document.text
|
||||
if (editingVm != null) {
|
||||
val id = editingVm.message.id
|
||||
launch(lifetime, Ui) {
|
||||
val chat = editingVm.channel.awaitLoaded(lifetime)
|
||||
if (newText.isBlank()) {
|
||||
SpaceStatsCounterCollector.SEND_EDIT_MESSAGE.log(project, true)
|
||||
chat?.deleteMessage(id)
|
||||
}
|
||||
else {
|
||||
SpaceStatsCounterCollector.SEND_EDIT_MESSAGE.log(project, false)
|
||||
chat?.alterMessage(id, newText)
|
||||
}
|
||||
}
|
||||
}
|
||||
message.stopEditing()
|
||||
}
|
||||
}
|
||||
|
||||
message.editingVm.forEach(lifetime) { editingVm ->
|
||||
if (editingVm == null) {
|
||||
editingModel.value = false
|
||||
return@forEach
|
||||
}
|
||||
val workspace = SpaceWorkspaceComponent.getInstance().workspace.value ?: return@forEach
|
||||
runWriteAction {
|
||||
submittableModel.document.setText(workspace.completion.editable(editingVm.message.text))
|
||||
}
|
||||
editingModel.value = true
|
||||
SpaceStatsCounterCollector.START_EDIT_MESSAGE.log(project)
|
||||
}
|
||||
addToCenter(
|
||||
ToggleableContainer.create(editingModel, { content }, {
|
||||
SubmittableTextField(SpaceBundle.message("chat.message.edit.action.text"), submittableModel, onCancel = {
|
||||
SpaceStatsCounterCollector.DISCARD_EDIT_MESSAGE.log(project)
|
||||
message.stopEditing()
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import circlet.code.api.ReviewerChangedType
|
||||
import circlet.platform.client.resolve
|
||||
import circlet.principals.asUser
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.plugins.newui.VerticalLayout
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.HtmlChunk
|
||||
import com.intellij.openapi.util.text.HtmlChunk.html
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.chat.model.api.SpaceChatItemType.*
|
||||
import com.intellij.space.chat.ui.discussion.SpaceChatCodeDiscussionComponentFactory
|
||||
import com.intellij.space.chat.ui.message.MessageTitleComponent
|
||||
import com.intellij.space.chat.ui.message.SpaceChatMessagePendingHeader
|
||||
import com.intellij.space.chat.ui.message.SpaceMCMessageComponent
|
||||
import com.intellij.space.chat.ui.message.SpaceStyledMessageComponent
|
||||
import com.intellij.space.chat.ui.thread.createCollapsedThreadComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.SpaceAvatarProvider
|
||||
import com.intellij.space.ui.resizeIcon
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.scale.JBUIScale
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.codereview.SingleValueModelImpl
|
||||
import com.intellij.util.ui.codereview.ToggleableContainer
|
||||
import com.intellij.util.ui.codereview.timeline.TimelineItemComponentFactory
|
||||
import com.intellij.util.ui.codereview.timeline.comment.SubmittableTextFieldModelBase
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import icons.SpaceIcons
|
||||
import icons.VcsCodeReviewIcons
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import runtime.Ui
|
||||
import javax.swing.*
|
||||
|
||||
internal class SpaceChatItemComponentFactory(
|
||||
private val project: Project,
|
||||
private val lifetime: Lifetime,
|
||||
private val server: String,
|
||||
private val avatarProvider: SpaceAvatarProvider
|
||||
) : TimelineItemComponentFactory<SpaceChatItem> {
|
||||
|
||||
private val codeDiscussionComponentFactory = SpaceChatCodeDiscussionComponentFactory(project, lifetime, server)
|
||||
|
||||
/**
|
||||
* Method should return [HoverableJPanel] because it is used to implement hovering properly
|
||||
*
|
||||
* @see SpaceChatPanel
|
||||
*/
|
||||
override fun createComponent(item: SpaceChatItem): HoverableJPanel {
|
||||
val component =
|
||||
when (val type = item.type) {
|
||||
is CodeDiscussion ->
|
||||
codeDiscussionComponentFactory.createComponent(item, type.discussion) ?: createUnsupportedMessageTypePanel(item.link)
|
||||
is SimpleText -> SpaceChatEditableComponent(project, lifetime, createSimpleMessageComponent(item), item)
|
||||
is ReviewCompletionStateChanged -> SpaceStyledMessageComponent(createSimpleMessageComponent(item))
|
||||
is ReviewerChanged -> {
|
||||
val user = type.uid.resolve().link()
|
||||
val text = when (type.changeType) {
|
||||
ReviewerChangedType.Joined -> SpaceBundle.message("chat.reviewer.added", user)
|
||||
ReviewerChangedType.Left -> SpaceBundle.message("chat.reviewer.removed", user)
|
||||
}
|
||||
SpaceStyledMessageComponent(SpaceChatMarkdownTextComponent(server, text))
|
||||
}
|
||||
is MergeRequestMerged -> SpaceStyledMessageComponent(
|
||||
SpaceChatMarkdownTextComponent(
|
||||
server,
|
||||
HtmlChunk.raw(
|
||||
SpaceBundle.message(
|
||||
"chat.review.merged",
|
||||
HtmlChunk.text(type.sourceBranch).bold(), // NON-NLS
|
||||
HtmlChunk.text(type.targetBranch).bold() // NON-NLS
|
||||
)
|
||||
).wrapWith(html()).toString()
|
||||
)
|
||||
)
|
||||
is MergeRequestBranchDeleted -> SpaceStyledMessageComponent(
|
||||
SpaceChatMarkdownTextComponent(
|
||||
server,
|
||||
HtmlChunk.raw(
|
||||
SpaceBundle.message("chat.review.deleted.branch", HtmlChunk.text(type.branch).bold()) // NON-NLS
|
||||
).wrapWith(html()).toString()
|
||||
)
|
||||
)
|
||||
is ReviewTitleChanged -> SpaceStyledMessageComponent(
|
||||
SpaceChatMarkdownTextComponent(
|
||||
server,
|
||||
HtmlChunk.raw(
|
||||
SpaceBundle.message(
|
||||
"chat.review.title.changed",
|
||||
HtmlChunk.text(type.oldTitle).bold(), // NON-NLS
|
||||
HtmlChunk.text(type.newTitle).bold() // NON-NLS
|
||||
)
|
||||
).wrapWith(html()).toString()
|
||||
)
|
||||
)
|
||||
is MCMessage -> SpaceMCMessageComponent(server, type.content, item.attachments)
|
||||
is Deleted -> SpaceChatMarkdownTextComponent(server, getGrayTextHtml(SpaceBundle.message("chat.message.removed.text")))
|
||||
is Unknown -> createUnsupportedMessageTypePanel(item.link)
|
||||
}
|
||||
return Item(
|
||||
createAvatarIcon(item),
|
||||
createTitleComponent(item),
|
||||
SpaceChatMessagePendingHeader(item),
|
||||
component.addThreadComponentIfNeeded(item).addStartThreadField(item)
|
||||
)
|
||||
}
|
||||
|
||||
private fun createTitleComponent(item: SpaceChatItem): MessageTitleComponent? {
|
||||
if (item.type == Deleted) {
|
||||
return null
|
||||
}
|
||||
return MessageTitleComponent(project, lifetime, item)
|
||||
}
|
||||
|
||||
private fun createAvatarIcon(item: SpaceChatItem): Icon {
|
||||
val user = item.author.asUser
|
||||
if (user != null) {
|
||||
return avatarProvider.getIcon(user)
|
||||
}
|
||||
if (item.type == Deleted) {
|
||||
return resizeIcon(VcsCodeReviewIcons.Delete)
|
||||
}
|
||||
return resizeIcon(SpaceIcons.Main)
|
||||
}
|
||||
|
||||
private fun resizeIcon(icon: Icon) = resizeIcon(icon, avatarProvider.iconSize.get())
|
||||
|
||||
private fun createUnsupportedMessageTypePanel(messageLink: String?): JComponent {
|
||||
val description = if (messageLink != null) {
|
||||
SpaceBundle.message("chat.unsupported.message.type.with.link", messageLink)
|
||||
}
|
||||
else {
|
||||
SpaceBundle.message("chat.unsupported.message.type")
|
||||
}
|
||||
return JBLabel(description, AllIcons.General.Warning, SwingConstants.LEFT).setCopyable(true)
|
||||
}
|
||||
|
||||
private fun JComponent.addThreadComponentIfNeeded(message: SpaceChatItem): JComponent {
|
||||
return if (message.type is CodeDiscussion) {
|
||||
this
|
||||
}
|
||||
else {
|
||||
val threadComponent = createCollapsedThreadComponent(project, lifetime, message, null)
|
||||
JPanel(VerticalLayout(0)).apply {
|
||||
isOpaque = false
|
||||
add(this@addThreadComponentIfNeeded, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(threadComponent, VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JComponent.addStartThreadField(message: SpaceChatItem): JComponent {
|
||||
val startThreadVm = message.startThreadVm
|
||||
|
||||
val isWritingFirstMessageModel = SingleValueModelImpl(false)
|
||||
startThreadVm.isWritingFirstMessage.forEach(lifetime) {
|
||||
isWritingFirstMessageModel.value = it
|
||||
}
|
||||
val emptyPanel = JPanel().apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.empty()
|
||||
}
|
||||
val statsPlace = SpaceStatsCounterCollector.SendMessagePlace.NEW_THREAD
|
||||
val firstThreadMessageField = ToggleableContainer.create(isWritingFirstMessageModel, { emptyPanel }, {
|
||||
val submittableModel = object : SubmittableTextFieldModelBase("") {
|
||||
override fun submit() {
|
||||
isBusy = true
|
||||
SpaceStatsCounterCollector.SEND_MESSAGE.log(statsPlace, false)
|
||||
launch(lifetime, Ui) {
|
||||
startThreadVm.startThread(document.text)
|
||||
isBusy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
val newMessageComponent = SpaceChatNewMessageWithAvatarComponent(
|
||||
lifetime,
|
||||
SpaceChatAvatarType.THREAD,
|
||||
submittableModel,
|
||||
statsPlace,
|
||||
onCancel = { startThreadVm.stopWritingFirstMessage() }
|
||||
)
|
||||
BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyTop(10)
|
||||
addToCenter(newMessageComponent)
|
||||
}
|
||||
})
|
||||
return JPanel(VerticalLayout(0)).apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.empty()
|
||||
add(this@addStartThreadField, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(firstThreadMessageField, VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSimpleMessageComponent(message: SpaceChatItem): SpaceChatMarkdownTextComponent =
|
||||
SpaceChatMarkdownTextComponent(server, message.text, message.isEdited)
|
||||
|
||||
internal class Item(
|
||||
avatar: Icon,
|
||||
private val title: MessageTitleComponent?,
|
||||
header: JComponent,
|
||||
content: JComponent
|
||||
) : HoverableJPanel() {
|
||||
companion object {
|
||||
val AVATAR_GAP: Int
|
||||
get() = JBUIScale.scale(8)
|
||||
}
|
||||
|
||||
init {
|
||||
val headerPart = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyLeft(avatar.iconWidth + AVATAR_GAP)
|
||||
addToCenter(header)
|
||||
}
|
||||
|
||||
val avatarPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.empty()
|
||||
addToTop(userAvatar(avatar))
|
||||
}
|
||||
val rightPart = JPanel(VerticalLayout(JBUI.scale(5))).apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyLeft(AVATAR_GAP)
|
||||
title?.let { add(it, VerticalLayout.FILL_HORIZONTAL) }
|
||||
val contentWrapper = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
if (title == null) {
|
||||
border = JBUI.Borders.emptyTop(avatar.iconHeight / 4)
|
||||
}
|
||||
addToCenter(content)
|
||||
}
|
||||
add(contentWrapper, VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
val messagePanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
addToLeft(avatarPanel)
|
||||
addToCenter(rightPart)
|
||||
}
|
||||
|
||||
layout = VerticalLayout(JBUI.scale(3))
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyTop(15)
|
||||
|
||||
add(headerPart, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(messagePanel, VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
|
||||
private fun userAvatar(avatar: Icon) = JLabel(avatar)
|
||||
|
||||
override fun hoverStateChanged(isHovered: Boolean) {
|
||||
title ?: return
|
||||
title.actionsPanel.isVisible = isHovered
|
||||
title.revalidate()
|
||||
title.repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import javax.swing.AbstractListModel
|
||||
import kotlin.math.min
|
||||
|
||||
internal class SpaceChatItemListModel : AbstractListModel<SpaceChatItem>() {
|
||||
private val items = mutableListOf<SpaceChatItem>()
|
||||
|
||||
override fun getElementAt(index: Int): SpaceChatItem = items[index]
|
||||
|
||||
override fun getSize() = items.size
|
||||
|
||||
fun messageListUpdated(new: List<SpaceChatItem>) {
|
||||
val newItems = keepOldItemsState(new)
|
||||
val oldItems = ArrayList(items)
|
||||
|
||||
items.clear()
|
||||
items.addAll(newItems)
|
||||
|
||||
for (i in 0 until min(oldItems.size, newItems.size)) {
|
||||
if (oldItems[i] != newItems[i]) {
|
||||
fireContentsChanged(this, i, i)
|
||||
}
|
||||
}
|
||||
if (newItems.size < oldItems.size) {
|
||||
fireIntervalRemoved(this, newItems.size, oldItems.size - 1)
|
||||
}
|
||||
if (newItems.size > oldItems.size) {
|
||||
fireIntervalAdded(this, oldItems.size, newItems.size - 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun keepOldItemsState(new: List<SpaceChatItem>): List<SpaceChatItem> {
|
||||
val oldItems = items.associateBy { it.id }
|
||||
return List(new.size) { i ->
|
||||
val newItem = new[i]
|
||||
val oldItem = oldItems[newItem.id]
|
||||
if (oldItem == newItem) {
|
||||
// if nothing changed, keep old reference
|
||||
oldItem
|
||||
}
|
||||
else {
|
||||
if (oldItem != null) {
|
||||
// if smth changed, keep loadingThread state
|
||||
newItem.loadingThread.value = oldItem.loadingThread.value
|
||||
}
|
||||
newItem
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import circlet.client.api.UnfurlAttachment
|
||||
import circlet.completion.mentions.MentionConverter
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.space.chat.markdown.convertToHtml
|
||||
import com.intellij.space.chat.markdown.processUnfurls
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.vcs.review.HtmlEditorPane
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import org.jetbrains.annotations.NonNls
|
||||
|
||||
internal class SpaceChatMarkdownTextComponent(
|
||||
private val server: @NonNls String,
|
||||
initialText: @NlsSafe String? = null,
|
||||
initialIsEdited: Boolean = false,
|
||||
initialUnfurls: List<UnfurlAttachment> = emptyList()
|
||||
) : HtmlEditorPane() {
|
||||
init {
|
||||
putClientProperty(UIUtil.HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY, true)
|
||||
initialText?.let { setMarkdownText(it, initialIsEdited, initialUnfurls) }
|
||||
}
|
||||
|
||||
fun setMarkdownText(text: @NlsSafe String, isEdited: Boolean, unfurls: List<UnfurlAttachment> = emptyList()) {
|
||||
setBody(processItemText(server, text, isEdited, unfurls))
|
||||
}
|
||||
}
|
||||
|
||||
@NlsSafe
|
||||
private fun processItemText(
|
||||
server: @NonNls String,
|
||||
@NlsSafe text: String,
|
||||
isEdited: Boolean,
|
||||
unfurls: List<UnfurlAttachment>
|
||||
): String {
|
||||
val markdown = MentionConverter.markdown(text, server).let {
|
||||
if (isEdited) {
|
||||
it + " " + getGrayTextHtml(SpaceBundle.message("chat.message.edited.text"))
|
||||
}
|
||||
else {
|
||||
it
|
||||
}
|
||||
}
|
||||
return convertToHtml(processUnfurls(markdown, unfurls), server)
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import circlet.client.api.M2ChannelRecord
|
||||
import circlet.m2.ChannelsVm
|
||||
import circlet.platform.api.Ref
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.chat.model.api.SpaceChatHeaderDetails
|
||||
import com.intellij.space.chat.ui.header.createComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.ui.ScrollPaneFactory
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import net.miginfocom.layout.AC
|
||||
import net.miginfocom.layout.CC
|
||||
import net.miginfocom.layout.LC
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.ScrollPaneConstants
|
||||
|
||||
internal class SpaceChatPanel(
|
||||
private val project: Project,
|
||||
private val lifetime: Lifetime,
|
||||
private val parent: Disposable,
|
||||
private val channelsVm: ChannelsVm,
|
||||
private val chatRecord: Ref<M2ChannelRecord>,
|
||||
private val headerDetails: SpaceChatHeaderDetails
|
||||
) : BorderLayoutPanel() {
|
||||
|
||||
init {
|
||||
isOpaque = true
|
||||
background = EditorColorsManager.getInstance().globalScheme.defaultBackground
|
||||
|
||||
val contentComponent = createContentComponent()
|
||||
val scrollableContentPanel = ScrollPaneFactory.createScrollPane(
|
||||
contentComponent,
|
||||
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
|
||||
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
|
||||
).apply {
|
||||
border = JBUI.Borders.empty()
|
||||
isOpaque = false
|
||||
viewport.isOpaque = false
|
||||
}
|
||||
addToCenter(scrollableContentPanel)
|
||||
}
|
||||
|
||||
private fun createContentComponent(): JComponent {
|
||||
val contentPanel = SpaceChatContentPanel(project, lifetime, parent, channelsVm, chatRecord).apply {
|
||||
setLoadingText(SpaceBundle.message("chat.loading.text"))
|
||||
}
|
||||
|
||||
return JPanel(null).apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.empty(5, 20)
|
||||
|
||||
val maxWidth = JBUI.scale(600)
|
||||
|
||||
layout = MigLayout(LC().gridGap("0", "${JBUI.scale(10)}")
|
||||
.insets("0", "0", "0", "0")
|
||||
.flowY(),
|
||||
AC().size(":$maxWidth:$maxWidth").gap("push"))
|
||||
add(headerDetails.createComponent(lifetime), CC().growX().minWidth(""))
|
||||
add(contentPanel, CC().grow().push().minWidth(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui
|
||||
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.ui.MessageDialogBuilder
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.SpaceAvatarProvider
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.codereview.timeline.comment.SubmittableTextField
|
||||
import com.intellij.util.ui.codereview.timeline.comment.SubmittableTextFieldModel
|
||||
import com.intellij.util.ui.codereview.timeline.comment.SubmittableTextFieldModelBase
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import net.miginfocom.layout.CC
|
||||
import net.miginfocom.layout.LC
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal fun createNewMessageField(
|
||||
chatVM: M2ChannelVm,
|
||||
avatarType: SpaceChatAvatarType,
|
||||
statsPlace: SpaceStatsCounterCollector.SendMessagePlace,
|
||||
pendingStateProvider: () -> Boolean = { false },
|
||||
onCancel: (() -> Unit)? = null,
|
||||
onSend: (() -> Unit)? = null
|
||||
): JComponent {
|
||||
val submittableModel = object : SubmittableTextFieldModelBase("") {
|
||||
override fun submit() {
|
||||
val isPending = pendingStateProvider()
|
||||
SpaceStatsCounterCollector.SEND_MESSAGE.log(statsPlace, isPending)
|
||||
chatVM.sendMessage(document.text, pending = isPending)
|
||||
runWriteAction {
|
||||
document.setText("")
|
||||
}
|
||||
onSend?.invoke()
|
||||
}
|
||||
}
|
||||
return SpaceChatNewMessageWithAvatarComponent(chatVM.lifetime, avatarType, submittableModel, statsPlace, onCancel)
|
||||
}
|
||||
|
||||
internal class SpaceChatNewMessageWithAvatarComponent(
|
||||
avatarProviderLifetime: Lifetime,
|
||||
avatarType: SpaceChatAvatarType,
|
||||
submittableModel: SubmittableTextFieldModel,
|
||||
statsPlace: SpaceStatsCounterCollector.SendMessagePlace,
|
||||
onCancel: (() -> Unit)?
|
||||
) : JPanel(null) {
|
||||
init {
|
||||
isOpaque = false
|
||||
layout = MigLayout(LC().gridGap("0", "0")
|
||||
.insets("0", "0", "0", "0")
|
||||
.fill()).apply {
|
||||
columnConstraints = "[][]"
|
||||
}
|
||||
val avatarProvider = SpaceAvatarProvider(avatarProviderLifetime, this, avatarType.size)
|
||||
val avatar = avatarProvider.getIcon(SpaceWorkspaceComponent.getInstance().workspace.value!!.me.value)
|
||||
val avatarComponent = JBUI.Panels.simplePanel(JBLabel(avatar)).apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyRight(SpaceChatItemComponentFactory.Item.AVATAR_GAP)
|
||||
}
|
||||
val submittableTextField = SubmittableTextField(
|
||||
SpaceBundle.message("chat.comment.action.text"),
|
||||
submittableModel,
|
||||
onCancel = {
|
||||
if (
|
||||
submittableModel.document.text.isBlank() ||
|
||||
MessageDialogBuilder.yesNo(
|
||||
SpaceBundle.message("chat.message.new.discard.changes.title"),
|
||||
SpaceBundle.message("chat.message.new.discard.changes.text")
|
||||
).ask(this)
|
||||
) {
|
||||
SpaceStatsCounterCollector.DISCARD_SEND_MESSAGE.log(statsPlace)
|
||||
onCancel?.invoke()
|
||||
}
|
||||
}
|
||||
)
|
||||
add(avatarComponent, CC().pushY())
|
||||
add(submittableTextField, CC().growX().pushX().alignY("center"))
|
||||
}
|
||||
}
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.discussion
|
||||
|
||||
import circlet.code.api.CodeDiscussionRecord
|
||||
import circlet.code.api.CodeDiscussionSnippet
|
||||
import circlet.platform.api.Ref
|
||||
import circlet.platform.client.property
|
||||
import circlet.platform.client.resolve
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.plugins.newui.HorizontalLayout
|
||||
import com.intellij.ide.plugins.newui.VerticalLayout
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.chat.ui.SpaceChatAvatarType
|
||||
import com.intellij.space.chat.ui.SpaceChatEditableComponent
|
||||
import com.intellij.space.chat.ui.SpaceChatMarkdownTextComponent
|
||||
import com.intellij.space.chat.ui.SpaceChatNewMessageWithAvatarComponent
|
||||
import com.intellij.space.chat.ui.message.createResolvedComponent
|
||||
import com.intellij.space.chat.ui.thread.createCollapsedThreadComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.RoundedLineBorder
|
||||
import com.intellij.ui.SideBorder
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.panels.NonOpaquePanel
|
||||
import com.intellij.ui.scale.JBUIScale
|
||||
import com.intellij.util.PathUtil
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.codereview.InlineIconButton
|
||||
import com.intellij.util.ui.codereview.SingleValueModel
|
||||
import com.intellij.util.ui.codereview.SingleValueModelImpl
|
||||
import com.intellij.util.ui.codereview.ToggleableContainer
|
||||
import com.intellij.util.ui.codereview.timeline.comment.SubmittableTextFieldModelBase
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import net.miginfocom.layout.CC
|
||||
import net.miginfocom.layout.LC
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import runtime.Ui
|
||||
import runtime.reactive.Property
|
||||
import runtime.reactive.property.map
|
||||
import java.awt.event.ActionListener
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.SwingConstants
|
||||
|
||||
internal class SpaceChatCodeDiscussionComponentFactory(
|
||||
private val project: Project,
|
||||
private val lifetime: Lifetime,
|
||||
private val server: String,
|
||||
) {
|
||||
fun createComponent(message: SpaceChatItem, codeDiscussion: Ref<CodeDiscussionRecord>): JComponent? {
|
||||
val discussionMessage = message.projectedItem ?: return null
|
||||
val discussion = codeDiscussion.resolve()
|
||||
val discussionProperty = codeDiscussion.property()
|
||||
val resolved = lifetime.map(discussionProperty) { it.resolved }
|
||||
val collapseButton = InlineIconButton(AllIcons.General.CollapseComponent, AllIcons.General.CollapseComponentHover)
|
||||
val expandButton = InlineIconButton(AllIcons.General.ExpandComponent, AllIcons.General.ExpandComponentHover)
|
||||
|
||||
val filePath = discussion.anchor.filename ?: return null
|
||||
val fileNameComponent = JBUI.Panels.simplePanel(
|
||||
createFileNameComponent(filePath, collapseButton, expandButton, resolved)
|
||||
).apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.empty(8)
|
||||
}
|
||||
val snippet = discussion.snippet ?: return null
|
||||
val diffEditorComponent = when (snippet) {
|
||||
is CodeDiscussionSnippet.PlainSnippet -> {
|
||||
return null
|
||||
}
|
||||
is CodeDiscussionSnippet.InlineDiffSnippet -> createDiffComponent(project, discussion.anchor, snippet)
|
||||
}.apply {
|
||||
border = IdeBorderFactory.createBorder(SideBorder.TOP)
|
||||
}
|
||||
|
||||
val snapshotComponent = JPanel(VerticalLayout(0)).apply {
|
||||
isOpaque = true
|
||||
background = EditorColorsManager.getInstance().globalScheme.defaultBackground
|
||||
border = RoundedLineBorder(UIUtil.CONTRAST_BORDER_COLOR, IdeBorderFactory.BORDER_ROUNDNESS)
|
||||
add(fileNameComponent)
|
||||
add(diffEditorComponent, VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
val reviewCommentComponent = SpaceChatMarkdownTextComponent(server)
|
||||
|
||||
val collapseModel = SingleValueModelImpl(true)
|
||||
val panel = JPanel(VerticalLayout(JBUIScale.scale(4))).apply {
|
||||
isOpaque = false
|
||||
add(snapshotComponent, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(createEditableReviewCommentComponent(reviewCommentComponent, message, collapseModel), VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
|
||||
val outerReplyComponent = createOuterReplyComponent(discussionProperty, message)
|
||||
val threadComponent = createThreadComponent(discussionProperty, message)
|
||||
val component = JPanel(VerticalLayout(0)).apply {
|
||||
isOpaque = false
|
||||
add(panel, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(threadComponent, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(outerReplyComponent, VerticalLayout.FILL_HORIZONTAL)
|
||||
addDiscussionExpandCollapseHandling(
|
||||
this,
|
||||
collapseModel,
|
||||
listOf(outerReplyComponent, threadComponent, reviewCommentComponent, diffEditorComponent),
|
||||
collapseButton,
|
||||
expandButton,
|
||||
resolved
|
||||
)
|
||||
}
|
||||
|
||||
discussionMessage.forEach(lifetime) {
|
||||
reviewCommentComponent.setMarkdownText(it.text, it.edited != null)
|
||||
reviewCommentComponent.repaint()
|
||||
}
|
||||
|
||||
return component
|
||||
}
|
||||
|
||||
private fun createOuterReplyComponent(discussion: Property<CodeDiscussionRecord>, message: SpaceChatItem): JComponent {
|
||||
val newMessageStateModel = SingleValueModelImpl(false)
|
||||
val replyAction = ActionLink(SpaceBundle.message("chat.reply.action")) {
|
||||
newMessageStateModel.value = true
|
||||
}
|
||||
val resolveComponent = createResolveComponent(lifetime, message.chat.client, discussion)
|
||||
val actionsComponent = JPanel(HorizontalLayout(JBUI.scale(5))).apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyTop(8)
|
||||
add(replyAction)
|
||||
add(resolveComponent)
|
||||
}
|
||||
val outerReplyComponent = ToggleableContainer.create(
|
||||
newMessageStateModel,
|
||||
mainComponentSupplier = { actionsComponent },
|
||||
toggleableComponentSupplier = {
|
||||
val statsPlace = SpaceStatsCounterCollector.SendMessagePlace.FIRST_DISCUSSION_ANSWER
|
||||
val submittableModel = object : SubmittableTextFieldModelBase("") {
|
||||
override fun submit() {
|
||||
val isPending = false
|
||||
SpaceStatsCounterCollector.SEND_MESSAGE.log(statsPlace, isPending)
|
||||
launch(lifetime, Ui) {
|
||||
val thread = message.loadThread(lifetime)
|
||||
thread?.sendMessage(document.text, pending = isPending)
|
||||
runWriteAction {
|
||||
document.setText("")
|
||||
}
|
||||
newMessageStateModel.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
SpaceChatNewMessageWithAvatarComponent(lifetime, SpaceChatAvatarType.THREAD, submittableModel, statsPlace,
|
||||
onCancel = {
|
||||
newMessageStateModel.value = false
|
||||
})
|
||||
}
|
||||
)
|
||||
message.threadPreview!!.messageCount.forEach(lifetime) { count ->
|
||||
actionsComponent.isVisible = count == 0
|
||||
}
|
||||
return BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
addToCenter(outerReplyComponent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createThreadComponent(discussionProperty: Property<CodeDiscussionRecord>, message: SpaceChatItem): JComponent {
|
||||
val threadActionsFactory = SpaceChatDiscussionActionsFactory(
|
||||
lifetime,
|
||||
message.chat.client,
|
||||
discussionProperty
|
||||
)
|
||||
return BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
addToCenter(createCollapsedThreadComponent(project, lifetime, message, threadActionsFactory, withFirst = false))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEditableReviewCommentComponent(
|
||||
reviewCommentContent: JComponent,
|
||||
message: SpaceChatItem,
|
||||
collapseModel: SingleValueModel<Boolean>
|
||||
): JComponent {
|
||||
val editableReviewCommentComponent = SpaceChatEditableComponent(project, lifetime, reviewCommentContent, message)
|
||||
editableReviewCommentComponent.editingModel.addValueUpdatedListener { isEditing ->
|
||||
if (isEditing) {
|
||||
collapseModel.value = false
|
||||
}
|
||||
}
|
||||
return editableReviewCommentComponent
|
||||
}
|
||||
|
||||
private fun createFileNameComponent(
|
||||
filePath: String,
|
||||
collapseButton: InlineIconButton,
|
||||
expandButton: InlineIconButton,
|
||||
resolved: Property<Boolean>
|
||||
): JComponent {
|
||||
val name = PathUtil.getFileName(filePath)
|
||||
val parentPath = PathUtil.getParentPath(filePath)
|
||||
val nameLabel = JBLabel(name, null, SwingConstants.LEFT).setCopyable(true)
|
||||
|
||||
val resolvedLabel = createResolvedComponent(lifetime, resolved)
|
||||
return NonOpaquePanel(MigLayout(LC().insets("0").gridGap("${JBUI.scale(5)}", "0").fill().noGrid())).apply {
|
||||
add(nameLabel, CC().minWidth("0").shrinkPrio(9))
|
||||
|
||||
if (parentPath.isNotBlank()) {
|
||||
add(JBLabel(parentPath).apply {
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
setCopyable(true)
|
||||
}, CC().minWidth("0").shrinkPrio(10))
|
||||
}
|
||||
add(resolvedLabel, CC().hideMode(3).shrinkPrio(0))
|
||||
add(collapseButton, CC().hideMode(3).shrinkPrio(0))
|
||||
add(expandButton, CC().hideMode(3).shrinkPrio(0))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Extract it to code-review module
|
||||
private fun addDiscussionExpandCollapseHandling(
|
||||
parentComponent: JComponent,
|
||||
collapseModel: SingleValueModel<Boolean>,
|
||||
componentsToHide: Collection<JComponent>,
|
||||
collapseButton: InlineIconButton,
|
||||
expandButton: InlineIconButton,
|
||||
resolved: Property<Boolean>
|
||||
) {
|
||||
collapseButton.actionListener = ActionListener {
|
||||
collapseModel.value = true
|
||||
SpaceStatsCounterCollector.COLLAPSE_DISCUSSION.log(project)
|
||||
}
|
||||
expandButton.actionListener = ActionListener {
|
||||
collapseModel.value = false
|
||||
SpaceStatsCounterCollector.EXPAND_DISCUSSION.log(project)
|
||||
}
|
||||
|
||||
fun stateUpdated(collapsed: Boolean) {
|
||||
collapseButton.isVisible = resolved.value && !collapsed
|
||||
expandButton.isVisible = resolved.value && collapsed
|
||||
|
||||
val areComponentsVisible = !resolved.value || !collapsed
|
||||
componentsToHide.forEach { it.isVisible = areComponentsVisible }
|
||||
parentComponent.revalidate()
|
||||
parentComponent.repaint()
|
||||
}
|
||||
|
||||
collapseModel.addValueUpdatedListener {
|
||||
stateUpdated(it)
|
||||
}
|
||||
|
||||
resolved.forEach(lifetime) {
|
||||
collapseModel.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.discussion
|
||||
|
||||
import circlet.code.api.CodeDiscussionAnchor
|
||||
import circlet.code.api.CodeDiscussionSnippet
|
||||
import com.intellij.diff.util.DiffDrawUtil
|
||||
import com.intellij.diff.util.TextDiffType
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.LineNumberConverter
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.JBColor.namedColor
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.codereview.timeline.TimelineDiffComponentFactory
|
||||
import org.jetbrains.annotations.Nls
|
||||
import runtime.code.DiffLineType
|
||||
import runtime.code.InlineDiffLine
|
||||
import runtime.text.TextRange
|
||||
import runtime.text.end
|
||||
import java.awt.Color
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal fun createDiffComponent(
|
||||
project: Project,
|
||||
anchor: CodeDiscussionAnchor,
|
||||
snippet: CodeDiscussionSnippet.InlineDiffSnippet
|
||||
): JComponent {
|
||||
val lines = snippet.lines
|
||||
val lineSeparator = "\n"
|
||||
val text = lines.joinToString(lineSeparator) { line ->
|
||||
line.text
|
||||
}
|
||||
val anchorLine = lines.findAnchorLine(anchor)
|
||||
val editorFactory = EditorFactory.getInstance()
|
||||
return TimelineDiffComponentFactory(project, editorFactory).createDiffComponent(text) { editor ->
|
||||
editor
|
||||
.addLinesHighlighting(lines, anchorLine, lineSeparator)
|
||||
.convertLinesNumber(lines)
|
||||
editor.scrollPane.border = JBUI.Borders.empty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun EditorEx.convertLinesNumber(lines: List<InlineDiffLine>): EditorEx {
|
||||
if (lines.any { it.newLineNum != null && it.oldLineNum != null }) {
|
||||
gutter.setLineNumberConverter(
|
||||
object : LineNumberConverter {
|
||||
override fun convert(editor: Editor, lineNumber: Int) = lines[lineNumber - 1].oldLineNum?.plus(1)
|
||||
|
||||
override fun getMaxLineNumber(editor: Editor): Int? = lines.mapNotNull { it.oldLineNum }.maxOrNull()
|
||||
},
|
||||
object : LineNumberConverter {
|
||||
override fun convert(editor: Editor, lineNumber: Int) = lines[lineNumber - 1].newLineNum?.plus(1)
|
||||
|
||||
override fun getMaxLineNumber(editor: Editor): Int? = lines.mapNotNull { it.newLineNum }.maxOrNull()
|
||||
}
|
||||
)
|
||||
}
|
||||
else {
|
||||
gutter.setLineNumberConverter(object : LineNumberConverter {
|
||||
override fun convert(editor: Editor, lineNumber: Int) = lines[lineNumber - 1].newLineNum?.plus(1)
|
||||
|
||||
override fun getMaxLineNumber(editor: Editor): Int? = lines.last().newLineNum
|
||||
})
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun EditorEx.addLinesHighlighting(
|
||||
lines: List<InlineDiffLine>,
|
||||
anchorLine: InlineDiffLine?,
|
||||
@NlsSafe separator: String
|
||||
): EditorEx {
|
||||
val prefixLengths = ArrayList<Int>()
|
||||
prefixLengths.add(0)
|
||||
for (i in lines.indices) {
|
||||
prefixLengths.add(prefixLengths[i] + lines[i].text.length + separator.length)
|
||||
}
|
||||
lines.forEachIndexed { i, line ->
|
||||
addFullLineHighlighting(line, i, anchorLine)
|
||||
val prefixLength = prefixLengths[i]
|
||||
line.inserts?.let { ranges -> addInlineHighlighting(line, prefixLength, ranges, TextDiffType.INSERTED) }
|
||||
line.deletes?.let { ranges -> addInlineHighlighting(line, prefixLength, ranges, TextDiffType.DELETED) }
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun EditorEx.addFullLineHighlighting(line: InlineDiffLine, lineIndex: Int, anchorLine: InlineDiffLine?) {
|
||||
if (anchorLine != null && anchorLine == line) {
|
||||
DiffDrawUtil.createHighlighter(this, lineIndex, lineIndex + 1, AnchorLine, false)
|
||||
}
|
||||
else {
|
||||
val diffType = when (line.type) {
|
||||
DiffLineType.ADDED -> TextDiffType.INSERTED
|
||||
DiffLineType.DELETED -> TextDiffType.DELETED
|
||||
DiffLineType.MODIFIED -> TextDiffType.MODIFIED
|
||||
DiffLineType.CONFLICT_OLD -> TextDiffType.CONFLICT
|
||||
DiffLineType.CONFLICT_NEW -> TextDiffType.CONFLICT
|
||||
DiffLineType.FILTERED_ADDED -> TextDiffType.INSERTED
|
||||
DiffLineType.FILTERED_DELETED -> TextDiffType.DELETED
|
||||
DiffLineType.FILTERED_MODIFIED -> TextDiffType.MODIFIED
|
||||
null -> null
|
||||
}
|
||||
val ignored = (line.deletes?.any { range -> !isFullLineRange(line, range) } ?: false) ||
|
||||
(line.inserts?.any { range -> !isFullLineRange(line, range) } ?: false)
|
||||
if (diffType != null) {
|
||||
DiffDrawUtil.createHighlighter(this, lineIndex, lineIndex + 1, diffType, ignored)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isFullLineRange(line: InlineDiffLine, range: TextRange): Boolean = line.text.length == range.length
|
||||
|
||||
private fun EditorEx.addInlineHighlighting(line: InlineDiffLine, prefixLength: Int, ranges: List<TextRange>, diffType: TextDiffType) {
|
||||
ranges.forEach { range ->
|
||||
if (!isFullLineRange(line, range)) {
|
||||
DiffDrawUtil.createInlineHighlighter(this, prefixLength + range.start, prefixLength + range.end, diffType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<InlineDiffLine>.findAnchorLine(anchor: CodeDiscussionAnchor): InlineDiffLine? =
|
||||
this.find { line ->
|
||||
line.oldLineNum != null && anchor.oldLine == line.oldLineNum ||
|
||||
line.newLineNum != null && anchor.line == line.newLineNum
|
||||
}
|
||||
|
||||
private object AnchorLine : TextDiffType {
|
||||
@Nls
|
||||
override fun getName() = SpaceBundle.message("review.diff.type.anchor.name")
|
||||
|
||||
override fun getColor(editor: Editor?): Color = namedColor(
|
||||
"Space.Review.diffAnchorBackground",
|
||||
JBColor(0xFBF1D1, 0x544B2D)
|
||||
)
|
||||
|
||||
override fun getIgnoredColor(editor: Editor?) = getColor(editor)
|
||||
|
||||
override fun getMarkerColor(editor: Editor?) = getColor(editor)
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.discussion
|
||||
|
||||
import circlet.code.api.CodeDiscussionRecord
|
||||
import circlet.platform.client.KCircletClient
|
||||
import com.intellij.space.chat.ui.thread.SpaceChatThreadActionsFactory
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import runtime.reactive.Property
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal class SpaceChatDiscussionActionsFactory(
|
||||
private val lifetime: Lifetime,
|
||||
private val client: KCircletClient,
|
||||
private val discussion: Property<CodeDiscussionRecord>
|
||||
) : SpaceChatThreadActionsFactory {
|
||||
override fun createActionsComponent(): JComponent = createResolveComponent(lifetime, client, discussion)
|
||||
}
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.discussion
|
||||
|
||||
import circlet.code.api.CodeDiscussionRecord
|
||||
import circlet.code.codeReview
|
||||
import circlet.platform.client.KCircletClient
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.codereview.SingleValueModel
|
||||
import com.intellij.util.ui.codereview.SingleValueModelImpl
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.delay
|
||||
import libraries.coroutines.extra.launch
|
||||
import runtime.Ui
|
||||
import runtime.reactive.Property
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
|
||||
internal fun createResolveComponent(
|
||||
lifetime: Lifetime,
|
||||
client: KCircletClient,
|
||||
discussion: Property<CodeDiscussionRecord>,
|
||||
): JComponent {
|
||||
val resolvingLabel = JLabel(SpaceBundle.message("chat.resolving.action.state")).apply {
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
}
|
||||
val reopeningLabel = JLabel(SpaceBundle.message("chat.reopening.action.state")).apply {
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
}
|
||||
val resolvingModel = SingleValueModelImpl(ResolvingState.READY)
|
||||
val resolveReopenLabel = createResolveReopenLabel(lifetime, client, discussion, resolvingModel)
|
||||
val contentPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
addToCenter(resolveReopenLabel)
|
||||
}
|
||||
resolvingModel.addValueUpdatedListener { newState ->
|
||||
val stateLabel = when (newState) {
|
||||
ResolvingState.RESOLVING -> resolvingLabel
|
||||
ResolvingState.REOPENING -> reopeningLabel
|
||||
ResolvingState.READY -> resolveReopenLabel
|
||||
}
|
||||
contentPanel.removeAll()
|
||||
contentPanel.addToCenter(stateLabel)
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
return contentPanel
|
||||
}
|
||||
|
||||
private fun createResolveReopenLabel(
|
||||
lifetime: Lifetime,
|
||||
client: KCircletClient,
|
||||
discussion: Property<CodeDiscussionRecord>,
|
||||
resolvingModel: SingleValueModel<ResolvingState>
|
||||
): JComponent {
|
||||
val reviewService = client.codeReview
|
||||
fun resolve() {
|
||||
val currentDiscussion = discussion.value
|
||||
launch(lifetime, Ui) {
|
||||
if (!currentDiscussion.resolved) {
|
||||
SpaceStatsCounterCollector.RESOLVE_DISCUSSION.log()
|
||||
resolvingModel.value = ResolvingState.RESOLVING
|
||||
}
|
||||
else {
|
||||
SpaceStatsCounterCollector.REOPEN_DISCUSSION.log()
|
||||
resolvingModel.value = ResolvingState.REOPENING
|
||||
}
|
||||
delay(200) // reduce status label blinking
|
||||
reviewService.resolveCodeDiscussion(currentDiscussion.id, !currentDiscussion.resolved)
|
||||
resolvingModel.value = ResolvingState.READY
|
||||
}
|
||||
}
|
||||
|
||||
val label = ActionLink(SpaceBundle.message("chat.resolve.action")) {
|
||||
resolve()
|
||||
}
|
||||
discussion.forEach(lifetime) {
|
||||
label.text = if (it.resolved) {
|
||||
SpaceBundle.message("chat.reopen.action")
|
||||
}
|
||||
else {
|
||||
SpaceBundle.message("chat.resolve.action")
|
||||
}
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
private enum class ResolvingState {
|
||||
RESOLVING,
|
||||
REOPENING,
|
||||
READY
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.header
|
||||
|
||||
import com.intellij.space.chat.model.api.SpaceChatHeaderDetails
|
||||
import com.intellij.space.chat.model.impl.SpaceChatReviewHeaderDetails
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal fun SpaceChatHeaderDetails.createComponent(lifetime: Lifetime): JComponent = when (this) {
|
||||
is SpaceChatReviewHeaderDetails -> SpaceChatReviewHeaderComponent(lifetime, this)
|
||||
else -> throw IllegalArgumentException("This chat header type is not supported")
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.header
|
||||
|
||||
import circlet.code.api.CodeReviewState
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.util.text.HtmlBuilder
|
||||
import com.intellij.space.chat.model.impl.SpaceChatReviewHeaderDetails
|
||||
import com.intellij.space.chat.ui.SpaceChatAvatarType
|
||||
import com.intellij.space.chat.ui.getGrayTextHtml
|
||||
import com.intellij.space.ui.resizeIcon
|
||||
import com.intellij.space.vcs.review.HtmlEditorPane
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import icons.VcsCodeReviewIcons
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import net.miginfocom.layout.CC
|
||||
import net.miginfocom.layout.LC
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import org.jetbrains.annotations.Nls
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal class SpaceChatReviewHeaderComponent(
|
||||
lifetime: Lifetime,
|
||||
details: SpaceChatReviewHeaderDetails
|
||||
) : JPanel() {
|
||||
companion object {
|
||||
private const val STATE_ICON_FACTOR: Double = 2.0 / 3
|
||||
|
||||
private const val STATE_ICON_GAP_FACTOR: Double = 0.5
|
||||
}
|
||||
|
||||
private val stateIconSize: Int
|
||||
get() = (SpaceChatAvatarType.MAIN_CHAT.size.get() * STATE_ICON_FACTOR).toInt()
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
layout = MigLayout(LC().gridGap("0", "0")
|
||||
.insets("0", "0", "0", "0")
|
||||
.fill()).apply {
|
||||
columnConstraints = "[][]"
|
||||
}
|
||||
|
||||
val reviewStateIconPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyRight((stateIconSize * STATE_ICON_GAP_FACTOR).toInt())
|
||||
}
|
||||
val headerContent = HtmlEditorPane().apply {
|
||||
font = font.deriveFont((font.size * 1.5).toFloat())
|
||||
}
|
||||
|
||||
add(reviewStateIconPanel, CC().pushY())
|
||||
add(headerContent, CC().pushX().alignY("center"))
|
||||
|
||||
details.title.forEach(lifetime) { newTitle ->
|
||||
headerContent.setBody(getHeaderHtml(newTitle, details.reviewKey))
|
||||
}
|
||||
details.state.forEach(lifetime) { newState ->
|
||||
if (reviewStateIconPanel.components.isNotEmpty()) {
|
||||
reviewStateIconPanel.remove(0)
|
||||
}
|
||||
reviewStateIconPanel.add(getReviewStateIcon(newState), 0)
|
||||
reviewStateIconPanel.revalidate()
|
||||
reviewStateIconPanel.repaint()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getReviewStateIcon(state: CodeReviewState): JComponent {
|
||||
val icon = when (state) {
|
||||
CodeReviewState.Opened -> VcsCodeReviewIcons.PullRequestOpen
|
||||
CodeReviewState.Closed, CodeReviewState.Deleted -> VcsCodeReviewIcons.PullRequestClosed
|
||||
}
|
||||
return JLabel(resizeIcon(icon, stateIconSize))
|
||||
}
|
||||
|
||||
@Nls
|
||||
private fun getHeaderHtml(@Nls title: String, @NlsSafe reviewKey: String?): String {
|
||||
val builder = HtmlBuilder().append(title)
|
||||
if (reviewKey != null) {
|
||||
builder
|
||||
.nbsp()
|
||||
.appendRaw(getGrayTextHtml(reviewKey))
|
||||
}
|
||||
return builder.toString()
|
||||
}
|
||||
}
|
||||
-151
@@ -1,151 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.message
|
||||
|
||||
import circlet.client.api.CApplicationPrincipalDetails
|
||||
import circlet.client.api.CPrincipal
|
||||
import circlet.client.api.CUserPrincipalDetails
|
||||
import circlet.platform.client.resolve
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.plugins.newui.HorizontalLayout
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.MessageDialogBuilder
|
||||
import com.intellij.openapi.util.text.HtmlChunk
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.chat.model.api.SpaceChatItemAdditionalFeature
|
||||
import com.intellij.space.chat.ui.link
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.SpaceAutoUpdatableComponentService
|
||||
import com.intellij.space.utils.SpaceUrls
|
||||
import com.intellij.space.utils.formatPrettyDateTime
|
||||
import com.intellij.space.vcs.review.HtmlEditorPane
|
||||
import com.intellij.ui.AnimatedIcon
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.codereview.InlineIconButton
|
||||
import icons.VcsCodeReviewIcons
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.delay
|
||||
import libraries.coroutines.extra.launch
|
||||
import runtime.Ui
|
||||
import java.awt.event.ActionListener
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal class MessageTitleComponent(
|
||||
private val project: Project,
|
||||
private val lifetime: Lifetime,
|
||||
message: SpaceChatItem
|
||||
) : JPanel(HorizontalLayout(JBUI.scale(5))) {
|
||||
val actionsPanel = JPanel(HorizontalLayout(JBUI.scale(5))).apply {
|
||||
isOpaque = false
|
||||
isVisible = false
|
||||
add(createStartThreadButton(message))
|
||||
createEditButton(message)?.let { add(it) }
|
||||
createDeleteButton(message)?.let { add(it) }
|
||||
}
|
||||
|
||||
init {
|
||||
val authorPanel = HtmlEditorPane().apply {
|
||||
putClientProperty(UIUtil.HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY, true)
|
||||
setBody(createMessageAuthorChunk(message.author).bold().toString())
|
||||
}
|
||||
val timePanel = SpaceAutoUpdatableComponentService.createAutoUpdatableComponent {
|
||||
HtmlEditorPane().apply {
|
||||
putClientProperty(UIUtil.HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY, true)
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
setBody(HtmlChunk.text(message.created.formatPrettyDateTime()).toString()) // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
isOpaque = false
|
||||
add(authorPanel)
|
||||
add(timePanel)
|
||||
val showResolvedStateFeature = message.additionalFeatures.filterIsInstance<SpaceChatItemAdditionalFeature.ShowResolvedState>().singleOrNull()
|
||||
if (showResolvedStateFeature != null) {
|
||||
add(createResolvedComponent(lifetime, showResolvedStateFeature.resolved))
|
||||
}
|
||||
add(actionsPanel)
|
||||
launch(lifetime, Ui) {
|
||||
delay(2000)
|
||||
if (!message.delivered) {
|
||||
add(JBLabel(AnimatedIcon.Default.INSTANCE))
|
||||
revalidate()
|
||||
repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMessageAuthorChunk(author: CPrincipal): HtmlChunk =
|
||||
when (val details = author.details) {
|
||||
is CUserPrincipalDetails -> {
|
||||
val user = details.user.resolve()
|
||||
user.link()
|
||||
}
|
||||
is CApplicationPrincipalDetails -> {
|
||||
val app = details.app.resolve()
|
||||
HtmlChunk.link(SpaceUrls.app(app), app.name) // NON-NLS
|
||||
}
|
||||
else -> {
|
||||
HtmlChunk.text(author.name) // NON-NLS
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDeleteButton(message: SpaceChatItem): JComponent? {
|
||||
if (!message.canDelete) {
|
||||
return null
|
||||
}
|
||||
return InlineIconButton(
|
||||
VcsCodeReviewIcons.Delete,
|
||||
VcsCodeReviewIcons.DeleteHovered,
|
||||
tooltip = SpaceBundle.message("chat.message.action.delete.tooltip")
|
||||
).apply {
|
||||
actionListener = ActionListener {
|
||||
if (
|
||||
MessageDialogBuilder.yesNo(
|
||||
SpaceBundle.message("chat.message.delete.dialog.title"),
|
||||
SpaceBundle.message("chat.message.delete.dialog.message")
|
||||
).ask(this)
|
||||
) {
|
||||
SpaceStatsCounterCollector.DELETE_MESSAGE.log(project)
|
||||
launch(lifetime, Ui) {
|
||||
message.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createEditButton(message: SpaceChatItem): JComponent? {
|
||||
if (!message.canEdit) {
|
||||
return null
|
||||
}
|
||||
return InlineIconButton(
|
||||
AllIcons.General.Inline_edit,
|
||||
AllIcons.General.Inline_edit_hovered,
|
||||
tooltip = SpaceBundle.message("chat.message.action.edit.tooltip")
|
||||
).apply {
|
||||
actionListener = ActionListener {
|
||||
message.startEditing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createStartThreadButton(message: SpaceChatItem): JComponent {
|
||||
val startThreadVm = message.startThreadVm
|
||||
val button = InlineIconButton(
|
||||
VcsCodeReviewIcons.Comment,
|
||||
VcsCodeReviewIcons.CommentHovered,
|
||||
tooltip = SpaceBundle.message("chat.message.action.start.thread.tooltip")
|
||||
).apply {
|
||||
actionListener = ActionListener {
|
||||
startThreadVm.startWritingFirstMessage()
|
||||
}
|
||||
}
|
||||
startThreadVm.canStartThread.forEach(lifetime) {
|
||||
button.isVisible = it
|
||||
}
|
||||
return button
|
||||
}
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.message
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.panels.HorizontalLayout
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.SwingConstants
|
||||
|
||||
internal class SpaceChatMessagePendingHeader(private val item: SpaceChatItem) : JPanel(HorizontalLayout(5)) {
|
||||
init {
|
||||
isOpaque = false
|
||||
isVisible = item.pending == true
|
||||
|
||||
val infoLabel = JBLabel(SpaceBundle.message("chat.message.pending.text"), AllIcons.General.Note, SwingConstants.LEFT).apply {
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
}
|
||||
val postNowLabel = ActionLink(SpaceBundle.message("chat.message.pending.post.action")) {
|
||||
item.chat.publishMessage(item.id)
|
||||
}
|
||||
add(infoLabel)
|
||||
add(postNowLabel)
|
||||
}
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.message
|
||||
|
||||
import circlet.client.api.AttachmentInfo
|
||||
import circlet.client.api.UnfurlAttachment
|
||||
import com.intellij.ide.plugins.newui.VerticalLayout
|
||||
import com.intellij.openapi.ui.OnePixelDivider
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.space.chat.model.api.SpaceMCMessageContent
|
||||
import com.intellij.space.chat.model.api.SpaceMCMessageElement
|
||||
import com.intellij.space.chat.model.api.SpaceMCMessageSection
|
||||
import com.intellij.space.chat.ui.SpaceChatMarkdownTextComponent
|
||||
import com.intellij.space.chat.ui.getGrayTextHtml
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.SideBorder
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal class SpaceMCMessageComponent(
|
||||
@NonNls private val server: String,
|
||||
message: SpaceMCMessageContent,
|
||||
attachments: List<AttachmentInfo>
|
||||
) : BorderLayoutPanel() {
|
||||
companion object {
|
||||
private const val SMALL_FONT_SCALING = 0.9
|
||||
private const val HEADER_FONT_SCALING = 1.2
|
||||
}
|
||||
|
||||
private val unfurls = attachments.mapNotNull { it.details }.filterIsInstance<UnfurlAttachment>()
|
||||
|
||||
init {
|
||||
isOpaque = false
|
||||
|
||||
message.outline?.let { outline ->
|
||||
addToTop(SpaceChatMarkdownTextComponent(server, outline.text, initialUnfurls = unfurls).withSmallFont())
|
||||
}
|
||||
|
||||
val sectionsComponent = message.sections.map { it.createComponent() }.wrapToSingleComponent()
|
||||
addToCenter(SpaceStyledMessageComponent(sectionsComponent, style = message.style))
|
||||
}
|
||||
|
||||
private fun SpaceMCMessageSection.createComponent(): JComponent =
|
||||
when (this) {
|
||||
is SpaceMCMessageSection.Divider -> createDividerPanel()
|
||||
is SpaceMCMessageSection.Section -> createComponent()
|
||||
}
|
||||
|
||||
private fun SpaceMCMessageSection.Section.createComponent(): JComponent = JPanel(VerticalLayout(JBUI.scale(10))).apply {
|
||||
isOpaque = false
|
||||
|
||||
header?.let { add(createHeaderComponent(it), VerticalLayout.FILL_HORIZONTAL) }
|
||||
|
||||
val elementsComponent = elements.map { it.createComponent() }.wrapToSingleComponent()
|
||||
add(elementsComponent, VerticalLayout.FILL_HORIZONTAL)
|
||||
|
||||
footer?.let { add(createFooterComponent(it), VerticalLayout.FILL_HORIZONTAL) }
|
||||
}
|
||||
|
||||
private fun SpaceMCMessageElement.createComponent(): JComponent =
|
||||
when (this) {
|
||||
is SpaceMCMessageElement.Text -> createComponent()
|
||||
is SpaceMCMessageElement.MessageDivider -> createDividerPanel()
|
||||
}
|
||||
|
||||
private fun SpaceMCMessageElement.Text.createComponent(): JComponent {
|
||||
val textComponent = SpaceChatMarkdownTextComponent(server, content, initialUnfurls = unfurls)
|
||||
|
||||
return BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
createIconComponent()?.let { iconComponent ->
|
||||
addToLeft(
|
||||
BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyRight(3)
|
||||
addToTop(iconComponent)
|
||||
}
|
||||
)
|
||||
}
|
||||
addToCenter(textComponent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SpaceMCMessageElement.Text.createIconComponent(): JComponent? = icon?.let { JBLabel(icon) }
|
||||
|
||||
private fun createHeaderComponent(@NlsSafe text: String) = SpaceChatMarkdownTextComponent(server, text, initialUnfurls = unfurls).apply {
|
||||
scaleFont(HEADER_FONT_SCALING)
|
||||
}
|
||||
|
||||
private fun createFooterComponent(@NlsSafe text: String) =
|
||||
SpaceChatMarkdownTextComponent(server, getGrayTextHtml(text), initialUnfurls = unfurls).withSmallFont()
|
||||
|
||||
private fun JComponent.withSmallFont(): JComponent = scaleFont(SMALL_FONT_SCALING)
|
||||
|
||||
private fun JComponent.scaleFont(scaling: Double): JComponent = apply {
|
||||
font = font.deriveFont((font.size * scaling).toFloat())
|
||||
}
|
||||
|
||||
private fun createDividerPanel() = JPanel().apply {
|
||||
isOpaque = false
|
||||
border = IdeBorderFactory.createBorder(OnePixelDivider.BACKGROUND, SideBorder.TOP)
|
||||
}
|
||||
|
||||
private fun List<JComponent>.wrapToSingleComponent(): JComponent {
|
||||
val wrapper = JPanel(VerticalLayout(JBUI.scale(10)))
|
||||
forEach { component ->
|
||||
wrapper.add(component, VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
return wrapper.apply {
|
||||
isOpaque = false
|
||||
}
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.message
|
||||
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import runtime.reactive.Property
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal fun createResolvedComponent(lifetime: Lifetime, resolved: Property<Boolean>): JComponent {
|
||||
val resolvedLabel = JBLabel(SpaceBundle.message("chat.message.resolved.text"), UIUtil.ComponentStyle.SMALL).apply {
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
background = UIUtil.getPanelBackground()
|
||||
isOpaque = true
|
||||
}
|
||||
resolved.forEach(lifetime) {
|
||||
resolvedLabel.isVisible = it
|
||||
}
|
||||
return resolvedLabel
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.message
|
||||
|
||||
import circlet.client.api.mc.MessageStyle
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import java.awt.*
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal class SpaceStyledMessageComponent(content: JComponent, style: MessageStyle = MessageStyle.PRIMARY) : BorderLayoutPanel() {
|
||||
// TODO: create colors for Darcula
|
||||
private val lineColor = when (style) {
|
||||
MessageStyle.PRIMARY -> Color(0xB3D8F8)
|
||||
MessageStyle.SECONDARY -> Color(0xB5B9BD)
|
||||
MessageStyle.SUCCESS -> Color(0xC7E3D4)
|
||||
MessageStyle.ERROR -> Color(0xECC5C6)
|
||||
MessageStyle.WARNING -> Color(0xF1E4C9)
|
||||
}
|
||||
|
||||
private val lineWidth
|
||||
get() = JBUI.scale(6)
|
||||
private val lineCenterX
|
||||
get() = lineWidth / 2
|
||||
private val yRoundOffset
|
||||
get() = lineWidth / 2
|
||||
|
||||
init {
|
||||
addToCenter(content)
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.empty(yRoundOffset, 15, yRoundOffset, 0)
|
||||
}
|
||||
|
||||
override fun paint(g: Graphics?) {
|
||||
super.paint(g)
|
||||
|
||||
with(g as Graphics2D) {
|
||||
setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
|
||||
color = lineColor
|
||||
stroke = BasicStroke(lineWidth.toFloat() / 2 + 1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)
|
||||
|
||||
drawLine(lineCenterX, yRoundOffset, lineCenterX, height - yRoundOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.thread
|
||||
|
||||
import circlet.m2.threads.M2ThreadPreviewVm
|
||||
import circlet.platform.api.toKDateTime
|
||||
import circlet.platform.client.resolve
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.text.HtmlChunk
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.chat.ui.SpaceChatAvatarType
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.SpaceAutoUpdatableComponentService
|
||||
import com.intellij.space.ui.SpaceAvatarProvider
|
||||
import com.intellij.space.utils.formatPrettyDateTime
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.labels.LinkLabel
|
||||
import com.intellij.ui.components.panels.HorizontalLayout
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import runtime.Ui
|
||||
import runtime.reactive.Property
|
||||
import runtime.reactive.SequentialLifetimes
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal fun createCollapsedThreadComponent(
|
||||
project: Project,
|
||||
lifetime: Lifetime,
|
||||
message: SpaceChatItem,
|
||||
threadActionsFactory: SpaceChatThreadActionsFactory?,
|
||||
withFirst: Boolean = true
|
||||
): JComponent {
|
||||
val contentPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyTop(8)
|
||||
}
|
||||
val threadPreview = message.threadPreview
|
||||
val loadingThreadsSqLifetime = SequentialLifetimes(lifetime)
|
||||
message.loadingThread.forEach(lifetime) { loadingThread ->
|
||||
val loadingThreadLifetime = loadingThreadsSqLifetime.next()
|
||||
contentPanel.removeAll()
|
||||
if (loadingThread != null) {
|
||||
if (!loadingThread.isCompleted) {
|
||||
val loadingThreadLabel = JBLabel(SpaceBundle.message("chat.message.loading.thread.label")).apply {
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
}
|
||||
contentPanel.addToCenter(loadingThreadLabel)
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
launch(lifetime, Ui) {
|
||||
val threadValue = loadingThread.await()
|
||||
val threadComponent = createThreadComponent(
|
||||
project, lifetime, threadValue,
|
||||
pendingStateProvider = { false },
|
||||
threadActionsFactory = threadActionsFactory,
|
||||
folded = false,
|
||||
withFirst = withFirst
|
||||
)
|
||||
threadValue.mvms.forEach(loadingThreadLifetime) { messages ->
|
||||
contentPanel.isVisible = messages.messages.size > if (withFirst) 0 else 1
|
||||
}
|
||||
contentPanel.removeAll()
|
||||
contentPanel.addToCenter(threadComponent)
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (threadPreview != null) {
|
||||
val actionsComponent = createActionsComponent(project, lifetime, message, threadPreview, threadActionsFactory)
|
||||
contentPanel.addToCenter(actionsComponent)
|
||||
threadPreview.messageCount.forEach(loadingThreadLifetime) { count ->
|
||||
contentPanel.isVisible = count > 0
|
||||
}
|
||||
}
|
||||
else {
|
||||
contentPanel.isVisible = false
|
||||
}
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
}
|
||||
return contentPanel
|
||||
}
|
||||
|
||||
private fun createActionsComponent(
|
||||
project: Project,
|
||||
lifetime: Lifetime,
|
||||
message: SpaceChatItem,
|
||||
threadPreview: M2ThreadPreviewVm,
|
||||
threadActionsFactory: SpaceChatThreadActionsFactory?
|
||||
): JComponent {
|
||||
val authorsPanel = createThreadAuthorAvatarsComponent(lifetime, threadPreview)
|
||||
val dateComponent = createDateComponent(lifetime, threadPreview.lastReplyTime)
|
||||
val repliesComponent = createRepliesLink(project, lifetime, threadPreview.messageCount) {
|
||||
launch(lifetime, Ui) {
|
||||
message.loadThread(lifetime)
|
||||
}
|
||||
}
|
||||
return JPanel(HorizontalLayout(JBUI.scale(5))).apply {
|
||||
isOpaque = false
|
||||
add(authorsPanel)
|
||||
add(repliesComponent)
|
||||
add(dateComponent)
|
||||
threadActionsFactory?.createActionsComponent()?.let { add(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun createThreadAuthorAvatarsComponent(lifetime: Lifetime, threadPreview: M2ThreadPreviewVm): JComponent {
|
||||
val contentPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
}
|
||||
val avatarsProvider = SpaceAvatarProvider(lifetime, contentPanel, SpaceChatAvatarType.THREAD.size)
|
||||
threadPreview.authors.forEach(lifetime) { authors ->
|
||||
contentPanel.removeAll()
|
||||
if (authors.isNullOrEmpty()) {
|
||||
contentPanel.border = JBUI.Borders.emptyLeft(10)
|
||||
}
|
||||
else {
|
||||
contentPanel.border = JBUI.Borders.empty()
|
||||
val icons = authors.take(3).map { avatarsProvider.getIcon(it.resolve()) }
|
||||
val iconsPanel = JPanel(HorizontalLayout(1)).apply {
|
||||
isOpaque = false
|
||||
for (icon in icons) {
|
||||
add(JBLabel(icon))
|
||||
}
|
||||
}
|
||||
contentPanel.addToCenter(iconsPanel)
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
}
|
||||
return contentPanel
|
||||
}
|
||||
|
||||
private fun createDateComponent(lifetime: Lifetime, lastReplyTime: Property<Long?>): JComponent {
|
||||
val contentPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
}
|
||||
lastReplyTime.forEach(lifetime) { time ->
|
||||
contentPanel.removeAll()
|
||||
if (time == null) {
|
||||
contentPanel.isVisible = false
|
||||
}
|
||||
else {
|
||||
contentPanel.isVisible = true
|
||||
val dateComponent = SpaceAutoUpdatableComponentService.createAutoUpdatableComponent {
|
||||
JBLabel(HtmlChunk.text(time.toKDateTime().formatPrettyDateTime()).toString()).apply { // NON-NLS
|
||||
putClientProperty(UIUtil.HIDE_EDITOR_FROM_DATA_CONTEXT_PROPERTY, true)
|
||||
foreground = UIUtil.getContextHelpForeground()
|
||||
setCopyable(true)
|
||||
}
|
||||
}
|
||||
contentPanel.addToCenter(dateComponent)
|
||||
}
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
return contentPanel
|
||||
}
|
||||
|
||||
private fun createRepliesLink(project: Project, lifetime: Lifetime, messagesCount: Property<Int>, action: () -> Unit): JComponent {
|
||||
val contentPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
}
|
||||
messagesCount.forEach(lifetime) { count ->
|
||||
val repliesLink = LinkLabel.create(SpaceBundle.message("chat.message.replies.link", count)) {
|
||||
SpaceStatsCounterCollector.OPEN_THREAD.log(project)
|
||||
action()
|
||||
}
|
||||
contentPanel.removeAll()
|
||||
contentPanel.addToCenter(repliesLink)
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
return contentPanel
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.thread
|
||||
|
||||
import circlet.client.api.M2ChannelRecord
|
||||
import circlet.m2.ChannelsVm
|
||||
import circlet.m2.M2MessageVm
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import circlet.platform.api.Ref
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.chat.model.impl.SpaceChatItemImpl.Companion.convertToChatItem
|
||||
import com.intellij.space.chat.ui.SpaceChatContentPanelBase
|
||||
import com.intellij.space.chat.ui.getLink
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
|
||||
internal class SpaceChatStandaloneThreadComponent(
|
||||
private val project: Project,
|
||||
private val lifetime: Lifetime,
|
||||
parent: Disposable,
|
||||
channelsVm: ChannelsVm,
|
||||
chatRecord: Ref<M2ChannelRecord>,
|
||||
private val pendingStateProvider: () -> Boolean,
|
||||
private val threadActionsFactory: SpaceChatThreadActionsFactory,
|
||||
private val statsPlace: SpaceStatsCounterCollector.SendMessagePlace = SpaceStatsCounterCollector.SendMessagePlace.THREAD,
|
||||
private val messageConverter: (index: Int, message: M2MessageVm) -> SpaceChatItem = { _, message ->
|
||||
message.convertToChatItem(message.getLink())
|
||||
}
|
||||
) : SpaceChatContentPanelBase(lifetime, parent, channelsVm, chatRecord) {
|
||||
override fun onChatLoad(chatVm: M2ChannelVm) {
|
||||
stopLoadingContent(
|
||||
createThreadComponent(project, lifetime, chatVm, pendingStateProvider, threadActionsFactory, statsPlace = statsPlace,
|
||||
messageConverter = messageConverter)
|
||||
)
|
||||
}
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.thread
|
||||
|
||||
import javax.swing.JComponent
|
||||
|
||||
internal interface SpaceChatThreadActionsFactory {
|
||||
fun createActionsComponent(): JComponent
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.chat.ui.thread
|
||||
|
||||
import circlet.m2.M2MessageVm
|
||||
import circlet.m2.channel.M2ChannelVm
|
||||
import com.intellij.ide.plugins.newui.HorizontalLayout
|
||||
import com.intellij.ide.plugins.newui.VerticalLayout
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.chat.model.api.SpaceChatItem
|
||||
import com.intellij.space.chat.model.impl.SpaceChatItemImpl.Companion.convertToChatItem
|
||||
import com.intellij.space.chat.ui.*
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.SpaceAvatarProvider
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.codereview.SingleValueModelImpl
|
||||
import com.intellij.util.ui.codereview.ToggleableContainer
|
||||
import com.intellij.util.ui.codereview.timeline.TimelineComponent
|
||||
import com.intellij.util.ui.codereview.timeline.thread.TimelineThreadCommentsPanel
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
internal fun createThreadComponent(
|
||||
project: Project,
|
||||
lifetime: Lifetime,
|
||||
thread: M2ChannelVm,
|
||||
pendingStateProvider: () -> Boolean,
|
||||
threadActionsFactory: SpaceChatThreadActionsFactory? = null,
|
||||
withFirst: Boolean = true,
|
||||
folded: Boolean = true,
|
||||
statsPlace: SpaceStatsCounterCollector.SendMessagePlace = SpaceStatsCounterCollector.SendMessagePlace.THREAD,
|
||||
messageConverter: (index: Int, message: M2MessageVm) -> SpaceChatItem = { _, message -> message.convertToChatItem(message.getLink()) }
|
||||
): JComponent {
|
||||
val threadComponent = JPanel(VerticalLayout(0)).apply {
|
||||
isOpaque = false
|
||||
}
|
||||
val server = thread.client.server
|
||||
val avatarSize = SpaceChatAvatarType.THREAD.size
|
||||
val avatarProvider = SpaceAvatarProvider(lifetime, threadComponent, avatarSize)
|
||||
|
||||
val itemsListModel = SpaceChatItemListModel()
|
||||
|
||||
thread.mvms.forEach(lifetime) { messageList ->
|
||||
val messages = messageList.messages
|
||||
itemsListModel.messageListUpdated(
|
||||
messages
|
||||
.drop(if (withFirst) 0 else 1)
|
||||
.mapIndexed { index, message -> messageConverter(index, message) }
|
||||
)
|
||||
threadComponent.isVisible = messages.size > if (withFirst) 0 else 1
|
||||
}
|
||||
|
||||
val itemComponentFactory = SpaceChatItemComponentFactory(project, lifetime, server, avatarProvider)
|
||||
val threadTimeline = if (folded) {
|
||||
TimelineThreadCommentsPanel(
|
||||
itemsListModel,
|
||||
commentComponentFactory = itemComponentFactory::createComponent,
|
||||
offset = 0
|
||||
).apply {
|
||||
border = JBUI.Borders.emptyBottom(5)
|
||||
}
|
||||
}
|
||||
else {
|
||||
TimelineComponent(itemsListModel, itemComponentFactory, offset = 0).apply {
|
||||
border = JBUI.Borders.emptyBottom(5)
|
||||
}
|
||||
}
|
||||
|
||||
val actionsComponent = createActionsComponent(thread, threadActionsFactory, statsPlace, pendingStateProvider)
|
||||
return threadComponent.apply {
|
||||
add(threadTimeline, VerticalLayout.FILL_HORIZONTAL)
|
||||
add(actionsComponent, VerticalLayout.FILL_HORIZONTAL)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createActionsComponent(
|
||||
thread: M2ChannelVm,
|
||||
threadActionsFactory: SpaceChatThreadActionsFactory?,
|
||||
statsPlace: SpaceStatsCounterCollector.SendMessagePlace,
|
||||
pendingStateProvider: () -> Boolean
|
||||
): JComponent {
|
||||
val newMessageStateModel = SingleValueModelImpl(false)
|
||||
val label = ActionLink(SpaceBundle.message("chat.reply.action")) {
|
||||
newMessageStateModel.value = true
|
||||
}
|
||||
|
||||
val additionalActions = threadActionsFactory?.createActionsComponent()
|
||||
val actionsComponent = JPanel(HorizontalLayout(JBUI.scale(5))).apply {
|
||||
isOpaque = false
|
||||
border = JBUI.Borders.emptyLeft(SpaceChatAvatarType.THREAD.size.get() + SpaceChatItemComponentFactory.Item.AVATAR_GAP)
|
||||
add(label)
|
||||
additionalActions?.let { add(it) }
|
||||
}
|
||||
return ToggleableContainer.create(
|
||||
newMessageStateModel,
|
||||
mainComponentSupplier = { actionsComponent },
|
||||
toggleableComponentSupplier = {
|
||||
createNewMessageField(
|
||||
thread,
|
||||
statsPlace = statsPlace,
|
||||
onCancel = { newMessageStateModel.value = false },
|
||||
avatarType = SpaceChatAvatarType.THREAD,
|
||||
pendingStateProvider = pendingStateProvider
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.components
|
||||
|
||||
import circlet.client.api.englishFullName
|
||||
import circlet.platform.client.ConnectionStatus
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.space.ui.SpaceAvatarUtils
|
||||
import com.intellij.space.ui.SpaceAvatars
|
||||
import com.intellij.space.ui.SpaceImageLoader
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import libraries.coroutines.extra.LifetimeSource
|
||||
import libraries.klogging.logger
|
||||
import runtime.reactive.Property
|
||||
import runtime.reactive.awaitFirst
|
||||
import runtime.reactive.filter
|
||||
import runtime.reactive.property.mapInit
|
||||
|
||||
@Service
|
||||
class SpaceUserAvatarProvider : Disposable {
|
||||
companion object {
|
||||
private val LOG = logger<SpaceUserAvatarProvider>()
|
||||
|
||||
fun getInstance(): SpaceUserAvatarProvider = service()
|
||||
}
|
||||
|
||||
private val lifetime: LifetimeSource = LifetimeSource()
|
||||
|
||||
private val avatarPlaceholders: SpaceAvatars = SpaceAvatars.MainIcon
|
||||
|
||||
val avatars: Property<SpaceAvatars> = lifetime.mapInit(SpaceWorkspaceComponent.getInstance().workspace, avatarPlaceholders) { ws ->
|
||||
ws ?: return@mapInit avatarPlaceholders
|
||||
val id = ws.me.value.username
|
||||
val name = ws.me.value.englishFullName()
|
||||
|
||||
val avatarTID = ws.me.value.smallAvatar ?: return@mapInit SpaceAvatarUtils.generateAvatars(id, name)
|
||||
val imageLoader = SpaceImageLoader.getInstance()
|
||||
|
||||
// await connected state before trying to load image.
|
||||
ws.client.connectionStatus.filter { it is ConnectionStatus.Connected }.awaitFirst(ws.lifetime)
|
||||
|
||||
try {
|
||||
LOG.info { "loading user avatar: $avatarTID" }
|
||||
val loadedImage = imageLoader.loadImageAsync(avatarTID)?.await()
|
||||
if (loadedImage == null) {
|
||||
SpaceAvatarUtils.generateAvatars(id, name)
|
||||
}
|
||||
else {
|
||||
SpaceAvatarUtils.createAvatars(loadedImage)
|
||||
}
|
||||
}
|
||||
catch (th: CancellationException) {
|
||||
throw th
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LOG.error { "user avatar not loaded: $e" }
|
||||
avatarPlaceholders
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
lifetime.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
-262
@@ -1,262 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.components
|
||||
|
||||
import circlet.arenas.initCircletArenas
|
||||
import circlet.client.api.impl.ApiClassesDeserializer
|
||||
import circlet.client.api.impl.tombstones.registerArenaTombstones
|
||||
import circlet.code.api.CodeReviewArena
|
||||
import circlet.code.api.CodeReviewParticipantsArena
|
||||
import circlet.code.api.ReviewPendingMessageCounterArena
|
||||
import circlet.common.oauth.IdeaOAuthConfig
|
||||
import circlet.permissions.FeatureFlagsVmPersistenceKey
|
||||
import circlet.platform.api.oauth.OAuthTokenResponse
|
||||
import circlet.platform.api.oauth.toTokenInfo
|
||||
import circlet.platform.api.serialization.ExtendableSerializationRegistry
|
||||
import circlet.platform.client.ClientArenaRegistry
|
||||
import circlet.platform.workspaces.CodeFlowConfig
|
||||
import circlet.platform.workspaces.WorkspaceConfiguration
|
||||
import circlet.platform.workspaces.WorkspaceManagerHost
|
||||
import circlet.workspaces.Workspace
|
||||
import circlet.workspaces.WorkspaceManager
|
||||
import com.intellij.ide.browsers.BrowserLauncher
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.Service.Level
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.wm.IdeFrame
|
||||
import com.intellij.space.auth.SpaceAuthNotifier
|
||||
import com.intellij.space.auth.startRedirectHandling
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.runtime.ApplicationDispatcher
|
||||
import com.intellij.space.settings.SpaceLoginState
|
||||
import com.intellij.space.settings.SpaceServerSettings
|
||||
import com.intellij.space.settings.SpaceSettings
|
||||
import com.intellij.space.settings.log
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.utils.IdeaPasswordSafePersistence
|
||||
import com.intellij.space.utils.LifetimedDisposable
|
||||
import com.intellij.space.utils.LifetimedDisposableImpl
|
||||
import com.intellij.ui.AppIcon
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.withContext
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import libraries.coroutines.extra.usingSource
|
||||
import libraries.klogging.KLogger
|
||||
import libraries.klogging.assert
|
||||
import libraries.klogging.logger
|
||||
import runtime.Ui
|
||||
import runtime.mutableUiDispatch
|
||||
import runtime.persistence.InMemoryPersistence
|
||||
import runtime.persistence.PersistenceConfiguration
|
||||
import runtime.persistence.PersistenceKey
|
||||
import runtime.reactive.MutableProperty
|
||||
import runtime.reactive.Property
|
||||
import runtime.reactive.SequentialLifetimes
|
||||
import runtime.reactive.mutableProperty
|
||||
import runtime.reactive.property.map
|
||||
import java.awt.Component
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.util.concurrent.CancellationException
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
/**
|
||||
* The main plugin's component that allows to log in to the Space server or disconnect from it.
|
||||
* If possible, the component is automatically authorized when the app starts
|
||||
*/
|
||||
@Service(Level.APP)
|
||||
internal class SpaceWorkspaceComponent : WorkspaceManagerHost(), LifetimedDisposable by LifetimedDisposableImpl() {
|
||||
companion object {
|
||||
internal fun getInstance(): SpaceWorkspaceComponent = service()
|
||||
private val LOG: KLogger = logger<SpaceWorkspaceComponent>()
|
||||
}
|
||||
|
||||
private val workspacesLifetimes = SequentialLifetimes(lifetime)
|
||||
|
||||
private val manager = mutableProperty<WorkspaceManager?>(null)
|
||||
|
||||
val workspace: Property<Workspace?> = map(manager) { wm ->
|
||||
wm?.workspace?.value
|
||||
}
|
||||
|
||||
val loginState: MutableProperty<SpaceLoginState> = mutableProperty(getInitialState())
|
||||
|
||||
init {
|
||||
initApp()
|
||||
val wsLifetime = workspacesLifetimes.next()
|
||||
|
||||
// sign in automatically on application startup.
|
||||
launch(wsLifetime, Ui) {
|
||||
val signInResult = autoSignIn(wsLifetime)
|
||||
if (signInResult == AutoSignInResult.NOT_AUTHORIZED) {
|
||||
authFailed()
|
||||
}
|
||||
}
|
||||
|
||||
workspace.forEach(lifetime) { ws ->
|
||||
loginState.value = if (ws == null) {
|
||||
SpaceLoginState.Disconnected(SpaceSettings.getInstance().serverSettings.server)
|
||||
}
|
||||
else {
|
||||
SpaceLoginState.Connected(ws.client.server, ws)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initApp() {
|
||||
val application = ApplicationManager.getApplication()
|
||||
|
||||
mutableUiDispatch = ApplicationDispatcher(this, application)
|
||||
|
||||
initCircletArenas()
|
||||
registerArenaTombstones(ExtendableSerializationRegistry.global)
|
||||
|
||||
ApiClassesDeserializer(ExtendableSerializationRegistry.global).registerDeserializers()
|
||||
|
||||
// code review
|
||||
ClientArenaRegistry.register(CodeReviewArena)
|
||||
ClientArenaRegistry.register(CodeReviewParticipantsArena)
|
||||
ClientArenaRegistry.register(ReviewPendingMessageCounterArena)
|
||||
circlet.code.api.impl.ApiClassesDeserializer(ExtendableSerializationRegistry.global).registerDeserializers()
|
||||
}
|
||||
|
||||
|
||||
override suspend fun authFailed() {
|
||||
signOut(SpaceStatsCounterCollector.LogoutPlace.AUTH_FAIL)
|
||||
SpaceAuthNotifier.authFailed()
|
||||
}
|
||||
|
||||
private suspend fun signIn(lifetime: Lifetime, server: String): OAuthTokenResponse {
|
||||
LOG.assert(manager.value == null, "manager.value == null")
|
||||
|
||||
val lt = workspacesLifetimes.next()
|
||||
val newManager = createWorkspaceManager(lt, server)
|
||||
|
||||
val portsMapping: Map<Int, URL> = IdeaOAuthConfig.redirectURIs.map { rawUri -> URL(rawUri).let { url -> url.port to url } }.toMap()
|
||||
val ports = portsMapping.keys
|
||||
val (port, redirectUrl) = startRedirectHandling(lifetime, server, ports)
|
||||
?: return OAuthTokenResponse.Error(server, "", SpaceBundle.message("auth.error.ports.busy.label"))
|
||||
|
||||
val authUrl = portsMapping.getValue(port)
|
||||
val codeFlow = CodeFlowConfig(newManager.wsConfig, authUrl.toExternalForm())
|
||||
val uri = URI(codeFlow.codeFlowURL())
|
||||
try {
|
||||
BrowserLauncher.instance.browse(uri)
|
||||
}
|
||||
catch (th: Throwable) {
|
||||
return OAuthTokenResponse.Error(server, "", SpaceBundle.message("auth.error.cant.open.browser.label", server))
|
||||
}
|
||||
|
||||
val response = withContext(AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher()) {
|
||||
codeFlow.handleCodeFlowRedirect(redirectUrl.await())
|
||||
}
|
||||
if (response is OAuthTokenResponse.Success) {
|
||||
LOG.info { "A personal token was received" }
|
||||
newManager.signInWithToken(response.toTokenInfo())
|
||||
SpaceSettings.getInstance().serverSettings = SpaceServerSettings(true, server)
|
||||
manager.value = newManager
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
fun signInManually(server:String, uiLifetime: Lifetime, component: Component) {
|
||||
var serverName = server
|
||||
if (serverName.isBlank()) {
|
||||
val error = SpaceBundle.message("login.panel.error.organization.url.should.not.be.empty")
|
||||
loginState.value = SpaceLoginState.Disconnected("", error)
|
||||
return
|
||||
}
|
||||
serverName = normalizeUrl(serverName)
|
||||
launch(uiLifetime, Ui) {
|
||||
uiLifetime.usingSource { connectLt ->
|
||||
try {
|
||||
loginState.value = SpaceLoginState.Connecting(serverName) {
|
||||
connectLt.terminate()
|
||||
loginState.value = SpaceLoginState.Disconnected(serverName)
|
||||
}
|
||||
when (val response = signIn(connectLt, serverName)) {
|
||||
is OAuthTokenResponse.Error -> {
|
||||
val error = response.description // NON-NLS
|
||||
loginState.value = SpaceLoginState.Disconnected(serverName, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (th: CancellationException) {
|
||||
throw th
|
||||
}
|
||||
catch (th: Throwable) {
|
||||
log.error(th)
|
||||
loginState.value = SpaceLoginState.Disconnected(
|
||||
serverName,
|
||||
th.message ?: SpaceBundle.message("auth.error.unknown.label", th.javaClass.simpleName)
|
||||
)
|
||||
}
|
||||
val frame = SwingUtilities.getAncestorOfClass(JFrame::class.java, component)
|
||||
AppIcon.getInstance().requestFocus(frame as IdeFrame?)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun normalizeUrl(serverName: String): String {
|
||||
var result = serverName
|
||||
result = if (result.startsWith("https://") || result.startsWith("http://")) result else "https://$result"
|
||||
result = result.removeSuffix("/")
|
||||
return result
|
||||
}
|
||||
|
||||
fun signOut(statsPlace: SpaceStatsCounterCollector.LogoutPlace) {
|
||||
SpaceStatsCounterCollector.LOG_OUT.log(statsPlace)
|
||||
val oldManager = manager.value
|
||||
oldManager?.signOut(true)
|
||||
workspacesLifetimes.clear()
|
||||
manager.value = null
|
||||
val settings = SpaceSettings.getInstance()
|
||||
settings.serverSettings = settings.serverSettings.copy(enabled = false)
|
||||
}
|
||||
|
||||
private suspend fun autoSignIn(wsLifetime: Lifetime): AutoSignInResult {
|
||||
val serverSettings = SpaceSettings.getInstance().serverSettings
|
||||
val server = serverSettings.server
|
||||
if (!serverSettings.enabled || server.isBlank()) {
|
||||
return AutoSignInResult.NOT_AUTHORIZED_BEFORE
|
||||
}
|
||||
val newManager = createWorkspaceManager(wsLifetime, server)
|
||||
return try {
|
||||
if (newManager.signInNonInteractive()) {
|
||||
manager.value = newManager
|
||||
AutoSignInResult.AUTHORIZED
|
||||
}
|
||||
else {
|
||||
AutoSignInResult.NOT_AUTHORIZED
|
||||
}
|
||||
}
|
||||
catch (th: Throwable) {
|
||||
LOG.info(th, "Couldn't authorize interactively")
|
||||
AutoSignInResult.NOT_AUTHORIZED
|
||||
}
|
||||
}
|
||||
|
||||
private fun createWorkspaceManager(lifetime: Lifetime, server: String): WorkspaceManager {
|
||||
val persistenceConfig = PersistenceConfiguration(
|
||||
FeatureFlagsVmPersistenceKey,
|
||||
PersistenceKey.AllArenas
|
||||
)
|
||||
val workspaceConfig = WorkspaceConfiguration(server, IdeaOAuthConfig.clientId, IdeaOAuthConfig.clientSecret)
|
||||
return WorkspaceManager(lifetime, null, this, InMemoryPersistence(), IdeaPasswordSafePersistence, persistenceConfig, workspaceConfig)
|
||||
}
|
||||
|
||||
private fun getInitialState(): SpaceLoginState {
|
||||
val workspace = workspace.value ?: return SpaceLoginState.Disconnected("")
|
||||
return SpaceLoginState.Connected(workspace.client.server, workspace)
|
||||
}
|
||||
|
||||
private enum class AutoSignInResult {
|
||||
NOT_AUTHORIZED_BEFORE,
|
||||
NOT_AUTHORIZED,
|
||||
AUTHORIZED
|
||||
}
|
||||
}
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.editor
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.vcs.editor.ComplexPathVirtualFileSystem
|
||||
import com.intellij.vcs.editor.GsonComplexPathSerializer
|
||||
|
||||
internal class SpaceChatComplexPathVirtualFileSystem : ComplexPathVirtualFileSystem<SpaceChatComplexPathVirtualFileSystem.ComplexPath>(
|
||||
GsonComplexPathSerializer(ComplexPath::class.java)
|
||||
) {
|
||||
|
||||
fun getPath(sessionId: String, projectHash: String, id: String): String {
|
||||
return getPath(ComplexPath(sessionId, projectHash, id))
|
||||
}
|
||||
|
||||
data class ComplexPath(override val sessionId: String,
|
||||
override val projectHash: String,
|
||||
val id: String) : ComplexPathVirtualFileSystem.ComplexPath
|
||||
|
||||
override fun getProtocol(): String = PROTOCOL
|
||||
|
||||
override fun findOrCreateFile(project: Project, path: ComplexPath): VirtualFile? {
|
||||
return project.service<SpaceVirtualFilesManager>().findChatFile(path.id)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PROTOCOL = "space-chat"
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance() = service<VirtualFileManager>().getFileSystem(PROTOCOL) as SpaceChatComplexPathVirtualFileSystem
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.editor
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.space.vcs.review.details.diff.SpaceDiffFileId
|
||||
import com.intellij.vcs.editor.ComplexPathVirtualFileSystem
|
||||
import com.intellij.vcs.editor.GsonComplexPathSerializer
|
||||
|
||||
internal class SpaceDiffComplexPathVirtualFileSystem : ComplexPathVirtualFileSystem<SpaceDiffComplexPathVirtualFileSystem.ComplexPath>(
|
||||
GsonComplexPathSerializer(ComplexPath::class.java)
|
||||
) {
|
||||
|
||||
fun getPath(sessionId: String, projectHash: String, fileId: SpaceDiffFileId): String {
|
||||
return getPath(ComplexPath(sessionId, projectHash, fileId))
|
||||
}
|
||||
|
||||
data class ComplexPath(override val sessionId: String,
|
||||
override val projectHash: String,
|
||||
val fileId: SpaceDiffFileId) : ComplexPathVirtualFileSystem.ComplexPath
|
||||
|
||||
override fun getProtocol(): String = PROTOCOL
|
||||
|
||||
override fun findOrCreateFile(project: Project, path: ComplexPath): VirtualFile? {
|
||||
return project.service<SpaceVirtualFilesManager>().findDiffFile(path.fileId)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PROTOCOL = "space-diff"
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance() = service<VirtualFileManager>().getFileSystem(PROTOCOL) as SpaceDiffComplexPathVirtualFileSystem
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.editor
|
||||
|
||||
import circlet.client.api.M2ChannelRecord
|
||||
import circlet.m2.ChannelsVm
|
||||
import circlet.platform.api.Ref
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.space.chat.editor.SpaceChatFile
|
||||
import com.intellij.space.chat.model.api.SpaceChatHeaderDetails
|
||||
import com.intellij.space.vcs.review.details.SpaceReviewChangesVm
|
||||
import com.intellij.space.vcs.review.details.diff.SpaceDiffFile
|
||||
import com.intellij.space.vcs.review.details.diff.SpaceDiffFileData
|
||||
import com.intellij.space.vcs.review.details.diff.SpaceDiffFileId
|
||||
import com.intellij.space.vcs.review.details.diff.SpaceDiffVm
|
||||
import com.intellij.util.containers.ContainerUtil.createWeakValueMap
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
|
||||
@Service
|
||||
internal class SpaceVirtualFilesManager(private val project: Project) : Disposable {
|
||||
// current time should be enough to distinguish the manager between launches
|
||||
private val sessionId = System.currentTimeMillis().toString()
|
||||
|
||||
private val chatFiles = createWeakValueMap<String, SpaceChatFile>()
|
||||
private val diffFiles = createWeakValueMap<SpaceDiffFileId, SpaceDiffFile>()
|
||||
|
||||
fun findChatFile(id: String) = chatFiles[id]
|
||||
fun findDiffFile(fileId: SpaceDiffFileId) = diffFiles[fileId]
|
||||
|
||||
fun findOrCreateChatFile(
|
||||
@NlsSafe id: String,
|
||||
@NlsSafe path: String,
|
||||
@NlsContexts.TabTitle displayName: String,
|
||||
@NlsContexts.Tooltip tabTooltip: String,
|
||||
channelsVm: ChannelsVm,
|
||||
chatRecord: Ref<M2ChannelRecord>,
|
||||
headerDetailsBuilder: (Lifetime) -> SpaceChatHeaderDetails
|
||||
): SpaceChatFile = chatFiles.getOrPut(id) {
|
||||
SpaceChatFile(sessionId, project.locationHash, id, path, displayName, tabTooltip, channelsVm, chatRecord, headerDetailsBuilder)
|
||||
}
|
||||
|
||||
fun findOrCreateDiffFile(
|
||||
changesVm: runtime.reactive.Property<SpaceReviewChangesVm>,
|
||||
diffVm: SpaceDiffVm
|
||||
): SpaceDiffFile = SpaceDiffFileId(diffVm.projectKey.key, diffVm.reviewKey, diffVm.reviewId).let { fileId ->
|
||||
diffFiles.getOrPut(fileId) { SpaceDiffFile(sessionId, project.locationHash, fileId, diffVm.reviewKey) }
|
||||
.apply { updateDiffPresentation(changesVm, diffVm) }
|
||||
}
|
||||
|
||||
fun updateDiffPresentation(changesVm: runtime.reactive.Property<SpaceReviewChangesVm>,
|
||||
diffVm: SpaceDiffVm) {
|
||||
val fileId = SpaceDiffFileId(diffVm.projectKey.key, diffVm.reviewKey, diffVm.reviewId)
|
||||
diffFiles[fileId]?.updateDiffFileData(SpaceDiffFileData(changesVm, diffVm))
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
chatFiles.clear()
|
||||
diffFiles.clear()
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.klogging.impl
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import libraries.klogging.BaseLogger
|
||||
|
||||
class ApplicationLogger(private val logger: Logger) : BaseLogger {
|
||||
|
||||
override val isTraceEnabled: Boolean get() = logger.isTraceEnabled
|
||||
override val isDebugEnabled: Boolean get() = logger.isDebugEnabled
|
||||
override val isInfoEnabled: Boolean get() = true
|
||||
override val isWarnEnabled: Boolean get() = true
|
||||
override val isErrorEnabled: Boolean get() = true
|
||||
|
||||
override fun trace(message: Any?) {
|
||||
logger.trace(message.toString())
|
||||
}
|
||||
|
||||
override fun debug(message: Any?) {
|
||||
logger.debug(message.toString())
|
||||
}
|
||||
|
||||
override fun info(message: Any?) {
|
||||
logger.info(message.toString())
|
||||
}
|
||||
|
||||
override fun warn(message: Any?) {
|
||||
logger.warn(message.toString())
|
||||
}
|
||||
|
||||
override fun trace(t: Throwable, message: Any?) {
|
||||
logger.trace(message.toString())
|
||||
logger.trace(t)
|
||||
}
|
||||
|
||||
override fun debug(t: Throwable, message: Any?) {
|
||||
logger.debug(message.toString(), t)
|
||||
}
|
||||
|
||||
override fun info(t: Throwable, message: Any?) {
|
||||
logger.info(message.toString(), t)
|
||||
}
|
||||
|
||||
override fun warn(t: Throwable, message: Any?) {
|
||||
logger.warn(message.toString(), t)
|
||||
}
|
||||
|
||||
override fun error(message: Any?) {
|
||||
logger.error(message.toString())
|
||||
}
|
||||
|
||||
override fun error(t: Throwable, message: Any?) {
|
||||
logger.error(message.toString(), t)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.klogging.impl
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import libraries.klogging.KLogger
|
||||
import libraries.klogging.KLoggerFactory
|
||||
import libraries.klogging.LoggerNameSource
|
||||
import libraries.klogging.loggerNameFromSource
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object KLoggerFactoryIdea : KLoggerFactory {
|
||||
override fun logger(owner: KClass<*>): KLogger = wrapLogger(Logger.getInstance(owner.java))
|
||||
|
||||
override fun logger(owner: Any): KLogger = logger(owner.javaClass.kotlin)
|
||||
|
||||
override fun logger(name: String): KLogger = wrapLogger(Logger.getInstance(name))
|
||||
|
||||
override fun logger(nameSource: LoggerNameSource): KLogger = logger(loggerNameFromSource(nameSource))
|
||||
|
||||
private fun wrapLogger(logger: Logger) = KLogger(wrapWithApplicationLogger(logger))
|
||||
|
||||
fun wrapWithApplicationLogger(logger: Logger): ApplicationLogger = ApplicationLogger(logger)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.messages;
|
||||
|
||||
import com.intellij.DynamicBundle;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.PropertyKey;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class SpaceBundle extends DynamicBundle {
|
||||
@NonNls public static final String BUNDLE = "messages.SpaceBundle";
|
||||
private static final SpaceBundle INSTANCE = new SpaceBundle();
|
||||
|
||||
private SpaceBundle() {
|
||||
super(BUNDLE);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
|
||||
return INSTANCE.getMessage(key, params);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key,
|
||||
Object @NotNull ... params) {
|
||||
return INSTANCE.getLazyMessage(key, params);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.messages
|
||||
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.PropertyKey
|
||||
|
||||
internal object SpaceBundleExtensions {
|
||||
fun messagePointer(@PropertyKey(resourceBundle = SpaceBundle.BUNDLE) key: String, vararg params: Any): () -> @Nls String = {
|
||||
SpaceBundle.message(key, *params)
|
||||
}
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.notification
|
||||
|
||||
import com.intellij.notification.impl.NotificationIdsHolder
|
||||
|
||||
class SpaceNotificationIdsHolder : NotificationIdsHolder {
|
||||
override fun getNotificationIds(): List<String> {
|
||||
return listOf(
|
||||
GIT_REPO_INIT_ERROR,
|
||||
PROJECT_SHARED_SUCCESSFULLY,
|
||||
SHARING_NOT_FINISHED
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val GIT_REPO_INIT_ERROR = "space.git.repo.init.error"
|
||||
const val PROJECT_SHARED_SUCCESSFULLY = "space.project.shared.successfully"
|
||||
const val SHARING_NOT_FINISHED = "space.sharing.not.finished"
|
||||
}
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import com.intellij.build.DefaultBuildDescriptor
|
||||
import com.intellij.build.events.BuildEvent
|
||||
import com.intellij.build.events.MessageEvent
|
||||
import com.intellij.build.events.impl.FinishBuildEventImpl
|
||||
import com.intellij.build.events.impl.MessageEventImpl
|
||||
import com.intellij.build.events.impl.StartBuildEventImpl
|
||||
import com.intellij.build.events.impl.SuccessResultImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.plugins.pipelines.viewmodel.LogData
|
||||
import runtime.reactive.ObservableList
|
||||
|
||||
fun publishBuildLog(project: Project, data: LogData) {
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode && !ApplicationManager.getApplication().isHeadlessEnvironment) {
|
||||
val descriptor = DefaultBuildDescriptor(data.buildId, SpaceBundle.message("build.log.title"), project.basePath!!,
|
||||
System.currentTimeMillis())
|
||||
|
||||
val view = ServiceManager.getService(project, SyncDslViewManager::class.java)
|
||||
|
||||
view.onEvent(data.buildId, StartBuildEventImpl(descriptor, SpaceBundle.message("build.log.message.started", project.name)))
|
||||
|
||||
view.onEvent(data.buildId, MessageEventImpl(data.buildId, MessageEvent.Kind.INFO, null,
|
||||
SpaceBundle.message("build.log.message.process"), null))
|
||||
|
||||
if (!data.lifetime.isTerminated) {
|
||||
data.messages.change.forEach(data.lifetime) { change ->
|
||||
when (change) {
|
||||
is ObservableList.Change.Add<BuildEvent> -> {
|
||||
val message = change.newValue
|
||||
view.onEvent(data.buildId, message)
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.lifetime.addOrCallImmediately {
|
||||
view.onEvent(data.buildId,
|
||||
FinishBuildEventImpl(descriptor.id, null, System.currentTimeMillis(), SpaceBundle.message("build.log.message.finished"),
|
||||
SuccessResultImpl(false)))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import circlet.automation.bootstrap.AutomationDslEvaluationBootstrap
|
||||
import circlet.pipelines.config.idea.api.IdeaScriptConfig
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.getProjectCachePath
|
||||
import com.intellij.util.io.safeOutputStream
|
||||
import libraries.klogging.logger
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileSystemException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.NoSuchFileException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
|
||||
private val log = logger<ScriptKtsPersistentState>()
|
||||
|
||||
class ScriptKtsPersistentState(val project: Project) {
|
||||
|
||||
// not for load failure
|
||||
fun load(): IdeaScriptConfig? {
|
||||
|
||||
val path = getCacheFile(project)
|
||||
|
||||
val fileAttributes = try {
|
||||
Files.readAttributes(path, BasicFileAttributes::class.java)
|
||||
}
|
||||
catch (ignored: FileSystemException) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (fileAttributes == null || !fileAttributes.isRegularFile) {
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
Files.newBufferedReader(path, Charsets.UTF_8).use { reader ->
|
||||
val evalService = AutomationDslEvaluationBootstrap(log, getAutomationConfigurationWithFallback()).loadEvaluatorForIdea()
|
||||
if (evalService == null) {
|
||||
log.error("DSL evaluation service not found, cannot deserialize automation DSL model")
|
||||
return null
|
||||
}
|
||||
evalService.deserializeJsonConfig(reader.readLine())
|
||||
}
|
||||
}
|
||||
catch (e: NoSuchFileException) {
|
||||
null
|
||||
}
|
||||
catch (e: IOException) {
|
||||
log.error(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCacheFile(project: Project): Path {
|
||||
return project.getProjectCachePath("space_automation").resolve(".space.kts.dat")
|
||||
}
|
||||
|
||||
fun save(config: IdeaScriptConfig) {
|
||||
val path = getCacheFile(project)
|
||||
path.safeOutputStream().use {
|
||||
it.write(config.printJson().toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import circlet.pipelines.config.dsl.scriptdefinition.ProjectScriptCompilationConfiguration
|
||||
import circlet.pipelines.config.dsl.scriptdefinition.resolveScriptArtifact
|
||||
import circlet.pipelines.config.ivy.resolver.ivyResolver
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import kotlin.script.experimental.api.*
|
||||
import kotlin.script.experimental.jvm.updateClasspath
|
||||
|
||||
internal class SpaceKtsCompilationConfiguration : ScriptCompilationConfiguration(ProjectScriptCompilationConfiguration(), body = {
|
||||
refineConfiguration {
|
||||
beforeCompiling { context ->
|
||||
addAutomationRuntimeToClasspath(context)
|
||||
}
|
||||
}
|
||||
|
||||
ide {
|
||||
acceptedLocations(ScriptAcceptedLocation.Everywhere)
|
||||
}
|
||||
}) {
|
||||
companion object {
|
||||
private val LOG = logger<SpaceKtsCompilationConfiguration>()
|
||||
|
||||
private val AUTH_ERROR = ResultWithDiagnostics.Failure(
|
||||
ScriptDiagnostic(4, SpaceBundle.message("kts.file.login.error"))
|
||||
)
|
||||
|
||||
private val DEPENDENCIES_LOADING_ERROR = ResultWithDiagnostics.Failure(
|
||||
ScriptDiagnostic(4, SpaceBundle.message("kts.file.dependencies.error"))
|
||||
)
|
||||
|
||||
private fun addAutomationRuntimeToClasspath(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
|
||||
val config = getAutomationConfigurationOrNull()
|
||||
if (config == null) {
|
||||
LOG.debug("[space-kts] User not logged in to Space, cannot fetch Automation runtime")
|
||||
return AUTH_ERROR
|
||||
}
|
||||
|
||||
try {
|
||||
LOG.debug("[space-kts] Resolving space-automation-runtime from ${config.locationDescription}")
|
||||
|
||||
return config.ivyResolver
|
||||
.resolveScriptArtifact("com.jetbrains", "space-automation-runtime", "latest.integration")
|
||||
.onSuccess { jars ->
|
||||
LOG.debug("[space-kts] Successfully resolved space-automation-runtime: ${jars.joinToString()}")
|
||||
ScriptCompilationConfiguration(context.compilationConfiguration) { updateClasspath(jars) }.asSuccess()
|
||||
}
|
||||
.onFailure { diagnostics ->
|
||||
LOG.warn("[space-kts] Failure while downloading dependencies for .space.kts file: $diagnostics")
|
||||
DEPENDENCIES_LOADING_ERROR
|
||||
}
|
||||
}
|
||||
catch (th: Throwable) {
|
||||
LOG.warn("[space-kts] Couldn't download dependencies for .space.kts file", th)
|
||||
return DEPENDENCIES_LOADING_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import com.intellij.space.plugins.pipelines.utils.DslFileFinder
|
||||
import com.intellij.space.tools.spaceKtsToolwindow
|
||||
import com.intellij.space.utils.LifetimedDisposable
|
||||
import com.intellij.space.utils.LifetimedDisposableImpl
|
||||
import com.intellij.space.vcs.PostStartupActivity
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.*
|
||||
import libraries.klogging.logger
|
||||
import runtime.reactive.Property
|
||||
import runtime.reactive.mutableProperty
|
||||
|
||||
private val log = logger<SpaceKtsFileDetector>()
|
||||
|
||||
class SpaceKtsFileDetectorActivator : PostStartupActivity() {
|
||||
override fun runActivity(project: Project) {
|
||||
project.service<SpaceKtsFileDetector>()
|
||||
}
|
||||
}
|
||||
|
||||
// listens to file system and exposes script dsl file
|
||||
@Service
|
||||
class SpaceKtsFileDetector(val project: Project) : LifetimedDisposable by LifetimedDisposableImpl() {
|
||||
|
||||
val dslFile: Property<VirtualFile?> get() = _dslFile
|
||||
|
||||
private val _dslFile = mutableProperty<VirtualFile?>(null)
|
||||
|
||||
private val fileListener = object : VirtualFileListener {
|
||||
override fun propertyChanged(event: VirtualFilePropertyEvent) {
|
||||
if (event.propertyName == "name") {
|
||||
handleFileChanged(event.oldValue.toString())
|
||||
handleFileChanged(event.newValue.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun fileCreated(event: VirtualFileEvent) {
|
||||
handleFileChanged(event.fileName)
|
||||
}
|
||||
|
||||
override fun fileDeleted(event: VirtualFileEvent) {
|
||||
handleFileChanged(event.fileName)
|
||||
}
|
||||
|
||||
override fun fileMoved(event: VirtualFileMoveEvent) {
|
||||
handleFileChanged(event.fileName)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
log.info("SpaceKtsFileDetector")
|
||||
|
||||
refreshScript()
|
||||
|
||||
LocalFileSystem.getInstance().addVirtualFileListener(fileListener)
|
||||
|
||||
lifetime.add {
|
||||
LocalFileSystem.getInstance().removeVirtualFileListener(fileListener)
|
||||
}
|
||||
|
||||
_dslFile.forEach(lifetime) { file ->
|
||||
project.spaceKtsToolwindow?.setAvailable(file != null, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun refreshScript() {
|
||||
runReadAction {
|
||||
if (!project.isDisposed) {
|
||||
_dslFile.value = checkIsDslFileExists()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleFileChanged(name: String) {
|
||||
if (DslFileFinder.checkFileNameIsApplicable(name)) {
|
||||
refreshScript()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIsDslFileExists(): VirtualFile? {
|
||||
return DslFileFinder.find(project)
|
||||
}
|
||||
|
||||
}
|
||||
-247
@@ -1,247 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import circlet.automation.bootstrap.AutomationCompilerBootstrap
|
||||
import circlet.automation.bootstrap.AutomationDslEvaluationBootstrap
|
||||
import circlet.automation.bootstrap.embeddedMavenServer
|
||||
import circlet.automation.bootstrap.publicMavenServer
|
||||
import circlet.pipelines.config.idea.api.IdeaScriptConfig
|
||||
import circlet.pipelines.config.utils.AutomationCompilerConfiguration
|
||||
import circlet.platform.client.backgroundDispatcher
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.invokeAndWaitIfNeeded
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.plugins.pipelines.utils.ObservableQueue
|
||||
import com.intellij.space.plugins.pipelines.viewmodel.LogData
|
||||
import com.intellij.space.plugins.pipelines.viewmodel.ScriptModel
|
||||
import com.intellij.space.plugins.pipelines.viewmodel.ScriptState
|
||||
import com.intellij.space.utils.LifetimedDisposable
|
||||
import com.intellij.space.utils.LifetimedDisposableImpl
|
||||
import kotlinx.coroutines.withContext
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import libraries.coroutines.extra.using
|
||||
import libraries.klogging.*
|
||||
import org.slf4j.event.Level
|
||||
import org.slf4j.event.SubstituteLoggingEvent
|
||||
import org.slf4j.helpers.SubstituteLogger
|
||||
import runtime.Ui
|
||||
import runtime.reactive.*
|
||||
import runtime.reactive.property.flatMap
|
||||
import runtime.reactive.property.map
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.name
|
||||
|
||||
private val log = logger<SpaceKtsModelBuilder>()
|
||||
|
||||
@Service
|
||||
class SpaceKtsModelBuilder(val project: Project) : LifetimedDisposable by LifetimedDisposableImpl(), KLogging() {
|
||||
private val persistentState = ScriptKtsPersistentState(project)
|
||||
|
||||
private val _model = mutableProperty<ScriptModelHolder?>(null)
|
||||
private val _modelBuildIsRequested = mutableProperty(false)
|
||||
|
||||
// script is null when no build file is found in project
|
||||
@Suppress("USELESS_CAST")
|
||||
val script = map(_model) { it as ScriptModel? }
|
||||
|
||||
val config = flatMap(_model) { (it?.config ?: mutableProperty(null)) }
|
||||
|
||||
init {
|
||||
launch(lifetime, backgroundDispatcher) {
|
||||
val saved = log.catch { persistentState.load() }
|
||||
withContext(Ui) {
|
||||
project.service<SpaceKtsFileDetector>().dslFile.view(lifetime) { lt, file ->
|
||||
_model.value = file?.let { ScriptModelHolder(lt, file, _modelBuildIsRequested, saved) }
|
||||
}
|
||||
config.skip(1).view(lifetime) { lt, config ->
|
||||
if (config != null) {
|
||||
launch(lt, backgroundDispatcher) {
|
||||
persistentState.save(config)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun requestModel() {
|
||||
_modelBuildIsRequested.value = true
|
||||
}
|
||||
|
||||
fun rebuildModel() {
|
||||
_model.value?.rebuildModel()
|
||||
}
|
||||
|
||||
// this class is created per automation script file found and it is responsible for building model content
|
||||
private inner class ScriptModelHolder(val lifetime: Lifetime,
|
||||
val scriptFile: VirtualFile,
|
||||
modelBuildIsRequested: Property<Boolean>,
|
||||
loadedConfig: IdeaScriptConfig?) : ScriptModel {
|
||||
|
||||
val sync = Any()
|
||||
|
||||
private val _config = mutableProperty(loadedConfig)
|
||||
private val _error = mutableProperty<String?>(null)
|
||||
private val _state = mutableProperty(if (loadedConfig == null) ScriptState.NotInitialised else ScriptState.Ready)
|
||||
|
||||
override val config: Property<IdeaScriptConfig?> get() = _config
|
||||
override val error: Property<String?> get() = _error
|
||||
override val state: Property<ScriptState> get() = _state
|
||||
|
||||
private val logBuildData = mutableProperty<LogData?>(null)
|
||||
|
||||
init {
|
||||
|
||||
// push to IDE build log
|
||||
logBuildData.view(lifetime) { lt, log ->
|
||||
if (log != null) {
|
||||
publishBuildLog(project, log)
|
||||
}
|
||||
}
|
||||
|
||||
modelBuildIsRequested.whenTrue(lifetime) { lt ->
|
||||
if (_state.value == ScriptState.NotInitialised)
|
||||
rebuildModel()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun rebuildModel() {
|
||||
ProgressManager.getInstance().run(object : Task.Backgroundable(
|
||||
project,
|
||||
SpaceBundle.message("kts.progress.title.building.dsl.model"),
|
||||
false
|
||||
) {
|
||||
override fun run(pi: ProgressIndicator) {
|
||||
// todo: implement build cancellation and re-building
|
||||
synchronized(sync) {
|
||||
if (_state.value == ScriptState.Building)
|
||||
return
|
||||
_state.value = ScriptState.Building
|
||||
}
|
||||
val data = LogData()
|
||||
try {
|
||||
logBuildData.value = data
|
||||
build(data)
|
||||
}
|
||||
finally {
|
||||
data.close()
|
||||
_state.value = ScriptState.Ready
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun build(logData: LogData) {
|
||||
lifetime.using { lt ->
|
||||
invokeAndWaitIfNeeded(ModalityState.NON_MODAL) {
|
||||
// We need to save the automation script to disk before compiling, to take into account the most recent changes.
|
||||
// The script might have dependencies on other files in the project, so we also save other files.
|
||||
FileDocumentManager.getInstance().saveAllDocuments()
|
||||
}
|
||||
compileAndEvaluate(logData.asLogger(lt), logData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileAndEvaluate(eventLogger: KLogger, logData: LogData) {
|
||||
val outputDir = createTempDir("space-automation-temp")
|
||||
try {
|
||||
val outputDirPath = outputDir.toPath()
|
||||
val compiledJarPath = outputDirPath.resolve("compiledJar.jar")
|
||||
val scriptRuntimePath = outputDirPath.resolve("space-automation-runtime.jar")
|
||||
|
||||
val configuration = getAutomationConfigurationWithFallback()
|
||||
|
||||
val compiler = AutomationCompilerBootstrap(log = eventLogger, configuration = configuration)
|
||||
val compileResultCode = compiler.compile(script = Paths.get(scriptFile.path), jar = compiledJarPath)
|
||||
|
||||
if (compileResultCode != 0) {
|
||||
_config.value = null
|
||||
_error.value = SpaceBundle.message("kts.toolwindow.error.compilation.failed.code", compileResultCode)
|
||||
return
|
||||
}
|
||||
if (!Files.isRegularFile(compiledJarPath)) {
|
||||
_config.value = null
|
||||
_error.value = SpaceBundle.message("kts.toolwindow.error.missing.compiled.jar", compiledJarPath.name)
|
||||
return
|
||||
}
|
||||
// See AutomationCompiler.kt (in space project) where we copy the runtime jar into the output folder for all resolver types
|
||||
if (!Files.exists(scriptRuntimePath)) {
|
||||
_config.value = null
|
||||
_error.value = SpaceBundle.message("kts.toolwindow.error.missing.runtime.jar", scriptRuntimePath.name)
|
||||
return
|
||||
}
|
||||
|
||||
val evaluator = AutomationDslEvaluationBootstrap(log = eventLogger, configuration = configuration).loadEvaluatorForIdea()
|
||||
if (evaluator == null) {
|
||||
_config.value = null
|
||||
_error.value = SpaceBundle.message("kts.toolwindow.error.evaluation.service.not.found")
|
||||
return
|
||||
}
|
||||
|
||||
val evalResult = evaluator.evaluateAndValidate(compiledJarPath, scriptRuntimePath)
|
||||
if (evalResult.validationErrors.any()) {
|
||||
val validationErrorsPrefix = SpaceBundle.message("kts.toolwindow.validation.errors.prefix")
|
||||
val message = evalResult.validationErrors.joinToString("\n", prefix = "$validationErrorsPrefix\n")
|
||||
logData.error(message)
|
||||
_config.value = null
|
||||
_error.value = message
|
||||
return
|
||||
}
|
||||
|
||||
_config.value = evalResult.config
|
||||
_error.value = null
|
||||
}
|
||||
catch (th: Throwable) {
|
||||
val stackTraceText = th.stackTraceToString()
|
||||
val logMessage = "${th.message}.\n$stackTraceText"
|
||||
logData.error(logMessage)
|
||||
// do not touch last config, just update the error state.
|
||||
_error.value = stackTraceText
|
||||
}
|
||||
finally {
|
||||
outputDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getAutomationConfigurationOrNull(): AutomationCompilerConfiguration? =
|
||||
SpaceWorkspaceComponent.getInstance().workspace.value?.client?.server?.let {
|
||||
AutomationCompilerConfiguration.Remote(server = embeddedMavenServer(it))
|
||||
}
|
||||
|
||||
|
||||
// Primary option is to download from currently connected server, fallback to the public maven
|
||||
internal fun getAutomationConfigurationWithFallback(): AutomationCompilerConfiguration =
|
||||
getAutomationConfigurationOrNull() ?: AutomationCompilerConfiguration.Remote(server = publicMavenServer)
|
||||
|
||||
private fun LogData.asLogger(lifetime: Lifetime): KLogger {
|
||||
val queue = ObservableQueue.mutable<SubstituteLoggingEvent>()
|
||||
|
||||
queue.change.forEach(lifetime) {
|
||||
val ev = it.index
|
||||
when (ev.level) {
|
||||
Level.INFO -> message(ev.message)
|
||||
Level.DEBUG -> message(ev.message)
|
||||
Level.WARN -> message(ev.message)
|
||||
Level.ERROR -> error(ev.message)
|
||||
Level.TRACE -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// slf4j's SubstituteLogger mechanism allows us to put logs as events in a queue (even though not initially made for this)
|
||||
return KLogger(JVMLogger(SubstituteLogger("ScriptModelBuilderLogger", queue, false)))
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import java.io.File
|
||||
import kotlin.script.experimental.api.ScriptEvaluationConfiguration
|
||||
import kotlin.script.experimental.host.ScriptDefinition
|
||||
import kotlin.script.experimental.host.ScriptingHostConfiguration
|
||||
import kotlin.script.experimental.intellij.ScriptDefinitionsProvider
|
||||
|
||||
private class SpaceScriptDefinitionsProvider : ScriptDefinitionsProvider {
|
||||
override val id = "SpaceScriptDefinitionsProvider"
|
||||
|
||||
override fun getDefinitionClasses(): Iterable<String> = listOf()
|
||||
override fun getDefinitionsClassPath(): Iterable<File> = listOf()
|
||||
|
||||
override fun provideDefinitions(
|
||||
baseHostConfiguration: ScriptingHostConfiguration,
|
||||
loadedScriptDefinitions: List<ScriptDefinition>
|
||||
): Iterable<ScriptDefinition> = listOf(
|
||||
ScriptDefinition(
|
||||
SpaceKtsCompilationConfiguration(),
|
||||
ScriptEvaluationConfiguration.Default
|
||||
)
|
||||
)
|
||||
|
||||
override fun useDiscovery() = false
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import com.intellij.execution.lineMarker.RunLineMarkerContributor
|
||||
import com.intellij.psi.PsiElement
|
||||
|
||||
class SpaceScriptRunLineMarkerProvider : RunLineMarkerContributor() {
|
||||
|
||||
override fun getInfo(element: PsiElement): Info? {
|
||||
val refs = element.references
|
||||
refs.forEach {
|
||||
// TODO: optimize it!!!
|
||||
/*
|
||||
if (it is KtSimpleNameReference) {
|
||||
val resolveResult = it.resolve()
|
||||
if (resolveResult != null) {
|
||||
val fqnName = resolveResult.getKotlinFqName()
|
||||
if (fqnName != null) {
|
||||
if (fqnName.asString() == "circlet.pipelines.config.dsl.api.Project.job") {
|
||||
val valueArgumentList = element.nextSibling as KtValueArgumentList
|
||||
val taskName = valueArgumentList.arguments.firstOrNull()?.children?.firstOrNull()?.reference?.canonicalText
|
||||
if (taskName != null) {
|
||||
val runAction = object : AnAction(ExecutionBundle.message("run.configurable.display.name"), null, AllIcons.RunConfigurations.TestState.Run) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
CircletRunConfigurationUtils.run(taskName, element.project)
|
||||
}
|
||||
}
|
||||
return Info(runAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services
|
||||
|
||||
import com.intellij.build.AbstractViewManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
|
||||
@Service
|
||||
class SyncDslViewManager(project: Project) : AbstractViewManager(project) {
|
||||
public override fun getViewName() = SpaceBundle.message("tab.title.space.automation.dsl")
|
||||
}
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services.execution
|
||||
|
||||
import com.intellij.execution.process.ProcessHandler
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.project.Project
|
||||
import libraries.io.random.Random
|
||||
import libraries.klogging.KLogging
|
||||
import java.io.OutputStream
|
||||
|
||||
@Service
|
||||
class SpaceTaskRunner(val project: Project) {
|
||||
companion object : KLogging()
|
||||
|
||||
private val ideaLocalRunnerLabel = "SpaceTaskRunner_${Random.nextString(10)}"
|
||||
|
||||
fun run(taskName: String): ProcessHandler {
|
||||
// todo: implement using local run from the SDK.
|
||||
return TaskProcessHandler(taskName)
|
||||
}
|
||||
|
||||
private val newLine: String = System.getProperty("line.separator", "\n")
|
||||
}
|
||||
|
||||
class TaskProcessHandler(private val taskName: String) : ProcessHandler() {
|
||||
|
||||
companion object : KLogging()
|
||||
|
||||
override fun getProcessInput(): OutputStream? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun detachIsDefault(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun detachProcessImpl() {
|
||||
logger.info("detachProcessImpl for task $taskName")
|
||||
}
|
||||
|
||||
override fun destroyProcessImpl() {
|
||||
logger.info("destroyProcessImpl for task $taskName")
|
||||
notifyProcessTerminated(0)
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
notifyProcessTerminated(0)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services.run
|
||||
|
||||
import com.intellij.execution.Executor
|
||||
import com.intellij.execution.configurations.ConfigurationFactory
|
||||
import com.intellij.execution.configurations.LocatableConfigurationBase
|
||||
import com.intellij.execution.configurations.RunConfiguration
|
||||
import com.intellij.execution.configurations.RunProfileState
|
||||
import com.intellij.execution.runners.ExecutionEnvironment
|
||||
import com.intellij.openapi.options.SettingsEditor
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
class SpaceRunConfiguration(project: Project, factory: ConfigurationFactory)
|
||||
: LocatableConfigurationBase<SpaceRunTaskConfigurationOptions>(project, factory, "Run") {
|
||||
|
||||
override fun getConfigurationEditor(): SettingsEditor<out RunConfiguration> {
|
||||
return SpaceRunTaskConfigurationEditor()
|
||||
}
|
||||
|
||||
override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? {
|
||||
return SpaceRunTaskState(options, environment)
|
||||
}
|
||||
|
||||
public override fun getOptions(): SpaceRunTaskConfigurationOptions {
|
||||
return super.getOptions() as SpaceRunTaskConfigurationOptions
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services.run
|
||||
|
||||
import com.intellij.execution.configurations.RunConfiguration
|
||||
import com.intellij.execution.configurations.SimpleConfigurationType
|
||||
import com.intellij.openapi.components.BaseState
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NotNullLazyValue
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import icons.SpaceIcons
|
||||
import platform.common.ProductName
|
||||
|
||||
class SpaceRunConfigurationType : SimpleConfigurationType(
|
||||
"SpaceRunConfiguration",
|
||||
SpaceBundle.message("space.task.run.configuration.name"),
|
||||
SpaceBundle.message("run.configuration.description", ProductName),
|
||||
NotNullLazyValue.createValue { SpaceIcons.Main }
|
||||
) {
|
||||
|
||||
override fun createTemplateConfiguration(project: Project): RunConfiguration {
|
||||
return SpaceRunConfiguration(project, this)
|
||||
}
|
||||
|
||||
override fun getOptionsClass(): Class<out BaseState>? {
|
||||
return SpaceRunTaskConfigurationOptions::class.java
|
||||
}
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services.run
|
||||
|
||||
import com.intellij.execution.ProgramRunnerUtil
|
||||
import com.intellij.execution.RunManager
|
||||
import com.intellij.execution.executors.DefaultRunExecutor
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
object SpaceRunConfigurationUtils {
|
||||
fun run(taskName: String, project: Project) {
|
||||
val runManager = RunManager.getInstance(project)
|
||||
val settings = runManager.createConfiguration("Run task $taskName", SpaceRunConfigurationType::class.java)
|
||||
val configuration = settings.configuration as SpaceRunConfiguration
|
||||
configuration.options.taskName = taskName
|
||||
runManager.addConfiguration(settings)
|
||||
runManager.selectedConfiguration = settings
|
||||
ProgramRunnerUtil.executeConfiguration(settings, DefaultRunExecutor.getRunExecutorInstance())
|
||||
}
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services.run
|
||||
|
||||
import com.intellij.openapi.options.SettingsEditor
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
|
||||
class SpaceRunTaskConfigurationEditor : SettingsEditor<SpaceRunConfiguration>() {
|
||||
private val taskNameField = JBTextField()
|
||||
override fun resetEditorFrom(s: SpaceRunConfiguration) {
|
||||
val options = s.options
|
||||
taskNameField.text = options.taskName
|
||||
}
|
||||
|
||||
override fun createEditor(): JComponent {
|
||||
val panel = JPanel()
|
||||
panel.layout = BoxLayout(panel, BoxLayout.LINE_AXIS)
|
||||
panel.add(JLabel(SpaceBundle.message("run.configuration.editor.task.name.label")))
|
||||
panel.add(taskNameField)
|
||||
return panel
|
||||
}
|
||||
|
||||
override fun applyEditorTo(s: SpaceRunConfiguration) {
|
||||
val options = s.options
|
||||
options.taskName = taskNameField.text
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services.run
|
||||
|
||||
import com.intellij.execution.configurations.LocatableRunConfigurationOptions
|
||||
import com.intellij.util.xmlb.annotations.OptionTag
|
||||
|
||||
class SpaceRunTaskConfigurationOptions : LocatableRunConfigurationOptions() {
|
||||
@get:OptionTag("TASK_NAME")
|
||||
var taskName by string("")
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.services.run
|
||||
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.configurations.CommandLineState
|
||||
import com.intellij.execution.process.ProcessHandler
|
||||
import com.intellij.execution.runners.ExecutionEnvironment
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.space.plugins.pipelines.services.execution.SpaceTaskRunner
|
||||
|
||||
class SpaceRunTaskState(private val settings: SpaceRunTaskConfigurationOptions, environment: ExecutionEnvironment) : CommandLineState(
|
||||
environment) {
|
||||
|
||||
override fun startProcess(): ProcessHandler {
|
||||
val project = environment.project
|
||||
val runner = project.service<SpaceTaskRunner>()
|
||||
val taskName = settings.taskName
|
||||
if (taskName == null) {
|
||||
throw ExecutionException("TaskName is null") // NON-NLS
|
||||
}
|
||||
else {
|
||||
return runner.run(taskName)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-390
@@ -1,390 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.ui
|
||||
|
||||
import circlet.pipelines.DefaultDslFileName
|
||||
import circlet.pipelines.config.idea.api.*
|
||||
import circlet.pipelines.config.idea.api.ProcessExecutable.*
|
||||
import circlet.pipelines.config.idea.api.ScriptStep.CompositeStep
|
||||
import circlet.pipelines.config.idea.api.ScriptStep.SimpleStep
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.ide.util.PsiNavigationSupport
|
||||
import com.intellij.openapi.actionSystem.ActionToolbarPosition
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.plugins.pipelines.services.SpaceKtsModelBuilder
|
||||
import com.intellij.space.plugins.pipelines.viewmodel.ScriptState
|
||||
import com.intellij.space.plugins.pipelines.viewmodel.SpaceModelTreeNode
|
||||
import com.intellij.space.utils.LifetimedDisposable
|
||||
import com.intellij.space.utils.LifetimedDisposableImpl
|
||||
import com.intellij.ui.DumbAwareActionButton
|
||||
import com.intellij.ui.ToggleActionButton
|
||||
import com.intellij.ui.ToolbarDecorator
|
||||
import com.intellij.ui.components.ActionLink
|
||||
import com.intellij.ui.components.Panel
|
||||
import com.intellij.ui.content.ContentFactory
|
||||
import com.intellij.ui.treeStructure.Tree
|
||||
import com.intellij.util.ui.JBEmptyBorder
|
||||
import com.intellij.util.ui.tree.TreeUtil
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.klogging.KLogging
|
||||
import runtime.reactive.bind
|
||||
import runtime.reactive.mutableProperty
|
||||
import runtime.reactive.view
|
||||
import java.awt.CardLayout
|
||||
import java.awt.Component
|
||||
import java.awt.GridBagConstraints
|
||||
import java.awt.GridBagLayout
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import javax.swing.tree.TreeSelectionModel
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.jvm.javaType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
class SpaceToolWindowViewModel(val lifetime: Lifetime) {
|
||||
val taskIsRunning = mutableProperty(false)
|
||||
val selectedNode = mutableProperty<SpaceModelTreeNode?>(null)
|
||||
val extendedViewModeEnabled = mutableProperty(true)
|
||||
}
|
||||
|
||||
@Service
|
||||
class SpaceToolWindowService(val project: Project) : LifetimedDisposable by LifetimedDisposableImpl(), KLogging() {
|
||||
val modelBuilder = project.service<SpaceKtsModelBuilder>()
|
||||
|
||||
val viewModel = SpaceToolWindowViewModel(lifetime)
|
||||
|
||||
fun createToolWindowContent(toolWindow: ToolWindow) {
|
||||
|
||||
// ping script builder first so that it starts building the model for the first time.
|
||||
modelBuilder.requestModel()
|
||||
|
||||
val panel = Panel(title = null).apply {
|
||||
add(createView())
|
||||
}
|
||||
|
||||
val content = ContentFactory.SERVICE.getInstance().createContent(panel, "", false).apply {
|
||||
isCloseable = false
|
||||
}
|
||||
|
||||
toolWindow.contentManager.addContent(content)
|
||||
}
|
||||
|
||||
fun createView(): JComponent {
|
||||
logger.debug("createView. begin")
|
||||
val layout = CardLayout()
|
||||
val panel = JPanel(layout)
|
||||
val treeCompName = "tree"
|
||||
val missedDslCompName = "empty"
|
||||
val treeView = createModelTreeView(lifetime, project)
|
||||
|
||||
panel.add(treeView, treeCompName)
|
||||
panel.add(createViewForMissedDsl(project), missedDslCompName)
|
||||
|
||||
modelBuilder.script.view(lifetime) { lt, script ->
|
||||
if (script == null) {
|
||||
layout.show(panel, missedDslCompName)
|
||||
}
|
||||
else {
|
||||
layout.show(panel, treeCompName)
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("createView. end")
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun createModelTreeView(lifetime: Lifetime, project: Project): JComponent {
|
||||
val tree = Tree()
|
||||
|
||||
tree.selectionModel.selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION
|
||||
|
||||
val root = tree.model.root as DefaultMutableTreeNode
|
||||
|
||||
tree.selectionModel.addTreeSelectionListener {
|
||||
val selectedNode = it.path.lastPathComponent
|
||||
viewModel.selectedNode.value = if (selectedNode is SpaceModelTreeNode) selectedNode else null
|
||||
}
|
||||
|
||||
TreeUtil.expandAll(tree)
|
||||
tree.isRootVisible = false
|
||||
|
||||
lifetime.bind(modelBuilder.script) { script ->
|
||||
bind(viewModel.extendedViewModeEnabled) { extendedViewModeEnabled ->
|
||||
if (script != null) {
|
||||
bind(script.config) { config ->
|
||||
bind(script.error) { error ->
|
||||
bind(script.state) { state ->
|
||||
resetNodes(root, config, error, state, viewModel.extendedViewModeEnabled.value)
|
||||
(tree.model as DefaultTreeModel).reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// seems unreachable state.
|
||||
root.removeAllChildren()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val refreshAction = object : DumbAwareActionButton(IdeBundle.message("action.refresh"), AllIcons.Actions.Refresh) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
modelBuilder.rebuildModel()
|
||||
}
|
||||
}
|
||||
|
||||
val expandAllAction = object : DumbAwareActionButton(IdeBundle.message("action.expand.all"), AllIcons.Actions.Expandall) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
if (modelBuilder.script.value?.state?.value == ScriptState.Building) {
|
||||
return
|
||||
}
|
||||
TreeUtil.expandAll(tree)
|
||||
}
|
||||
}
|
||||
|
||||
val collapseAllAction = object : DumbAwareActionButton(IdeBundle.message("action.collapse.all"), AllIcons.Actions.Collapseall) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
if (modelBuilder.script.value?.state?.value == ScriptState.Building) {
|
||||
return
|
||||
}
|
||||
TreeUtil.collapseAll(tree, 0)
|
||||
}
|
||||
}
|
||||
|
||||
val showExtendedInfoAction = object : ToggleActionButton(SpaceBundle.message("show.details"), AllIcons.Actions.Show) {
|
||||
override fun isSelected(e: AnActionEvent?): Boolean {
|
||||
return viewModel.extendedViewModeEnabled.value
|
||||
}
|
||||
|
||||
override fun setSelected(e: AnActionEvent?, state: Boolean) {
|
||||
viewModel.extendedViewModeEnabled.value = state
|
||||
}
|
||||
}
|
||||
|
||||
fun updateActionsIsEnabledStates() {
|
||||
val smthIsRunning = modelBuilder.script.value?.state?.value == ScriptState.Building || viewModel.taskIsRunning.value
|
||||
refreshAction.isEnabled = !smthIsRunning
|
||||
}
|
||||
|
||||
viewModel.selectedNode.forEach(lifetime) {
|
||||
updateActionsIsEnabledStates()
|
||||
}
|
||||
|
||||
modelBuilder.script.view(lifetime) { lt, script ->
|
||||
script?.state?.forEach(lt) {
|
||||
updateActionsIsEnabledStates()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.taskIsRunning.forEach(lifetime) {
|
||||
updateActionsIsEnabledStates()
|
||||
}
|
||||
|
||||
val panel = ToolbarDecorator
|
||||
.createDecorator(tree)
|
||||
.addExtraAction(refreshAction)
|
||||
// .addExtraAction(runAction)
|
||||
.addExtraAction(expandAllAction)
|
||||
.addExtraAction(collapseAllAction)
|
||||
.addExtraAction(showExtendedInfoAction)
|
||||
.setToolbarPosition(ActionToolbarPosition.TOP)
|
||||
.createPanel()
|
||||
panel.border = JBEmptyBorder(0)
|
||||
return panel
|
||||
}
|
||||
|
||||
private fun resetNodes(root: DefaultMutableTreeNode,
|
||||
config: IdeaScriptConfig?,
|
||||
error: String?,
|
||||
state: ScriptState,
|
||||
extendedViewModeEnabled: Boolean) {
|
||||
root.removeAllChildren()
|
||||
if (config == null) {
|
||||
if (error != null) {
|
||||
root.add(SpaceModelTreeNode(error))
|
||||
return
|
||||
}
|
||||
if (state == ScriptState.Building) {
|
||||
root.add(SpaceModelTreeNode("Compiling script..."))
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val jobs = config.jobs
|
||||
val targets = config.targets
|
||||
val pipelines = config.pipelines
|
||||
|
||||
var jobsTypesCount = 0
|
||||
if (jobs.any()) {
|
||||
jobsTypesCount++
|
||||
}
|
||||
if (targets.any()) {
|
||||
jobsTypesCount++
|
||||
}
|
||||
if (pipelines.any()) {
|
||||
jobsTypesCount++
|
||||
}
|
||||
val shouldAddGroupingNodes = jobsTypesCount > 1
|
||||
if (jobs.any()) {
|
||||
val jobsCollectionNode = getGroupingNode(root, "jobs", shouldAddGroupingNodes)
|
||||
config.jobs.forEach { job ->
|
||||
val jobNode = job.toTreeNode(showChildren = extendedViewModeEnabled)
|
||||
jobsCollectionNode.add(jobNode)
|
||||
}
|
||||
}
|
||||
|
||||
targets.forEach {
|
||||
val child = SpaceModelTreeNode("target + ${it.name}. not implemented in UI yet")
|
||||
root.add(child)
|
||||
}
|
||||
pipelines.forEach {
|
||||
val child = SpaceModelTreeNode("pipelines + ${it.name}. not implemented in UI yet")
|
||||
root.add(child)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewForMissedDsl(project: Project): JComponent {
|
||||
val panel = JPanel()
|
||||
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
|
||||
|
||||
val infoText = JLabel(SpaceBundle.message("kts.toolwindow.automation.is.not.configured.label"))
|
||||
infoText.isEnabled = false
|
||||
infoText.alignmentX = Component.CENTER_ALIGNMENT
|
||||
panel.add(infoText)
|
||||
|
||||
val createDslLink = ActionLink(SpaceBundle.message("kts.toolwindow.add.automation.script.link")) {
|
||||
val basePath = project.basePath
|
||||
if (basePath != null) {
|
||||
val baseDirFile = LocalFileSystem.getInstance().findFileByPath(basePath)
|
||||
if (baseDirFile != null) {
|
||||
val application = ApplicationManager.getApplication()
|
||||
application.runWriteAction {
|
||||
val file = baseDirFile.createChildData(this, DefaultDslFileName)
|
||||
val newLine = System.getProperty("line.separator", "\n")
|
||||
val newFileContent =
|
||||
"// Write your automation script $newLine" +
|
||||
"// Once the script is ready, commit the changes to the repository $newLine" +
|
||||
"// Use full DSL reference when needed https://jetbrains.team/help/space/automation-dsl.html $newLine" +
|
||||
"job(\"My First Job\") {$newLine " +
|
||||
"container(\"hello-world\")$newLine" +
|
||||
"}"
|
||||
VfsUtil.saveText(file, newFileContent)
|
||||
PsiNavigationSupport.getInstance().createNavigatable(project, file, -1).navigate(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createDslLink.alignmentX = Component.CENTER_ALIGNMENT
|
||||
panel.add(createDslLink)
|
||||
|
||||
val rootPanel = JPanel()
|
||||
rootPanel.layout = GridBagLayout()
|
||||
rootPanel.add(panel, GridBagConstraints())
|
||||
return rootPanel
|
||||
}
|
||||
|
||||
private fun getGroupingNode(root: DefaultMutableTreeNode, name: String, shouldAddGroupingNodes: Boolean): DefaultMutableTreeNode {
|
||||
if (shouldAddGroupingNodes) {
|
||||
val res = SpaceModelTreeNode(name, true)
|
||||
root.add(res)
|
||||
return res
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
}
|
||||
|
||||
private fun ScriptJob.toTreeNode(showChildren: Boolean) = SpaceModelTreeNode(name, true).apply {
|
||||
if (showChildren) {
|
||||
if (triggers.any()) {
|
||||
add(triggers.toTreeNode())
|
||||
}
|
||||
add(steps.toTreeNode())
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Trigger>.toTreeNode() = SpaceModelTreeNode("triggers").apply {
|
||||
forEach { trigger ->
|
||||
add(trigger.toTreeNode())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Trigger.toTreeNode() = SpaceModelTreeNode(usefulSubTypeName() ?: "other trigger")
|
||||
|
||||
private fun StepSequence.toTreeNode() = SpaceModelTreeNode("steps").apply {
|
||||
forEach { step ->
|
||||
add(step.toTreeNode())
|
||||
}
|
||||
}
|
||||
|
||||
private fun ScriptStep.toTreeNode(): SpaceModelTreeNode = when (val step = this) {
|
||||
is CompositeStep -> SpaceModelTreeNode(step.treeNodeText).apply {
|
||||
step.children.forEach { subStep ->
|
||||
add(subStep.toTreeNode())
|
||||
}
|
||||
}
|
||||
is SimpleStep.Process.Container -> SpaceModelTreeNode("container: ${step.image}").apply {
|
||||
add(step.data.exec.toTreeNode())
|
||||
}
|
||||
is SimpleStep.DockerComposeStep -> SpaceModelTreeNode("compose: ${step.mainService}")
|
||||
else -> SpaceModelTreeNode(step.usefulSubTypeName())
|
||||
}
|
||||
|
||||
private val CompositeStep.treeNodeText: String get() = when(this) {
|
||||
is CompositeStep.Fork -> "parallel"
|
||||
is CompositeStep.Sequence -> "sequence"
|
||||
else -> usefulSubTypeName() ?: "composite step"
|
||||
}
|
||||
|
||||
private fun ProcessExecutable.toTreeNode() = DefaultMutableTreeNode(treeNodeText)
|
||||
|
||||
private val ProcessExecutable.treeNodeText: String
|
||||
get() = when (this) {
|
||||
is ContainerExecutable.DefaultCommand -> "exec: defaultCommand${args.presentArgs()}"
|
||||
is ContainerExecutable.OverrideEntryPoint -> "exec: overrideEntryPoint: $entryPoint${args.presentArgs()}"
|
||||
is KotlinScript -> "exec: kts script"
|
||||
is ShellScript -> "exec: shell script"
|
||||
else -> "exec: ${usefulSubTypeName()}"
|
||||
}
|
||||
|
||||
private fun List<String>.presentArgs(): String {
|
||||
return if (this.any()) ". args: ${this.joinToString()}" else ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this object's dynamic type (which is a subtype of its static type [T]).
|
||||
* If this object is of an anonymous type, this method falls back to the name of the closest supertype of the runtime type of this object
|
||||
* that implements/extends its static type [T].
|
||||
* If this object is a direct anonymous implementation of the static type [T], this method returns `null` to allow other fallbacks.
|
||||
*
|
||||
* Most instances of the interfaces are anonymous classes at the moment in the space project.
|
||||
* For instance, the [Trigger] interface may have subinterfaces that we don't know of at compile time here, but could be added in the
|
||||
* future.
|
||||
* Since the object is of an anonymous class, we should use the name of the closest supertype that implements [Trigger], e.g. GitPush.
|
||||
*/
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private inline fun <reified T : Any> T.usefulSubTypeName(): String? {
|
||||
val simpleClassName = this::class.simpleName
|
||||
if (simpleClassName != null) { // false for anonymous classes
|
||||
return simpleClassName
|
||||
}
|
||||
// There is always at least one supertype (T), because we know this is an anonymous class that is also an instance of T
|
||||
val subTypeOfT = this::class.supertypes.first { it.isSubtypeOf(typeOf<T>()) }
|
||||
val simpleSubtypeName = subTypeOfT.javaType.typeName.substringAfterLast('.')
|
||||
return simpleSubtypeName.takeIf { it != T::class.simpleName }
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.utils
|
||||
|
||||
import circlet.pipelines.DefaultDslFileName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
|
||||
object DslFileFinder {
|
||||
|
||||
fun find(project: Project): VirtualFile? {
|
||||
val basePath = project.basePath ?: return null
|
||||
val baseDirFile = LocalFileSystem.getInstance().findFileByPath(basePath) ?: return null
|
||||
return find(baseDirFile)
|
||||
}
|
||||
|
||||
fun checkFileNameIsApplicable(name: String): Boolean {
|
||||
return DefaultDslFileName.equals(name, true)
|
||||
}
|
||||
|
||||
private fun find(baseDirFile: VirtualFile): VirtualFile? {
|
||||
return baseDirFile.children.firstOrNull {
|
||||
checkFileNameIsApplicable(it.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.utils
|
||||
|
||||
import runtime.reactive.Signal
|
||||
import runtime.reactive.Source
|
||||
import java.util.*
|
||||
|
||||
interface ObservableQueue<T> : Queue<T> {
|
||||
@Suppress("unused")
|
||||
sealed class Change<out T>(val index: T) {
|
||||
class Add<out T>(val value: T) : Change<T>(value)
|
||||
}
|
||||
|
||||
val change: Source<Change<T>>
|
||||
|
||||
companion object {
|
||||
fun <T> mutable(): ObservableQueue<T> = ObservableMutableQueue()
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableMutableQueue<T> : ObservableQueue<T>, Queue<T> {
|
||||
|
||||
private val storage = ArrayDeque<T>()
|
||||
|
||||
override val change = Signal.create<ObservableQueue.Change<T>>()
|
||||
|
||||
override fun contains(element: T): Boolean {
|
||||
return storage.contains(element)
|
||||
}
|
||||
|
||||
override fun addAll(elements: Collection<T>): Boolean {
|
||||
return storage.addAll(elements)
|
||||
}
|
||||
|
||||
override fun clear() {
|
||||
TODO("not implemented") //return storage.clear()
|
||||
}
|
||||
|
||||
override fun element(): T {
|
||||
TODO("not implemented") //return storage.element()
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return storage.isEmpty()
|
||||
}
|
||||
|
||||
override fun remove(): T {
|
||||
TODO("not implemented") //return storage.remove()
|
||||
}
|
||||
|
||||
override fun remove(element: T): Boolean {
|
||||
TODO("not implemented") //return storage.remove(element)
|
||||
}
|
||||
|
||||
override fun containsAll(elements: Collection<T>): Boolean {
|
||||
TODO("not implemented") //return storage.containsAll(elements)
|
||||
}
|
||||
|
||||
override fun removeAll(elements: Collection<T>): Boolean {
|
||||
TODO("not implemented") //return storage.removeAll(elements)
|
||||
}
|
||||
|
||||
override fun add(element: T): Boolean {
|
||||
storage.add(element)
|
||||
change.fire(ObservableQueue.Change.Add(element))
|
||||
return true
|
||||
}
|
||||
|
||||
override fun offer(e: T): Boolean {
|
||||
TODO("not implemented") //return storage.offer(e)
|
||||
}
|
||||
|
||||
override fun iterator(): MutableIterator<T> {
|
||||
TODO("not implemented") //return storage.iterator()
|
||||
}
|
||||
|
||||
override fun retainAll(elements: Collection<T>): Boolean {
|
||||
TODO("not implemented") //return storage.retainAll(elements)
|
||||
}
|
||||
|
||||
override fun peek(): T {
|
||||
TODO("not implemented") //return storage.peek()
|
||||
}
|
||||
|
||||
override fun poll(): T {
|
||||
TODO("not implemented") //return storage.poll()
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = storage.size
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.plugins.pipelines.viewmodel
|
||||
|
||||
import circlet.pipelines.config.idea.api.IdeaScriptConfig
|
||||
import com.intellij.build.events.BuildEvent
|
||||
import com.intellij.build.events.BuildEventsNls
|
||||
import com.intellij.build.events.impl.OutputBuildEventImpl
|
||||
import libraries.coroutines.extra.LifetimeSource
|
||||
import libraries.coroutines.extra.assertNotTerminated
|
||||
import libraries.io.random.Random
|
||||
import runtime.reactive.ObservableList
|
||||
import runtime.reactive.Property
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
|
||||
enum class ScriptState { NotInitialised, Building, Ready }
|
||||
|
||||
interface ScriptModel {
|
||||
val config: Property<IdeaScriptConfig?>
|
||||
val error: Property<String?>
|
||||
val state: Property<ScriptState>
|
||||
}
|
||||
|
||||
class LogData {
|
||||
|
||||
// lifetime corresponding to the entire build process, it terminates when this build finishes
|
||||
val lifetime get() = _buildLifetime
|
||||
|
||||
val buildId = Random.nextUID()
|
||||
|
||||
val messages = ObservableList.mutable<BuildEvent>()
|
||||
|
||||
private val _buildLifetime = LifetimeSource()
|
||||
|
||||
fun message(@BuildEventsNls.Message message: String) {
|
||||
_buildLifetime.assertNotTerminated()
|
||||
messages.add(OutputBuildEventImpl(buildId, message + "\n", true))
|
||||
}
|
||||
|
||||
fun error(@BuildEventsNls.Message message: String) {
|
||||
_buildLifetime.assertNotTerminated()
|
||||
messages.add(OutputBuildEventImpl(buildId, message + "\n", false))
|
||||
}
|
||||
|
||||
fun close() {
|
||||
_buildLifetime.terminate()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SpaceModelTreeNode(text: String? = null, val isRunnable: Boolean = false) : DefaultMutableTreeNode(text)
|
||||
@@ -1,194 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.promo
|
||||
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.ide.ui.fullRow
|
||||
import com.intellij.openapi.ui.panel.ComponentPanelBuilder
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.ui.components.BrowserLink
|
||||
import com.intellij.ui.layout.*
|
||||
import com.intellij.ui.scale.ScaleContext
|
||||
import com.intellij.ui.scale.ScaleType
|
||||
import com.intellij.util.FontUtil
|
||||
import com.intellij.util.JBHiDPIScaledImage
|
||||
import com.intellij.util.SVGLoader
|
||||
import com.intellij.util.ui.*
|
||||
import icons.SpaceIcons
|
||||
import libraries.klogging.logger
|
||||
import java.awt.Color
|
||||
import java.awt.Cursor
|
||||
import java.awt.Dimension
|
||||
import java.awt.Image
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JLayeredPane
|
||||
|
||||
internal const val SPACE_PROMO_VIDEO_URL: String = "https://www.jetbrains.com/space/?show-overview=from-ide&switch-to-branch=JS-14525-space-overview-video-qs-parameter"
|
||||
internal const val EXPLORE_SPACE_PROMO_URL: String = "https://www.jetbrains.com/space/?fromIDEplugin"
|
||||
internal const val SIGN_UP_SPACE_URL: String = "https://www.jetbrains.com/space/?fromIDEplugin#sign-up"
|
||||
|
||||
internal const val SPACE_TOOLBAR_PROMO_BANNER_PATH = "/images/spacePromo.png"
|
||||
internal const val SPACE_TOOLBAR_PROMO_BANNER_PATH_RETINA = "/images/spacePromo@2x.png"
|
||||
|
||||
internal const val SPACE_BIG_PROMO_BANNER_NAME = "/images/spaceVideoPreview"
|
||||
internal const val DARK_POSTFIX = "_dark"
|
||||
internal const val HIDPI_POSTFIX = "@2x"
|
||||
|
||||
internal const val JETBRAINS_SPACE_LOGO = "/images/jetbrainsSpace.svg"
|
||||
internal const val JETBRAINS_SPACE_LOGO_DARK = "/images/jetbrainsSpaceDark.svg"
|
||||
|
||||
internal fun LayoutBuilder.promoPanel(statsExplorePlace: SpaceStatsCounterCollector.ExplorePlace) {
|
||||
fullRow { createSpaceByJetbrainsLabel()() }
|
||||
fullRow { promoText(76)() }
|
||||
fullRow { exploreSpaceLink(statsExplorePlace)() }.largeGapAfter()
|
||||
}
|
||||
|
||||
internal fun toolbarPromoBanner(): JComponent? {
|
||||
val imagePath = if (StartupUiUtil.isJreHiDPI()) SPACE_TOOLBAR_PROMO_BANNER_PATH_RETINA else SPACE_TOOLBAR_PROMO_BANNER_PATH
|
||||
val image = ImageLoader.loadImage(imagePath, 364, 199) ?: return null
|
||||
|
||||
return wrapWithWatchSpaceOverviewLabelOverlay(JLabel(JBImageIcon(image)), SpaceStatsCounterCollector.OverviewPlace.MAIN_TOOLBAR)
|
||||
}
|
||||
|
||||
internal fun bigPromoBanner(statsOverviewPlace: SpaceStatsCounterCollector.OverviewPlace): JComponent? =
|
||||
bigPromoBanner(500, 285, statsOverviewPlace)
|
||||
|
||||
internal fun bigPromoBanner(width: Int, height: Int, statsOverviewPlace: SpaceStatsCounterCollector.OverviewPlace): JComponent? {
|
||||
val isDarcula = StartupUiUtil.isUnderDarcula()
|
||||
val themePart = if (isDarcula) DARK_POSTFIX else ""
|
||||
val retinaPart = if (StartupUiUtil.isJreHiDPI()) HIDPI_POSTFIX else ""
|
||||
val imagePath = "${SPACE_BIG_PROMO_BANNER_NAME}${retinaPart}${themePart}.png"
|
||||
|
||||
val image = ImageLoader.loadImage(imagePath, width, height) ?: return null
|
||||
|
||||
return wrapWithWatchSpaceOverviewLabelOverlay(
|
||||
JLabel(JBImageIcon(image)),
|
||||
statsOverviewPlace,
|
||||
alwaysDisplayLabel = true,
|
||||
useDarkLabel = isDarcula
|
||||
)
|
||||
}
|
||||
|
||||
internal fun exploreSpaceLink(statsExplorePlace: SpaceStatsCounterCollector.ExplorePlace): BrowserLink =
|
||||
BrowserLink(
|
||||
SpaceBundle.message("space.promo.explore.space.button"),
|
||||
EXPLORE_SPACE_PROMO_URL
|
||||
).apply {
|
||||
addActionListener {
|
||||
SpaceStatsCounterCollector.EXPLORE_SPACE.log(statsExplorePlace)
|
||||
}
|
||||
isFocusPainted = false
|
||||
}
|
||||
|
||||
private fun wrapWithWatchSpaceOverviewLabelOverlay(
|
||||
component: JComponent,
|
||||
statsOverviewPlace: SpaceStatsCounterCollector.OverviewPlace,
|
||||
alwaysDisplayLabel: Boolean = false,
|
||||
useDarkLabel: Boolean = true
|
||||
): JComponent {
|
||||
@NlsSafe val text = "\u25B6${FontUtil.spaceAndThinSpace()}${SpaceBundle.message("space.promo.watch.space.overview.label")}"
|
||||
val labelBackground = if (useDarkLabel) Color(0, 0, 0, 180) else Color(255, 255, 255, 180)
|
||||
val labelForeground = if (useDarkLabel) Color.WHITE else Color.BLACK
|
||||
|
||||
val watchVideoButton = JLabel(text).apply {
|
||||
isFocusable = false
|
||||
isOpaque = true
|
||||
isVisible = alwaysDisplayLabel
|
||||
background = labelBackground
|
||||
foreground = labelForeground
|
||||
border = JBUI.Borders.empty(8)
|
||||
}
|
||||
|
||||
return JComponentOverlay.createCentered(component, watchVideoButton).apply {
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
|
||||
addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseEntered(e: MouseEvent?) {
|
||||
if (!alwaysDisplayLabel) watchVideoButton.isVisible = true
|
||||
}
|
||||
|
||||
override fun mouseExited(e: MouseEvent?) {
|
||||
if (!alwaysDisplayLabel) watchVideoButton.isVisible = false
|
||||
}
|
||||
|
||||
override fun mouseClicked(e: MouseEvent?) {
|
||||
SpaceStatsCounterCollector.WATCH_OVERVIEW.log(statsOverviewPlace)
|
||||
watchPromoVideo()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createSpaceByJetbrainsLabel(): JComponent = JLabel().apply {
|
||||
val jbSpaceSvgLogo = SvgLoader.loadSvg(if (StartupUiUtil.isUnderDarcula()) JETBRAINS_SPACE_LOGO_DARK else JETBRAINS_SPACE_LOGO, this)
|
||||
if (jbSpaceSvgLogo != null) {
|
||||
icon = jbSpaceSvgLogo
|
||||
}
|
||||
else {
|
||||
icon = SpaceIcons.Main
|
||||
text = SpaceBundle.message("product.name.jetbrains.space")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun promoText(maxLineLength: Int = 0): JLabel = ComponentPanelBuilder.createCommentComponent(
|
||||
SpaceBundle.message("space.promo.text.full"), true, maxLineLength).apply {
|
||||
foreground = JBUI.CurrentTheme.Label.foreground()
|
||||
font = JBUI.Fonts.label()
|
||||
}
|
||||
|
||||
private fun watchPromoVideo() {
|
||||
BrowserUtil.browse(SPACE_PROMO_VIDEO_URL)
|
||||
}
|
||||
|
||||
private object JComponentOverlay {
|
||||
fun createCentered(component: JComponent, centeredOverlay: JComponent): JLayeredPane {
|
||||
val pane = object : JLayeredPane() {
|
||||
override fun getPreferredSize(): Dimension = component.preferredSize
|
||||
|
||||
override fun doLayout() {
|
||||
super.doLayout()
|
||||
component.setBounds(0, 0, width, height)
|
||||
centeredOverlay.bounds = SingleComponentCenteringLayout.getBoundsForCentered(component, centeredOverlay)
|
||||
}
|
||||
}
|
||||
pane.isFocusable = false
|
||||
pane.add(component, JLayeredPane.DEFAULT_LAYER, 1)
|
||||
pane.add(centeredOverlay, JLayeredPane.DEFAULT_LAYER, 0)
|
||||
return pane
|
||||
}
|
||||
}
|
||||
|
||||
val log = logger<ImageLoader>()
|
||||
|
||||
private object ImageLoader {
|
||||
|
||||
fun loadImage(path: String, width: Int, height: Int): Image? {
|
||||
return try {
|
||||
val img = ImageIO.read(javaClass.getResourceAsStream(path))
|
||||
JBHiDPIScaledImage(img, width, height, img.type)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
log.error { "Image ${path} is not loaded" }
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object SvgLoader {
|
||||
fun loadSvg(path: String, comp: JComponent): JBImageIcon? = try {
|
||||
val bytes = javaClass.getResourceAsStream(path)?.readAllBytes()
|
||||
val ctx = ScaleContext.create(comp)
|
||||
val image = SVGLoader.loadWithoutCache(bytes, ctx.getScale(ScaleType.SYS_SCALE).toFloat())
|
||||
val hiDpi = ImageUtil.ensureHiDPI(image, ctx)
|
||||
JBImageIcon(hiDpi)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
log.error { "Svg ${path} is not loaded" }
|
||||
null
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.runtime
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.Application
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import kotlinx.coroutines.*
|
||||
import libraries.klogging.logger
|
||||
import runtime.Cancellable
|
||||
import runtime.CoroutineExceptionLogger
|
||||
import runtime.Dispatcher
|
||||
import runtime.TaskCancellable
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
private val log = logger<ApplicationDispatcher>()
|
||||
|
||||
internal class ApplicationDispatcher(disposable: Disposable, private val application: Application) : Dispatcher {
|
||||
private val executor = AppExecutorUtil.createBoundedScheduledExecutorService("Application Auxiliary Scheduler", 1)
|
||||
|
||||
private val context = ApplicationCoroutineContext(application, executor)
|
||||
|
||||
private val contextWithLog = context + CoroutineExceptionLogger.create(log)
|
||||
|
||||
override val coroutineContext: CoroutineContext
|
||||
get() = contextWithLog
|
||||
|
||||
init {
|
||||
Disposer.register(disposable, Disposable {
|
||||
executor.shutdown()
|
||||
try {
|
||||
executor.awaitTermination(5, TimeUnit.SECONDS)
|
||||
}
|
||||
catch (e: InterruptedException) {
|
||||
log.warn { "Executor was not properly terminated" }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun dispatch(fn: () -> Unit) {
|
||||
application.invokeLater(fn, ModalityState.any())
|
||||
}
|
||||
|
||||
override fun dispatch(delay: Int, fn: () -> Unit): Cancellable {
|
||||
val invoke = java.lang.Runnable {
|
||||
application.invokeLater(fn, ModalityState.any())
|
||||
}
|
||||
val disposable = executor.schedule(invoke, delay.toLong(), TimeUnit.MILLISECONDS)
|
||||
|
||||
return TaskCancellable(disposable)
|
||||
}
|
||||
|
||||
override fun dispatchInterval(delay: Int, interval: Int, fn: () -> Unit): Cancellable {
|
||||
val invoke = java.lang.Runnable {
|
||||
application.invokeLater(fn, ModalityState.any())
|
||||
}
|
||||
val disposable = executor.scheduleWithFixedDelay(invoke, delay.toLong(), interval.toLong(), TimeUnit.MILLISECONDS)
|
||||
|
||||
return TaskCancellable(disposable)
|
||||
}
|
||||
}
|
||||
|
||||
@UseExperimental(InternalCoroutinesApi::class)
|
||||
private class ApplicationCoroutineContext(
|
||||
private val application: Application,
|
||||
private val executor: ScheduledExecutorService
|
||||
) : CoroutineDispatcher(), Delay {
|
||||
|
||||
override fun dispatch(context: CoroutineContext, block: Runnable) {
|
||||
application.invokeLater(block, ModalityState.any())
|
||||
}
|
||||
|
||||
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
|
||||
val toResume = java.lang.Runnable {
|
||||
application.invokeLater({ with(continuation) { this@ApplicationCoroutineContext.resumeUndispatched(Unit) } }, ModalityState.any())
|
||||
}
|
||||
|
||||
executor.schedule(toResume, timeMillis, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
|
||||
val toResume = java.lang.Runnable {
|
||||
application.invokeLater({
|
||||
block.run()
|
||||
}, ModalityState.any())
|
||||
}
|
||||
|
||||
return DisposableFutureHandle(executor.schedule(toResume, timeMillis, TimeUnit.MILLISECONDS))
|
||||
}
|
||||
}
|
||||
|
||||
private class DisposableFutureHandle(private val future: Future<*>) : DisposableHandle {
|
||||
override fun dispose() {
|
||||
future.cancel(false)
|
||||
}
|
||||
|
||||
override fun toString(): String = "DisposableFutureHandle[$future]"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.settings
|
||||
|
||||
import circlet.workspaces.Workspace
|
||||
import com.intellij.openapi.util.NlsContexts.DetailedDescription
|
||||
|
||||
sealed class SpaceLoginState(val server: String) {
|
||||
class Disconnected(server: String, @DetailedDescription val error: String? = null) : SpaceLoginState(server)
|
||||
class Connected(server: String, val workspace: Workspace) : SpaceLoginState(server)
|
||||
class Connecting(server: String, private val cancelationHandler: () -> Unit) : SpaceLoginState(server) {
|
||||
fun cancel() = cancelationHandler()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.settings
|
||||
|
||||
data class SpaceServerSettings(
|
||||
var enabled: Boolean = false,
|
||||
var server: String = ""
|
||||
)
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.settings
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.*
|
||||
|
||||
@State(
|
||||
name = "SpaceServerConfigurable",
|
||||
storages = [Storage(value = "SpaceServer.xml", roamingType = RoamingType.DEFAULT)]
|
||||
)
|
||||
class SpaceSettings : SimplePersistentStateComponent<SpaceSettingsState>(SpaceSettingsState()) {
|
||||
var serverSettings: SpaceServerSettings
|
||||
get() = state.serverSettings
|
||||
set(value) {
|
||||
state.serverSettings = value
|
||||
}
|
||||
|
||||
var cloneType: CloneType
|
||||
get() = state.cloneType
|
||||
set(value) {
|
||||
state.cloneType = value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getInstance(): SpaceSettings = ApplicationManager.getApplication().getService(SpaceSettings::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
class SpaceSettingsState : BaseState() {
|
||||
var serverSettings: SpaceServerSettings by this.property(SpaceServerSettings()) {
|
||||
it.enabled.not() && it.server.isBlank()
|
||||
}
|
||||
|
||||
var cloneType: CloneType by enum(CloneType.HTTPS)
|
||||
}
|
||||
|
||||
enum class CloneType {
|
||||
HTTPS,
|
||||
SSH
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.settings
|
||||
|
||||
import circlet.client.api.englishFullName
|
||||
import com.intellij.ide.ui.fullRow
|
||||
import com.intellij.openapi.options.BoundConfigurable
|
||||
import com.intellij.openapi.options.SearchableConfigurable
|
||||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogPanel
|
||||
import com.intellij.space.components.SpaceUserAvatarProvider
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.messages.SpaceBundle
|
||||
import com.intellij.space.promo.bigPromoBanner
|
||||
import com.intellij.space.promo.promoPanel
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector
|
||||
import com.intellij.space.ui.LoginComponents.buildConnectingPanel
|
||||
import com.intellij.space.ui.LoginComponents.loginPanel
|
||||
import com.intellij.space.ui.LoginComponents.separatorRow
|
||||
import com.intellij.space.ui.cleanupUrl
|
||||
import com.intellij.space.ui.resizeIcon
|
||||
import com.intellij.space.utils.LifetimedDisposable
|
||||
import com.intellij.space.utils.LifetimedDisposableImpl
|
||||
import com.intellij.space.utils.SpaceUrls
|
||||
import com.intellij.ui.EnumComboBoxModel
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.BrowserLink
|
||||
import com.intellij.ui.components.panels.VerticalLayout
|
||||
import com.intellij.ui.layout.*
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import libraries.klogging.logger
|
||||
import net.miginfocom.layout.CC
|
||||
import net.miginfocom.layout.LC
|
||||
import net.miginfocom.swing.MigLayout
|
||||
import runtime.reactive.SequentialLifetimes
|
||||
import javax.swing.JButton
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JPanel
|
||||
|
||||
val log = logger<SpaceSettingsPanel>()
|
||||
|
||||
class SpaceSettingsPanel :
|
||||
BoundConfigurable(SpaceBundle.message("configurable.name"), null),
|
||||
SearchableConfigurable,
|
||||
LifetimedDisposable by LifetimedDisposableImpl() {
|
||||
private val sqLifetime = SequentialLifetimes(lifetime)
|
||||
|
||||
override fun getId(): String = "settings.space"
|
||||
|
||||
override fun createPanel(): DialogPanel = panel {
|
||||
val panelLifetime = sqLifetime.next()
|
||||
val accountPanel = BorderLayoutPanel()
|
||||
row {
|
||||
cell(isFullWidth = true) { accountPanel(pushX, growX) }
|
||||
}
|
||||
val separatorRow = separatorRow()
|
||||
val cloneTypeRow = row {
|
||||
cell(isFullWidth = true) {
|
||||
label(SpaceBundle.message("settings.panel.clone.repositories.with.label"))
|
||||
comboBox(EnumComboBoxModel(CloneType::class.java), SpaceSettings.getInstance()::cloneType)
|
||||
createConfigureHttpAndSshLink()().withLargeLeftGap()
|
||||
}
|
||||
}
|
||||
|
||||
SpaceWorkspaceComponent.getInstance().loginState.forEach(panelLifetime) { loginState ->
|
||||
accountPanel.removeAll()
|
||||
accountPanel.addToCenter(createView(accountPanel, loginState))
|
||||
accountPanel.revalidate()
|
||||
accountPanel.repaint()
|
||||
|
||||
val rowsVisibility = when (loginState) {
|
||||
is SpaceLoginState.Connected -> true
|
||||
else -> false
|
||||
}
|
||||
separatorRow.visible = rowsVisibility
|
||||
cloneTypeRow.visible = rowsVisibility
|
||||
}
|
||||
}
|
||||
|
||||
private fun createView(wrapper: JComponent, st: SpaceLoginState): JComponent {
|
||||
when (st) {
|
||||
is SpaceLoginState.Disconnected -> return buildSettingsLoginPanel(st) { server ->
|
||||
SpaceWorkspaceComponent.getInstance().signInManually(server, lifetime, wrapper)
|
||||
}
|
||||
|
||||
is SpaceLoginState.Connecting -> return buildConnectingPanel(st, SpaceStatsCounterCollector.LoginPlace.SETTINGS) {
|
||||
st.cancel()
|
||||
}
|
||||
|
||||
is SpaceLoginState.Connected -> {
|
||||
val serverComponent = JLabel(cleanupUrl(st.server)).apply {
|
||||
foreground = SimpleTextAttributes.GRAYED_ATTRIBUTES.fgColor
|
||||
font = font.deriveFont((font.size * 0.9).toFloat())
|
||||
}
|
||||
val logoutButton = JButton(SpaceBundle.message("settings.panel.log.out.button.text")).apply {
|
||||
addActionListener {
|
||||
SpaceWorkspaceComponent.getInstance().signOut(SpaceStatsCounterCollector.LogoutPlace.SETTINGS)
|
||||
}
|
||||
}
|
||||
|
||||
val namePanel = JPanel(VerticalLayout(UIUtil.DEFAULT_VGAP)).apply {
|
||||
add(JLabel(st.workspace.me.value.englishFullName())) // NON-NLS
|
||||
add(serverComponent)
|
||||
}
|
||||
|
||||
val infoPanel = JPanel(VerticalLayout(UIUtil.DEFAULT_VGAP)).apply {
|
||||
add(namePanel)
|
||||
val logoutButtonPanel = BorderLayoutPanel().addToLeft(logoutButton).apply {
|
||||
border = JBUI.Borders.emptyTop(5)
|
||||
}
|
||||
add(logoutButtonPanel)
|
||||
}
|
||||
|
||||
val avatarLabel = JLabel()
|
||||
SpaceUserAvatarProvider.getInstance().avatars.forEach(lifetime) { avatars ->
|
||||
avatarLabel.icon = resizeIcon(avatars.circle, 64)
|
||||
}
|
||||
return JPanel().apply {
|
||||
layout = MigLayout(LC().gridGap("${JBUI.scale(10)}", "0")
|
||||
.insets("0", "0", "0", "0")
|
||||
.fill()).apply {
|
||||
columnConstraints = "[][]"
|
||||
}
|
||||
add(avatarLabel, CC().pushY().alignY("center"))
|
||||
add(infoPanel, CC().push())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createConfigureHttpAndSshLink(): JComponent {
|
||||
val contentPanel = BorderLayoutPanel().apply {
|
||||
isOpaque = false
|
||||
}
|
||||
SpaceWorkspaceComponent.getInstance().workspace.forEach(lifetime) {
|
||||
contentPanel.removeAll()
|
||||
if (it != null) {
|
||||
val url = SpaceUrls.git(it.me.value.username)
|
||||
val browserLink = BrowserLink(SpaceBundle.message("settings.panel.configure.git.ssh.keys.http.password.link"), url).apply {
|
||||
addActionListener {
|
||||
SpaceStatsCounterCollector.OPEN_GIT_SETTINGS_IN_SPACE.log()
|
||||
}
|
||||
}
|
||||
contentPanel.addToCenter(browserLink)
|
||||
}
|
||||
contentPanel.revalidate()
|
||||
contentPanel.repaint()
|
||||
}
|
||||
return contentPanel
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun openSettings(project: Project?) {
|
||||
ShowSettingsUtil.getInstance().showSettingsDialog(project, SpaceSettingsPanel::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun buildSettingsLoginPanel(st: SpaceLoginState.Disconnected,
|
||||
loginAction: (String) -> Unit
|
||||
): DialogPanel {
|
||||
return panel {
|
||||
loginPanel(st, SpaceStatsCounterCollector.LoginPlace.SETTINGS, isLoginActionDefault = false, withOrganizationsUrlLabel = true,
|
||||
loginAction)
|
||||
promoPanel(SpaceStatsCounterCollector.ExplorePlace.SETTINGS)
|
||||
bigPromoBanner(SpaceStatsCounterCollector.OverviewPlace.SETTINGS)?.let {
|
||||
fullRow { it() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,363 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.stats
|
||||
|
||||
import circlet.code.api.CodeReviewParticipantRole
|
||||
import com.intellij.internal.statistic.eventLog.EventLogGroup
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
|
||||
import com.intellij.space.settings.SpaceLoginState
|
||||
import com.intellij.space.vcs.review.list.ReviewListQuickFilter
|
||||
|
||||
internal class SpaceStatsCounterCollector : CounterUsagesCollector() {
|
||||
override fun getGroup(): EventLogGroup = GROUP
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val GROUP = EventLogGroup("space", 1)
|
||||
|
||||
|
||||
// --------------------
|
||||
// General
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val LOGIN_STATUS = EventFields.Enum<LoginState>("login_status")
|
||||
|
||||
@JvmField
|
||||
val OPEN_MAIN_TOOLBAR_POPUP = GROUP.registerEvent("open_main_toolbar_popup", LOGIN_STATUS)
|
||||
|
||||
@JvmField
|
||||
val OPEN_GIT_SETTINGS_IN_SPACE = GROUP.registerEvent("open_git_settings_in_space")
|
||||
|
||||
// --------------------
|
||||
// Advertisement
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val EXPLORE_PLACE = EventFields.Enum<ExplorePlace>("adv_explore_place")
|
||||
|
||||
@JvmField
|
||||
val OVERVIEW_PLACE = EventFields.Enum<OverviewPlace>("adv_overview_place")
|
||||
|
||||
@JvmField
|
||||
val EXPLORE_SPACE = GROUP.registerEvent("adv_explore_space", EXPLORE_PLACE)
|
||||
|
||||
@JvmField
|
||||
val WATCH_OVERVIEW = GROUP.registerEvent("adv_watch_overview", OVERVIEW_PLACE)
|
||||
|
||||
@JvmField
|
||||
val ADV_LOG_IN_LINK = GROUP.registerEvent("adv_log_in_link")
|
||||
|
||||
@JvmField
|
||||
val SIGN_UP_LINK = GROUP.registerEvent("adv_sign_up_link")
|
||||
|
||||
// --------------------
|
||||
// Clone
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val OPEN_SPACE_CLONE_TAB = GROUP.registerEvent("open_space_clone_tab", LOGIN_STATUS)
|
||||
|
||||
@JvmField
|
||||
val CLONE_REPO = GROUP.registerEvent("clone_repo")
|
||||
|
||||
// --------------------
|
||||
// Share project
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val OPEN_SHARE_PROJECT = GROUP.registerEvent("open_share_project", LOGIN_STATUS)
|
||||
|
||||
@JvmField
|
||||
val START_CREATING_NEW_PROJECT = GROUP.registerEvent("start_creating_new_project")
|
||||
|
||||
@JvmField
|
||||
val CREATE_NEW_PROJECT = GROUP.registerEvent("create_new_project")
|
||||
|
||||
@JvmField
|
||||
val SHARE_PROJECT = GROUP.registerEvent("share_project")
|
||||
|
||||
// --------------------
|
||||
// Chat
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val SEND_MESSAGE_PLACE = EventFields.Enum<SendMessagePlace>("new_message_place")
|
||||
|
||||
@JvmField
|
||||
val SEND_MESSAGE_IS_PENDING = EventFields.Boolean("new_message_is_pending")
|
||||
|
||||
@JvmField
|
||||
val EDIT_MESSAGE_IS_EMPTY = EventFields.Boolean("edit_message_is_empty")
|
||||
|
||||
@JvmField
|
||||
val SEND_MESSAGE = GROUP.registerEvent("chat_send_message", SEND_MESSAGE_PLACE, SEND_MESSAGE_IS_PENDING)
|
||||
|
||||
@JvmField
|
||||
val DISCARD_SEND_MESSAGE = GROUP.registerEvent("chat_discard_send_message", SEND_MESSAGE_PLACE)
|
||||
|
||||
@JvmField
|
||||
val START_EDIT_MESSAGE = GROUP.registerEvent("chat_start_edit_message")
|
||||
|
||||
@JvmField
|
||||
val DISCARD_EDIT_MESSAGE = GROUP.registerEvent("chat_discard_edit_message")
|
||||
|
||||
@JvmField
|
||||
val SEND_EDIT_MESSAGE = GROUP.registerEvent("chat_send_edit_message", EDIT_MESSAGE_IS_EMPTY)
|
||||
|
||||
@JvmField
|
||||
val OPEN_THREAD = GROUP.registerEvent("chat_open_thread")
|
||||
|
||||
@JvmField
|
||||
val DELETE_MESSAGE = GROUP.registerEvent("chat_delete_message")
|
||||
|
||||
@JvmField
|
||||
val EXPAND_DISCUSSION = GROUP.registerEvent("chat_expand_discussion")
|
||||
|
||||
@JvmField
|
||||
val COLLAPSE_DISCUSSION = GROUP.registerEvent("chat_collapse_discussion")
|
||||
|
||||
@JvmField
|
||||
val RESOLVE_DISCUSSION = GROUP.registerEvent("chat_resolve_discussion")
|
||||
|
||||
@JvmField
|
||||
val REOPEN_DISCUSSION = GROUP.registerEvent("chat_reopen_discussion")
|
||||
|
||||
// --------------------
|
||||
// Login
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val LOGIN_PLACE = EventFields.Enum<LoginPlace>("login_place")
|
||||
|
||||
@JvmField
|
||||
val LOGOUT_PLACE = EventFields.Enum<LogoutPlace>("logout_place")
|
||||
|
||||
@JvmField
|
||||
val LOG_IN = GROUP.registerEvent("button_log_in", LOGIN_PLACE)
|
||||
|
||||
@JvmField
|
||||
val LOG_OUT = GROUP.registerEvent("button_log_out", LOGOUT_PLACE)
|
||||
|
||||
@JvmField
|
||||
val CANCEL_LOGIN = GROUP.registerEvent("cancel_login", LOGIN_PLACE)
|
||||
|
||||
// --------------------
|
||||
// ReviewsList
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val QUICK_FILTER = EventFields.Enum<ReviewListQuickFilter>("quick_filter", transform = { it.name })
|
||||
|
||||
@JvmField
|
||||
val OPEN_REVIEW_TYPE = EventFields.Enum<OpenReviewActionType>("open_review_type")
|
||||
|
||||
@JvmField
|
||||
val FILTER_TEXT_EMPTY = EventFields.Boolean("filter_text_empty")
|
||||
|
||||
@JvmField
|
||||
val REFRESH_REVIEWS_PLACE = EventFields.Enum<RefreshReviewsPlace>("refresh_reviews_place")
|
||||
|
||||
@JvmField
|
||||
val REVIEWS_LOG_IN_LINK = GROUP.registerEvent("reviews_list_log_in_link")
|
||||
|
||||
@JvmField
|
||||
val CHANGE_QUICK_FILTER = GROUP.registerEvent("reviews_list_change_quick_filter", QUICK_FILTER)
|
||||
|
||||
@JvmField
|
||||
val CHANGE_TEXT_FILTER = GROUP.registerEvent("reviews_list_change_text_filter", FILTER_TEXT_EMPTY)
|
||||
|
||||
@JvmField
|
||||
val OPEN_REVIEW = GROUP.registerEvent("reviews_list_open_review", OPEN_REVIEW_TYPE)
|
||||
|
||||
@JvmField
|
||||
val REFRESH_REVIEWS_ACTION = GROUP.registerEvent("reviews_list_refresh_action", REFRESH_REVIEWS_PLACE)
|
||||
|
||||
// --------------------
|
||||
// ReviewDetails
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val PARTICIPANT_ROLE = EventFields.Enum<CodeReviewParticipantRole>("participant_role", transform = { it.name })
|
||||
|
||||
@JvmField
|
||||
val PARTICIPANT_EDIT_TYPE = EventFields.Enum<ParticipantEditType>("participant_edit_type")
|
||||
|
||||
@JvmField
|
||||
val COMMITS_SELECTION_TYPE = EventFields.Enum<CommitsSelectionType>("commits_selection_type")
|
||||
|
||||
@JvmField
|
||||
val DETAILS_TAB_TYPE = EventFields.Enum<DetailsTabType>("details_tab_type")
|
||||
|
||||
@JvmField
|
||||
val REVIEW_DIFF_PLACE = EventFields.Enum<ReviewDiffPlace>("review_diff_place")
|
||||
|
||||
@JvmField
|
||||
val EDIT_PARTICIPANT = GROUP.registerEvent("review_details_edit_participant", PARTICIPANT_ROLE, PARTICIPANT_EDIT_TYPE)
|
||||
|
||||
@JvmField
|
||||
val EDIT_PARTICIPANT_ICON = GROUP.registerEvent("review_details_add_participant_icon", PARTICIPANT_ROLE)
|
||||
|
||||
@JvmField
|
||||
val SHOW_TIMELINE = GROUP.registerEvent("review_details_show_timeline")
|
||||
|
||||
@JvmField
|
||||
val OPEN_PROJECT_IN_SPACE = GROUP.registerEvent("review_details_open_project_in_space")
|
||||
|
||||
@JvmField
|
||||
val OPEN_REVIEW_IN_SPACE = GROUP.registerEvent("review_details_open_review_in_space")
|
||||
|
||||
@JvmField
|
||||
val BACK_TO_LIST = GROUP.registerEvent("review_details_back_to_list", DETAILS_TAB_TYPE)
|
||||
|
||||
@JvmField
|
||||
val CHANGE_COMMITS_SELECTION = GROUP.registerEvent("review_details_change_commits_selection", COMMITS_SELECTION_TYPE)
|
||||
|
||||
@JvmField
|
||||
val SELECT_DETAILS_TAB = GROUP.registerEvent("review_details_select_details_tab", DETAILS_TAB_TYPE)
|
||||
|
||||
@JvmField
|
||||
val OPEN_REVIEW_DIFF = GROUP.registerEvent("review_details_open_review_diff", REVIEW_DIFF_PLACE)
|
||||
|
||||
@JvmField
|
||||
val CHECKOUT_BRANCH = GROUP.registerEvent("review_details_checkout_branch")
|
||||
|
||||
@JvmField
|
||||
val UPDATE_BRANCH = GROUP.registerEvent("review_details_update_branch")
|
||||
|
||||
@JvmField
|
||||
val ACCEPT_CHANGES = GROUP.registerEvent("review_details_accept_changes")
|
||||
|
||||
@JvmField
|
||||
val WAIT_FOR_RESPONSE = GROUP.registerEvent("review_details_wait_for_response")
|
||||
|
||||
@JvmField
|
||||
val RESUME_REVIEW = GROUP.registerEvent("review_details_resume_review")
|
||||
|
||||
// --------------------
|
||||
// ReviewDiff
|
||||
// --------------------
|
||||
|
||||
@JvmField
|
||||
val LOADER_TYPE = EventFields.Enum<LoaderType>("loader_type")
|
||||
|
||||
@JvmField
|
||||
val LEAVE_COMMENT = GROUP.registerEvent("review_diff_leave_comment")
|
||||
|
||||
@JvmField
|
||||
val CLOSE_LEAVE_COMMENT = GROUP.registerEvent("review_diff_close_leave_comment")
|
||||
|
||||
@JvmField
|
||||
val DIFF_LOADED = GROUP.registerEvent("review_diff_loaded", LOADER_TYPE)
|
||||
}
|
||||
|
||||
|
||||
// helper classes should be outside the companion to get rid of Companion class in imports and usages
|
||||
|
||||
enum class LoginState {
|
||||
CONNECTED,
|
||||
CONNECTING,
|
||||
DISCONNECTED;
|
||||
|
||||
companion object {
|
||||
fun convert(state: SpaceLoginState) = when (state) {
|
||||
is SpaceLoginState.Connected -> CONNECTED
|
||||
is SpaceLoginState.Connecting -> CONNECTING
|
||||
is SpaceLoginState.Disconnected -> DISCONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ExplorePlace {
|
||||
MAIN_TOOLBAR,
|
||||
SETTINGS,
|
||||
SHARE,
|
||||
CLONE
|
||||
}
|
||||
|
||||
enum class OverviewPlace {
|
||||
MAIN_TOOLBAR,
|
||||
SETTINGS,
|
||||
CLONE
|
||||
}
|
||||
|
||||
enum class SendMessagePlace {
|
||||
MAIN_CHAT,
|
||||
THREAD,
|
||||
DIFF,
|
||||
NEW_THREAD,
|
||||
FIRST_DISCUSSION_ANSWER,
|
||||
NEW_DISCUSSION
|
||||
}
|
||||
|
||||
enum class LoginPlace {
|
||||
MAIN_TOOLBAR,
|
||||
SETTINGS,
|
||||
SHARE,
|
||||
CLONE
|
||||
}
|
||||
|
||||
enum class LogoutPlace {
|
||||
ACTION,
|
||||
SETTINGS,
|
||||
MAIN_TOOLBAR,
|
||||
CLONE,
|
||||
AUTH_FAIL,
|
||||
}
|
||||
|
||||
enum class OpenReviewActionType {
|
||||
ENTER,
|
||||
DOUBLE_CLICK,
|
||||
ARROW
|
||||
}
|
||||
|
||||
enum class RefreshReviewsPlace {
|
||||
EMPTY_LIST,
|
||||
CONTEXT_MENU
|
||||
}
|
||||
|
||||
enum class ParticipantEditType {
|
||||
ADD,
|
||||
REMOVE
|
||||
}
|
||||
|
||||
enum class CommitsSelectionType {
|
||||
SINGLE,
|
||||
ALL,
|
||||
SUBSET_CONNECTED,
|
||||
SUBSET_SPLIT;
|
||||
|
||||
companion object {
|
||||
fun calculateSelectionType(newSelection: List<Int>, commitsCount: Int): CommitsSelectionType =
|
||||
when (newSelection.size) {
|
||||
commitsCount -> ALL
|
||||
1 -> SINGLE
|
||||
else -> calculateSubsetType(newSelection)
|
||||
}
|
||||
|
||||
private fun calculateSubsetType(newSelection: List<Int>): CommitsSelectionType {
|
||||
for (i in 1 until newSelection.size) {
|
||||
if (newSelection[i - 1] + 1 != newSelection[i]) {
|
||||
return SUBSET_SPLIT
|
||||
}
|
||||
}
|
||||
return SUBSET_CONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DetailsTabType {
|
||||
DETAILS,
|
||||
COMMITS
|
||||
}
|
||||
|
||||
enum class ReviewDiffPlace {
|
||||
EDITOR,
|
||||
DIALOG
|
||||
}
|
||||
|
||||
enum class LoaderType {
|
||||
GIT,
|
||||
SPACE
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.stats
|
||||
|
||||
import com.intellij.internal.statistic.beans.MetricEvent
|
||||
import com.intellij.internal.statistic.eventLog.EventLogGroup
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.service.fus.collectors.ProjectUsagesCollector
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.space.components.SpaceWorkspaceComponent
|
||||
import com.intellij.space.plugins.pipelines.services.SpaceKtsFileDetector
|
||||
import com.intellij.space.settings.CloneType
|
||||
import com.intellij.space.settings.SpaceSettings
|
||||
import com.intellij.space.stats.SpaceStatsCounterCollector.Companion.LOGIN_STATUS
|
||||
import com.intellij.space.vcs.SpaceProjectContext
|
||||
|
||||
internal class SpaceStatsStateCollector : ProjectUsagesCollector() {
|
||||
override fun getGroup() = GROUP
|
||||
|
||||
override fun getMetrics(project: Project): MutableSet<MetricEvent> {
|
||||
val result = HashSet<MetricEvent>()
|
||||
val workspace = SpaceWorkspaceComponent.getInstance()
|
||||
result.add(LOGIN_STATE.metric(SpaceStatsCounterCollector.LoginState.convert(workspace.loginState.value)))
|
||||
|
||||
val context = SpaceProjectContext.getInstance(project)
|
||||
result.add(ASSOCIATED_REPOS_STATE.metric(
|
||||
context.currentContext.isAssociatedWithSpaceRepository,
|
||||
context.probablyContainsSpaceRepo.value
|
||||
))
|
||||
|
||||
val automationScriptDetector = project.service<SpaceKtsFileDetector>()
|
||||
result.add(AUTOMATION_FILE_STATE.metric(automationScriptDetector.dslFile.value != null))
|
||||
|
||||
val cloneType = SpaceSettings.getInstance().cloneType
|
||||
result.add(GIT_CLONE_TYPE_STATE.metric(cloneType))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val GROUP = EventLogGroup("space.state", 1)
|
||||
|
||||
@JvmField
|
||||
val IS_ASSOCIATED_WITH_SPACE_REPO = EventFields.Boolean("is_associated_with_space_repo")
|
||||
|
||||
@JvmField
|
||||
val IS_PROBABLY_CONTAINS_SPACE_REPO = EventFields.Boolean("is_probably_contains_space_repo")
|
||||
|
||||
@JvmField
|
||||
val AUTOMATION_FILE_EXISTS = EventFields.Boolean("automation_file_exists")
|
||||
|
||||
@JvmField
|
||||
val GIT_CLONE_TYPE = EventFields.Enum<CloneType>("type")
|
||||
|
||||
@JvmField
|
||||
val LOGIN_STATE = GROUP.registerEvent("login_status", LOGIN_STATUS)
|
||||
|
||||
@JvmField
|
||||
val ASSOCIATED_REPOS_STATE = GROUP.registerEvent(
|
||||
"associated_repos_state",
|
||||
IS_ASSOCIATED_WITH_SPACE_REPO,
|
||||
IS_PROBABLY_CONTAINS_SPACE_REPO
|
||||
)
|
||||
|
||||
@JvmField
|
||||
val AUTOMATION_FILE_STATE = GROUP.registerEvent("automation_file_state", AUTOMATION_FILE_EXISTS)
|
||||
|
||||
@JvmField
|
||||
val GIT_CLONE_TYPE_STATE = GROUP.registerEvent("git_clone_type", GIT_CLONE_TYPE)
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.tools
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
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 com.intellij.space.plugins.pipelines.ui.SpaceToolWindowService
|
||||
import com.intellij.space.utils.computeSafe
|
||||
|
||||
class SpaceToolWindowFactory : ToolWindowFactory, DumbAware {
|
||||
|
||||
override fun shouldBeAvailable(project: Project): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||
val service = project.service<SpaceToolWindowService>()
|
||||
service.createToolWindowContent(toolWindow)
|
||||
}
|
||||
|
||||
override fun isDoNotActivateOnStart(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TOOL_WINDOW_ID = "Space Automation" // NON-NLS
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val Project.spaceKtsToolwindow: ToolWindow?
|
||||
get() = computeSafe {
|
||||
toolWindowManager.getToolWindow(SpaceToolWindowFactory.TOOL_WINDOW_ID)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.tools
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindowManager
|
||||
import com.intellij.openapi.wm.ex.ToolWindowManagerEx
|
||||
|
||||
val Project.toolWindowManager: ToolWindowManager get() = ToolWindowManager.getInstance(this)
|
||||
val Project.toolWindowManagerEx: ToolWindowManagerEx get() = ToolWindowManagerEx.getInstanceEx(this)
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.space.ui
|
||||
|
||||
import circlet.client.api.TD_MemberProfile
|
||||
import circlet.client.api.englishFullName
|
||||
import circlet.platform.api.TID
|
||||
import com.intellij.ui.scale.ScaleContext
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.ui.AvatarUtils.generateColoredAvatar
|
||||
import com.intellij.util.ui.ImageUtil
|
||||
import com.intellij.util.ui.JBImageIcon
|
||||
import com.intellij.util.ui.JBValue
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import runtime.Ui
|
||||
import java.awt.Component
|
||||
import java.awt.Graphics
|
||||
import javax.swing.Icon
|
||||
|
||||
class SpaceAvatarProvider(
|
||||
private val lifetime: Lifetime,
|
||||
private val component: Component,
|
||||
val iconSize: JBValue
|
||||
) {
|
||||
private val scaleContext = ScaleContext.create(component)
|
||||
|
||||
private val avatarIconsCache: MutableMap<TID, Icon> = HashMap()
|
||||
|
||||
private val imageLoader: SpaceImageLoader = SpaceImageLoader.getInstance()
|
||||
|
||||
@RequiresEdt
|
||||
fun getIcon(user: TD_MemberProfile): Icon {
|
||||
val iconSize = iconSize.get()
|
||||
|
||||
// so that icons are rescaled when any scale changes (be it font size or current DPI)
|
||||
if (scaleContext.update(ScaleContext.create(component))) {
|
||||
avatarIconsCache.clear()
|
||||
}
|
||||
|
||||
return avatarIconsCache.getOrPut(user.id) {
|
||||
val gradientIcon = resizeIcon(JBImageIcon(ImageUtil.createCircleImage(
|
||||
generateColoredAvatar(
|
||||
user.username,
|
||||
user.englishFullName()
|
||||
)
|
||||
)), iconSize)
|
||||
val icon = DelegatingIcon(gradientIcon)
|
||||
|
||||
user.smallAvatar?.let { tid ->
|
||||
launch(lifetime, Ui) {
|
||||
val image = imageLoader.loadImageAsync(tid)?.await()
|
||||
if (image != null) {
|
||||
val circleImage = ImageUtil.createCircleImage(image)
|
||||
icon.delegate = resizeIcon(JBImageIcon(circleImage), iconSize)
|
||||
component.repaint()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
icon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DelegatingIcon(var delegate: Icon) : Icon {
|
||||
override fun getIconHeight() = delegate.iconHeight
|
||||
override fun getIconWidth() = delegate.iconWidth
|
||||
override fun paintIcon(c: Component?, g: Graphics?, x: Int, y: Int) = delegate.paintIcon(c, g, x, y)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user