Skip to content

Commit

Permalink
Overhaul Solid implementations support (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
WarningImHack3r authored May 20, 2024
1 parent e7ca900 commit 000c765
Show file tree
Hide file tree
Showing 17 changed files with 382 additions and 121 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,7 +13,8 @@ object SourceScanner {
val log = logger<SourceScanner>()

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 {
Expand All @@ -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}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,43 @@ import java.nio.file.NoSuchFileException

abstract class Source<C : Config>(val project: Project, private val serializer: KSerializer<C>) {
private val log = logger<Source<C>>()
abstract var framework: String
private var config: C? = null

protected val domain: String
get() = URI(getLocalConfig().`$schema`).let { uri ->
"${uri.scheme}://${uri.host}".also {
log.debug("Parsed domain: $it")
}
}

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
Expand All @@ -63,15 +71,12 @@ abstract class Source<C : Config>(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<ComponentWithContents> {
Expand All @@ -94,7 +99,7 @@ abstract class Source<C : Config>(val project: Project, private val serializer:

open fun getInstalledComponents(): List<String> {
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 {
Expand All @@ -105,7 +110,7 @@ abstract class Source<C : Config>(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()
Expand All @@ -118,7 +123,7 @@ abstract class Source<C : Config>(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
Expand Down Expand Up @@ -208,7 +213,7 @@ abstract class Source<C : Config>(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 ""
Expand All @@ -230,7 +235,7 @@ abstract class Source<C : Config>(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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>components.json</code> file could not be parsed.<br />Please check that it is a valid JSON and that it contains the correct fields.",
"Your <code>${configName}</code> file could not be parsed.<br />Please check that it is a valid JSON and that it contains the correct fields.",
NotificationType.ERROR
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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()
Expand All @@ -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()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Loading

0 comments on commit 000c765

Please sign in to comment.