diff --git a/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt b/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt index 0b48ab3..b0b913b 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/HiscoresView.kt @@ -64,8 +64,6 @@ object Constants { val SKILL_DISPLAY_ORDER = arrayOf(0,3,14,2,16,13,1,15,10,4,17,7,5,12,11,6,9,8,20,18,19,22,21,23) } -var text: String = "" - object HiscoresView { const val VIEW_NAME = "HISCORE_SEARCH_VIEW" @@ -73,6 +71,7 @@ object HiscoresView { class CustomSearchField(private val hiscoresPanel: JPanel) : Canvas() { private var cursorVisible: Boolean = true + private var text: String = "" private val gson = Gson() private val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(Constants.MAG_SPRITE)) diff --git a/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/CustomSearchField.kt b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/CustomSearchField.kt new file mode 100644 index 0000000..7601413 --- /dev/null +++ b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/CustomSearchField.kt @@ -0,0 +1,156 @@ +package KondoKit.ReflectiveEditorComponents + +import KondoKit.ImageCanvas +import KondoKit.plugin +import KondoKit.plugin.Companion.WIDGET_COLOR +import KondoKit.plugin.Companion.secondaryColor +import plugin.api.API +import java.awt.* +import java.awt.datatransfer.DataFlavor +import java.awt.event.ActionEvent +import java.awt.event.ActionListener +import java.awt.event.KeyAdapter +import java.awt.event.KeyEvent +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.awt.image.BufferedImage +import javax.imageio.ImageIO +import javax.swing.* + +class CustomSearchField(private val parentPanel: JPanel, private val onSearch: (String) -> Unit) : Canvas() { + private var cursorVisible: Boolean = true + private var text: String = "" + + private val bufferedImageSprite = getBufferedImageFromSprite(API.GetSprite(1423)) // MAG_SPRITE + private val imageCanvas = bufferedImageSprite.let { + ImageCanvas(it).apply { + preferredSize = Dimension(12, 12) // ICON_DIMENSION_SMALL + size = preferredSize + minimumSize = preferredSize + maximumSize = preferredSize + fillColor = WIDGET_COLOR + } + } + + init { + preferredSize = Dimension(230, 30) // SEARCH_FIELD_DIMENSION + background = WIDGET_COLOR // COLOR_BACKGROUND_DARK + foreground = secondaryColor // COLOR_FOREGROUND_LIGHT + font = Font("Arial", Font.PLAIN, 14) // FONT_ARIAL_PLAIN_14 + minimumSize = preferredSize + maximumSize = preferredSize + + addKeyListener(object : KeyAdapter() { + override fun keyTyped(e: KeyEvent) { + // Prevent null character from being typed on Ctrl+A & Ctrl+V + if (e.isControlDown && (e.keyChar == '\u0001' || e.keyChar == '\u0016')) { + e.consume() + return + } + if (e.keyChar == '\b') { + if (text.isNotEmpty()) { + text = text.dropLast(1) + } + } else if (e.keyChar == '\n') { + onSearch(text) + } else { + text += e.keyChar + } + SwingUtilities.invokeLater { + repaint() + } + } + override fun keyPressed(e: KeyEvent) { + if (e.isControlDown) { + when (e.keyCode) { + KeyEvent.VK_A -> { + text = "" + SwingUtilities.invokeLater { + repaint() + } + } + KeyEvent.VK_V -> { + val clipboard = Toolkit.getDefaultToolkit().systemClipboard + val pasteText = clipboard.getData(DataFlavor.stringFlavor) as String + text += pasteText + SwingUtilities.invokeLater { + repaint() + } + } + } + } + } + }) + + addMouseListener(object : MouseAdapter() { + override fun mouseClicked(e: MouseEvent) { + if (e.x > width - 20 && e.y < 20) { + text = "" + onSearch(text) + SwingUtilities.invokeLater { + repaint() + } + } + } + }) + + javax.swing.Timer(500, ActionListener { _ -> + cursorVisible = !cursorVisible + if (plugin.StateManager.focusedView == "REFLECTIVE_EDITOR_VIEW") + SwingUtilities.invokeLater { + repaint() + } + }).start() + } + + override fun paint(g: Graphics) { + super.paint(g) + g.color = foreground + g.font = font + + val fm = g.fontMetrics + val cursorX = fm.stringWidth(text) + 30 + + // Draw magnifying glass icon + imageCanvas.let { canvas -> + val imgG = g.create(5, 5, canvas.width, canvas.height) + canvas.paint(imgG) + imgG.dispose() + } + + // Use a local copy of the text to avoid threading issues + val currentText = text + + // Draw placeholder text if field is empty, otherwise draw actual text + if (currentText == "") { + g.color = Color.GRAY // Use a lighter color for placeholder text + g.drawString("Search plugins...", 30, 20) + } else { + g.color = foreground // Use normal color for actual text + g.drawString(currentText, 30, 20) + } + + if (cursorVisible && hasFocus()) { + g.color = foreground + g.drawLine(cursorX, 5, cursorX, 25) + } + + // Only draw the "x" button if there's text + if (currentText != "") { + g.color = Color.RED + g.drawString("x", width - 20, 20) + } + } + + private fun getBufferedImageFromSprite(sprite: Any?): BufferedImage { + // This is a simplified version - you might need to adjust based on your actual implementation + // For now, let's just return a placeholder image + val image = BufferedImage(12, 12, BufferedImage.TYPE_INT_ARGB) + val g2d = image.createGraphics() + g2d.color = Color.GRAY + g2d.fillOval(2, 2, 8, 8) + g2d.drawLine(8, 8, 11, 11) + g2d.dispose() + return image + } +} \ No newline at end of file diff --git a/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/GitLabDataModels.kt b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/GitLabDataModels.kt new file mode 100644 index 0000000..340afd0 --- /dev/null +++ b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/GitLabDataModels.kt @@ -0,0 +1,14 @@ +package KondoKit.ReflectiveEditorComponents + +data class GitLabPlugin( + val id: String, + val path: String, + val pluginProperties: PluginProperties?, + val pluginError: String? +) + +data class PluginProperties( + val author: String, + val version: String, + val description: String +) \ No newline at end of file diff --git a/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/GitLabPluginFetcher.kt b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/GitLabPluginFetcher.kt new file mode 100644 index 0000000..e67dd3e --- /dev/null +++ b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorComponents/GitLabPluginFetcher.kt @@ -0,0 +1,117 @@ +package KondoKit.ReflectiveEditorComponents + +import KondoKit.ReflectiveEditorView +import com.google.gson.Gson +import com.google.gson.JsonObject +import java.awt.EventQueue +import java.io.BufferedReader +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL +import javax.swing.SwingUtilities + +object GitLabPluginFetcher { + private const val GITLAB_ACCESS_TOKEN = "glpat-dE2Cs2e4b32-H7c9oGuS" + private const val GITLAB_PROJECT_ID = "38297322" + private const val GITLAB_BRANCH = "master" + + // Function to fetch plugins from GitLab + fun fetchGitLabPlugins(onComplete: (List) -> Unit) { + Thread { + try { + val plugins = mutableListOf() + val apiUrl = "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/repository/tree?ref=${GITLAB_BRANCH}" + + // Create URL connection + val url = URL(apiUrl) + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.setRequestProperty("PRIVATE-TOKEN", GITLAB_ACCESS_TOKEN) + + // Read response + val responseCode = connection.responseCode + if (responseCode == HttpURLConnection.HTTP_OK) { + val reader = BufferedReader(InputStreamReader(connection.inputStream)) + val response = reader.use { it.readText() } + + // Parse JSON response + val gson = Gson() + val treeItems = gson.fromJson(response, Array::class.java) + + // Filter for directories (trees) + for (item in treeItems) { + val jsonObject = item as JsonObject + if (jsonObject["type"].asString == "tree") { + val folderId = jsonObject["id"].asString + val folderPath = jsonObject["path"].asString + + try { + val pluginProperties = fetchPluginProperties(folderPath) + plugins.add(GitLabPlugin(folderId, folderPath, pluginProperties, null)) + } catch (e: Exception) { + plugins.add(GitLabPlugin(folderId, folderPath, null, "Error fetching plugin.properties")) + } + } + } + } + + // Update on UI thread + SwingUtilities.invokeLater { + onComplete(plugins) + } + } catch (e: Exception) { + e.printStackTrace() + SwingUtilities.invokeLater { + onComplete(emptyList()) + } + } + }.start() + } + + // Function to fetch plugin.properties from a specific folder + private fun fetchPluginProperties(folderPath: String): PluginProperties { + val pluginFilePath = "$folderPath/plugin.properties" + val pluginUrl = "https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/repository/files/${pluginFilePath.replace("/", "%2F")}/raw?ref=${GITLAB_BRANCH}" + + // Create URL connection + val url = URL(pluginUrl) + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.setRequestProperty("PRIVATE-TOKEN", GITLAB_ACCESS_TOKEN) + + // Read response + val responseCode = connection.responseCode + if (responseCode == HttpURLConnection.HTTP_OK) { + val reader = BufferedReader(InputStreamReader(connection.inputStream)) + val rawContent = reader.use { it.readText() } + + return parseProperties(rawContent) + } else { + throw Exception("Plugin file not found for folder: $folderPath") + } + } + + // Function to parse plugin.properties content + private fun parseProperties(content: String): PluginProperties { + val lines = content.split("\n") + var author = "Unknown" + var version = "Unknown" + var description = "No description available" + + for (line in lines) { + val parts = line.split("=") + if (parts.size == 2) { + val key = parts[0].trim() + val value = parts[1].replace("\"", "").replace("'", "").trim() + + when { + key.startsWith("AUTHOR", ignoreCase = true) -> author = value + key.startsWith("VERSION", ignoreCase = true) -> version = value + key.startsWith("DESCRIPTION", ignoreCase = true) -> description = value + } + } + } + + return PluginProperties(author, version, description) + } +} \ No newline at end of file diff --git a/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt index cb5e5b2..e1930dd 100644 --- a/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt +++ b/plugin-playground/src/main/kotlin/KondoKit/ReflectiveEditorView.kt @@ -2,6 +2,10 @@ package KondoKit import KondoKit.Helpers.convertValue import KondoKit.Helpers.showToast +import KondoKit.ReflectiveEditorComponents.CustomSearchField +import KondoKit.ReflectiveEditorComponents.GitLabPlugin +import KondoKit.ReflectiveEditorComponents.GitLabPluginFetcher +import KondoKit.ReflectiveEditorComponents.PluginProperties import KondoKit.plugin.Companion.TITLE_BAR_COLOR import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR @@ -12,8 +16,8 @@ import plugin.PluginInfo import plugin.PluginRepository import rt4.GlobalJsonConfig import java.awt.* -import java.awt.event.MouseAdapter -import java.awt.event.MouseEvent +import java.awt.datatransfer.DataFlavor +import java.awt.event.* import java.awt.image.BufferedImage import java.io.File import java.util.* @@ -39,6 +43,14 @@ object ReflectiveEditorView { const val PLUGIN_DETAIL_VIEW = "PLUGIN_DETAIL" val pluginsDirectory: File = File(GlobalJsonConfig.instance.pluginsFolder) + // GitLab API configuration + private const val GITLAB_ACCESS_TOKEN = "glpat-dE2Cs2e4b32-H7c9oGuS" + private const val GITLAB_PROJECT_ID = "38297322" + private const val GITLAB_BRANCH = "master" + + // Store fetched GitLab plugins + private var gitLabPlugins: List = listOf() + // Store the cog icon to be reused private var cogIcon: Icon? = null @@ -61,6 +73,9 @@ object ReflectiveEditorView { private var currentPluginInfo: PluginInfo? = null private var currentPlugin: Plugin? = null + // Search text for filtering plugins + private var pluginSearchText: String = "" + fun createReflectiveEditorView() { // Load the cog icon once loadCogIcon() @@ -89,6 +104,12 @@ object ReflectiveEditorView { // Show the plugin list view by default cardLayout.show(mainPanel, PLUGIN_LIST_VIEW) + + // Fetch GitLab plugins in the background + GitLabPluginFetcher.fetchGitLabPlugins { plugins -> + gitLabPlugins = plugins + // We'll update the UI when needed + } } private fun createPluginListView(): JPanel { @@ -96,6 +117,29 @@ object ReflectiveEditorView { panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS) panel.background = VIEW_BACKGROUND_COLOR + // Add search field at the top + val searchField = CustomSearchField(panel) { searchText -> + pluginSearchText = searchText + // Refresh the plugin list to apply filtering + SwingUtilities.invokeLater { + addPlugins(reflectiveEditorView!!) + } + } + + val searchFieldWrapper = JPanel().apply { + layout = BoxLayout(this, BoxLayout.X_AXIS) + background = VIEW_BACKGROUND_COLOR + preferredSize = Dimension(230, 30) + maximumSize = preferredSize + minimumSize = preferredSize + alignmentX = Component.CENTER_ALIGNMENT + add(searchField) + } + + panel.add(Box.createVerticalStrut(10)) // Spacer + panel.add(searchFieldWrapper) + panel.add(Box.createVerticalStrut(10)) // Spacer + try { panel.add(Box.createVerticalStrut(10)) // Spacer val loadedPluginsField = PluginRepository::class.java.getDeclaredField("loadedPlugins") @@ -113,10 +157,14 @@ object ReflectiveEditorView { } } - if (exposedFields.isNotEmpty()) { - pluginsWithExposed.add(pluginInfo as PluginInfo to plugin as Plugin) - } else { - pluginsWithoutExposed.add(pluginInfo as PluginInfo to plugin as Plugin) + // Apply search filter + val pluginName = plugin.javaClass.`package`.name + if (pluginSearchText.isBlank() || pluginName.contains(pluginSearchText, ignoreCase = true)) { + if (exposedFields.isNotEmpty()) { + pluginsWithExposed.add(pluginInfo as PluginInfo to plugin as Plugin) + } else { + pluginsWithoutExposed.add(pluginInfo as PluginInfo to plugin as Plugin) + } } } @@ -151,16 +199,56 @@ object ReflectiveEditorView { e.printStackTrace() } - // Add disabled plugins to the list + // Add disabled plugins to the list (filtered by search text) val disabledDir = File(pluginsDirectory, "disabled") if (disabledDir.exists() && disabledDir.isDirectory) { val disabledPlugins = disabledDir.listFiles { file -> file.isDirectory } ?: arrayOf() - // Add disabled plugins to the list without exposed attributes + // Add disabled plugins to the list without exposed attributes (filtered by search text) for (pluginDir in disabledPlugins.sortedBy { it.name }) { - val pluginPanel = createDisabledPluginItemPanel(pluginDir.name) - panel.add(pluginPanel) + // Apply search filter + if (pluginSearchText.isBlank() || pluginDir.name.contains(pluginSearchText, ignoreCase = true)) { + val pluginPanel = createDisabledPluginItemPanel(pluginDir.name) + panel.add(pluginPanel) + panel.add(Box.createVerticalStrut(5)) + } + } + } + + // Add a section for available plugins from GitLab that are not installed + if (pluginSearchText.isNotBlank()) { + val matchingGitLabPlugins = gitLabPlugins.filter { plugin -> + plugin.pluginProperties != null && + (plugin.path.contains(pluginSearchText, ignoreCase = true) || + plugin.pluginProperties!!.description.contains(pluginSearchText, ignoreCase = true)) + } + + if (matchingGitLabPlugins.isNotEmpty()) { + // Add a separator + val separator = JPanel() + separator.background = VIEW_BACKGROUND_COLOR + separator.preferredSize = Dimension(Int.MAX_VALUE, 1) + separator.maximumSize = Dimension(Int.MAX_VALUE, 1) + panel.add(Box.createVerticalStrut(10)) + panel.add(separator) panel.add(Box.createVerticalStrut(5)) + + // Add a header for available plugins + val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT)) + headerPanel.background = VIEW_BACKGROUND_COLOR + val headerLabel = JLabel("Available Plugins") + headerLabel.foreground = secondaryColor + headerLabel.font = Font("RuneScape Small", Font.BOLD, 16) + headerPanel.add(headerLabel) + panel.add(headerPanel) + panel.add(Box.createVerticalStrut(5)) + + // Add matching GitLab plugins + for (gitLabPlugin in matchingGitLabPlugins) { + val pluginPanel = createGitLabPluginItemPanel(gitLabPlugin) + panel.add(pluginPanel) + panel.add(Box.createVerticalStrut(5)) + } } } @@ -280,6 +368,48 @@ object ReflectiveEditorView { return panel } + private fun createGitLabPluginItemPanel(gitLabPlugin: GitLabPlugin): JPanel { + val panel = JPanel(BorderLayout()) + panel.background = WIDGET_COLOR + panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10) + panel.maximumSize = Dimension(220, 60) + + // Plugin name + val nameLabel = JLabel(gitLabPlugin.path) + nameLabel.foreground = secondaryColor + nameLabel.font = Font("RuneScape Small", Font.PLAIN, 16) + + // Download button (placeholder for now) + val downloadButton = JButton("Download") + downloadButton.background = TITLE_BAR_COLOR + downloadButton.foreground = secondaryColor + downloadButton.font = Font("RuneScape Small", Font.PLAIN, 14) + downloadButton.addActionListener { + // TODO: Implement download functionality + showToast(mainPanel, "Download functionality not yet implemented", JOptionPane.INFORMATION_MESSAGE) + } + + // Plugin toggle switch (iOS style) - initially off for GitLab plugins + val toggleSwitch = ToggleSwitch() + toggleSwitch.setActivated(false) + toggleSwitch.isEnabled = false // Disable toggle for GitLab plugins until downloaded + + // Layout + val infoPanel = JPanel(BorderLayout()) + infoPanel.background = WIDGET_COLOR + infoPanel.add(nameLabel, BorderLayout.WEST) + + val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0)) + controlsPanel.background = WIDGET_COLOR + controlsPanel.add(downloadButton) + controlsPanel.add(toggleSwitch) + + panel.add(infoPanel, BorderLayout.CENTER) + panel.add(controlsPanel, BorderLayout.EAST) + + return panel + } + private fun enablePlugin(pluginName: String) { try { // Source and destination directories