mirror of
https://github.com/octopusYan/alist-gui.git
synced 2024-11-21 19:56:41 +08:00
Initial commit
This commit is contained in:
commit
198c44f404
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
.mvn/
|
||||
mvnw
|
||||
mvnw.cmd
|
||||
log/
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea/
|
||||
.idea/modules.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
203
pom.xml
Normal file
203
pom.xml
Normal file
@ -0,0 +1,203 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>cn.octopusyan</groupId>
|
||||
<artifactId>alist-gui</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<name>alist-gui</name>
|
||||
|
||||
<organization>
|
||||
<name>octopus_yan</name>
|
||||
<url>octopus_yan@foxmail.com</url>
|
||||
</organization>
|
||||
|
||||
<inceptionYear>2024</inceptionYear>
|
||||
<description>alist windows gui</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<java.version>17</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
||||
<junit.version>5.10.0</junit.version>
|
||||
<javafx.version>17.0.6</javafx.version>
|
||||
<slf4j.version>2.0.16</slf4j.version>
|
||||
<logback.version>1.4.14</logback.version>
|
||||
<fastjson.version>2.0.52</fastjson.version>
|
||||
<hutool.version>5.8.25</hutool.version>
|
||||
<common-lang3.version>3.16.0</common-lang3.version>
|
||||
<common-io.version>2.16.1</common-io.version>
|
||||
<common-exec.version>1.4.0</common-exec.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- javafx -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- slf4j -->
|
||||
<!-- https://slf4j.org/manual.html -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-core</artifactId>
|
||||
<version>${logback.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- junit -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- common -->
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${common-lang3.version}</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${common-io.version}</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-exec -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-exec</artifactId>
|
||||
<version>${common-exec.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON -->
|
||||
<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/org.kordamp.ikonli/ikonli-javafx -->
|
||||
<dependency>
|
||||
<groupId>org.kordamp.ikonli</groupId>
|
||||
<artifactId>ikonli-javafx</artifactId>
|
||||
<version>12.3.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.kordamp.ikonli</groupId>
|
||||
<artifactId>ikonli-coreui-pack</artifactId>
|
||||
<version>12.3.1</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.11.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<nonFilteredFileExtensions>
|
||||
<nonFilteredFileExtension>exe</nonFilteredFileExtension>
|
||||
<nonFilteredFileExtension>dll</nonFilteredFileExtension>
|
||||
</nonFilteredFileExtensions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- Default configuration for running with: mvn clean javafx:run -->
|
||||
<id>default-cli</id>
|
||||
<configuration>
|
||||
<mainClass>cn.octopusyan.alistgui/cn.octopusyan.alistgui.AppLuncher
|
||||
</mainClass>
|
||||
<launcher>launcher</launcher>
|
||||
<jlinkZipName>app</jlinkZipName>
|
||||
<jlinkImageName>app</jlinkImageName>
|
||||
<noManPages>true</noManPages>
|
||||
<stripDebug>true</stripDebug>
|
||||
<noHeaderFiles>true</noHeaderFiles>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>io.github.fvarrui</groupId>
|
||||
<artifactId>javapackager</artifactId>
|
||||
<version>1.7.5</version>
|
||||
<configuration>
|
||||
<bundleJre>true</bundleJre>
|
||||
<mainClass>cn.octopusyan.alistgui.AppLuncher</mainClass>
|
||||
<generateInstaller>false</generateInstaller>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>bundling-for-windows</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>package</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<platform>windows</platform>
|
||||
<createZipball>true</createZipball>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
13
src/main/java/cn/octopusyan/alistgui/AppLuncher.java
Normal file
13
src/main/java/cn/octopusyan/alistgui/AppLuncher.java
Normal file
@ -0,0 +1,13 @@
|
||||
package cn.octopusyan.alistgui;
|
||||
|
||||
/**
|
||||
* 启动类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class AppLuncher {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Application.launch(Application.class, args);
|
||||
}
|
||||
}
|
93
src/main/java/cn/octopusyan/alistgui/Application.java
Normal file
93
src/main/java/cn/octopusyan/alistgui/Application.java
Normal file
@ -0,0 +1,93 @@
|
||||
package cn.octopusyan.alistgui;
|
||||
|
||||
import cn.octopusyan.alistgui.config.AppConstant;
|
||||
import cn.octopusyan.alistgui.config.CustomConfig;
|
||||
import cn.octopusyan.alistgui.controller.MainController;
|
||||
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.scene.Scene;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
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);
|
||||
|
||||
@Override
|
||||
public void init() throws Exception {
|
||||
logger.info("application init ...");
|
||||
// 初始化客户端配置
|
||||
CustomConfig.init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws IOException {
|
||||
|
||||
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();//底层面板
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
logger.info("application start over ...");
|
||||
}
|
||||
|
||||
private void showErrorDialog(Thread t, Throwable e) {
|
||||
logger.error("", e);
|
||||
AlertUtil.exceptionAlert(new Exception(e)).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
logger.info("application stop ...");
|
||||
// 停止所有线程
|
||||
ThreadPoolManager.getInstance().shutdown();
|
||||
// 保存应用数据
|
||||
CustomConfig.store();
|
||||
}
|
||||
}
|
203
src/main/java/cn/octopusyan/alistgui/base/BaseController.java
Normal file
203
src/main/java/cn/octopusyan/alistgui/base/BaseController.java
Normal file
@ -0,0 +1,203 @@
|
||||
package cn.octopusyan.alistgui.base;
|
||||
|
||||
import cn.octopusyan.alistgui.config.AppConstant;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* 通用视图控制器基类
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public abstract class BaseController<P extends Pane> 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<P> 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<? extends BaseController<?>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL url, ResourceBundle resourceBundle) {
|
||||
// 全局窗口拖拽
|
||||
if (dragWindow()) {
|
||||
// 窗口拖拽
|
||||
getRootPanel().setOnMousePressed(event -> {
|
||||
xOffSet = event.getSceneX();
|
||||
yOffSet = event.getSceneY();
|
||||
});
|
||||
getRootPanel().setOnMouseDragged(event -> {
|
||||
Stage stage = (Stage) getWindow();
|
||||
stage.setX(event.getScreenX() - xOffSet);
|
||||
stage.setY(event.getScreenY() - yOffSet);
|
||||
});
|
||||
}
|
||||
|
||||
// 窗口初始化完成监听
|
||||
getRootPanel().sceneProperty().addListener((observable, oldValue, newValue) -> {
|
||||
newValue.windowProperty().addListener(new ChangeListener<Window>() {
|
||||
@Override
|
||||
public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
|
||||
//关闭窗口监听
|
||||
getWindow().setOnCloseRequest(windowEvent -> onDestroy());
|
||||
|
||||
// app 版本信息
|
||||
if (getAppVersionLabel() != null) getAppVersionLabel().setText("v" + AppConstant.APP_VERSION);
|
||||
|
||||
// 初始化数据
|
||||
initData();
|
||||
|
||||
// 初始化视图样式
|
||||
initViewStyle();
|
||||
|
||||
// 初始化视图事件
|
||||
initViewAction();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void setApplication(Application application) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
public Application getApplication() {
|
||||
return application;
|
||||
}
|
||||
|
||||
public Stage getPrimaryStage() {
|
||||
return primaryStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口拖拽设置
|
||||
*
|
||||
* @return 是否启用
|
||||
*/
|
||||
public abstract boolean dragWindow();
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
*
|
||||
* @return 根布局对象
|
||||
*/
|
||||
public abstract P getRootPanel();
|
||||
|
||||
/**
|
||||
* 获取根布局
|
||||
* <p> 搭配 <code>FxmlUtil.load</code> 使用
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
public abstract void initData();
|
||||
|
||||
/**
|
||||
* 视图样式
|
||||
*/
|
||||
public abstract void initViewStyle();
|
||||
|
||||
/**
|
||||
* 视图事件
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
21
src/main/java/cn/octopusyan/alistgui/config/AppConstant.java
Normal file
21
src/main/java/cn/octopusyan/alistgui/config/AppConstant.java
Normal file
@ -0,0 +1,21 @@
|
||||
package cn.octopusyan.alistgui.config;
|
||||
|
||||
import cn.octopusyan.alistgui.util.PropertiesUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 应用信息
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
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 BAK_FILE_PATH = AppConstant.TMP_DIR_PATH + File.separator + "bak";
|
||||
}
|
110
src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java
Normal file
110
src/main/java/cn/octopusyan/alistgui/config/CustomConfig.java
Normal file
@ -0,0 +1,110 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 主页面控制器
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class MainController extends BaseController<VBox> implements Initializable {
|
||||
|
||||
private double xOffset;
|
||||
private double yOffset;
|
||||
|
||||
// 布局
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
@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);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
package cn.octopusyan.alistgui.manager.http;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLParameters;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.net.Authenticator;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.http.HttpClient;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Http配置参数
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class HttpConfig {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HttpConfig.class);
|
||||
/**
|
||||
* http版本
|
||||
*/
|
||||
private HttpClient.Version version = HttpClient.Version.HTTP_2;
|
||||
|
||||
/**
|
||||
* 转发策略
|
||||
*/
|
||||
private HttpClient.Redirect redirect = HttpClient.Redirect.NORMAL;
|
||||
|
||||
/**
|
||||
* 线程池
|
||||
*/
|
||||
private Executor executor;
|
||||
|
||||
/**
|
||||
* 认证
|
||||
*/
|
||||
private Authenticator authenticator;
|
||||
|
||||
/**
|
||||
* 代理
|
||||
*/
|
||||
private ProxySelector proxySelector;
|
||||
|
||||
/**
|
||||
* CookieHandler
|
||||
*/
|
||||
private CookieHandler cookieHandler;
|
||||
|
||||
/**
|
||||
* sslContext
|
||||
*/
|
||||
private SSLContext sslContext;
|
||||
|
||||
/**
|
||||
* sslParams
|
||||
*/
|
||||
private SSLParameters sslParameters;
|
||||
|
||||
/**
|
||||
* 连接超时时间毫秒
|
||||
*/
|
||||
private int connectTimeout = 10000;
|
||||
|
||||
/**
|
||||
* 默认读取数据超时时间
|
||||
*/
|
||||
private int defaultReadTimeout = 1200000;
|
||||
|
||||
|
||||
public HttpConfig() {
|
||||
TrustManager[] trustAllCertificates = new X509TrustManager[]{new X509TrustManager() {
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0]; // Not relevant.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
|
||||
// TODO Auto-generated method stub
|
||||
}
|
||||
}};
|
||||
sslParameters = new SSLParameters();
|
||||
sslParameters.setEndpointIdentificationAlgorithm("");
|
||||
|
||||
|
||||
try {
|
||||
sslContext = SSLContext.getInstance("TLSv1.2");
|
||||
System.setProperty("jdk.internal.httpclient.disableHostnameVerification", "true");//取消主机名验证
|
||||
sslContext.init(null, trustAllCertificates, new SecureRandom());
|
||||
} 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;
|
||||
}
|
||||
}
|
141
src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
Normal file
141
src/main/java/cn/octopusyan/alistgui/manager/http/HttpUtil.java
Normal file
@ -0,0 +1,141 @@
|
||||
package cn.octopusyan.alistgui.manager.http;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
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.Optional;
|
||||
|
||||
/**
|
||||
* 网络请求封装
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class HttpUtil {
|
||||
private volatile static HttpUtil util;
|
||||
private volatile HttpClient httpClient;
|
||||
private final HttpConfig httpConfig;
|
||||
|
||||
private HttpUtil(HttpConfig httpConfig) {
|
||||
this.httpConfig = httpConfig;
|
||||
this.httpClient = createClient(httpConfig);
|
||||
}
|
||||
|
||||
public static HttpUtil getInstance() {
|
||||
if (util == null) {
|
||||
throw new RuntimeException("are you ready ?");
|
||||
}
|
||||
return util;
|
||||
}
|
||||
|
||||
public static void init(HttpConfig httpConfig) {
|
||||
synchronized (HttpUtil.class) {
|
||||
util = new HttpUtil(httpConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private static HttpClient createClient(HttpConfig httpConfig) {
|
||||
HttpClient.Builder builder = HttpClient.newBuilder()
|
||||
.version(httpConfig.getVersion())
|
||||
.connectTimeout(Duration.ofMillis(httpConfig.getConnectTimeout()))
|
||||
.sslContext(httpConfig.getSslContext())
|
||||
.sslParameters(httpConfig.getSslParameters())
|
||||
.followRedirects(httpConfig.getRedirect());
|
||||
Optional.ofNullable(httpConfig.getAuthenticator()).ifPresent(builder::authenticator);
|
||||
Optional.ofNullable(httpConfig.getCookieHandler()).ifPresent(builder::cookieHandler);
|
||||
Optional.ofNullable(httpConfig.getProxySelector()).ifPresent(builder::proxy);
|
||||
Optional.ofNullable(httpConfig.getExecutor()).ifPresent(builder::executor);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public HttpUtil proxy(String host, int port) {
|
||||
if (httpClient == null)
|
||||
throw new RuntimeException("are you ready ?");
|
||||
|
||||
InetSocketAddress unresolved = InetSocketAddress.createUnresolved(host, port);
|
||||
ProxySelector other = ProxySelector.of(unresolved);
|
||||
this.httpConfig.setProxySelector(other);
|
||||
this.httpClient = createClient(httpConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void clearProxy() {
|
||||
if (httpClient == null)
|
||||
throw new RuntimeException("are you ready ?");
|
||||
|
||||
httpConfig.setProxySelector(HttpClient.Builder.NO_PROXY);
|
||||
httpClient = createClient(httpConfig);
|
||||
}
|
||||
|
||||
public HttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
public String get(String uri, JSONObject header, JSONObject param) throws IOException, InterruptedException {
|
||||
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header).GET();
|
||||
HttpResponse<String> 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 {
|
||||
HttpRequest.Builder request = getRequest(uri, header)
|
||||
.POST(HttpRequest.BodyPublishers.ofString(param.toJSONString()));
|
||||
HttpResponse<String> 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 {
|
||||
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header)
|
||||
.POST(HttpRequest.BodyPublishers.noBody());
|
||||
|
||||
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
|
||||
return response.body();
|
||||
}
|
||||
|
||||
private HttpRequest.Builder getRequest(String uri, JSONObject 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));
|
||||
}
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private String createFormParams(JSONObject 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);
|
||||
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 {
|
||||
formParams.append("&").append(key).append("=").append(params.getJSONObject(key));
|
||||
}
|
||||
}
|
||||
if (!formParams.isEmpty()) {
|
||||
formParams = new StringBuilder("?" + formParams.substring(1));
|
||||
}
|
||||
|
||||
return formParams.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cn.octopusyan.alistgui.manager.thread;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 自定义线程工厂
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class ThreadFactory implements java.util.concurrent.ThreadFactory {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ThreadFactory.class);
|
||||
|
||||
public static final String DEFAULT_THREAD_PREFIX = "thread-factory-pool";
|
||||
|
||||
private static final AtomicInteger poolNumber = new AtomicInteger(1);
|
||||
private final ThreadGroup group;
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
private final String namePrefix;
|
||||
|
||||
public ThreadFactory() {
|
||||
this(DEFAULT_THREAD_PREFIX);
|
||||
}
|
||||
|
||||
public ThreadFactory(String prefix) {
|
||||
group = Thread.currentThread().getThreadGroup();
|
||||
namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable runnable) {
|
||||
|
||||
Thread t = new Thread(group, runnable,
|
||||
namePrefix + threadNumber.getAndIncrement(),
|
||||
0);
|
||||
|
||||
t.setUncaughtExceptionHandler((t1, e) -> {
|
||||
logger.error("thread : {}, error", t1.getName(), e);
|
||||
});
|
||||
|
||||
if (t.isDaemon())
|
||||
t.setDaemon(false);
|
||||
if (t.getPriority() != Thread.NORM_PRIORITY)
|
||||
t.setPriority(Thread.NORM_PRIORITY);
|
||||
return t;
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package cn.octopusyan.alistgui.manager.thread;
|
||||
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 线程池管理类
|
||||
*/
|
||||
public final class ThreadPoolManager extends ThreadPoolExecutor {
|
||||
|
||||
private static volatile ThreadPoolManager sInstance;
|
||||
|
||||
private ThreadPoolManager() {
|
||||
super(32,
|
||||
200,
|
||||
10,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(200),
|
||||
new ThreadFactory(ThreadFactory.DEFAULT_THREAD_PREFIX),
|
||||
new DiscardPolicy());
|
||||
}
|
||||
|
||||
public static ThreadPoolManager getInstance() {
|
||||
if (sInstance == null) sInstance = new ThreadPoolManager();
|
||||
return sInstance;
|
||||
}
|
||||
}
|
228
src/main/java/cn/octopusyan/alistgui/util/AlertUtil.java
Normal file
228
src/main/java/cn/octopusyan/alistgui/util/AlertUtil.java
Normal file
@ -0,0 +1,228 @@
|
||||
package cn.octopusyan.alistgui.util;
|
||||
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 弹窗工具
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class AlertUtil {
|
||||
private static Window mOwner;
|
||||
private static Builder builder;
|
||||
|
||||
public static void initOwner(Stage stage) {
|
||||
AlertUtil.mOwner = stage;
|
||||
}
|
||||
|
||||
public static class Builder<T extends Dialog> {
|
||||
T alert;
|
||||
|
||||
public Builder(T alert) {
|
||||
this.alert = alert;
|
||||
if (mOwner != null) this.alert.initOwner(mOwner);
|
||||
}
|
||||
|
||||
public Builder<T> title(String title) {
|
||||
alert.setTitle(title);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> header(String header) {
|
||||
alert.setHeaderText(header);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> content(String content) {
|
||||
alert.setContentText(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> icon(String path) {
|
||||
icon(new Image(Objects.requireNonNull(this.getClass().getResource(path)).toString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> icon(Image image) {
|
||||
getStage().getIcons().add(image);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void show() {
|
||||
if (AlertUtil.builder == null) {
|
||||
AlertUtil.builder = this;
|
||||
} else if (AlertUtil.builder.alert.isShowing()) {
|
||||
if (!Objects.equals(AlertUtil.builder.alert.getContentText(), alert.getContentText()))
|
||||
((Alert) AlertUtil.builder.alert).setOnHidden(event -> {
|
||||
AlertUtil.builder = null;
|
||||
show();
|
||||
});
|
||||
}
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.confirm
|
||||
*/
|
||||
public void show(OnClickListener listener) {
|
||||
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
|
||||
listener.onClicked(result.get().getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.confirm
|
||||
*/
|
||||
public void show(OnChoseListener listener) {
|
||||
Optional<ButtonType> result = alert.showAndWait();
|
||||
if (result.get() == ButtonType.OK) {
|
||||
listener.confirm();
|
||||
} else {
|
||||
listener.cancelOrClose(result.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.input
|
||||
* 如果用户点击了取消按钮,将会返回null
|
||||
*/
|
||||
public String getInput() {
|
||||
Optional<String> result = alert.showAndWait();
|
||||
if (result.isPresent()) {
|
||||
return result.get();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* AlertUtil.choices
|
||||
*/
|
||||
public <R> R getChoice(R... choices) {
|
||||
Optional result = alert.showAndWait();
|
||||
return (R) result.get();
|
||||
}
|
||||
|
||||
private Stage getStage() {
|
||||
return (Stage) alert.getDialogPane().getScene().getWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public static Builder<Alert> info(String content) {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION)).content(content).header(null);
|
||||
}
|
||||
|
||||
public static Builder<Alert> info() {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.INFORMATION));
|
||||
}
|
||||
|
||||
public static Builder<Alert> error(String message) {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.ERROR)).header(null).content(message);
|
||||
}
|
||||
|
||||
public static Builder<Alert> warning() {
|
||||
return new Builder<Alert>(new Alert(Alert.AlertType.WARNING));
|
||||
}
|
||||
|
||||
public static Builder<Alert> exception(Exception ex) {
|
||||
return new Builder<Alert>(exceptionAlert(ex));
|
||||
}
|
||||
|
||||
public static Alert exceptionAlert(Exception ex) {
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setTitle("Exception Dialog");
|
||||
alert.setHeaderText(ex.getClass().getSimpleName());
|
||||
alert.setContentText(ex.getMessage());
|
||||
|
||||
// 创建可扩展的异常。
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
ex.printStackTrace(pw);
|
||||
String exceptionText = sw.toString();
|
||||
|
||||
Label label = new Label("The exception stacktrace was :");
|
||||
|
||||
TextArea textArea = new TextArea(exceptionText);
|
||||
textArea.setEditable(false);
|
||||
textArea.setWrapText(true);
|
||||
|
||||
textArea.setMaxWidth(Double.MAX_VALUE);
|
||||
textArea.setMaxHeight(Double.MAX_VALUE);
|
||||
GridPane.setVgrow(textArea, Priority.ALWAYS);
|
||||
GridPane.setHgrow(textArea, Priority.ALWAYS);
|
||||
|
||||
GridPane expContent = new GridPane();
|
||||
expContent.setMaxWidth(Double.MAX_VALUE);
|
||||
expContent.add(label, 0, 0);
|
||||
expContent.add(textArea, 0, 1);
|
||||
|
||||
// 将可扩展异常设置到对话框窗格中。
|
||||
alert.getDialogPane().setExpandableContent(expContent);
|
||||
return alert;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认对话框
|
||||
*/
|
||||
public static Builder<Alert> confirm() {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle("确认对话框");
|
||||
return new Builder<Alert>(alert);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义确认对话框 <p>
|
||||
* <code>"Cancel"</code> OR <code>"取消"</code> 为取消按钮
|
||||
*/
|
||||
public static Builder<Alert> confirm(String... buttons) {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
List<ButtonType> buttonList = Arrays.stream(buttons).map((type) -> {
|
||||
ButtonBar.ButtonData buttonData = ButtonBar.ButtonData.OTHER;
|
||||
if ("Cancel".equals(type) || "取消".equals(type))
|
||||
buttonData = ButtonBar.ButtonData.CANCEL_CLOSE;
|
||||
return new ButtonType(type, buttonData);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
alert.getButtonTypes().setAll(buttonList);
|
||||
|
||||
return new Builder<Alert>(alert);
|
||||
}
|
||||
|
||||
public static Builder<TextInputDialog> input(String content) {
|
||||
TextInputDialog dialog = new TextInputDialog();
|
||||
dialog.setContentText(content);
|
||||
return new Builder<TextInputDialog>(dialog);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Builder<ChoiceDialog<T>> choices(String hintText, T... choices) {
|
||||
ChoiceDialog<T> dialog = new ChoiceDialog<T>(choices[0], choices);
|
||||
dialog.setContentText(hintText);
|
||||
return new Builder<ChoiceDialog<T>>(dialog);
|
||||
}
|
||||
|
||||
|
||||
public interface OnChoseListener {
|
||||
void confirm();
|
||||
|
||||
void cancelOrClose(ButtonType buttonType);
|
||||
}
|
||||
|
||||
public interface OnClickListener {
|
||||
void onClicked(String result);
|
||||
}
|
||||
}
|
28
src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java
Normal file
28
src/main/java/cn/octopusyan/alistgui/util/ClipUtil.java
Normal file
@ -0,0 +1,28 @@
|
||||
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();
|
||||
}
|
||||
}
|
135
src/main/java/cn/octopusyan/alistgui/util/FileUtil.java
Normal file
135
src/main/java/cn/octopusyan/alistgui/util/FileUtil.java
Normal file
@ -0,0 +1,135 @@
|
||||
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<String> listFileNames(String path) {
|
||||
Collection<File> 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;
|
||||
}
|
||||
}
|
26
src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java
Normal file
26
src/main/java/cn/octopusyan/alistgui/util/FxmlUtil.java
Normal file
@ -0,0 +1,26 @@
|
||||
package cn.octopusyan.alistgui.util;
|
||||
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.JavaFXBuilderFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* FXML 工具
|
||||
*
|
||||
* @author octopus_yan@foxmail.com
|
||||
*/
|
||||
public class FxmlUtil {
|
||||
|
||||
public static FXMLLoader load(String name) {
|
||||
String prefix = "/fxml/";
|
||||
String suffix = ".fxml";
|
||||
return new FXMLLoader(
|
||||
FxmlUtil.class.getResource(prefix + name + suffix),
|
||||
null,
|
||||
new JavaFXBuilderFactory(),
|
||||
null,
|
||||
StandardCharsets.UTF_8
|
||||
);
|
||||
}
|
||||
}
|
96
src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
Normal file
96
src/main/java/cn/octopusyan/alistgui/util/ProcessesUtil.java
Normal file
@ -0,0 +1,96 @@
|
||||
package cn.octopusyan.alistgui.util;
|
||||
|
||||
import org.apache.commons.exec.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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 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() {
|
||||
|
||||
}
|
||||
});
|
||||
handler.waitFor();
|
||||
} catch (Exception e) {
|
||||
logger.error("", e);
|
||||
}
|
||||
return 0 == handler.getExitValue();
|
||||
}
|
||||
|
||||
public static void exec(String command, OnExecuteListener listener) {
|
||||
LogOutputStream logout = new LogOutputStream() {
|
||||
@Override
|
||||
protected void processLine(String line, int logLevel) {
|
||||
if (listener != null) listener.onExecute(line + NEWLINE);
|
||||
}
|
||||
};
|
||||
|
||||
CommandLine commandLine = CommandLine.parse(command);
|
||||
DefaultExecutor executor = DefaultExecutor.builder().get();
|
||||
executor.setStreamHandler(new PumpStreamHandler(logout, logout));
|
||||
DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler() {
|
||||
@Override
|
||||
public void onProcessComplete(int exitValue) {
|
||||
if (listener != null) {
|
||||
listener.onExecuteSuccess(exitValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProcessFailed(ExecuteException e) {
|
||||
if (listener != null) {
|
||||
listener.onExecuteError(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
executor.execute(commandLine, handler);
|
||||
} catch (IOException e) {
|
||||
if (listener != null) listener.onExecuteError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnExecuteListener {
|
||||
void onExecute(String msg);
|
||||
|
||||
void onExecuteSuccess(int exitValue);
|
||||
|
||||
void onExecuteError(Exception e);
|
||||
void onExecuteOver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent construction.
|
||||
*/
|
||||
private ProcessesUtil() {
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package cn.octopusyan.alistgui.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* 配置文件读取工具
|
||||
*
|
||||
* @author liubin5620
|
||||
* @see <a href="https://blog.csdn.net/liubin5620/article/details/104618950">配置文件信息读取工具类【PropertiesUtils】</a>
|
||||
*/
|
||||
public class PropertiesUtils {
|
||||
/**
|
||||
* 主配置文件
|
||||
*/
|
||||
private final Properties properties;
|
||||
/**
|
||||
* 启用配置文件
|
||||
*/
|
||||
private final Properties propertiesCustom;
|
||||
|
||||
private static PropertiesUtils propertiesUtils = new PropertiesUtils();
|
||||
|
||||
public static final Logger logger = LoggerFactory.getLogger(PropertiesUtils.class);
|
||||
|
||||
/**
|
||||
* 私有构造,禁止直接创建
|
||||
*/
|
||||
private PropertiesUtils() {
|
||||
// 读取配置启用的配置文件名
|
||||
properties = new Properties();
|
||||
propertiesCustom = new Properties();
|
||||
InputStream in = PropertiesUtils.class.getClassLoader().getResourceAsStream("application.properties");
|
||||
try {
|
||||
properties.load(in);
|
||||
// 加载启用的配置
|
||||
String property = properties.getProperty("profiles.active");
|
||||
if (!StringUtils.isBlank(property)) {
|
||||
InputStream cin = PropertiesUtils.class.getClassLoader().getResourceAsStream("application-" + property + ".properties");
|
||||
propertiesCustom.load(cin);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("读取配置文件失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例
|
||||
*
|
||||
* @return PropertiesUtils
|
||||
*/
|
||||
public static PropertiesUtils getInstance() {
|
||||
if (propertiesUtils == null) {
|
||||
propertiesUtils = new PropertiesUtils();
|
||||
}
|
||||
return propertiesUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据属性名读取值
|
||||
* 先去主配置查询,如果查询不到,就去启用配置查询
|
||||
*
|
||||
* @param name 名称
|
||||
*/
|
||||
public String getProperty(String name) {
|
||||
String val = properties.getProperty(name);
|
||||
if (StringUtils.isBlank(val)) {
|
||||
val = propertiesCustom.getProperty(name);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
17
src/main/java/module-info.java
Normal file
17
src/main/java/module-info.java
Normal file
@ -0,0 +1,17 @@
|
||||
module cn.octopusyan.alistgui {
|
||||
requires java.net.http;
|
||||
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;
|
||||
|
||||
exports cn.octopusyan.alistgui;
|
||||
opens cn.octopusyan.alistgui to javafx.fxml;
|
||||
opens cn.octopusyan.alistgui.controller to javafx.fxml;
|
||||
}
|
3
src/main/resources/application.properties
Normal file
3
src/main/resources/application.properties
Normal file
@ -0,0 +1,3 @@
|
||||
app.name=${project.name}
|
||||
app.title=alist gui
|
||||
app.version=${project.version}
|
176
src/main/resources/css/root-view.css
Normal file
176
src/main/resources/css/root-view.css
Normal file
@ -0,0 +1,176 @@
|
||||
@import "root.css";
|
||||
|
||||
/**************************************************
|
||||
* Window Header
|
||||
**************************************************/
|
||||
#windowHeader .iconButton {
|
||||
/*-fx-max-height: 5px;*/
|
||||
/*-fx-max-width: 5px;*/
|
||||
-fx-border-radius: 15;
|
||||
}
|
||||
|
||||
#rootPane #closeIcon {
|
||||
-fx-color: #fa6057;
|
||||
-fx-opacity: 0.5;
|
||||
}
|
||||
|
||||
#rootPane #closeIcon:hover {
|
||||
-fx-opacity: 1.0;
|
||||
}
|
||||
|
||||
#rootPane #minimizeIcon {
|
||||
-fx-color: #fbbc2e;
|
||||
-fx-opacity: 0.5;
|
||||
}
|
||||
|
||||
#rootPane #minimizeIcon:hover {
|
||||
-fx-opacity: 1.0;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* Tab label
|
||||
**************************************************/
|
||||
|
||||
#tabPane .tab-header-area {
|
||||
-fx-background-radius: 10;
|
||||
-fx-background-color: #0000;
|
||||
}
|
||||
|
||||
#tabPane .headers-region {
|
||||
-fx-alignment: TOP_CENTER;
|
||||
-fx-background-color: #18181a;
|
||||
-fx-background-radius: 10;
|
||||
-fx-padding: 5 0 5 0;
|
||||
}
|
||||
|
||||
#tabPane .tab-header-background {
|
||||
-fx-background-color: #0000;
|
||||
}
|
||||
|
||||
#tabPane .tab {
|
||||
-fx-text-fill: white;
|
||||
-fx-padding: 10 20 10 20;
|
||||
-fx-background-radius: 10;
|
||||
-fx-background-color: #0000;
|
||||
-fx-border-width: 0;
|
||||
}
|
||||
|
||||
#tabPane .tab-label {
|
||||
-fx-font-size: 15px;
|
||||
-fx-text-fill: #707079;
|
||||
}
|
||||
|
||||
#tabPane .tab:selected {
|
||||
-fx-background-color: #2c69e0;
|
||||
}
|
||||
|
||||
#tabPane .tab:selected .tab-label {
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* Main View
|
||||
**************************************************/
|
||||
#homeLabel {
|
||||
-fx-font-size: 35px;
|
||||
-fx-font-weight: bold;
|
||||
-fx-text-fill: white;
|
||||
-fx-font-family: 'JetBrains Mono';
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
15
src/main/resources/css/root.css
Normal file
15
src/main/resources/css/root.css
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
/**************************************************
|
||||
* Root
|
||||
**************************************************/
|
||||
|
||||
.rootPane {
|
||||
-fx-background-color: black;
|
||||
-fx-background-radius: 10;
|
||||
-fx-border-radius: 10;
|
||||
}
|
||||
|
||||
.root {
|
||||
-fx-font-family: "Comic Sans MS";
|
||||
}
|
||||
|
90
src/main/resources/fxml/root-view.fxml
Normal file
90
src/main/resources/fxml/root-view.fxml
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.kordamp.ikonli.javafx.*?>
|
||||
<VBox fx:id="rootPane" styleClass="rootPane" alignment="TOP_CENTER" prefHeight="620.0" prefWidth="700.0" spacing="10.0"
|
||||
stylesheets="@../css/root-view.css" xmlns="http://javafx.com/javafx/11.0.14-internal"
|
||||
xmlns:fx="http://javafx.com/fxml/1" fx:controller="cn.octopusyan.alistgui.controller.MainController">
|
||||
|
||||
|
||||
<HBox fx:id="windowHeader" alignment="CENTER_RIGHT" prefWidth="Infinity" spacing="10.0">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
<Button fx:id="alwaysOnTopIcon" styleClass="iconButton"/>
|
||||
<Button fx:id="minimizeIcon" styleClass="iconButton"/>
|
||||
<Button fx:id="closeIcon" styleClass="iconButton"/>
|
||||
</HBox>
|
||||
|
||||
<TabPane fx:id="tabPane" prefWidth="Infinity" VBox.vgrow="ALWAYS" tabClosingPolicy="UNAVAILABLE">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0"/>
|
||||
</padding>
|
||||
<Tab text="主页">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="cil-library" iconColor="white"/>
|
||||
</graphic>
|
||||
<VBox>
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
<HBox styleClass="mainViewHeader" prefWidth="Infinity" alignment="TOP_CENTER">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
<Label fx:id="homeLabel" text="AList GUI" alignment="CENTER"/>
|
||||
<Label fx:id="statusLabel" text="运行中" alignment="TOP_CENTER">
|
||||
<HBox.margin>
|
||||
<Insets left="-10.0" top="-5"/>
|
||||
</HBox.margin>
|
||||
</Label>
|
||||
</HBox>
|
||||
<HBox prefWidth="Infinity" alignment="TOP_CENTER" spacing="25.0">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
|
||||
</padding>
|
||||
<VBox.margin>
|
||||
<Insets bottom="10.0" top="10.0"/>
|
||||
</VBox.margin>
|
||||
<Button fx:id="startButton" styleClass="controlMenu" text="开始"/>
|
||||
<Button fx:id="passwordButton" styleClass="controlMenu" text="密码"/>
|
||||
<Button fx:id="restartButton" styleClass="controlMenu" text="重启"/>
|
||||
<Button fx:id="moreButton" styleClass="controlMenu" text="更多"/>
|
||||
</HBox>
|
||||
<TextArea fx:id="logArea" editable="false" wrapText="true" prefWidth="Infinity" VBox.vgrow="ALWAYS"
|
||||
text="123d1a32s1d3as21d3a2s1d3a2s1d3a2s1d3a2s1d3a2s1d3a2s1d32aasda3s21da32s1d32a1sd">
|
||||
<VBox.margin>
|
||||
<Insets bottom="10.0" top="10.0"/>
|
||||
</VBox.margin>
|
||||
</TextArea>
|
||||
<HBox prefWidth="Infinity" alignment="CENTER" spacing="25.0">
|
||||
<padding>
|
||||
<Insets bottom="10.0" top="30.0"/>
|
||||
</padding>
|
||||
<Button fx:id="docmentLabel" text="文档" textAlignment="CENTER">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="cib-readme"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button fx:id="gethubLabel" text="Github" textAlignment="CENTER">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="cib-github"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
<Button fx:id="otherLabel" text="赞助" textAlignment="CENTER">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="cib-buy-me-a-coffee"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</Tab>
|
||||
<Tab fx:id="setupTab" text="设置">
|
||||
<graphic>
|
||||
<FontIcon iconLiteral="cil-settings" iconColor="white"/>
|
||||
</graphic>
|
||||
</Tab>
|
||||
</TabPane>
|
||||
</VBox>
|
98
src/main/resources/logback.xml
Normal file
98
src/main/resources/logback.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration scan="true" scanPeriod="60 seconds" debug="false">
|
||||
|
||||
<property name="logback.logdir" value="log"/>
|
||||
<property name="CHARSET" value="utf-8"/>
|
||||
<property name="logback.app" value="alist-gui"/>
|
||||
<!-- 彩色日志格式 -->
|
||||
<property name="CONSOLE_LOG_PATTERN" value="%highlight(%d{HH:mm:ss.SSS}) ${logback.app} %boldYellow([%thread]) %highlight(%-5level) %cyan(%logger{36}) - %mdc{client} [%X{trace_id}] %msg%n"/>
|
||||
|
||||
<!--输出到控制台 ConsoleAppender-->
|
||||
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
|
||||
<charset>${CHARSET}</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--输出到文件 fileLog-->
|
||||
<appender name="fileLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"/>
|
||||
<File>${logback.logdir}/${logback.app}.info.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
|
||||
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.info.log</FileNamePattern>
|
||||
<!--只保留最近30天的日志-->
|
||||
<maxHistory>30</maxHistory>
|
||||
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<!--日志输出编码格式化-->
|
||||
<encoder>
|
||||
<charset>${CHARSET}</charset>
|
||||
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
|
||||
</encoder>
|
||||
<!--只打印错误日志-->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>INFO</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<appender name="fileLog-debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<!--如果只是想要 Error 级别的日志,那么需要过滤一下,默认是 info 级别的,ThresholdFilter-->
|
||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"/>
|
||||
<File>${logback.logdir}/${logback.app}.debug.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--文件路径,定义了日志的切分方式——把每一天的日志归档到一个文件中,以防止日志填满整个磁盘空间-->
|
||||
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.debug.log</FileNamePattern>
|
||||
<!--只保留最近30天的日志-->
|
||||
<maxHistory>30</maxHistory>
|
||||
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<!--日志输出编码格式化-->
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
|
||||
</encoder>
|
||||
<!--只打印错误日志-->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>DEBUG</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 错误日志 -->
|
||||
<appender name="fileLog-err" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<File>${logback.logdir}/${logback.app}.err.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<FileNamePattern>${logback.logdir}/${logback.app}_%d{yyyy-MM-dd}.err.log</FileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
<totalSizeCap>1GB</totalSizeCap>
|
||||
</rollingPolicy>
|
||||
<encoder>
|
||||
<charset>UTF-8</charset>
|
||||
<pattern>%d [%thread] %-5level %logger{36} %line - %mdc{client} [%X{trace_id}] %msg%n</pattern>
|
||||
</encoder>
|
||||
<!--只打印错误日志-->
|
||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||
<level>ERROR</level>
|
||||
<onMatch>ACCEPT</onMatch>
|
||||
<onMismatch>DENY</onMismatch>
|
||||
</filter>
|
||||
</appender>
|
||||
|
||||
<!--指定最基础的日志输出级别-->
|
||||
<root level="INFO">
|
||||
<!--appender将会添加到这个logger-->
|
||||
<appender-ref ref="consoleLog"/>
|
||||
<appender-ref ref="fileLog"/>
|
||||
<appender-ref ref="fileLog-debug"/>
|
||||
<appender-ref ref="fileLog-err"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
Loading…
Reference in New Issue
Block a user