Cleanup and implement custom icons
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.beacon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.util.MarkdownHelper;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.browser.icon;
|
||||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileKind;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.CompStructure;
|
||||
import io.xpipe.app.fxcomps.SimpleCompStructure;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
|
||||
import io.xpipe.app.fxcomps.impl.StackComp;
|
||||
|
|
|
@ -17,6 +17,7 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
|
|||
import io.xpipe.app.fxcomps.util.DerivedObservableList;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.resources.SystemIcon;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static atlantafx.base.theme.Styles.TEXT_SMALL;
|
||||
|
||||
public class StoreIconChoiceComp extends SimpleComp {
|
||||
|
||||
private final Property<SystemIcon> selected;
|
||||
private final List<SystemIcon> icons;
|
||||
private final int columns;
|
||||
private final SimpleStringProperty filter;
|
||||
|
||||
public StoreIconChoiceComp(Property<SystemIcon> selected, List<SystemIcon> icons, int columns, SimpleStringProperty filter) {
|
||||
this.selected = selected;
|
||||
this.icons = icons;
|
||||
this.columns = columns;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var table = new TableView<List<SystemIcon>>();
|
||||
initTable(table);
|
||||
updateData(table, null);
|
||||
filter.addListener((observable, oldValue, newValue) -> updateData(table, newValue));
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
private void initTable(TableView<List<SystemIcon>> table) {
|
||||
for (int i = 0; i < columns; i++) {
|
||||
var col = new TableColumn<List<SystemIcon>, SystemIcon>("col" + i);
|
||||
final int colIndex = i;
|
||||
col.setCellValueFactory(cb -> {
|
||||
var row = cb.getValue();
|
||||
var item = row.size() > colIndex ? row.get(colIndex) : null;
|
||||
return new SimpleObjectProperty<>(item);
|
||||
});
|
||||
col.setCellFactory(cb -> new IconCell());
|
||||
col.getStyleClass().add(Tweaks.ALIGN_CENTER);
|
||||
table.getColumns().add(col);
|
||||
}
|
||||
|
||||
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
|
||||
table.getSelectionModel().setCellSelectionEnabled(true);
|
||||
table.getStyleClass().add("icon-browser");
|
||||
}
|
||||
|
||||
private void updateData(TableView<List<SystemIcon>> table, String filterString) {
|
||||
var displayedIcons = filterString == null || filterString.isBlank() || filterString.length() < 2 ? icons : icons.stream().filter(
|
||||
icon -> containsString(icon.getDisplayName(), filterString)).toList();
|
||||
|
||||
var data = partitionList(displayedIcons, columns);
|
||||
table.getItems().setAll(data);
|
||||
}
|
||||
|
||||
private <T> Collection<List<T>> partitionList(List<T> list, int size) {
|
||||
List<List<T>> partitions = new ArrayList<>();
|
||||
if (list.size() == 0) {
|
||||
return partitions;
|
||||
}
|
||||
|
||||
int length = list.size();
|
||||
int numOfPartitions = length / size + ((length % size == 0) ? 0 : 1);
|
||||
|
||||
for (int i = 0; i < numOfPartitions; i++) {
|
||||
int from = i * size;
|
||||
int to = Math.min((i * size + size), length);
|
||||
partitions.add(list.subList(from, to));
|
||||
}
|
||||
return partitions;
|
||||
}
|
||||
|
||||
private boolean containsString(String s1, String s2) {
|
||||
return s1.toLowerCase(Locale.ROOT).contains(s2.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
public class IconCell extends TableCell<List<SystemIcon>, SystemIcon> {
|
||||
|
||||
private final Label root = new Label();
|
||||
private final StringProperty image = new SimpleStringProperty();
|
||||
private final Region imageView = PrettyImageHelper.ofFixedSize(image, 40, 40).createRegion();
|
||||
|
||||
public IconCell() {
|
||||
super();
|
||||
|
||||
root.setContentDisplay(ContentDisplay.TOP);
|
||||
root.setGraphic(imageView);
|
||||
root.setGraphicTextGap(10);
|
||||
root.getStyleClass().addAll("icon-label", TEXT_SMALL);
|
||||
|
||||
setOnMouseClicked(event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY) {
|
||||
selected.setValue(getItem());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(SystemIcon icon, boolean empty) {
|
||||
super.updateItem(icon, empty);
|
||||
|
||||
if (icon == null) {
|
||||
setGraphic(null);
|
||||
return;
|
||||
}
|
||||
|
||||
root.setText(icon.getDisplayName());
|
||||
image.set(icon.getIconName() + ".svg");
|
||||
setGraphic(root);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.base.DialogComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.FilterComp;
|
||||
import io.xpipe.app.resources.SystemIcon;
|
||||
import io.xpipe.app.resources.SystemIcons;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class StoreIconChoiceDialogComp extends SimpleComp {
|
||||
|
||||
public static void show(DataStoreEntry entry) {
|
||||
SystemIcons.load();
|
||||
var icon = new SimpleObjectProperty<>(SystemIcons.getForId(entry.getIcon()).orElse(null));
|
||||
var window = AppWindowHelper.sideWindow(AppI18n.get("chooseCustomIcon"), stage -> new StoreIconChoiceDialogComp(icon, entry,stage),true,null);
|
||||
window.show();
|
||||
}
|
||||
|
||||
private final Property<SystemIcon> selected;
|
||||
private final DataStoreEntry entry;
|
||||
private final Stage dialogStage;
|
||||
|
||||
public StoreIconChoiceDialogComp(Property<SystemIcon> selected, DataStoreEntry entry, Stage dialogStage) {
|
||||
this.selected = selected;
|
||||
this.entry = entry;
|
||||
this.dialogStage = dialogStage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var filterText = new SimpleStringProperty();
|
||||
var table = new StoreIconChoiceComp(selected, SystemIcons.getSystemIcons(), 5, filterText);
|
||||
var filter = new FilterComp(filterText).apply(struc -> {
|
||||
dialogStage.setOnShowing(event -> {
|
||||
struc.get().requestFocus();
|
||||
event.consume();
|
||||
});
|
||||
});
|
||||
var dialog = new DialogComp() {
|
||||
@Override
|
||||
protected void finish() {
|
||||
var icon = selected.getValue().getIconName();
|
||||
entry.setIcon(icon);
|
||||
dialogStage.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comp<?> content() {
|
||||
return table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comp<?> bottom() {
|
||||
return filter;
|
||||
}
|
||||
};
|
||||
dialog.prefWidth(600);
|
||||
dialog.prefHeight(600);
|
||||
return dialog.createRegion();
|
||||
}
|
||||
}
|
|
@ -1,27 +1,24 @@
|
|||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
|
||||
import io.xpipe.app.fxcomps.impl.TooltipAugment;
|
||||
import io.xpipe.app.resources.SystemIcons;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class StoreIconComp extends SimpleComp {
|
||||
|
||||
private final StoreEntryWrapper wrapper;
|
||||
private final int w;
|
||||
private final int h;
|
||||
|
||||
private static final AtomicBoolean loaded = new AtomicBoolean();
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
|
@ -49,6 +46,13 @@ public class StoreIconComp extends SimpleComp {
|
|||
storeIcon.opacityProperty().bind(Bindings.createDoubleBinding(() -> {
|
||||
return stack.isHover() ? 0.5 : 1.0;
|
||||
}, stack.hoverProperty()));
|
||||
|
||||
stack.addEventFilter(MouseEvent.MOUSE_PRESSED,event -> {
|
||||
if (event.getButton() == MouseButton.PRIMARY) {
|
||||
StoreIconChoiceDialogComp.show(wrapper.getEntry());
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
|
||||
return stack;
|
||||
}
|
||||
|
@ -65,12 +69,7 @@ public class StoreIconComp extends SimpleComp {
|
|||
.getDisplayIconFileName(wrapper.getEntry().getStore());
|
||||
}
|
||||
|
||||
synchronized (loaded) {
|
||||
if (!loaded.get()) {
|
||||
AppImages.loadDirectory(AppResources.XPIPE_MODULE, "img/apps", true, false);
|
||||
}
|
||||
loaded.set(true);
|
||||
return "app:apps/" + wrapper.getIcon().getValue();
|
||||
}
|
||||
SystemIcons.load();
|
||||
return "app:system/" + wrapper.getIcon().getValue() + ".svg";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.xpipe.app.core;
|
|||
import io.xpipe.app.ext.ExtensionException;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
|
|
@ -5,6 +5,7 @@ import io.xpipe.app.core.mode.OperationMode;
|
|||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.WritableImage;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AppImages {
|
||||
|
||||
public static final Image DEFAULT_IMAGE = new WritableImage(1, 1);
|
||||
private static final Map<String, Image> images = new HashMap<>();
|
||||
private static final Map<String, String> svgImages = new HashMap<>();
|
||||
|
||||
public static void init() {
|
||||
if (images.size() > 0 || svgImages.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackEvent.info("Loading images ...");
|
||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
||||
loadDirectory(module.getName(), "img", true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) {
|
||||
AppResources.with(module, dir, basePath -> {
|
||||
if (!Files.exists(basePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var simpleName = FilenameUtils.getExtension(module);
|
||||
String defaultPrefix = simpleName + ":";
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
var relativeFileName = FilenameUtils.separatorsToUnix(
|
||||
basePath.relativize(file).toString());
|
||||
try {
|
||||
if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) {
|
||||
var s = Files.readString(file);
|
||||
svgImages.put(defaultPrefix + relativeFileName, s);
|
||||
} else if (loadImages) {
|
||||
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 "";
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
|
||||
if (svgImages.containsKey(key)) {
|
||||
return svgImages.get(key);
|
||||
}
|
||||
|
||||
TrackEvent.warn("Svg image " + key + " not found");
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean hasNormalImage(String file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
return images.containsKey(key);
|
||||
}
|
||||
|
||||
public static boolean hasSvgImage(String file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
return svgImages.containsKey(key);
|
||||
}
|
||||
|
||||
public static Image image(String file) {
|
||||
if (file == null) {
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
|
||||
if (images.containsKey(key)) {
|
||||
return images.get(key);
|
||||
}
|
||||
|
||||
TrackEvent.warn("Normal image " + key + " not found");
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
public static BufferedImage toAwtImage(Image fxImage) {
|
||||
BufferedImage img =
|
||||
new BufferedImage((int) fxImage.getWidth(), (int) fxImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
for (int x = 0; x < fxImage.getWidth(); x++) {
|
||||
for (int y = 0; y < fxImage.getHeight(); y++) {
|
||||
int rgb = fxImage.getPixelReader().getArgb(x, y);
|
||||
img.setRGB(x, y, rgb);
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
public static Image loadImage(Path p) {
|
||||
if (p == null) {
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
if (!Files.isRegularFile(p)) {
|
||||
TrackEvent.error("Image file " + p + " not found.");
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
try (var in = Files.newInputStream(p)) {
|
||||
return new Image(in, -1, -1, true, true);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
import io.xpipe.modulefs.ModuleFileSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AppResources {
|
||||
|
||||
public static final String XPIPE_MODULE = "io.xpipe.app";
|
||||
|
||||
private static final Map<String, ModuleFileSystem> fileSystems = new ConcurrentHashMap<>();
|
||||
|
||||
public static void reset() {
|
||||
fileSystems.forEach((s, moduleFileSystem) -> {
|
||||
try {
|
||||
moduleFileSystem.close();
|
||||
} catch (IOException ignored) {
|
||||
// Usually when updating, a SIGTERM is sent to this application.
|
||||
// However, it takes a while to shut down but the installer is deleting files meanwhile.
|
||||
// It can happen that the jar it does not exist anymore
|
||||
}
|
||||
});
|
||||
fileSystems.clear();
|
||||
}
|
||||
|
||||
private static ModuleFileSystem openFileSystemIfNeeded(String module) throws IOException {
|
||||
var layer = AppExtensionManager.getInstance() != null
|
||||
? AppExtensionManager.getInstance().getExtendedLayer()
|
||||
: null;
|
||||
|
||||
// Only cache file systems with extended layer
|
||||
if (layer != null && fileSystems.containsKey(module)) {
|
||||
return fileSystems.get(module);
|
||||
}
|
||||
|
||||
if (layer == null) {
|
||||
layer = ModuleLayer.boot();
|
||||
}
|
||||
|
||||
var fs = (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
|
||||
if (AppExtensionManager.getInstance() != null) {
|
||||
fileSystems.put(module, fs);
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
public static Optional<URL> getResourceURL(String module, String file) {
|
||||
try {
|
||||
var fs = openFileSystemIfNeeded(module);
|
||||
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
|
||||
var url = f.getWrappedPath().toUri().toURL();
|
||||
return Optional.of(url);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static void with(String module, String file, FailableConsumer<Path, IOException> con) {
|
||||
if (AppProperties.get() != null
|
||||
&& !AppProperties.get().isImage()
|
||||
&& AppProperties.get().isDeveloperMode()) {
|
||||
// Check if resource was found. If we use external processed resources, we can't use local dev resources
|
||||
if (withLocalDevResource(module, file, con)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
withResource(module, file, con);
|
||||
}
|
||||
|
||||
public static void withResourceInLayer(
|
||||
String module, String file, ModuleLayer layer, FailableConsumer<Path, IOException> con) {
|
||||
try (var fs = FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer))) {
|
||||
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
|
||||
con.accept(f);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
}
|
||||
}
|
||||
|
||||
private static void withResource(String module, String file, FailableConsumer<Path, IOException> con) {
|
||||
var path = module.startsWith("io.xpipe") ? module.replace('.', '/') + "/resources/" + file : file;
|
||||
try {
|
||||
var fs = openFileSystemIfNeeded(module);
|
||||
var f = fs.getPath(path);
|
||||
con.accept(f);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean withLocalDevResource(String module, String file, FailableConsumer<Path, IOException> con) {
|
||||
try {
|
||||
var fs = openFileSystemIfNeeded(module);
|
||||
var url = fs.getPath("").getWrappedPath().toUri().toURL();
|
||||
if (!url.getProtocol().equals("jar")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
||||
URL fileUrl = connection.getJarFileURL();
|
||||
var jarFile = Path.of(fileUrl.toURI());
|
||||
var resDir = jarFile.getParent()
|
||||
.getParent()
|
||||
.getParent()
|
||||
.resolve("src")
|
||||
.resolve("main")
|
||||
.resolve("resources");
|
||||
var f = resDir.resolve(module.replace('.', '/') + "/resources/" + file);
|
||||
if (!Files.exists(f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
con.accept(f);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import io.xpipe.app.issue.ErrorEvent;
|
|||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -6,6 +6,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
|
|||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.animation.Interpolator;
|
||||
|
|
|
@ -2,6 +2,8 @@ package io.xpipe.app.core;
|
|||
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import java.awt.*;
|
||||
|
|
|
@ -4,6 +4,8 @@ import io.xpipe.app.comp.base.MarkdownComp;
|
|||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.core.mode.OperationMode;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.WindowsRegistry;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
|
|
@ -10,6 +10,7 @@ import io.xpipe.app.ext.ActionProvider;
|
|||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStorageSyncHandler;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
|
|
|
@ -5,6 +5,8 @@ import io.xpipe.app.core.*;
|
|||
import io.xpipe.app.core.check.AppFontLoadingCheck;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.resources.SystemIcons;
|
||||
import io.xpipe.app.update.UpdateAvailableAlert;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
@ -33,6 +35,7 @@ public abstract class PlatformMode extends OperationMode {
|
|||
AppTheme.init();
|
||||
AppStyle.init();
|
||||
AppImages.init();
|
||||
SystemIcons.init();
|
||||
AppLayoutModel.init();
|
||||
TrackEvent.info("Finished essential component initialization before platform");
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.core.window;
|
||||
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.AppTheme;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
|
|
|
@ -5,6 +5,8 @@ import io.xpipe.app.core.*;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
|
|
@ -7,7 +7,7 @@ import io.xpipe.app.comp.store.StoreEntryWrapper;
|
|||
import io.xpipe.app.comp.store.StoreSection;
|
||||
import io.xpipe.app.comp.store.StoreSectionComp;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.util.BindingsHelper;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package io.xpipe.app.fxcomps.impl;
|
||||
|
||||
import io.xpipe.app.core.AppImages;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
import io.xpipe.app.fxcomps.util.PlatformThread;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
|
|
@ -9,6 +9,7 @@ import io.xpipe.app.core.window.AppWindowHelper;
|
|||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.fxcomps.SimpleComp;
|
||||
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
|
55
app/src/main/java/io/xpipe/app/prefs/LockChangeAlert.java
Normal file
|
@ -0,0 +1,55 @@
|
|||
package io.xpipe.app.prefs;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.fxcomps.impl.SecretFieldComp;
|
||||
import io.xpipe.core.util.InPlaceSecretValue;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class LockChangeAlert {
|
||||
|
||||
public static void show() {
|
||||
var prop1 = new SimpleObjectProperty<InPlaceSecretValue>();
|
||||
var prop2 = new SimpleObjectProperty<InPlaceSecretValue>();
|
||||
AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("lockCreationAlertTitle"));
|
||||
alert.setHeaderText(AppI18n.get("lockCreationAlertHeader"));
|
||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
var label1 = new LabelComp(AppI18n.observable("passphrase")).createRegion();
|
||||
var p1 = new SecretFieldComp(prop1, false).createRegion();
|
||||
p1.setStyle("-fx-border-width: 1px");
|
||||
|
||||
var label2 = new LabelComp(AppI18n.observable("repeatPassphrase")).createRegion();
|
||||
var p2 = new SecretFieldComp(prop2, false).createRegion();
|
||||
p1.setStyle("-fx-border-width: 1px");
|
||||
|
||||
var content = new VBox(label1, p1, new Spacer(15), label2, p2);
|
||||
content.setSpacing(5);
|
||||
alert.getDialogPane().setContent(content);
|
||||
|
||||
var button = alert.getDialogPane().lookupButton(ButtonType.OK);
|
||||
button.disableProperty()
|
||||
.bind(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return !Objects.equals(prop1.getValue(), prop2.getValue());
|
||||
},
|
||||
prop1,
|
||||
prop2));
|
||||
})
|
||||
.filter(b -> b.getButtonData().isDefaultButton())
|
||||
.ifPresent(t -> {
|
||||
AppPrefs.get().changeLock(prop1.getValue());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package io.xpipe.app.prefs;
|
||||
|
||||
import io.xpipe.app.core.AppExtensionManager;
|
||||
import io.xpipe.app.core.AppResources;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.xpipe.app.comp.base.ButtonComp;
|
|||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.Comp;
|
||||
import io.xpipe.app.util.LockChangeAlert;
|
||||
import io.xpipe.app.util.OptionsBuilder;
|
||||
import io.xpipe.app.util.Validator;
|
||||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
|
145
app/src/main/java/io/xpipe/app/resources/AppImages.java
Normal file
|
@ -0,0 +1,145 @@
|
|||
package io.xpipe.app.resources;
|
||||
|
||||
import io.xpipe.app.core.AppExtensionManager;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.WritableImage;
|
||||
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class AppImages {
|
||||
|
||||
public static final Image DEFAULT_IMAGE = new WritableImage(1, 1);
|
||||
private static final Map<String, Image> images = new HashMap<>();
|
||||
private static final Map<String, String> svgImages = new HashMap<>();
|
||||
|
||||
public static void init() {
|
||||
if (images.size() > 0 || svgImages.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
TrackEvent.info("Loading images ...");
|
||||
for (var module : AppExtensionManager.getInstance().getContentModules()) {
|
||||
loadDirectory(module.getName(), "img", true, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) {
|
||||
AppResources.with(module, dir, basePath -> {
|
||||
if (!Files.exists(basePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var simpleName = FilenameUtils.getExtension(module);
|
||||
String defaultPrefix = simpleName + ":";
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
var relativeFileName = FilenameUtils.separatorsToUnix(
|
||||
basePath.relativize(file).toString());
|
||||
try {
|
||||
if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) {
|
||||
var s = Files.readString(file);
|
||||
svgImages.put(defaultPrefix + relativeFileName, s);
|
||||
} else if (loadImages) {
|
||||
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 "";
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
|
||||
if (svgImages.containsKey(key)) {
|
||||
return svgImages.get(key);
|
||||
}
|
||||
|
||||
TrackEvent.warn("Svg image " + key + " not found");
|
||||
return "";
|
||||
}
|
||||
|
||||
public static boolean hasNormalImage(String file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
return images.containsKey(key);
|
||||
}
|
||||
|
||||
public static boolean hasSvgImage(String file) {
|
||||
if (file == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
return svgImages.containsKey(key);
|
||||
}
|
||||
|
||||
public static Image image(String file) {
|
||||
if (file == null) {
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
var key = file.contains(":") ? file : "app:" + file;
|
||||
|
||||
if (images.containsKey(key)) {
|
||||
return images.get(key);
|
||||
}
|
||||
|
||||
TrackEvent.warn("Normal image " + key + " not found");
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
public static BufferedImage toAwtImage(Image fxImage) {
|
||||
BufferedImage img =
|
||||
new BufferedImage((int) fxImage.getWidth(), (int) fxImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
for (int x = 0; x < fxImage.getWidth(); x++) {
|
||||
for (int y = 0; y < fxImage.getHeight(); y++) {
|
||||
int rgb = fxImage.getPixelReader().getArgb(x, y);
|
||||
img.setRGB(x, y, rgb);
|
||||
}
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
public static Image loadImage(Path p) {
|
||||
if (p == null) {
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
if (!Files.isRegularFile(p)) {
|
||||
TrackEvent.error("Image file " + p + " not found.");
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
|
||||
try (var in = Files.newInputStream(p)) {
|
||||
return new Image(in, -1, -1, true, true);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
return DEFAULT_IMAGE;
|
||||
}
|
||||
}
|
||||
}
|
134
app/src/main/java/io/xpipe/app/resources/AppResources.java
Normal file
|
@ -0,0 +1,134 @@
|
|||
package io.xpipe.app.resources;
|
||||
|
||||
import io.xpipe.app.core.AppExtensionManager;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.core.util.FailableConsumer;
|
||||
import io.xpipe.modulefs.ModuleFileSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.JarURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class AppResources {
|
||||
|
||||
public static final String XPIPE_MODULE = "io.xpipe.app";
|
||||
|
||||
private static final Map<String, ModuleFileSystem> fileSystems = new ConcurrentHashMap<>();
|
||||
|
||||
public static void reset() {
|
||||
fileSystems.forEach((s, moduleFileSystem) -> {
|
||||
try {
|
||||
moduleFileSystem.close();
|
||||
} catch (IOException ignored) {
|
||||
// Usually when updating, a SIGTERM is sent to this application.
|
||||
// However, it takes a while to shut down but the installer is deleting files meanwhile.
|
||||
// It can happen that the jar it does not exist anymore
|
||||
}
|
||||
});
|
||||
fileSystems.clear();
|
||||
}
|
||||
|
||||
private static ModuleFileSystem openFileSystemIfNeeded(String module) throws IOException {
|
||||
var layer = AppExtensionManager.getInstance() != null
|
||||
? AppExtensionManager.getInstance().getExtendedLayer()
|
||||
: null;
|
||||
|
||||
// Only cache file systems with extended layer
|
||||
if (layer != null && fileSystems.containsKey(module)) {
|
||||
return fileSystems.get(module);
|
||||
}
|
||||
|
||||
if (layer == null) {
|
||||
layer = ModuleLayer.boot();
|
||||
}
|
||||
|
||||
var fs = (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
|
||||
if (AppExtensionManager.getInstance() != null) {
|
||||
fileSystems.put(module, fs);
|
||||
}
|
||||
return fs;
|
||||
}
|
||||
|
||||
public static Optional<URL> getResourceURL(String module, String file) {
|
||||
try {
|
||||
var fs = openFileSystemIfNeeded(module);
|
||||
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
|
||||
var url = f.getWrappedPath().toUri().toURL();
|
||||
return Optional.of(url);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static void with(String module, String file, FailableConsumer<Path, IOException> con) {
|
||||
if (AppProperties.get() != null
|
||||
&& !AppProperties.get().isImage()
|
||||
&& AppProperties.get().isDeveloperMode()) {
|
||||
// Check if resource was found. If we use external processed resources, we can't use local dev resources
|
||||
if (withLocalDevResource(module, file, con)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
withResource(module, file, con);
|
||||
}
|
||||
|
||||
public static void withResourceInLayer(
|
||||
String module, String file, ModuleLayer layer, FailableConsumer<Path, IOException> con) {
|
||||
try (var fs = FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer))) {
|
||||
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
|
||||
con.accept(f);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
}
|
||||
}
|
||||
|
||||
private static void withResource(String module, String file, FailableConsumer<Path, IOException> con) {
|
||||
var path = module.startsWith("io.xpipe") ? module.replace('.', '/') + "/resources/" + file : file;
|
||||
try {
|
||||
var fs = openFileSystemIfNeeded(module);
|
||||
var f = fs.getPath(path);
|
||||
con.accept(f);
|
||||
} catch (IOException e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean withLocalDevResource(String module, String file, FailableConsumer<Path, IOException> con) {
|
||||
try {
|
||||
var fs = openFileSystemIfNeeded(module);
|
||||
var url = fs.getPath("").getWrappedPath().toUri().toURL();
|
||||
if (!url.getProtocol().equals("jar")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JarURLConnection connection = (JarURLConnection) url.openConnection();
|
||||
URL fileUrl = connection.getJarFileURL();
|
||||
var jarFile = Path.of(fileUrl.toURI());
|
||||
var resDir = jarFile.getParent()
|
||||
.getParent()
|
||||
.getParent()
|
||||
.resolve("src")
|
||||
.resolve("main")
|
||||
.resolve("resources");
|
||||
var f = resDir.resolve(module.replace('.', '/') + "/resources/" + file);
|
||||
if (!Files.exists(f)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
con.accept(f);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
23
app/src/main/java/io/xpipe/app/resources/AutoSystemIcon.java
Normal file
|
@ -0,0 +1,23 @@
|
|||
package io.xpipe.app.resources;
|
||||
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.util.FailableFunction;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
@EqualsAndHashCode(callSuper=true)
|
||||
public class AutoSystemIcon extends SystemIcon {
|
||||
|
||||
FailableFunction<ShellControl, Boolean, Exception> applicable;
|
||||
|
||||
public AutoSystemIcon(String iconName, String displayName, FailableFunction<ShellControl, Boolean, Exception> applicable) {
|
||||
super(iconName, displayName);
|
||||
this.applicable = applicable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(ShellControl sc) throws Exception {
|
||||
return applicable.apply(sc);
|
||||
}
|
||||
}
|
17
app/src/main/java/io/xpipe/app/resources/SystemIcon.java
Normal file
|
@ -0,0 +1,17 @@
|
|||
package io.xpipe.app.resources;
|
||||
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import lombok.Value;
|
||||
import lombok.experimental.NonFinal;
|
||||
|
||||
@Value
|
||||
@NonFinal
|
||||
public class SystemIcon {
|
||||
|
||||
String iconName;
|
||||
String displayName;
|
||||
|
||||
public boolean isApplicable(ShellControl sc) throws Exception {
|
||||
return false;
|
||||
}
|
||||
}
|
81
app/src/main/java/io/xpipe/app/resources/SystemIcons.java
Normal file
|
@ -0,0 +1,81 @@
|
|||
package io.xpipe.app.resources;
|
||||
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.process.ShellDialects;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SystemIcons {
|
||||
|
||||
private static final List<AutoSystemIcon> AUTO_SYSTEM_ICONS = List.of(new AutoSystemIcon("opnsense", "OpnSense",sc -> {
|
||||
return sc.getOriginalShellDialect() == ShellDialects.OPNSENSE;
|
||||
}));
|
||||
|
||||
private static final List<SystemIcon> SYSTEM_ICONS = new ArrayList<>();
|
||||
private static boolean loaded = false;
|
||||
|
||||
public static synchronized void init() {
|
||||
if (SYSTEM_ICONS.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SYSTEM_ICONS.addAll(AUTO_SYSTEM_ICONS);
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "img/system", path -> {
|
||||
try (var stream = Files.list(path)) {
|
||||
var all = stream.toList();
|
||||
for (Path file : all) {
|
||||
var name = FilenameUtils.getBaseName(file.getFileName().toString());
|
||||
if (name.contains("-dark") || name.contains("-40")) {
|
||||
continue;
|
||||
}
|
||||
var base = name.replaceAll("-24", "");
|
||||
if (AUTO_SYSTEM_ICONS.stream().anyMatch(autoSystemIcon -> autoSystemIcon.getIconName().equals(base))) {
|
||||
continue;
|
||||
}
|
||||
var displayName = base.replaceAll("-", " ");
|
||||
SYSTEM_ICONS.add(new SystemIcon(base, displayName));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static synchronized void load() {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppImages.loadDirectory(AppResources.XPIPE_MODULE,"img/system", true, false);
|
||||
loaded = true;
|
||||
}
|
||||
|
||||
public static Optional<SystemIcon> getForId(String id) {
|
||||
if (id == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
for (SystemIcon systemIcon : SYSTEM_ICONS) {
|
||||
if (systemIcon.getIconName().equals(id)) {
|
||||
return Optional.of(systemIcon);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Optional<SystemIcon> detectForSystem(ShellControl sc) throws Exception {
|
||||
for (AutoSystemIcon autoSystemIcon : AUTO_SYSTEM_ICONS) {
|
||||
if (autoSystemIcon.isApplicable(sc)) {
|
||||
return Optional.of(autoSystemIcon);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static List<SystemIcon> getSystemIcons() {
|
||||
return SYSTEM_ICONS;
|
||||
}
|
||||
}
|
|
@ -68,6 +68,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
@NonFinal
|
||||
Order explicitOrder;
|
||||
|
||||
@NonFinal
|
||||
String icon;
|
||||
|
||||
private DataStoreEntry(
|
||||
|
@ -349,6 +350,14 @@ public class DataStoreEntry extends StorageElement {
|
|||
return (T) storePersistentState;
|
||||
}
|
||||
|
||||
public void setIcon(String icon) {
|
||||
var changed = !Objects.equals(this.icon, icon);
|
||||
this.icon = icon;
|
||||
if (changed) {
|
||||
notifyUpdate(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void setStorePersistentState(DataStoreState value) {
|
||||
var changed = !Objects.equals(storePersistentState, value);
|
||||
this.storePersistentState = value;
|
||||
|
@ -391,7 +400,7 @@ public class DataStoreEntry extends StorageElement {
|
|||
obj.put("name", name);
|
||||
obj.put("categoryUuid", categoryUuid.toString());
|
||||
obj.set("color", mapper.valueToTree(color));
|
||||
obj.set("icons", mapper.valueToTree(icon));
|
||||
obj.set("icon", mapper.valueToTree(icon));
|
||||
stateObj.put("lastUsed", lastUsed.toString());
|
||||
stateObj.put("lastModified", lastModified.toString());
|
||||
stateObj.set("persistentState", storePersistentStateNode);
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.*;
|
||||
|
||||
public class Indicator {
|
||||
|
||||
private static final Color lColor = Color.rgb(0x66, 0x66, 0x66);
|
||||
private static final Color rColor = Color.rgb(0x0f, 0x87, 0xc3);
|
||||
|
||||
private static final PathElement[] ELEMS = new PathElement[] {
|
||||
new MoveTo(9.2362945, 19.934046),
|
||||
new CubicCurveTo(-1.3360939, -0.28065, -1.9963146, -1.69366, -1.9796182, -2.95487),
|
||||
new CubicCurveTo(-0.1152909, -1.41268, -0.5046634, -3.07081, -1.920768, -3.72287),
|
||||
new CubicCurveTo(-1.4711631, -0.77284, -3.4574873, -0.11153, -4.69154031, -1.40244),
|
||||
new CubicCurveTo(-1.30616123, -1.40422, -0.5308003, -4.1855799, 1.46313121, -4.4219799),
|
||||
new CubicCurveTo(1.4290018, -0.25469, 3.1669517, -0.0875, 4.1676818, -1.36207),
|
||||
new CubicCurveTo(0.9172241, -1.12206, 0.9594176, -2.63766, 1.0685793, -4.01259),
|
||||
new CubicCurveTo(0.4020299, -1.95732999, 3.2823027, -2.72818999, 4.5638567, -1.15760999),
|
||||
new CubicCurveTo(1.215789, 1.31824999, 0.738899, 3.90740999, -1.103778, 4.37267999),
|
||||
new CubicCurveTo(-1.3972543, 0.40868, -3.0929979, 0.0413, -4.2208253, 1.16215),
|
||||
new CubicCurveTo(-1.3524806, 1.26423, -1.3178578, 3.29187, -1.1086673, 4.9895199),
|
||||
new CubicCurveTo(0.167826, 1.28946, 1.0091133, 2.5347, 2.3196964, 2.86608),
|
||||
new CubicCurveTo(1.6253079, 0.53477, 3.4876372, 0.45004, 5.0294052, -0.30121),
|
||||
new CubicCurveTo(1.335829, -0.81654, 1.666839, -2.49408, 1.717756, -3.9432),
|
||||
new CubicCurveTo(0.08759, -1.1232899, 0.704887, -2.3061299, 1.871843, -2.5951699),
|
||||
new CubicCurveTo(1.534558, -0.50726, 3.390804, 0.62784, 3.467269, 2.28631),
|
||||
new CubicCurveTo(0.183147, 1.4285099, -0.949563, 2.9179999, -2.431156, 2.9383699),
|
||||
new CubicCurveTo(-1.390597, 0.17337, -3.074035, 0.18128, -3.971365, 1.45069),
|
||||
new CubicCurveTo(-0.99314, 1.271, -0.676157, 2.98683, -1.1715, 4.43018),
|
||||
new CubicCurveTo(-0.518248, 1.11436, -1.909118, 1.63902, -3.0700005, 1.37803),
|
||||
new ClosePath()
|
||||
};
|
||||
|
||||
static {
|
||||
for (int i = 1; i < ELEMS.length; ++i) {
|
||||
ELEMS[i].setAbsolute(false);
|
||||
}
|
||||
}
|
||||
|
||||
private final Path left;
|
||||
private final Path right;
|
||||
private final Group g;
|
||||
private final int steps;
|
||||
|
||||
private boolean fw = true;
|
||||
private int step = 0;
|
||||
|
||||
public Indicator(int ticksPerCycle, double scale) {
|
||||
this.steps = ticksPerCycle;
|
||||
|
||||
left = new Path(ELEMS);
|
||||
right = new Path(ELEMS);
|
||||
|
||||
left.setScaleX(scale);
|
||||
left.setScaleY(scale);
|
||||
right.setScaleX(-1 * scale);
|
||||
right.setScaleY(-1 * scale);
|
||||
right.setTranslateX(7.266 * scale);
|
||||
right.setOpacity(0.0);
|
||||
|
||||
left.setStroke(null);
|
||||
right.setStroke(null);
|
||||
left.setFill(lColor);
|
||||
right.setFill(rColor);
|
||||
|
||||
g = new Group(left, right);
|
||||
|
||||
AnimationTimer timer = new AnimationTimer() {
|
||||
@Override
|
||||
public void handle(long l) {
|
||||
step();
|
||||
}
|
||||
};
|
||||
timer.start();
|
||||
}
|
||||
|
||||
public Parent getNode() {
|
||||
return g;
|
||||
}
|
||||
|
||||
private void step() {
|
||||
double lOpacity, rOpacity;
|
||||
|
||||
step += fw ? 1 : -1;
|
||||
|
||||
if (step == steps) {
|
||||
fw = false;
|
||||
lOpacity = 0.0;
|
||||
rOpacity = 1.0;
|
||||
} else if (step == 0) {
|
||||
fw = true;
|
||||
lOpacity = 1.0;
|
||||
rOpacity = 0.0;
|
||||
} else {
|
||||
lOpacity = 1.0 * (steps - step) / steps;
|
||||
rOpacity = 1.0 * step / steps;
|
||||
}
|
||||
|
||||
left.setOpacity(lOpacity);
|
||||
right.setOpacity(rOpacity);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.fxcomps.impl.LabelComp;
|
||||
import io.xpipe.app.fxcomps.impl.SecretFieldComp;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.util.InPlaceSecretValue;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class LockChangeAlert {
|
||||
|
||||
public static void show() {
|
||||
var prop1 = new SimpleObjectProperty<InPlaceSecretValue>();
|
||||
var prop2 = new SimpleObjectProperty<InPlaceSecretValue>();
|
||||
AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setTitle(AppI18n.get("lockCreationAlertTitle"));
|
||||
alert.setHeaderText(AppI18n.get("lockCreationAlertHeader"));
|
||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
|
||||
var label1 = new LabelComp(AppI18n.observable("passphrase")).createRegion();
|
||||
var p1 = new SecretFieldComp(prop1, false).createRegion();
|
||||
p1.setStyle("-fx-border-width: 1px");
|
||||
|
||||
var label2 = new LabelComp(AppI18n.observable("repeatPassphrase")).createRegion();
|
||||
var p2 = new SecretFieldComp(prop2, false).createRegion();
|
||||
p1.setStyle("-fx-border-width: 1px");
|
||||
|
||||
var content = new VBox(label1, p1, new Spacer(15), label2, p2);
|
||||
content.setSpacing(5);
|
||||
alert.getDialogPane().setContent(content);
|
||||
|
||||
var button = alert.getDialogPane().lookupButton(ButtonType.OK);
|
||||
button.disableProperty()
|
||||
.bind(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return !Objects.equals(prop1.getValue(), prop2.getValue());
|
||||
},
|
||||
prop1,
|
||||
prop2));
|
||||
})
|
||||
.filter(b -> b.getButtonData().isDefaultButton())
|
||||
.ifPresent(t -> {
|
||||
AppPrefs.get().changeLock(prop1.getValue());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.core.dialog.QueryConverter;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Value
|
||||
public class NamedCharacter {
|
||||
|
||||
char character;
|
||||
List<String> names;
|
||||
String translationKey;
|
||||
|
||||
public static QueryConverter<Character> converter(List<NamedCharacter> chars, boolean allowOthers) {
|
||||
return new QueryConverter<>() {
|
||||
@Override
|
||||
protected Character fromString(String s) {
|
||||
if (s.length() == 0) {
|
||||
throw new IllegalArgumentException("No character");
|
||||
}
|
||||
|
||||
var byName = chars.stream()
|
||||
.filter(nc -> nc.getNames().stream()
|
||||
.anyMatch(n -> n.toLowerCase().contains(s.toLowerCase())))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (byName != null) {
|
||||
return byName.getCharacter();
|
||||
}
|
||||
|
||||
var byChar = chars.stream()
|
||||
.filter(nc -> String.valueOf(nc.getCharacter()).equalsIgnoreCase(s))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (byChar != null) {
|
||||
return byChar.getCharacter();
|
||||
}
|
||||
|
||||
if (!allowOthers) {
|
||||
throw new IllegalArgumentException("Unknown character: " + s);
|
||||
}
|
||||
|
||||
return QueryConverter.CHARACTER.convertFromString(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String toString(Character value) {
|
||||
var byChar = chars.stream()
|
||||
.filter(nc -> value.equals(nc.getCharacter()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (byChar != null) {
|
||||
return byChar.getNames().getFirst();
|
||||
}
|
||||
|
||||
return value.toString();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.MultipleSelectionModel;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public class PrettyListView<T> extends ListView<T> {
|
||||
|
||||
private final ScrollBar vBar = new ScrollBar();
|
||||
private final ScrollBar hBar = new ScrollBar();
|
||||
|
||||
public PrettyListView() {
|
||||
super();
|
||||
skinProperty().addListener(it -> {
|
||||
// first bind, then add new scrollbars, otherwise the new bars will be found
|
||||
bindScrollBars();
|
||||
getChildren().addAll(vBar, hBar);
|
||||
});
|
||||
|
||||
vBar.setManaged(false);
|
||||
vBar.setOrientation(Orientation.VERTICAL);
|
||||
vBar.getStyleClass().add("pretty-scroll-bar");
|
||||
// vBar.visibleProperty().bind(vBar.visibleAmountProperty().isNotEqualTo(0));
|
||||
|
||||
hBar.setManaged(false);
|
||||
hBar.setOrientation(Orientation.HORIZONTAL);
|
||||
hBar.getStyleClass().add("pretty-scroll-bar");
|
||||
hBar.visibleProperty().setValue(false);
|
||||
}
|
||||
|
||||
public void disableSelection() {
|
||||
setSelectionModel(new NoSelectionModel<>());
|
||||
}
|
||||
|
||||
private void bindScrollBars() {
|
||||
final Set<Node> nodes = lookupAll("VirtualScrollBar");
|
||||
for (Node node : nodes) {
|
||||
if (node instanceof ScrollBar bar) {
|
||||
if (bar.getOrientation().equals(Orientation.VERTICAL)) {
|
||||
bindScrollBars(vBar, bar, true);
|
||||
} else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
|
||||
bindScrollBars(hBar, bar, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bindScrollBars(ScrollBar scrollBarA, ScrollBar scrollBarB, boolean bindVisibility) {
|
||||
scrollBarA.valueProperty().bindBidirectional(scrollBarB.valueProperty());
|
||||
scrollBarA.minProperty().bindBidirectional(scrollBarB.minProperty());
|
||||
scrollBarA.maxProperty().bindBidirectional(scrollBarB.maxProperty());
|
||||
scrollBarA.visibleAmountProperty().bindBidirectional(scrollBarB.visibleAmountProperty());
|
||||
scrollBarA.unitIncrementProperty().bindBidirectional(scrollBarB.unitIncrementProperty());
|
||||
scrollBarA.blockIncrementProperty().bindBidirectional(scrollBarB.blockIncrementProperty());
|
||||
if (bindVisibility) {
|
||||
scrollBarA.visibleProperty().bind(scrollBarB.visibleProperty());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
Insets insets = getInsets();
|
||||
double w = getWidth();
|
||||
double h = getHeight();
|
||||
final double prefWidth = vBar.prefWidth(-1);
|
||||
vBar.resizeRelocate(
|
||||
w - prefWidth - insets.getRight(),
|
||||
insets.getTop(),
|
||||
prefWidth,
|
||||
h - insets.getTop() - insets.getBottom());
|
||||
|
||||
final double prefHeight = hBar.prefHeight(-1);
|
||||
hBar.resizeRelocate(
|
||||
insets.getLeft(),
|
||||
h - prefHeight - insets.getBottom(),
|
||||
w - insets.getLeft() - insets.getRight(),
|
||||
prefHeight);
|
||||
}
|
||||
|
||||
public static class NoSelectionModel<T> extends MultipleSelectionModel<T> {
|
||||
|
||||
@Override
|
||||
public ObservableList<Integer> getSelectedIndices() {
|
||||
return FXCollections.emptyObservableList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableList<T> getSelectedItems() {
|
||||
return FXCollections.emptyObservableList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectIndices(int index, int... indices) {}
|
||||
|
||||
@Override
|
||||
public void selectAll() {}
|
||||
|
||||
@Override
|
||||
public void selectFirst() {}
|
||||
|
||||
@Override
|
||||
public void selectLast() {}
|
||||
|
||||
@Override
|
||||
public void clearAndSelect(int index) {}
|
||||
|
||||
@Override
|
||||
public void select(int index) {}
|
||||
|
||||
@Override
|
||||
public void select(T obj) {}
|
||||
|
||||
@Override
|
||||
public void clearSelection(int index) {}
|
||||
|
||||
@Override
|
||||
public void clearSelection() {}
|
||||
|
||||
@Override
|
||||
public boolean isSelected(int index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectPrevious() {}
|
||||
|
||||
@Override
|
||||
public void selectNext() {}
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.core.process.ShellControl;
|
||||
import io.xpipe.core.util.ProxyManagerProvider;
|
||||
|
||||
import javafx.scene.control.Alert;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ProxyManagerProviderImpl extends ProxyManagerProvider {
|
||||
|
||||
private static boolean showAlert() {
|
||||
return AppWindowHelper.showBlockingAlert(alert -> {
|
||||
alert.setAlertType(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle(AppI18n.get("connectorInstallationTitle"));
|
||||
alert.setHeaderText(AppI18n.get("connectorInstallationHeader"));
|
||||
alert.getDialogPane()
|
||||
.setContent(AppWindowHelper.alertContentText(AppI18n.get("connectorInstallationContent")));
|
||||
})
|
||||
.filter(buttonType -> buttonType.getButtonData().isDefaultButton())
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> checkCompatibility(ShellControl s) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setup(ShellControl s) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
public class UserConfig {}
|
|
@ -1,6 +0,0 @@
|
|||
package io.xpipe.app.util;
|
||||
|
||||
public interface Validatable {
|
||||
|
||||
Validator getValidator();
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import com.fasterxml.jackson.databind.Module;
|
||||
import io.xpipe.app.beacon.impl.*;
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.core.AppLogs;
|
||||
|
@ -7,15 +8,11 @@ import io.xpipe.app.issue.EventHandlerImpl;
|
|||
import io.xpipe.app.storage.DataStateProviderImpl;
|
||||
import io.xpipe.app.util.AppJacksonModule;
|
||||
import io.xpipe.app.util.LicenseProvider;
|
||||
import io.xpipe.app.util.ProxyManagerProviderImpl;
|
||||
import io.xpipe.app.util.TerminalLauncher;
|
||||
import io.xpipe.beacon.BeaconInterface;
|
||||
import io.xpipe.core.util.DataStateProvider;
|
||||
import io.xpipe.core.util.ModuleLayerLoader;
|
||||
import io.xpipe.core.util.ProxyFunction;
|
||||
import io.xpipe.core.util.ProxyManagerProvider;
|
||||
|
||||
import com.fasterxml.jackson.databind.Module;
|
||||
import org.slf4j.spi.SLF4JServiceProvider;
|
||||
|
||||
open module io.xpipe.app {
|
||||
|
@ -45,6 +42,7 @@ open module io.xpipe.app {
|
|||
exports io.xpipe.app.browser.fs;
|
||||
exports io.xpipe.app.browser.file;
|
||||
exports io.xpipe.app.core.window;
|
||||
exports io.xpipe.app.resources;
|
||||
|
||||
requires com.sun.jna;
|
||||
requires com.sun.jna.platform;
|
||||
|
@ -124,8 +122,6 @@ open module io.xpipe.app {
|
|||
ScanProvider.Loader;
|
||||
provides DataStateProvider with
|
||||
DataStateProviderImpl;
|
||||
provides ProxyManagerProvider with
|
||||
ProxyManagerProviderImpl;
|
||||
provides SLF4JServiceProvider with
|
||||
AppLogs.Slf4jProvider;
|
||||
provides EventHandler with
|
||||
|
|
After Width: | Height: | Size: 325 B |
After Width: | Height: | Size: 376 B |
After Width: | Height: | Size: 398 B |
After Width: | Height: | Size: 461 B |
After Width: | Height: | Size: 702 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 866 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 871 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 741 B |
After Width: | Height: | Size: 945 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 688 B |
After Width: | Height: | Size: 649 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 749 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 790 B |
After Width: | Height: | Size: 799 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 981 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 754 B |
After Width: | Height: | Size: 952 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 445 B |
After Width: | Height: | Size: 658 B |
After Width: | Height: | Size: 1 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 674 B |
After Width: | Height: | Size: 1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 597 B |
After Width: | Height: | Size: 1,002 B |
After Width: | Height: | Size: 850 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 692 B |
After Width: | Height: | Size: 1,001 B |
After Width: | Height: | Size: 1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 839 B |