Release v1.0.0: in-panel settings (gear icon), default URL 127.0.0.1:8899
This commit is contained in:
@@ -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(
|
||||
"<html>Embedded browser will open this URL when the tool window is shown.<br>" +
|
||||
"The target site must allow embedding (no strict <code>X-Frame-Options</code> / <code>CSP frame-ancestors</code>).<br>" +
|
||||
"e.g. <b>http://127.0.0.1:8888</b></html>", 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)
|
||||
}
|
||||
}
|
||||
@@ -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<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("<html><h2>Settings</h2></html>")
|
||||
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("<html><small>例如:http://127.0.0.1:8899 (本地 Harness 服务地址)</small></html>")
|
||||
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<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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<SettingsState.State> {
|
||||
|
||||
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<SettingsState.State> {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user