diff --git a/build.gradle.kts b/build.gradle.kts index f036437..340de4b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } group = "com.prepeep" -version = "1.0.0-SNAPSHOT" +version = "1.0.0" repositories { mavenCentral() diff --git a/src/main/kotlin/com/harmness/toolwindow/HarmnessBrowserConfigurablePanel.kt b/src/main/kotlin/com/harmness/toolwindow/HarmnessBrowserConfigurablePanel.kt deleted file mode 100644 index dffd0e0..0000000 --- a/src/main/kotlin/com/harmness/toolwindow/HarmnessBrowserConfigurablePanel.kt +++ /dev/null @@ -1,45 +0,0 @@ -package com.harmness.toolwindow - -import com.intellij.openapi.diagnostic.Logger -import com.intellij.ui.components.JBLabel -import com.intellij.ui.components.JBTextField -import com.intellij.util.ui.FormBuilder -import com.intellij.util.ui.UIUtil -import javax.swing.JComponent -import javax.swing.JPanel - -/** - * 设置面板 UI:编辑「起始地址」并即时生效。 - */ -class HarmnessBrowserConfigurablePanel { - - private val urlField = JBTextField() - - val panel: JPanel = FormBuilder.createFormBuilder() - .addLabeledComponent("Start URL", urlField, 1, false) - .addComponentFillVertically(JBLabel( - "Embedded browser will open this URL when the tool window is shown.
" + - "The target site must allow embedding (no strict X-Frame-Options / CSP frame-ancestors).
" + - "e.g. http://127.0.0.1:8888", UIUtil.ComponentStyle.SMALL, UIUtil.FontColor.NORMAL), 1) - .panel - - init { - urlField.columns = 40 - urlField.emptyText.text = "http://127.0.0.1:8888" - } - - fun isModified(settings: SettingsState): Boolean = - urlField.text != settings.startUrl - - fun apply(settings: SettingsState) { - settings.startUrl = urlField.text.trim() - } - - fun reset(settings: SettingsState) { - urlField.text = settings.startUrl - } - - companion object { - private val LOG = Logger.getInstance(HarmnessBrowserConfigurablePanel::class.java) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/harmness/toolwindow/HarmnessBrowserPanel.kt b/src/main/kotlin/com/harmness/toolwindow/HarmnessBrowserPanel.kt index 1737f19..c41f7f2 100644 --- a/src/main/kotlin/com/harmness/toolwindow/HarmnessBrowserPanel.kt +++ b/src/main/kotlin/com/harmness/toolwindow/HarmnessBrowserPanel.kt @@ -18,24 +18,19 @@ import java.awt.Desktop import java.awt.FlowLayout import java.awt.event.ActionEvent import javax.swing.AbstractAction +import javax.swing.BorderFactory import javax.swing.JButton import javax.swing.JComponent import javax.swing.JPanel import javax.swing.JTextField +import javax.swing.SwingUtilities /** * 嵌入浏览器面板。 * - * 职责: - * - 使用 JBCefBrowser 渲染用户配置的起始地址(例如本地 Harness 地址) - * - 工具栏:地址栏、前进/后退、刷新、设置入口(快捷跳转到 IDE Settings 的插件配置页) - * - 通过 [JBCefJSQuery] 提供 JS ↔ 插件代码双向桥 - * - 拦截并处理 _blank 弹窗(默认吞掉,可选交给系统浏览器) - * - * 由于 IntelliJ Platform 官方插件模型目前不允许在 JCEF 浏览器内部直接调用 - * "打开当前项目文件" 之类的 API(无 DOM 访问权限),插件通过注入到页面中的 - * 桥接函数 [JS_BRIDGE_NAME] 让网页(如 Harness 前端)发起请求, - * 再由本类把结果返回给页面。 + * 两种视图模式,通过右上角「⚙」图标切换: + * - [Mode.BROWSER]:显示配置好的网页(JCEF) + * - [Mode.SETTINGS]:表单,可修改「起始地址」,点保存后自动切回浏览器并加载新地址 */ class HarmnessBrowserPanel( private val project: com.intellij.openapi.project.Project, @@ -49,6 +44,10 @@ class HarmnessBrowserPanel( private lateinit var query: JBCefJSQuery private val loadEnded = java.util.concurrent.atomic.AtomicBoolean(false) + private var mode = Mode.BROWSER + + private enum class Mode { BROWSER, SETTINGS } + init { Disposer.register(parentDisposable, this) @@ -59,21 +58,39 @@ class HarmnessBrowserPanel( BorderLayout.CENTER) } else { browser = JBCefBrowser() - - // 布局:顶部工具栏 + 浏览器 - add(createToolbar(), BorderLayout.NORTH) - add(browser.component, BorderLayout.CENTER) - - // 设置弹窗处理 setupPopupHandling() - // 设置 JS 桥 setupBridge() - // 初始加载 - loadCurrentUrl() + // 顶部工具栏(右上角 ⚙) + add(createToolbar(), BorderLayout.NORTH) + // 初始为浏览器视图 + showBrowserMode() } } + // ------------------------------------------------------------------ + // 视图切换 + // ------------------------------------------------------------------ + + private fun showBrowserMode() { + mode = Mode.BROWSER + removeAll() + add(createToolbar(), BorderLayout.NORTH) + add(browser.component, BorderLayout.CENTER) + revalidate() + repaint() + loadCurrentUrl() + } + + private fun showSettingsMode() { + mode = Mode.SETTINGS + removeAll() + add(createToolbar(), BorderLayout.NORTH) + add(createSettingsForm(), BorderLayout.CENTER) + revalidate() + repaint() + } + // ------------------------------------------------------------------ // 工具栏 // ------------------------------------------------------------------ @@ -94,12 +111,13 @@ class HarmnessBrowserPanel( val reload = JButton(object : AbstractAction("⟳") { override fun actionPerformed(e: ActionEvent) { cefBrowser.reload() } }) + + // 右上角:设置图标(⚙),点击在两种模式间切换 val settingsBtn = JButton(object : AbstractAction("⚙") { - override fun actionPerformed(e: ActionEvent) { openSettings() } - }) - val externalBtn = JButton(object : AbstractAction("⧉") { - override fun actionPerformed(e: ActionEvent) { openInExternalBrowser() } + override fun actionPerformed(e: ActionEvent) { toggleSettings() } }) + settingsBtn.toolTipText = if (mode == Mode.BROWSER) "设置" else "返回主界面" + settingsBtn.border = BorderFactory.createEmptyBorder(2, 6, 2, 6) addressField = JTextField(settings.startUrl) addressField.toolTipText = "当前页面地址(可手动输入后回车跳转)" @@ -111,35 +129,81 @@ class HarmnessBrowserPanel( toolbar.add(reload) toolbar.add(addressField) toolbar.add(settingsBtn) - toolbar.add(externalBtn) return toolbar } + private fun toggleSettings() { + if (mode == Mode.BROWSER) { + showSettingsMode() + } else { + // 从设置页切回主界面时重新加载 + showBrowserMode() + } + } + private fun navigate(url: String) { val fixedUrl = if (url.startsWith("http://") || url.startsWith("https://")) url else "http://$url" browser.loadURL(fixedUrl) + loadEnded.set(false) } - private fun openSettings() { - val app = com.intellij.openapi.application.ApplicationManager.getApplication() - val show = Runnable { - com.intellij.openapi.options.ShowSettingsUtil.getInstance() - .showSettingsDialog(project, "Harmness Browser") - } - if (app.isDispatchThread) show.run() else app.invokeLater(show) - } + // ------------------------------------------------------------------ + // 设置表单 + // ------------------------------------------------------------------ - private fun openInExternalBrowser() { - val url = addressField.text.ifBlank { settings.startUrl } - if (Desktop.isDesktopSupported()) { - try { - Desktop.getDesktop().browse(java.net.URI(url)) - } catch (e: Exception) { - BrowserUtil.browse(url) + private fun createSettingsForm(): JComponent { + val panel = JBPanel>() + panel.layout = java.awt.GridBagLayout() + val gbc = java.awt.GridBagConstraints() + gbc.insets = java.awt.Insets(6, 8, 6, 8) + gbc.fill = java.awt.GridBagConstraints.HORIZONTAL + gbc.anchor = java.awt.GridBagConstraints.NORTH + + val title = JBLabel("

Settings

") + gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 2 + panel.add(title, gbc) + gbc.gridwidth = 1 + + val label = JBLabel("起始地址 (Start URL):") + gbc.gridx = 0; gbc.gridy = 1 + panel.add(label, gbc) + + val urlField = JTextField(settings.startUrl, 30) + gbc.gridx = 1; gbc.gridy = 1 + panel.add(urlField, gbc) + + val hint = JBLabel("例如:http://127.0.0.1:8899 (本地 Harness 服务地址)") + gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2 + panel.add(hint, gbc) + gbc.gridwidth = 1 + + val saveBtn = JButton("保存") + saveBtn.addActionListener { + settings.startUrl = urlField.text.trim() + saveBtn.text = "已保存 ✓" + SwingUtilities.invokeLater { + Thread.sleep(400) + saveBtn.text = "保存" } - } else { - BrowserUtil.browse(url) + showBrowserMode() } + + val cancelBtn = JButton("取消") + cancelBtn.addActionListener { + showBrowserMode() + } + + val btnPanel = JPanel(FlowLayout(FlowLayout.LEFT, 8, 0)) + btnPanel.add(saveBtn) + btnPanel.add(cancelBtn) + gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 2 + panel.add(btnPanel, gbc) + + gbc.gridy = 4 + gbc.weighty = 1.0 + panel.add(JBPanel>(), gbc) + + return panel } // ------------------------------------------------------------------ @@ -224,10 +288,6 @@ class HarmnessBrowserPanel( loadEnded.set(false) } - fun reloadFromSettings() { - loadCurrentUrl() - } - override fun dispose() { loadEnded.set(false) if (::query.isInitialized) { diff --git a/src/main/kotlin/com/harmness/toolwindow/HarmnessSettingsConfigurable.kt b/src/main/kotlin/com/harmness/toolwindow/HarmnessSettingsConfigurable.kt deleted file mode 100644 index b3fa402..0000000 --- a/src/main/kotlin/com/harmness/toolwindow/HarmnessSettingsConfigurable.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.harmness.toolwindow - -import com.intellij.openapi.options.Configurable -import javax.swing.JComponent - -/** - * Settings configurable registered in plugin.xml. - * Shows the HarmnessBrowserConfigurablePanel under Tools → Harmness Browser. - */ -class HarmnessSettingsConfigurable : Configurable { - - private var panel: HarmnessBrowserConfigurablePanel? = null - - override fun getDisplayName(): String = "Harmness Browser" - - override fun getHelpTopic(): String? = null - - override fun createComponent(): JComponent { - panel = HarmnessBrowserConfigurablePanel() - return panel!!.panel - } - - override fun isModified(): Boolean = panel?.isModified(SettingsState.getInstance()) ?: false - - override fun apply() { - panel?.apply(SettingsState.getInstance()) - } - - override fun reset() { - panel?.reset(SettingsState.getInstance()) - } - - override fun disposeUIResources() { - panel = null - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/harmness/toolwindow/SettingsState.kt b/src/main/kotlin/com/harmness/toolwindow/SettingsState.kt index e898cb6..a2a15ec 100644 --- a/src/main/kotlin/com/harmness/toolwindow/SettingsState.kt +++ b/src/main/kotlin/com/harmness/toolwindow/SettingsState.kt @@ -8,18 +8,14 @@ import com.intellij.openapi.components.Storage import com.intellij.openapi.diagnostic.Logger /** - * 插件设置:目前仅保存一个「浏览器起始地址」。 - * - * 该地址必须是一个允许被嵌入的 Web 应用地址(例如本地 Harness 服务地址), - * 并且需要在页面上开启允许被 iframe/嵌入浏览(X-Frame-Options / CSP frame-ancestors) - * 或者由 Harness 提供专门的无 frame 限制入口。 + * 插件设置:仅保存「浏览器起始地址」。 */ @State(name = "HarmnessToolwindowSettings", storages = [Storage("harmness-toolwindow.xml")]) @Service(Service.Level.APP) class SettingsState : PersistentStateComponent { data class State( - var startUrl: String = "http://127.0.0.1:8888", + var startUrl: String = DEFAULT_URL, ) private var myState = State() @@ -39,7 +35,8 @@ class SettingsState : PersistentStateComponent { } companion object { - const val DEFAULT_URL = "http://127.0.0.1:8888" + // 与 DSH Web GUI 默认端口一致 + const val DEFAULT_URL = "http://127.0.0.1:8899" fun getInstance(): SettingsState = ServiceManager.getService(SettingsState::class.java) diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 914f2ab..e41bb7d 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -9,7 +9,7 @@ IDEA without switching to a separate browser window. ]]> - + com.intellij.modules.platform @@ -21,12 +21,6 @@ anchor="right" /> - -