Rework icon handling
|
@ -25,7 +25,7 @@ components from it when it is run in a development environment.
|
||||||
Note that in case the current master branch is ahead of the latest release, it might happen that there are some incompatibilities when loading data from your local XPipe installation.
|
Note that in case the current master branch is ahead of the latest release, it might happen that there are some incompatibilities when loading data from your local XPipe installation.
|
||||||
You should therefore always check out the matching version tag for your local repository and local XPipe installation.
|
You should therefore always check out the matching version tag for your local repository and local XPipe installation.
|
||||||
You can find the available version tags at https://github.com/xpipe-io/xpipe/tags.
|
You can find the available version tags at https://github.com/xpipe-io/xpipe/tags.
|
||||||
So for example if you currently have XPipe `10.0` installed, you should run `git reset --hard 10.0` first to properly compile against it.
|
So for example if you currently have XPipe `11.3` installed, you should run `git reset --hard 11.3` first to properly compile against it.
|
||||||
|
|
||||||
You need to have JDK for Java 21 installed to compile the project.
|
You need to have JDK for Java 21 installed to compile the project.
|
||||||
If you are on Linux or macOS, you can easily accomplish that by running
|
If you are on Linux or macOS, you can easily accomplish that by running
|
||||||
|
@ -74,7 +74,7 @@ Especially when starting out, it might be a good idea to start with easy tasks f
|
||||||
|
|
||||||
### Interacting via the HTTP API
|
### Interacting via the HTTP API
|
||||||
|
|
||||||
You can create clients they communicate with the XPipe daemon via its HTTP API.
|
You can create clients that communicate with the XPipe daemon via its HTTP API.
|
||||||
To get started, see the [OpenAPI spec](/openapi.yaml).
|
To get started, see the [OpenAPI spec](/openapi.yaml).
|
||||||
|
|
||||||
### Implementing support for a new editor
|
### Implementing support for a new editor
|
||||||
|
@ -98,6 +98,10 @@ All actions that you can perform for certain connections in the connection overv
|
||||||
|
|
||||||
You can add custom script definitions [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java) and [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/resources/io/xpipe/ext/base/resources/scripts).
|
You can add custom script definitions [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/java/io/xpipe/ext/base/script/PredefinedScriptStore.java) and [here](https://github.com/xpipe-io/xpipe/tree/master/ext/base/src/main/resources/io/xpipe/ext/base/resources/scripts).
|
||||||
|
|
||||||
|
### Adding more system icons for system autodetection
|
||||||
|
|
||||||
|
You can register new system types [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/java/io/xpipe/app/resources/SystemIcons.java) and add the respective icons [here](https://github.com/xpipe-io/xpipe/tree/master/app/src/main/resources/io/xpipe/app/resources/img/system).
|
||||||
|
|
||||||
### Adding more file icons for specific types
|
### Adding more file icons for specific types
|
||||||
|
|
||||||
You can register file types [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/resources/io/xpipe/app/resources/file_list.txt) and add the respective icons [here](https://github.com/xpipe-io/xpipe/tree/master/app/src/main/resources/io/xpipe/app/resources/img/browser).
|
You can register file types [here](https://github.com/xpipe-io/xpipe/blob/master/app/src/main/resources/io/xpipe/app/resources/file_list.txt) and add the respective icons [here](https://github.com/xpipe-io/xpipe/tree/master/app/src/main/resources/io/xpipe/app/resources/img/browser).
|
||||||
|
@ -108,6 +112,6 @@ The existing file list and icons are taken from the [vscode-icons](https://githu
|
||||||
|
|
||||||
if you want to work on something that was not listed here, you can still do so of course. You can reach out on the [Discord server](https://discord.gg/8y89vS8cRb) to discuss any development plans and get you started.
|
if you want to work on something that was not listed here, you can still do so of course. You can reach out on the [Discord server](https://discord.gg/8y89vS8cRb) to discuss any development plans and get you started.
|
||||||
|
|
||||||
### Translations
|
### Adding translations
|
||||||
|
|
||||||
See the [translation guide](/lang) for details.
|
See the [translation guide](/lang) for details.
|
||||||
|
|
|
@ -631,6 +631,10 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||||
() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
() -> getTableRow().getItem(), fileList.getFileSystemModel())
|
||||||
.hide(Bindings.createBooleanBinding(
|
.hide(Bindings.createBooleanBinding(
|
||||||
() -> {
|
() -> {
|
||||||
|
if (getTableRow() == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var item = getTableRow().getItem();
|
var item = getTableRow().getItem();
|
||||||
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY;
|
||||||
var isParentLink = item.getRawFileEntry()
|
var isParentLink = item.getRawFileEntry()
|
||||||
|
|
|
@ -142,7 +142,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
||||||
this.menu = new Menu(
|
this.menu = new Menu(
|
||||||
// Use original name, not the link target
|
// Use original name, not the link target
|
||||||
browserEntry.getRawFileEntry().getName(),
|
browserEntry.getRawFileEntry().getName(),
|
||||||
PrettyImageHelper.ofFixedRasterized(
|
PrettyImageHelper.ofFixedSize(
|
||||||
FileIconManager.getFileIcon(browserEntry.getRawFileEntry(), false), 24, 24)
|
FileIconManager.getFileIcon(browserEntry.getRawFileEntry(), false), 24, 24)
|
||||||
.createRegion());
|
.createRegion());
|
||||||
createMenu();
|
createMenu();
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||||
// But it is good enough.
|
// But it is good enough.
|
||||||
var showProvider = true;
|
var showProvider = true;
|
||||||
try {
|
try {
|
||||||
showProvider = storeEntryWrapper.getEntry().getProvider().shouldShow(storeEntryWrapper);
|
showProvider = storeEntryWrapper.getEntry().getProvider() == null || storeEntryWrapper.getEntry().getProvider().shouldShow(storeEntryWrapper);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
return inRootCategory && showProvider;
|
return inRootCategory && showProvider;
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class StoreIconChoiceDialogComp extends SimpleComp {
|
||||||
|
|
||||||
public static void show(DataStoreEntry entry) {
|
public static void show(DataStoreEntry entry) {
|
||||||
SystemIcons.load();
|
SystemIcons.load();
|
||||||
var window = AppWindowHelper.sideWindow(AppI18n.get("chooseCustomIcon"), stage -> new StoreIconChoiceDialogComp(entry,stage),true,null);
|
var window = AppWindowHelper.sideWindow(AppI18n.get("chooseCustomIcon"), stage -> new StoreIconChoiceDialogComp(entry,stage),false,null);
|
||||||
window.initModality(Modality.APPLICATION_MODAL);
|
window.initModality(Modality.APPLICATION_MODAL);
|
||||||
window.show();
|
window.show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import io.xpipe.app.util.LicenseProvider;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.value.ObservableDoubleValue;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
@ -63,4 +65,12 @@ public class App extends Application {
|
||||||
stage.requestFocus();
|
stage.requestFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ObservableDoubleValue displayScale() {
|
||||||
|
if (getStage() == null) {
|
||||||
|
return new SimpleDoubleProperty(1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getStage().outputScaleXProperty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,55 +1,68 @@
|
||||||
package io.xpipe.app.fxcomps.impl;
|
package io.xpipe.app.fxcomps.impl;
|
||||||
|
|
||||||
|
import io.xpipe.app.core.App;
|
||||||
import io.xpipe.app.resources.AppImages;
|
import io.xpipe.app.resources.AppImages;
|
||||||
import io.xpipe.app.fxcomps.Comp;
|
import io.xpipe.app.fxcomps.Comp;
|
||||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||||
import io.xpipe.core.store.FileNames;
|
import io.xpipe.core.store.FileNames;
|
||||||
|
|
||||||
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class PrettyImageHelper {
|
public class PrettyImageHelper {
|
||||||
|
|
||||||
private static Optional<String> rasterizedImageIfExists(String img, int width, int height) {
|
private static Optional<String> rasterizedImageIfExists(String img, int height) {
|
||||||
if (img != null && img.endsWith(".svg")) {
|
if (img != null && img.endsWith(".svg")) {
|
||||||
var base = FileNames.getBaseName(img);
|
var base = FileNames.getBaseName(img);
|
||||||
var renderedName = base + "-" + height + ".png";
|
var renderedName = base + "-" + height + ".png";
|
||||||
if (AppImages.hasNormalImage(base + "-" + height + ".png")) {
|
if (AppImages.hasNormalImage(renderedName)) {
|
||||||
return Optional.of(renderedName);
|
return Optional.of(renderedName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (img != null && img.endsWith(".png")) {
|
||||||
|
if (AppImages.hasNormalImage(img)) {
|
||||||
|
return Optional.of(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ObservableValue<String> rasterizedImageIfExistsScaled(String img, int height) {
|
||||||
|
return Bindings.createStringBinding(() -> {
|
||||||
|
if (img == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!img.endsWith(".svg")) {
|
||||||
|
return rasterizedImageIfExists(img, height).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizes = List.of(16, 24, 40, 80);
|
||||||
|
var mult = Math.round(App.getApp().displayScale().get() * height);
|
||||||
|
var base = FileNames.getBaseName(img);
|
||||||
|
var available = sizes.stream()
|
||||||
|
.filter(integer -> AppImages.hasNormalImage(base + "-" + integer + ".png"))
|
||||||
|
.toList();
|
||||||
|
var closest = available.stream()
|
||||||
|
.filter(integer -> integer >= mult)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(available.size() > 0 ? available.getLast() : 0);
|
||||||
|
return rasterizedImageIfExists(img, closest).orElse(null);
|
||||||
|
}, App.getApp().displayScale());
|
||||||
|
}
|
||||||
|
|
||||||
public static Comp<?> ofFixedSizeSquare(String img, int size) {
|
public static Comp<?> ofFixedSizeSquare(String img, int size) {
|
||||||
return ofFixedSize(img, size, size);
|
return ofFixedSize(img, size, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> ofFixedRasterized(String img, int w, int h) {
|
|
||||||
if (img == null) {
|
|
||||||
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
var rasterized = rasterizedImageIfExists(img, w, h);
|
|
||||||
return new PrettyImageComp(new SimpleStringProperty(rasterized.orElse(null)), w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Comp<?> ofFixedSize(String img, int w, int h) {
|
public static Comp<?> ofFixedSize(String img, int w, int h) {
|
||||||
if (img == null) {
|
return ofFixedSize(new SimpleStringProperty(img), w,h);
|
||||||
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
var rasterized = rasterizedImageIfExists(img, w, h);
|
|
||||||
if (rasterized.isPresent()) {
|
|
||||||
return new PrettyImageComp(new SimpleStringProperty(rasterized.get()), w, h);
|
|
||||||
} else {
|
|
||||||
return img.endsWith(".svg")
|
|
||||||
? new PrettySvgComp(new SimpleStringProperty(img), w, h)
|
|
||||||
: new PrettyImageComp(new SimpleStringProperty(img), w, h);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comp<?> ofFixedSize(ObservableValue<String> img, int w, int h) {
|
public static Comp<?> ofFixedSize(ObservableValue<String> img, int w, int h) {
|
||||||
|
@ -57,8 +70,8 @@ public class PrettyImageHelper {
|
||||||
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
return new PrettyImageComp(new SimpleStringProperty(null), w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
var binding = BindingsHelper.map(img, s -> {
|
var binding = BindingsHelper.flatMap(img, s -> {
|
||||||
return rasterizedImageIfExists(s, w, h).orElse(s);
|
return rasterizedImageIfExistsScaled(s, h);
|
||||||
});
|
});
|
||||||
return new PrettyImageComp(binding, w, h);
|
return new PrettyImageComp(binding, w, h);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import java.util.Optional;
|
||||||
public class SystemIcons {
|
public class SystemIcons {
|
||||||
|
|
||||||
private static final List<SystemIcon> AUTO_SYSTEM_ICONS = List.of(
|
private static final List<SystemIcon> AUTO_SYSTEM_ICONS = List.of(
|
||||||
new SystemIcon("opnsense", "OpnSense") {
|
new SystemIcon("opnsense", "opnsense") {
|
||||||
@Override
|
@Override
|
||||||
public boolean isApplicable(DataStore store) {
|
public boolean isApplicable(DataStore store) {
|
||||||
return store instanceof StatefulDataStore<?> statefulDataStore &&
|
return store instanceof StatefulDataStore<?> statefulDataStore &&
|
||||||
|
@ -26,7 +26,7 @@ public class SystemIcons {
|
||||||
shellStoreState.getShellDialect() == ShellDialects.OPNSENSE;
|
shellStoreState.getShellDialect() == ShellDialects.OPNSENSE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new SystemIcon("pfsense", "PfSense") {
|
new SystemIcon("pfsense", "pfsense") {
|
||||||
@Override
|
@Override
|
||||||
public boolean isApplicable(DataStore store) {
|
public boolean isApplicable(DataStore store) {
|
||||||
return store instanceof StatefulDataStore<?> statefulDataStore &&
|
return store instanceof StatefulDataStore<?> statefulDataStore &&
|
||||||
|
@ -34,8 +34,8 @@ public class SystemIcons {
|
||||||
shellStoreState.getShellDialect() == ShellDialects.PFSENSE;
|
shellStoreState.getShellDialect() == ShellDialects.PFSENSE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new ContainerAutoSystemIcon("file-browser", "File Browser", name -> name.contains("filebrowser")),
|
new ContainerAutoSystemIcon("file-browser", "file browser", name -> name.contains("filebrowser")),
|
||||||
new FileAutoSystemIcon("syncthing", "Syncthing", OsType.LINUX, "~/.local/state/syncthing")
|
new FileAutoSystemIcon("syncthing", "syncthing", OsType.LINUX, "~/.local/state/syncthing")
|
||||||
);
|
);
|
||||||
|
|
||||||
private static final List<SystemIcon> SYSTEM_ICONS = new ArrayList<>();
|
private static final List<SystemIcon> SYSTEM_ICONS = new ArrayList<>();
|
||||||
|
@ -52,10 +52,10 @@ public class SystemIcons {
|
||||||
var all = stream.toList();
|
var all = stream.toList();
|
||||||
for (Path file : all) {
|
for (Path file : all) {
|
||||||
var name = FilenameUtils.getBaseName(file.getFileName().toString());
|
var name = FilenameUtils.getBaseName(file.getFileName().toString());
|
||||||
if (name.contains("-dark") || name.contains("-40")) {
|
if (name.contains("-dark") || name.contains("-16") || name.contains("-24")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var base = name.replaceAll("-24", "");
|
var base = name.replaceAll("-40", "");
|
||||||
if (AUTO_SYSTEM_ICONS.stream().anyMatch(autoSystemIcon -> autoSystemIcon.getIconName().equals(base))) {
|
if (AUTO_SYSTEM_ICONS.stream().anyMatch(autoSystemIcon -> autoSystemIcon.getIconName().equals(base))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ public enum PlatformState {
|
||||||
// fails
|
// fails
|
||||||
} catch (HeadlessException h) {
|
} catch (HeadlessException h) {
|
||||||
var msg = (OsType.getLocal().equals(OsType.LINUX)
|
var msg = (OsType.getLocal().equals(OsType.LINUX)
|
||||||
? "No X11 DISPLAY variable was set or no headful library support was found."
|
? "No DISPLAY variable was set or no headful library support was found."
|
||||||
: "The application does not have desktop access, but this program performed an operation which requires it.")
|
: "The application does not have desktop access, but this program performed an operation which requires it.")
|
||||||
+ "\n\n"
|
+ "\n\n"
|
||||||
+ "Please note that XPipe is a desktop application that should be run on your local workstation."
|
+ "Please note that XPipe is a desktop application that should be run on your local workstation."
|
||||||
|
|
BIN
app/src/main/resources/io/xpipe/app/resources/img/os/alma-40.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/resources/io/xpipe/app/resources/img/os/arch-40.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/resources/io/xpipe/app/resources/img/os/kali-40.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 793 B |
After Width: | Height: | Size: 696 B |
After Width: | Height: | Size: 285 B |
BIN
app/src/main/resources/io/xpipe/app/resources/img/os/mint-40.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3 KiB |
BIN
app/src/main/resources/io/xpipe/app/resources/img/os/pop-40.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 998 B |
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/resources/io/xpipe/app/resources/img/os/suse-40.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 253 B |
After Width: | Height: | Size: 949 B |
After Width: | Height: | Size: 280 B |
After Width: | Height: | Size: 327 B |
After Width: | Height: | Size: 508 B |
After Width: | Height: | Size: 552 B |
After Width: | Height: | Size: 591 B |
After Width: | Height: | Size: 486 B |
After Width: | Height: | Size: 645 B |
After Width: | Height: | Size: 455 B |
After Width: | Height: | Size: 432 B |
After Width: | Height: | Size: 540 B |
After Width: | Height: | Size: 546 B |
After Width: | Height: | Size: 544 B |
After Width: | Height: | Size: 674 B |
After Width: | Height: | Size: 613 B |
After Width: | Height: | Size: 494 B |
After Width: | Height: | Size: 636 B |
After Width: | Height: | Size: 357 B |
After Width: | Height: | Size: 667 B |
After Width: | Height: | Size: 500 B |
After Width: | Height: | Size: 902 B |
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 443 B |
After Width: | Height: | Size: 526 B |
After Width: | Height: | Size: 841 B |
After Width: | Height: | Size: 471 B |
After Width: | Height: | Size: 651 B |
After Width: | Height: | Size: 902 B |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 808 B |
After Width: | Height: | Size: 490 B |
After Width: | Height: | Size: 843 B |
After Width: | Height: | Size: 789 B |
After Width: | Height: | Size: 414 B |
After Width: | Height: | Size: 384 B |
After Width: | Height: | Size: 737 B |
After Width: | Height: | Size: 410 B |
After Width: | Height: | Size: 618 B |
After Width: | Height: | Size: 425 B |
After Width: | Height: | Size: 401 B |
After Width: | Height: | Size: 660 B |
After Width: | Height: | Size: 264 B |
After Width: | Height: | Size: 284 B |
After Width: | Height: | Size: 890 B |
After Width: | Height: | Size: 412 B |
After Width: | Height: | Size: 453 B |
After Width: | Height: | Size: 873 B |
After Width: | Height: | Size: 399 B |
After Width: | Height: | Size: 523 B |
After Width: | Height: | Size: 611 B |
After Width: | Height: | Size: 766 B |
After Width: | Height: | Size: 339 B |
After Width: | Height: | Size: 523 B |
After Width: | Height: | Size: 476 B |
After Width: | Height: | Size: 651 B |
After Width: | Height: | Size: 470 B |
After Width: | Height: | Size: 366 B |
After Width: | Height: | Size: 472 B |
After Width: | Height: | Size: 796 B |
After Width: | Height: | Size: 403 B |
After Width: | Height: | Size: 426 B |