vito
2 years ago
66 changed files with 1364 additions and 1002 deletions
@ -0,0 +1,40 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import com.fr.design.bridge.exec.JSExecutor; |
||||||
|
import com.teamdev.jxbrowser.js.JsFunction; |
||||||
|
import com.teamdev.jxbrowser.js.JsObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* 用于 jxbrowser 执行后的回调执行器 |
||||||
|
* 适配7.15之后 |
||||||
|
* |
||||||
|
* @author vito |
||||||
|
* @since 11.0 |
||||||
|
* Created on 2023/6/8 |
||||||
|
*/ |
||||||
|
public class BrowserExecutor implements JSExecutor { |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建一个回调执行器 |
||||||
|
* |
||||||
|
* @param window js环境的window对象 |
||||||
|
* @param callback 回调 |
||||||
|
* @return 执行器 |
||||||
|
*/ |
||||||
|
public static BrowserExecutor create(JsObject window, JsFunction callback) { |
||||||
|
return new BrowserExecutor(window, callback); |
||||||
|
} |
||||||
|
|
||||||
|
private final JsObject window; |
||||||
|
private final JsFunction callback; |
||||||
|
|
||||||
|
private BrowserExecutor(JsObject window, JsFunction callback) { |
||||||
|
this.window = window; |
||||||
|
this.callback = callback; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void executor(String newValue) { |
||||||
|
callback.invoke(window, newValue); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,125 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import com.fr.design.DesignerEnvManager; |
||||||
|
import com.fr.design.ui.ModernUIConstants; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.value.ClearableLazyValue; |
||||||
|
import com.fr.web.struct.AssembleComponent; |
||||||
|
import com.teamdev.jxbrowser.engine.Engine; |
||||||
|
import com.teamdev.jxbrowser.engine.EngineOptions; |
||||||
|
import com.teamdev.jxbrowser.engine.RenderingMode; |
||||||
|
import com.teamdev.jxbrowser.engine.event.EngineCrashed; |
||||||
|
import com.teamdev.jxbrowser.net.Network; |
||||||
|
import com.teamdev.jxbrowser.net.Scheme; |
||||||
|
import com.teamdev.jxbrowser.net.callback.VerifyCertificateCallback; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
/** |
||||||
|
* 可重复启动的 Jxbrowser 引擎 |
||||||
|
* 手动创建的引擎,应当自己负责管理声明周期 {@link #newInstance()} |
||||||
|
* 单例的引擎,由系统管理生命周期,使用后仅需清理browser和map等数据 |
||||||
|
* {@link #getEngine()} 和 {@link #getPublicEngineInstance()} |
||||||
|
* |
||||||
|
* @author vito |
||||||
|
* @since 11.0 |
||||||
|
* Created on 2023/6/8 |
||||||
|
*/ |
||||||
|
public class JxEngine { |
||||||
|
|
||||||
|
private static final JxEngine INSTANCE = new JxEngine(); |
||||||
|
|
||||||
|
private AssembleComponent component; |
||||||
|
private Map<String, String> parameterMap = Collections.emptyMap(); |
||||||
|
|
||||||
|
|
||||||
|
private final ClearableLazyValue<Engine> ENGINE = ClearableLazyValue.create(() -> { |
||||||
|
EngineOptions.Builder builder = EngineOptions |
||||||
|
.newBuilder(RenderingMode.HARDWARE_ACCELERATED) |
||||||
|
.addSwitch("--disable-google-traffic") |
||||||
|
.addScheme(Scheme.of(ModernUIConstants.EMB_TAG), |
||||||
|
new NxInterceptRequestCallback(this::getComponent, this::getParameterMap)); |
||||||
|
Engine engine = Engine.newInstance(builder.build()); |
||||||
|
engine.on(EngineCrashed.class, (event) -> { |
||||||
|
FineLoggerFactory.getLogger().error("jxBrowser engine crashed with exitCode: {}", event.exitCode()); |
||||||
|
event.engine().close(); |
||||||
|
}); |
||||||
|
if (DesignerEnvManager.getEnvManager().isOpenDebug()) { |
||||||
|
// 调试模式下,禁止HTTPS证书验证,使得可以正常访问商城测试服务器等
|
||||||
|
Network network = engine.network(); |
||||||
|
network.set(VerifyCertificateCallback.class, params -> VerifyCertificateCallback.Response.valid()); |
||||||
|
} |
||||||
|
return engine; |
||||||
|
}); |
||||||
|
|
||||||
|
public Map<String, String> getParameterMap() { |
||||||
|
return Collections.unmodifiableMap(parameterMap); |
||||||
|
} |
||||||
|
|
||||||
|
public void setMap(Map<String, String> parameterMap) { |
||||||
|
if (parameterMap == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
this.parameterMap = parameterMap; |
||||||
|
} |
||||||
|
|
||||||
|
public void clearMap() { |
||||||
|
this.parameterMap = Collections.emptyMap(); |
||||||
|
} |
||||||
|
|
||||||
|
public AssembleComponent getComponent() { |
||||||
|
return component; |
||||||
|
} |
||||||
|
|
||||||
|
public void setComponent(AssembleComponent component) { |
||||||
|
this.component = component; |
||||||
|
} |
||||||
|
|
||||||
|
public void clearComponent() { |
||||||
|
this.component = null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取单例引擎包装,能够更新渲染 |
||||||
|
* 从单例获取的引擎不用负责关闭, |
||||||
|
* 应用系统管理声明周期 |
||||||
|
* |
||||||
|
* @return jxbrowser 引擎包装类 |
||||||
|
*/ |
||||||
|
public static JxEngine getInstance() { |
||||||
|
return INSTANCE; |
||||||
|
} |
||||||
|
|
||||||
|
@NotNull |
||||||
|
public Engine getEngine() { |
||||||
|
return ENGINE.getValue(); |
||||||
|
} |
||||||
|
|
||||||
|
public void close() { |
||||||
|
ENGINE.getValue().close(); |
||||||
|
ENGINE.drop(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 获取公共引擎,公共引擎使用后不用关闭引擎,但需要自己管理 browser。 |
||||||
|
* 应用系统管理引擎生命周期 |
||||||
|
* |
||||||
|
* @return 引擎 |
||||||
|
*/ |
||||||
|
public static Engine getPublicEngineInstance() { |
||||||
|
return getInstance().ENGINE.getValue(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 创建一个新的引擎,创建引擎使用后需负责关闭引擎 |
||||||
|
* 但可以独立使用 map 和 component |
||||||
|
* |
||||||
|
* @return 引擎 |
||||||
|
*/ |
||||||
|
public static JxEngine newInstance() { |
||||||
|
return new JxEngine(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,456 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import com.fr.design.DesignerEnvManager; |
||||||
|
import com.fr.design.gui.ibutton.UIButton; |
||||||
|
import com.fr.design.gui.itoolbar.UIToolbar; |
||||||
|
import com.fr.design.i18n.Toolkit; |
||||||
|
import com.fr.design.ui.ModernUIConstants; |
||||||
|
import com.fr.design.ui.ModernUIPane; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.web.struct.AssembleComponent; |
||||||
|
import com.teamdev.jxbrowser.browser.Browser; |
||||||
|
import com.teamdev.jxbrowser.browser.callback.InjectJsCallback; |
||||||
|
import com.teamdev.jxbrowser.frame.Frame; |
||||||
|
import com.teamdev.jxbrowser.js.JsObject; |
||||||
|
import com.teamdev.jxbrowser.view.swing.BrowserView; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
import org.jetbrains.annotations.Nullable; |
||||||
|
|
||||||
|
import javax.swing.SwingUtilities; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
import static com.fr.design.ui.ModernUIConstants.COMPONENT_TAG; |
||||||
|
import static com.fr.design.ui.ModernUIConstants.DEFAULT_EXPRESSION; |
||||||
|
import static com.fr.design.ui.ModernUIConstants.DEFAULT_NAMESPACE; |
||||||
|
import static com.fr.design.ui.ModernUIConstants.DEFAULT_VARIABLE; |
||||||
|
import static com.fr.design.ui.ModernUIConstants.DOT; |
||||||
|
import static com.fr.design.ui.ModernUIConstants.EMB_TAG; |
||||||
|
import static com.fr.design.ui.ModernUIConstants.SCHEME_HEADER; |
||||||
|
import static com.fr.design.ui.ModernUIConstants.WINDOW; |
||||||
|
|
||||||
|
/** |
||||||
|
* 基于v7 jxbrowser 实现 |
||||||
|
* 用于加载 html5 的Swing容器,可以在设计选项设置中打开调试窗口, |
||||||
|
* 示例可查看:com.fr.design.ui.JxUIPaneTest |
||||||
|
* |
||||||
|
* @author vito |
||||||
|
* @since 11.0 |
||||||
|
* Created on 2023-06-12 |
||||||
|
*/ |
||||||
|
public class JxUIPane<T> extends ModernUIPane<T> { |
||||||
|
|
||||||
|
private Browser browser; |
||||||
|
private String namespace = "Pool"; |
||||||
|
private String variable = "data"; |
||||||
|
private String expression = "update()"; |
||||||
|
|
||||||
|
private JxUIPane() { |
||||||
|
super(); |
||||||
|
} |
||||||
|
|
||||||
|
private void initialize() { |
||||||
|
setLayout(new BorderLayout()); |
||||||
|
if (browser != null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
initDebugIfNeeded(); |
||||||
|
// 使用公共引擎创建浏览器
|
||||||
|
browser = JxEngine.getPublicEngineInstance().newBrowser(); |
||||||
|
add(BrowserView.newInstance(browser), BorderLayout.CENTER); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 按需初始化debug界面UI |
||||||
|
*/ |
||||||
|
private void initDebugIfNeeded() { |
||||||
|
if (DesignerEnvManager.getEnvManager().isOpenDebug()) { |
||||||
|
UIToolbar toolbar = new UIToolbar(); |
||||||
|
add(toolbar, BorderLayout.NORTH); |
||||||
|
UIButton openDebugButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Open_Debug_Window")); |
||||||
|
openDebugButton.addActionListener(e -> browser.devTools().show()); |
||||||
|
toolbar.add(openDebugButton); |
||||||
|
UIButton reloadButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Reload")); |
||||||
|
reloadButton.addActionListener(e -> browser.navigation().reloadIgnoringCache()); |
||||||
|
toolbar.add(reloadButton); |
||||||
|
UIButton closeButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Close_Window")); |
||||||
|
closeButton.addActionListener(e -> SwingUtilities.getWindowAncestor(JxUIPane.this).setVisible(false)); |
||||||
|
toolbar.add(closeButton); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 在初始化时进行注入JS的方法,只被build调用 |
||||||
|
* |
||||||
|
* @param injectJsCallback 回调 |
||||||
|
*/ |
||||||
|
private void initInjectJs(InjectJsCallback injectJsCallback) { |
||||||
|
browser.set(InjectJsCallback.class, params -> { |
||||||
|
// 初始化的时候,就把命名空间对象初始化好,确保window.a.b.c("a.b.c"为命名空间)对象都是初始化过的
|
||||||
|
params.frame().executeJavaScript(String.format(ModernUIConstants.SCRIPT_INIT_NAME_SPACE, namespace)); |
||||||
|
return injectJsCallback.on(params); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 设置 InjectJsCallback。 |
||||||
|
* 这个方法解决重复InjectJsCallback被覆盖的问题。 |
||||||
|
* 用于本类内部方法使用,如{@link #populate(Object)} |
||||||
|
* |
||||||
|
* @param injectJsCallback 回调 |
||||||
|
*/ |
||||||
|
private void setInjectJsCallback(InjectJsCallback injectJsCallback) { |
||||||
|
Optional<InjectJsCallback> callback = browser.get(InjectJsCallback.class); |
||||||
|
if (callback.isPresent()) { |
||||||
|
browser.set(InjectJsCallback.class, params -> { |
||||||
|
callback.get().on(params); |
||||||
|
return injectJsCallback.on(params); |
||||||
|
}); |
||||||
|
} else { |
||||||
|
browser.set(InjectJsCallback.class, injectJsCallback); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 转向一个新的地址,相当于重新加载 |
||||||
|
* |
||||||
|
* @param url 新的地址 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void redirect(String url) { |
||||||
|
browser.navigation().loadUrl(url); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 转向一个新的地址,相当于重新加载 |
||||||
|
* |
||||||
|
* @param url 新的地址 |
||||||
|
* @param map 初始化参数 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public void redirect(String url, Map<String, String> map) { |
||||||
|
setMap(map); |
||||||
|
browser.navigation().loadUrl(url); |
||||||
|
} |
||||||
|
|
||||||
|
private void setMap(Map<String, String> map) { |
||||||
|
JxEngine.getInstance().setMap(map); |
||||||
|
} |
||||||
|
|
||||||
|
private void setComponent(AssembleComponent component) { |
||||||
|
JxEngine.getInstance().setComponent(component); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected String title4PopupWindow() { |
||||||
|
return "ModernUI7"; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Override |
||||||
|
public void populate(final T t) { |
||||||
|
setInjectJsCallback(params -> { |
||||||
|
executeJsObject(params.frame(), WINDOW + DOT + namespace) |
||||||
|
.ifPresent(ns -> ns.putProperty(variable, t)); |
||||||
|
return InjectJsCallback.Response.proceed(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@Nullable |
||||||
|
public T update() { |
||||||
|
if (browser.mainFrame().isPresent()) { |
||||||
|
return browser.mainFrame().get().executeJavaScript("window." + namespace + "." + expression); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 关闭浏览器 |
||||||
|
*/ |
||||||
|
public void disposeBrowser() { |
||||||
|
if (browser != null) { |
||||||
|
browser.close(); |
||||||
|
browser = null; |
||||||
|
JxEngine.getInstance().clearMap(); |
||||||
|
JxEngine.getInstance().clearComponent(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 清理浏览器缓存 |
||||||
|
*/ |
||||||
|
public void clearCache() { |
||||||
|
if (browser != null) { |
||||||
|
browser.engine().httpCache().clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 执行一段js |
||||||
|
* |
||||||
|
* @param javaScript 待执行的js脚本 |
||||||
|
*/ |
||||||
|
public void executeJavaScript(String javaScript) { |
||||||
|
if (browser != null) { |
||||||
|
browser.mainFrame().ifPresent(frame -> { |
||||||
|
frame.executeJavaScript(javaScript); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取js对象 |
||||||
|
* 注意:类内部使用,用于简化编码,提供 Optional 包装 |
||||||
|
* |
||||||
|
* @param frame 页面frame对象 |
||||||
|
* @param name 变量命名 |
||||||
|
* @return js对象 |
||||||
|
*/ |
||||||
|
private static Optional<JsObject> executeJsObject(Frame frame, String name) { |
||||||
|
return Optional.ofNullable(frame.executeJavaScript(name)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* JxUIPane 的建造者 |
||||||
|
* |
||||||
|
* @param <T> 参数 |
||||||
|
*/ |
||||||
|
public static class Builder<T> { |
||||||
|
private String namespace; |
||||||
|
private String variable; |
||||||
|
private String expression; |
||||||
|
private InjectJsCallback callback; |
||||||
|
private final Map<String, Object> propertyMap; |
||||||
|
private final Map<String, PropertyBuild> buildPropertyMap; |
||||||
|
private Object variableProperty; |
||||||
|
private Map<String, String> parameterMap; |
||||||
|
private AssembleComponent component; |
||||||
|
private String url; |
||||||
|
private String html; |
||||||
|
|
||||||
|
public Builder() { |
||||||
|
this.namespace = DEFAULT_NAMESPACE; |
||||||
|
this.variable = DEFAULT_VARIABLE; |
||||||
|
this.expression = DEFAULT_EXPRESSION; |
||||||
|
this.callback = null; |
||||||
|
this.propertyMap = new HashMap<>(); |
||||||
|
this.buildPropertyMap = new HashMap<>(); |
||||||
|
this.variableProperty = null; |
||||||
|
this.parameterMap = null; |
||||||
|
this.component = null; |
||||||
|
this.url = StringUtils.EMPTY; |
||||||
|
this.html = StringUtils.EMPTY; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 注入一个回调,回调的js会在初始化进行执行 |
||||||
|
* |
||||||
|
* @param callback 回调 |
||||||
|
* @return builder |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> prepare(InjectJsCallback callback) { |
||||||
|
this.callback = callback; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载jar包中的资源 |
||||||
|
* |
||||||
|
* @param path 资源路径 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> withEMB(final String path) { |
||||||
|
this.url = EMB_TAG + SCHEME_HEADER + path; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载jar包中的资源 |
||||||
|
* |
||||||
|
* @param path 资源路径 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> withEMB(final String path, Map<String, String> map) { |
||||||
|
this.parameterMap = map; |
||||||
|
this.url = EMB_TAG + SCHEME_HEADER + path; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载url指向的资源 |
||||||
|
* |
||||||
|
* @param url 文件的地址 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> withURL(final String url) { |
||||||
|
this.url = url; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载url指向的资源 |
||||||
|
* |
||||||
|
* @param url 文件的地址 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> withURL(final String url, Map<String, String> map) { |
||||||
|
this.parameterMap = map; |
||||||
|
this.url = url; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载Atom组件 |
||||||
|
* |
||||||
|
* @param component Atom组件 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> withComponent(AssembleComponent component) { |
||||||
|
return withComponent(component, null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 加载Atom组件 |
||||||
|
* |
||||||
|
* @param component Atom组件 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> withComponent(AssembleComponent component, Map<String, String> map) { |
||||||
|
this.parameterMap = map; |
||||||
|
this.component = component; |
||||||
|
this.url = COMPONENT_TAG; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* 加载html文本内容 |
||||||
|
* |
||||||
|
* @param html 要加载html文本内容 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> withHTML(String html) { |
||||||
|
this.html = html; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 设置该前端页面做数据交换所使用的对象 |
||||||
|
* |
||||||
|
* @param namespace 对象名 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> namespace(String namespace) { |
||||||
|
this.namespace = namespace; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* java端往js端传数据时使用的变量名字 |
||||||
|
* 默认值为 data |
||||||
|
* |
||||||
|
* @param name 变量的名字 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> variable(String name) { |
||||||
|
this.variable = name; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* js端往java端传数据时执行的函数表达式 |
||||||
|
* |
||||||
|
* @param expression 函数表达式 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> expression(String expression) { |
||||||
|
this.expression = expression; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 注入一个java对象到js中,绑定在全局变量 window的 |
||||||
|
* property指定的变量。这个方法仅在在加载的网页上执 |
||||||
|
* 行 JavaScript 之前注入 |
||||||
|
* |
||||||
|
* @param property 属性 |
||||||
|
* @param obj java对象 |
||||||
|
* @return 链式对象 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> property(String property, @Nullable Object obj) { |
||||||
|
this.propertyMap.put(property, obj); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 注入一个java对象到js中,绑定在全局变量window的指定变量variable。 |
||||||
|
* variable 可由 {@link #variable(String)} 设置,默认值为 data |
||||||
|
* 这个方法仅在在加载的网页上执行 JavaScript 之前注入 |
||||||
|
* |
||||||
|
* @param obj java对象 |
||||||
|
* @return 链式对象 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> property(@NotNull Object obj) { |
||||||
|
this.variableProperty = obj; |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 注入一个java对象到js中。绑定在全局变量 window的property指定的变量。 |
||||||
|
* PropertyBuild用于动态生成绑定属性。个方法仅在在加载的网页上执行 |
||||||
|
* JavaScript 之前注入 |
||||||
|
* |
||||||
|
* @param property 属性构建器 |
||||||
|
* @param obj java对象 |
||||||
|
* @return 链式对象 |
||||||
|
*/ |
||||||
|
public JxUIPane.Builder<T> buildProperty(String property, PropertyBuild obj) { |
||||||
|
buildPropertyMap.put(property, obj); |
||||||
|
return this; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建 |
||||||
|
*/ |
||||||
|
public JxUIPane<T> build() { |
||||||
|
JxUIPane<T> pane = new JxUIPane<>(); |
||||||
|
pane.namespace = namespace; |
||||||
|
pane.variable = variable; |
||||||
|
pane.expression = expression; |
||||||
|
pane.setMap(parameterMap); |
||||||
|
pane.setComponent(component); |
||||||
|
pane.initialize(); |
||||||
|
injectJs(pane); |
||||||
|
if (StringUtils.isNotEmpty(this.url)) { |
||||||
|
pane.browser.navigation().loadUrl(this.url); |
||||||
|
} else if (StringUtils.isNotEmpty(this.html)) { |
||||||
|
pane.browser.mainFrame().ifPresent(f -> f.loadHtml(html)); |
||||||
|
} |
||||||
|
return pane; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 由于 InjectJsCallback 的回调机制,在初始化期间,只有 |
||||||
|
* 在 InjectJsCallback 中 putProperty 才能生效。 |
||||||
|
* 因此,嵌套回调分别做默认初始化、putProperty、外置初始化 |
||||||
|
*/ |
||||||
|
private void injectJs(JxUIPane<T> pane) { |
||||||
|
pane.initInjectJs(params -> { |
||||||
|
Frame frame = params.frame(); |
||||||
|
if (!propertyMap.isEmpty()) { |
||||||
|
propertyMap.forEach((key, value) -> |
||||||
|
executeJsObject(frame, WINDOW) |
||||||
|
.ifPresent(window -> window.putProperty(key, value))); |
||||||
|
} |
||||||
|
if (!buildPropertyMap.isEmpty()) { |
||||||
|
buildPropertyMap.forEach((key, value) -> |
||||||
|
executeJsObject(frame, WINDOW) |
||||||
|
.ifPresent(window -> window.putProperty(key, value.build(window)))); |
||||||
|
} |
||||||
|
if (variableProperty != null) { |
||||||
|
executeJsObject(frame, WINDOW + DOT + namespace) |
||||||
|
.ifPresent(pool -> pool.putProperty(variable, variableProperty)); |
||||||
|
} |
||||||
|
if (callback != null) { |
||||||
|
return callback.on(params); |
||||||
|
} |
||||||
|
return InjectJsCallback.Response.proceed(); |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,113 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.nio.file.Path; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Optional; |
||||||
|
|
||||||
|
/** |
||||||
|
* jxbrowser 使用的一些媒体类型 |
||||||
|
* |
||||||
|
* @author vito |
||||||
|
* @since 11.0 |
||||||
|
* Created on 2023/6/13 |
||||||
|
*/ |
||||||
|
public enum MimeType { |
||||||
|
/** |
||||||
|
* html 格式 |
||||||
|
*/ |
||||||
|
HTML(".html", "text/html"), |
||||||
|
/** |
||||||
|
* CSS 格式 |
||||||
|
*/ |
||||||
|
CSS(".css", "text/css"), |
||||||
|
/** |
||||||
|
* js 格式 |
||||||
|
*/ |
||||||
|
JS(".js", "text/css"), |
||||||
|
/** |
||||||
|
* svg 格式 |
||||||
|
*/ |
||||||
|
SVG(".svg", "text/javascript"), |
||||||
|
/** |
||||||
|
* png 格式 |
||||||
|
*/ |
||||||
|
PNG(".png", "image/png"), |
||||||
|
|
||||||
|
/** |
||||||
|
* jpg 格式 |
||||||
|
*/ |
||||||
|
JPG(".jpg", "image/jpeg"), |
||||||
|
|
||||||
|
/** |
||||||
|
* jpeg 格式 |
||||||
|
*/ |
||||||
|
JPEG(".jpeg", "image/jpeg"), |
||||||
|
|
||||||
|
/** |
||||||
|
* gif 格式 |
||||||
|
*/ |
||||||
|
GIF(".gif", "image/gif"), |
||||||
|
/** |
||||||
|
* woff 字体格式 |
||||||
|
*/ |
||||||
|
WOFF(".woff", "font/woff"), |
||||||
|
/** |
||||||
|
* ttf 字体格式 |
||||||
|
*/ |
||||||
|
TTF(".ttf", "truetype"), |
||||||
|
|
||||||
|
/** |
||||||
|
* MS 嵌入式开放字体 |
||||||
|
*/ |
||||||
|
EOT(".eot", "embedded-opentype"); |
||||||
|
|
||||||
|
private final String suffix; |
||||||
|
private final String mimeType; |
||||||
|
|
||||||
|
MimeType(String suffix, String mimeType) { |
||||||
|
this.suffix = suffix; |
||||||
|
this.mimeType = mimeType; |
||||||
|
} |
||||||
|
|
||||||
|
public String parseMimeType() { |
||||||
|
return mimeType; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取指定路径对应的 mimetype,优先匹配常量中的类型 |
||||||
|
* 如果没有,尝试使用 Files.probeContentType 检测 |
||||||
|
* 如果没有,默认返回 text/html |
||||||
|
* |
||||||
|
* @param url url路径 |
||||||
|
* @return MimeType |
||||||
|
*/ |
||||||
|
public static String parseMimeType(String url) { |
||||||
|
if (StringUtils.isBlank(url)) { |
||||||
|
return HTML.mimeType; |
||||||
|
} |
||||||
|
String finalPath = url.split("\\?")[0]; |
||||||
|
Optional<MimeType> mimeType = Arrays.stream(values()) |
||||||
|
.filter(type -> finalPath.endsWith(type.suffix)) |
||||||
|
.findFirst(); |
||||||
|
if (mimeType.isPresent()) { |
||||||
|
return mimeType.get().mimeType; |
||||||
|
} else { |
||||||
|
return getFileMimeType(finalPath); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static String getFileMimeType(String finalPath) { |
||||||
|
Path file = new File(finalPath).toPath(); |
||||||
|
try { |
||||||
|
String s = Files.probeContentType(file); |
||||||
|
return StringUtils.isEmpty(s) ? HTML.mimeType : s; |
||||||
|
} catch (IOException e) { |
||||||
|
return HTML.mimeType; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,171 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import com.fr.base.TemplateUtils; |
||||||
|
import com.fr.design.ui.ModernRequestClient; |
||||||
|
import com.fr.design.ui.ModernUIConstants; |
||||||
|
import com.fr.general.IOUtils; |
||||||
|
import com.fr.log.FineLoggerFactory; |
||||||
|
import com.fr.stable.EncodeConstants; |
||||||
|
import com.fr.stable.StringUtils; |
||||||
|
import com.fr.web.struct.AssembleComponent; |
||||||
|
import com.fr.web.struct.AtomBuilder; |
||||||
|
import com.fr.web.struct.PathGroup; |
||||||
|
import com.fr.web.struct.category.ScriptPath; |
||||||
|
import com.fr.web.struct.category.StylePath; |
||||||
|
import com.teamdev.jxbrowser.net.HttpHeader; |
||||||
|
import com.teamdev.jxbrowser.net.HttpStatus; |
||||||
|
import com.teamdev.jxbrowser.net.UrlRequest; |
||||||
|
import com.teamdev.jxbrowser.net.UrlRequestJob; |
||||||
|
import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback; |
||||||
|
import org.jetbrains.annotations.NotNull; |
||||||
|
|
||||||
|
import java.io.InputStream; |
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Optional; |
||||||
|
import java.util.function.Supplier; |
||||||
|
|
||||||
|
import static com.fr.design.ui.ModernUIConstants.COMPONENT_TAG; |
||||||
|
|
||||||
|
/** |
||||||
|
* jxbrowser7 自定义 scheme 处理回调 |
||||||
|
* |
||||||
|
* @author vito |
||||||
|
* @since 11.0 |
||||||
|
* Created on 2023/6/8 |
||||||
|
*/ |
||||||
|
public class NxInterceptRequestCallback implements InterceptUrlRequestCallback { |
||||||
|
|
||||||
|
private Supplier<AssembleComponent> component; |
||||||
|
|
||||||
|
private Supplier<Map<String, String>> renderParameterBuild; |
||||||
|
|
||||||
|
public NxInterceptRequestCallback(Supplier<Map<String, String>> renderParameterBuild) { |
||||||
|
this.renderParameterBuild = renderParameterBuild; |
||||||
|
} |
||||||
|
|
||||||
|
public NxInterceptRequestCallback(Supplier<AssembleComponent> component, |
||||||
|
Supplier<Map<String, String>> renderParameterBuild) { |
||||||
|
this.component = component; |
||||||
|
this.renderParameterBuild = renderParameterBuild; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 主要包括 atom 和 emb协议的文件链接处理, |
||||||
|
* 去掉file文件协议的支持,因此jxbrowser 7之后不支持覆盖内置协议,详细见 |
||||||
|
* {@link com.teamdev.jxbrowser.net.internal.NonInterceptableScheme} |
||||||
|
* |
||||||
|
* @param params 参数 |
||||||
|
* @return 响应 |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Response on(Params params) { |
||||||
|
UrlRequest urlRequest = params.urlRequest(); |
||||||
|
String path = urlRequest.url(); |
||||||
|
Optional<UrlRequestJob> urlRequestJobOptional; |
||||||
|
if (path.startsWith(COMPONENT_TAG)) { |
||||||
|
String text = htmlText(renderParameterBuild.get()); |
||||||
|
urlRequestJobOptional = generateBasicUrlRequestJob(params, |
||||||
|
"text/html", text.getBytes(StandardCharsets.UTF_8)); |
||||||
|
} else { |
||||||
|
urlRequestJobOptional = generateFileProtocolUrlRequestJob(params, path); |
||||||
|
} |
||||||
|
return urlRequestJobOptional |
||||||
|
.map(Response::intercept) |
||||||
|
.orElseGet(Response::proceed); |
||||||
|
} |
||||||
|
|
||||||
|
protected Optional<UrlRequestJob> generateFileProtocolUrlRequestJob(Params params, String path) { |
||||||
|
try { |
||||||
|
InputStream inputStream = getResourceStream(path); |
||||||
|
String mimeType = MimeType.parseMimeType(path); |
||||||
|
byte[] bytes; |
||||||
|
if (isHtml(mimeType)) { |
||||||
|
String text = IOUtils.inputStream2String(inputStream, EncodeConstants.ENCODING_UTF_8); |
||||||
|
text = TemplateUtils.renderParameter4Tpl(text, renderParameterBuild.get()); |
||||||
|
bytes = text.getBytes(StandardCharsets.UTF_8); |
||||||
|
} else { |
||||||
|
bytes = IOUtils.inputStream2Bytes(inputStream); |
||||||
|
} |
||||||
|
return generateBasicUrlRequestJob(params, mimeType, bytes); |
||||||
|
} catch (Exception e) { |
||||||
|
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
||||||
|
} |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 获取资源文件流 |
||||||
|
* |
||||||
|
* @param path 文件路径 |
||||||
|
* @return 输入流 |
||||||
|
* @throws Exception IO异常 |
||||||
|
*/ |
||||||
|
private InputStream getResourceStream(String path) throws Exception { |
||||||
|
int index = path.indexOf("="); |
||||||
|
if (index > 0) { |
||||||
|
path = path.substring(index + 1); |
||||||
|
} else { |
||||||
|
// jxbrowser 7之后,协议会自动补齐双斜杠//
|
||||||
|
path = path.split(":/")[1]; |
||||||
|
} |
||||||
|
return IOUtils.readResource(path); |
||||||
|
} |
||||||
|
|
||||||
|
private boolean isHtml(String mimeType) { |
||||||
|
return MimeType.HTML.parseMimeType().equals(mimeType); |
||||||
|
} |
||||||
|
|
||||||
|
private Optional<UrlRequestJob> generateBasicUrlRequestJob(Params params, String mimeType, byte[] bytes) { |
||||||
|
if (StringUtils.isEmpty(mimeType)) { |
||||||
|
return Optional.empty(); |
||||||
|
} |
||||||
|
UrlRequestJob.Options options = UrlRequestJob.Options |
||||||
|
.newBuilder(HttpStatus.OK) |
||||||
|
.addHttpHeader(HttpHeader.of("Content-Type", mimeType)) |
||||||
|
.build(); |
||||||
|
UrlRequestJob urlRequestJob = params.newUrlRequestJob(options); |
||||||
|
urlRequestJob.write(bytes); |
||||||
|
urlRequestJob.complete(); |
||||||
|
return Optional.of(urlRequestJob); |
||||||
|
} |
||||||
|
|
||||||
|
private String htmlText(Map<String, String> map) { |
||||||
|
return component.get() == null ? |
||||||
|
StringUtils.EMPTY : |
||||||
|
parseComponent(component.get(), map); |
||||||
|
} |
||||||
|
|
||||||
|
@NotNull |
||||||
|
private static String parseComponent(AssembleComponent component, Map<String, String> map) { |
||||||
|
PathGroup pathGroup = AtomBuilder.create().buildAssembleFilePath(ModernRequestClient.KEY, component); |
||||||
|
StylePath[] stylePaths = pathGroup.toStylePathGroup(); |
||||||
|
StringBuilder styleText = new StringBuilder(); |
||||||
|
for (StylePath path : stylePaths) { |
||||||
|
if (StringUtils.isNotBlank(path.toFilePath())) { |
||||||
|
styleText.append("<link rel=\"stylesheet\" href=\"emb://"); |
||||||
|
styleText.append(path.toFilePath()); |
||||||
|
styleText.append("\"/>"); |
||||||
|
} |
||||||
|
} |
||||||
|
String result = ModernUIConstants.HTML_TPL.replaceAll("##style##", styleText.toString()); |
||||||
|
ScriptPath[] scriptPaths = pathGroup.toScriptPathGroup(); |
||||||
|
StringBuilder scriptText = new StringBuilder(); |
||||||
|
for (ScriptPath path : scriptPaths) { |
||||||
|
if (StringUtils.isNotBlank(path.toFilePath())) { |
||||||
|
scriptText.append("<script src=\"emb://"); |
||||||
|
scriptText.append(path.toFilePath()); |
||||||
|
scriptText.append("\"></script>"); |
||||||
|
} |
||||||
|
} |
||||||
|
result = result.replaceAll("##script##", scriptText.toString()); |
||||||
|
if (map != null) { |
||||||
|
for (Map.Entry<String, String> entry : map.entrySet()) { |
||||||
|
String key = entry.getKey(); |
||||||
|
String value = entry.getValue(); |
||||||
|
result = result.replaceAll("\\$\\{" + key + "}", value); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import com.teamdev.jxbrowser.js.JsObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* 属性构建器 |
||||||
|
* |
||||||
|
* @author vito |
||||||
|
* @since 11.0 |
||||||
|
* Created on 2023/6/8 |
||||||
|
*/ |
||||||
|
public interface PropertyBuild { |
||||||
|
/** |
||||||
|
* 构建属性 |
||||||
|
* |
||||||
|
* @param window js环境的window对象 |
||||||
|
* @return 待注入java对象 |
||||||
|
*/ |
||||||
|
Object build(JsObject window); |
||||||
|
} |
@ -1,26 +0,0 @@ |
|||||||
package com.fr.design.ui.compatible; |
|
||||||
|
|
||||||
import com.fr.design.ui.ModernUIPane; |
|
||||||
import com.teamdev.jxbrowser.browser.callback.InjectJsCallback; |
|
||||||
import com.teamdev.jxbrowser.chromium.events.LoadListener; |
|
||||||
import com.teamdev.jxbrowser.chromium.events.ScriptContextListener; |
|
||||||
import com.teamdev.jxbrowser.event.Observer; |
|
||||||
|
|
||||||
/** |
|
||||||
* 封装jxbrwoser v6/v7的构建方式的差异 |
|
||||||
* |
|
||||||
* @author hades |
|
||||||
* @version 10.0 |
|
||||||
* Created by hades on 2021/6/13 |
|
||||||
*/ |
|
||||||
public interface BuilderDiff<T> { |
|
||||||
|
|
||||||
ModernUIPane.Builder<T> prepareForV6(ScriptContextListener contextListener); |
|
||||||
|
|
||||||
ModernUIPane.Builder<T> prepareForV6(LoadListener loadListener); |
|
||||||
|
|
||||||
ModernUIPane.Builder<T> prepareForV7(InjectJsCallback callback); |
|
||||||
|
|
||||||
ModernUIPane.Builder<T> prepareForV7(Class event, Observer listener); |
|
||||||
|
|
||||||
} |
|
@ -1,36 +0,0 @@ |
|||||||
package com.fr.design.ui.compatible; |
|
||||||
|
|
||||||
import com.fr.design.ui.ModernUIPane; |
|
||||||
import com.fr.stable.os.OperatingSystem; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author hades |
|
||||||
* @version 10.0 |
|
||||||
* Created by hades on 2021/6/13 |
|
||||||
*/ |
|
||||||
public class ModernUIPaneFactory { |
|
||||||
|
|
||||||
public static <T> ModernUIPane.Builder<T> modernUIPaneBuilder() { |
|
||||||
|
|
||||||
if (isV7()) { |
|
||||||
return new NewModernUIPane.Builder<>(); |
|
||||||
} else { |
|
||||||
return new ModernUIPane.Builder<>(); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
public static boolean isV7() { |
|
||||||
|
|
||||||
// 7.15的class不存在时 走老版本
|
|
||||||
boolean hasJxBrowserV7_15 = true; |
|
||||||
try { |
|
||||||
Class.forName("com.teamdev.jxbrowser.net.Scheme"); |
|
||||||
} catch (ClassNotFoundException e) { |
|
||||||
hasJxBrowserV7_15 = false; |
|
||||||
} |
|
||||||
|
|
||||||
return OperatingSystem.isWindows() && hasJxBrowserV7_15; |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
@ -1,362 +0,0 @@ |
|||||||
package com.fr.design.ui.compatible; |
|
||||||
|
|
||||||
import com.fr.design.DesignerEnvManager; |
|
||||||
import com.fr.design.gui.ibutton.UIButton; |
|
||||||
import com.fr.design.gui.itoolbar.UIToolbar; |
|
||||||
import com.fr.design.i18n.Toolkit; |
|
||||||
import com.fr.design.ui.ModernUIConstants; |
|
||||||
import com.fr.design.ui.ModernUIPane; |
|
||||||
import com.fr.design.utils.gui.GUICoreUtils; |
|
||||||
import com.fr.web.struct.AssembleComponent; |
|
||||||
import com.teamdev.jxbrowser.browser.Browser; |
|
||||||
import com.teamdev.jxbrowser.browser.callback.InjectJsCallback; |
|
||||||
import com.teamdev.jxbrowser.chromium.events.LoadListener; |
|
||||||
import com.teamdev.jxbrowser.chromium.events.ScriptContextListener; |
|
||||||
import com.teamdev.jxbrowser.engine.Engine; |
|
||||||
import com.teamdev.jxbrowser.engine.EngineOptions; |
|
||||||
import com.teamdev.jxbrowser.engine.RenderingMode; |
|
||||||
import com.teamdev.jxbrowser.event.Observer; |
|
||||||
import com.teamdev.jxbrowser.js.JsObject; |
|
||||||
import com.teamdev.jxbrowser.net.Network; |
|
||||||
import com.teamdev.jxbrowser.net.Scheme; |
|
||||||
import com.teamdev.jxbrowser.net.callback.VerifyCertificateCallback; |
|
||||||
import com.teamdev.jxbrowser.view.swing.BrowserView; |
|
||||||
import org.jetbrains.annotations.Nullable; |
|
||||||
|
|
||||||
|
|
||||||
import java.awt.BorderLayout; |
|
||||||
import java.awt.Dimension; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
import javax.swing.JDialog; |
|
||||||
import javax.swing.SwingUtilities; |
|
||||||
import javax.swing.WindowConstants; |
|
||||||
|
|
||||||
/** |
|
||||||
* 基于v7 jxbrowser实现 |
|
||||||
* |
|
||||||
* @author richie |
|
||||||
* @version 10.0 |
|
||||||
* Created by richie on 2019-03-04 |
|
||||||
* 用于加载html5的Swing容器,可以在设计选项设置中打开调试窗口,示例可查看:com.fr.design.ui.ModernUIPaneTest |
|
||||||
*/ |
|
||||||
public class NewModernUIPane<T> extends ModernUIPane<T> { |
|
||||||
|
|
||||||
private Browser browser; |
|
||||||
private String namespace = "Pool"; |
|
||||||
private String variable = "data"; |
|
||||||
private String expression = "update()"; |
|
||||||
private Scheme scheme; |
|
||||||
private NxInterceptRequestCallback requestCallback; |
|
||||||
|
|
||||||
private NewModernUIPane() { |
|
||||||
super(); |
|
||||||
initialize(); |
|
||||||
} |
|
||||||
|
|
||||||
private void initialize() { |
|
||||||
setLayout(new BorderLayout()); |
|
||||||
if (browser == null) { |
|
||||||
if (DesignerEnvManager.getEnvManager().isOpenDebug()) { |
|
||||||
UIToolbar toolbar = new UIToolbar(); |
|
||||||
add(toolbar, BorderLayout.NORTH); |
|
||||||
UIButton openDebugButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Open_Debug_Window")); |
|
||||||
toolbar.add(openDebugButton); |
|
||||||
UIButton reloadButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Reload")); |
|
||||||
toolbar.add(reloadButton); |
|
||||||
UIButton closeButton = new UIButton(Toolkit.i18nText("Fine-Design_Basic_Close_Window")); |
|
||||||
toolbar.add(closeButton); |
|
||||||
|
|
||||||
openDebugButton.addActionListener(e -> showDebuggerDialog()); |
|
||||||
|
|
||||||
reloadButton.addActionListener(e -> browser.navigation().reloadIgnoringCache()); |
|
||||||
|
|
||||||
closeButton.addActionListener(e -> SwingUtilities.getWindowAncestor( |
|
||||||
NewModernUIPane.this).setVisible(false)); |
|
||||||
initializeBrowser(); |
|
||||||
add(BrowserView.newInstance(browser), BorderLayout.CENTER); |
|
||||||
} else { |
|
||||||
initializeBrowser(); |
|
||||||
add(BrowserView.newInstance(browser), BorderLayout.CENTER); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private void showDebuggerDialog() { |
|
||||||
JDialog dialog = new JDialog(SwingUtilities.getWindowAncestor(this)); |
|
||||||
|
|
||||||
Browser debugger = browser.engine().newBrowser(); |
|
||||||
BrowserView debuggerView = BrowserView.newInstance(debugger); |
|
||||||
dialog.add(debuggerView, BorderLayout.CENTER); |
|
||||||
dialog.setSize(new Dimension(800, 400)); |
|
||||||
GUICoreUtils.centerWindow(dialog); |
|
||||||
dialog.setVisible(true); |
|
||||||
dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); |
|
||||||
|
|
||||||
browser.devTools().remoteDebuggingUrl().ifPresent(url -> { |
|
||||||
debugger.navigation().loadUrl(url); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
private void initializeBrowser() { |
|
||||||
EngineOptions.Builder builder; |
|
||||||
if (scheme != null && requestCallback != null) { |
|
||||||
builder = EngineOptions.newBuilder(RenderingMode.HARDWARE_ACCELERATED).addSwitch("--disable-google-traffic").addScheme(scheme, requestCallback); |
|
||||||
} else { |
|
||||||
builder = EngineOptions.newBuilder(RenderingMode.HARDWARE_ACCELERATED).addSwitch("--disable-google-traffic"); |
|
||||||
} |
|
||||||
|
|
||||||
if (DesignerEnvManager.getEnvManager().isOpenDebug()) { |
|
||||||
builder.remoteDebuggingPort(9222); |
|
||||||
} |
|
||||||
|
|
||||||
Engine engine = Engine.newInstance(builder.build()); |
|
||||||
if (DesignerEnvManager.getEnvManager().isOpenDebug()) { |
|
||||||
// 调试模式下,禁止HTTPS证书验证,使得可以正常访问商城测试服务器等
|
|
||||||
Network network = engine.network(); |
|
||||||
network.set(VerifyCertificateCallback.class, new VerifyCertificateCallback() { |
|
||||||
@Nullable |
|
||||||
@Override |
|
||||||
public Response on(Params params) { |
|
||||||
return VerifyCertificateCallback.Response.valid(); |
|
||||||
} |
|
||||||
}); |
|
||||||
} |
|
||||||
browser = engine.newBrowser(); |
|
||||||
|
|
||||||
// 初始化的时候,就把命名空间对象初始化好,确保window.a.b.c("a.b.c"为命名空间)对象都是初始化过的
|
|
||||||
browser.set(InjectJsCallback.class, params -> { |
|
||||||
params.frame().executeJavaScript(String.format(ModernUIConstants.SCRIPT_INIT_NAME_SPACE, namespace)); |
|
||||||
return InjectJsCallback.Response.proceed(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 转向一个新的地址,相当于重新加载 |
|
||||||
* |
|
||||||
* @param url 新的地址 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public void redirect(String url) { |
|
||||||
browser.navigation().loadUrl(url); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 转向一个新的地址,相当于重新加载 |
|
||||||
* |
|
||||||
* @param url 新的地址 |
|
||||||
* @param map 初始化参数 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public void redirect(String url, Map<String, String> map) { |
|
||||||
if (requestCallback != null) { |
|
||||||
requestCallback.setMap(map); |
|
||||||
} |
|
||||||
browser.navigation().loadUrl(url); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected String title4PopupWindow() { |
|
||||||
return "Modern"; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void populate(final T t) { |
|
||||||
browser.set(InjectJsCallback.class, params -> { |
|
||||||
JsObject ns = params.frame().executeJavaScript("window." + namespace); |
|
||||||
if (ns != null) { |
|
||||||
ns.putProperty(variable, t); |
|
||||||
} |
|
||||||
return InjectJsCallback.Response.proceed(); |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public T update() { |
|
||||||
if (browser.mainFrame().isPresent()) { |
|
||||||
return browser.mainFrame().get().executeJavaScript("window." + namespace + "." + expression); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
public void disposeBrowser() { |
|
||||||
|
|
||||||
if (browser != null) { |
|
||||||
browser.engine().close(); |
|
||||||
browser = null; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
public void clearCache() { |
|
||||||
if (browser != null) { |
|
||||||
browser.engine().httpCache().clear(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void executeJavaScript(String javaScript) { |
|
||||||
if (browser != null) { |
|
||||||
browser.mainFrame().ifPresent(frame -> { |
|
||||||
frame.executeJavaScript(javaScript); |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static class Builder<T> extends ModernUIPane.Builder<T> { |
|
||||||
|
|
||||||
private NewModernUIPane<T> pane = new NewModernUIPane<>(); |
|
||||||
|
|
||||||
public Builder() { |
|
||||||
super((ModernUIPane<T>)null); |
|
||||||
} |
|
||||||
|
|
||||||
public NewModernUIPane.Builder<T> prepare(InjectJsCallback callback) { |
|
||||||
pane.browser.set(InjectJsCallback.class, callback); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 加载jar包中的资源 |
|
||||||
* |
|
||||||
* @param path 资源路径 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> withEMB(final String path) { |
|
||||||
pane.scheme = Scheme.of("emb"); |
|
||||||
pane.requestCallback = new NxComplexInterceptRequestCallback(null); |
|
||||||
pane.browser.navigation().loadUrl("emb:" + path); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 加载url指向的资源 |
|
||||||
* |
|
||||||
* @param url 文件的地址 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> withURL(final String url) { |
|
||||||
pane.scheme = Scheme.of("file"); |
|
||||||
pane.requestCallback = new NxComplexInterceptRequestCallback(null); |
|
||||||
pane.browser.navigation().loadUrl(url); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 加载url指向的资源 |
|
||||||
* |
|
||||||
* @param url 文件的地址 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> withURL(final String url, Map<String, String> map) { |
|
||||||
pane.scheme = Scheme.of("file"); |
|
||||||
pane.requestCallback = new NxInterceptRequestCallback(map); |
|
||||||
pane.browser.navigation().loadUrl(url); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 加载Atom组件 |
|
||||||
* |
|
||||||
* @param component Atom组件 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> withComponent(AssembleComponent component) { |
|
||||||
pane.scheme = Scheme.of("emb"); |
|
||||||
pane.requestCallback = new NxComplexInterceptRequestCallback(component); |
|
||||||
pane.browser.navigation().loadUrl("emb:dynamic"); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 加载Atom组件 |
|
||||||
* |
|
||||||
* @param component Atom组件 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> withComponent(AssembleComponent component, Map<String, String> map) { |
|
||||||
pane.scheme = Scheme.of("emb"); |
|
||||||
pane.requestCallback = new NxComplexInterceptRequestCallback(component, map); |
|
||||||
pane.browser.navigation().loadUrl("emb:dynamic"); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* 加载html文本内容 |
|
||||||
* |
|
||||||
* @param html 要加载html文本内容 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> withHTML(String html) { |
|
||||||
pane.scheme = Scheme.of("html"); |
|
||||||
pane.requestCallback = new NxInterceptRequestCallback(); |
|
||||||
pane.browser.mainFrame().ifPresent(frame -> { |
|
||||||
frame.loadHtml(html); |
|
||||||
}); |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* 设置该前端页面做数据交换所使用的对象 |
|
||||||
* |
|
||||||
* @param namespace 对象名 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> namespace(String namespace) { |
|
||||||
pane.namespace = namespace; |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* java端往js端传数据时使用的变量名字 |
|
||||||
* |
|
||||||
* @param name 变量的名字 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> variable(String name) { |
|
||||||
pane.variable = name; |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* js端往java端传数据时执行的函数表达式 |
|
||||||
* |
|
||||||
* @param expression 函数表达式 |
|
||||||
*/ |
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> expression(String expression) { |
|
||||||
pane.expression = expression; |
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> prepareForV6(ScriptContextListener contextListener) { |
|
||||||
// do nothing
|
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> prepareForV6(LoadListener loadListener) { |
|
||||||
// do nothing
|
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public NewModernUIPane.Builder<T> prepareForV7(InjectJsCallback callback) { |
|
||||||
return prepare(callback); |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public ModernUIPane.Builder<T> prepareForV7(Class event, Observer listener) { |
|
||||||
|
|
||||||
pane.browser.navigation().on(event, listener); |
|
||||||
|
|
||||||
return this; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public NewModernUIPane<T> build() { |
|
||||||
return pane; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,86 +0,0 @@ |
|||||||
package com.fr.design.ui.compatible; |
|
||||||
|
|
||||||
import com.fr.design.ui.ModernRequestClient; |
|
||||||
import com.fr.design.ui.ModernUIConstants; |
|
||||||
import com.fr.general.IOUtils; |
|
||||||
import com.fr.stable.StringUtils; |
|
||||||
import com.fr.web.struct.AssembleComponent; |
|
||||||
import com.fr.web.struct.AtomBuilder; |
|
||||||
import com.fr.web.struct.PathGroup; |
|
||||||
import com.fr.web.struct.category.ScriptPath; |
|
||||||
import com.fr.web.struct.category.StylePath; |
|
||||||
|
|
||||||
import java.io.InputStream; |
|
||||||
import java.nio.charset.StandardCharsets; |
|
||||||
import java.util.Map; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author richie |
|
||||||
* @version 10.0 |
|
||||||
* Created by richie on 2020/3/25 |
|
||||||
*/ |
|
||||||
public class NxComplexInterceptRequestCallback extends NxInterceptRequestCallback { |
|
||||||
|
|
||||||
private AssembleComponent component; |
|
||||||
|
|
||||||
public NxComplexInterceptRequestCallback(AssembleComponent component) { |
|
||||||
this.component = component; |
|
||||||
} |
|
||||||
|
|
||||||
public NxComplexInterceptRequestCallback(AssembleComponent component, Map<String, String> map) { |
|
||||||
super(map); |
|
||||||
this.component = component; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected Response next(Params params, String path) { |
|
||||||
if (path.startsWith("emb:dynamic")) { |
|
||||||
String text = htmlText(map); |
|
||||||
return Response.intercept(generateBasicUrlRequestJob(params, "text/html", text.getBytes(StandardCharsets.UTF_8))); |
|
||||||
} else { |
|
||||||
int index = path.indexOf("="); |
|
||||||
if (index > 0) { |
|
||||||
path = path.substring(index + 1); |
|
||||||
} else { |
|
||||||
path = path.substring(4); |
|
||||||
} |
|
||||||
InputStream inputStream = IOUtils.readResource(path); |
|
||||||
if (inputStream == null) { |
|
||||||
return Response.proceed(); |
|
||||||
} |
|
||||||
return Response.intercept(generateBasicUrlRequestJob(params, getMimeType(path), IOUtils.inputStream2Bytes(inputStream))); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
private String htmlText(Map<String, String> map) { |
|
||||||
PathGroup pathGroup = AtomBuilder.create().buildAssembleFilePath(ModernRequestClient.KEY, component); |
|
||||||
StylePath[] stylePaths = pathGroup.toStylePathGroup(); |
|
||||||
StringBuilder styleText = new StringBuilder(); |
|
||||||
for (StylePath path : stylePaths) { |
|
||||||
if (StringUtils.isNotBlank(path.toFilePath())) { |
|
||||||
styleText.append("<link rel=\"stylesheet\" href=\"emb:"); |
|
||||||
styleText.append(path.toFilePath()); |
|
||||||
styleText.append("\"/>"); |
|
||||||
} |
|
||||||
} |
|
||||||
String result = ModernUIConstants.HTML_TPL.replaceAll("##style##", styleText.toString()); |
|
||||||
ScriptPath[] scriptPaths = pathGroup.toScriptPathGroup(); |
|
||||||
StringBuilder scriptText = new StringBuilder(); |
|
||||||
for (ScriptPath path : scriptPaths) { |
|
||||||
if (StringUtils.isNotBlank(path.toFilePath())) { |
|
||||||
scriptText.append("<script src=\"emb:"); |
|
||||||
scriptText.append(path.toFilePath()); |
|
||||||
scriptText.append("\"></script>"); |
|
||||||
} |
|
||||||
} |
|
||||||
result = result.replaceAll("##script##", scriptText.toString()); |
|
||||||
if (map != null) { |
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) { |
|
||||||
String key = entry.getKey(); |
|
||||||
String value = entry.getValue(); |
|
||||||
result = result.replaceAll("\\$\\{" + key + "}", value); |
|
||||||
} |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
} |
|
@ -1,148 +0,0 @@ |
|||||||
package com.fr.design.ui.compatible; |
|
||||||
|
|
||||||
import com.fr.base.TemplateUtils; |
|
||||||
import com.fr.general.IOUtils; |
|
||||||
import com.fr.log.FineLoggerFactory; |
|
||||||
import com.fr.stable.ArrayUtils; |
|
||||||
import com.fr.stable.EncodeConstants; |
|
||||||
import com.fr.stable.StringUtils; |
|
||||||
import com.fr.third.org.apache.commons.codec.net.URLCodec; |
|
||||||
import com.teamdev.jxbrowser.net.HttpHeader; |
|
||||||
import com.teamdev.jxbrowser.net.HttpStatus; |
|
||||||
import com.teamdev.jxbrowser.net.UrlRequest; |
|
||||||
import com.teamdev.jxbrowser.net.UrlRequestJob; |
|
||||||
|
|
||||||
import com.teamdev.jxbrowser.net.callback.InterceptUrlRequestCallback; |
|
||||||
import java.io.File; |
|
||||||
import java.io.IOException; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.net.URI; |
|
||||||
import java.nio.charset.StandardCharsets; |
|
||||||
import java.nio.file.Files; |
|
||||||
import java.nio.file.Path; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Optional; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author richie |
|
||||||
* @version 10.0 |
|
||||||
* Created by richie on 2020/3/25 |
|
||||||
*/ |
|
||||||
public class NxInterceptRequestCallback implements InterceptUrlRequestCallback { |
|
||||||
|
|
||||||
Map<String, String> map; |
|
||||||
|
|
||||||
public NxInterceptRequestCallback() { |
|
||||||
} |
|
||||||
|
|
||||||
public NxInterceptRequestCallback(Map<String, String> map) { |
|
||||||
this.map = map; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public Response on(Params params) { |
|
||||||
UrlRequest urlRequest = params.urlRequest(); |
|
||||||
String path = urlRequest.url(); |
|
||||||
if (path.startsWith("file:")) { |
|
||||||
Optional<UrlRequestJob> optional = generateFileProtocolUrlRequestJob(params, path); |
|
||||||
if (optional.isPresent()) { |
|
||||||
return Response.intercept(optional.get()); |
|
||||||
} |
|
||||||
} else { |
|
||||||
return next(params, path); |
|
||||||
} |
|
||||||
return Response.proceed(); |
|
||||||
} |
|
||||||
|
|
||||||
Response next(Params params, String path) { |
|
||||||
return Response.proceed(); |
|
||||||
} |
|
||||||
|
|
||||||
private Optional<UrlRequestJob> generateFileProtocolUrlRequestJob(Params params, String path) { |
|
||||||
try { |
|
||||||
String url = new URLCodec().decode(path); |
|
||||||
String filePath = TemplateUtils.renderParameter4Tpl(url, map); |
|
||||||
File file = new File(URI.create(filePath).getPath()); |
|
||||||
InputStream inputStream = IOUtils.readResource(file.getAbsolutePath()); |
|
||||||
String mimeType = getMimeType(path); |
|
||||||
byte[] bytes; |
|
||||||
if (isPlainText(mimeType)) { |
|
||||||
String text = IOUtils.inputStream2String(inputStream, EncodeConstants.ENCODING_UTF_8); |
|
||||||
text = TemplateUtils.renderParameter4Tpl(text, map); |
|
||||||
bytes = text.getBytes(StandardCharsets.UTF_8); |
|
||||||
} else { |
|
||||||
bytes = IOUtils.inputStream2Bytes(inputStream); |
|
||||||
} |
|
||||||
return Optional.of(generateBasicUrlRequestJob(params, mimeType, bytes)); |
|
||||||
} catch (Exception e) { |
|
||||||
FineLoggerFactory.getLogger().error(e.getMessage(), e); |
|
||||||
} |
|
||||||
return Optional.empty(); |
|
||||||
} |
|
||||||
|
|
||||||
private boolean isPlainText(String mimeType) { |
|
||||||
return ArrayUtils.contains(new String[]{"text/html", "text/javascript", "text/css"}, mimeType); |
|
||||||
} |
|
||||||
|
|
||||||
UrlRequestJob generateBasicUrlRequestJob(Params params, String mimeType, byte[] bytes) { |
|
||||||
UrlRequestJob.Options options = UrlRequestJob.Options |
|
||||||
.newBuilder(HttpStatus.OK) |
|
||||||
.addHttpHeader(HttpHeader.of("Content-Type", mimeType)) |
|
||||||
.build(); |
|
||||||
UrlRequestJob urlRequestJob = params.newUrlRequestJob(options); |
|
||||||
urlRequestJob.write(bytes); |
|
||||||
urlRequestJob.complete(); |
|
||||||
return urlRequestJob; |
|
||||||
} |
|
||||||
|
|
||||||
String getMimeType(String path) { |
|
||||||
// 去除 xxx?xxx 后面部分
|
|
||||||
int index = path.indexOf("?"); |
|
||||||
if (index != -1) { |
|
||||||
path = path.substring(0, path.indexOf("?")); |
|
||||||
} |
|
||||||
if (StringUtils.isBlank(path)) { |
|
||||||
return "text/html"; |
|
||||||
} |
|
||||||
if (path.endsWith(".html")) { |
|
||||||
return "text/html"; |
|
||||||
} |
|
||||||
if (path.endsWith(".css")) { |
|
||||||
return "text/css"; |
|
||||||
} |
|
||||||
if (path.endsWith(".js")) { |
|
||||||
return "text/javascript"; |
|
||||||
} |
|
||||||
if (path.endsWith(".svg")) { |
|
||||||
return "image/svg+xml"; |
|
||||||
} |
|
||||||
if (path.endsWith(".png")) { |
|
||||||
return "image/png"; |
|
||||||
} |
|
||||||
if (path.endsWith(".jpeg")) { |
|
||||||
return "image/jpeg"; |
|
||||||
} |
|
||||||
if (path.endsWith(".gif")) { |
|
||||||
return "image/gif"; |
|
||||||
} |
|
||||||
if (path.endsWith(".woff")) { |
|
||||||
return "font/woff"; |
|
||||||
} |
|
||||||
if (path.endsWith(".ttf")) { |
|
||||||
return "truetype"; |
|
||||||
} |
|
||||||
if (path.endsWith(".eot")) { |
|
||||||
return "embedded-opentype"; |
|
||||||
} |
|
||||||
Path file = new File(path).toPath(); |
|
||||||
try { |
|
||||||
return Files.probeContentType(file); |
|
||||||
} catch (IOException e) { |
|
||||||
return "text/html"; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public void setMap(Map<String, String> map) { |
|
||||||
this.map = map; |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,72 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import com.fr.design.DesignerEnvManager; |
||||||
|
import com.teamdev.jxbrowser.js.JsAccessible; |
||||||
|
|
||||||
|
import javax.swing.JButton; |
||||||
|
import javax.swing.JFrame; |
||||||
|
import javax.swing.JOptionPane; |
||||||
|
import javax.swing.JPanel; |
||||||
|
import javax.swing.WindowConstants; |
||||||
|
import java.awt.BorderLayout; |
||||||
|
import java.awt.FlowLayout; |
||||||
|
|
||||||
|
public class JxUIPaneTest { |
||||||
|
|
||||||
|
public static void main(String... args) { |
||||||
|
final JFrame frame = new JFrame(); |
||||||
|
frame.setSize(1200, 800); |
||||||
|
JPanel contentPane = (JPanel) frame.getContentPane(); |
||||||
|
// 是否需要开启调试窗口
|
||||||
|
DesignerEnvManager.getEnvManager().setOpenDebug(true); |
||||||
|
final JxUIPane<Model> pane = new JxUIPane.Builder<Model>() |
||||||
|
.withEMB("/com/fr/design/ui/demo.html").namespace("Pool").build(); |
||||||
|
contentPane.add(pane, BorderLayout.CENTER); |
||||||
|
|
||||||
|
Model model = new Model(); |
||||||
|
model.setAge(20); |
||||||
|
model.setName("Pick"); |
||||||
|
pane.populate(model); |
||||||
|
|
||||||
|
JPanel panel = new JPanel(new FlowLayout()); |
||||||
|
contentPane.add(panel, BorderLayout.SOUTH); |
||||||
|
JButton button = new JButton("点击我可以看到Swing的弹框,输出填写的信息"); |
||||||
|
panel.add(button); |
||||||
|
button.addActionListener(e -> { |
||||||
|
Model returnValue = pane.update(); |
||||||
|
if (returnValue != null) { |
||||||
|
JOptionPane.showMessageDialog(frame, String.format("姓名为:%s,年龄为:%d", returnValue.getName(), returnValue.getAge())); |
||||||
|
} |
||||||
|
}); |
||||||
|
frame.setVisible(true); |
||||||
|
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); |
||||||
|
} |
||||||
|
|
||||||
|
@JsAccessible |
||||||
|
public static class Model { |
||||||
|
private String name; |
||||||
|
private int age; |
||||||
|
|
||||||
|
public String getName() { |
||||||
|
return name; |
||||||
|
} |
||||||
|
|
||||||
|
public void setName(String name) { |
||||||
|
this.name = name; |
||||||
|
} |
||||||
|
|
||||||
|
public int getAge() { |
||||||
|
return age; |
||||||
|
} |
||||||
|
|
||||||
|
public void setAge(int age) { |
||||||
|
this.age = age; |
||||||
|
} |
||||||
|
|
||||||
|
public void print(String message) { |
||||||
|
System.out.println(message); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
package com.fr.design.jxbrowser; |
||||||
|
|
||||||
|
import org.junit.Assert; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author vito |
||||||
|
* @since 11.0 |
||||||
|
* Created on 2023/6/13 |
||||||
|
*/ |
||||||
|
public class MimeTypeTest { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getMimeType() { |
||||||
|
Assert.assertEquals("text/html", MimeType.parseMimeType("http://a.html")); |
||||||
|
Assert.assertEquals("text/html", MimeType.parseMimeType("http://a.html?a=ji")); |
||||||
|
Assert.assertEquals("text/html", MimeType.parseMimeType("http://a.xml?a=ji")); |
||||||
|
Assert.assertEquals("image/jpeg", MimeType.parseMimeType("http://a.jpg?a=ji")); |
||||||
|
Assert.assertEquals("image/jpeg", MimeType.parseMimeType("http://a.jpeg?a=ji")); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue