diff --git a/CHANGELOG.md b/CHANGELOG.md index a3398fe..cf10751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Consequently, the plugin now depends on [Svelte](https://plugins.jetbrains.com/plugin/12375-svelte) & [Vue](https://plugins.jetbrains.com/plugin/9442-vue-js) extensions and only works on WebStorm or IntelliJ IDEA Ultimate +- Overhaul support for Solid as both implementations diverged from shadcn/ui - Improve crash reporter to include more relevant information ### Fixed diff --git a/README.md b/README.md index d338a9c..4f0f573 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - Add support for Vue `typescript` option (transpiling TypeScript to JavaScript as well as in `*.vue` files) - See https://github.com/radix-vue/shadcn-vue/issues/378 +- Add support for React/Solid (UI) `tsx` option (transpiling TypeScript to JavaScript) ## Description @@ -16,7 +17,7 @@ Manage your shadcn/ui components in your project. Supports Svelte, React, Vue, a This plugin will help you manage your shadcn/ui components through a simple tool window. Add, remove, update them with a single click. -**This plugin will only work with an existing `components.json` file. Manually copied components will not be detected +**This plugin will only work with an existing `components.json` (or `ui.config.json` for Solid UI) file. Manually copied components will not be detected otherwise.** ## Features diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/SourceScanner.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/SourceScanner.kt index 16a22ac..05df32a 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/SourceScanner.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/SourceScanner.kt @@ -2,10 +2,7 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.Source -import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl.ReactSource -import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl.SolidSource -import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl.SvelteSource -import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl.VueSource +import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl.* import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import kotlinx.serialization.json.Json @@ -16,7 +13,8 @@ object SourceScanner { val log = logger() fun findShadcnImplementation(project: Project): Source<*>? { - return FileManager(project).getFileContentsAtPath("components.json")?.let { componentsJson -> + val fileManager = FileManager(project) + return fileManager.getFileContentsAtPath("components.json")?.let { componentsJson -> val contents = Json.parseToJsonElement(componentsJson).jsonObject val schema = contents["\$schema"]?.jsonPrimitive?.content ?: "" when { @@ -26,6 +24,8 @@ object SourceScanner { schema.contains("shadcn-solid") || schema.contains("solid-ui.com") -> SolidSource(project) else -> null } + } ?: fileManager.getFileContentsAtPath("ui.config.json")?.let { + SolidUISource(project) }.also { if (it == null) log.warn("No shadcn implementation found") else log.info("Found shadcn implementation: ${it.javaClass.name}") diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt index f458657..8a63813 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/helpers/FileManager.kt @@ -57,12 +57,12 @@ class FileManager(private val project: Project) { // because it works fine during local development. runReadAction { FilenameIndex.getVirtualFilesByName( - "components.json", + "package.json", GlobalSearchScope.projectScope(project) ) }.firstOrNull().also { if (it == null) { - log.warn("components.json not found with the workaround") + log.warn("package.json not found with the workaround") } }?.parent?.children?.filter { it.name.contains(name) diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/Source.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/Source.kt index 19644c4..9ee6ec7 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/Source.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/Source.kt @@ -23,8 +23,8 @@ import java.nio.file.NoSuchFileException abstract class Source(val project: Project, private val serializer: KSerializer) { private val log = logger>() - abstract var framework: String private var config: C? = null + protected val domain: String get() = URI(getLocalConfig().`$schema`).let { uri -> "${uri.scheme}://${uri.host}".also { @@ -32,26 +32,34 @@ abstract class Source(val project: Project, private val serializer: } } + protected open val configFile = "components.json" + + abstract val framework: String + + // Paths + protected abstract fun getURLPathForComponent(componentName: String): String + + protected abstract fun getLocalPathForComponents(): String + // Utility methods protected fun getLocalConfig(): C { - val file = "components.json" return config?.also { log.debug("Returning cached config") - } ?: FileManager(project).getFileContentsAtPath(file)?.let { - log.debug("Parsing config from $file") + } ?: FileManager(project).getFileContentsAtPath(configFile)?.let { + log.debug("Parsing config from $configFile") try { Json.decodeFromString(serializer, it).also { log.debug("Parsed config") } } catch (e: Exception) { - throw UnparseableConfigException(project, "Unable to parse $file", e) + throw UnparseableConfigException(project, configFile, e) } }?.also { if (config == null) { log.debug("Caching config") config = it } - } ?: throw NoSuchFileException("$file not found") + } ?: throw NoSuchFileException("$configFile not found") } protected abstract fun usesDirectoriesForComponents(): Boolean @@ -63,15 +71,12 @@ abstract class Source(val project: Project, private val serializer: protected abstract fun adaptFileToConfig(file: PsiFile) protected open fun fetchComponent(componentName: String): ComponentWithContents { - return RequestSender.sendRequest("$domain/registry/styles/${getLocalConfig().style}/$componentName.json") + return RequestSender.sendRequest("$domain/${getURLPathForComponent(componentName)}") .ok { Json.decodeFromString(it.body) } ?: throw Exception("Component $componentName not found") } - protected fun fetchColors(): JsonElement { - val baseColor = getLocalConfig().tailwind.baseColor - return RequestSender.sendRequest("$domain/registry/colors/$baseColor.json").ok { - Json.parseToJsonElement(it.body) - } ?: throw Exception("Colors not found") + protected open fun fetchColors(): JsonElement { + throw NoSuchMethodException("Not implemented") } protected open fun getRegistryDependencies(component: ComponentWithContents): List { @@ -94,7 +99,7 @@ abstract class Source(val project: Project, private val serializer: open fun getInstalledComponents(): List { return FileManager(project).getFileAtPath( - "${resolveAlias(getLocalConfig().aliases.components)}/ui" + "${resolveAlias(getLocalPathForComponents())}/ui" )?.children?.map { file -> if (file.isDirectory) file.name else file.name.substringBeforeLast(".") }?.sorted()?.also { @@ -105,7 +110,7 @@ abstract class Source(val project: Project, private val serializer: } open fun addComponent(componentName: String) { - val config = getLocalConfig() + val componentsPath = resolveAlias(getLocalPathForComponents()) // Install component val component = fetchComponent(componentName) val installedComponents = getInstalledComponents() @@ -118,7 +123,7 @@ abstract class Source(val project: Project, private val serializer: log.debug("Installing ${it.size} components: ${it.joinToString(", ") { component -> component.name }}") }.forEach { downloadedComponent -> val path = - "${resolveAlias(config.aliases.components)}/${component.type.substringAfterLast(":")}" + if (usesDirectoriesForComponents()) { + "${componentsPath}/${component.type.substringAfterLast(":")}" + if (usesDirectoriesForComponents()) { "/${downloadedComponent.name}" } else "" // Check for deprecated components @@ -208,7 +213,7 @@ abstract class Source(val project: Project, private val serializer: open fun isComponentUpToDate(componentName: String): Boolean { val remoteComponent = fetchComponent(componentName) val componentPath = - "${resolveAlias(getLocalConfig().aliases.components)}/${remoteComponent.type.substringAfterLast(":")}${ + "${resolveAlias(getLocalPathForComponents())}/${remoteComponent.type.substringAfterLast(":")}${ if (usesDirectoriesForComponents()) { "/${remoteComponent.name}" } else "" @@ -230,7 +235,7 @@ abstract class Source(val project: Project, private val serializer: open fun removeComponent(componentName: String) { val remoteComponent = fetchComponent(componentName) val componentsDir = - "${resolveAlias(getLocalConfig().aliases.components)}/${remoteComponent.type.substringAfterLast(":")}" + "${resolveAlias(getLocalPathForComponents())}/${remoteComponent.type.substringAfterLast(":")}" if (usesDirectoriesForComponents()) { FileManager(project).deleteFileAtPath("$componentsDir/${remoteComponent.name}") } else { diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/UnparseableConfigException.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/UnparseableConfigException.kt index 6064daa..a572d91 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/UnparseableConfigException.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/UnparseableConfigException.kt @@ -6,13 +6,13 @@ import com.intellij.openapi.project.Project class UnparseableConfigException( project: Project, - message: String? = null, + configName: String, cause: Throwable? = null -) : Exception(message, cause) { +) : Exception("Unable to parse $configName", cause) { init { NotificationManager(project).sendNotification( "Unparseable configuration file", - "Your components.json file could not be parsed.
Please check that it is a valid JSON and that it contains the correct fields.", + "Your ${configName} file could not be parsed.
Please check that it is a valid JSON and that it contains the correct fields.", NotificationType.ERROR ) } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/Config.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/Config.kt index 6a096ee..f9674ea 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/Config.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/Config.kt @@ -13,15 +13,10 @@ sealed class Config { */ abstract val `$schema`: String - /** - * The library's style used. - */ - abstract val style: String - /** * The Tailwind configuration. */ - abstract val tailwind: Tailwind + abstract val tailwind: Tailwind? /** * The aliases for the components and utils directories. @@ -42,26 +37,11 @@ sealed class Config { * The relative path of the Tailwind CSS file. */ abstract val css: String - - /** - * The library's base color. - */ - abstract val baseColor: String } /** * The aliases for the components and utils directories. */ @Serializable - sealed class Aliases { - /** - * The alias for the components' directory. - */ - abstract val components: String - - /** - * The alias for the utils directory. - */ - abstract val utils: String - } + sealed class Aliases } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/ReactConfig.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/ReactConfig.kt index 5ffd4cd..6d09e1a 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/ReactConfig.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/ReactConfig.kt @@ -5,17 +5,17 @@ import kotlinx.serialization.Serializable /** * A shadcn-svelte locally installed components.json file. * @param `$schema` The schema URL for the file. - * @param style The library style used. - * @param tailwind The Tailwind configuration. + * @param style The library's style used. * @param rsc Whether to support React Server Components. * @param tsx Whether to use TypeScript over JavaScript. + * @param tailwind The Tailwind configuration. * @param aliases The aliases for the components and utils directories. */ @Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117") @Serializable class ReactConfig( override val `$schema`: String = "https://ui.shadcn.com/schema.json", - override val style: String, + val style: String, val rsc: Boolean = false, val tsx: Boolean = true, override val tailwind: Tailwind, @@ -34,7 +34,7 @@ class ReactConfig( class Tailwind( override val config: String, override val css: String, - override val baseColor: String, + val baseColor: String, val cssVariables: Boolean = true, val prefix: String = "" ) : Config.Tailwind() @@ -47,8 +47,8 @@ class ReactConfig( */ @Serializable class Aliases( - override val components: String, - override val utils: String, + val components: String, + val utils: String, val ui: String? = null ) : Config.Aliases() } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SolidConfig.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SolidConfig.kt index 697317e..775dc28 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SolidConfig.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SolidConfig.kt @@ -2,18 +2,66 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config import kotlinx.serialization.Serializable +/** + * A shadcn-svelte locally installed components.json file. + * @param `$schema` The schema URL for the file. + * @param tailwind The Tailwind configuration. + * @param uno The UnoCSS configuration. + * @param aliases The aliases for the components and utils directories. + */ @Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117") @Serializable class SolidConfig( override val `$schema`: String = "", - override val style: String, - override val tailwind: VueConfig.Tailwind, + override val tailwind: Tailwind? = null, + val uno: Uno? = null, override val aliases: Aliases ) : Config() { + /** + * The Tailwind configuration. + * @param config The relative path to the Tailwind config file. + * @param css The relative path of the Tailwind CSS file. + * @param baseColor The library's base color. + * @param cssVariables Whether to use CSS variables or utility classes. + * @param prefix The prefix to use for utility classes. + */ + @Serializable + class Tailwind( + override val config: String, + override val css: String, + val baseColor: String, + val cssVariables: Boolean = true, + val prefix: String = "" + ) : Config.Tailwind() + + /** + * The UnoCSS configuration. + * @param config The relative path to the UnoCSS config file. + * @param css The relative path of the UnoCSS file. + * @param baseColor The library's base color. + * @param cssVariables Whether to use CSS variables or utility classes. + * @param prefix The prefix to use for utility classes. + */ + @Serializable + class Uno( + override val config: String, + override val css: String, + val baseColor: String, + val cssVariables: Boolean = true, + val prefix: String = "" + ) : Config.Tailwind() + + /** + * The aliases for the components and utils directories. + * @param components The alias for the components' directory. + * @param utils The alias for the utils directory. + * @param ui The alias for the UI directory. + */ @Serializable class Aliases( - override val components: String, - override val utils: String + val components: String, + val utils: String, + val ui: String? = null ) : Config.Aliases() } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SolidUIConfig.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SolidUIConfig.kt new file mode 100644 index 0000000..55d5b56 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SolidUIConfig.kt @@ -0,0 +1,42 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config + +import kotlinx.serialization.Serializable + +/** + * A shadcn-svelte locally installed components.json file. + * @param `$schema` The schema URL for the file. + * @param tsx Whether to use TypeScript over JavaScript. + * @param componentDir The components' directory. + * @param tailwind The Tailwind configuration. + * @param aliases The aliases for the components and utils directories. + */ +@Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117") +@Serializable +class SolidUIConfig( + override val `$schema`: String = "", + val tsx: Boolean, + val componentDir: String, + override val tailwind: Tailwind, + override val aliases: Aliases +) : Config() { + + /** + * The Tailwind configuration. + * @param config The relative path to the Tailwind config file. + * @param css The relative path of the Tailwind CSS file. + */ + @Serializable + class Tailwind( + override val config: String, + override val css: String + ) : Config.Tailwind() + + /** + * The aliases for the components and utils directories. + * @param path The import alias for the `src` directory. + */ + @Serializable + class Aliases( + val path: String, + ) : Config.Aliases() +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SvelteConfig.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SvelteConfig.kt index 3262850..b133b6b 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SvelteConfig.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/SvelteConfig.kt @@ -2,26 +2,45 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config import kotlinx.serialization.Serializable +/** + * A shadcn-svelte locally installed components.json file. + * @param `$schema` The schema URL for the file. + * @param style The library's style used. + * @param tailwind The Tailwind configuration. + * @param aliases The aliases for the components and utils directories. + * @param typescript Whether to use TypeScript over JavaScript. + */ @Suppress("PROVIDED_RUNTIME_TOO_LOW", "kotlin:S117") @Serializable class SvelteConfig( override val `$schema`: String = "https://shadcn-svelte.com/schema.json", - override val style: String, + val style: String, override val tailwind: Tailwind, override val aliases: Aliases, val typescript: Boolean = true ) : Config() { + /** + * The Tailwind configuration. + * @param config The relative path to the Tailwind config file. + * @param css The relative path of the Tailwind CSS file. + * @param baseColor The library's base color. + */ @Serializable class Tailwind( override val config: String, override val css: String, - override val baseColor: String + val baseColor: String ) : Config.Tailwind() + /** + * The aliases for the components and utils directories. + * @param components The alias for the components' directory. + * @param utils The alias for the utils directory. + */ @Serializable class Aliases( - override val components: String, - override val utils: String + val components: String, + val utils: String ) : Config.Aliases() } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/VueConfig.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/VueConfig.kt index 8eca708..2db5919 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/VueConfig.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/config/VueConfig.kt @@ -16,7 +16,7 @@ import kotlinx.serialization.Serializable @Serializable class VueConfig( override val `$schema`: String = "https://shadcn-vue.com/schema.json", - override val style: String, + val style: String, val typescript: Boolean = true, val tsConfigPath: String = "./tsconfig.json", override val tailwind: Tailwind, @@ -30,12 +30,13 @@ class VueConfig( * @param css The relative path of the Tailwind CSS file. * @param baseColor The library's base color. * @param cssVariables Whether to use CSS variables instead of Tailwind utility classes. + * @param prefix The prefix to use for utility classes. */ @Serializable open class Tailwind( override val config: String, override val css: String, - override val baseColor: String, + val baseColor: String, val cssVariables: Boolean = true, val prefix: String = "" ) : Config.Tailwind() @@ -59,10 +60,16 @@ class VueConfig( ASTRO } + /** + * The aliases for the components and utils directories. + * @param components The alias for the components' directory. + * @param utils The alias for the utils directory. + * @param ui The alias for UI components. + */ @Serializable class Aliases( - override val components: String, - override val utils: String, + val components: String, + val utils: String, val ui: String? = null ) : Config.Aliases() } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/ReactSource.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/ReactSource.kt index eccf62d..fabd578 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/ReactSource.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/ReactSource.kt @@ -1,33 +1,40 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager +import com.github.warningimhack3r.intellijshadcnplugin.backend.http.RequestSender import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.Source import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config.ReactConfig import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.replacement.ImportsPackagesReplacementVisitor import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.replacement.JSXClassReplacementVisitor import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.replacement.ReactDirectiveRemovalVisitor +import com.github.warningimhack3r.intellijshadcnplugin.notifications.NotificationManager +import com.intellij.notification.NotificationType import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* import java.nio.file.NoSuchFileException class ReactSource(project: Project) : Source(project, ReactConfig.serializer()) { companion object { private val log = logger() + private var isJsUnsupportedNotified = false } override var framework = "React" + override fun getURLPathForComponent(componentName: String) = + "registry/styles/${getLocalConfig().style}/$componentName.json" + + override fun getLocalPathForComponents() = getLocalConfig().aliases.components + override fun usesDirectoriesForComponents() = false override fun resolveAlias(alias: String): String { - if (!alias.startsWith("$") && !alias.startsWith("@")) return alias.also { - log.debug("Alias $alias does not start with $ or @, returning it as-is") + if (!alias.startsWith("$") && !alias.startsWith("@") && !alias.startsWith("~")) { + log.warn("Alias $alias does not start with $, @ or ~, returning it as-is") + return alias } val configFile = if (getLocalConfig().tsx) "tsconfig.json" else "jsconfig.json" val tsConfig = FileManager(project).getFileContentsAtPath(configFile) @@ -55,6 +62,18 @@ class ReactSource(project: Project) : Source(project, ReactConfig.s override fun adaptFileToConfig(file: PsiFile) { val config = getLocalConfig() + if (!config.tsx) { + if (!isJsUnsupportedNotified) { + NotificationManager(project).sendNotification( + "TypeScript option for React", + "You have TypeScript disabled in your shadcn/ui config. This feature is not supported yet. Please install/update your components with the CLI for now.", + NotificationType.WARNING + ) + isJsUnsupportedNotified = true + } + // TODO: detype React file + } + val importsPackagesReplacementVisitor = ImportsPackagesReplacementVisitor(project) runReadAction { file.accept(importsPackagesReplacementVisitor) } importsPackagesReplacementVisitor.replaceImports visitor@{ `package` -> @@ -114,4 +133,11 @@ class ReactSource(project: Project) : Source(project, ReactConfig.s } else "$modifier$twPrefix$className" } } + + override fun fetchColors(): JsonElement { + val baseColor = getLocalConfig().tailwind.baseColor + return RequestSender.sendRequest("$domain/registry/colors/$baseColor.json").ok { + Json.parseToJsonElement(it.body) + } ?: throw Exception("Colors not found") + } } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SolidSource.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SolidSource.kt index a206027..0c0bb9d 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SolidSource.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SolidSource.kt @@ -1,6 +1,7 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager +import com.github.warningimhack3r.intellijshadcnplugin.backend.http.RequestSender import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.Source import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config.SolidConfig import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.replacement.ImportsPackagesReplacementVisitor @@ -9,10 +10,7 @@ import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* import java.nio.file.NoSuchFileException class SolidSource(project: Project) : Source(project, SolidConfig.serializer()) { @@ -22,11 +20,26 @@ class SolidSource(project: Project) : Source(project, SolidConfig.s override var framework = "Solid" + private fun cssFrameworkName(): String { + val config = getLocalConfig() + return when { + config.tailwind != null -> "tailwindcss" + config.uno != null -> "unocss" + else -> throw Exception("Framework not found. Is your config valid?") + } + } + + override fun getURLPathForComponent(componentName: String) = + "registry/frameworks/${cssFrameworkName()}/$componentName.json" + + override fun getLocalPathForComponents() = getLocalConfig().aliases.components + override fun usesDirectoriesForComponents() = false override fun resolveAlias(alias: String): String { - if (!alias.startsWith("$") && !alias.startsWith("@")) return alias.also { - log.debug("Alias $alias does not start with $ or @, returning it as-is") + if (!alias.startsWith("$") && !alias.startsWith("@") && !alias.startsWith("~")) { + log.warn("Alias $alias does not start with $, @ or ~, returning it as-is") + return alias } val configFile = "tsconfig.json" val tsConfig = FileManager(project).getFileContentsAtPath(configFile) @@ -55,35 +68,46 @@ class SolidSource(project: Project) : Source(project, SolidConfig.s `package` } - if (!config.tailwind.cssVariables) { - val prefixesToReplace = listOf("bg-", "text-", "border-", "ring-offset-", "ring-") + val prefixesToReplace = listOf("bg-", "text-", "border-", "ring-offset-", "ring-") - val inlineColors = fetchColors().jsonObject["inlineColors"]?.jsonObject - ?: throw Exception("Inline colors not found") - val lightColors = inlineColors.jsonObject["light"]?.jsonObject?.let { lightColors -> - lightColors.keys.associateWith { lightColors[it]?.jsonPrimitive?.content ?: "" } - } ?: emptyMap() - val darkColors = inlineColors.jsonObject["dark"]?.jsonObject?.let { darkColors -> - darkColors.keys.associateWith { darkColors[it]?.jsonPrimitive?.content ?: "" } - } ?: emptyMap() + val inlineColors = fetchColors().jsonObject["inlineColors"]?.jsonObject + ?: throw Exception("Inline colors not found") + val lightColors = inlineColors.jsonObject["light"]?.jsonObject?.let { lightColors -> + lightColors.keys.associateWith { lightColors[it]?.jsonPrimitive?.content ?: "" } + } ?: emptyMap() + val darkColors = inlineColors.jsonObject["dark"]?.jsonObject?.let { darkColors -> + darkColors.keys.associateWith { darkColors[it]?.jsonPrimitive?.content ?: "" } + } ?: emptyMap() - val replacementVisitor = JSXClassReplacementVisitor(project) - runReadAction { file.accept(replacementVisitor) } - replacementVisitor.replaceClasses replacer@{ `class` -> - val modifier = if (`class`.contains(":")) `class`.substringBeforeLast(":") + ":" else "" - val className = `class`.substringAfterLast(":") - if (className == "border") { - return@replacer "${modifier}border ${modifier}border-border" - } - val prefix = prefixesToReplace.find { className.startsWith(it) } - ?: return@replacer "$modifier$className" - val color = className.substringAfter(prefix) - val lightColor = lightColors[color] - val darkColor = darkColors[color] - if (lightColor != null && darkColor != null) { - "$modifier$prefix$lightColor dark:$modifier$prefix$darkColor" - } else "$modifier$className" + val replacementVisitor = JSXClassReplacementVisitor(project) + runReadAction { file.accept(replacementVisitor) } + replacementVisitor.replaceClasses replacer@{ `class` -> + val modifier = if (`class`.contains(":")) `class`.substringBeforeLast(":") + ":" else "" + val className = `class`.substringAfterLast(":") + val twPrefix = config.tailwind?.prefix ?: config.uno?.prefix ?: "" + if (config.tailwind?.cssVariables == true || config.uno?.cssVariables == true) { + return@replacer "$modifier$twPrefix$className" + } + if (className == "border") { + return@replacer "$modifier${twPrefix}border $modifier${twPrefix}border-border" } + val prefix = prefixesToReplace.find { className.startsWith(it) } + ?: return@replacer "$modifier$twPrefix$className" + val color = className.substringAfter(prefix) + val lightColor = lightColors[color] + val darkColor = darkColors[color] + if (lightColor != null && darkColor != null) { + "$modifier$twPrefix$prefix$lightColor dark:$modifier$twPrefix$prefix$darkColor" + } else "$modifier$twPrefix$className" } } + + override fun fetchColors(): JsonElement { + val config = getLocalConfig() + val baseColor = config.tailwind?.baseColor ?: config.uno?.baseColor + ?: throw Exception("Base color not found. Is your config valid?") + return RequestSender.sendRequest("$domain/registry/colors/${cssFrameworkName()}/$baseColor.json").ok { + Json.parseToJsonElement(it.body) + } ?: throw Exception("Colors not found") + } } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SolidUISource.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SolidUISource.kt new file mode 100644 index 0000000..8eca501 --- /dev/null +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SolidUISource.kt @@ -0,0 +1,87 @@ +package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl + +import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager +import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.Source +import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config.SolidUIConfig +import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.replacement.ImportsPackagesReplacementVisitor +import com.github.warningimhack3r.intellijshadcnplugin.notifications.NotificationManager +import com.intellij.notification.NotificationType +import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import java.nio.file.NoSuchFileException + +class SolidUISource(project: Project) : Source(project, SolidUIConfig.serializer()) { + companion object { + private val log = logger() + private var isJsUnsupportedNotified = false + } + + override var framework = "Solid" + + override val configFile = "ui.config.json" + + override fun getURLPathForComponent(componentName: String) = "registry/ui/$componentName.json" + + override fun getLocalPathForComponents() = getLocalConfig().componentDir + + override fun usesDirectoriesForComponents() = false + + override fun resolveAlias(alias: String): String { + if (!alias.startsWith("$") && !alias.startsWith("@") && !alias.startsWith("~")) { + log.warn("Alias $alias does not start with $, @ or ~, returning it as-is") + return alias + } + val configFile = if (getLocalConfig().tsx) "tsconfig.json" else "jsconfig.json" + val tsConfig = FileManager(project).getFileContentsAtPath(configFile) + ?: throw NoSuchFileException("$configFile not found") + val aliasPath = Json.parseToJsonElement(tsConfig) + .jsonObject["compilerOptions"] + ?.jsonObject?.get("paths") + ?.jsonObject?.get("${alias.substringBefore("/")}/*") + ?.jsonArray?.get(0) + ?.jsonPrimitive?.content ?: throw Exception("Cannot find alias $alias in $tsConfig") + return aliasPath.replace(Regex("^\\.+/"), "") + .replace(Regex("\\*$"), alias.substringAfter("/")).also { + log.debug("Resolved alias $alias to $it") + } + } + + override fun adaptFileExtensionToConfig(extension: String): String { + return if (!getLocalConfig().tsx) { + extension + .replace(Regex("\\.tsx$"), ".ts") + .replace(Regex("\\.jsx$"), ".js") + } else extension + } + + override fun adaptFileToConfig(file: PsiFile) { + val config = getLocalConfig() + + if (!config.tsx) { + if (!isJsUnsupportedNotified) { + NotificationManager(project).sendNotification( + "TypeScript option for Solid", + "You have TypeScript disabled in your shadcn/ui config. This feature is not supported yet. Please install/update your components with the CLI for now.", + NotificationType.WARNING + ) + isJsUnsupportedNotified = true + } + // TODO: detype Solid file + } + + val importsPackagesReplacementVisitor = ImportsPackagesReplacementVisitor(project) + runReadAction { file.accept(importsPackagesReplacementVisitor) } + importsPackagesReplacementVisitor.replaceImports visitor@{ `package` -> + val pathAlias = config.aliases.path.replace("/*", "") + return@visitor `package` + .replace("~/registry/ui", "$pathAlias/components/ui") + .replace("~/lib/utils", "$pathAlias/lib/utils") + } + } +} diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SvelteSource.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SvelteSource.kt index 7eb9950..02a5b99 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SvelteSource.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/SvelteSource.kt @@ -15,10 +15,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* import java.nio.file.NoSuchFileException class SvelteSource(project: Project) : Source(project, SvelteConfig.serializer()) { @@ -28,11 +25,17 @@ class SvelteSource(project: Project) : Source(project, SvelteConfi override var framework = "Svelte" + override fun getURLPathForComponent(componentName: String) = + "registry/styles/${getLocalConfig().style}/$componentName.json" + + override fun getLocalPathForComponents() = getLocalConfig().aliases.components + override fun usesDirectoriesForComponents() = true override fun resolveAlias(alias: String): String { - if (!alias.startsWith("$") && !alias.startsWith("@")) return alias.also { - log.debug("Alias $alias does not start with $ or @, returning it as-is") + if (!alias.startsWith("$") && !alias.startsWith("@") && !alias.startsWith("~")) { + log.warn("Alias $alias does not start with $, @ or ~, returning it as-is") + return alias } val usesKit = DependencyManager(project).isDependencyInstalled("@sveltejs/kit") val tsConfigName = if (getLocalConfig().typescript) "tsconfig.json" else "jsconfig.json" @@ -94,4 +97,11 @@ class SvelteSource(project: Project) : Source(project, SvelteConfi .replace("\$lib/utils", config.aliases.utils) } } + + override fun fetchColors(): JsonElement { + val baseColor = getLocalConfig().tailwind.baseColor + return RequestSender.sendRequest("$domain/registry/colors/$baseColor.json").ok { + Json.parseToJsonElement(it.body) + } ?: throw Exception("Colors not found") + } } diff --git a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/VueSource.kt b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/VueSource.kt index 519e886..76361e0 100644 --- a/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/VueSource.kt +++ b/src/main/kotlin/com/github/warningimhack3r/intellijshadcnplugin/backend/sources/impl/VueSource.kt @@ -1,6 +1,7 @@ package com.github.warningimhack3r.intellijshadcnplugin.backend.sources.impl import com.github.warningimhack3r.intellijshadcnplugin.backend.helpers.FileManager +import com.github.warningimhack3r.intellijshadcnplugin.backend.http.RequestSender import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.Source import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.config.VueConfig import com.github.warningimhack3r.intellijshadcnplugin.backend.sources.remote.ComponentWithContents @@ -12,25 +13,28 @@ import com.intellij.openapi.application.runReadAction import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.* import java.nio.file.NoSuchFileException class VueSource(project: Project) : Source(project, VueConfig.serializer()) { companion object { private val log = logger() - private var jsVueNotified = false + private var isJsUnsupportedNotified = false } override var framework = "Vue" + override fun getURLPathForComponent(componentName: String) = + "registry/styles/${getLocalConfig().style}/$componentName.json" + + override fun getLocalPathForComponents() = getLocalConfig().aliases.components + override fun usesDirectoriesForComponents() = true override fun resolveAlias(alias: String): String { - if (!alias.startsWith("$") && !alias.startsWith("@")) return alias.also { - log.debug("Alias $alias does not start with $ or @, returning it as-is") + if (!alias.startsWith("$") && !alias.startsWith("@") && !alias.startsWith("~")) { + log.warn("Alias $alias does not start with $, @ or ~, returning it as-is") + return alias } fun resolvePath(configFile: String): String? { @@ -75,13 +79,13 @@ class VueSource(project: Project) : Source(project, VueConfig.seriali override fun adaptFileToConfig(file: PsiFile) { val config = getLocalConfig() if (!config.typescript) { - if (!jsVueNotified) { + if (!isJsUnsupportedNotified) { NotificationManager(project).sendNotification( "TypeScript option for Vue", "You have TypeScript disabled in your shadcn/ui config. This feature is not supported yet. Please install/update your components with the CLI for now.", NotificationType.WARNING ) - jsVueNotified = true + isJsUnsupportedNotified = true } // TODO: detype Vue file } @@ -138,6 +142,13 @@ class VueSource(project: Project) : Source(project, VueConfig.seriali } } + override fun fetchColors(): JsonElement { + val baseColor = getLocalConfig().tailwind.baseColor + return RequestSender.sendRequest("$domain/registry/colors/$baseColor.json").ok { + Json.parseToJsonElement(it.body) + } ?: throw Exception("Colors not found") + } + override fun getRegistryDependencies(component: ComponentWithContents): List { return super.getRegistryDependencies(component.copy( registryDependencies = component.registryDependencies.filterNot { it == "utils" }