diff --git a/config/gui.yaml b/config/gui.yaml new file mode 100644 index 0000000..f4e2a49 --- /dev/null +++ b/config/gui.yaml @@ -0,0 +1,7 @@ +--- +autoStart: false +silentStartup: false +proxyInfo: {} +proxySetup: "no_proxy" +language: "zh" +aListVersion: "unknown" diff --git a/pom.xml b/pom.xml index 590bfac..4612d77 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ cn.octopusyan alist-gui - 0.1.0 + 0.1 alist-gui @@ -15,7 +15,7 @@ 2024 - alist windows gui + AList windows gui 17 @@ -28,11 +28,13 @@ 17.0.6 2.0.16 1.4.14 - 2.0.52 - 5.8.25 + 5.8.32 3.16.0 - 2.16.1 + 1.4.0 + 1.18.32 + 12.3.1 + 1.0.1 @@ -48,6 +50,13 @@ ${javafx.version} + + io.github.mkpaz + atlantafx-base + 2.0.1 + + + @@ -87,12 +96,6 @@ commons-lang3 ${common-lang3.version} - - - commons-io - commons-io - ${common-io.version} - org.apache.commons @@ -100,24 +103,45 @@ ${common-exec.version} - - + - com.alibaba.fastjson2 - fastjson2 - ${fastjson.version} + cn.hutool + hutool-core + ${hutool.version} + + + + org.projectlombok + lombok + ${lombok.version} + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.15.4 org.kordamp.ikonli ikonli-javafx - 12.3.1 + ${ikonli.version} + + + org.kordamp.ikonli + ikonli-fontawesome-pack + ${ikonli.version} org.kordamp.ikonli ikonli-coreui-pack - 12.3.1 + ${ikonli.version} + + + com.gluonhq + emoji + ${gluonhq-emoji.version} @@ -164,6 +188,7 @@ exe dll + ttf diff --git a/src/main/java/cn/octopusyan/alistgui/Application.java b/src/main/java/cn/octopusyan/alistgui/Application.java index 1be5558..6bd56c3 100644 --- a/src/main/java/cn/octopusyan/alistgui/Application.java +++ b/src/main/java/cn/octopusyan/alistgui/Application.java @@ -1,79 +1,82 @@ package cn.octopusyan.alistgui; +import atlantafx.base.theme.PrimerLight; import cn.octopusyan.alistgui.config.AppConstant; -import cn.octopusyan.alistgui.config.CustomConfig; -import cn.octopusyan.alistgui.controller.MainController; +import cn.octopusyan.alistgui.config.ConfigManager; +import cn.octopusyan.alistgui.config.Context; +import cn.octopusyan.alistgui.enums.ProxySetup; import cn.octopusyan.alistgui.manager.http.HttpConfig; import cn.octopusyan.alistgui.manager.http.HttpUtil; import cn.octopusyan.alistgui.manager.thread.ThreadPoolManager; import cn.octopusyan.alistgui.util.AlertUtil; -import cn.octopusyan.alistgui.util.FxmlUtil; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; +import javafx.application.Platform; import javafx.scene.Scene; -import javafx.scene.paint.Color; import javafx.stage.Stage; import javafx.stage.StageStyle; +import lombok.Getter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.net.InetSocketAddress; import java.net.ProxySelector; -import java.util.Objects; public class Application extends javafx.application.Application { private static final Logger logger = LoggerFactory.getLogger(Application.class); + @Getter + private static Stage primaryStage; @Override public void init() throws Exception { logger.info("application init ..."); // 初始化客户端配置 - CustomConfig.init(); + ConfigManager.load(); } @Override public void start(Stage primaryStage) throws IOException { + Application.primaryStage = primaryStage; logger.info("application start ..."); // 初始化弹窗工具 AlertUtil.initOwner(primaryStage); - // http请求工具初始化 - HttpConfig httpConfig = new HttpConfig(); - if (CustomConfig.hasProxy()) { - InetSocketAddress unresolved = InetSocketAddress.createUnresolved(CustomConfig.proxyHost(), CustomConfig.proxyPort()); - httpConfig.setProxySelector(ProxySelector.of(unresolved)); - } - httpConfig.setConnectTimeout(10); - HttpUtil.init(httpConfig); - // 全局异常处理 Thread.setDefaultUncaughtExceptionHandler(this::showErrorDialog); Thread.currentThread().setUncaughtExceptionHandler(this::showErrorDialog); - // 启动主界面 - try { - FXMLLoader loader = FxmlUtil.load("root-view"); - loader.setControllerFactory(c -> new MainController(primaryStage)); - Parent root = loader.load();//底层面板 + // http请求工具初始化 + HttpConfig httpConfig = new HttpConfig(); - Scene scene = new Scene(root); - scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm()); - scene.setFill(Color.TRANSPARENT); - - primaryStage.setScene(scene); - primaryStage.initStyle(StageStyle.TRANSPARENT); - primaryStage.setTitle(String.format("%s v%s", AppConstant.APP_TITLE, AppConstant.APP_VERSION)); - primaryStage.show(); - - MainController controller = loader.getController(); - controller.setApplication(this); - } catch (Throwable t) { - showErrorDialog(Thread.currentThread(), t); + if(!ProxySetup.NO_PROXY.equals(ConfigManager.proxySetup())) { + // 系统代理 + if (ProxySetup.SYSTEM.equals(ConfigManager.proxySetup())) { + httpConfig.setProxySelector(ProxySelector.getDefault()); + } + // 自定义代理 + if (ProxySetup.MANUAL.equals(ConfigManager.proxySetup()) && ConfigManager.hasProxy()) { + InetSocketAddress unresolved = InetSocketAddress.createUnresolved(ConfigManager.proxyHost(), ConfigManager.getProxyPort()); + httpConfig.setProxySelector(ProxySelector.of(unresolved)); + } } + httpConfig.setConnectTimeout(10); + HttpUtil.init(httpConfig); + + // i18n + Context.setLanguage(ConfigManager.language()); + + // 主题样式 + Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); + + // 启动主界面 + Scene scene = Context.initScene(); + primaryStage.setScene(scene); + primaryStage.initStyle(StageStyle.TRANSPARENT); + primaryStage.setTitle(String.format("%s v%s", AppConstant.APP_TITLE, AppConstant.APP_VERSION)); + primaryStage.show(); + logger.info("application start over ..."); } @@ -85,9 +88,12 @@ public class Application extends javafx.application.Application { @Override public void stop() throws Exception { logger.info("application stop ..."); + // 保存应用数据 + ConfigManager.save(); // 停止所有线程 ThreadPoolManager.getInstance().shutdown(); - // 保存应用数据 - CustomConfig.store(); + // 关闭主界面 + Platform.exit(); + System.exit(0); } } \ No newline at end of file diff --git a/src/main/java/cn/octopusyan/alistgui/base/BaseController.java b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java index 4237148..1b414f2 100644 --- a/src/main/java/cn/octopusyan/alistgui/base/BaseController.java +++ b/src/main/java/cn/octopusyan/alistgui/base/BaseController.java @@ -1,26 +1,16 @@ package cn.octopusyan.alistgui.base; -import cn.octopusyan.alistgui.config.AppConstant; +import cn.octopusyan.alistgui.Application; +import cn.octopusyan.alistgui.config.Context; import cn.octopusyan.alistgui.util.FxmlUtil; -import javafx.application.Application; -import javafx.application.Platform; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.fxml.FXMLLoader; +import javafx.fxml.FXML; import javafx.fxml.Initializable; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.scene.control.Label; import javafx.scene.layout.Pane; -import javafx.stage.Modality; import javafx.stage.Stage; -import javafx.stage.Window; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; import java.net.URL; -import java.util.Objects; import java.util.ResourceBundle; /** @@ -30,58 +20,14 @@ import java.util.ResourceBundle; */ public abstract class BaseController

implements Initializable { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - private Application application; - private final Stage primaryStage; - private double xOffSet = 0, yOffSet = 0; - protected BaseController(Stage primaryStage) { - this.primaryStage = primaryStage; - } - - public void jumpTo(BaseController

controller) throws IOException { - FXMLLoader fxmlLoader = FxmlUtil.load(controller.getRootFxml()); - - Scene scene = getRootPanel().getScene(); - double oldHeight = getRootPanel().getPrefHeight(); - double oldWidth = getRootPanel().getPrefWidth(); - - Pane root = fxmlLoader.load(); - Stage stage = (Stage) scene.getWindow(); - // 窗口大小 - double newWidth = root.getPrefWidth(); - double newHeight = root.getPrefHeight(); - // 窗口位置 - double newX = stage.getX() - (newWidth - oldWidth) / 2; - double newY = stage.getY() - (newHeight - oldHeight) / 2; - scene.setRoot(root); - stage.setX(newX < 0 ? 0 : newX); - stage.setY(newY < 0 ? 0 : newY); - stage.setWidth(newWidth); - stage.setHeight(newHeight); - - controller = fxmlLoader.getController(); - controller.setApplication(getApplication()); - } - - protected void open(Class> clazz, String title) { - try { - FXMLLoader load = FxmlUtil.load(clazz.getDeclaredConstructor().newInstance().getRootFxml()); - Parent root = load.load(); - Scene scene = new Scene(root); - scene.getStylesheets().addAll(Objects.requireNonNull(getClass().getResource("/css/root.css")).toExternalForm()); - Stage stage = new Stage(); - stage.setScene(scene); - stage.setTitle(title); - stage.initOwner(getWindow()); - stage.initModality(Modality.WINDOW_MODAL); - stage.show(); - load.getController(); - } catch (Exception e) { - logger.error("", e); - } + public BaseController() { + //初始化时保存当前Controller实例 + Context.getControllers().put(this.getClass().getSimpleName(), this); } + @FXML @Override public void initialize(URL url, ResourceBundle resourceBundle) { // 全局窗口拖拽 @@ -92,46 +38,19 @@ public abstract class BaseController

implements Initializable { yOffSet = event.getSceneY(); }); getRootPanel().setOnMouseDragged(event -> { - Stage stage = (Stage) getWindow(); - stage.setX(event.getScreenX() - xOffSet); - stage.setY(event.getScreenY() - yOffSet); + getWindow().setX(event.getScreenX() - xOffSet); + getWindow().setY(event.getScreenY() - yOffSet); }); } - // 窗口初始化完成监听 - getRootPanel().sceneProperty().addListener((observable, oldValue, newValue) -> { - newValue.windowProperty().addListener(new ChangeListener() { - @Override - public void changed(ObservableValue observable, Window oldValue, Window newValue) { - //关闭窗口监听 - getWindow().setOnCloseRequest(windowEvent -> onDestroy()); + // 初始化数据 + initData(); - // app 版本信息 - if (getAppVersionLabel() != null) getAppVersionLabel().setText("v" + AppConstant.APP_VERSION); + // 初始化视图样式 + initViewStyle(); - // 初始化数据 - initData(); - - // 初始化视图样式 - initViewStyle(); - - // 初始化视图事件 - initViewAction(); - } - }); - }); - } - - public void setApplication(Application application) { - this.application = application; - } - - public Application getApplication() { - return application; - } - - public Stage getPrimaryStage() { - return primaryStage; + // 初始化视图事件 + initViewAction(); } /** @@ -139,7 +58,9 @@ public abstract class BaseController

implements Initializable { * * @return 是否启用 */ - public abstract boolean dragWindow(); + public boolean dragWindow() { + return false; + } /** * 获取根布局 @@ -150,25 +71,17 @@ public abstract class BaseController

implements Initializable { /** * 获取根布局 - *

搭配 FxmlUtil.load 使用 + *

搭配 {@link FxmlUtil#load(String)} 使用 * * @return 根布局对象 - * @see FxmlUtil#load(String) */ protected String getRootFxml() { System.out.println(getClass().getSimpleName()); return ""; } - protected Window getWindow() { - return getRootPanel().getScene().getWindow(); - } - - /** - * App版本信息标签 - */ - public Label getAppVersionLabel() { - return null; + protected Stage getWindow() { + return Application.getPrimaryStage(); } /** @@ -185,19 +98,4 @@ public abstract class BaseController

implements Initializable { * 视图事件 */ public abstract void initViewAction(); - - /** - * 关闭窗口 - */ - public void onDestroy() { - Stage stage = (Stage) getWindow(); - stage.hide(); - stage.close(); - try { - Thread.sleep(1000); - Platform.exit(); - } catch (InterruptedException e) { - logger.error("", e); - } - } } diff --git a/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java b/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java index 291a381..95ab185 100644 --- a/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java +++ b/src/main/java/cn/octopusyan/alistgui/config/AppConstant.java @@ -1,9 +1,9 @@ package cn.octopusyan.alistgui.config; import cn.octopusyan.alistgui.util.PropertiesUtils; -import org.apache.commons.io.FileUtils; import java.io.File; +import java.nio.file.Paths; /** * 应用信息 @@ -14,8 +14,9 @@ public class AppConstant { public static final String APP_TITLE = PropertiesUtils.getInstance().getProperty("app.title"); public static final String APP_NAME = PropertiesUtils.getInstance().getProperty("app.name"); public static final String APP_VERSION = PropertiesUtils.getInstance().getProperty("app.version"); - public static final String DATA_DIR_PATH = System.getProperty("user.home") + File.separator + "AppData" + File.separator + "Local" + File.separator + APP_NAME; - public static final String TMP_DIR_PATH = FileUtils.getTempDirectoryPath() + APP_NAME; - public static final String CUSTOM_CONFIG_PATH = DATA_DIR_PATH + File.separator + "config.properties"; + public static final String DATA_DIR_PATH = Paths.get(".").toFile().getAbsolutePath(); + public static final String TMP_DIR_PATH = System.getProperty("java.io.tmpdir") + APP_NAME; + public static final String CONFIG_DIR_PATH = DATA_DIR_PATH + File.separator + "config"; + public static final String GUI_CONFIG_PATH = CONFIG_DIR_PATH + File.separator + "gui.yaml"; public static final String BAK_FILE_PATH = AppConstant.TMP_DIR_PATH + File.separator + "bak"; } diff --git a/src/main/java/cn/octopusyan/alistgui/config/ConfigManager.java b/src/main/java/cn/octopusyan/alistgui/config/ConfigManager.java new file mode 100644 index 0000000..4c155a6 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/config/ConfigManager.java @@ -0,0 +1,127 @@ +package cn.octopusyan.alistgui.config; + +import cn.hutool.core.lang.PatternPool; +import cn.hutool.core.util.NumberUtil; +import cn.octopusyan.alistgui.enums.ProxySetup; +import cn.octopusyan.alistgui.manager.http.HttpUtil; +import cn.octopusyan.alistgui.model.GuiConfig; +import cn.octopusyan.alistgui.model.ProxyInfo; +import org.apache.commons.lang3.LocaleUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Locale; +import java.util.Optional; +import java.util.regex.Matcher; + +/** + * 客户端设置 + * + * @author octopus_yan@foxmail.com + */ +public class ConfigManager { + private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class); + public static final Locale DEFAULT_LANGUAGE = Locale.CHINESE; + private static GuiConfig guiConfig; + + public static void load() { + try { + guiConfig = GuiConfig.getInstance(); + } catch (IOException e) { + logger.error("load config error", e); + } + } + + public static boolean hasProxy() { + if (guiConfig == null) + return false; + ProxyInfo proxyInfo = guiConfig.getProxyInfo(); + return proxyInfo != null + && StringUtils.isNoneEmpty(proxyInfo.getHost()) + && StringUtils.isNoneEmpty(proxyInfo.getPort()) + && Integer.parseInt(proxyInfo.getPort()) > 0; + } + + public static ProxyInfo getProxyInfo() { + return guiConfig.getProxyInfo(); + } + + public static String proxyHost() { + return guiConfig.getProxyInfo().getHost(); + } + + public static void proxyHost(String host) { + final Matcher matcher = PatternPool.IPV4.matcher(host); + if (matcher.matches()) { + guiConfig.getProxyInfo().setHost(host); + } + } + + public static String proxyPort() { + return guiConfig.getProxyInfo().getPort(); + } + + public static int getProxyPort() { + return Integer.parseInt(guiConfig.getProxyInfo().getPort()); + } + + public static void proxyPort(String port) { + if (NumberUtil.isNumber(port)) + guiConfig.getProxyInfo().setPort(port); + } + + public static Locale language() { + String language = guiConfig.getLanguage(); + return LocaleUtils.toLocale(Optional.ofNullable(language).orElse(DEFAULT_LANGUAGE.toString())); + } + + public static void language(Locale locale) { + guiConfig.setLanguage(locale.toString()); + } + + public static boolean autoStart() { + return guiConfig.getAutoStart(); + } + + public static void autoStart(Boolean autoStart) { + guiConfig.setAutoStart(autoStart); + } + + public static ProxySetup proxySetup() { + return ProxySetup.valueOf(StringUtils.upperCase(guiConfig.getProxySetup())); + } + + public static void proxySetup(ProxySetup setup) { + guiConfig.setProxySetup(setup.getName()); + + if (!ProxySetup.NO_PROXY.equals(setup) && hasProxy()) { + HttpUtil.getInstance().proxy(setup, ConfigManager.getProxyInfo()); + } + } + + public static boolean silentStartup() { + return guiConfig.getSilentStartup(); + } + + public static void silentStartup(Boolean startup) { + guiConfig.setSilentStartup(startup); + } + + public static String aListVersion() { + return guiConfig.getAListVersion(); + } + + public static void aListVersion(String version) { + guiConfig.setAListVersion(version); + } + + public static void save() { + try { + guiConfig.save(); + } catch (IOException e) { + logger.error("save config error", e); + } + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/config/Context.java b/src/main/java/cn/octopusyan/alistgui/config/Context.java new file mode 100644 index 0000000..e906c74 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/config/Context.java @@ -0,0 +1,209 @@ +package cn.octopusyan.alistgui.config; + +import cn.octopusyan.alistgui.base.BaseController; +import cn.octopusyan.alistgui.controller.MainController; +import cn.octopusyan.alistgui.controller.RootController; +import cn.octopusyan.alistgui.controller.SetupController; +import cn.octopusyan.alistgui.util.FxmlUtil; +import javafx.application.Platform; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.effect.DropShadow; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.util.Callback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URL; +import java.util.*; + +/** + * test contect + * + * @author octopus_yan + */ +public class Context { + private static final Logger log = LoggerFactory.getLogger(Context.class); + private static Scene scene; + private static final IntegerProperty currentViewIndexProperty = new SimpleIntegerProperty(0); + /** + * 控制器集合 + */ + private static final Map> controllers = new HashMap<>(); + /** + * 默认语言文件 Base Name + */ + private static final String LANGUAGE_RESOURCE_NAME = "language/language"; + /** + * 语言资源工厂 + */ + private static final ObservableResourceBundleFactory LANGUAGE_RESOURCE_FACTORY = new ObservableResourceBundleFactory(); + /** + * 支持的语言集合,应与语言资源文件同步手动更新 + */ + public static final List SUPPORT_LANGUAGE_LIST = Arrays.asList(Locale.CHINESE, Locale.ENGLISH); + /** + * 记录当前所选时区 + */ + private static final ObjectProperty currentLocale = new SimpleObjectProperty<>(); + + + private Context() { + throw new IllegalStateException("Utility class"); + } + + // 获取控制器集合 + public static Map> getControllers() { + return controllers; + } + + // 获取控制工厂 + public static Callback, Object> getControlFactory() { + return type -> { + if (type.equals(RootController.class)) { + return new RootController(); + } else if (type.equals(MainController.class)) { + return new MainController(); + } else if (type.equals(SetupController.class)) { + return new SetupController(); + } + throw new IllegalStateException("Unexpected value: " + type); + }; + } + + // 获取当前所选时区属性 + public static ObjectProperty currentLocaleProperty() { + return currentLocale; + } + + // 设置当前所选时区 + public static void setCurrentLocale(Locale locale) { + currentLocaleProperty().set(locale); + } + + /** + * 更换语言的组件使用此方法初始化自己的值,调用 {@link Context#setLanguage(Locale)} 来更新界面语言 + * + * @return 当前界面语言 + */ + // 获取当前界面语言 + public static Locale getCurrentLocale() { + return currentLocaleProperty().get(); + } + + /** + * 更新界面语言 + * + * @param locale 区域 + */ + // 更新界面语言 + public static void setLanguage(Locale locale) { + setCurrentLocale(locale); + ConfigManager.language(locale); + LANGUAGE_RESOURCE_FACTORY.setResourceBundle(ResourceBundle.getBundle(LANGUAGE_RESOURCE_NAME, locale)); + } + + /** + * 获取指定标识的字符串绑定 + * + * @param key 标识 + * @return 对应该标识的字符串属性绑定 + */ + // 获取指定标识的字符串绑定 + public static StringBinding getLanguageBinding(String key) { + return LANGUAGE_RESOURCE_FACTORY.getStringBinding(key); + } + + /** + * 获取语言资源属性 + */ + public static ObjectProperty getLanguageResource() { + return LANGUAGE_RESOURCE_FACTORY.getResourceBundleProperty(); + } + + /** + * 初始化 语言 + */ + private static void initI18n() { + currentLocaleProperty().addListener((observable, oldValue, newValue) -> { + Platform.runLater(() -> { + try { + loadScene(); + } catch (IOException e) { + log.error("", e); + } + }); + }); + } + + /** + * 有此类所在路径决定相对路径 + * + * @param path 资源文件相对路径 + * @return 资源文件路径 + */ + // 加载资源文件 + public static URL load(String path) { + return Context.class.getResource(path); + } + + /** + * 初始化场景 + * + * @return Scene + * @throws IOException 如果在加载过程中发生错误 + */ + public static Scene initScene() throws IOException { + initI18n(); + loadScene(); + return scene; + } + + private static void loadScene() throws IOException { + FXMLLoader loader = FxmlUtil.load("root-view"); + loader.setControllerFactory(Context.getControlFactory()); + Parent root = loader.load();//底层面板 +// bindShadow((Pane) root); + Optional.ofNullable(scene).ifPresentOrElse( + s -> s.setRoot(root), + () -> { + scene = new Scene(root); + URL resource = Objects.requireNonNull(Context.class.getResource("/css/root.css")); + scene.getStylesheets().addAll(resource.toExternalForm()); + scene.setFill(Color.TRANSPARENT); + } + ); + } + + // 设置当前展示的界面 + public static void setCurrentViewIndex(Number newValue) { + currentViewIndexProperty.setValue(newValue); + } + + // 获取当前展示的界面Index + public static Integer getCurrentViewIndex() { + return currentViewIndexProperty.get(); + } + + private static void bindShadow(Pane pane) { + Pane root = new Pane(); + root.setPadding(new Insets(20, 20, 20, 20)); + DropShadow dropshadow = new DropShadow();// 阴影向外 + dropshadow.setRadius(10);// 颜色蔓延的距离 + dropshadow.setOffsetX(0);// 水平方向,0则向左右两侧,正则向右,负则向左 + dropshadow.setOffsetY(0);// 垂直方向,0则向上下两侧,正则向下,负则向上 + dropshadow.setSpread(0.1);// 颜色变淡的程度 + dropshadow.setColor(Color.BLACK);// 设置颜色 + root.setEffect(dropshadow);// 绑定指定窗口控件 + root.getChildren().addAll(pane); + } +} \ No newline at end of file diff --git a/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java b/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java deleted file mode 100644 index dc2de54..0000000 --- a/src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java +++ /dev/null @@ -1,110 +0,0 @@ -package cn.octopusyan.alistgui.config; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintStream; -import java.nio.charset.StandardCharsets; -import java.util.Objects; -import java.util.Properties; - -/** - * 客户端设置 - * - * @author octopus_yan@foxmail.com - */ -public class CustomConfig { - private static final Logger logger = LoggerFactory.getLogger(CustomConfig.class); - private static final Properties properties = new Properties(); - public static final String PROXY_HOST_KEY = "proxy.host"; - public static final String PROXY_PORT_KEY = "proxy.port"; - - public static void init() { - FileReader reader = null; - try { - File file = new File(AppConstant.CUSTOM_CONFIG_PATH); - if (!file.exists()) { - // 创建配置文件 - if (!file.getParentFile().exists()) { - FileUtils.createParentDirectories(file); - } - boolean newFile = file.createNewFile(); - // 保存配置 - store(); - } else { - reader = new FileReader(file); - properties.load(reader); - } - } catch (Exception e) { - logger.error("读取配置文件失败", e); - } finally { - try { - if (reader != null) { - reader.close(); - } - } catch (IOException e) { - logger.error("关闭配置文件流", e); - } - } - } - - /** - * 是否配置代理 - */ - public static boolean hasProxy() { - String host = proxyHost(); - Integer port = proxyPort(); - - return StringUtils.isNoneBlank(host) && Objects.nonNull(port); - } - - /** - * 代理地址 - */ - public static String proxyHost() { - return properties.getProperty(PROXY_HOST_KEY); - } - - /** - * 代理地址 - */ - public static void proxyHost(String host) { - properties.setProperty(PROXY_HOST_KEY, host); - } - - /** - * 代理端口 - */ - public static Integer proxyPort() { - try { - return Integer.parseInt(properties.getProperty(PROXY_PORT_KEY)); - } catch (Exception ignored) { - } - return 10809; - } - - /** - * 代理端口 - */ - public static void proxyPort(int port) { - properties.setProperty(PROXY_PORT_KEY, String.valueOf(port)); - } - - - /** - * 保存配置 - */ - public static void store() { - // 生成配置文件 - try { - properties.store(new PrintStream(AppConstant.CUSTOM_CONFIG_PATH), String.valueOf(StandardCharsets.UTF_8)); - } catch (IOException e) { - logger.error("保存客户端配置失败", e); - } - } -} diff --git a/src/main/java/cn/octopusyan/alistgui/config/ObservableResourceBundleFactory.java b/src/main/java/cn/octopusyan/alistgui/config/ObservableResourceBundleFactory.java new file mode 100644 index 0000000..9b45a26 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/config/ObservableResourceBundleFactory.java @@ -0,0 +1,40 @@ +package cn.octopusyan.alistgui.config; + +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import lombok.Getter; + +import java.util.ResourceBundle; + +/** + * 多国语言属性绑定 + * + * @author octopus_yan + */ +@Getter +public class ObservableResourceBundleFactory { + + private final ObjectProperty resourceBundleProperty = new SimpleObjectProperty<>(); + + public ResourceBundle getResourceBundle() { + return getResourceBundleProperty().get(); + } + + public void setResourceBundle(ResourceBundle resourceBundle) { + getResourceBundleProperty().set(resourceBundle); + } + + public StringBinding getStringBinding(String key) { + return new StringBinding() { + { + bind(resourceBundleProperty); + } + + @Override + protected String computeValue() { + return getResourceBundle().getString(key); + } + }; + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/controller/MainController.java b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java index 34f5e20..cc2ef60 100644 --- a/src/main/java/cn/octopusyan/alistgui/controller/MainController.java +++ b/src/main/java/cn/octopusyan/alistgui/controller/MainController.java @@ -1,102 +1,39 @@ package cn.octopusyan.alistgui.controller; import cn.octopusyan.alistgui.base.BaseController; -import javafx.css.PseudoClass; import javafx.fxml.FXML; -import javafx.fxml.Initializable; -import javafx.scene.control.Button; -import javafx.scene.control.TabPane; -import javafx.scene.input.MouseEvent; -import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import javafx.stage.Stage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * 主页面控制器 + * 主界面控制器 * - * @author octopus_yan@foxmail.com + * @author octopus_yan */ -public class MainController extends BaseController implements Initializable { +public class MainController extends BaseController { + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - private double xOffset; - private double yOffset; + @FXML + public VBox mainView; - // 布局 - @FXML - public VBox rootPane; - @FXML - public HBox windowHeader; - @FXML - public Button alwaysOnTopIcon; - @FXML - public Button minimizeIcon; - @FXML - public Button closeIcon; - - // 界面 - @FXML - public TabPane tabPane; - - public MainController(Stage primaryStage) { - super(primaryStage); - } - - /** - * 窗口拖拽设置 - * - * @return 是否启用 - */ - @Override - public boolean dragWindow() { - return false; - } - - /** - * 获取根布局 - * - * @return 根布局对象 - */ @Override public VBox getRootPanel() { - return rootPane; + return mainView; } - /** - * 初始化数据 - */ @Override public void initData() { } - /** - * 视图样式 - */ @Override public void initViewStyle() { } - /** - * 视图事件 - */ @Override public void initViewAction() { - closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> onDestroy()); - minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> ((Stage) rootPane.getScene().getWindow()).setIconified(true)); - alwaysOnTopIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> { - boolean newVal = !getPrimaryStage().isAlwaysOnTop(); - alwaysOnTopIcon.pseudoClassStateChanged(PseudoClass.getPseudoClass("always-on-top"), newVal); - getPrimaryStage().setAlwaysOnTop(newVal); - }); - windowHeader.setOnMousePressed(event -> { - xOffset = getPrimaryStage().getX() - event.getScreenX(); - yOffset = getPrimaryStage().getY() - event.getScreenY(); - }); - windowHeader.setOnMouseDragged(event -> { - getPrimaryStage().setX(event.getScreenX() + xOffset); - getPrimaryStage().setY(event.getScreenY() + yOffset); - }); } } diff --git a/src/main/java/cn/octopusyan/alistgui/controller/RootController.java b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java new file mode 100644 index 0000000..ae4ff99 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/controller/RootController.java @@ -0,0 +1,108 @@ +package cn.octopusyan.alistgui.controller; + +import cn.octopusyan.alistgui.base.BaseController; +import cn.octopusyan.alistgui.config.Context; +import com.gluonhq.emoji.EmojiData; +import com.gluonhq.emoji.util.EmojiImageUtils; +import javafx.css.PseudoClass; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.TabPane; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import org.kordamp.ikonli.javafx.FontIcon; + +/** + * 主页面控制器 + * + * @author octopus_yan@foxmail.com + */ +public class RootController extends BaseController { + + private double xOffset; + private double yOffset; + + // 布局 + @FXML + private VBox rootPane; + @FXML + private HBox windowHeader; + @FXML + private FontIcon alwaysOnTopIcon; + @FXML + private FontIcon minimizeIcon; + @FXML + private FontIcon closeIcon; + + // 界面 + @FXML + private TabPane tabPane; + + // footer + @FXML + public Button document; + @FXML + public Button github; + @FXML + public Button sponsor; + /** + * 获取根布局 + * + * @return 根布局对象 + */ + @Override + public VBox getRootPanel() { + return rootPane; + } + + /** + * 初始化数据 + */ + @Override + public void initData() { + tabPane.getSelectionModel().select(Context.getCurrentViewIndex()); + } + + /** + * 视图样式 + */ + @Override + public void initViewStyle() { + // 设置图标 + ImageView book = EmojiImageUtils.emojiView(EmojiData.emojiFromShortName("book").get(), 25); + document.setGraphic(book); + ImageView githubIcon = EmojiImageUtils.emojiView(EmojiData.emojiFromShortName("cat").get(), 25); + github.setGraphic(githubIcon); + ImageView juice = EmojiImageUtils.emojiView(EmojiData.emojiFromShortName("tropical_drink").get(), 25); + sponsor.setGraphic(juice); + } + + /** + * 视图事件 + */ + @Override + public void initViewAction() { + closeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> getWindow().close()); + minimizeIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> getWindow().setIconified(true)); + alwaysOnTopIcon.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> { + boolean newVal = !getWindow().isAlwaysOnTop(); + alwaysOnTopIcon.pseudoClassStateChanged(PseudoClass.getPseudoClass("always-on-top"), newVal); + getWindow().setAlwaysOnTop(newVal); + }); + + windowHeader.setOnMousePressed(event -> { + xOffset = getWindow().getX() - event.getScreenX(); + yOffset = getWindow().getY() - event.getScreenY(); + }); + windowHeader.setOnMouseDragged(event -> { + getWindow().setX(event.getScreenX() + xOffset); + getWindow().setY(event.getScreenY() + yOffset); + }); + + tabPane.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> { + Context.setCurrentViewIndex(newValue); + }); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java new file mode 100644 index 0000000..a8687a8 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/controller/SetupController.java @@ -0,0 +1,99 @@ +package cn.octopusyan.alistgui.controller; + +import cn.octopusyan.alistgui.base.BaseController; +import cn.octopusyan.alistgui.config.ConfigManager; +import cn.octopusyan.alistgui.config.Context; +import cn.octopusyan.alistgui.enums.ProxySetup; +import cn.octopusyan.alistgui.viewModel.SetupViewModel; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Pane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Locale; + +/** + * 设置页面控制器 + * + * @author octopus_yan + */ +public class SetupController extends BaseController implements Initializable { + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + @FXML + public GridPane setupView; + @FXML + public CheckBox autoStartCheckBox; + @FXML + public CheckBox silentStartupCheckBox; + @FXML + public ComboBox languageComboBox; + @FXML + public ComboBox proxySetupComboBox; +// @FXML +// public RadioButton noProxy; +// @FXML +// public RadioButton systemProxy; +// @FXML +// public RadioButton manualProxy; + @FXML + public Pane proxySetupPane; + @FXML + public TextField proxyHost; + @FXML + public TextField proxyPort; + @FXML + public Label alistVersion; + + private final SetupViewModel setupViewModel = new SetupViewModel(); + private final ToggleGroup proxySetupGroup = new ToggleGroup(); + + @Override + public GridPane getRootPanel() { + return setupView; + } + + @Override + public void initData() { + languageComboBox.setItems(FXCollections.observableList(Context.SUPPORT_LANGUAGE_LIST)); + proxySetupComboBox.setItems(FXCollections.observableList(List.of(ProxySetup.values()))); + +// noProxy.setToggleGroup(proxySetupGroup); +// systemProxy.setToggleGroup(proxySetupGroup); +// manualProxy.setToggleGroup(proxySetupGroup); + } + + @Override + public void initViewStyle() { + proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener(observable -> { + proxySetupPane.setVisible(ProxySetup.MANUAL.equals(setupViewModel.proxySetupProperty().get())); + }); + proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + proxySetupPane.setVisible(ProxySetup.MANUAL.equals(newValue)); + }); + + languageComboBox.getSelectionModel().select(ConfigManager.language()); + proxySetupComboBox.getSelectionModel().select(ConfigManager.proxySetup()); + } + + @Override + public void initViewAction() { + autoStartCheckBox.selectedProperty().bindBidirectional(setupViewModel.autoStartProperty()); + silentStartupCheckBox.selectedProperty().bindBidirectional(setupViewModel.silentStartupProperty()); + proxySetupComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + setupViewModel.proxySetupProperty().set(newValue); + }); + proxyHost.textProperty().bindBidirectional(setupViewModel.proxyHostProperty()); + proxyPort.textProperty().bindBidirectional(setupViewModel.proxyPortProperty()); + languageComboBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + setupViewModel.languageProperty().set(newValue); + logger.info("language changed to {}", newValue); + }); + alistVersion.textProperty().bindBidirectional(setupViewModel.aListVersionProperty()); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java b/src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java new file mode 100644 index 0000000..d0e119a --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/enums/ProxySetup.java @@ -0,0 +1,25 @@ +package cn.octopusyan.alistgui.enums; + +import cn.octopusyan.alistgui.config.Context; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * 代理类型 + * + * @author octopus_yan + */ +@Getter +@RequiredArgsConstructor +public enum ProxySetup { + NO_PROXY("no_proxy"), + SYSTEM("system"), + MANUAL("manual"); + + private final String name; + + @Override + public String toString() { + return Context.getLanguageBinding("proxy.setup.label." + getName()).getValue(); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java index fb9ad1a..31bc4e4 100644 --- a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java +++ b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpConfig.java @@ -1,5 +1,6 @@ package cn.octopusyan.alistgui.manager.http; +import lombok.Data; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +24,7 @@ import java.util.concurrent.Executor; * * @author octopus_yan@foxmail.com */ +@Data public class HttpConfig { private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class); /** @@ -95,8 +97,7 @@ public class HttpConfig { }}; sslParameters = new SSLParameters(); sslParameters.setEndpointIdentificationAlgorithm(""); - - + sslParameters.setProtocols(new String[]{"TLSv1.2"}); try { sslContext = SSLContext.getInstance("TLSv1.2"); System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证 @@ -104,80 +105,5 @@ public class HttpConfig { } catch (NoSuchAlgorithmException | KeyManagementException e) { logger.error("", e); } - - } - - - public HttpClient.Version getVersion() { - return version; - } - - public void setVersion(HttpClient.Version version) { - this.version = version; - } - - public int getConnectTimeout() { - return connectTimeout; - } - - public void setConnectTimeout(int connectTimeout) { - this.connectTimeout = connectTimeout; - } - - - public HttpClient.Redirect getRedirect() { - return redirect; - } - - public void setRedirect(HttpClient.Redirect redirect) { - this.redirect = redirect; - } - - public Executor getExecutor() { - return executor; - } - - public void setExecutor(Executor executor) { - this.executor = executor; - } - - public Authenticator getAuthenticator() { - return authenticator; - } - - public void setAuthenticator(Authenticator authenticator) { - this.authenticator = authenticator; - } - - public ProxySelector getProxySelector() { - return proxySelector; - } - - public void setProxySelector(ProxySelector proxySelector) { - this.proxySelector = proxySelector; - } - - public CookieHandler getCookieHandler() { - return cookieHandler; - } - - public void setCookieHandler(CookieHandler cookieHandler) { - this.cookieHandler = cookieHandler; - } - - public int getDefaultReadTimeout() { - return defaultReadTimeout; - } - - public void setDefaultReadTimeout(int defaultReadTimeout) { - this.defaultReadTimeout = defaultReadTimeout; - } - - public SSLContext getSslContext() { - return sslContext; - } - - public SSLParameters getSslParameters() { - return sslParameters; } } diff --git a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java index a351a33..89557bd 100644 --- a/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java +++ b/src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java @@ -1,6 +1,10 @@ package cn.octopusyan.alistgui.manager.http; -import com.alibaba.fastjson2.JSONObject; +import cn.octopusyan.alistgui.config.ConfigManager; +import cn.octopusyan.alistgui.enums.ProxySetup; +import cn.octopusyan.alistgui.model.ProxyInfo; +import cn.octopusyan.alistgui.util.JsonUtil; +import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; import java.net.InetSocketAddress; @@ -12,7 +16,7 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.List; +import java.util.Map; import java.util.Optional; /** @@ -57,15 +61,20 @@ public class HttpUtil { return builder.build(); } - public HttpUtil proxy(String host, int port) { + public void proxy(ProxySetup setup, ProxyInfo proxy) { if (httpClient == null) throw new RuntimeException("are you ready ?"); - InetSocketAddress unresolved = InetSocketAddress.createUnresolved(host, port); - ProxySelector other = ProxySelector.of(unresolved); - this.httpConfig.setProxySelector(other); + switch (setup) { + case NO_PROXY -> clearProxy(); + case SYSTEM -> httpConfig.setProxySelector(ProxySelector.getDefault()); + case MANUAL -> { + InetSocketAddress unresolved = InetSocketAddress.createUnresolved(ConfigManager.proxyHost(), ConfigManager.getProxyPort()); + httpConfig.setProxySelector(ProxySelector.of(unresolved)); + } + } + this.httpClient = createClient(httpConfig); - return this; } public void clearProxy() { @@ -76,24 +85,20 @@ public class HttpUtil { httpClient = createClient(httpConfig); } - public HttpClient getHttpClient() { - return httpClient; - } - - public String get(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException { + public String get(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException { HttpRequest.Builder request = getRequest(uri + createFormParams(param), header).GET(); HttpResponse response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); return response.body(); } - public String post(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException { + public String post(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException { HttpRequest.Builder request = getRequest(uri, header) - .POST(HttpRequest.BodyPublishers.ofString(param.toJSONString())); + .POST(HttpRequest.BodyPublishers.ofString(JsonUtil.toJsonString(param))); HttpResponse response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); return response.body(); } - public String postForm(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException { + public String postForm(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException { HttpRequest.Builder request = getRequest(uri + createFormParams(param), header) .POST(HttpRequest.BodyPublishers.noBody()); @@ -101,35 +106,37 @@ public class HttpUtil { return response.body(); } - private HttpRequest.Builder getRequest(String uri, JSONObject header) { + private HttpRequest.Builder getRequest(String uri, JsonNode header) { HttpRequest.Builder request = HttpRequest.newBuilder(); // 请求地址 request.uri(URI.create(uri)); // 请求头 if (header != null && !header.isEmpty()) { - for (String key : header.keySet()) { - request.header(key, header.getString(key)); + for (Map.Entry property : header.properties()) { + String key = property.getKey(); + request.header(key, JsonUtil.toJsonString(property.getValue())); } } return request; } - private String createFormParams(JSONObject params) { + private String createFormParams(JsonNode params) { StringBuilder formParams = new StringBuilder(); if (params == null) { return formParams.toString(); } - for (String key : params.keySet()) { - Object value = params.get(key); - if (value instanceof String) { - value = URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8); + for (Map.Entry property : params.properties()) { + String key = property.getKey(); + JsonNode value = params.get(key); + if (value.isTextual()) { + String value_ = URLEncoder.encode(String.valueOf(value), StandardCharsets.UTF_8); + formParams.append("&").append(key).append("=").append(value_); + } else if (value.isNumber()) { formParams.append("&").append(key).append("=").append(value); - } else if (value instanceof Number) { - formParams.append("&").append(key).append("=").append(value); - } else if (value instanceof List) { - formParams.append("&").append(key).append("=").append(params.getJSONArray(key)); + } else if (value.isArray()) { + formParams.append("&").append(key).append("=").append(JsonUtil.toJsonString(value)); } else { - formParams.append("&").append(key).append("=").append(params.getJSONObject(key)); + formParams.append("&").append(key).append("=").append(JsonUtil.toJsonString(value)); } } if (!formParams.isEmpty()) { diff --git a/src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java b/src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java new file mode 100644 index 0000000..ac8fa1d --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/model/GuiConfig.java @@ -0,0 +1,71 @@ +package cn.octopusyan.alistgui.model; + +import cn.hutool.core.io.FileUtil; +import cn.octopusyan.alistgui.config.AppConstant; +import cn.octopusyan.alistgui.config.ConfigManager; +import cn.octopusyan.alistgui.enums.ProxySetup; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import lombok.Data; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; + +/** + * GUI配置信息 + * 通过Jackson 写入yaml + * + * @author octopus_yan + */ +@Data +public class GuiConfig { + private static final Logger log = LoggerFactory.getLogger(GuiConfig.class); + public static GuiConfig INSTANCE; + public static ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); + + private Boolean autoStart = false; + private Boolean silentStartup = false; + private ProxyInfo proxyInfo = new ProxyInfo(); + private String proxySetup = ProxySetup.NO_PROXY.getName(); + private String language = ConfigManager.DEFAULT_LANGUAGE.toString(); + @JsonProperty("aListVersion") + private String aListVersion = "unknown"; + + static { + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + } + + private GuiConfig() { + + } + + public static GuiConfig getInstance() throws IOException { + File src = new File(AppConstant.GUI_CONFIG_PATH); + if (INSTANCE == null) { + if (!src.exists()) { + checkFile(src); + } + INSTANCE = objectMapper.readValue(src, GuiConfig.class); + } + return INSTANCE; + } + + private static void checkFile(File src) throws IOException { + File parent = FileUtil.getParent(src, 1); + if (!parent.exists()) { + boolean wasSuccessful = parent.mkdirs(); + objectMapper.writeValue(src, new GuiConfig()); + if (!wasSuccessful) + log.error("{} 创建失败", src.getAbsolutePath()); + } + } + + public void save() throws IOException { + objectMapper.writeValue(new File(AppConstant.GUI_CONFIG_PATH), INSTANCE); + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java b/src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java new file mode 100644 index 0000000..f6e5d70 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/model/ProxyInfo.java @@ -0,0 +1,16 @@ +package cn.octopusyan.alistgui.model; + +import lombok.Data; + +/** + * 代理信息 + * + * @author octopus_yan + */ +@Data +public class ProxyInfo { + private String host = ""; + private String port = ""; + private String username = ""; + private String password = ""; +} diff --git a/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java b/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java deleted file mode 100644 index 03efe0a..0000000 --- a/src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.octopusyan.alistgui.util; - -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; - -/** - * 剪切板工具 - * - * @author octopus_yan@foxmail.com - */ -public class ClipUtil { - //获取系统剪切板 - private static final Clipboard clipboard = Clipboard.getSystemClipboard(); - - public static void setClip(String data) { - clipboard.clear(); - // 设置剪切板内容 - ClipboardContent clipboardContent = new ClipboardContent(); - clipboardContent.putString(data); - clipboard.setContent(clipboardContent); - - } - - public static String getString() { - // javafx 从剪切板获取文本 - return clipboard.getString(); - } -} diff --git a/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java b/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java deleted file mode 100644 index 1cc4ba3..0000000 --- a/src/main/java/cn/octopusyan/alistgui/util/FileUtil.java +++ /dev/null @@ -1,135 +0,0 @@ -package cn.octopusyan.alistgui.util; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.filefilter.CanReadFileFilter; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 文件工具类 - * - * @author octopus_yan@foxmail.com - */ -public class FileUtil { - private static final Logger logger = LoggerFactory.getLogger(FileUtil.class); - - public static File[] ls(String path) { - File dir = new File(path); - if (!dir.exists()) - throw new RuntimeException(path + "不存在!"); - - if (!dir.isDirectory()) - throw new RuntimeException(path + "不是一个文件夹!"); - - return dir.listFiles(); - } - - public static void copyFilesFromDir(String path, String dest) throws IOException { - if (StringUtils.isBlank(path) || StringUtils.isBlank(dest)) { - logger.error("path is blank !"); - return; - } - File dir = new File(path); - if (!dir.exists()) { - logger.error("[" + path + "] 不存在!"); - return; - } - if (!dir.isDirectory()) { - logger.error("[" + path + "] 不是一个文件夹!"); - } - - File[] files = dir.listFiles(); - if (files == null) return; - - File directory = new File(dest); - if (directory.exists() && !directory.isDirectory()) { - logger.error("[" + dest + "] 不是一个文件夹!"); - } - - FileUtils.forceMkdir(directory); - - for (File file : files) { - copyFile(file, new File(dest + File.separator + file.getName())); - } - } - - public static void copyFile(File in, File out) throws IOException { - copyFile(Files.newInputStream(in.toPath()), out); - } - - public static void copyFile(InputStream input, File out) throws IOException { - OutputStream output = null; - try { - output = Files.newOutputStream(out.toPath()); - byte[] buf = new byte[1024]; - int bytesRead; - while ((bytesRead = input.read(buf)) > 0) { - output.write(buf, 0, bytesRead); - } - } catch (IOException e) { - logger.error("", e); - } finally { - if (output != null) input.close(); - if (output != null) output.close(); - } - } - - /** - * 获取文件主名称 - * - * @param file 文件对象 - * @return 文件名称 - */ - public static String mainName(File file) { - //忽略判断 - String fileName = file.getName(); - return fileName.substring(0, fileName.lastIndexOf(".")); - } - - public static List listFileNames(String path) { - Collection files = FileUtils.listFiles(new File(path), CanReadFileFilter.CAN_READ, null); - return files.stream().map(File::getName).collect(Collectors.toList()); - } - - /** - * 返回被查找到的文件的绝对路径(匹配到一个就返回) - * - * @param root 根目录文件 - * @param fileName 要找的文件名 - * @return 绝对路径 - */ - private static String findFiles(File root, String fileName) { - //定义一个返回值 - String path = null; - //如果传进来的是目录,并且存在 - if (root.exists() && root.isDirectory()) { - //遍历文件夹中的各个文件 - File[] files = root.listFiles(); - if (files != null) { - for (File file : files) { - //如果path的值没有变化 - if (path == null) { - if (file.isFile() && file.getName().contains(fileName)) { - path = file.getAbsolutePath(); - } else { - path = findFiles(file, fileName); - } - } else { - break;//跳出循环,增加性能 - } - } - } - } - return path; - } -} diff --git a/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java b/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java index 404d3a6..de5c07d 100644 --- a/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java +++ b/src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java @@ -1,9 +1,11 @@ package cn.octopusyan.alistgui.util; +import cn.octopusyan.alistgui.config.Context; import javafx.fxml.FXMLLoader; import javafx.fxml.JavaFXBuilderFactory; import java.nio.charset.StandardCharsets; +import java.util.ResourceBundle; /** * FXML 工具 @@ -13,11 +15,15 @@ import java.nio.charset.StandardCharsets; public class FxmlUtil { public static FXMLLoader load(String name) { + return load(name, Context.getLanguageResource().get()); + } + + public static FXMLLoader load(String name, ResourceBundle bundle) { String prefix = "/fxml/"; String suffix = ".fxml"; return new FXMLLoader( FxmlUtil.class.getResource(prefix + name + suffix), - null, + bundle, new JavaFXBuilderFactory(), null, StandardCharsets.UTF_8 diff --git a/src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java b/src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java new file mode 100644 index 0000000..716139e --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/util/JsonUtil.java @@ -0,0 +1,187 @@ +package cn.octopusyan.alistgui.util; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; + +/** + * Jackson 封装工具类 + * + * @author octopus_yan + */ +public class JsonUtil { + private static final Logger log = LoggerFactory.getLogger(JsonUtil.class); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 时间日期格式 + */ + private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + static { + //对象的所有字段全部列入序列化 + objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); + //取消默认转换timestamps形式 + objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); + //忽略空Bean转json的错误 + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + //所有的日期格式都统一为以下的格式,即yyyy-MM-dd HH:mm:ss + objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT)); + //忽略 在json字符串中存在,但在java对象中不存在对应属性的情况。防止错误 + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + /** + * Json字符串 转 JavaBean + * + * @param jsonString Json字符串 + * @param clazz Java类对象 + * @param Java类 + * @return JavaBean + */ + public static T parseObject(String jsonString, Class clazz) { + T t = null; + try { + t = objectMapper.readValue(jsonString, clazz); + } catch (JsonProcessingException e) { + log.error("失败:{}", e.getMessage()); + } + return t; + } + + /** + * 读取Json文件 转 JavaBean + * + * @param file Json文件 + * @param clazz Java类对象 + * @param Java类 + * @return JavaBean + */ + public static T parseObject(File file, Class clazz) { + T t = null; + try { + t = objectMapper.readValue(file, clazz); + } catch (IOException e) { + log.error("失败:{}", e.getMessage()); + } + return t; + } + + /** + * 读取Json字符串 转 JavaBean集合 + * + * @param jsonArray Json字符串 + * @param reference 类型 + * @param JavaBean类型 + * @return JavaBean集合 + */ + public static T parseJsonArray(String jsonArray, TypeReference reference) { + T t = null; + try { + t = objectMapper.readValue(jsonArray, reference); + } catch (JsonProcessingException e) { + log.error("失败:{}", e.getMessage()); + } + return t; + } + + + /** + * JavaBean 转 Json字符串 + * + * @param object JavaBean + * @return Json字符串 + */ + public static String toJsonString(Object object) { + String jsonString = null; + try { + jsonString = objectMapper.writeValueAsString(object); + } catch (JsonProcessingException e) { + log.error("失败:{}", e.getMessage()); + } + return jsonString; + } + + /** + * JavaBean 转 字节数组 + * + * @param object JavaBean + * @return 字节数组 + */ + public static byte[] toByteArray(Object object) { + byte[] bytes = null; + try { + bytes = objectMapper.writeValueAsBytes(object); + } catch (JsonProcessingException e) { + log.error("失败:{}", e.getMessage()); + } + return bytes; + } + + /** + * JavaBean序列化到文件 + * + * @param file 写入文件对象 + * @param object JavaBean + */ + public static void objectToFile(File file, Object object) { + try { + objectMapper.writeValue(file, object); + } catch (Exception e) { + log.error("失败:{}", e.getMessage()); + } + } + + + /** + * Json字符串 转 JsonNode + * + * @param jsonString Json字符串 + * @return JsonNode + */ + public static JsonNode parseJsonObject(String jsonString) { + JsonNode jsonNode = null; + try { + jsonNode = objectMapper.readTree(jsonString); + } catch (JsonProcessingException e) { + log.error("失败:{}", e.getMessage()); + } + return jsonNode; + } + + /** + * JavaBean 转 JsonNode + * + * @param object JavaBean + * @return JsonNode + */ + public static JsonNode parseJsonObject(Object object) { + return objectMapper.valueToTree(object); + } + + /** + * JsonNode 转 Json字符串 + * + * @param jsonNode JsonNode + * @return Json字符串 + */ + public static String toJsonString(JsonNode jsonNode) { + String jsonString = null; + try { + jsonString = objectMapper.writeValueAsString(jsonNode); + } catch (JsonProcessingException e) { + log.error("失败:{}", e.getMessage()); + } + return jsonString; + } +} diff --git a/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java index 1aec45b..0cd12bf 100644 --- a/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java +++ b/src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java @@ -12,33 +12,14 @@ import java.io.IOException; * @author octopus_yan@foxmail.com */ public class ProcessesUtil { + private static final Logger logger = LoggerFactory.getLogger(ProcessesUtil.class); - private static final String NEWLINE = System.lineSeparator(); + private static final String NEW_LINE = System.lineSeparator(); private static final DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler(); public static boolean exec(String command) { try { - exec(command, new OnExecuteListener() { - @Override - public void onExecute(String msg) { - - } - - @Override - public void onExecuteSuccess(int exitValue) { - - } - - @Override - public void onExecuteError(Exception e) { - - } - - @Override - public void onExecuteOver() { - - } - }); + exec(command, msg -> {}); handler.waitFor(); } catch (Exception e) { logger.error("", e); @@ -50,7 +31,7 @@ public class ProcessesUtil { LogOutputStream logout = new LogOutputStream() { @Override protected void processLine(String line, int logLevel) { - if (listener != null) listener.onExecute(line + NEWLINE); + if (listener != null) listener.onExecute(line + NEW_LINE); } }; @@ -81,11 +62,8 @@ public class ProcessesUtil { public interface OnExecuteListener { void onExecute(String msg); - - void onExecuteSuccess(int exitValue); - - void onExecuteError(Exception e); - void onExecuteOver(); + default void onExecuteSuccess(int exitValue){} + default void onExecuteError(Exception e){} } /** diff --git a/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java b/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java new file mode 100644 index 0000000..b931ae9 --- /dev/null +++ b/src/main/java/cn/octopusyan/alistgui/viewModel/SetupViewModel.java @@ -0,0 +1,87 @@ +package cn.octopusyan.alistgui.viewModel; + +import cn.hutool.core.lang.Validator; +import cn.hutool.core.net.url.UrlBuilder; +import cn.octopusyan.alistgui.config.ConfigManager; +import cn.octopusyan.alistgui.config.Context; +import cn.octopusyan.alistgui.enums.ProxySetup; +import javafx.beans.property.*; +import org.apache.commons.lang3.StringUtils; + +import java.util.Locale; + +/** + * 设置视图数据 + * + * @author octopus_yan + */ +public class SetupViewModel { + private final BooleanProperty autoStart = new SimpleBooleanProperty(ConfigManager.autoStart()); + private final BooleanProperty silentStartup = new SimpleBooleanProperty(ConfigManager.silentStartup()); + private final StringProperty proxyHost = new SimpleStringProperty(ConfigManager.proxyHost()); + private final StringProperty proxyPort = new SimpleStringProperty(ConfigManager.proxyPort()); + private final BooleanProperty proxyVerify = new SimpleBooleanProperty(false); + private final ObjectProperty language = new SimpleObjectProperty<>(ConfigManager.language()); + private final ObjectProperty proxySetup = new SimpleObjectProperty<>(ConfigManager.proxySetup()); + private final SimpleStringProperty aListVersion = new SimpleStringProperty(ConfigManager.aListVersion()); + + public SetupViewModel() { + aListVersion.addListener((observable, oldValue, newValue) -> ConfigManager.aListVersion(newValue)); + autoStart.addListener((observable, oldValue, newValue) -> ConfigManager.autoStart(newValue)); + silentStartup.addListener((observable, oldValue, newValue) -> ConfigManager.silentStartup(newValue)); + proxySetup.addListener((observable, oldValue, newValue) -> ConfigManager.proxySetup(newValue)); + proxyHost.addListener((observable, oldValue, newValue) -> ConfigManager.proxyHost(newValue)); + proxyPort.addListener((observable, oldValue, newValue) -> ConfigManager.proxyPort(newValue)); + language.addListener((observable, oldValue, newValue) -> Context.setLanguage(newValue)); + } + + public BooleanProperty autoStartProperty() { + return autoStart; + } + + public BooleanProperty silentStartupProperty() { + return silentStartup; + } + + public ObjectProperty languageProperty() { + return language; + } + + public ObjectProperty proxySetupProperty() { + return proxySetup; + } + + public StringProperty proxyHostProperty() { + return proxyHost; + } + + public StringProperty proxyPortProperty() { + return proxyPort; + } + + public Property aListVersionProperty() { + return aListVersion; + } + + /** + * 验证代理地址 + * + * @param address 新的代理地址 + * @return UrlBuilder, 验证失败时为null + */ + public UrlBuilder validateAddress(String address) { + if (StringUtils.isEmpty(address)) + return null; + + // 验证 URL 的可用性 + UrlBuilder ub = UrlBuilder.of(address); + boolean isUrlValid = Validator.isUrl(address) && ub.getPort() > 0; + + // 设置 proxyVerify 和 proxyVerifyMsg + proxyVerify.setValue(isUrlValid); + + if (isUrlValid) + return ub; + else return null; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a09a857..b6e3dd2 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -3,15 +3,21 @@ module cn.octopusyan.alistgui { requires javafx.controls; requires javafx.fxml; requires javafx.graphics; - requires org.apache.commons.io; requires org.apache.commons.lang3; requires org.apache.commons.exec; requires org.slf4j; requires ch.qos.logback.core; requires ch.qos.logback.classic; - requires com.alibaba.fastjson2; + requires cn.hutool.core; + requires org.kordamp.ikonli.javafx; + requires com.gluonhq.emoji; + requires static lombok; + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.dataformat.yaml; + requires atlantafx.base; exports cn.octopusyan.alistgui; opens cn.octopusyan.alistgui to javafx.fxml; + opens cn.octopusyan.alistgui.model to com.fasterxml.jackson.databind; opens cn.octopusyan.alistgui.controller to javafx.fxml; } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7553f33..a63aa58 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,3 @@ app.name=${project.name} -app.title=alist gui +app.title=AList GUI app.version=${project.version} \ No newline at end of file diff --git a/src/main/resources/css/main-view.css b/src/main/resources/css/main-view.css new file mode 100644 index 0000000..584b99a --- /dev/null +++ b/src/main/resources/css/main-view.css @@ -0,0 +1,87 @@ +/************************************************** + * Main View +**************************************************/ +#homeLabel { + -fx-font-size: 35; + -fx-font-weight: bold; +} + +#statusLabel { + -fx-padding: 2 5 2 5; + -fx-text-fill: white; + -fx-font-size: 15; + -fx-background-color: #1bc964; + -fx-background-radius: 10; + -fx-text-alignment: CENTER; + -fx-border-radius: 10; +} + +.control-menu { + -fx-font-size: 15; + -fx-background-radius: 15; + -fx-padding: 10 40 10 40; + -fx-border-radius: 15; + -fx-border-width: 2; + -fx-opacity: 0.9; +} +.control-menu:hover { + -fx-opacity: 1; +} + +#startButton { + -fx-background-color: #fa6057; + -fx-border-color: #fa6057; + -fx-text-fill: white; +} + +#passwordButton { + -fx-background-color: #1bc964; + -fx-border-color: #1bc964; +} + +#restartButton { + -fx-background-color: linear-gradient(#57b4f2, #9198e5); + -fx-border-color: linear-gradient(#57b4f2, #9198e5); + -fx-text-fill: white; +} + +#moreButton { + -fx-background-color: transparent; + -fx-text-fill: #9254d1; + -fx-border-color: #9254d1; + -fx-border-width: 2; +} + +#logArea { + -fx-font-family: "Lucida Console"; + -fx-font-size: 20; + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-color: transparent; + -fx-background-insets: 0; + -fx-background-color: #e9e9e9; +} +#logArea:focused { + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-color: transparent; + -fx-background-color: #e9e9e9; +} +#logArea .content { + -fx-padding: 0 15 0 15; + -fx-background-radius: 15; + -fx-background-color: #e9e9e9; + -fx-border-radius: 15; + -fx-border-color: transparent; +} +#logArea .content:focused { + -fx-background-color: #e9e9e9; +} +#logArea .scroll-pane { + -fx-background-color: transparent; +} +#logArea .scroll-pane .viewport { + -fx-background-color: transparent; +} + +/*# sourceMappingURL=main-view.css.map */ diff --git a/src/main/resources/css/main-view.css.map b/src/main/resources/css/main-view.css.map new file mode 100644 index 0000000..7f57c04 --- /dev/null +++ b/src/main/resources/css/main-view.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["main-view.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAGA;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE","file":"main-view.css"} \ No newline at end of file diff --git a/src/main/resources/css/main-view.scss b/src/main/resources/css/main-view.scss new file mode 100644 index 0000000..6b1db7d --- /dev/null +++ b/src/main/resources/css/main-view.scss @@ -0,0 +1,91 @@ +/************************************************** + * Main View +**************************************************/ +#homeLabel { + -fx-font-size: 35; + -fx-font-weight: bold; +} + +#statusLabel { + -fx-padding: 2 5 2 5; + -fx-text-fill: white; + -fx-font-size: 15; + -fx-background-color: #1bc964; + -fx-background-radius: 10; + -fx-text-alignment: CENTER; + -fx-border-radius: 10; +} + +.control-menu { + -fx-font-size: 15; + -fx-background-radius: 15; + -fx-padding: 10 40 10 40; + -fx-border-radius: 15; + -fx-border-width: 2; + -fx-opacity: 0.9; + + &:hover { + -fx-opacity: 1.0; + } +} + +#startButton { + -fx-background-color: #fa6057; + -fx-border-color: #fa6057; + -fx-text-fill: white; +} + +#passwordButton { + -fx-background-color: #1bc964; + -fx-border-color: #1bc964; +} + +#restartButton { + -fx-background-color: linear-gradient(#57b4f2, #9198e5); + -fx-border-color: linear-gradient(#57b4f2, #9198e5); + -fx-text-fill: white; +} + +#moreButton { + -fx-background-color: transparent; + -fx-text-fill: #9254d1; + -fx-border-color: #9254d1; + -fx-border-width: 2; +} + +#logArea { + -fx-font-family: "Lucida Console"; + -fx-font-size: 20; + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-color: transparent; + -fx-background-insets: 0; + -fx-background-color: #e9e9e9; + + &:focused { + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-color: transparent; + -fx-background-color: #e9e9e9; + } + + .content { + -fx-padding: 0 15 0 15; + -fx-background-radius: 15; + -fx-background-color: #e9e9e9; + -fx-border-radius: 15; + -fx-border-color: transparent; + + &:focused { + -fx-background-color: #e9e9e9; + } + } + + .scroll-pane { + -fx-background-color: transparent; + + .viewport { + -fx-background-color: transparent; + } + } +} \ No newline at end of file diff --git a/src/main/resources/css/root-view.css b/src/main/resources/css/root-view.css index 13fc80f..5464151 100644 --- a/src/main/resources/css/root-view.css +++ b/src/main/resources/css/root-view.css @@ -1,176 +1,99 @@ @import "root.css"; - /************************************************** * Window Header **************************************************/ -#windowHeader .iconButton { - /*-fx-max-height: 5px;*/ - /*-fx-max-width: 5px;*/ - -fx-border-radius: 15; +#windowHeader .icon-button { + -fx-icon-code: fa-circle; + -fx-opacity: 0.5; } - -#rootPane #closeIcon { - -fx-color: #fa6057; - -fx-opacity: 0.5; +#windowHeader .icon-button:hover { + -fx-opacity: 1; } - -#rootPane #closeIcon:hover { - -fx-opacity: 1.0; +#windowHeader #closeIcon { + -fx-color: #fa6057; + -fx-background-color: #fa6057; + -fx-icon-color: #fa6057; } - -#rootPane #minimizeIcon { - -fx-color: #fbbc2e; - -fx-opacity: 0.5; +#windowHeader #minimizeIcon { + -fx-color: #fbbc2e; + -fx-background-color: #fbbc2e; + -fx-icon-color: #fbbc2e; } - -#rootPane #minimizeIcon:hover { - -fx-opacity: 1.0; +#windowHeader #alwaysOnTopIcon { + -fx-color: #27c940; + -fx-background-color: #27c940; + -fx-icon-color: #27c940; } - -#rootPane #alwaysOnTopIcon { - -fx-color: #27c940; - -fx-opacity: 0.5; -} - -#rootPane #alwaysOnTopIcon:hover { - -fx-opacity: 1.0; -} - -#rootPane #alwaysOnTopIcon:always-on-top { - -fx-opacity: 1.0; +#windowHeader #alwaysOnTopIcon:always-on-top { + -fx-opacity: 1; } /************************************************** * Tab label **************************************************/ - #tabPane .tab-header-area { - -fx-background-radius: 10; - -fx-background-color: #0000; + -fx-background-radius: 0 0 0 0; + -fx-background-color: transparent; } - #tabPane .headers-region { - -fx-alignment: TOP_CENTER; - -fx-background-color: #18181a; - -fx-background-radius: 10; - -fx-padding: 5 0 5 0; + -fx-alignment: TOP_CENTER; + -fx-background-color: #e9e9e9; + -fx-background-radius: 15; + -fx-padding: 5 0 5 0; } - #tabPane .tab-header-background { - -fx-background-color: #0000; + -fx-background-color: transparent; } - #tabPane .tab { - -fx-text-fill: white; - -fx-padding: 10 20 10 20; - -fx-background-radius: 10; - -fx-background-color: #0000; - -fx-border-width: 0; + -fx-padding: 10 20 10 20; + -fx-background-radius: 15; + -fx-background-color: transparent; + -fx-border-width: 0; } - -#tabPane .tab-label { - -fx-font-size: 15px; - -fx-text-fill: #707079; +#tabPane .tab .ikonli-font-icon { + -fx-icon-color: black; +} +#tabPane .tab .tab-label { + -fx-text-alignment: CENTER; + -fx-alignment: CENTER; } - #tabPane .tab:selected { - -fx-background-color: #2c69e0; + -fx-background-color: #2c69e0; +} +#tabPane .tab:selected .ikonli-font-icon { + -fx-icon-color: white; } - #tabPane .tab:selected .tab-label { - -fx-text-fill: white; + -fx-text-fill: white; +} +#tabPane .tab:selected .focus-indicator { + -fx-border-width: 0; + -fx-border-color: transparent; + -fx-border-insets: 0; +} +#tabPane .tab-label { + -fx-font-size: 15px; } /************************************************** - * Main View + * Window Footer **************************************************/ -#homeLabel { - -fx-font-size: 35px; - -fx-font-weight: bold; - -fx-text-fill: white; - -fx-font-family: 'JetBrains Mono'; +#windowFooter .button { + -fx-font-size: 15; + -fx-background-color: transparent; + -fx-text-alignment: CENTER; +} +#windowFooter #document { + -fx-text-fill: #1bc964; +} +#windowFooter #github { + -fx-text-fill: #1f4ca6; +} +#windowFooter #sponsor { + -fx-text-fill: #f22760; +} +#windowFooter .ikonli-font-icon { + -fx-font-size: 15; } -#statusLabel { - -fx-padding: 2 5 2 5; - -fx-text-fill: black; - -fx-background-color: #1bc964; - -fx-background-radius: 10; - -fx-text-alignment: CENTER; - -fx-border-radius: 10; - -fx-border-color: black; -} - -.controlMenu { - -fx-font-size: 15; - -fx-background-radius: 10px; - -fx-padding: 10 40 10 40; - -fx-border-radius: 10; -} - -.controlMenu:focused { - -fx-opacity: 0.5; -} - -#startButton { - -fx-background-color: #fa6057; - -fx-text-fill: white; - -fx-opacity: 1.0; -} - -#passwordButton { - -fx-background-color: #1bc964; - -fx-opacity: 1.0; -} - -#restartButton { - -fx-background-color: linear-gradient(#57b4f2, #9198e5); - -fx-text-fill: white; - -fx-opacity: 1.0; -} - -#moreButton { - -fx-background-color: #0000; - -fx-text-fill: #9254d1; - -fx-border-color: #9254d1; - -fx-border-width: 2px; - -fx-opacity: 1.0; -} - -#tabPane .tab:selected .focus-indicator { - -fx-border-width: 0; - -fx-border-color: #0000; -} - -#logArea { - -fx-text-fill: #e3e4e4; - -fx-font-family: 'JetBrains Mono'; - -fx-font-size: 20; - -fx-background-radius: 15; - -fx-border-radius: 15; - -fx-border-color: transparent; - -fx-background-insets: 0; - -fx-background-color: #18181c; -} - -#logArea .content { - -fx-padding: 15; - -fx-background-radius: 15; - -fx-background-color: #18181c; - -fx-border-radius: 15; - -fx-border-color: transparent; -} - -#logArea .scroll-pane { - -fx-background-color: transparent; - } -#logArea .scroll-pane .viewport{ - -fx-background-color: transparent; -} - -#logArea:focused { - -fx-background-radius: 15; - -fx-border-radius: 15; - -fx-border-color: transparent; - -fx-background-color: #18181c; -} \ No newline at end of file +/*# sourceMappingURL=root-view.css.map */ diff --git a/src/main/resources/css/root-view.css.map b/src/main/resources/css/root-view.css.map new file mode 100644 index 0000000..a55b2cc --- /dev/null +++ b/src/main/resources/css/root-view.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["root-view.scss"],"names":[],"mappings":"AAAQ;AACR;AAAA;AAAA;AAIE;EACE;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAEA;EACE;;;AAMN;AAAA;AAAA;AAME;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAKN;EACE;;;AAIJ;AAAA;AAAA;AAKE;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE","file":"root-view.css"} \ No newline at end of file diff --git a/src/main/resources/css/root-view.scss b/src/main/resources/css/root-view.scss new file mode 100644 index 0000000..a703d73 --- /dev/null +++ b/src/main/resources/css/root-view.scss @@ -0,0 +1,126 @@ +@import "root.css"; +/************************************************** + * Window Header +**************************************************/ +#windowHeader { + .icon-button { + -fx-icon-code: fa-circle; + -fx-opacity: 0.5; + + &:hover { + -fx-opacity: 1.0; + } + } + + #closeIcon { + -fx-color: #fa6057; + -fx-background-color: #fa6057; + -fx-icon-color: #fa6057; + } + + #minimizeIcon { + -fx-color: #fbbc2e; + -fx-background-color: #fbbc2e; + -fx-icon-color: #fbbc2e; + } + + #alwaysOnTopIcon { + -fx-color: #27c940; + -fx-background-color: #27c940; + -fx-icon-color: #27c940; + + &:always-on-top { + -fx-opacity: 1.0; + } + } +} + + +/************************************************** + * Tab label +**************************************************/ + +#tabPane { + + .tab-header-area { + -fx-background-radius: 0 0 0 0; + -fx-background-color: transparent; + } + + .headers-region { + -fx-alignment: TOP_CENTER; + -fx-background-color: #e9e9e9; + -fx-background-radius: 15; + -fx-padding: 5 0 5 0; + } + + .tab-header-background { + -fx-background-color: transparent; + } + + .tab { + -fx-padding: 10 20 10 20; + -fx-background-radius: 15; + -fx-background-color: transparent; + -fx-border-width: 0; + + .ikonli-font-icon { + -fx-icon-color: black; + } + + .tab-label { + -fx-text-alignment: CENTER; + -fx-alignment: CENTER; + } + + &:selected { + -fx-background-color: #2c69e0; + + .ikonli-font-icon { + -fx-icon-color: white; + } + + .tab-label { + -fx-text-fill: white; + } + + .focus-indicator { + -fx-border-width: 0; + -fx-border-color: transparent; + -fx-border-insets: 0; + } + } + } + + .tab-label { + -fx-font-size: 15px; + } +} + +/************************************************** + * Window Footer +**************************************************/ + +#windowFooter { + .button { + -fx-font-size: 15; + -fx-background-color: transparent; + -fx-text-alignment: CENTER; + } + + #document { + -fx-text-fill: #1bc964; + } + + #github { + -fx-text-fill: #1f4ca6; + } + + #sponsor { + -fx-text-fill: #f22760; + } + + .ikonli-font-icon { + -fx-font-size: 15; + } +} \ No newline at end of file diff --git a/src/main/resources/css/root.css b/src/main/resources/css/root.css index 8fa808f..2f9d683 100644 --- a/src/main/resources/css/root.css +++ b/src/main/resources/css/root.css @@ -1,15 +1,14 @@ - /************************************************** * Root **************************************************/ - -.rootPane { - -fx-background-color: black; - -fx-background-radius: 10; - -fx-border-radius: 10; -} - .root { - -fx-font-family: "Comic Sans MS"; + -fx-font-size: 15; + -fx-font-weight: normal; } +.root-pane { + -fx-background-radius: 15; + -fx-border-radius: 15; +} + +/*# sourceMappingURL=root.css.map */ diff --git a/src/main/resources/css/root.css.map b/src/main/resources/css/root.css.map new file mode 100644 index 0000000..32c7e3f --- /dev/null +++ b/src/main/resources/css/root.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["root.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAIA;EACE;EACA;;;AAGF;EACE;EACA","file":"root.css"} \ No newline at end of file diff --git a/src/main/resources/css/root.scss b/src/main/resources/css/root.scss new file mode 100644 index 0000000..d11c35a --- /dev/null +++ b/src/main/resources/css/root.scss @@ -0,0 +1,14 @@ +/************************************************** + * Root +**************************************************/ + +.root { + -fx-font-size: 15; + -fx-font-weight: normal; +} + +.root-pane { + -fx-background-radius: 15; + -fx-border-radius: 15; +} + diff --git a/src/main/resources/css/setup-view.css b/src/main/resources/css/setup-view.css new file mode 100644 index 0000000..a7aecf5 --- /dev/null +++ b/src/main/resources/css/setup-view.css @@ -0,0 +1,34 @@ +/************************************************** + * Setup View +**************************************************/ +#setupView .check-box { + -fx-font-size: 15; +} +#setupView .proxy-panel { + -fx-background-color: #e9e9e9; + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-width: 5; +} +#setupView .proxy-panel .radio-button { + -fx-background-color: transparent; +} +#setupView .proxy-label { + -fx-font-size: 15; + -fx-text-fill: #2c69e0; + -fx-text-alignment: CENTER; +} +#setupView .shield .label { + -fx-text-fill: white; + -fx-label-padding: 3 5 3 5; +} +#setupView .shield .shield-name { + -fx-background-color: #555555; + -fx-background-radius: 5 0 0 5; +} +#setupView .shield .shield-version { + -fx-background-color: #6969AA; + -fx-background-radius: 0 5 5 0; +} + +/*# sourceMappingURL=setup-view.css.map */ diff --git a/src/main/resources/css/setup-view.css.map b/src/main/resources/css/setup-view.css.map new file mode 100644 index 0000000..6828792 --- /dev/null +++ b/src/main/resources/css/setup-view.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["setup-view.scss"],"names":[],"mappings":"AAAA;AAAA;AAAA;AAKE;EACE;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;EACA;;AAKA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA","file":"setup-view.css"} \ No newline at end of file diff --git a/src/main/resources/css/setup-view.scss b/src/main/resources/css/setup-view.scss new file mode 100644 index 0000000..fe2a1ea --- /dev/null +++ b/src/main/resources/css/setup-view.scss @@ -0,0 +1,45 @@ +/************************************************** + * Setup View +**************************************************/ + +#setupView { + .check-box { + -fx-font-size: 15; + } + + .proxy-panel { + -fx-background-color: #e9e9e9; + -fx-background-radius: 15; + -fx-border-radius: 15; + -fx-border-width: 5; + + .radio-button { + -fx-background-color: transparent; + } + } + + .proxy-label { + -fx-font-size: 15; + -fx-text-fill: #2c69e0; + -fx-text-alignment: CENTER; + } + + .shield { + + .label { + -fx-text-fill: white; + -fx-label-padding: 3 5 3 5; + } + + .shield-name { + -fx-background-color: #555555; + -fx-background-radius: 5 0 0 5; + } + + .shield-version { + -fx-background-color: #6969AA; + -fx-background-radius: 0 5 5 0; + } + } +} + diff --git a/src/main/resources/fxml/main-view.fxml b/src/main/resources/fxml/main-view.fxml new file mode 100644 index 0000000..3f82c29 --- /dev/null +++ b/src/main/resources/fxml/main-view.fxml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + - - - - + + - + - + + + + + + + +