mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-06-21 22:09:09 -04:00
Add file browser icons
This commit is contained in:
@@ -4,6 +4,7 @@ package io.xpipe.app.browser;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import io.xpipe.app.browser.icon.FileIconManager;
|
||||
import io.xpipe.app.comp.base.LazyTextFieldComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
@@ -295,20 +296,17 @@ final class FileListComp extends AnchorPane {
|
||||
HBox.setHgrow(textField, Priority.ALWAYS);
|
||||
setGraphic(box);
|
||||
|
||||
if (!isDirectory) {
|
||||
img.set("file_drag_icon.png");
|
||||
} else {
|
||||
img.set("folder_closed.svg");
|
||||
}
|
||||
var isParentLink = getTableRow()
|
||||
.getItem()
|
||||
.equals(fileList.getFileSystemModel().getCurrentParentDirectory());
|
||||
img.set(FileIconManager.getFileIcon(isParentLink ? fileList.getFileSystemModel().getCurrentDirectory() : getTableRow().getItem(), isParentLink));
|
||||
|
||||
pseudoClassStateChanged(FOLDER, isDirectory);
|
||||
|
||||
var fileName = getTableRow()
|
||||
.getItem()
|
||||
.equals(fileList.getFileSystemModel().getCurrentParentDirectory())
|
||||
var fileName = isParentLink
|
||||
? ".."
|
||||
: FileNames.getFileName(fullPath);
|
||||
var hidden = getTableRow().getItem().isHidden() || fileName.startsWith(".");
|
||||
var hidden = !isParentLink && (getTableRow().getItem().isHidden() || fileName.startsWith("."));
|
||||
getTableRow().pseudoClassStateChanged(HIDDEN, hidden);
|
||||
text.set(fileName);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.icon.FileIcons;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public interface FileIconFactory {
|
||||
|
||||
class SimpleFile extends IconVariant implements FileIconFactory {
|
||||
|
||||
private final String[] endings;
|
||||
|
||||
public SimpleFile(String lightIcon, String darkIcon, String... endings) {
|
||||
super(lightIcon, darkIcon);
|
||||
this.endings = endings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon(FileSystem.FileEntry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Arrays.stream(endings).anyMatch(ending -> entry.getPath().endsWith(ending)) ? getIcon() : null;
|
||||
}
|
||||
}
|
||||
|
||||
String getIcon(FileSystem.FileEntry entry);
|
||||
}
|
||||
131
app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java
Normal file
131
app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java
Normal file
@@ -0,0 +1,131 @@
|
||||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class FileIconManager {
|
||||
|
||||
private static final List<FileIconFactory> factories = new ArrayList<>();
|
||||
private static final List<FolderIconFactory> folderFactories = new ArrayList<>();
|
||||
private static boolean loaded;
|
||||
|
||||
static {
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "browser_icons/file_list.txt", path -> {
|
||||
try (var reader =
|
||||
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
var split = line.split("\\|");
|
||||
var id = split[0].trim();
|
||||
var filter = Arrays.stream(split[1].split(","))
|
||||
.map(s -> {
|
||||
var r = s.trim();
|
||||
if (r.startsWith(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r.contains(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return "." + r;
|
||||
})
|
||||
.toList();
|
||||
var darkIcon = split[2].trim();
|
||||
var lightIcon = split.length > 3 ? split[3].trim() : darkIcon;
|
||||
factories.add(new FileIconFactory.SimpleFile(lightIcon, darkIcon, filter.toArray(String[]::new)));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
folderFactories.addAll(List.of(new FolderIconFactory.SimpleDirectory(
|
||||
new IconVariant("default_root_folder.svg"), new IconVariant("default_root_folder_opened.svg"), "")));
|
||||
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "browser_icons/folder_list.txt", path -> {
|
||||
try (var reader =
|
||||
new BufferedReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
var split = line.split("\\|");
|
||||
var id = split[0].trim();
|
||||
var filter = Arrays.stream(split[1].split(","))
|
||||
.map(s -> {
|
||||
var r = s.trim();
|
||||
if (r.startsWith(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
if (r.contains(".")) {
|
||||
return r;
|
||||
}
|
||||
|
||||
return "." + r;
|
||||
})
|
||||
.toList();
|
||||
|
||||
var closedIcon = split[2].trim();
|
||||
var openIcon = split[3].trim();
|
||||
|
||||
var lightClosedIcon = split.length > 4 ? split[4].trim() : closedIcon;
|
||||
var lightOpenIcon = split.length > 4 ? split[5].trim() : openIcon;
|
||||
|
||||
folderFactories.add(new FolderIconFactory.SimpleDirectory(
|
||||
new IconVariant(lightClosedIcon, closedIcon),
|
||||
new IconVariant(lightOpenIcon, openIcon),
|
||||
filter.toArray(String[]::new)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void loadIfNecessary() {
|
||||
if (!loaded) {
|
||||
AppImages.loadDirectory(AppResources.XPIPE_MODULE, "browser_icons");
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFileIcon(FileSystem.FileEntry entry, boolean open) {
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
loadIfNecessary();
|
||||
|
||||
if (!entry.isDirectory()) {
|
||||
for (var f : factories) {
|
||||
var icon = f.getIcon(entry);
|
||||
if (icon != null) {
|
||||
return getIconPath(icon);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var f : folderFactories) {
|
||||
var icon = f.getIcon(entry, open);
|
||||
if (icon != null) {
|
||||
return getIconPath(icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entry.isDirectory() ? (open ? "default_folder_opened.svg" : "default_folder.svg") : "default_file.svg";
|
||||
}
|
||||
|
||||
public static String getParentLinkIcon() {
|
||||
loadIfNecessary();
|
||||
return "default_folder_opened.svg";
|
||||
}
|
||||
|
||||
private static String getIconPath(String name) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.xpipe.app.browser;
|
||||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
@@ -11,10 +11,6 @@ public class FileIcons {
|
||||
}
|
||||
|
||||
public static String getIcon(FileSystem.FileEntry entry) {
|
||||
if (!entry.isDirectory()) {
|
||||
return "app:file_drag_icon.png";
|
||||
} else {
|
||||
return "app:folder_closed.svg";
|
||||
}
|
||||
return FileIconManager.getFileIcon(entry, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.core.impl.FileNames;
|
||||
import io.xpipe.core.store.FileSystem;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public interface FolderIconFactory {
|
||||
|
||||
class SimpleDirectory implements FolderIconFactory {
|
||||
|
||||
private final IconVariant closed;
|
||||
private final IconVariant open;
|
||||
private final String[] names;
|
||||
|
||||
public SimpleDirectory(IconVariant closed, IconVariant open, String... names) {
|
||||
this.closed = closed;
|
||||
this.open = open;
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIcon(FileSystem.FileEntry entry, boolean open) {
|
||||
if (!entry.isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Arrays.stream(names).anyMatch(name -> FileNames.getFileName(entry.getPath())
|
||||
.equalsIgnoreCase(name))
|
||||
? (open ? this.open.getIcon() : this.closed.getIcon())
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
String getIcon(FileSystem.FileEntry entry, boolean open);
|
||||
}
|
||||
27
app/src/main/java/io/xpipe/app/browser/icon/IconVariant.java
Normal file
27
app/src/main/java/io/xpipe/app/browser/icon/IconVariant.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
||||
public class IconVariant {
|
||||
|
||||
private final String lightIcon;
|
||||
private final String darkIcon;
|
||||
|
||||
public IconVariant(String icon) {
|
||||
this(icon, icon);
|
||||
}
|
||||
|
||||
public IconVariant(String lightIcon, String darkIcon) {
|
||||
this.lightIcon = lightIcon;
|
||||
this.darkIcon = darkIcon;
|
||||
}
|
||||
|
||||
protected final String getIcon() {
|
||||
var t = AppPrefs.get() != null ? AppPrefs.get().theme.getValue() : null;
|
||||
if (t == null) {
|
||||
return lightIcon;
|
||||
}
|
||||
|
||||
return t.getTheme().isDarkMode() ? darkIcon : lightIcon;
|
||||
}
|
||||
}
|
||||
@@ -25,38 +25,39 @@ public class AppImages {
|
||||
public static void init() {
|
||||
TrackEvent.info("Loading images ...");
|
||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
||||
AppResources.with(module.getName(), "img", basePath -> {
|
||||
if (!Files.exists(basePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var simpleName = FilenameUtils.getExtension(module.getName());
|
||||
String defaultPrefix = simpleName + ":";
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
var relativeFileName = FilenameUtils.separatorsToUnix(basePath.relativize(file).toString());
|
||||
try {
|
||||
if (FilenameUtils.getExtension(file.toString()).equals("svg")) {
|
||||
var s = Files.readString(file);
|
||||
svgImages.put(
|
||||
defaultPrefix + relativeFileName,
|
||||
s);
|
||||
} else {
|
||||
images.put(
|
||||
defaultPrefix + relativeFileName,
|
||||
loadImage(file));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
});
|
||||
loadDirectory(module.getName(), "img");
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadDirectory(String module, String dir) {
|
||||
AppResources.with(module, dir, basePath -> {
|
||||
if (!Files.exists(basePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var simpleName = FilenameUtils.getExtension(module);
|
||||
String defaultPrefix = simpleName + ":";
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
var relativeFileName = FilenameUtils.separatorsToUnix(
|
||||
basePath.relativize(file).toString());
|
||||
try {
|
||||
if (FilenameUtils.getExtension(file.toString()).equals("svg")) {
|
||||
var s = Files.readString(file);
|
||||
svgImages.put(defaultPrefix + relativeFileName, s);
|
||||
} else {
|
||||
images.put(defaultPrefix + relativeFileName, loadImage(file));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static String svgImage(String file) {
|
||||
if (file == null) {
|
||||
return "";
|
||||
|
||||
@@ -9,6 +9,7 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.css.Size;
|
||||
import javafx.css.SizeUnits;
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
@@ -55,24 +56,38 @@ public class SvgComp {
|
||||
return;
|
||||
}
|
||||
|
||||
var regularExpression = Pattern.compile("<svg.+?width=\"([^\s]+)\"", Pattern.DOTALL);
|
||||
var matcher = regularExpression.matcher(val);
|
||||
if (!matcher.find()) {
|
||||
var dim = getDimensions(val);
|
||||
if (dim == null) {
|
||||
return;
|
||||
}
|
||||
var width = matcher.group(1);
|
||||
regularExpression = Pattern.compile("<svg.+?height=\"([^\s]+)\"", Pattern.DOTALL);
|
||||
matcher = regularExpression.matcher(val);
|
||||
matcher.find();
|
||||
var height = matcher.group(1);
|
||||
var widthInteger = parseSize(width).pixels();
|
||||
var heightInteger = parseSize(height).pixels();
|
||||
widthProperty.set((int) Math.ceil(widthInteger));
|
||||
heightProperty.set((int) Math.ceil(heightInteger));
|
||||
|
||||
widthProperty.set((int) Math.ceil(dim.getX()));
|
||||
heightProperty.set((int) Math.ceil(dim.getY()));
|
||||
});
|
||||
return new SvgComp(widthProperty, heightProperty, content);
|
||||
}
|
||||
|
||||
private static Point2D getDimensions(String val) {
|
||||
var regularExpression = Pattern.compile("<svg[^>]+?width=\"([^\s]+)\"", Pattern.DOTALL);
|
||||
var matcher = regularExpression.matcher(val);
|
||||
|
||||
if (!matcher.find()) {
|
||||
var viewBox = Pattern.compile(
|
||||
"<svg.+?viewBox=\"([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\"", Pattern.DOTALL);
|
||||
matcher = viewBox.matcher(val);
|
||||
if (matcher.find()) {
|
||||
return new Point2D(parseSize(matcher.group(3)).pixels(), parseSize(matcher.group(4)).pixels());
|
||||
}
|
||||
}
|
||||
|
||||
var width = matcher.group(1);
|
||||
regularExpression = Pattern.compile("<svg.+?height=\"([^\s]+)\"", Pattern.DOTALL);
|
||||
matcher = regularExpression.matcher(val);
|
||||
matcher.find();
|
||||
var height = matcher.group(1);
|
||||
return new Point2D(parseSize(width).pixels(), parseSize(height).pixels());
|
||||
}
|
||||
|
||||
private String getHtml(String content) {
|
||||
return "<html><body style='margin: 0; padding: 0; border: none;' >" + content + "</body></html>";
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@ public class AppUpdater {
|
||||
lastUpdateCheckResult.addListener((c, o, n) -> {
|
||||
downloadedUpdate.setValue(null);
|
||||
});
|
||||
refreshUpdateCheckSilent();
|
||||
|
||||
if (XPipeDistributionType.get().checkForUpdateOnStartup()) {
|
||||
refreshUpdateCheckSilent();
|
||||
}
|
||||
}
|
||||
|
||||
private static void event(String msg) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
||||
@@ -9,13 +8,13 @@ public interface XPipeDistributionType {
|
||||
XPipeDistributionType DEVELOPMENT = new XPipeDistributionType() {
|
||||
|
||||
@Override
|
||||
public boolean supportsUpdate() {
|
||||
return true;
|
||||
public boolean checkForUpdateOnStartup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performUpdateAction() {
|
||||
TrackEvent.info("Development mode update executed");
|
||||
public boolean supportsUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -26,12 +25,14 @@ public interface XPipeDistributionType {
|
||||
XPipeDistributionType PORTABLE = new XPipeDistributionType() {
|
||||
|
||||
@Override
|
||||
public boolean supportsUpdate() {
|
||||
public boolean checkForUpdateOnStartup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performUpdateAction() {}
|
||||
public boolean supportsUpdate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -41,13 +42,13 @@ public interface XPipeDistributionType {
|
||||
XPipeDistributionType INSTALLATION = new XPipeDistributionType() {
|
||||
|
||||
@Override
|
||||
public boolean supportsUpdate() {
|
||||
public boolean checkForUpdateOnStartup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void performUpdateAction() {
|
||||
TrackEvent.info("Update action called");
|
||||
public boolean supportsUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,9 +69,9 @@ public interface XPipeDistributionType {
|
||||
}
|
||||
}
|
||||
|
||||
boolean supportsUpdate();
|
||||
boolean checkForUpdateOnStartup();
|
||||
|
||||
void performUpdateAction();
|
||||
boolean supportsUpdate();
|
||||
|
||||
String getName();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ open module io.xpipe.app {
|
||||
exports io.xpipe.app.fxcomps.augment;
|
||||
exports io.xpipe.app.test;
|
||||
exports io.xpipe.app.browser;
|
||||
exports io.xpipe.app.browser.icon;
|
||||
|
||||
requires com.sun.jna;
|
||||
requires com.sun.jna.platform;
|
||||
|
||||
Reference in New Issue
Block a user