diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java index 823fe157..1cc14216 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserComp.java @@ -7,8 +7,10 @@ import io.xpipe.app.fxcomps.SimpleComp; import io.xpipe.app.fxcomps.impl.PrettyImageComp; import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.util.BusyProperty; import io.xpipe.core.store.FileSystem; import javafx.beans.binding.Bindings; +import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.ListChangeListener; import javafx.event.EventHandler; import javafx.geometry.Insets; @@ -94,8 +96,15 @@ public class FileBrowserComp extends SimpleComp { }); tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(model.getSelected().getValue())); + // Used for ignoring changes by the tabpane when new tabs are added. We want to perform the selections manually! + var modifying = new SimpleBooleanProperty(); + // Handle selection from platform tabs.getSelectionModel().selectedIndexProperty().addListener((observable, oldValue, newValue) -> { + if (modifying.get()) { + return; + } + if (newValue.intValue() == -1) { model.getSelected().setValue(null); return; @@ -104,31 +113,36 @@ public class FileBrowserComp extends SimpleComp { model.getSelected().setValue(model.getOpenFileSystems().get(newValue.intValue())); }); + // Handle selection from model + model.getSelected().addListener((observable, oldValue, newValue) -> { + PlatformThread.runLaterIfNeeded(() -> { + tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(newValue)); + }); + }); + model.getOpenFileSystems().addListener((ListChangeListener) c -> { while (c.next()) { for (var r : c.getRemoved()) { PlatformThread.runLaterIfNeeded(() -> { - var t = map.remove(r); - tabs.getTabs().remove(t); + try (var b = new BusyProperty(modifying)) { + var t = map.remove(r); + tabs.getTabs().remove(t); + } }); } for (var a : c.getAddedSubList()) { PlatformThread.runLaterIfNeeded(() -> { - var t = createTab(tabs, a); - map.put(a, t); - tabs.getTabs().add(t); + try (var b = new BusyProperty(modifying)) { + var t = createTab(tabs, a); + map.put(a, t); + tabs.getTabs().add(t); + } }); } } }); - model.getSelected().addListener((observable, oldValue, newValue) -> { - PlatformThread.runLaterIfNeeded(() -> { - tabs.getSelectionModel().select(model.getOpenFileSystems().indexOf(newValue)); - }); - }); - tabs.getTabs().addListener((ListChangeListener) c -> { while (c.next()) { for (var r : c.getRemoved()) { @@ -198,7 +212,7 @@ public class FileBrowserComp extends SimpleComp { .getDisplayIconFileName() : null; }, - PlatformThread.sync(model.getStore())); + model.getStore()); var logo = new PrettyImageComp(image, 20, 20).createRegion(); var label = new Label(); diff --git a/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java b/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java index f69b76b1..504cc016 100644 --- a/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/FileBrowserModel.java @@ -83,13 +83,14 @@ public class FileBrowserModel { return; } - var found = openFileSystems.stream() - .filter(fileSystemModel -> fileSystemModel.getStore().getValue().equals(store)) - .findFirst(); - if (found.isPresent()) { - selected.setValue(found.get()); - return; - } + // Duplication protection (Not needed for now) +// var found = openFileSystems.stream() +// .filter(fileSystemModel -> fileSystemModel.getStore().getValue().equals(store)) +// .findFirst(); +// if (found.isPresent()) { +// selected.setValue(found.get()); +// return; +// } var model = new OpenFileSystemModel(this); openFileSystems.add(model); diff --git a/app/src/main/java/io/xpipe/app/comp/about/ThirdPartyDependencyListComp.java b/app/src/main/java/io/xpipe/app/comp/about/ThirdPartyDependencyListComp.java index 8ddc8ef6..7975fd76 100644 --- a/app/src/main/java/io/xpipe/app/comp/about/ThirdPartyDependencyListComp.java +++ b/app/src/main/java/io/xpipe/app/comp/about/ThirdPartyDependencyListComp.java @@ -22,7 +22,7 @@ public class ThirdPartyDependencyListComp extends Comp> { tp.setPadding(Insets.EMPTY); tp.setGraphic(link); tp.setAlignment(Pos.CENTER_LEFT); - AppFont.medium(tp); + AppFont.small(tp); var licenseName = new Label("(" + t.licenseName() + ")"); var sp = new StackPane(link, licenseName); diff --git a/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java b/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java index 98b9840e..6fb7e741 100644 --- a/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java +++ b/app/src/main/java/io/xpipe/app/comp/source/store/GuiDsStoreCreator.java @@ -1,7 +1,6 @@ package io.xpipe.app.comp.source.store; import io.xpipe.app.comp.base.InstallExtensionComp; -import io.xpipe.app.comp.base.LoadingOverlayComp; import io.xpipe.app.comp.base.MessageComp; import io.xpipe.app.comp.base.MultiStepComp; import io.xpipe.app.core.AppExtensionManager; @@ -125,6 +124,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { Consumer con) { var prop = new SimpleObjectProperty(provider); var store = new SimpleObjectProperty(s); + var loading = new SimpleBooleanProperty(); var name = "addConnection"; Platform.runLater(() -> { var stage = AppWindowHelper.sideWindow( @@ -137,6 +137,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { @Override protected List setup() { + loading.bind(creator.busy); return List.of(new Entry(AppI18n.observable("a"), creator)); } @@ -150,7 +151,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { }; }, false, - null); + loading); stage.show(); }); } @@ -226,7 +227,7 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { top.getStyleClass().add("top"); layout.setTop(top); // layout.getStyleClass().add("data-input-creation-step"); - return new LoadingOverlayComp(Comp.of(() -> layout), busy).createStructure(); + return Comp.of(() -> layout).createStructure(); } @Override @@ -281,8 +282,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step> { ThreadHelper.runAsync(() -> { try (var b = new BusyProperty(busy)) { - entry.getValue().setStore(input.getValue()); - entry.getValue().refresh(true); + var e = DataStoreEntry.createNew(UUID.randomUUID(), entry.getValue().getName(), input.getValue()); + e.refresh(true); finished.setValue(true); PlatformThread.runLaterIfNeeded(parent::next); } catch (Exception ex) { diff --git a/app/src/main/java/io/xpipe/app/core/AppSplashScreen.java b/app/src/main/java/io/xpipe/app/core/AppSplashScreen.java new file mode 100644 index 00000000..5e23805e --- /dev/null +++ b/app/src/main/java/io/xpipe/app/core/AppSplashScreen.java @@ -0,0 +1,30 @@ +package io.xpipe.app.core; + +import io.xpipe.app.Main; +import javafx.application.ConditionalFeature; +import javafx.application.Platform; +import javafx.scene.Scene; +import javafx.scene.SceneAntialiasing; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + +public class AppSplashScreen { + + public static void show() { + var stage = new Stage(); + stage.setWidth(500); + stage.setHeight(500); + stage.setResizable(false); + stage.initStyle(StageStyle.TRANSPARENT); + + var content = new ImageView(Main.class.getResource("resources/img/loading.gif").toString()); + var aa = Platform.isSupported(ConditionalFeature.SCENE3D) + ? SceneAntialiasing.BALANCED + : SceneAntialiasing.DISABLED; + var scene = new Scene(new Pane(content), -1, -1, false, aa); + stage.setScene(scene); + stage.show(); + } +} diff --git a/app/src/main/java/io/xpipe/app/exchange/cli/EditStoreExchangeImpl.java b/app/src/main/java/io/xpipe/app/exchange/cli/EditStoreExchangeImpl.java index 5e1e88f3..b13c3045 100644 --- a/app/src/main/java/io/xpipe/app/exchange/cli/EditStoreExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/exchange/cli/EditStoreExchangeImpl.java @@ -14,7 +14,7 @@ public class EditStoreExchangeImpl extends EditStoreExchange var s = getStoreEntryByName(msg.getName(), false); var dialog = s.getProvider().dialogForStore(s.getStore()); var reference = DialogExchangeImpl.add(dialog, (DataStore newStore) -> { - s.setStore(newStore); + // s.setStore(newStore); }); return Response.builder().dialog(reference).build(); } diff --git a/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java b/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java index 415752ec..f18ec115 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java +++ b/app/src/main/java/io/xpipe/app/fxcomps/util/PlatformThread.java @@ -38,7 +38,7 @@ public class PlatformThread { } public static ObservableValue sync(ObservableValue ov) { - return new ObservableValue<>() { + ObservableValue obs = new ObservableValue<>() { private final Map, ChangeListener> changeListenerMap = new ConcurrentHashMap<>(); @@ -79,10 +79,12 @@ public class PlatformThread { ov.removeListener(invListenerMap.getOrDefault(listener, listener)); } }; + BindingsHelper.linkPersistently(obs, ov); + return obs; } public static ObservableList sync(ObservableList ol) { - return new ObservableList<>() { + ObservableList obs = new ObservableList<>() { private final Map, ListChangeListener> listChangeListenerMap = new ConcurrentHashMap<>(); @@ -263,6 +265,8 @@ public class PlatformThread { ol.removeListener(invListenerMap.getOrDefault(listener, listener)); } }; + BindingsHelper.linkPersistently(obs, ol); + return obs; } public static void runLaterIfNeeded(Runnable r) { diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java b/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java index 0e3a97f4..c11ae3f4 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorageParser.java @@ -58,15 +58,15 @@ public class DataStorageParser { return Optional.empty(); } - var storeNode = entry.get().getResolvedNode(); - if (storeNode == null) { + var testNode = entry.get().getResolvedNode(); + if (testNode == null) { TrackEvent.withWarn("storage", "Encountered disabled store").tag("id", id); return Optional.empty(); } var newSeenIds = new HashSet<>(seenIds); newSeenIds.add(id); - return Optional.of(replaceReferenceIds(storeNode, newSeenIds)); + return Optional.of(replaceReferenceIds(entry.get().getStoreNode(), newSeenIds)); }); node = replaceReferenceIdsForType(node, "sourceId", id -> { @@ -86,6 +86,10 @@ public class DataStorageParser { var value = getReferenceIdFromNode(node, replacementKeyName).orElse(null); if (value != null) { var found = function.apply(value); + if (found.isEmpty() || found.get().isNull()) { + TrackEvent.withWarn("storage", "Encountered unknown reference").tag("id", value); + } + return found.orElseGet(NullNode::getInstance); } diff --git a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java index 4303f301..0c6f92b4 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStoreEntry.java @@ -152,12 +152,6 @@ public class DataStoreEntry extends StorageElement { return store; } - public void setStore(DataStore store) { - var node = DataStorageWriter.storeToNode(store); - this.storeNode = node; - simpleRefresh(); - } - public boolean matches(String filter) { return getName().toLowerCase().contains(filter.toLowerCase()) || (!isDisabled() @@ -204,7 +198,13 @@ public class DataStoreEntry extends StorageElement { dirty = dirty || oldStore != null; listeners.forEach(l -> l.onUpdate()); } else { - dirty = dirty || !Objects.equals(store, newStore); + var newNode = DataStorageWriter.storeToNode(newStore); + var nodesEqual = Objects.equals(storeNode, newNode); + if (!nodesEqual) { + storeNode = newNode; + } + + dirty = dirty || !nodesEqual; store = newStore; var complete = newStore.isComplete(); diff --git a/app/src/main/java/io/xpipe/app/update/AppUpdater.java b/app/src/main/java/io/xpipe/app/update/AppUpdater.java index 3e0a2f08..5348d90c 100644 --- a/app/src/main/java/io/xpipe/app/update/AppUpdater.java +++ b/app/src/main/java/io/xpipe/app/update/AppUpdater.java @@ -43,6 +43,8 @@ public class AppUpdater { event("Was updated is " + hasUpdated); if (hasUpdated) { AppCache.clear("performedUpdate"); + AppCache.clear("lastUpdateCheckResult"); + AppCache.clear("downloadedUpdate"); event("Found information about recent update"); } @@ -217,9 +219,6 @@ public class AppUpdater { } catch (Throwable ex) { ex.printStackTrace(); } finally { - AppCache.clear("lastUpdateCheckResult"); - AppCache.clear("downloadedUpdate"); - var performedUpdate = new PerformedUpdate( downloadedUpdate.getValue().getVersion(), downloadedUpdate.getValue().getBody(), diff --git a/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java b/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java index a095ca53..ad43a837 100644 --- a/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java +++ b/app/src/main/java/io/xpipe/app/update/UpdateAvailableAlert.java @@ -17,9 +17,9 @@ public class UpdateAvailableAlert { } var update = AppWindowHelper.showBlockingAlert(alert -> { - alert.setTitle(AppI18n.get("updateReadyTitle")); - alert.setHeaderText(AppI18n.get("updateReadyHeader")); - alert.setContentText(AppI18n.get("updateReadyContent")); + alert.setTitle(AppI18n.get("updateReadyAlertTitle")); + alert.setHeaderText(AppI18n.get("updateReadyAlertHeader")); + alert.setContentText(AppI18n.get("updateReadyAlertContent")); alert.setAlertType(Alert.AlertType.INFORMATION); }) .map(buttonType -> buttonType.getButtonData().isDefaultButton()) diff --git a/app/src/main/resources/io/xpipe/app/resources/img/loading.gif b/app/src/main/resources/io/xpipe/app/resources/img/loading.gif new file mode 100644 index 00000000..416c511b Binary files /dev/null and b/app/src/main/resources/io/xpipe/app/resources/img/loading.gif differ diff --git a/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties b/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties index f1ec06de..576aa8c9 100644 --- a/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties +++ b/app/src/main/resources/io/xpipe/app/resources/lang/translations_en.properties @@ -48,9 +48,9 @@ target=Target data=Data more=More pipeDataSource=Pipe Data Source -updateReadyTitle=Update Ready -updateReadyHeader=An update is ready to be installed -updateReadyContent=This will install the new version. +updateReadyAlertTitle=Update Ready +updateReadyAlertHeader=An update to version $VERSION$ is ready to be installed +updateReadyAlertContent=This will install the new version and restart X-Pipe once the installation finished. errorNoDetail=No error details are available updateAvailableTitle=Update Available updateAvailableHeader=An X-Pipe update is available to install diff --git a/version b/version index 4e69c2a2..cacdf3bd 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.5.10 \ No newline at end of file +0.5.11 \ No newline at end of file