mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-05-03 03:10:34 +00:00
Squash merge branch 15-release into master
This commit is contained in:
@@ -8,6 +8,7 @@ dev.properties
|
||||
extensions.txt
|
||||
dev_storage
|
||||
local/
|
||||
local*/
|
||||
local_*/
|
||||
.vs
|
||||
.vscode
|
||||
|
||||
@@ -20,9 +20,8 @@ It currently supports:
|
||||
- [Kubernetes](https://kubernetes.io/) clusters, pods, and containers
|
||||
- [Windows Subsystem for Linux](https://ubuntu.com/wsl), [Cygwin](https://www.cygwin.com/), and [MSYS2](https://www.msys2.org/) instances
|
||||
- [Powershell Remote Sessions](https://learn.microsoft.com/en-us/powershell/scripting/learn/remoting/running-remote-commands?view=powershell-7.3)
|
||||
- [Teleport tsh connections](https://goteleport.com/)
|
||||
- VNC connections
|
||||
- Any other custom remote connection methods that work through the command-line
|
||||
- [Tailscale SSH](https://tailscale.com/kb/1193/tailscale-ssh) and [Teleport](https://goteleport.com/) connections
|
||||
- RDP and VNC connections
|
||||
|
||||
## Connection hub
|
||||
|
||||
|
||||
+7
-2
@@ -48,9 +48,10 @@ dependencies {
|
||||
api 'com.vladsch.flexmark:flexmark-ext-yaml-front-matter:0.64.8'
|
||||
api 'com.vladsch.flexmark:flexmark-ext-toc:0.64.8'
|
||||
|
||||
api("com.github.weisj:jsvg:1.7.0")
|
||||
api files("$rootDir/gradle/gradle_scripts/markdowngenerator-1.3.1.1.jar")
|
||||
api files("$rootDir/gradle/gradle_scripts/vernacular-1.16.jar")
|
||||
api 'org.bouncycastle:bcprov-jdk18on:1.79'
|
||||
api 'org.bouncycastle:bcprov-jdk18on:1.80'
|
||||
api 'info.picocli:picocli:4.7.6'
|
||||
api ('org.kohsuke:github-api:1.326') {
|
||||
exclude group: 'org.apache.commons', module: 'commons-lang3'
|
||||
@@ -108,6 +109,8 @@ run {
|
||||
|
||||
def exts = files(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0].outputs.files.singleFile).toList());
|
||||
classpath += exts
|
||||
|
||||
dependsOn(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0]).toList())
|
||||
}
|
||||
|
||||
task runAttachedDebugger(type: JavaExec) {
|
||||
@@ -118,7 +121,7 @@ task runAttachedDebugger(type: JavaExec) {
|
||||
modularity.inferModulePath = true
|
||||
jvmArgs += jvmRunArgs
|
||||
jvmArgs += List.of(
|
||||
"-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.4.jar=port:7857,host:localhost".toString(),
|
||||
"-javaagent:${System.getProperty("user.home")}/.attachme/attachme-agent-1.2.9.jar=port:7857,host:localhost".toString(),
|
||||
"-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=127.0.0.1:0"
|
||||
)
|
||||
jvmArgs += ['-XX:+EnableDynamicAgentLoading']
|
||||
@@ -126,6 +129,8 @@ task runAttachedDebugger(type: JavaExec) {
|
||||
|
||||
def exts = files(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0].outputs.files.singleFile).toList());
|
||||
classpath += exts
|
||||
dependsOn(project.allExtensions.stream().map(p -> p.getTasksByName('jar', true)[0]).toList())
|
||||
|
||||
}
|
||||
|
||||
processResources {
|
||||
|
||||
@@ -19,7 +19,7 @@ public class Main {
|
||||
"""
|
||||
The daemon executable xpiped does not accept any command-line arguments.
|
||||
|
||||
For a reference on what you can do from the CLI, take a look at the xpipe CLI executable instead.
|
||||
For a reference on how to use xpipe from the command-line, take a look at https://docs.xpipe.io/cli.
|
||||
""");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ public class CategoryAddExchangeImpl extends CategoryAddExchange {
|
||||
throw new BeaconClientException("Parent category with id " + msg.getParent() + " does not exist");
|
||||
}
|
||||
|
||||
var found = DataStorage.get().getStoreCategories().stream().filter(
|
||||
dataStoreCategory -> msg.getParent().equals(dataStoreCategory.getParentCategory()) &&
|
||||
msg.getName().equals(dataStoreCategory.getName())).findAny();
|
||||
var found = DataStorage.get().getStoreCategories().stream()
|
||||
.filter(dataStoreCategory -> msg.getParent().equals(dataStoreCategory.getParentCategory())
|
||||
&& msg.getName().equals(dataStoreCategory.getName()))
|
||||
.findAny();
|
||||
if (found.isPresent()) {
|
||||
return Response.builder().category(found.get().getUuid()).build();
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.beacon.BlobManager;
|
||||
import io.xpipe.app.ext.ConnectionFileSystem;
|
||||
import io.xpipe.app.util.FixedSizeInputStream;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.api.FsReadExchange;
|
||||
import io.xpipe.core.store.ConnectionFileSystem;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@@ -2,8 +2,8 @@ package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.beacon.AppBeaconServer;
|
||||
import io.xpipe.app.beacon.BlobManager;
|
||||
import io.xpipe.app.ext.ConnectionFileSystem;
|
||||
import io.xpipe.beacon.api.FsWriteExchange;
|
||||
import io.xpipe.core.store.ConnectionFileSystem;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.terminal.TerminalLauncherManager;
|
||||
import io.xpipe.app.terminal.TerminalView;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.api.TerminalPrepareExchange;
|
||||
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
public class TerminalPrepareExchangeImpl extends TerminalPrepareExchange {
|
||||
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException {
|
||||
TerminalView.get().open(msg.getRequest(), msg.getPid());
|
||||
TerminalLauncherManager.registerPid(msg.getRequest(), msg.getPid());
|
||||
var term = AppPrefs.get().terminalType().getValue();
|
||||
var unicode = term.supportsUnicode();
|
||||
var escapes = term.supportsEscapes();
|
||||
var finished = TerminalLauncherManager.isCompletedSuccessfully(msg.getRequest());
|
||||
return Response.builder()
|
||||
.supportsUnicode(unicode)
|
||||
.supportsEscapeSequences(escapes)
|
||||
.alreadyFinished(finished)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresEnabledApi() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.xpipe.app.beacon.impl;
|
||||
|
||||
import io.xpipe.app.terminal.TerminalLauncherManager;
|
||||
import io.xpipe.app.terminal.TerminalView;
|
||||
import io.xpipe.beacon.BeaconClientException;
|
||||
import io.xpipe.beacon.BeaconServerException;
|
||||
import io.xpipe.beacon.api.TerminalWaitExchange;
|
||||
@@ -9,10 +8,10 @@ import io.xpipe.beacon.api.TerminalWaitExchange;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
public class TerminalWaitExchangeImpl extends TerminalWaitExchange {
|
||||
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException {
|
||||
TerminalView.get().open(msg.getRequest(), msg.getPid());
|
||||
TerminalLauncherManager.waitExchange(msg.getRequest(), msg.getPid());
|
||||
TerminalLauncherManager.waitExchange(msg.getRequest());
|
||||
return Response.builder().build();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import io.xpipe.app.comp.base.LeftSplitPaneComp;
|
||||
import io.xpipe.app.comp.base.StackComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.comp.store.StoreEntryWrapper;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
@@ -62,7 +61,6 @@ public class BrowserFileChooserSessionComp extends DialogComp {
|
||||
});
|
||||
var comp = new BrowserFileChooserSessionComp(stage, model);
|
||||
comp.apply(struc -> struc.get().setPrefSize(1200, 700))
|
||||
.apply(struc -> AppFont.normal(struc.get()))
|
||||
.styleClass("browser")
|
||||
.styleClass("chooser");
|
||||
return comp;
|
||||
|
||||
@@ -93,13 +93,14 @@ public class BrowserFullSessionComp extends SimpleComp {
|
||||
node.setClip(null);
|
||||
node.setPickOnBounds(false);
|
||||
});
|
||||
struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1));
|
||||
struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(-1));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
splitPane.styleClass("browser");
|
||||
return splitPane.createRegion();
|
||||
var r = splitPane.createRegion();
|
||||
return r;
|
||||
}
|
||||
|
||||
private Comp<CompStructure<VBox>> createLeftSide() {
|
||||
@@ -194,7 +195,6 @@ public class BrowserFullSessionComp extends SimpleComp {
|
||||
struc.get().setMinWidth(rightSplit.get());
|
||||
struc.get().setPrefWidth(rightSplit.get());
|
||||
struc.get().setMaxWidth(rightSplit.get());
|
||||
struc.get().getParent().requestLayout();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -202,7 +202,6 @@ public class BrowserFullSessionComp extends SimpleComp {
|
||||
struc.get().setMinWidth(newValue.doubleValue());
|
||||
struc.get().setPrefWidth(newValue.doubleValue());
|
||||
struc.get().setMaxWidth(newValue.doubleValue());
|
||||
struc.get().getParent().requestLayout();
|
||||
});
|
||||
|
||||
AnchorPane.setBottomAnchor(struc.get(), 0.0);
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.xpipe.app.browser;
|
||||
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.browser.file.BrowserHistorySavedState;
|
||||
import io.xpipe.app.browser.file.BrowserHistorySavedStateImpl;
|
||||
import io.xpipe.app.browser.file.BrowserHistoryTabModel;
|
||||
import io.xpipe.app.browser.file.BrowserTransferModel;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
@@ -189,7 +188,9 @@ public class BrowserFullSessionModel extends BrowserAbstractSessionModel<Browser
|
||||
// Prevent blocking of shutdown
|
||||
closeAsync(o);
|
||||
}
|
||||
BrowserHistorySavedStateImpl.get().save();
|
||||
if (all.size() > 0) {
|
||||
ThreadHelper.sleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all files
|
||||
|
||||
@@ -57,7 +57,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||
var tabs = createTabPane();
|
||||
var topBackground = Comp.hspacer().styleClass("top-spacer").createRegion();
|
||||
leftPadding.subscribe(number -> {
|
||||
StackPane.setMargin(topBackground, new Insets(0, 0, 0, -number.doubleValue() - 6));
|
||||
StackPane.setMargin(topBackground, new Insets(0, 0, 0, -number.doubleValue() - 3));
|
||||
});
|
||||
var stack = new StackPane(topBackground, tabs);
|
||||
stack.setAlignment(Pos.TOP_CENTER);
|
||||
@@ -217,7 +217,8 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||
headerArea
|
||||
.paddingProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> new Insets(2, 0, 4, -leftPadding.get() + 2), leftPadding));
|
||||
() -> new Insets(2, 0, 4, -leftPadding.get() + 3), leftPadding));
|
||||
tabs.setPadding(new Insets(0, 0, 0, -5));
|
||||
headerHeight.bind(headerArea.heightProperty());
|
||||
});
|
||||
}
|
||||
@@ -431,7 +432,7 @@ public class BrowserSessionTabsComp extends SimpleComp {
|
||||
},
|
||||
tabModel.getName(),
|
||||
global,
|
||||
AppPrefs.get().language(),
|
||||
AppI18n.activeLanguage(),
|
||||
AppPrefs.get().censorMode()));
|
||||
} else {
|
||||
tab.textProperty().bind(tabModel.getName());
|
||||
|
||||
@@ -45,7 +45,12 @@ public class BrowserClipboard {
|
||||
}
|
||||
|
||||
List<File> data = (List<File>) clipboard.getData(DataFlavor.javaFileListFlavor);
|
||||
var files = data.stream().map(f -> f.toPath()).toList();
|
||||
// Sometimes file data can contain invalid chars. Why?
|
||||
var files = data.stream()
|
||||
.filter(file ->
|
||||
file.toString().chars().noneMatch(value -> Character.isISOControl(value)))
|
||||
.map(f -> f.toPath())
|
||||
.toList();
|
||||
if (files.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.xpipe.app.comp.base.FilterComp;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.store.StoreCategoryWrapper;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.util.DataStoreCategoryChoiceComp;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
@@ -33,14 +33,14 @@ public final class BrowserConnectionListFilterComp extends SimpleComp {
|
||||
this.category)
|
||||
.styleClass(Styles.LEFT_PILL)
|
||||
.apply(struc -> {
|
||||
AppFont.medium(struc.get());
|
||||
AppFontSizes.base(struc.get());
|
||||
});
|
||||
var filter = new FilterComp(this.filter)
|
||||
.styleClass(Styles.RIGHT_PILL)
|
||||
.minWidth(0)
|
||||
.hgrow()
|
||||
.apply(struc -> {
|
||||
AppFont.medium(struc.get());
|
||||
AppFontSizes.base(struc.get());
|
||||
});
|
||||
|
||||
var top = new HorizontalComp(List.of(category, filter))
|
||||
@@ -51,6 +51,7 @@ public final class BrowserConnectionListFilterComp extends SimpleComp {
|
||||
first.prefHeightProperty().bind(second.heightProperty());
|
||||
first.minHeightProperty().bind(second.heightProperty());
|
||||
first.maxHeightProperty().bind(second.heightProperty());
|
||||
AppFontSizes.xl(struc.get());
|
||||
})
|
||||
.styleClass("bookmarks-header")
|
||||
.createRegion();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
|
||||
import javafx.scene.control.ContextMenu;
|
||||
@@ -24,13 +24,13 @@ public final class BrowserContextMenu extends ContextMenu {
|
||||
}
|
||||
|
||||
private void createMenu() {
|
||||
AppFontSizes.lg(getStyleableNode());
|
||||
|
||||
InputHelper.onLeft(this, false, e -> {
|
||||
hide();
|
||||
e.consume();
|
||||
});
|
||||
|
||||
AppFont.normal(this.getStyleableNode());
|
||||
|
||||
var empty = source == null;
|
||||
var selected = new ArrayList<>(
|
||||
empty
|
||||
|
||||
@@ -125,7 +125,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||
fileList.setComparator(table.getComparator());
|
||||
return true;
|
||||
});
|
||||
table.setFixedCellSize(32.0);
|
||||
table.setFixedCellSize(30.0);
|
||||
|
||||
prepareColumnVisibility(table, ownerCol, filenameCol);
|
||||
prepareTableScrollFix(table);
|
||||
@@ -134,7 +134,6 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||
prepareTableEntries(table);
|
||||
prepareTableChanges(table, filenameCol, mtimeCol, modeCol, ownerCol);
|
||||
prepareTypedSelectionModel(table);
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
@@ -291,7 +290,7 @@ public final class BrowserFileListComp extends SimpleComp {
|
||||
});
|
||||
|
||||
fileList.getSelection().addListener((ListChangeListener<? super BrowserEntry>) c -> {
|
||||
var existing = new HashSet<>(fileList.getSelection());
|
||||
var existing = new HashSet<>(table.getSelectionModel().getSelectedItems());
|
||||
var toApply = new HashSet<>(c.getList());
|
||||
if (existing.equals(toApply)) {
|
||||
return;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.core.window.AppWindowHelper;
|
||||
import io.xpipe.app.ext.ConnectionFileSystem;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.FileBridge;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import io.xpipe.core.process.ElevationFunction;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.store.ConnectionFileSystem;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
import io.xpipe.core.store.FileInfo;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
@@ -45,8 +45,8 @@ public class BrowserFileOpener {
|
||||
return fileSystem.openOutput(file.getPath(), totalBytes);
|
||||
}
|
||||
|
||||
var rootSc = sc.identicalSubShell()
|
||||
.elevated(ElevationFunction.elevated("sudo"))
|
||||
var rootSc = sc.identicalDialectSubShell()
|
||||
.elevated(ElevationFunction.elevated(null))
|
||||
.start();
|
||||
var rootFs = new ConnectionFileSystem(rootSc);
|
||||
try {
|
||||
|
||||
@@ -6,7 +6,6 @@ import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.augment.GrowAugment;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.VBoxViewComp;
|
||||
import io.xpipe.core.store.FileEntry;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -48,12 +47,10 @@ public class BrowserFileOverviewComp extends SimpleComp {
|
||||
});
|
||||
};
|
||||
|
||||
if (grow) {
|
||||
var c = new ListBoxViewComp<>(list, list, factory, true).styleClass("overview-file-list");
|
||||
return c.createRegion();
|
||||
} else {
|
||||
var c = new VBoxViewComp<>(list, list, factory).styleClass("overview-file-list");
|
||||
return c.createRegion();
|
||||
var c = new ListBoxViewComp<>(list, list, factory, true).styleClass("overview-file-list");
|
||||
if (!grow) {
|
||||
c.apply(struc -> struc.get().setFitToHeight(true));
|
||||
}
|
||||
return c.createRegion();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class BrowserFileSelectionListComp extends SimpleComp {
|
||||
.createRegion();
|
||||
var t = nameTransformation.apply(entry);
|
||||
var l = new Label(t.getValue(), image);
|
||||
l.setGraphicTextGap(6);
|
||||
l.setTextOverrun(OverrunStyle.CENTER_ELLIPSIS);
|
||||
t.addListener((observable, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
@@ -80,9 +80,10 @@ public class BrowserFileSystemTabComp extends SimpleComp {
|
||||
var topBar = new HBox();
|
||||
topBar.setAlignment(Pos.CENTER);
|
||||
topBar.getStyleClass().add("top-bar");
|
||||
AppFontSizes.xl(topBar);
|
||||
var navBar = new BrowserNavBarComp(model).createStructure();
|
||||
filter.textField().prefHeightProperty().bind(navBar.get().heightProperty());
|
||||
AppFont.medium(navBar.get());
|
||||
AppFontSizes.base(navBar.get());
|
||||
topBar.getChildren()
|
||||
.setAll(
|
||||
overview,
|
||||
|
||||
@@ -10,7 +10,6 @@ import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.terminal.*;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
@@ -26,6 +25,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -121,13 +121,15 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||
|
||||
var current = getCurrentDirectory();
|
||||
// We might close this after storage shutdown
|
||||
if (DataStorage.get() != null
|
||||
&& DataStorage.get().getStoreEntries().contains(getEntry().get())
|
||||
&& savedState != null
|
||||
&& current != null) {
|
||||
// If this entry does not exist, it's not that bad if we save it anyway
|
||||
if (
|
||||
// DataStorage.get() != null
|
||||
// && DataStorage.get().getStoreEntries().contains(getEntry().get())
|
||||
savedState != null && current != null) {
|
||||
savedState.cd(current.getPath(), false);
|
||||
BrowserHistorySavedStateImpl.get()
|
||||
.add(new BrowserHistorySavedState.Entry(getEntry().get().getUuid(), current.getPath()));
|
||||
BrowserHistorySavedStateImpl.get().save();
|
||||
}
|
||||
try {
|
||||
fileSystem.close();
|
||||
@@ -301,11 +303,22 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||
if (ShellDialects.getStartableDialects().stream().anyMatch(dialect -> adjustedPath
|
||||
.toLowerCase()
|
||||
.startsWith(dialect.getExecutableName().toLowerCase()))) {
|
||||
var cc = fileSystem
|
||||
.getShell()
|
||||
.get()
|
||||
.singularSubShell(ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false));
|
||||
openTerminalAsync(name, directory, cc, true);
|
||||
var sub = fileSystem.getShell().get().subShell();
|
||||
var open = new ShellOpenFunction() {
|
||||
|
||||
@Override
|
||||
public CommandBuilder prepareWithoutInitCommand() {
|
||||
return CommandBuilder.ofString(adjustedPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandBuilder prepareWithInitCommand(@NonNull String command) {
|
||||
return CommandBuilder.ofString(command);
|
||||
}
|
||||
};
|
||||
sub.setDumbOpen(open);
|
||||
sub.setTerminalOpen(open);
|
||||
openTerminalAsync(name, directory, sub, true);
|
||||
} else {
|
||||
var cc = fileSystem.getShell().get().command(adjustedPath);
|
||||
openTerminalAsync(name, directory, cc, true);
|
||||
@@ -328,7 +341,7 @@ public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab<File
|
||||
}
|
||||
|
||||
try {
|
||||
BrowserFileSystemHelper.validateDirectoryPath(this, resolvedPath, customInput);
|
||||
BrowserFileSystemHelper.validateDirectoryPath(this, resolvedPath, true);
|
||||
cdSyncWithoutCheck(path);
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).handle();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
@@ -24,7 +24,7 @@ public class BrowserGreetingComp extends SimpleComp {
|
||||
r.setText(getText());
|
||||
});
|
||||
});
|
||||
AppFont.setSize(r, 7);
|
||||
AppFontSizes.title(r);
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
r.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
|
||||
@@ -3,35 +3,26 @@ package io.xpipe.app.browser.file;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.base.TileButtonComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.layout.*;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import atlantafx.base.theme.Styles;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class BrowserHistoryTabComp extends SimpleComp {
|
||||
@@ -45,30 +36,12 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var state = BrowserHistorySavedStateImpl.get();
|
||||
|
||||
var welcome = new BrowserGreetingComp().createSimple();
|
||||
|
||||
var vbox = new VBox(welcome, new Spacer(4, Orientation.VERTICAL));
|
||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
var img = PrettyImageHelper.ofSpecificFixedSize("graphics/Hips.svg", 50, 61)
|
||||
.padding(new Insets(5, 0, 0, 0))
|
||||
.createRegion();
|
||||
|
||||
var hbox = new HBox(img, vbox);
|
||||
hbox.setAlignment(Pos.CENTER_LEFT);
|
||||
hbox.setSpacing(15);
|
||||
|
||||
if (state == null) {
|
||||
var header = new Label();
|
||||
header.textProperty().bind(AppI18n.observable("browserWelcomeEmpty"));
|
||||
vbox.getChildren().add(header);
|
||||
hbox.setPadding(new Insets(40, 40, 40, 50));
|
||||
return new VBox(hbox);
|
||||
}
|
||||
|
||||
var list = new DerivedObservableList<>(state.getEntries(), true)
|
||||
.filtered(e -> {
|
||||
if (DataStorage.get() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
if (entry.isEmpty()) {
|
||||
return false;
|
||||
@@ -82,20 +55,22 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
||||
})
|
||||
.getList();
|
||||
var empty = Bindings.createBooleanBinding(() -> list.isEmpty(), list);
|
||||
var contentDisplay = createListDisplay(list);
|
||||
var emptyDisplay = createEmptyDisplay();
|
||||
var map = new LinkedHashMap<Comp<?>, ObservableValue<Boolean>>();
|
||||
map.put(emptyDisplay, empty);
|
||||
map.put(contentDisplay, empty.not());
|
||||
var stack = new MultiContentComp(map);
|
||||
return stack.createRegion();
|
||||
}
|
||||
|
||||
var headerBinding = BindingsHelper.flatMap(empty, b -> {
|
||||
if (b) {
|
||||
return AppI18n.observable("browserWelcomeEmpty");
|
||||
} else {
|
||||
return AppI18n.observable("browserWelcomeSystems");
|
||||
}
|
||||
});
|
||||
var header = new LabelComp(headerBinding).createRegion();
|
||||
AppFont.setSize(header, 1);
|
||||
vbox.getChildren().add(header);
|
||||
private Comp<?> createListDisplay(ObservableList<BrowserHistorySavedState.Entry> list) {
|
||||
var state = BrowserHistorySavedStateImpl.get();
|
||||
|
||||
var storeList = new VBox();
|
||||
storeList.setSpacing(8);
|
||||
var welcome = new BrowserGreetingComp();
|
||||
var header = new LabelComp(AppI18n.observable("browserWelcomeSystems"));
|
||||
var vbox = new VerticalComp(List.of(welcome, Comp.vspacer(4), header));
|
||||
vbox.apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT));
|
||||
|
||||
var listBox = new ListBoxViewComp<>(
|
||||
list,
|
||||
@@ -117,32 +92,38 @@ public class BrowserHistoryTabComp extends SimpleComp {
|
||||
.apply(struc -> {
|
||||
VBox vBox = (VBox) struc.get().getContent();
|
||||
vBox.setSpacing(10);
|
||||
})
|
||||
.hide(empty)
|
||||
.createRegion();
|
||||
|
||||
var layout = new VBox();
|
||||
layout.getStyleClass().add("welcome");
|
||||
layout.setPadding(new Insets(25, 40, 40, 40));
|
||||
layout.setSpacing(18);
|
||||
layout.getChildren().add(hbox);
|
||||
layout.getChildren().add(Comp.separator().hide(empty).createRegion());
|
||||
layout.getChildren().add(listBox);
|
||||
VBox.setVgrow(layout.getChildren().get(2), Priority.NEVER);
|
||||
layout.getChildren().add(Comp.separator().hide(empty).createRegion());
|
||||
});
|
||||
|
||||
var tile = new TileButtonComp("restore", "restoreAllSessions", "mdmz-restore", actionEvent -> {
|
||||
model.restoreState(state);
|
||||
actionEvent.consume();
|
||||
})
|
||||
.grow(true, false)
|
||||
.hide(empty)
|
||||
.accessibleTextKey("restoreAllSessions");
|
||||
layout.getChildren().add(tile.createRegion());
|
||||
AppFont.medium(layout);
|
||||
|
||||
var layout = new VerticalComp(List.of(vbox, Comp.vspacer(5), listBox, Comp.separator(), tile));
|
||||
layout.styleClass("welcome");
|
||||
layout.spacing(14);
|
||||
layout.maxWidth(1000);
|
||||
layout.padding(new Insets(45, 40, 40, 50));
|
||||
layout.apply(struc -> {
|
||||
struc.get().setMaxWidth(1000);
|
||||
});
|
||||
return layout;
|
||||
}
|
||||
|
||||
private Comp<?> createEmptyDisplay() {
|
||||
var intro = new IntroComp(
|
||||
"browserWelcomeEmpty",
|
||||
new LabelGraphic.CompGraphic(PrettyImageHelper.ofSpecificFixedSize("graphics/Hips.svg", 100, 122)));
|
||||
intro.setButtonAction(() -> {
|
||||
BrowserFullSessionModel.DEFAULT.openFileSystemAsync(
|
||||
DataStorage.get().local().ref(), null, null);
|
||||
});
|
||||
intro.setButtonDefault(true);
|
||||
return intro;
|
||||
}
|
||||
|
||||
private Comp<?> entryButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) {
|
||||
var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid());
|
||||
var graphic = entry.get().getEffectiveIconFile();
|
||||
|
||||
@@ -9,12 +9,15 @@ import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.comp.base.TooltipAugment;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ContextMenuHelper;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
@@ -84,15 +87,25 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
|
||||
pathRegion.focusedProperty(),
|
||||
model.getInOverview()));
|
||||
var stack = new StackPane(pathRegion, breadcrumbsRegion);
|
||||
stack.setAlignment(Pos.CENTER_LEFT);
|
||||
pathRegion.prefHeightProperty().bind(stack.heightProperty());
|
||||
|
||||
stack.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||
setMargin(stack, breadcrumbsRegion);
|
||||
});
|
||||
|
||||
model.getCurrentPath().addListener((observable, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
setMargin(stack, breadcrumbsRegion);
|
||||
});
|
||||
});
|
||||
|
||||
// Prevent overflow
|
||||
var clip = new Rectangle();
|
||||
clip.widthProperty().bind(stack.widthProperty());
|
||||
clip.heightProperty().bind(stack.heightProperty());
|
||||
breadcrumbsRegion.setClip(clip);
|
||||
stack.setClip(clip);
|
||||
|
||||
stack.setAlignment(Pos.CENTER_LEFT);
|
||||
HBox.setHgrow(stack, Priority.ALWAYS);
|
||||
|
||||
var topBox = new HBox(homeButton, stack, historyButton);
|
||||
@@ -110,6 +123,15 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
|
||||
return new Structure(topBox, pathRegion, historyButton);
|
||||
}
|
||||
|
||||
private void setMargin(StackPane stackPane, Region region) {
|
||||
var off = region.getWidth() - stackPane.getWidth();
|
||||
if (off <= 0) {
|
||||
StackPane.setMargin(region, new Insets(0, 0, 0, 0));
|
||||
} else {
|
||||
StackPane.setMargin(region, new Insets(0, 20, 0, -off - 20));
|
||||
}
|
||||
}
|
||||
|
||||
private Comp<CompStructure<TextField>> createPathBar() {
|
||||
var path = new SimpleStringProperty(model.getCurrentPath().get());
|
||||
model.getCurrentPath().subscribe((newValue) -> {
|
||||
@@ -172,7 +194,7 @@ public class BrowserNavBarComp extends Comp<BrowserNavBarComp.Structure> {
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu() {
|
||||
var cm = new ContextMenu();
|
||||
var cm = ContextMenuHelper.create();
|
||||
|
||||
var f = model.getHistory().getForwardHistory(8).stream().toList();
|
||||
for (int i = f.size() - 1; i >= 0; i--) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.xpipe.app.browser.file;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.SimpleTitledPaneComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
@@ -77,7 +76,6 @@ public class BrowserOverviewComp extends SimpleComp {
|
||||
|
||||
var vbox = new VerticalComp(List.of(recentPane, commonPane, rootsPane)).styleClass("overview");
|
||||
var r = vbox.createRegion();
|
||||
AppFont.medium(r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.xpipe.app.browser.file;
|
||||
|
||||
import io.xpipe.app.browser.icon.BrowserIconManager;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.util.BooleanAnimationTimer;
|
||||
import io.xpipe.app.util.InputHelper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
@@ -41,6 +42,7 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
||||
this.base = base;
|
||||
this.model = model;
|
||||
|
||||
AppFontSizes.lg(getStyleableNode());
|
||||
addEventFilter(Menu.ON_SHOWING, e -> {
|
||||
Node content = getSkin().getNode();
|
||||
if (content instanceof Region r) {
|
||||
@@ -115,7 +117,13 @@ public class BrowserQuickAccessContextMenu extends ContextMenu {
|
||||
var dirs = browserEntries.stream()
|
||||
.filter(e -> e.getRawFileEntry().getKind() == FileKind.DIRECTORY)
|
||||
.toList();
|
||||
if (dirs.size() == 1) {
|
||||
// Expand subdir if only one
|
||||
// Note that if we have a link to the directory itself, we shouldn't do it, otherwise we are stuck in a loop
|
||||
if (dirs.size() == 1
|
||||
&& !dirs.getFirst()
|
||||
.getRawFileEntry()
|
||||
.getPath()
|
||||
.equals(entry.getRawFileEntry().getPath())) {
|
||||
updateMenuItems((Menu) menus.get(dirs.getFirst()), dirs.getFirst(), true);
|
||||
}
|
||||
newItems.addAll(menus.values());
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.HumanReadableFormat;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
@@ -49,7 +49,7 @@ public class BrowserStatusBarComp extends SimpleComp {
|
||||
event.consume();
|
||||
r.startFullDrag();
|
||||
});
|
||||
AppFont.small(r);
|
||||
AppFontSizes.xs(r);
|
||||
simulateEmptyCell(r);
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.xpipe.app.browser.file;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
@@ -81,7 +80,6 @@ public class BrowserTransferComp extends SimpleComp {
|
||||
.grow(false, true);
|
||||
var dragNotice = new LabelComp(AppI18n.observable("dragLocalFiles"))
|
||||
.apply(struc -> struc.get().setGraphic(new FontIcon("mdi2h-hand-left")))
|
||||
.apply(struc -> AppFont.medium(struc.get()))
|
||||
.apply(struc -> struc.get().setWrapText(true))
|
||||
.hide(model.getEmpty());
|
||||
|
||||
@@ -193,6 +191,7 @@ public class BrowserTransferComp extends SimpleComp {
|
||||
});
|
||||
});
|
||||
|
||||
return stack.styleClass("transfer").createRegion();
|
||||
var r = stack.styleClass("transfer").createRegion();
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class BrowserIconVariant {
|
||||
}
|
||||
|
||||
protected final String getIcon() {
|
||||
var t = AppPrefs.get() != null ? AppPrefs.get().theme.getValue() : null;
|
||||
var t = AppPrefs.get() != null ? AppPrefs.get().theme().getValue() : null;
|
||||
if (t == null) {
|
||||
return lightIcon;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,15 @@ public abstract class Comp<S extends CompStructure<?>> {
|
||||
}
|
||||
|
||||
public Comp<S> visible(ObservableValue<Boolean> o) {
|
||||
return apply(struc -> struc.get().visibleProperty().bind(o));
|
||||
return apply(struc -> {
|
||||
var region = struc.get();
|
||||
BindingsHelper.preserve(region, o);
|
||||
o.subscribe(n -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
region.setVisible(n);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public Comp<S> disable(ObservableValue<Boolean> o) {
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.xpipe.app.comp.base;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.store.StoreViewState;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
@@ -68,7 +67,6 @@ public class AppLayoutComp extends Comp<AppLayoutComp.Structure> {
|
||||
}
|
||||
});
|
||||
});
|
||||
AppFont.normal(pane);
|
||||
pane.getStyleClass().add("layout");
|
||||
return new Structure(pane, multiR, sidebarR, new ArrayList<>(multiR.getChildren()));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.window.AppDialog;
|
||||
import io.xpipe.app.core.window.AppMainWindow;
|
||||
@@ -57,7 +57,7 @@ public class AppMainWindowContentComp extends SimpleComp {
|
||||
var version = new LabelComp((AppProperties.get().isStaging() ? "XPipe PTB" : "XPipe") + " "
|
||||
+ AppProperties.get().getVersion());
|
||||
version.apply(struc -> {
|
||||
AppFont.setSize(struc.get(), 1);
|
||||
AppFontSizes.xxl(struc.get());
|
||||
struc.get().setOpacity(0.6);
|
||||
});
|
||||
|
||||
@@ -83,12 +83,13 @@ public class AppMainWindowContentComp extends SimpleComp {
|
||||
loaded.subscribe(struc -> {
|
||||
if (struc != null) {
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
struc.prepareAddition();
|
||||
anim.stop();
|
||||
struc.prepareAddition();
|
||||
pane.getChildren().add(struc.get());
|
||||
struc.show();
|
||||
pane.getChildren().remove(vbox);
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
pane.getStyleClass().remove("background");
|
||||
pane.getChildren().remove(vbox);
|
||||
struc.show();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +66,9 @@ public class ButtonComp extends Comp<CompStructure<Button>> {
|
||||
}
|
||||
});
|
||||
}
|
||||
button.setOnAction(e -> getListener().run());
|
||||
if (listener != null) {
|
||||
button.setOnAction(e -> getListener().run());
|
||||
}
|
||||
button.getStyleClass().add("button-comp");
|
||||
return new SimpleCompStructure<>(button);
|
||||
}
|
||||
|
||||
@@ -68,10 +68,7 @@ public class ContextualFileReferenceChoiceComp extends Comp<CompStructure<HBox>>
|
||||
BrowserFileChooserSessionComp.openSingleFile(
|
||||
() -> fileSystem.getValue(),
|
||||
fileStore -> {
|
||||
if (fileStore == null) {
|
||||
filePath.setValue(null);
|
||||
fileSystem.setValue(null);
|
||||
} else {
|
||||
if (fileStore != null) {
|
||||
filePath.setValue(fileStore.getPath());
|
||||
fileSystem.setValue(fileStore.getFileSystem());
|
||||
}
|
||||
|
||||
@@ -6,45 +6,38 @@ import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.beans.value.ObservableIntegerValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.OverrunStyle;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class CountComp<T> extends Comp<CompStructure<Label>> {
|
||||
@AllArgsConstructor
|
||||
public class CountComp extends Comp<CompStructure<Label>> {
|
||||
|
||||
private final ObservableList<T> sub;
|
||||
private final ObservableList<T> all;
|
||||
private final ObservableIntegerValue sub;
|
||||
private final ObservableIntegerValue all;
|
||||
private final Function<String, String> transformation;
|
||||
|
||||
public CountComp(ObservableList<T> sub, ObservableList<T> all) {
|
||||
this(sub, all, Function.identity());
|
||||
}
|
||||
|
||||
public CountComp(ObservableList<T> sub, ObservableList<T> all, Function<String, String> transformation) {
|
||||
this.sub = PlatformThread.sync(sub);
|
||||
this.all = PlatformThread.sync(all);
|
||||
this.transformation = transformation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<Label> createBase() {
|
||||
var label = new Label();
|
||||
label.setTextOverrun(OverrunStyle.CLIP);
|
||||
label.setAlignment(Pos.CENTER);
|
||||
label.textProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
if (sub.size() == all.size()) {
|
||||
return transformation.apply(all.size() + "");
|
||||
} else {
|
||||
return transformation.apply(sub.size() + "/" + all.size());
|
||||
}
|
||||
},
|
||||
sub,
|
||||
all));
|
||||
var binding = Bindings.createStringBinding(
|
||||
() -> {
|
||||
if (sub.get() == all.get()) {
|
||||
return transformation.apply(all.get() + "");
|
||||
} else {
|
||||
return transformation.apply(sub.get() + "/" + all.get());
|
||||
}
|
||||
},
|
||||
sub,
|
||||
all);
|
||||
label.textProperty().bind(PlatformThread.sync(binding));
|
||||
label.getStyleClass().add("count-comp");
|
||||
return new SimpleCompStructure<>(label);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.util.ContextMenuHelper;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.Size;
|
||||
import javafx.css.SizeUnits;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
@@ -27,11 +27,13 @@ public class DropdownComp extends Comp<CompStructure<Button>> {
|
||||
|
||||
@Override
|
||||
public CompStructure<Button> createBase() {
|
||||
ContextMenu cm = new ContextMenu(items.stream()
|
||||
.map(comp -> {
|
||||
return new MenuItem(null, comp.createRegion());
|
||||
})
|
||||
.toArray(MenuItem[]::new));
|
||||
var cm = ContextMenuHelper.create();
|
||||
cm.getItems()
|
||||
.setAll(items.stream()
|
||||
.map(comp -> {
|
||||
return new MenuItem(null, comp.createRegion());
|
||||
})
|
||||
.toList());
|
||||
|
||||
Button button = (Button) new ButtonComp(null, () -> {})
|
||||
.apply(new ContextMenuAugment<>(e -> true, null, () -> {
|
||||
|
||||
@@ -2,10 +2,17 @@ package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.util.FileOpener;
|
||||
import io.xpipe.core.process.ShellScript;
|
||||
import io.xpipe.core.process.ShellStoreState;
|
||||
import io.xpipe.core.store.StatefulDataStore;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
@@ -20,6 +27,33 @@ import java.nio.file.Files;
|
||||
|
||||
public class IntegratedTextAreaComp extends Comp<IntegratedTextAreaComp.Structure> {
|
||||
|
||||
public static IntegratedTextAreaComp script(
|
||||
ObservableValue<DataStoreEntryRef<ShellStore>> host, Property<ShellScript> value) {
|
||||
var string = new SimpleStringProperty(
|
||||
value.getValue() != null ? value.getValue().getValue() : null);
|
||||
string.addListener((observable, oldValue, newValue) -> {
|
||||
value.setValue(newValue != null ? new ShellScript(newValue) : null);
|
||||
});
|
||||
var i = new IntegratedTextAreaComp(
|
||||
string,
|
||||
false,
|
||||
"script",
|
||||
Bindings.createStringBinding(
|
||||
() -> {
|
||||
return host.getValue() != null
|
||||
&& host.getValue().getStore() instanceof StatefulDataStore<?> sd
|
||||
&& sd.getState() instanceof ShellStoreState sss
|
||||
&& sss.getShellDialect() != null
|
||||
? sss.getShellDialect().getScriptFileEnding()
|
||||
: "sh";
|
||||
},
|
||||
host));
|
||||
i.minHeight(60);
|
||||
i.prefHeight(60);
|
||||
i.maxHeight(60);
|
||||
return i;
|
||||
}
|
||||
|
||||
private final Property<String> value;
|
||||
private final boolean lazy;
|
||||
private final String identifier;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.core.process.OsType;
|
||||
@@ -43,7 +43,7 @@ public class IntroComp extends SimpleComp {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
AppFont.setSize(title, 7);
|
||||
AppFontSizes.title(title);
|
||||
|
||||
var introDesc = new Label();
|
||||
introDesc.textProperty().bind(AppI18n.observable(translationsKey + "Content"));
|
||||
|
||||
@@ -50,8 +50,10 @@ public class LazyTextFieldComp extends Comp<CompStructure<TextField>> {
|
||||
});
|
||||
|
||||
// Handles external updates
|
||||
PlatformThread.sync(appliedValue).addListener((observable, oldValue, n) -> {
|
||||
currentValue.setValue(n);
|
||||
appliedValue.addListener((observable, oldValue, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
currentValue.setValue(n);
|
||||
});
|
||||
});
|
||||
|
||||
r.setMinWidth(0);
|
||||
|
||||
@@ -3,6 +3,7 @@ package io.xpipe.app.comp.base;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
@@ -11,6 +12,7 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -18,10 +20,7 @@ import javafx.scene.layout.VBox;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
@@ -34,7 +33,6 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
private final ObservableList<T> shown;
|
||||
private final ObservableList<T> all;
|
||||
private final Function<T, Comp<?>> compFunction;
|
||||
private final int limit = Integer.MAX_VALUE;
|
||||
private final boolean scrollBar;
|
||||
|
||||
@Setter
|
||||
@@ -55,12 +53,12 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
VBox vbox = new VBox();
|
||||
vbox.getStyleClass().add("list-box-content");
|
||||
vbox.setFocusTraversable(false);
|
||||
var scroll = new ScrollPane(vbox);
|
||||
|
||||
refresh(vbox, shown, all, cache, false);
|
||||
vbox.requestLayout();
|
||||
refresh(scroll, vbox, shown, all, cache, false, false);
|
||||
|
||||
shown.addListener((ListChangeListener<? super T>) (c) -> {
|
||||
refresh(vbox, c.getList(), all, cache, true);
|
||||
refresh(scroll, vbox, c.getList(), all, cache, true, true);
|
||||
});
|
||||
|
||||
all.addListener((ListChangeListener<? super T>) c -> {
|
||||
@@ -69,7 +67,6 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
}
|
||||
});
|
||||
|
||||
var scroll = new ScrollPane(vbox);
|
||||
if (scrollBar) {
|
||||
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
|
||||
scroll.skinProperty().subscribe(newValue -> {
|
||||
@@ -93,15 +90,110 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.getStyleClass().add("list-box-view-comp");
|
||||
|
||||
scroll.vvalueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
scroll.heightProperty().addListener((observable, oldValue, newValue) -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
vbox.heightProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
});
|
||||
|
||||
// We can't directly listen to any parent element changing visibility, so this is a compromise
|
||||
if (AppLayoutModel.get() != null) {
|
||||
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
vbox.sceneProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Node c = vbox;
|
||||
while ((c = c.getParent()) != null) {
|
||||
c.boundsInParentProperty().addListener((observable1, oldValue1, newValue1) -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
if (newValue != null) {
|
||||
newValue.heightProperty().addListener((observable1, oldValue1, newValue1) -> {
|
||||
updateVisibilities(scroll, vbox);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(scroll);
|
||||
}
|
||||
|
||||
private boolean isVisible(ScrollPane pane, VBox box, Node node) {
|
||||
if (pane.getScene() == null || box.getScene() == null || node.getScene() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var paneHeight = pane.getHeight();
|
||||
var scrollCenter = box.getBoundsInLocal().getHeight() * pane.getVvalue();
|
||||
var minBoundsHeight = scrollCenter - paneHeight;
|
||||
var maxBoundsHeight = scrollCenter + paneHeight;
|
||||
|
||||
var nodeMinHeight = node.getBoundsInParent().getMinY();
|
||||
var nodeMaxHeight = node.getBoundsInParent().getMaxY();
|
||||
|
||||
if (paneHeight == 0.0
|
||||
|| box.getHeight() == 0.0
|
||||
|| ((Region) node).getHeight() == 0.0
|
||||
|| nodeMinHeight == nodeMaxHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nodeMaxHeight < minBoundsHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nodeMinHeight > maxBoundsHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pane.getScene().getHeight() > 200) {
|
||||
var sceneNodeBounds = node.localToScene(node.getBoundsInLocal());
|
||||
if (sceneNodeBounds.getMaxY() < 0
|
||||
|| sceneNodeBounds.getMinY() > pane.getScene().getHeight()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateVisibilities(ScrollPane scroll, VBox vbox) {
|
||||
for (Node child : vbox.getChildren()) {
|
||||
var v = isVisible(scroll, vbox, child);
|
||||
child.setVisible(v);
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh(
|
||||
VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
|
||||
ScrollPane scroll,
|
||||
VBox listView,
|
||||
List<? extends T> shown,
|
||||
List<? extends T> all,
|
||||
Map<T, Region> cache,
|
||||
boolean asynchronous,
|
||||
boolean refreshVisibilities) {
|
||||
Runnable update = () -> {
|
||||
synchronized (cache) {
|
||||
var set = new HashSet<T>();
|
||||
// These lists might diverge on updates
|
||||
set.addAll(shown);
|
||||
set.addAll(all);
|
||||
// Clear cache of unused values
|
||||
cache.keySet().removeIf(t -> !all.contains(t));
|
||||
cache.keySet().removeIf(t -> !set.contains(t));
|
||||
}
|
||||
|
||||
final long[] lastPause = {System.currentTimeMillis()};
|
||||
@@ -117,13 +209,18 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
|
||||
if (!cache.containsKey(v)) {
|
||||
var comp = compFunction.apply(v);
|
||||
cache.put(v, comp != null ? comp.createRegion() : null);
|
||||
if (comp != null) {
|
||||
var r = comp.createRegion();
|
||||
r.setVisible(false);
|
||||
cache.put(v, r);
|
||||
} else {
|
||||
cache.put(v, null);
|
||||
}
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.filter(region -> region != null)
|
||||
.limit(limit)
|
||||
.toList();
|
||||
|
||||
if (listView.getChildren().equals(newShown)) {
|
||||
@@ -140,6 +237,9 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
|
||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
||||
d.setContent(newShown);
|
||||
if (refreshVisibilities) {
|
||||
updateVisibilities(scroll, listView);
|
||||
}
|
||||
};
|
||||
|
||||
if (asynchronous) {
|
||||
|
||||
@@ -91,7 +91,8 @@ public class ListSelectorComp<T> extends SimpleComp {
|
||||
|
||||
if (showAllSelector.get()) {
|
||||
var allSelector = new CheckBox(null);
|
||||
allSelector.setSelected(currentVals.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
||||
allSelector.setSelected(
|
||||
currentVals.stream().filter(t -> !disable.test(t)).count() == selected.size());
|
||||
allSelector.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
cbs.forEach(checkBox -> {
|
||||
if (checkBox.isDisabled()) {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.skin.VirtualFlow;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class ListVirtualViewComp<T> extends Comp<CompStructure<ScrollPane>> {
|
||||
|
||||
private static final PseudoClass ODD = PseudoClass.getPseudoClass("odd");
|
||||
private static final PseudoClass EVEN = PseudoClass.getPseudoClass("even");
|
||||
private static final PseudoClass FIRST = PseudoClass.getPseudoClass("first");
|
||||
private static final PseudoClass LAST = PseudoClass.getPseudoClass("last");
|
||||
|
||||
private final ObservableList<T> shown;
|
||||
private final ObservableList<T> all;
|
||||
private final Function<T, Comp<?>> compFunction;
|
||||
private final int limit = Integer.MAX_VALUE;
|
||||
private final boolean scrollBar;
|
||||
|
||||
@Setter
|
||||
private int platformPauseInterval = -1;
|
||||
|
||||
public ListVirtualViewComp(
|
||||
ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction, boolean scrollBar) {
|
||||
this.shown = shown;
|
||||
this.all = all;
|
||||
this.compFunction = compFunction;
|
||||
this.scrollBar = scrollBar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<ScrollPane> createBase() {
|
||||
Map<T, Region> cache = new IdentityHashMap<>();
|
||||
|
||||
var vbox = new VirtualFlow<>();
|
||||
vbox.getStyleClass().add("list-box-content");
|
||||
vbox.setFocusTraversable(false);
|
||||
|
||||
var scroll = new ScrollPane(vbox);
|
||||
if (scrollBar) {
|
||||
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
|
||||
scroll.skinProperty().subscribe(newValue -> {
|
||||
if (newValue != null) {
|
||||
ScrollBar bar = (ScrollBar) scroll.lookup(".scroll-bar:vertical");
|
||||
bar.opacityProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
var v = bar.getVisibleAmount();
|
||||
// Check for rounding and accuracy issues
|
||||
// It might not be exactly equal to 1.0
|
||||
return v < 0.99 ? 1.0 : 0.0;
|
||||
},
|
||||
bar.visibleAmountProperty()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
scroll.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setFitToHeight(true);
|
||||
}
|
||||
scroll.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
scroll.setFitToWidth(true);
|
||||
scroll.getStyleClass().add("list-box-view-comp");
|
||||
return new SimpleCompStructure<>(scroll);
|
||||
}
|
||||
|
||||
private void refresh(
|
||||
VBox listView, List<? extends T> shown, List<? extends T> all, Map<T, Region> cache, boolean asynchronous) {
|
||||
Runnable update = () -> {
|
||||
synchronized (cache) {
|
||||
// Clear cache of unused values
|
||||
cache.keySet().removeIf(t -> !all.contains(t));
|
||||
}
|
||||
|
||||
final long[] lastPause = {System.currentTimeMillis()};
|
||||
// Create copy to reduce chances of concurrent modification
|
||||
var shownCopy = new ArrayList<>(shown);
|
||||
var newShown = shownCopy.stream()
|
||||
.map(v -> {
|
||||
var elapsed = System.currentTimeMillis() - lastPause[0];
|
||||
if (platformPauseInterval != -1 && elapsed > platformPauseInterval) {
|
||||
PlatformThread.runNestedLoopIteration();
|
||||
lastPause[0] = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (!cache.containsKey(v)) {
|
||||
var comp = compFunction.apply(v);
|
||||
cache.put(v, comp != null ? comp.createRegion() : null);
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.filter(region -> region != null)
|
||||
.limit(limit)
|
||||
.toList();
|
||||
|
||||
if (listView.getChildren().equals(newShown)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < newShown.size(); i++) {
|
||||
var r = newShown.get(i);
|
||||
r.pseudoClassStateChanged(ODD, i % 2 != 0);
|
||||
r.pseudoClassStateChanged(EVEN, i % 2 == 0);
|
||||
r.pseudoClassStateChanged(FIRST, i == 0);
|
||||
r.pseudoClassStateChanged(LAST, i == newShown.size() - 1);
|
||||
}
|
||||
|
||||
var d = new DerivedObservableList<>(listView.getChildren(), true);
|
||||
d.setContent(newShown);
|
||||
};
|
||||
|
||||
if (asynchronous) {
|
||||
Platform.runLater(update);
|
||||
} else {
|
||||
PlatformThread.runLaterIfNeeded(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,10 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||
}
|
||||
|
||||
if (!showLoading.getValue()) {
|
||||
Platform.runLater(() -> loadingOverlay.setVisible(false));
|
||||
Platform.runLater(() -> {
|
||||
loadingOverlay.setVisible(false);
|
||||
loadingOverlay.setManaged(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -68,7 +71,10 @@ public class LoadingOverlayComp extends Comp<CompStructure<StackPane>> {
|
||||
}
|
||||
|
||||
if (showLoading.getValue()) {
|
||||
Platform.runLater(() -> loadingOverlay.setVisible(true));
|
||||
Platform.runLater(() -> {
|
||||
loadingOverlay.setVisible(true);
|
||||
loadingOverlay.setManaged(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,8 +95,8 @@ public class MarkdownComp extends Comp<CompStructure<StackPane>> {
|
||||
.setUserDataDirectory(
|
||||
AppProperties.get().getDataDir().resolve("webview").toFile());
|
||||
var theme = AppPrefs.get() != null
|
||||
&& AppPrefs.get().theme.getValue() != null
|
||||
&& AppPrefs.get().theme.getValue().isDark()
|
||||
&& AppPrefs.get().theme().getValue() != null
|
||||
&& AppPrefs.get().theme().getValue().isDark()
|
||||
? "misc/github-markdown-dark.css"
|
||||
: "misc/github-markdown-light.css";
|
||||
var url = AppResources.getResourceURL(AppResources.XPIPE_MODULE, theme).orElseThrow();
|
||||
|
||||
@@ -40,7 +40,11 @@ public class ModalButton {
|
||||
}
|
||||
|
||||
public static ModalButton cancel() {
|
||||
return new ModalButton("cancel", null, true, false);
|
||||
return cancel(null);
|
||||
}
|
||||
|
||||
public static ModalButton cancel(Runnable action) {
|
||||
return new ModalButton("cancel", action, true, false);
|
||||
}
|
||||
|
||||
public static ModalButton skip() {
|
||||
|
||||
@@ -64,6 +64,10 @@ public class ModalOverlay {
|
||||
AppDialog.show(this, false);
|
||||
}
|
||||
|
||||
public boolean isShowing() {
|
||||
return AppDialog.getModalOverlay().contains(this);
|
||||
}
|
||||
|
||||
public void showAndWait() {
|
||||
AppDialog.showAndWait(this);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.AppLogs;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
@@ -45,9 +45,9 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
protected Region createSimple() {
|
||||
var bgRegion = background.createRegion();
|
||||
var modal = new ModalPane();
|
||||
modal.setInTransitionFactory(OsType.getLocal() == OsType.LINUX ? null : node -> fadeInDelyed(node));
|
||||
modal.setInTransitionFactory(null);
|
||||
modal.setOutTransitionFactory(
|
||||
OsType.getLocal() == OsType.LINUX ? null : node -> Animations.fadeOut(node, Duration.millis(200)));
|
||||
OsType.getLocal() == OsType.LINUX ? null : node -> Animations.fadeOut(node, Duration.millis(50)));
|
||||
modal.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
var c = modal.getContent();
|
||||
if (newValue && c != null) {
|
||||
@@ -177,7 +177,7 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
AppI18n.get(newValue.getTitleKey()),
|
||||
newValue.getGraphic() != null ? newValue.getGraphic().createGraphicNode() : null);
|
||||
l.setGraphicTextGap(8);
|
||||
AppFont.normal(l);
|
||||
AppFontSizes.xl(l);
|
||||
content.getChildren().addFirst(l);
|
||||
} else {
|
||||
content.getChildren().addFirst(Comp.vspacer(0).createRegion());
|
||||
@@ -194,7 +194,7 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
}
|
||||
}
|
||||
content.getChildren().add(buttonBar);
|
||||
AppFont.small(buttonBar);
|
||||
AppFontSizes.xs(buttonBar);
|
||||
}
|
||||
|
||||
var modalBox = new ModalBox(content) {
|
||||
@@ -228,6 +228,14 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
var busy = mocc.busy();
|
||||
if (busy != null) {
|
||||
var loading = LoadingOverlayComp.noProgress(Comp.of(() -> modalBox), busy);
|
||||
// loading.apply(struc -> {
|
||||
// var bg = struc.get().getChildren().getFirst();
|
||||
// struc.get().getChildren().get(1).addEventFilter(MouseEvent.MOUSE_PRESSED, event ->
|
||||
// {
|
||||
// bg.fireEvent(event);
|
||||
// event.consume();
|
||||
// });
|
||||
// });
|
||||
return loading.createRegion();
|
||||
}
|
||||
}
|
||||
@@ -289,7 +297,7 @@ public class ModalOverlayComp extends SimpleComp {
|
||||
var t = new Timeline(
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(node.opacityProperty(), 0.01)),
|
||||
new KeyFrame(Duration.millis(50), new KeyValue(node.opacityProperty(), 0.01, Animations.EASE)),
|
||||
new KeyFrame(Duration.millis(150), new KeyValue(node.opacityProperty(), 1, Animations.EASE)));
|
||||
new KeyFrame(Duration.millis(1250), new KeyValue(node.opacityProperty(), 1, Animations.EASE)));
|
||||
|
||||
t.statusProperty().addListener((obs, old, val) -> {
|
||||
if (val == Animation.Status.STOPPED) {
|
||||
|
||||
@@ -6,9 +6,9 @@ import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public abstract class ModalOverlayContentComp extends SimpleComp {
|
||||
|
||||
@Getter
|
||||
protected ModalOverlay modalOverlay;
|
||||
|
||||
void setModalOverlay(ModalOverlay modalOverlay) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package io.xpipe.app.comp.base;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
@@ -58,7 +58,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
||||
if (showVertical) {
|
||||
var line = new VBox();
|
||||
line.prefWidthProperty().bind(pane.widthProperty());
|
||||
line.setSpacing(5);
|
||||
line.setSpacing(2);
|
||||
|
||||
var name = new Label();
|
||||
name.getStyleClass().add("name");
|
||||
@@ -92,7 +92,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
||||
popover.setCloseButtonEnabled(false);
|
||||
popover.setHeaderAlwaysVisible(false);
|
||||
popover.setDetachable(true);
|
||||
AppFont.small(popover.getContentNode());
|
||||
AppFontSizes.xs(popover.getContentNode());
|
||||
|
||||
var extendedDescription = new Button("... ?");
|
||||
extendedDescription.setMinWidth(Region.USE_PREF_SIZE);
|
||||
@@ -100,7 +100,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
||||
extendedDescription.getStyleClass().add(Styles.ACCENT);
|
||||
extendedDescription.getStyleClass().add("long-description");
|
||||
extendedDescription.setAccessibleText("Help");
|
||||
AppFont.normal(extendedDescription);
|
||||
AppFontSizes.xl(extendedDescription);
|
||||
extendedDescription.setOnAction(e -> {
|
||||
popover.show(extendedDescription);
|
||||
e.consume();
|
||||
@@ -119,6 +119,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
||||
}
|
||||
} else {
|
||||
line.getChildren().add(description);
|
||||
line.getChildren().add(new Spacer(2, Orientation.VERTICAL));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +129,7 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
|
||||
compRegion.accessibleHelpProperty().bind(PlatformThread.sync(entry.description()));
|
||||
}
|
||||
line.getChildren().add(compRegion);
|
||||
compRegion.getStyleClass().add("options-content");
|
||||
}
|
||||
|
||||
pane.getChildren().add(line);
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.Button;
|
||||
@@ -32,7 +32,7 @@ public class PopupMenuButtonComp extends SimpleComp {
|
||||
popover.setCloseButtonEnabled(false);
|
||||
popover.setHeaderAlwaysVisible(false);
|
||||
popover.setDetachable(true);
|
||||
AppFont.small(popover.getContentNode());
|
||||
AppFontSizes.xs(popover.getContentNode());
|
||||
|
||||
var extendedDescription = new Button();
|
||||
extendedDescription.textProperty().bind(name);
|
||||
|
||||
@@ -94,8 +94,8 @@ public class PrettyImageComp extends SimpleComp {
|
||||
|
||||
Consumer<String> update = val -> {
|
||||
var useDark = AppPrefs.get() != null
|
||||
&& AppPrefs.get().theme.get() != null
|
||||
&& AppPrefs.get().theme.get().isDark();
|
||||
&& AppPrefs.get().theme().getValue() != null
|
||||
&& AppPrefs.get().theme().getValue().isDark();
|
||||
var fixed = val != null
|
||||
? FileNames.getBaseName(val) + (useDark ? "-dark" : "") + "." + FileNames.getExtension(val)
|
||||
: null;
|
||||
@@ -110,7 +110,7 @@ public class PrettyImageComp extends SimpleComp {
|
||||
|
||||
PlatformThread.sync(value).subscribe(update);
|
||||
if (AppPrefs.get() != null) {
|
||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||
AppPrefs.get().theme().addListener((observable, oldValue, newValue) -> {
|
||||
update.accept(value.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.AppImages;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class PrettySvgComp extends SimpleComp {
|
||||
|
||||
private final ObservableValue<String> value;
|
||||
private final double width;
|
||||
private final double height;
|
||||
|
||||
public PrettySvgComp(ObservableValue<String> value, double width, double height) {
|
||||
this.value = value;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var image = new SimpleStringProperty();
|
||||
var syncValue = PlatformThread.sync(value);
|
||||
var storeIcon = SvgView.create(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
if (image.get() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (AppImages.hasSvgImage(image.getValue())) {
|
||||
return AppImages.svgImage(image.getValue());
|
||||
} else if (AppImages.hasSvgImage(image.getValue().replace("-dark", ""))) {
|
||||
return AppImages.svgImage(image.getValue().replace("-dark", ""));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
image));
|
||||
var ar = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
return storeIcon.getWidth().getValue().doubleValue()
|
||||
/ storeIcon.getHeight().getValue().doubleValue();
|
||||
},
|
||||
storeIcon.getWidth(),
|
||||
storeIcon.getHeight());
|
||||
var widthProperty = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
boolean widthLimited = width / height < ar.doubleValue();
|
||||
if (widthLimited) {
|
||||
return width;
|
||||
} else {
|
||||
return height * ar.doubleValue();
|
||||
}
|
||||
},
|
||||
ar);
|
||||
var heightProperty = Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
boolean widthLimited = width / height < ar.doubleValue();
|
||||
if (widthLimited) {
|
||||
return width / ar.doubleValue();
|
||||
} else {
|
||||
return height;
|
||||
}
|
||||
},
|
||||
ar);
|
||||
|
||||
var stack = new StackPane();
|
||||
var wv = storeIcon.createWebview();
|
||||
if (wv.isPresent()) {
|
||||
var node = wv.get();
|
||||
node.prefWidthProperty().bind(widthProperty);
|
||||
node.maxWidthProperty().bind(widthProperty);
|
||||
node.minWidthProperty().bind(widthProperty);
|
||||
node.prefHeightProperty().bind(heightProperty);
|
||||
node.maxHeightProperty().bind(heightProperty);
|
||||
node.minHeightProperty().bind(heightProperty);
|
||||
stack.getChildren().add(node);
|
||||
}
|
||||
|
||||
Consumer<String> update = val -> {
|
||||
var useDark = AppPrefs.get() != null
|
||||
&& AppPrefs.get().theme.get() != null
|
||||
&& AppPrefs.get().theme.get().isDark();
|
||||
var fixed = val != null
|
||||
? FileNames.getBaseName(val) + (useDark ? "-dark" : "") + "." + FileNames.getExtension(val)
|
||||
: null;
|
||||
image.set(fixed);
|
||||
};
|
||||
|
||||
syncValue.subscribe(update);
|
||||
if (AppPrefs.get() != null) {
|
||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||
update.accept(syncValue.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
stack.setFocusTraversable(false);
|
||||
stack.setPrefWidth(width);
|
||||
stack.setMinWidth(width);
|
||||
stack.setPrefHeight(height);
|
||||
stack.setMinHeight(height);
|
||||
stack.setAlignment(Pos.CENTER);
|
||||
stack.getStyleClass().add("stack");
|
||||
return stack;
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,10 @@ package io.xpipe.app.comp.base;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppDistributionType;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppLayoutModel;
|
||||
import io.xpipe.app.update.UpdateAvailableDialog;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
@@ -76,7 +76,7 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||
var shortcut = e.combination();
|
||||
b.apply(new TooltipAugment<>(e.name(), shortcut));
|
||||
b.apply(struc -> {
|
||||
AppFont.setSize(struc.get(), 1);
|
||||
AppFontSizes.xl(struc.get());
|
||||
struc.get().pseudoClassStateChanged(selected, value.getValue().equals(e));
|
||||
value.addListener((c, o, n) -> {
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
@@ -123,17 +123,17 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||
.tooltipKey("updateAvailableTooltip")
|
||||
.accessibleTextKey("updateAvailableTooltip");
|
||||
b.apply(struc -> {
|
||||
AppFont.setSize(struc.get(), 1);
|
||||
AppFontSizes.xl(struc.get());
|
||||
});
|
||||
b.hide(PlatformThread.sync(Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return XPipeDistributionType.get()
|
||||
return AppDistributionType.get()
|
||||
.getUpdateHandler()
|
||||
.getPreparedUpdate()
|
||||
.getValue()
|
||||
== null;
|
||||
},
|
||||
XPipeDistributionType.get().getUpdateHandler().getPreparedUpdate())));
|
||||
AppDistributionType.get().getUpdateHandler().getPreparedUpdate())));
|
||||
vbox.getChildren().add(b.createRegion());
|
||||
}
|
||||
|
||||
@@ -142,7 +142,6 @@ public class SideMenuBarComp extends Comp<CompStructure<VBox>> {
|
||||
filler.setMaxHeight(3000);
|
||||
vbox.getChildren().add(filler);
|
||||
VBox.setVgrow(filler, Priority.ALWAYS);
|
||||
filler.prefWidthProperty().bind(((Region) vbox.getChildren().getFirst()).widthProperty());
|
||||
vbox.getStyleClass().add("sidebar-comp");
|
||||
return new SimpleCompStructure<>(vbox);
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import javafx.css.Size;
|
||||
import javafx.css.SizeUnits;
|
||||
import javafx.geometry.Point2D;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class SvgHelper {
|
||||
|
||||
public static Size parseSize(String string) {
|
||||
for (SizeUnits unit : SizeUnits.values()) {
|
||||
if (string.endsWith(unit.toString())) {
|
||||
return new Size(
|
||||
Double.parseDouble(string.substring(
|
||||
0, string.length() - unit.toString().length())),
|
||||
unit);
|
||||
}
|
||||
}
|
||||
return new Size(Double.parseDouble(string), SizeUnits.PX);
|
||||
}
|
||||
|
||||
public static Point2D getDimensions(String val) {
|
||||
var regularExpression = Pattern.compile("<svg[^>]+?width=\"([^ ]+)\"", Pattern.DOTALL);
|
||||
var matcher = regularExpression.matcher(val);
|
||||
|
||||
if (!matcher.find()) {
|
||||
var viewBox = Pattern.compile(
|
||||
"<svg.+?viewBox=\"([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\"", Pattern.DOTALL);
|
||||
matcher = viewBox.matcher(val);
|
||||
if (matcher.find()) {
|
||||
return new Point2D(
|
||||
parseSize(matcher.group(3)).pixels(),
|
||||
parseSize(matcher.group(4)).pixels());
|
||||
}
|
||||
}
|
||||
|
||||
var width = matcher.group(1);
|
||||
regularExpression = Pattern.compile("<svg.+?height=\"([^ ]+)\"", Pattern.DOTALL);
|
||||
matcher = regularExpression.matcher(val);
|
||||
matcher.find();
|
||||
var height = matcher.group(1);
|
||||
return new Point2D(parseSize(width).pixels(), parseSize(height).pixels());
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.scene.AccessibleRole;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.web.WebView;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
public class SvgView {
|
||||
|
||||
private final ObservableValue<Number> width;
|
||||
private final ObservableValue<Number> height;
|
||||
private final ObservableValue<String> svgContent;
|
||||
|
||||
private static boolean canCreateWebview = true;
|
||||
|
||||
private SvgView(ObservableValue<Number> width, ObservableValue<Number> height, ObservableValue<String> svgContent) {
|
||||
this.width = PlatformThread.sync(width);
|
||||
this.height = PlatformThread.sync(height);
|
||||
this.svgContent = PlatformThread.sync(svgContent);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static SvgView create(ObservableValue<String> content) {
|
||||
var widthProperty = new SimpleIntegerProperty();
|
||||
var heightProperty = new SimpleIntegerProperty();
|
||||
content.subscribe(val -> {
|
||||
if (val == null || val.isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dim = SvgHelper.getDimensions(val);
|
||||
widthProperty.set((int) Math.ceil(dim.getX()));
|
||||
heightProperty.set((int) Math.ceil(dim.getY()));
|
||||
});
|
||||
return new SvgView(widthProperty, heightProperty, content);
|
||||
}
|
||||
|
||||
private String getHtml(String content) {
|
||||
return "<html><body style='margin: 0; padding: 0; border: none;' >" + content + "</body></html>";
|
||||
}
|
||||
|
||||
private WebView createWebView() {
|
||||
if (!canCreateWebview) {
|
||||
return null;
|
||||
}
|
||||
|
||||
WebView wv;
|
||||
try {
|
||||
// This can happen if we are using a custom JavaFX build without webkit
|
||||
wv = new WebView();
|
||||
} catch (Throwable t) {
|
||||
ErrorEvent.fromThrowable(t).omit().expected().handle();
|
||||
canCreateWebview = false;
|
||||
return null;
|
||||
}
|
||||
|
||||
wv.getStyleClass().add("svg-comp");
|
||||
wv.getEngine()
|
||||
.setUserDataDirectory(
|
||||
AppProperties.get().getDataDir().resolve("webview").toFile());
|
||||
// Sometimes a web view might not render when the background is set to transparent, at least according to stack
|
||||
// overflow
|
||||
wv.setPageFill(Color.valueOf("#00000001"));
|
||||
// wv.setPageFill(Color.BLACK);
|
||||
wv.getEngine().setJavaScriptEnabled(false);
|
||||
wv.setContextMenuEnabled(false);
|
||||
wv.setFocusTraversable(false);
|
||||
wv.setAccessibleRole(AccessibleRole.IMAGE_VIEW);
|
||||
wv.setDisable(true);
|
||||
|
||||
wv.getEngine().loadContent(svgContent.getValue() != null ? getHtml(svgContent.getValue()) : null);
|
||||
svgContent.subscribe(n -> {
|
||||
if (n == null) {
|
||||
wv.setOpacity(0.0);
|
||||
return;
|
||||
}
|
||||
|
||||
wv.setOpacity(1.0);
|
||||
var html = getHtml(n);
|
||||
wv.getEngine().loadContent(html);
|
||||
});
|
||||
|
||||
// Hide scrollbars that popup on every content change. Bug in WebView?
|
||||
wv.getChildrenUnmodifiable().addListener((ListChangeListener<Node>) change -> {
|
||||
Set<Node> scrolls = wv.lookupAll(".scroll-bar");
|
||||
for (Node scroll : scrolls) {
|
||||
scroll.setFocusTraversable(false);
|
||||
scroll.setVisible(false);
|
||||
scroll.setManaged(false);
|
||||
}
|
||||
});
|
||||
|
||||
// As the aspect ratio of the WebView is kept constant, we can compute the zoom only using the width
|
||||
wv.zoomProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
return wv.getWidth() / width.getValue().doubleValue();
|
||||
},
|
||||
wv.widthProperty(),
|
||||
width));
|
||||
|
||||
wv.maxWidthProperty().bind(wv.prefWidthProperty());
|
||||
wv.maxHeightProperty().bind(wv.prefHeightProperty());
|
||||
|
||||
wv.minWidthProperty().bind(wv.prefWidthProperty());
|
||||
wv.minHeightProperty().bind(wv.prefHeightProperty());
|
||||
|
||||
return wv;
|
||||
}
|
||||
|
||||
public Optional<WebView> createWebview() {
|
||||
var wv = createWebView();
|
||||
return Optional.ofNullable(wv);
|
||||
}
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public static class Structure implements CompStructure<StackPane> {
|
||||
StackPane pane;
|
||||
WebView webView;
|
||||
|
||||
@Override
|
||||
public StackPane get() {
|
||||
return pane;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
@@ -68,7 +68,7 @@ public class TileButtonComp extends Comp<TileButtonComp.Structure> {
|
||||
header.textProperty().bind(PlatformThread.sync(name));
|
||||
var desc = new Label();
|
||||
desc.textProperty().bind(PlatformThread.sync(description));
|
||||
AppFont.small(desc);
|
||||
AppFontSizes.xs(desc);
|
||||
desc.setOpacity(0.8);
|
||||
var text = new VBox(header, desc);
|
||||
text.setSpacing(2);
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.control.TitledPane;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class TitledPaneComp extends Comp<CompStructure<TitledPane>> {
|
||||
|
||||
private final ObservableValue<String> name;
|
||||
private final Comp<?> content;
|
||||
private final int height;
|
||||
|
||||
public TitledPaneComp(ObservableValue<String> name, Comp<?> content, int height) {
|
||||
this.name = name;
|
||||
this.content = content;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<TitledPane> createBase() {
|
||||
var tp = new TitledPane(null, content.createRegion());
|
||||
tp.textProperty().bind(name);
|
||||
tp.getStyleClass().add("titled-pane-comp");
|
||||
tp.setExpanded(false);
|
||||
tp.setAnimated(false);
|
||||
AtomicInteger minimizedSize = new AtomicInteger();
|
||||
tp.expandedProperty().addListener((c, o, n) -> {
|
||||
if (n) {
|
||||
if (minimizedSize.get() == 0) {
|
||||
minimizedSize.set((int) tp.getHeight());
|
||||
}
|
||||
tp.setPrefHeight(height);
|
||||
} else {
|
||||
tp.setPrefHeight(minimizedSize.get());
|
||||
}
|
||||
});
|
||||
return new SimpleCompStructure<>(tp);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.augment.Augment;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
@@ -41,7 +42,7 @@ public class TooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||
} else {
|
||||
tt.textProperty().bind(PlatformThread.sync(text));
|
||||
}
|
||||
tt.setStyle("-fx-font-size: 11pt;");
|
||||
AppFontSizes.base(tt.getStyleableNode());
|
||||
tt.setWrapText(true);
|
||||
tt.setMaxWidth(400);
|
||||
tt.getStyleClass().add("fancy-tooltip");
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.CompStructure;
|
||||
import io.xpipe.app.comp.SimpleCompStructure;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class VBoxViewComp<T> extends Comp<CompStructure<VBox>> {
|
||||
|
||||
private final ObservableList<T> shown;
|
||||
private final ObservableList<T> all;
|
||||
private final Function<T, Comp<?>> compFunction;
|
||||
|
||||
public VBoxViewComp(ObservableList<T> shown, ObservableList<T> all, Function<T, Comp<?>> compFunction) {
|
||||
this.shown = PlatformThread.sync(shown);
|
||||
this.all = PlatformThread.sync(all);
|
||||
this.compFunction = compFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompStructure<VBox> createBase() {
|
||||
Map<T, Region> cache = new HashMap<>();
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.setFocusTraversable(false);
|
||||
|
||||
refresh(vbox, shown, cache, false);
|
||||
vbox.requestLayout();
|
||||
|
||||
shown.addListener((ListChangeListener<? super T>) (c) -> {
|
||||
refresh(vbox, c.getList(), cache, true);
|
||||
});
|
||||
|
||||
all.addListener((ListChangeListener<? super T>) c -> {
|
||||
cache.keySet().retainAll(c.getList());
|
||||
});
|
||||
|
||||
return new SimpleCompStructure<>(vbox);
|
||||
}
|
||||
|
||||
private void refresh(VBox listView, List<? extends T> c, Map<T, Region> cache, boolean asynchronous) {
|
||||
Runnable update = () -> {
|
||||
var newShown = c.stream()
|
||||
.map(v -> {
|
||||
if (!cache.containsKey(v)) {
|
||||
cache.put(v, compFunction.apply(v).createRegion());
|
||||
}
|
||||
|
||||
return cache.get(v);
|
||||
})
|
||||
.toList();
|
||||
|
||||
if (!listView.getChildren().equals(newShown)) {
|
||||
listView.getChildren().setAll(newShown);
|
||||
listView.layout();
|
||||
}
|
||||
};
|
||||
|
||||
if (asynchronous) {
|
||||
Platform.runLater(update);
|
||||
} else {
|
||||
PlatformThread.runLaterIfNeeded(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,8 @@ import javafx.scene.layout.*;
|
||||
|
||||
public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
|
||||
private final boolean showIcon;
|
||||
|
||||
public DenseStoreEntryComp(StoreSection section, boolean showIcon, Comp<?> content) {
|
||||
public DenseStoreEntryComp(StoreSection section, Comp<?> content) {
|
||||
super(section, content);
|
||||
this.showIcon = showIcon;
|
||||
}
|
||||
|
||||
private Label createInformation(GridPane grid) {
|
||||
@@ -74,12 +71,10 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
var notes = new StoreNotesComp(getWrapper()).createRegion();
|
||||
var userIcon = createUserIcon().createRegion();
|
||||
|
||||
if (showIcon) {
|
||||
var storeIcon = createIcon(28, 24);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(38));
|
||||
grid.add(storeIcon, 0, 0);
|
||||
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
||||
}
|
||||
var storeIcon = createIcon(28, 24);
|
||||
GridPane.setHalignment(storeIcon, HPos.CENTER);
|
||||
grid.add(storeIcon, 0, 0);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(34));
|
||||
|
||||
var customSize = content != null ? 100 : 0;
|
||||
var custom = new ColumnConstraints(0, customSize, customSize);
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.base.StackComp;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.core.process.SystemState;
|
||||
import io.xpipe.core.store.FileNames;
|
||||
|
||||
@@ -51,7 +50,11 @@ public class OsLogoComp extends SimpleComp {
|
||||
},
|
||||
wrapper.getPersistentState(),
|
||||
state);
|
||||
var hide = BindingsHelper.map(img, s -> s != null);
|
||||
var hide = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return img.get() != null;
|
||||
},
|
||||
img);
|
||||
return new StackComp(List.of(
|
||||
new SystemStateComp(state).hide(hide),
|
||||
PrettyImageHelper.ofFixedSize(img, 24, 24).visible(hide)))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.geometry.HPos;
|
||||
@@ -25,7 +25,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
var summary = new Label();
|
||||
summary.textProperty().bind(getWrapper().getShownSummary());
|
||||
summary.getStyleClass().add("summary");
|
||||
AppFont.small(summary);
|
||||
AppFontSizes.xs(summary);
|
||||
return summary;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
|
||||
var storeIcon = createIcon(46, 40);
|
||||
grid.add(storeIcon, 0, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(56));
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(52));
|
||||
|
||||
var active = new StoreActiveComp(getWrapper()).createRegion();
|
||||
var nameBox = new HBox(name, userIcon, notes);
|
||||
|
||||
@@ -4,7 +4,7 @@ import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.augment.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataColor;
|
||||
@@ -12,11 +12,11 @@ import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreCategory;
|
||||
import io.xpipe.app.util.ClipboardHelper;
|
||||
import io.xpipe.app.util.ContextMenuHelper;
|
||||
import io.xpipe.app.util.DerivedObservableList;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
@@ -29,6 +29,7 @@ import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyCodeCombination;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -50,7 +51,8 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
|
||||
@Override
|
||||
protected Region createSimple() {
|
||||
var prop = new SimpleStringProperty(category.getName().getValue());
|
||||
var prop = new SimpleStringProperty();
|
||||
category.getName().subscribe(prop::setValue);
|
||||
AppPrefs.get().censorMode().subscribe(aBoolean -> {
|
||||
var n = category.getName().getValue();
|
||||
prop.setValue(aBoolean ? "*".repeat(n.length()) : n);
|
||||
@@ -67,18 +69,17 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
() -> {
|
||||
var exp = category.getExpanded().get()
|
||||
&& category.getChildren().getList().size() > 0;
|
||||
return new LabelGraphic.IconGraphic(exp ? "mdal-keyboard_arrow_down" : "mdal-keyboard_arrow_right");
|
||||
return new LabelGraphic.IconGraphic(exp ? "mdi2m-menu-down-outline" : "mdi2m-menu-right-outline");
|
||||
},
|
||||
category.getExpanded(),
|
||||
category.getChildren().getList());
|
||||
var expandButton = new IconButtonComp(expandIcon, () -> {
|
||||
category.toggleExpanded();
|
||||
})
|
||||
.apply(struc -> AppFont.medium(struc.get()))
|
||||
.apply(struc -> {
|
||||
struc.get().setAlignment(Pos.CENTER);
|
||||
struc.get().setPadding(new Insets(-2, 0, 0, 0));
|
||||
struc.get().setFocusTraversable(false);
|
||||
HBox.setMargin(struc.get(), new Insets(0, 0, 2.6, 0));
|
||||
})
|
||||
.disable(Bindings.isEmpty(category.getChildren().getList()))
|
||||
.styleClass("expand-button")
|
||||
@@ -92,10 +93,7 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
}
|
||||
|
||||
if (!DataStorage.get().supportsSharing()
|
||||
|| (!category.getCategory().canShare()
|
||||
&& !category.getCategory()
|
||||
.getUuid()
|
||||
.equals(DataStorage.LOCAL_IDENTITIES_CATEGORY_UUID))) {
|
||||
|| (!category.getCategory().canShare())) {
|
||||
return new LabelGraphic.IconGraphic("mdi2g-git");
|
||||
}
|
||||
|
||||
@@ -104,7 +102,7 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
category.getSync(),
|
||||
hover);
|
||||
var statusButton = new IconButtonComp(statusIcon)
|
||||
.apply(struc -> AppFont.small(struc.get()))
|
||||
.apply(struc -> AppFontSizes.xs(struc.get()))
|
||||
.apply(struc -> {
|
||||
struc.get().setAlignment(Pos.CENTER);
|
||||
struc.get().setPadding(new Insets(0, 0, 0, 0));
|
||||
@@ -119,25 +117,18 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
}))
|
||||
.styleClass("status-button");
|
||||
|
||||
var shownList = new DerivedObservableList<>(
|
||||
category.getAllContainedEntries().getList(), true)
|
||||
.filtered(
|
||||
storeEntryWrapper -> {
|
||||
return storeEntryWrapper.matchesFilter(
|
||||
StoreViewState.get().getFilterString().getValue());
|
||||
},
|
||||
StoreViewState.get().getFilterString())
|
||||
.getList();
|
||||
var count =
|
||||
new CountComp<>(shownList, category.getAllContainedEntries().getList(), string -> "(" + string + ")");
|
||||
count.visible(Bindings.isNotEmpty(shownList));
|
||||
var count = new CountComp(
|
||||
category.getShownContainedEntriesCount(),
|
||||
category.getAllContainedEntriesCount(),
|
||||
string -> "(" + string + ")");
|
||||
count.visible(Bindings.notEqual(0, category.getShownContainedEntriesCount()));
|
||||
|
||||
var showStatus = hover.or(new SimpleBooleanProperty(DataStorage.get().supportsSharing()))
|
||||
.or(showing);
|
||||
var focus = new SimpleBooleanProperty();
|
||||
var h = new HorizontalComp(List.of(
|
||||
expandButton,
|
||||
Comp.hspacer(1),
|
||||
Comp.hspacer(category.getCategory().getParentCategory() == null ? 3 : 0),
|
||||
Comp.of(() -> name).hgrow(),
|
||||
Comp.hspacer(2),
|
||||
count,
|
||||
@@ -145,7 +136,8 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
statusButton.hide(showStatus.not())));
|
||||
h.padding(new Insets(0, 10, 0, (category.getDepth() * 10)));
|
||||
|
||||
var categoryButton = new ButtonComp(null, h.createRegion(), category::select)
|
||||
var categoryButton = new ButtonComp(
|
||||
null, new SimpleObjectProperty<>(new LabelGraphic.CompGraphic(h)), category::select)
|
||||
.focusTraversable()
|
||||
.styleClass("category-button")
|
||||
.apply(struc -> hover.bind(struc.get().hoverProperty()))
|
||||
@@ -172,6 +164,7 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
var children =
|
||||
new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper), false);
|
||||
children.styleClass("children");
|
||||
children.minHeight(0);
|
||||
|
||||
var hide = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
@@ -197,7 +190,6 @@ public class StoreCategoryComp extends SimpleComp {
|
||||
|
||||
private ContextMenu createContextMenu(Region text) {
|
||||
var contextMenu = ContextMenuHelper.create();
|
||||
AppFont.normal(contextMenu.getStyleableNode());
|
||||
|
||||
if (AppPrefs.get().enableHttpApi().get()) {
|
||||
var copyId = new MenuItem(AppI18n.get("copyId"), new FontIcon("mdi2c-content-copy"));
|
||||
|
||||
@@ -2,6 +2,8 @@ package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.ScrollComp;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -20,12 +22,16 @@ public class StoreCategoryListComp extends SimpleComp {
|
||||
sp.styleClass("store-category-bar");
|
||||
sp.apply(struc -> {
|
||||
Region content = (Region) struc.get().getContent();
|
||||
struc.get().setFitToWidth(true);
|
||||
if (OsType.getLocal() == OsType.MACOS) {
|
||||
AppFontSizes.lg(struc.get());
|
||||
}
|
||||
struc.get()
|
||||
.minHeightProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
var h = content.getHeight();
|
||||
return Math.min(200, h + 2);
|
||||
return Math.min(150, h + 2);
|
||||
},
|
||||
content.heightProperty()));
|
||||
});
|
||||
|
||||
@@ -32,9 +32,11 @@ public class StoreCategoryWrapper {
|
||||
private final Property<Boolean> sync;
|
||||
private final DerivedObservableList<StoreCategoryWrapper> children;
|
||||
private final DerivedObservableList<StoreEntryWrapper> directContainedEntries;
|
||||
private final DerivedObservableList<StoreEntryWrapper> allContainedEntries;
|
||||
private final IntegerProperty shownContainedEntriesCount = new SimpleIntegerProperty();
|
||||
private final IntegerProperty allContainedEntriesCount = new SimpleIntegerProperty();
|
||||
private final BooleanProperty expanded = new SimpleBooleanProperty();
|
||||
private final Property<DataColor> color = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty largeCategoryOptimizations = new SimpleBooleanProperty();
|
||||
private StoreCategoryWrapper cachedParent;
|
||||
|
||||
public StoreCategoryWrapper(DataStoreCategory category) {
|
||||
@@ -57,7 +59,6 @@ public class StoreCategoryWrapper {
|
||||
this.sortMode = new SimpleObjectProperty<>(category.getSortMode());
|
||||
this.sync = new SimpleObjectProperty<>(category.isSync());
|
||||
this.children = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||
this.allContainedEntries = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||
this.directContainedEntries = new DerivedObservableList<>(FXCollections.observableArrayList(), true);
|
||||
this.color.setValue(category.getColor());
|
||||
setupListeners();
|
||||
@@ -136,7 +137,7 @@ public class StoreCategoryWrapper {
|
||||
update();
|
||||
});
|
||||
|
||||
AppPrefs.get().language().addListener((observable, oldValue, newValue) -> {
|
||||
AppI18n.activeLanguage().addListener((observable, oldValue, newValue) -> {
|
||||
update();
|
||||
});
|
||||
|
||||
@@ -177,22 +178,32 @@ public class StoreCategoryWrapper {
|
||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid());
|
||||
})
|
||||
.toList());
|
||||
allContainedEntries.setContent(allEntries.stream()
|
||||
.filter(entry -> {
|
||||
return entry.getEntry().getCategoryUuid().equals(category.getUuid())
|
||||
|| (AppPrefs.get()
|
||||
.showChildCategoriesInParentCategory()
|
||||
.get()
|
||||
&& children.getList().stream()
|
||||
.anyMatch(storeCategoryWrapper -> storeCategoryWrapper.contains(entry)));
|
||||
})
|
||||
.toList());
|
||||
|
||||
children.setContent(StoreViewState.get().getCategories().getList().stream()
|
||||
.filter(storeCategoryWrapper -> getCategory()
|
||||
.getUuid()
|
||||
.equals(storeCategoryWrapper.getCategory().getParentCategory()))
|
||||
.toList());
|
||||
var direct = directContainedEntries.getList().size();
|
||||
var sub = children.getList().stream()
|
||||
.mapToInt(value -> value.allContainedEntriesCount.get())
|
||||
.sum();
|
||||
allContainedEntriesCount.setValue(direct + sub);
|
||||
|
||||
var performanceCount =
|
||||
AppPrefs.get().showChildCategoriesInParentCategory().get() ? allContainedEntriesCount.get() : direct;
|
||||
if (performanceCount > 500) {
|
||||
largeCategoryOptimizations.setValue(true);
|
||||
}
|
||||
|
||||
var directFiltered = directContainedEntries.getList().stream()
|
||||
.filter(storeEntryWrapper -> storeEntryWrapper.matchesFilter(
|
||||
StoreViewState.get().getFilterString().getValue()))
|
||||
.count();
|
||||
var subFiltered = children.getList().stream()
|
||||
.mapToInt(value -> value.shownContainedEntriesCount.get())
|
||||
.sum();
|
||||
shownContainedEntriesCount.setValue(directFiltered + subFiltered);
|
||||
Optional.ofNullable(getParent()).ifPresent(storeCategoryWrapper -> {
|
||||
storeCategoryWrapper.update();
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.FilterComp;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.LocalStore;
|
||||
import io.xpipe.app.ext.ShellStore;
|
||||
@@ -170,7 +170,7 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
||||
popover.setHeaderAlwaysVisible(true);
|
||||
popover.setDetachable(true);
|
||||
popover.setTitle(AppI18n.get("selectConnection"));
|
||||
AppFont.small(popover.getContentNode());
|
||||
AppFontSizes.xs(popover.getContentNode());
|
||||
}
|
||||
|
||||
return popover;
|
||||
@@ -231,7 +231,7 @@ public class StoreChoiceComp<T extends DataStore> extends SimpleComp {
|
||||
var icon = new FontIcon("mdal-keyboard_arrow_down");
|
||||
icon.setDisable(true);
|
||||
icon.setPickOnBounds(false);
|
||||
AppFont.header(icon);
|
||||
AppFontSizes.xl(icon);
|
||||
var pane = new StackPane(r, icon);
|
||||
pane.focusedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
|
||||
@@ -23,7 +23,6 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Orientation;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -32,7 +31,6 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import atlantafx.base.controls.Spacer;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import net.synedra.validatorfx.GraphicDecorationStackPane;
|
||||
@@ -448,10 +446,18 @@ public class StoreCreationComp extends DialogComp {
|
||||
}
|
||||
|
||||
private Region createStoreProperties(Comp<?> comp, Validator propVal) {
|
||||
var p = provider.getValue();
|
||||
var nameKey = p == null
|
||||
|| p.getCreationCategory() == null
|
||||
|| p.getCreationCategory().getCategory().equals(DataStorage.ALL_CONNECTIONS_CATEGORY_UUID)
|
||||
? "connection"
|
||||
: p.getCreationCategory().getCategory().equals(DataStorage.ALL_SCRIPTS_CATEGORY_UUID)
|
||||
? "script"
|
||||
: "identity";
|
||||
return new OptionsBuilder()
|
||||
.addComp(comp, store)
|
||||
.name("connectionName")
|
||||
.description("connectionNameDescription")
|
||||
.name(nameKey + "Name")
|
||||
.description(nameKey + "NameDescription")
|
||||
.addString(name, false)
|
||||
.nonNull(propVal)
|
||||
.buildComp()
|
||||
@@ -514,7 +520,7 @@ public class StoreCreationComp extends DialogComp {
|
||||
|
||||
var sep = new Separator();
|
||||
sep.getStyleClass().add("spacer");
|
||||
var top = new VBox(providerChoice.createRegion(), new Spacer(5, Orientation.VERTICAL), sep);
|
||||
var top = new VBox(providerChoice.createRegion(), sep);
|
||||
top.getStyleClass().add("top");
|
||||
if (showProviders) {
|
||||
layout.setTop(top);
|
||||
|
||||
@@ -69,7 +69,7 @@ public class StoreCreationMenu {
|
||||
item.setOnAction(event -> {
|
||||
StoreCreationComp.showCreation(
|
||||
defaultProvider != null
|
||||
? DataStoreProviders.byName(defaultProvider).orElseThrow()
|
||||
? DataStoreProviders.byId(defaultProvider).orElseThrow()
|
||||
: null,
|
||||
category);
|
||||
event.consume();
|
||||
@@ -87,7 +87,7 @@ public class StoreCreationMenu {
|
||||
|
||||
StoreCreationComp.showCreation(
|
||||
defaultProvider != null
|
||||
? DataStoreProviders.byName(defaultProvider).orElseThrow()
|
||||
? DataStoreProviders.byId(defaultProvider).orElseThrow()
|
||||
: null,
|
||||
category);
|
||||
event.consume();
|
||||
|
||||
@@ -9,18 +9,15 @@ import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.comp.base.LoadingOverlayComp;
|
||||
import io.xpipe.app.comp.base.TooltipAugment;
|
||||
import io.xpipe.app.core.App;
|
||||
import io.xpipe.app.core.AppActionLinkDetector;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.core.*;
|
||||
import io.xpipe.app.ext.ActionProvider;
|
||||
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;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ObservableDoubleValue;
|
||||
@@ -65,7 +62,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
var forceCondensed = AppPrefs.get() != null
|
||||
&& AppPrefs.get().condenseConnectionDisplay().get();
|
||||
if (!preferLarge || forceCondensed) {
|
||||
return new DenseStoreEntryComp(section, true, content);
|
||||
return new DenseStoreEntryComp(section, content);
|
||||
} else {
|
||||
return new StandardStoreEntryComp(section, content);
|
||||
}
|
||||
@@ -78,7 +75,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
} else {
|
||||
var forceCondensed = AppPrefs.get() != null
|
||||
&& AppPrefs.get().condenseConnectionDisplay().get();
|
||||
return forceCondensed ? new DenseStoreEntryComp(e, true, null) : new StandardStoreEntryComp(e, null);
|
||||
return forceCondensed ? new DenseStoreEntryComp(e, null) : new StandardStoreEntryComp(e, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +135,19 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
|
||||
var loading = LoadingOverlayComp.noProgress(
|
||||
Comp.of(() -> button), getWrapper().getEffectiveBusy());
|
||||
AppFont.normal(button);
|
||||
if (OsType.getLocal() == OsType.MACOS) {
|
||||
AppFontSizes.base(button);
|
||||
} else if (OsType.getLocal() == OsType.LINUX) {
|
||||
AppFontSizes.xl(button);
|
||||
} else {
|
||||
AppFontSizes.apply(button, sizes -> {
|
||||
if (sizes.getBase().equals("10.5")) {
|
||||
return sizes.getXl();
|
||||
} else {
|
||||
return sizes.getLg();
|
||||
}
|
||||
});
|
||||
}
|
||||
return loading.createRegion();
|
||||
}
|
||||
|
||||
@@ -175,7 +184,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
button.styleClass("user-icon");
|
||||
button.tooltipKey("personalConnection");
|
||||
button.apply(struc -> {
|
||||
AppFont.medium(struc.get());
|
||||
AppFontSizes.base(struc.get());
|
||||
struc.get().setOpacity(1.0);
|
||||
});
|
||||
button.hide(Bindings.not(getWrapper().getPerUser()));
|
||||
@@ -207,7 +216,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
update.run();
|
||||
ig.setAlignment(Pos.CENTER_RIGHT);
|
||||
ig.getStyleClass().add("button-bar");
|
||||
AppFont.medium(ig);
|
||||
AppFontSizes.base(ig);
|
||||
return ig;
|
||||
}
|
||||
|
||||
@@ -266,8 +275,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
}
|
||||
|
||||
protected ContextMenu createContextMenu() {
|
||||
var contextMenu = new ContextMenu();
|
||||
AppFont.normal(contextMenu.getStyleableNode());
|
||||
var contextMenu = ContextMenuHelper.create();
|
||||
|
||||
var hasSep = false;
|
||||
for (var p : getWrapper().getActionProviders()) {
|
||||
@@ -473,7 +481,7 @@ public abstract class StoreEntryComp extends SimpleComp {
|
||||
});
|
||||
menu.getItems().add(sc);
|
||||
|
||||
if (XPipeDistributionType.get().isSupportsUrls()) {
|
||||
if (AppDistributionType.get().isSupportsUrls()) {
|
||||
var l = new MenuItem(null, new FontIcon("mdi2c-clipboard-list-outline"));
|
||||
l.textProperty().bind(AppI18n.observable("base.copyShareLink"));
|
||||
l.setOnAction(event -> {
|
||||
|
||||
@@ -5,7 +5,7 @@ import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.CountComp;
|
||||
import io.xpipe.app.comp.base.FilterComp;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
@@ -28,6 +28,8 @@ import javafx.scene.text.TextAlignment;
|
||||
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
|
||||
private final Property<StoreSortMode> sortMode;
|
||||
@@ -76,7 +78,8 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
return inRootCategory;
|
||||
},
|
||||
StoreViewState.get().getActiveCategory());
|
||||
var count = new CountComp<>(all.getList(), all.getList());
|
||||
var allCount = Bindings.size(all.getList());
|
||||
var count = new CountComp(allCount, allCount, Function.identity());
|
||||
|
||||
var c = count.createRegion();
|
||||
var topBar = new HBox(
|
||||
@@ -86,8 +89,13 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
createDateSortButton().createRegion(),
|
||||
Comp.hspacer(2).createRegion(),
|
||||
createAlphabeticalSortButton().createRegion());
|
||||
AppFont.setSize(label, 2);
|
||||
AppFont.setSize(c, 2);
|
||||
if (OsType.getLocal() == OsType.MACOS) {
|
||||
AppFontSizes.xxxl(label);
|
||||
AppFontSizes.xxxl(c);
|
||||
} else {
|
||||
AppFontSizes.xxl(label);
|
||||
AppFontSizes.xxl(c);
|
||||
}
|
||||
topBar.setAlignment(Pos.CENTER);
|
||||
topBar.getStyleClass().add("top");
|
||||
return topBar;
|
||||
@@ -112,7 +120,6 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
HBox.setHgrow(f, Priority.ALWAYS);
|
||||
|
||||
f.getStyleClass().add("filter-bar");
|
||||
AppFont.medium(hbox);
|
||||
return hbox;
|
||||
}
|
||||
|
||||
@@ -121,7 +128,6 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
menu.textProperty().bind(AppI18n.observable("addConnections"));
|
||||
menu.setAlignment(Pos.CENTER);
|
||||
menu.setTextAlignment(TextAlignment.CENTER);
|
||||
AppFont.medium(menu);
|
||||
StoreCreationMenu.addButtons(menu);
|
||||
menu.setOpacity(0.85);
|
||||
menu.setMinWidth(Region.USE_PREF_SIZE);
|
||||
@@ -129,7 +135,7 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
menu.setPadding(new Insets(-2, 0, -2, 0));
|
||||
} else {
|
||||
menu.setPadding(new Insets(-4, 0, -4, 0));
|
||||
menu.setPadding(new Insets(-5, -2, -5, -2));
|
||||
}
|
||||
|
||||
return menu;
|
||||
@@ -157,7 +163,6 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
}
|
||||
});
|
||||
alphabetical.apply(alphabeticalR -> {
|
||||
AppFont.medium(alphabeticalR.get());
|
||||
alphabeticalR
|
||||
.get()
|
||||
.opacityProperty()
|
||||
@@ -198,7 +203,6 @@ public class StoreEntryListOverviewComp extends SimpleComp {
|
||||
}
|
||||
});
|
||||
date.apply(dateR -> {
|
||||
AppFont.medium(dateR.get());
|
||||
dateR.get()
|
||||
.opacityProperty()
|
||||
.bind(Bindings.createDoubleBinding(
|
||||
|
||||
@@ -15,7 +15,7 @@ import io.xpipe.core.store.SingletonSessionStore;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.beans.value.ObservableStringValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
|
||||
import lombok.Getter;
|
||||
@@ -53,6 +53,10 @@ public class StoreEntryWrapper {
|
||||
private final Property<DataStore> store = new SimpleObjectProperty<>();
|
||||
private final Property<String> information = new SimpleStringProperty();
|
||||
private final BooleanProperty perUser = new SimpleBooleanProperty();
|
||||
private final ObservableValue<String> shownName;
|
||||
private final ObservableValue<String> shownSummary;
|
||||
private final ObservableValue<String> shownInformation;
|
||||
private final BooleanProperty largeCategoryOptimizations = new SimpleBooleanProperty();
|
||||
|
||||
private boolean effectiveBusyProviderBound = false;
|
||||
private final BooleanProperty effectiveBusy = new SimpleBooleanProperty();
|
||||
@@ -61,6 +65,27 @@ public class StoreEntryWrapper {
|
||||
this.entry = entry;
|
||||
this.name = new SimpleStringProperty(entry.getName());
|
||||
this.lastAccess = new SimpleObjectProperty<>(entry.getLastAccess().minus(Duration.ofMillis(500)));
|
||||
this.shownName = Bindings.createStringBinding(
|
||||
() -> {
|
||||
var n = name.getValue();
|
||||
return n != null && AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||
},
|
||||
AppPrefs.get().censorMode(),
|
||||
name);
|
||||
this.shownSummary = Bindings.createStringBinding(
|
||||
() -> {
|
||||
var n = summary.getValue();
|
||||
return n != null && AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||
},
|
||||
AppPrefs.get().censorMode(),
|
||||
summary);
|
||||
this.shownInformation = Bindings.createStringBinding(
|
||||
() -> {
|
||||
var n = information.getValue();
|
||||
return n != null && AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||
},
|
||||
AppPrefs.get().censorMode(),
|
||||
information);
|
||||
ActionProvider.ALL_STANDALONE.stream()
|
||||
.filter(dataStoreActionProvider -> {
|
||||
return !entry.isDisabled()
|
||||
@@ -155,34 +180,27 @@ public class StoreEntryWrapper {
|
||||
this.effectiveBusy.bind(busy);
|
||||
}
|
||||
|
||||
var storeChanged = store.getValue() != entry.getStore();
|
||||
store.setValue(entry.getStore());
|
||||
if (storeChanged || !information.isBound()) {
|
||||
if (entry.getProvider() != null) {
|
||||
var section = StoreViewState.get().getSectionForWrapper(this);
|
||||
if (section.isPresent()) {
|
||||
information.unbind();
|
||||
try {
|
||||
var binding = PlatformThread.sync(entry.getProvider().informationString(section.get()));
|
||||
information.bind(binding);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
information.bind(new SimpleStringProperty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastAccess.setValue(entry.getLastAccess());
|
||||
disabled.setValue(entry.isDisabled());
|
||||
validity.setValue(entry.getValidity());
|
||||
expanded.setValue(entry.isExpanded());
|
||||
persistentState.setValue(entry.getStorePersistentState());
|
||||
|
||||
// The property values are only registered as changed once they are queried
|
||||
// If we use information bindings that depend on some of these properties
|
||||
// but use the store methods to retrieve data instead of the wrapper properties,
|
||||
// the bindings do not get updated as the change events are not fired.
|
||||
// We can also fire them manually with this
|
||||
persistentState.getValue();
|
||||
|
||||
// Use map copy to recognize update
|
||||
// This is a synchronized map, so we synchronize the access
|
||||
synchronized (entry.getStoreCache()) {
|
||||
cache.setValue(new HashMap<>(entry.getStoreCache()));
|
||||
if (!entry.getStoreCache().equals(cache.getValue())) {
|
||||
cache.setValue(new HashMap<>(entry.getStoreCache()));
|
||||
// Same here
|
||||
cache.getValue();
|
||||
}
|
||||
}
|
||||
color.setValue(entry.getColor());
|
||||
notes.setValue(new StoreNotes(entry.getNotes(), entry.getNotes()));
|
||||
@@ -198,10 +216,30 @@ public class StoreEntryWrapper {
|
||||
storeCategoryWrapper.getCategory().getUuid().equals(entry.getCategoryUuid()))
|
||||
.findFirst()
|
||||
.orElse(StoreViewState.get().getAllConnectionsCategory()));
|
||||
largeCategoryOptimizations.setValue(
|
||||
category.getValue().getLargeCategoryOptimizations().getValue());
|
||||
perUser.setValue(
|
||||
!category.getValue().getRoot().equals(StoreViewState.get().getAllIdentitiesCategory())
|
||||
&& entry.isPerUserStore());
|
||||
|
||||
var storeChanged = store.getValue() != entry.getStore();
|
||||
store.setValue(entry.getStore());
|
||||
if (storeChanged || !information.isBound()) {
|
||||
if (entry.getProvider() != null) {
|
||||
var section = StoreViewState.get().getSectionForWrapper(this);
|
||||
if (section.isPresent()) {
|
||||
information.unbind();
|
||||
try {
|
||||
var is = entry.getProvider().informationString(section.get());
|
||||
information.bind(is);
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
information.bind(new SimpleStringProperty());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!entry.getValidity().isUsable()) {
|
||||
summary.setValue(null);
|
||||
} else {
|
||||
@@ -323,7 +361,7 @@ public class StoreEntryWrapper {
|
||||
}
|
||||
|
||||
public boolean matchesFilter(String filter) {
|
||||
if (filter == null || nameProperty().getValue().toLowerCase().contains(filter.toLowerCase())) {
|
||||
if (filter == null || name.getValue().toLowerCase().contains(filter.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -339,38 +377,4 @@ public class StoreEntryWrapper {
|
||||
public Property<String> nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public BooleanProperty disabledProperty() {
|
||||
return disabled;
|
||||
}
|
||||
|
||||
public ObservableStringValue getShownName() {
|
||||
return Bindings.createStringBinding(
|
||||
() -> {
|
||||
var n = nameProperty().getValue();
|
||||
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||
},
|
||||
AppPrefs.get().censorMode(),
|
||||
nameProperty());
|
||||
}
|
||||
|
||||
public ObservableStringValue getShownSummary() {
|
||||
return Bindings.createStringBinding(
|
||||
() -> {
|
||||
var n = summary.getValue();
|
||||
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||
},
|
||||
AppPrefs.get().censorMode(),
|
||||
summary);
|
||||
}
|
||||
|
||||
public ObservableStringValue getShownInformation() {
|
||||
return Bindings.createStringBinding(
|
||||
() -> {
|
||||
var n = information.getValue();
|
||||
return AppPrefs.get().censorMode().get() ? "*".repeat(n.length()) : n;
|
||||
},
|
||||
AppPrefs.get().censorMode(),
|
||||
information);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,39 +2,46 @@ package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.resources.SystemIcon;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.icon.SystemIcon;
|
||||
import io.xpipe.app.icon.SystemIconCache;
|
||||
import io.xpipe.app.icon.SystemIconManager;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
import atlantafx.base.theme.Tweaks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.*;
|
||||
|
||||
import static atlantafx.base.theme.Styles.TEXT_SMALL;
|
||||
|
||||
public class StoreIconChoiceComp extends SimpleComp {
|
||||
|
||||
private final Runnable reshow;
|
||||
private final Property<SystemIcon> selected;
|
||||
private final List<SystemIcon> icons;
|
||||
private final Set<SystemIcon> icons;
|
||||
private final int columns;
|
||||
private final SimpleStringProperty filter;
|
||||
private final Runnable doubleClick;
|
||||
|
||||
public StoreIconChoiceComp(
|
||||
Runnable reshow,
|
||||
Property<SystemIcon> selected,
|
||||
List<SystemIcon> icons,
|
||||
Set<SystemIcon> icons,
|
||||
int columns,
|
||||
SimpleStringProperty filter,
|
||||
Runnable doubleClick) {
|
||||
this.reshow = reshow;
|
||||
this.selected = selected;
|
||||
this.icons = icons;
|
||||
this.columns = columns;
|
||||
@@ -52,30 +59,59 @@ public class StoreIconChoiceComp extends SimpleComp {
|
||||
}
|
||||
|
||||
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);
|
||||
if (SystemIconCache.isBuilt()) {
|
||||
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");
|
||||
table.setPlaceholder(new Region());
|
||||
|
||||
var busy = new SimpleBooleanProperty(false);
|
||||
var refreshButton = new ButtonComp(
|
||||
AppI18n.observable("refreshIcons"),
|
||||
new SimpleObjectProperty<>(new LabelGraphic.IconGraphic("mdi2r-refresh")),
|
||||
() -> {
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
try (var ignored = new BooleanScope(busy).start()) {
|
||||
SystemIconManager.reload();
|
||||
}
|
||||
reshow.run();
|
||||
});
|
||||
});
|
||||
refreshButton.disable(busy);
|
||||
var text = new LabelComp(AppI18n.observable("refreshIconsDescription"));
|
||||
text.apply(struc -> {
|
||||
struc.get().setWrapText(true);
|
||||
struc.get().setTextAlignment(TextAlignment.CENTER);
|
||||
struc.get().setPrefWidth(300);
|
||||
});
|
||||
text.styleClass(Styles.TEXT_SUBTLE);
|
||||
text.visible(busy);
|
||||
var vbox = new VerticalComp(List.of(refreshButton, text)).spacing(25);
|
||||
vbox.apply(struc -> struc.get().setAlignment(Pos.CENTER));
|
||||
var placeholder = new StackPane(vbox.createRegion());
|
||||
table.setPlaceholder(placeholder);
|
||||
}
|
||||
|
||||
private void updateData(TableView<List<SystemIcon>> table, String filterString) {
|
||||
var displayedIcons = filterString == null || filterString.isBlank() || filterString.length() < 2
|
||||
? icons
|
||||
? icons.stream()
|
||||
.sorted(Comparator.comparing(systemIcon -> systemIcon.getId()))
|
||||
.toList()
|
||||
: icons.stream()
|
||||
.filter(icon -> containsString(icon.getDisplayName(), filterString))
|
||||
.filter(icon -> containsString(icon.getId(), filterString))
|
||||
.toList();
|
||||
|
||||
var data = partitionList(displayedIcons, columns);
|
||||
@@ -137,8 +173,8 @@ public class StoreIconChoiceComp extends SimpleComp {
|
||||
return;
|
||||
}
|
||||
|
||||
root.setText(icon.getDisplayName());
|
||||
image.set("app:system/" + icon.getIconName() + ".svg");
|
||||
root.setText(icon.getId());
|
||||
image.set(SystemIconManager.getIconFile(icon));
|
||||
setGraphic(root);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.base.*;
|
||||
import io.xpipe.app.resources.SystemIcon;
|
||||
import io.xpipe.app.resources.SystemIcons;
|
||||
import io.xpipe.app.icon.SystemIcon;
|
||||
import io.xpipe.app.icon.SystemIconManager;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStoreEntry;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
@@ -35,15 +36,28 @@ public class StoreIconChoiceDialog {
|
||||
var filterText = new SimpleStringProperty();
|
||||
var filter = new FilterComp(filterText).grow(true, false);
|
||||
filter.focusOnShow();
|
||||
var github = new ButtonComp(null, new FontIcon("mdi2g-github"), () -> {
|
||||
Hyperlinks.open(Hyperlinks.SELFHST_ICONS);
|
||||
var github = new ButtonComp(null, new FontIcon("mdomz-settings"), () -> {
|
||||
overlay.close();
|
||||
AppPrefs.get().selectCategory("icons");
|
||||
})
|
||||
.grow(false, true);
|
||||
var modal = ModalOverlay.of(
|
||||
"chooseCustomIcon",
|
||||
new StoreIconChoiceComp(selected, SystemIcons.getSystemIcons(), 5, filterText, () -> {
|
||||
finish();
|
||||
})
|
||||
new StoreIconChoiceComp(
|
||||
() -> {
|
||||
var showing = overlay.isShowing();
|
||||
overlay.close();
|
||||
if (showing) {
|
||||
Platform.runLater(() -> overlay.show());
|
||||
}
|
||||
},
|
||||
selected,
|
||||
SystemIconManager.getIcons(),
|
||||
5,
|
||||
filterText,
|
||||
() -> {
|
||||
finish();
|
||||
})
|
||||
.prefWidth(600));
|
||||
modal.addButtonBarComp(github);
|
||||
modal.addButtonBarComp(filter);
|
||||
@@ -63,7 +77,12 @@ public class StoreIconChoiceDialog {
|
||||
}
|
||||
|
||||
private void finish() {
|
||||
entry.setIcon(selected.get() != null ? selected.getValue().getIconName() : null, true);
|
||||
entry.setIcon(
|
||||
selected.get() != null
|
||||
? selected.getValue().getSource().getId() + "/"
|
||||
+ selected.getValue().getId()
|
||||
: null,
|
||||
true);
|
||||
overlay.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,10 @@ public class StoreIconComp extends SimpleComp {
|
||||
dots.setIconSize((int) (h * 1.3));
|
||||
|
||||
var stack = new StackPane(background, storeIcon, dots);
|
||||
stack.setMinHeight(w + 7);
|
||||
stack.setMinWidth(w + 7);
|
||||
stack.setMaxHeight(w + 7);
|
||||
stack.setMaxWidth(w + 7);
|
||||
stack.setMinHeight(w + 5);
|
||||
stack.setMinWidth(w + 5);
|
||||
stack.setMaxHeight(w + 5);
|
||||
stack.setMaxWidth(w + 5);
|
||||
stack.getStyleClass().add("icon");
|
||||
stack.setAlignment(Pos.CENTER);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.ext.DataStoreCreationCategory;
|
||||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
@@ -29,7 +29,7 @@ public class StoreIdentitiesIntroComp extends SimpleComp {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
AppFont.setSize(title, 7);
|
||||
AppFontSizes.title(title);
|
||||
|
||||
var introDesc = new Label();
|
||||
introDesc.textProperty().bind(AppI18n.observable("identitiesIntroText"));
|
||||
@@ -50,8 +50,8 @@ public class StoreIdentitiesIntroComp extends SimpleComp {
|
||||
addButton.setOnAction(event -> {
|
||||
var canSync = DataStorage.get().supportsSharing();
|
||||
var prov = canSync
|
||||
? DataStoreProviders.byName("syncedIdentity").orElseThrow()
|
||||
: DataStoreProviders.byName("localIdentity").orElseThrow();
|
||||
? DataStoreProviders.byId("syncedIdentity").orElseThrow()
|
||||
: DataStoreProviders.byId("localIdentity").orElseThrow();
|
||||
StoreCreationComp.showCreation(prov, DataStoreCreationCategory.IDENTITY);
|
||||
event.consume();
|
||||
});
|
||||
@@ -76,7 +76,7 @@ public class StoreIdentitiesIntroComp extends SimpleComp {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
AppFont.setSize(title, 7);
|
||||
AppFontSizes.title(title);
|
||||
|
||||
var importDesc = new Label();
|
||||
importDesc.textProperty().bind(AppI18n.observable("identitiesIntroBottomText"));
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
@@ -29,7 +29,7 @@ public class StoreIntroComp extends SimpleComp {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
AppFont.setSize(title, 7);
|
||||
AppFontSizes.title(title);
|
||||
|
||||
var introDesc = new Label();
|
||||
introDesc.textProperty().bind(AppI18n.observable("storeIntroDescription"));
|
||||
@@ -69,7 +69,7 @@ public class StoreIntroComp extends SimpleComp {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
AppFont.setSize(title, 7);
|
||||
AppFontSizes.title(title);
|
||||
|
||||
var importDesc = new Label();
|
||||
importDesc.textProperty().bind(AppI18n.observable("storeIntroImportDescription"));
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.DialogComp;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.comp.base.MarkdownEditorComp;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
@@ -35,7 +35,7 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
|
||||
public Structure createBase() {
|
||||
var n = wrapper.getNotes();
|
||||
var button = new IconButtonComp("mdi2n-note-text")
|
||||
.apply(struc -> AppFont.small(struc.get()))
|
||||
.apply(struc -> AppFontSizes.xs(struc.get()))
|
||||
.focusTraversableForAccessibility()
|
||||
.tooltipKey("notes")
|
||||
.styleClass("notes-button")
|
||||
@@ -139,7 +139,7 @@ public class StoreNotesComp extends Comp<StoreNotesComp.Structure> {
|
||||
ref.set(null);
|
||||
}
|
||||
});
|
||||
AppFont.small(popover.getContentNode());
|
||||
AppFontSizes.xs(popover.getContentNode());
|
||||
|
||||
md.getEditButton().addEventFilter(ActionEvent.ANY, event -> {
|
||||
if (!popover.isDetached()) {
|
||||
|
||||
@@ -2,7 +2,7 @@ package io.xpipe.app.comp.store;
|
||||
|
||||
import io.xpipe.app.comp.SimpleComp;
|
||||
import io.xpipe.app.core.AppCache;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class StoreScriptsIntroComp extends SimpleComp {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
AppFont.setSize(title, 7);
|
||||
AppFontSizes.title(title);
|
||||
|
||||
var introDesc = new Label();
|
||||
introDesc.textProperty().bind(AppI18n.observable("scriptsIntroText"));
|
||||
@@ -66,7 +66,7 @@ public class StoreScriptsIntroComp extends SimpleComp {
|
||||
if (OsType.getLocal() != OsType.MACOS) {
|
||||
title.getStyleClass().add(Styles.TEXT_BOLD);
|
||||
}
|
||||
AppFont.setSize(title, 7);
|
||||
AppFontSizes.title(title);
|
||||
|
||||
var importDesc = new Label();
|
||||
importDesc.textProperty().bind(AppI18n.observable("scriptsIntroBottomText"));
|
||||
|
||||
@@ -14,7 +14,7 @@ import javafx.beans.value.ObservableIntegerValue;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
|
||||
import lombok.Value;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
@@ -22,14 +22,14 @@ import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
@Value
|
||||
@Getter
|
||||
public class StoreSection {
|
||||
|
||||
StoreEntryWrapper wrapper;
|
||||
DerivedObservableList<StoreSection> allChildren;
|
||||
DerivedObservableList<StoreSection> shownChildren;
|
||||
int depth;
|
||||
ObservableBooleanValue showDetails;
|
||||
private final StoreEntryWrapper wrapper;
|
||||
private final DerivedObservableList<StoreSection> allChildren;
|
||||
private final DerivedObservableList<StoreSection> shownChildren;
|
||||
private final int depth;
|
||||
private final ObservableBooleanValue showDetails;
|
||||
|
||||
public StoreSection(
|
||||
StoreEntryWrapper wrapper,
|
||||
@@ -85,9 +85,8 @@ public class StoreSection {
|
||||
}
|
||||
});
|
||||
var comp = explicitOrderComp;
|
||||
var mappedSortMode = BindingsHelper.flatMap(
|
||||
category,
|
||||
storeCategoryWrapper -> storeCategoryWrapper.getSortMode());
|
||||
var mappedSortMode =
|
||||
BindingsHelper.flatMap(category, storeCategoryWrapper -> storeCategoryWrapper.getSortMode());
|
||||
return list.sorted(
|
||||
(o1, o2) -> {
|
||||
var r = comp.compare(o1, o2);
|
||||
@@ -114,7 +113,8 @@ public class StoreSection {
|
||||
ObservableIntegerValue updateObservable) {
|
||||
var topLevel = all.filtered(
|
||||
section -> {
|
||||
return DataStorage.get().isRootEntry(section.getEntry(), category.getValue().getCategory());
|
||||
return DataStorage.get()
|
||||
.isRootEntry(section.getEntry(), category.getValue().getCategory());
|
||||
},
|
||||
category,
|
||||
updateObservable);
|
||||
@@ -163,7 +163,8 @@ public class StoreSection {
|
||||
// .orElse(false);
|
||||
|
||||
// is children. This check is fast as the children are cached in the storage
|
||||
if (!DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry())) {
|
||||
if (DataStorage.get() == null
|
||||
|| !DataStorage.get().getStoreChildren(e.getEntry()).contains(other.getEntry())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -200,7 +201,10 @@ public class StoreSection {
|
||||
// If this entry is already shown as root due to a different category than parent, don't
|
||||
// show it
|
||||
// again here
|
||||
!DataStorage.get().isRootEntry(section.getWrapper().getEntry(), category.getValue().getCategory());
|
||||
!DataStorage.get()
|
||||
.isRootEntry(
|
||||
section.getWrapper().getEntry(),
|
||||
category.getValue().getCategory());
|
||||
},
|
||||
category,
|
||||
filterString,
|
||||
|
||||
@@ -56,7 +56,7 @@ public class StoreViewState {
|
||||
INSTANCE.updateContent();
|
||||
INSTANCE.initSections();
|
||||
INSTANCE.updateContent();
|
||||
INSTANCE.initFilterJump();
|
||||
INSTANCE.initFilterListener();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
@@ -96,9 +96,10 @@ public class StoreViewState {
|
||||
}
|
||||
}
|
||||
|
||||
private void initFilterJump() {
|
||||
private void initFilterListener() {
|
||||
var all = getAllConnectionsCategory();
|
||||
filter.addListener((observable, oldValue, newValue) -> {
|
||||
categories.getList().forEach(e -> e.update());
|
||||
var matchingCats = categories.getList().stream()
|
||||
.filter(storeCategoryWrapper ->
|
||||
storeCategoryWrapper.getRoot().equals(all))
|
||||
@@ -175,12 +176,13 @@ public class StoreViewState {
|
||||
|
||||
@Override
|
||||
public void onStoreAdd(DataStoreEntry... entry) {
|
||||
var l = Arrays.stream(entry)
|
||||
.map(StoreEntryWrapper::new)
|
||||
.peek(storeEntryWrapper -> storeEntryWrapper.update())
|
||||
.peek(wrapper -> wrapper.applyLastAccess())
|
||||
.toList();
|
||||
Platform.runLater(() -> {
|
||||
var l = Arrays.stream(entry)
|
||||
.map(StoreEntryWrapper::new)
|
||||
.peek(storeEntryWrapper -> storeEntryWrapper.update())
|
||||
.peek(wrapper -> wrapper.applyLastAccess())
|
||||
.toList();
|
||||
|
||||
// Don't update anything if we have already reset
|
||||
if (INSTANCE == null) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.update.GitHubUpdater;
|
||||
import io.xpipe.app.update.PortableUpdater;
|
||||
import io.xpipe.app.update.UpdateHandler;
|
||||
import io.xpipe.app.util.LocalExec;
|
||||
import io.xpipe.app.util.Translatable;
|
||||
import io.xpipe.core.process.OsType;
|
||||
import io.xpipe.core.util.ModuleHelper;
|
||||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public enum AppDistributionType implements Translatable {
|
||||
UNKNOWN("unknown", false, () -> new GitHubUpdater(false)),
|
||||
DEVELOPMENT("development", true, () -> new GitHubUpdater(false)),
|
||||
PORTABLE("portable", false, () -> new PortableUpdater(true)),
|
||||
NATIVE_INSTALLATION("install", true, () -> new GitHubUpdater(true)),
|
||||
HOMEBREW("homebrew", true, () -> new PortableUpdater(true)),
|
||||
APT_REPO("apt", true, () -> new PortableUpdater(true)),
|
||||
RPM_REPO("rpm", true, () -> new PortableUpdater(true)),
|
||||
WEBTOP("webtop", true, () -> new PortableUpdater(false)),
|
||||
CHOCO("choco", true, () -> new PortableUpdater(true));
|
||||
|
||||
private static AppDistributionType type;
|
||||
|
||||
@Getter
|
||||
private final String id;
|
||||
|
||||
@Getter
|
||||
private final boolean supportsUrls;
|
||||
|
||||
private final Supplier<UpdateHandler> updateHandlerSupplier;
|
||||
private UpdateHandler updateHandler;
|
||||
|
||||
AppDistributionType(String id, boolean supportsUrls, Supplier<UpdateHandler> updateHandlerSupplier) {
|
||||
this.id = id;
|
||||
this.supportsUrls = supportsUrls;
|
||||
this.updateHandlerSupplier = updateHandlerSupplier;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (type != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ModuleHelper.isImage()) {
|
||||
type = DEVELOPMENT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!AppProperties.get().isNewBuildSession() && !isDifferentDaemonExecutable()) {
|
||||
var cached = AppCache.getNonNull("dist", String.class, () -> null);
|
||||
var cachedType = Arrays.stream(values())
|
||||
.filter(xPipeDistributionType ->
|
||||
xPipeDistributionType.getId().equals(cached))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
if (cachedType != null) {
|
||||
type = cachedType;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var det = determine();
|
||||
|
||||
// Don't cache unknown type
|
||||
if (det == UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
type = det;
|
||||
AppCache.update("dist", type.getId());
|
||||
TrackEvent.withInfo("Determined distribution type")
|
||||
.tag("type", type.getId())
|
||||
.handle();
|
||||
}
|
||||
|
||||
private static boolean isDifferentDaemonExecutable() {
|
||||
var cached = AppCache.getNonNull("daemonExecutable", String.class, () -> null);
|
||||
var current = XPipeInstallation.getCurrentInstallationBasePath()
|
||||
.resolve(XPipeInstallation.getDaemonExecutablePath(OsType.getLocal()))
|
||||
.toString();
|
||||
if (current.equals(cached)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AppCache.update("daemonExecutable", current);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static AppDistributionType get() {
|
||||
if (type == null) {
|
||||
TrackEvent.withWarn("Distribution type requested before init").handle();
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public static AppDistributionType determine() {
|
||||
var base = XPipeInstallation.getCurrentInstallationBasePath();
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
if (!base.toString().equals(XPipeInstallation.getLocalDefaultInstallationBasePath())) {
|
||||
return PORTABLE;
|
||||
}
|
||||
|
||||
try {
|
||||
var r = LocalExec.readStdoutIfPossible(
|
||||
"pkgutil",
|
||||
"--pkg-info",
|
||||
AppProperties.get().isStaging() ? "io.xpipe.xpipe-ptb" : "io.xpipe.xpipe");
|
||||
if (r.isEmpty()) {
|
||||
return PORTABLE;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEvent.fromThrowable(ex).omit().handle();
|
||||
return PORTABLE;
|
||||
}
|
||||
} else {
|
||||
var file = base.resolve("installation");
|
||||
if (!Files.exists(file)) {
|
||||
return PORTABLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (OsType.getLocal() == OsType.LINUX && Files.isDirectory(Path.of("/kclient"))) {
|
||||
return WEBTOP;
|
||||
}
|
||||
|
||||
if (OsType.getLocal().equals(OsType.WINDOWS)) {
|
||||
var out = LocalExec.readStdoutIfPossible("choco", "list", "xpipe");
|
||||
if (out.isPresent()) {
|
||||
if (out.get().contains("xpipe")) {
|
||||
return CHOCO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In theory, we can also add && !AppProperties.get().isStaging() here, but we want to replicate the
|
||||
// production behavior
|
||||
if (OsType.getLocal().equals(OsType.MACOS)) {
|
||||
var out = LocalExec.readStdoutIfPossible("/opt/homebrew/bin/brew", "list", "--casks", "--versions");
|
||||
if (out.isPresent()) {
|
||||
if (out.get().lines().anyMatch(s -> {
|
||||
var split = s.split(" ");
|
||||
return split.length == 2
|
||||
&& split[0].equals("xpipe")
|
||||
&& split[1].equals(AppProperties.get().getVersion());
|
||||
})) {
|
||||
return HOMEBREW;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (OsType.getLocal() == OsType.LINUX) {
|
||||
if (base.startsWith("/opt")) {
|
||||
var aptOut = LocalExec.readStdoutIfPossible("apt", "show", "xpipe");
|
||||
if (aptOut.isPresent()) {
|
||||
var fromRepo = aptOut.get().lines().anyMatch(s -> {
|
||||
return s.contains("APT-Sources") && s.contains("apt.xpipe.io");
|
||||
});
|
||||
if (fromRepo) {
|
||||
return APT_REPO;
|
||||
}
|
||||
}
|
||||
|
||||
var yumRepo = LocalExec.readStdoutIfPossible("test", "-f", "/etc/yum.repos.d/xpipe.repo");
|
||||
if (yumRepo.isPresent()) {
|
||||
return RPM_REPO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return AppDistributionType.NATIVE_INSTALLATION;
|
||||
}
|
||||
|
||||
public UpdateHandler getUpdateHandler() {
|
||||
if (updateHandler == null) {
|
||||
updateHandler = updateHandlerSupplier.get();
|
||||
}
|
||||
return updateHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObservableValue<String> toTranslatedString() {
|
||||
return AppI18n.observable(getId() + "Dist");
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import java.util.stream.Stream;
|
||||
public class AppExtensionManager {
|
||||
|
||||
private static AppExtensionManager INSTANCE;
|
||||
private final boolean loadedProviders;
|
||||
private final List<Extension> loadedExtensions = new ArrayList<>();
|
||||
private final List<ModuleLayer> leafModuleLayers = new ArrayList<>();
|
||||
private final List<Path> extensionBaseDirectories = new ArrayList<>();
|
||||
@@ -35,29 +34,22 @@ public class AppExtensionManager {
|
||||
@Getter
|
||||
private ModuleLayer extendedLayer;
|
||||
|
||||
public AppExtensionManager(boolean loadedProviders) {
|
||||
this.loadedProviders = loadedProviders;
|
||||
}
|
||||
|
||||
public static void init(boolean loadProviders) throws Exception {
|
||||
var load = INSTANCE == null || !INSTANCE.loadedProviders && loadProviders;
|
||||
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new AppExtensionManager(loadProviders);
|
||||
INSTANCE.determineExtensionDirectories();
|
||||
INSTANCE.loadBaseExtension();
|
||||
INSTANCE.loadAllExtensions();
|
||||
public static synchronized void init() throws Exception {
|
||||
if (INSTANCE != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (load) {
|
||||
try {
|
||||
ProcessControlProvider.init(INSTANCE.extendedLayer);
|
||||
ModuleLayerLoader.loadAll(INSTANCE.extendedLayer, t -> {
|
||||
ErrorEvent.fromThrowable(t).handle();
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
throw ExtensionException.corrupt("Service provider initialization failed", t);
|
||||
}
|
||||
INSTANCE = new AppExtensionManager();
|
||||
INSTANCE.determineExtensionDirectories();
|
||||
INSTANCE.loadBaseExtension();
|
||||
INSTANCE.loadAllExtensions();
|
||||
try {
|
||||
ProcessControlProvider.init(INSTANCE.extendedLayer);
|
||||
ModuleLayerLoader.loadAll(INSTANCE.extendedLayer, t -> {
|
||||
ErrorEvent.fromThrowable(t).handle();
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
throw ExtensionException.corrupt("Service provider initialization failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
@@ -17,41 +16,14 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||
|
||||
public class AppFont {
|
||||
|
||||
public static void header(Node node) {
|
||||
setSize(node, +1);
|
||||
}
|
||||
|
||||
public static void normal(Node node) {
|
||||
setSize(node, 0);
|
||||
}
|
||||
|
||||
public static void medium(Node node) {
|
||||
setSize(node, -1);
|
||||
}
|
||||
|
||||
public static void small(Node node) {
|
||||
setSize(node, -2);
|
||||
}
|
||||
|
||||
public static void verySmall(Node node) {
|
||||
setSize(node, -3);
|
||||
}
|
||||
|
||||
public static void setSize(Node node, int off) {
|
||||
if (node.getStyle().contains("-fx-font-size: ")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Somehow the font is bigger on Linux
|
||||
var baseSize = OsType.getLocal() == OsType.LINUX ? 11 : 12;
|
||||
node.setStyle(node.getStyle() + "-fx-font-size: " + (baseSize + off) + "pt;");
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
// Load ikonli fonts
|
||||
TrackEvent.info("Loading ikonli fonts ...");
|
||||
new FontIcon("mdi2s-stop");
|
||||
new FontIcon("mdi2m-magnify");
|
||||
new FontIcon("mdi2d-database-plus");
|
||||
new FontIcon("mdi2p-professional-hexagon");
|
||||
new FontIcon("mdi2c-chevron-double-right");
|
||||
|
||||
TrackEvent.info("Loading bundled fonts ...");
|
||||
AppResources.with(
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Value
|
||||
@AllArgsConstructor
|
||||
public class AppFontSizes {
|
||||
|
||||
private static final Pattern FONT_SIZE_PATTERN = Pattern.compile("-fx-font-size: \\d+(\\.\\d+)?pt;");
|
||||
|
||||
public static void xs(Node node) {
|
||||
apply(node, AppFontSizes::getXs);
|
||||
}
|
||||
|
||||
public static void sm(Node node) {
|
||||
apply(node, AppFontSizes::getSm);
|
||||
}
|
||||
|
||||
public static void base(Node node) {
|
||||
apply(node, AppFontSizes::getBase);
|
||||
}
|
||||
|
||||
public static void lg(Node node) {
|
||||
apply(node, AppFontSizes::getLg);
|
||||
}
|
||||
|
||||
public static void xl(Node node) {
|
||||
apply(node, AppFontSizes::getXl);
|
||||
}
|
||||
|
||||
public static void xxl(Node node) {
|
||||
apply(node, AppFontSizes::getXxl);
|
||||
}
|
||||
|
||||
public static void xxxl(Node node) {
|
||||
apply(node, AppFontSizes::getXxl);
|
||||
}
|
||||
|
||||
public static void title(Node node) {
|
||||
apply(node, AppFontSizes::getTitle);
|
||||
}
|
||||
|
||||
public static void apply(Node node, Function<AppFontSizes, String> function) {
|
||||
if (AppPrefs.get() == null) {
|
||||
setFont(node, function.apply(getDefault()));
|
||||
return;
|
||||
}
|
||||
|
||||
AppPrefs.get().theme().subscribe((newValue) -> {
|
||||
var effective = newValue != null ? newValue.getFontSizes() : getDefault();
|
||||
setFont(node, function.apply(effective));
|
||||
});
|
||||
}
|
||||
|
||||
private static void setFont(Node node, String fontSize) {
|
||||
var s = node.getStyle();
|
||||
var matcher = FONT_SIZE_PATTERN.matcher(s);
|
||||
s = matcher.replaceAll("");
|
||||
node.setStyle("-fx-font-size: " + fontSize + "pt;" + s);
|
||||
}
|
||||
|
||||
public static final AppFontSizes DEFAULT = getDefault();
|
||||
public static final AppFontSizes BASE_10 = ofBase("10");
|
||||
public static final AppFontSizes BASE_10_5 = ofBase("10.5");
|
||||
public static final AppFontSizes BASE_11 = ofBase("11");
|
||||
|
||||
public static AppFontSizes ofBase(String base) {
|
||||
if (base.contains(".")) {
|
||||
var l = Integer.parseInt(base.split("\\.")[0]);
|
||||
var r = "." + base.split("\\.")[1];
|
||||
return new AppFontSizes(
|
||||
(l - 1) + r, (l - 1) + "", base, (l + 1) + "", (l + 1) + r, (l + 2) + r, (l + 3) + r, (l + 7) + r);
|
||||
} else {
|
||||
var l = Integer.parseInt(base);
|
||||
return new AppFontSizes(
|
||||
(l - 1) + "", (l - 1) + ".5", l + "", l + ".5", l + 1 + "", l + 2 + "", l + 3 + "", l + 7 + "");
|
||||
}
|
||||
}
|
||||
|
||||
public static AppFontSizes forOs(AppFontSizes windows, AppFontSizes linux, AppFontSizes mac) {
|
||||
return switch (OsType.getLocal()) {
|
||||
case OsType.Linux linux1 -> linux;
|
||||
case OsType.MacOs macOs -> mac;
|
||||
case OsType.Windows windows1 -> windows;
|
||||
};
|
||||
}
|
||||
|
||||
public static AppFontSizes getDefault() {
|
||||
return forOs(AppFontSizes.BASE_10_5, AppFontSizes.BASE_10, AppFontSizes.BASE_11);
|
||||
}
|
||||
|
||||
// -1.0pt
|
||||
String xs;
|
||||
|
||||
// -0.5pt
|
||||
String sm;
|
||||
|
||||
// 0pt
|
||||
String base;
|
||||
|
||||
// +0.5pt
|
||||
String lg;
|
||||
|
||||
// +1.0pt
|
||||
String xl;
|
||||
|
||||
// +2.0pt
|
||||
String xxl;
|
||||
|
||||
// +3.0pt
|
||||
String xxxl;
|
||||
|
||||
// +7.0pt
|
||||
String title;
|
||||
}
|
||||
@@ -25,7 +25,6 @@ public class AppGreetingsDialog {
|
||||
tp.setExpanded(true);
|
||||
tp.setText(AppI18n.get("introduction"));
|
||||
tp.setAlignment(Pos.CENTER_LEFT);
|
||||
AppFont.normal(tp);
|
||||
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "misc/welcome.md", file -> {
|
||||
var md = Files.readString(file);
|
||||
@@ -41,7 +40,6 @@ public class AppGreetingsDialog {
|
||||
tp.setExpanded(false);
|
||||
tp.setText(AppI18n.get("eula"));
|
||||
tp.setAlignment(Pos.CENTER_LEFT);
|
||||
AppFont.normal(tp);
|
||||
|
||||
AppResources.with(AppResources.XPIPE_MODULE, "misc/eula.md", file -> {
|
||||
var md = Files.readString(file);
|
||||
@@ -84,7 +82,6 @@ public class AppGreetingsDialog {
|
||||
|
||||
var label = new Label(AppI18n.get("legalAccept"));
|
||||
label.setGraphic(cb);
|
||||
AppFont.medium(label);
|
||||
label.setPadding(new Insets(20, 0, 10, 0));
|
||||
label.setOnMouseClicked(event -> accepted.set(!accepted.get()));
|
||||
label.setGraphicTextGap(10);
|
||||
|
||||
@@ -4,36 +4,34 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.SupportedLocale;
|
||||
import io.xpipe.app.util.BindingsHelper;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import lombok.Value;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AppI18n {
|
||||
|
||||
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
|
||||
private static AppI18n INSTANCE;
|
||||
private final Property<LoadedTranslations> currentLanguage = new SimpleObjectProperty<>();
|
||||
private LoadedTranslations english;
|
||||
private final Property<AppI18nData> currentLanguage = new SimpleObjectProperty<>();
|
||||
private final ObservableValue<SupportedLocale> currentLocale = BindingsHelper.map(
|
||||
currentLanguage,
|
||||
appI18nData -> appI18nData != null ? appI18nData.getLocale() : SupportedLocale.getEnglish());
|
||||
private final Map<String, ObservableValue<String>> observableCache = new HashMap<>();
|
||||
private AppI18nData english;
|
||||
|
||||
public static ObservableValue<SupportedLocale> activeLanguage() {
|
||||
if (INSTANCE == null) {
|
||||
return new SimpleObjectProperty<>(SupportedLocale.getEnglish());
|
||||
}
|
||||
|
||||
return INSTANCE.currentLocale;
|
||||
}
|
||||
|
||||
public static void init() throws Exception {
|
||||
if (INSTANCE == null) {
|
||||
@@ -47,48 +45,55 @@ public class AppI18n {
|
||||
}
|
||||
|
||||
public static ObservableValue<String> observable(String s, Object... vars) {
|
||||
return INSTANCE.observableImpl(s, vars);
|
||||
}
|
||||
|
||||
private ObservableValue<String> observableImpl(String s, Object... vars) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = INSTANCE.getKey(s);
|
||||
return Bindings.createStringBinding(
|
||||
() -> {
|
||||
return get(key, vars);
|
||||
},
|
||||
INSTANCE.currentLanguage);
|
||||
synchronized (this) {
|
||||
var key = getKey(s);
|
||||
|
||||
// Don't cache vars
|
||||
if (vars.length > 0) {
|
||||
var binding = Bindings.createStringBinding(
|
||||
() -> {
|
||||
return getLocalised(key, vars);
|
||||
},
|
||||
currentLanguage);
|
||||
return binding;
|
||||
}
|
||||
|
||||
var found = observableCache.get(key);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
|
||||
var binding = Bindings.createStringBinding(
|
||||
() -> {
|
||||
return getLocalised(key, vars);
|
||||
},
|
||||
currentLanguage);
|
||||
observableCache.put(key, binding);
|
||||
return binding;
|
||||
}
|
||||
}
|
||||
|
||||
public static String get(String s, Object... vars) {
|
||||
return INSTANCE.getLocalised(s, vars);
|
||||
}
|
||||
|
||||
private static String getValue(String s, Object... vars) {
|
||||
Objects.requireNonNull(s);
|
||||
|
||||
s = s.replace("\\n", "\n");
|
||||
for (var v : vars) {
|
||||
v = v != null ? v : "null";
|
||||
var matcher = VAR_PATTERN.matcher(s);
|
||||
if (matcher.find()) {
|
||||
var group = matcher.group();
|
||||
s = s.replace(group, v.toString());
|
||||
} else {
|
||||
TrackEvent.warn("No match found for value " + v + " in string " + s);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private void load() throws Exception {
|
||||
if (english == null) {
|
||||
english = load(Locale.ENGLISH);
|
||||
english = AppI18nData.load(SupportedLocale.getEnglish());
|
||||
Locale.setDefault(Locale.ENGLISH);
|
||||
|
||||
// Load bundled JDK locale resources
|
||||
SupportedLocale.ALL.forEach(supportedLocale -> {
|
||||
supportedLocale.getLocale().getDisplayName();
|
||||
});
|
||||
// Load bundled JDK locale resources by initializing the classes
|
||||
for (var value : SupportedLocale.values()) {
|
||||
value.getLocale().getDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLanguage.getValue() == null && PlatformState.getCurrent() == PlatformState.RUNNING) {
|
||||
@@ -97,8 +102,11 @@ public class AppI18n {
|
||||
PlatformThread.runLaterIfNeededBlocking(() -> {
|
||||
AppPrefs.get().language().subscribe(n -> {
|
||||
try {
|
||||
currentLanguage.setValue(n != null ? load(n.getLocale()) : null);
|
||||
Locale.setDefault(n != null ? n.getLocale() : Locale.ENGLISH);
|
||||
var newValue = n != null ? AppI18nData.load(n) : null;
|
||||
PlatformThread.runLaterIfNeeded(() -> {
|
||||
currentLanguage.setValue(newValue);
|
||||
Locale.setDefault(n != null ? n.getLocale() : Locale.ENGLISH);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
ErrorEvent.fromThrowable(e).handle();
|
||||
}
|
||||
@@ -108,11 +116,7 @@ public class AppI18n {
|
||||
}
|
||||
}
|
||||
|
||||
public LoadedTranslations getLoaded() {
|
||||
return currentLanguage.getValue() != null ? currentLanguage.getValue() : english;
|
||||
}
|
||||
|
||||
public String getKey(String s) {
|
||||
private String getKey(String s) {
|
||||
var key = s;
|
||||
if (s.startsWith("app.")
|
||||
|| s.startsWith("base.")
|
||||
@@ -124,35 +128,30 @@ public class AppI18n {
|
||||
return key;
|
||||
}
|
||||
|
||||
public String getLocalised(String s, Object... vars) {
|
||||
private String getLocalised(String s, Object... vars) {
|
||||
var key = getKey(s);
|
||||
|
||||
if (english == null) {
|
||||
TrackEvent.warn("Translations not initialized for " + key);
|
||||
return s;
|
||||
return key;
|
||||
}
|
||||
|
||||
if (currentLanguage.getValue() != null
|
||||
&& currentLanguage.getValue().getTranslations().containsKey(key)) {
|
||||
var localisedString = currentLanguage.getValue().getTranslations().get(key);
|
||||
return getValue(localisedString, vars);
|
||||
if (currentLanguage.getValue() != null) {
|
||||
var localisedString = currentLanguage.getValue().getLocalised(key, vars);
|
||||
if (localisedString.isPresent()) {
|
||||
return localisedString.get();
|
||||
}
|
||||
}
|
||||
|
||||
if (english.getTranslations().containsKey(key)) {
|
||||
var localisedString = english.getTranslations().get(key);
|
||||
return getValue(localisedString, vars);
|
||||
if (english != null) {
|
||||
var localisedString = english.getLocalised(key, vars);
|
||||
if (localisedString.isPresent()) {
|
||||
return localisedString.get();
|
||||
}
|
||||
}
|
||||
|
||||
TrackEvent.warn("Translation key not found for " + key);
|
||||
return key;
|
||||
}
|
||||
|
||||
private boolean matchesLocale(Path f, Locale l) {
|
||||
var name = FilenameUtils.getBaseName(f.getFileName().toString());
|
||||
var ending = "_" + l.toLanguageTag();
|
||||
return name.endsWith(ending);
|
||||
}
|
||||
|
||||
public String getMarkdownDocumentation(String name) {
|
||||
if (name.contains(":")) {
|
||||
name = name.substring(name.indexOf(":") + 1);
|
||||
@@ -174,77 +173,4 @@ public class AppI18n {
|
||||
.handle();
|
||||
return "";
|
||||
}
|
||||
|
||||
private LoadedTranslations load(Locale l) throws Exception {
|
||||
TrackEvent.info("Loading translations ...");
|
||||
|
||||
var translations = new HashMap<String, String>();
|
||||
{
|
||||
var basePath = XPipeInstallation.getLangPath().resolve("strings");
|
||||
AtomicInteger fileCounter = new AtomicInteger();
|
||||
AtomicInteger lineCounter = new AtomicInteger();
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
if (!matchesLocale(file, l)) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
if (!file.getFileName().toString().endsWith(".properties")) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
fileCounter.incrementAndGet();
|
||||
try (var in = Files.newInputStream(file)) {
|
||||
var props = new Properties();
|
||||
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
props.forEach((key, value) -> {
|
||||
translations.put(key.toString(), value.toString());
|
||||
lineCounter.incrementAndGet();
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var markdownDocumentations = new HashMap<String, String>();
|
||||
{
|
||||
var basePath = XPipeInstallation.getLangPath().resolve("texts");
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
if (!matchesLocale(file, l)) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
if (!file.getFileName().toString().endsWith(".md")) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
var name = file.getFileName()
|
||||
.toString()
|
||||
.substring(0, file.getFileName().toString().lastIndexOf("_"));
|
||||
try (var in = Files.newInputStream(file)) {
|
||||
markdownDocumentations.put(name, new String(in.readAllBytes(), StandardCharsets.UTF_8));
|
||||
} catch (IOException ex) {
|
||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new LoadedTranslations(l, translations, markdownDocumentations);
|
||||
}
|
||||
|
||||
@Value
|
||||
public static class LoadedTranslations {
|
||||
|
||||
Locale locale;
|
||||
Map<String, String> translations;
|
||||
Map<String, String> markdownDocumentations;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package io.xpipe.app.core;
|
||||
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.SupportedLocale;
|
||||
import io.xpipe.core.util.XPipeInstallation;
|
||||
|
||||
import lombok.Value;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Value
|
||||
public class AppI18nData {
|
||||
|
||||
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
|
||||
|
||||
SupportedLocale locale;
|
||||
Map<String, String> translations;
|
||||
Map<String, String> markdownDocumentations;
|
||||
|
||||
Optional<String> getLocalised(String s, Object... vars) {
|
||||
if (getTranslations().containsKey(s)) {
|
||||
var localisedString = getTranslations().get(s);
|
||||
return Optional.ofNullable(getValue(localisedString, vars));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private String getValue(String s, Object... vars) {
|
||||
s = s.replace("\\n", "\n");
|
||||
if (vars.length > 0) {
|
||||
var matcher = VAR_PATTERN.matcher(s);
|
||||
for (var v : vars) {
|
||||
v = v != null ? v : "null";
|
||||
if (matcher.find()) {
|
||||
var group = matcher.group();
|
||||
s = s.replace(group, v.toString());
|
||||
} else {
|
||||
TrackEvent.warn("No match found for value " + v + " in string " + s);
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static AppI18nData load(SupportedLocale l) throws Exception {
|
||||
TrackEvent.info("Loading translations ...");
|
||||
|
||||
var translations = new HashMap<String, String>();
|
||||
{
|
||||
var basePath = XPipeInstallation.getLangPath().resolve("strings");
|
||||
AtomicInteger fileCounter = new AtomicInteger();
|
||||
AtomicInteger lineCounter = new AtomicInteger();
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
if (!matchesLocale(file, l.getLocale())) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
if (!file.getFileName().toString().endsWith(".properties")) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
fileCounter.incrementAndGet();
|
||||
try (var in = Files.newInputStream(file)) {
|
||||
var props = new Properties();
|
||||
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
props.forEach((key, value) -> {
|
||||
translations.put(key.toString(), value.toString());
|
||||
lineCounter.incrementAndGet();
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var markdownDocumentations = new HashMap<String, String>();
|
||||
{
|
||||
var basePath = XPipeInstallation.getLangPath().resolve("texts");
|
||||
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
|
||||
if (!matchesLocale(file, l.getLocale())) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
if (!file.getFileName().toString().endsWith(".md")) {
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
var name = file.getFileName()
|
||||
.toString()
|
||||
.substring(0, file.getFileName().toString().lastIndexOf("_"));
|
||||
try (var in = Files.newInputStream(file)) {
|
||||
markdownDocumentations.put(name, new String(in.readAllBytes(), StandardCharsets.UTF_8));
|
||||
} catch (IOException ex) {
|
||||
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new AppI18nData(l, translations, markdownDocumentations);
|
||||
}
|
||||
|
||||
private static boolean matchesLocale(Path f, Locale l) {
|
||||
var name = FilenameUtils.getBaseName(f.getFileName().toString());
|
||||
var ending = "_" + l.toLanguageTag();
|
||||
return name.endsWith(ending);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import io.xpipe.app.browser.BrowserFullSessionComp;
|
||||
import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.store.StoreLayoutComp;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.prefs.AppPrefsComp;
|
||||
import io.xpipe.app.util.Hyperlinks;
|
||||
import io.xpipe.app.util.LabelGraphic;
|
||||
@@ -102,12 +101,6 @@ public class AppLayoutModel {
|
||||
LicenseProvider.get().overviewPage(),
|
||||
null,
|
||||
null),
|
||||
new Entry(
|
||||
AppI18n.observable("team"),
|
||||
new LabelGraphic.IconGraphic("mdi2a-account-group"),
|
||||
null,
|
||||
() -> AppPrefs.get().selectCategory("vault"),
|
||||
null),
|
||||
new Entry(
|
||||
AppI18n.observable("visitGithubRepository"),
|
||||
new LabelGraphic.IconGraphic("mdi2g-github"),
|
||||
@@ -127,17 +120,17 @@ public class AppLayoutModel {
|
||||
// () -> Hyperlinks.open(
|
||||
// "http://localhost:" + AppBeaconServer.get().getPort()),
|
||||
// null),
|
||||
new Entry(
|
||||
AppI18n.observable("documentation"),
|
||||
new LabelGraphic.IconGraphic("mdi2b-book-open-variant"),
|
||||
null,
|
||||
() -> Hyperlinks.open(Hyperlinks.DOCS),
|
||||
null),
|
||||
new Entry(
|
||||
AppI18n.observable("webtop"),
|
||||
new LabelGraphic.IconGraphic("mdi2d-desktop-mac"),
|
||||
null,
|
||||
() -> Hyperlinks.open(Hyperlinks.GITHUB_WEBTOP),
|
||||
null),
|
||||
new Entry(
|
||||
AppI18n.observable("pythonApi"),
|
||||
new LabelGraphic.IconGraphic("mdi2l-language-python"),
|
||||
null,
|
||||
() -> Hyperlinks.open(Hyperlinks.GITHUB_PYTHON_API),
|
||||
null)));
|
||||
|
||||
return l;
|
||||
|
||||
@@ -21,6 +21,7 @@ public class AppOpenArguments {
|
||||
private static final List<String> bufferedArguments = new ArrayList<>();
|
||||
|
||||
public static synchronized void init() {
|
||||
handleImpl(AppProperties.get().getArguments().getOpenArgs());
|
||||
handleImpl(bufferedArguments);
|
||||
bufferedArguments.clear();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ public class AppProperties {
|
||||
UUID buildUuid;
|
||||
String sentryUrl;
|
||||
String arch;
|
||||
List<String> languages;
|
||||
|
||||
@Getter
|
||||
boolean image;
|
||||
@@ -95,14 +94,10 @@ public class AppProperties {
|
||||
.orElse(UUID.randomUUID());
|
||||
sentryUrl = System.getProperty("io.xpipe.app.sentryUrl");
|
||||
arch = System.getProperty("io.xpipe.app.arch");
|
||||
languages = Arrays.stream(System.getProperty("io.xpipe.app.languages").split(","))
|
||||
.sorted()
|
||||
.toList();
|
||||
staging = Optional.ofNullable(System.getProperty("io.xpipe.app.staging"))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(false);
|
||||
devLoginPassword = Optional.ofNullable(System.getProperty("io.xpipe.app.loginPassword"))
|
||||
.orElse(null);
|
||||
devLoginPassword = System.getProperty("io.xpipe.app.loginPassword");
|
||||
useVirtualThreads = Optional.ofNullable(System.getProperty("io.xpipe.app.useVirtualThreads"))
|
||||
.map(Boolean::parseBoolean)
|
||||
.orElse(true);
|
||||
|
||||
@@ -5,8 +5,11 @@ import io.xpipe.app.issue.TrackEvent;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.resources.AppResources;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Scene;
|
||||
|
||||
import atlantafx.base.theme.Styles;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
@@ -19,6 +22,7 @@ public class AppStyle {
|
||||
|
||||
private static final Map<Path, String> STYLESHEET_CONTENTS = new LinkedHashMap<>();
|
||||
private static final Map<AppTheme.Theme, String> THEME_SPECIFIC_STYLESHEET_CONTENTS = new LinkedHashMap<>();
|
||||
private static final Map<AppTheme.Theme, String> THEME_PREFERENCES_STYLESHEET_CONTENTS = new LinkedHashMap<>();
|
||||
private static final List<Scene> scenes = new ArrayList<>();
|
||||
private static String FONT_CONTENTS = "";
|
||||
|
||||
@@ -34,10 +38,15 @@ public class AppStyle {
|
||||
AppPrefs.get().useSystemFont().addListener((c, o, n) -> {
|
||||
changeFontUsage(n);
|
||||
});
|
||||
AppPrefs.get().theme.addListener((c, o, n) -> {
|
||||
AppPrefs.get().theme().addListener((c, o, n) -> {
|
||||
changeTheme(n);
|
||||
});
|
||||
}
|
||||
|
||||
var fxPrefs = Platform.getPreferences();
|
||||
fxPrefs.accentColorProperty().addListener((c, o, n) -> {
|
||||
changePlatformPreferences();
|
||||
});
|
||||
}
|
||||
|
||||
private static void loadStylesheets() {
|
||||
@@ -88,6 +97,8 @@ public class AppStyle {
|
||||
var bytes = Files.readAllBytes(file);
|
||||
var s = "data:text/css;base64," + Base64.getEncoder().encodeToString(bytes);
|
||||
THEME_SPECIFIC_STYLESHEET_CONTENTS.put(theme, s);
|
||||
THEME_PREFERENCES_STYLESHEET_CONTENTS.put(
|
||||
theme, Styles.toDataURI(theme.getPlatformPreferencesStylesheet()));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -104,16 +115,42 @@ public class AppStyle {
|
||||
}
|
||||
}
|
||||
|
||||
private static void changePlatformPreferences() {
|
||||
if (AppPrefs.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var t = AppPrefs.get().theme().getValue();
|
||||
if (t == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
scenes.forEach(scene -> {
|
||||
scene.getStylesheets().remove(THEME_PREFERENCES_STYLESHEET_CONTENTS.get(t));
|
||||
});
|
||||
THEME_PREFERENCES_STYLESHEET_CONTENTS.clear();
|
||||
for (AppTheme.Theme theme : AppTheme.Theme.ALL) {
|
||||
THEME_PREFERENCES_STYLESHEET_CONTENTS.put(
|
||||
theme, Styles.toDataURI(theme.getPlatformPreferencesStylesheet()));
|
||||
}
|
||||
scenes.forEach(scene -> {
|
||||
scene.getStylesheets().add(THEME_PREFERENCES_STYLESHEET_CONTENTS.get(t));
|
||||
});
|
||||
}
|
||||
|
||||
private static void changeTheme(AppTheme.Theme theme) {
|
||||
scenes.forEach(scene -> {
|
||||
scene.getStylesheets().removeAll(THEME_SPECIFIC_STYLESHEET_CONTENTS.values());
|
||||
scene.getStylesheets().removeAll(THEME_PREFERENCES_STYLESHEET_CONTENTS.values());
|
||||
scene.getStylesheets().add(THEME_SPECIFIC_STYLESHEET_CONTENTS.get(theme));
|
||||
scene.getStylesheets().add(THEME_PREFERENCES_STYLESHEET_CONTENTS.get(theme));
|
||||
});
|
||||
}
|
||||
|
||||
public static void reloadStylesheets(Scene scene) {
|
||||
STYLESHEET_CONTENTS.clear();
|
||||
THEME_SPECIFIC_STYLESHEET_CONTENTS.clear();
|
||||
THEME_PREFERENCES_STYLESHEET_CONTENTS.clear();
|
||||
FONT_CONTENTS = "";
|
||||
|
||||
init();
|
||||
@@ -130,9 +167,10 @@ public class AppStyle {
|
||||
scene.getStylesheets().add(s);
|
||||
});
|
||||
if (AppPrefs.get() != null) {
|
||||
var t = AppPrefs.get().theme.get();
|
||||
var t = AppPrefs.get().theme().getValue();
|
||||
if (t != null) {
|
||||
scene.getStylesheets().add(THEME_SPECIFIC_STYLESHEET_CONTENTS.get(t));
|
||||
scene.getStylesheets().add(THEME_PREFERENCES_STYLESHEET_CONTENTS.get(t));
|
||||
}
|
||||
}
|
||||
TrackEvent.debug("Added stylesheets for scene");
|
||||
|
||||
@@ -6,6 +6,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 io.xpipe.app.util.ColorHelper;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
import io.xpipe.core.process.OsType;
|
||||
|
||||
@@ -22,6 +23,7 @@ import javafx.css.PseudoClass;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
|
||||
@@ -32,6 +34,7 @@ import lombok.SneakyThrows;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AppTheme {
|
||||
@@ -60,20 +63,16 @@ public class AppTheme {
|
||||
return;
|
||||
}
|
||||
|
||||
AppPrefs.get().theme.subscribe(t -> {
|
||||
AppPrefs.get().theme().subscribe(t -> {
|
||||
Theme.ALL.forEach(theme -> root.getStyleClass().remove(theme.getCssId()));
|
||||
if (t == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
root.getStyleClass().add(t.getCssId());
|
||||
stage.getScene().getStylesheets().addAll(t.getAdditionalStylesheets());
|
||||
root.pseudoClassStateChanged(LIGHT, !t.isDark());
|
||||
root.pseudoClassStateChanged(DARK, t.isDark());
|
||||
});
|
||||
AppPrefs.get().theme.addListener((observable, oldValue, newValue) -> {
|
||||
stage.getScene().getStylesheets().removeAll(oldValue.getAdditionalStylesheets());
|
||||
});
|
||||
|
||||
AppPrefs.get().performanceMode().subscribe(val -> {
|
||||
root.pseudoClassStateChanged(PRETTY, !val);
|
||||
@@ -96,19 +95,19 @@ public class AppTheme {
|
||||
var lastSystemDark = AppCache.getBoolean("lastDarkTheme", false);
|
||||
var nowDark = Platform.getPreferences().getColorScheme() == ColorScheme.DARK;
|
||||
AppCache.update("lastDarkTheme", nowDark);
|
||||
if (AppPrefs.get().theme.getValue() == null || lastSystemDark != nowDark) {
|
||||
if (AppPrefs.get().theme().getValue() == null || lastSystemDark != nowDark) {
|
||||
setDefault();
|
||||
}
|
||||
|
||||
Platform.getPreferences().colorSchemeProperty().addListener((observableValue, colorScheme, t1) -> {
|
||||
Platform.runLater(() -> {
|
||||
if (t1 == ColorScheme.DARK
|
||||
&& !AppPrefs.get().theme.getValue().isDark()) {
|
||||
&& !AppPrefs.get().theme().getValue().isDark()) {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultDarkTheme());
|
||||
}
|
||||
|
||||
if (t1 != ColorScheme.DARK
|
||||
&& AppPrefs.get().theme.getValue().isDark()) {
|
||||
&& AppPrefs.get().theme().getValue().isDark()) {
|
||||
AppPrefs.get().theme.setValue(Theme.getDefaultLightTheme());
|
||||
}
|
||||
});
|
||||
@@ -120,11 +119,11 @@ public class AppTheme {
|
||||
ErrorEvent.fromThrowable(t).omit().handle();
|
||||
}
|
||||
|
||||
var t = AppPrefs.get().theme.getValue();
|
||||
var t = AppPrefs.get().theme().getValue();
|
||||
t.apply();
|
||||
TrackEvent.debug("Set theme " + t.getId() + " for scene");
|
||||
|
||||
AppPrefs.get().theme.addListener((c, o, n) -> {
|
||||
AppPrefs.get().theme().addListener((c, o, n) -> {
|
||||
changeTheme(n);
|
||||
});
|
||||
|
||||
@@ -199,8 +198,15 @@ public class AppTheme {
|
||||
private final String name;
|
||||
private final int skipLines;
|
||||
|
||||
public DerivedTheme(String id, String cssId, String name, atlantafx.base.theme.Theme theme, int skipLines) {
|
||||
super(id, cssId, theme);
|
||||
public DerivedTheme(
|
||||
String id,
|
||||
String cssId,
|
||||
String name,
|
||||
atlantafx.base.theme.Theme theme,
|
||||
AppFontSizes sizes,
|
||||
Supplier<Color> contextMenuColor,
|
||||
int skipLines) {
|
||||
super(id, cssId, theme, sizes, contextMenuColor);
|
||||
this.name = name;
|
||||
this.skipLines = skipLines;
|
||||
}
|
||||
@@ -237,17 +243,75 @@ public class AppTheme {
|
||||
@AllArgsConstructor
|
||||
public static class Theme implements PrefsChoiceValue {
|
||||
|
||||
public static final Theme PRIMER_LIGHT = new Theme("light", "primer", new PrimerLight());
|
||||
public static final Theme PRIMER_DARK = new Theme("dark", "primer", new PrimerDark());
|
||||
public static final Theme NORD_LIGHT = new Theme("nordLight", "nord", new NordLight());
|
||||
public static final Theme NORD_DARK = new Theme("nordDark", "nord", new NordDark());
|
||||
public static final Theme CUPERTINO_LIGHT = new Theme("cupertinoLight", "cupertino", new CupertinoLight());
|
||||
public static final Theme CUPERTINO_DARK = new Theme("cupertinoDark", "cupertino", new CupertinoDark());
|
||||
public static final Theme DRACULA = new Theme("dracula", "dracula", new Dracula());
|
||||
public static final Theme MOCHA = new DerivedTheme("mocha", "mocha", "Mocha", new PrimerDark(), 115);
|
||||
public static final Theme PRIMER_LIGHT = new Theme(
|
||||
"light",
|
||||
"primer",
|
||||
new PrimerLight(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_10_5, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().darker().desaturate(), 0.3));
|
||||
public static final Theme PRIMER_DARK = new Theme(
|
||||
"dark",
|
||||
"primer",
|
||||
new PrimerDark(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_11, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().desaturate().desaturate(), 0.2));
|
||||
public static final Theme NORD_LIGHT = new Theme(
|
||||
"nordLight",
|
||||
"nord",
|
||||
new NordLight(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_10_5, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().darker().desaturate(), 0.3));
|
||||
public static final Theme NORD_DARK = new Theme(
|
||||
"nordDark",
|
||||
"nord",
|
||||
new NordDark(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_11, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().desaturate().desaturate(), 0.2));
|
||||
public static final Theme CUPERTINO_LIGHT = new Theme(
|
||||
"cupertinoLight",
|
||||
"cupertino",
|
||||
new CupertinoLight(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_10_5, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().darker().desaturate(), 0.3));
|
||||
public static final Theme CUPERTINO_DARK = new Theme(
|
||||
"cupertinoDark",
|
||||
"cupertino",
|
||||
new CupertinoDark(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_11, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().desaturate().desaturate(), 0.2));
|
||||
public static final Theme DRACULA = new Theme(
|
||||
"dracula",
|
||||
"dracula",
|
||||
new Dracula(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_11, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().desaturate().desaturate(), 0.2));
|
||||
public static final Theme MOCHA = new DerivedTheme(
|
||||
"mocha",
|
||||
"mocha",
|
||||
"Mocha",
|
||||
new PrimerDark(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_11, AppFontSizes.BASE_10, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().desaturate().desaturate(), 0.2),
|
||||
115);
|
||||
|
||||
// Adjust this to create your own theme
|
||||
public static final Theme CUSTOM = new DerivedTheme("custom", "primer", "Custom", new PrimerDark(), 115);
|
||||
public static final Theme CUSTOM = new DerivedTheme(
|
||||
"custom",
|
||||
"primer",
|
||||
"Custom",
|
||||
new PrimerDark(),
|
||||
AppFontSizes.forOs(AppFontSizes.BASE_10_5, AppFontSizes.BASE_10_5, AppFontSizes.BASE_11),
|
||||
() -> ColorHelper.withOpacity(
|
||||
Platform.getPreferences().getAccentColor().desaturate().desaturate(), 0.2),
|
||||
115);
|
||||
|
||||
// Also include your custom theme here
|
||||
public static final List<Theme> ALL = List.of(
|
||||
@@ -259,6 +323,12 @@ public class AppTheme {
|
||||
|
||||
protected final atlantafx.base.theme.Theme theme;
|
||||
|
||||
@Getter
|
||||
protected final AppFontSizes fontSizes;
|
||||
|
||||
@Getter
|
||||
protected final Supplier<Color> contextMenuColor;
|
||||
|
||||
static Theme getDefaultLightTheme() {
|
||||
return switch (OsType.getLocal()) {
|
||||
case OsType.Windows windows -> PRIMER_LIGHT;
|
||||
@@ -283,6 +353,13 @@ public class AppTheme {
|
||||
Application.setUserAgentStylesheet(theme.getUserAgentStylesheetBSS());
|
||||
}
|
||||
|
||||
protected String getPlatformPreferencesStylesheet() {
|
||||
var c = contextMenuColor.get();
|
||||
var hex = ColorHelper.toWeb(c);
|
||||
var s = "* { -color-context-menu: " + hex + "; }";
|
||||
return s;
|
||||
}
|
||||
|
||||
public List<String> getAdditionalStylesheets() {
|
||||
var r = AppResources.getResourceURL(AppResources.XPIPE_MODULE, "theme/" + getId() + ".css");
|
||||
if (r.isEmpty()) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.xpipe.app.core.check;
|
||||
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.PlatformState;
|
||||
|
||||
@@ -9,6 +10,15 @@ import javafx.application.Platform;
|
||||
public class AppGpuCheck {
|
||||
|
||||
public static void check() {
|
||||
if (!AppProperties.get().isInitialLaunch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We might launch the platform due to an error early
|
||||
if (AppPrefs.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlatformState.getCurrent() != PlatformState.RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class AppJavaOptionsCheck {
|
||||
}
|
||||
|
||||
var env = System.getenv("_JAVA_OPTIONS");
|
||||
if (env == null) {
|
||||
if (env == null || env.isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class AppShellCheck {
|
||||
case OsType.MacOs macOs -> new AppShellChecker() {
|
||||
|
||||
@Override
|
||||
protected boolean shouldAttemptFallback() {
|
||||
protected boolean shouldAttemptFallbackForProcessStartFail() {
|
||||
// We don't want to fall back on macOS as occasional zsh spawn issues would cause many users
|
||||
// to use sh
|
||||
return false;
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.xpipe.app.issue.ErrorEvent;
|
||||
import io.xpipe.app.util.LocalShell;
|
||||
import io.xpipe.app.util.ScriptHelper;
|
||||
import io.xpipe.core.process.ProcessOutputException;
|
||||
import io.xpipe.core.process.ShellSpawnException;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
@@ -16,10 +17,11 @@ public abstract class AppShellChecker {
|
||||
var err = selfTestErrorCheck();
|
||||
|
||||
var canFallback = !ProcessControlProvider.get()
|
||||
.getEffectiveLocalDialect()
|
||||
.equals(ProcessControlProvider.get().getFallbackDialect())
|
||||
&& shouldAttemptFallback();
|
||||
if (err.isPresent() && canFallback) {
|
||||
.getEffectiveLocalDialect()
|
||||
.equals(ProcessControlProvider.get().getFallbackDialect());
|
||||
if (err.isPresent()
|
||||
&& canFallback
|
||||
&& (shouldAttemptFallbackForProcessStartFail() || !err.get().isProcessSpawnIssue())) {
|
||||
var msg = formatMessage(err.get().getMessage());
|
||||
ErrorEvent.fromThrowable(new IllegalStateException(msg)).expected().handle();
|
||||
toggleFallback();
|
||||
@@ -41,7 +43,7 @@ public abstract class AppShellChecker {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean shouldAttemptFallback() {
|
||||
protected boolean shouldAttemptFallbackForProcessStartFail() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -90,12 +92,15 @@ public abstract class AppShellChecker {
|
||||
.readStdoutOrThrow();
|
||||
if (!out.equals("test")) {
|
||||
return Optional.of(new FailureResult(
|
||||
"Expected output \"test\", got output \"" + out + "\" when running test script", true));
|
||||
"Expected output \"test\", got output \"" + out + "\" when running test script", false, true));
|
||||
}
|
||||
} catch (ProcessOutputException ex) {
|
||||
return Optional.of(new FailureResult(ex.getOutput() != null ? ex.getOutput() : ex.getMessage(), true));
|
||||
return Optional.of(
|
||||
new FailureResult(ex.getOutput() != null ? ex.getOutput() : ex.getMessage(), false, true));
|
||||
} catch (ShellSpawnException ex) {
|
||||
return Optional.of(new FailureResult(ex.getMessage(), true, true));
|
||||
} catch (Throwable t) {
|
||||
return Optional.of(new FailureResult(t.getMessage() != null ? t.getMessage() : t.toString(), false));
|
||||
return Optional.of(new FailureResult(t.getMessage() != null ? t.getMessage() : t.toString(), false, false));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -104,6 +109,7 @@ public abstract class AppShellChecker {
|
||||
public static class FailureResult {
|
||||
|
||||
String message;
|
||||
boolean processSpawnIssue;
|
||||
boolean canContinue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,11 @@ import io.xpipe.app.core.window.AppMainWindow;
|
||||
import io.xpipe.app.ext.ActionProvider;
|
||||
import io.xpipe.app.ext.DataStoreProviders;
|
||||
import io.xpipe.app.ext.ProcessControlProvider;
|
||||
import io.xpipe.app.icon.SystemIconManager;
|
||||
import io.xpipe.app.issue.ErrorEvent;
|
||||
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.resources.SystemIcons;
|
||||
import io.xpipe.app.resources.*;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStorageSyncHandler;
|
||||
import io.xpipe.app.terminal.TerminalLauncherManager;
|
||||
@@ -26,7 +25,6 @@ import io.xpipe.app.terminal.TerminalView;
|
||||
import io.xpipe.app.update.UpdateAvailableDialog;
|
||||
import io.xpipe.app.update.UpdateChangelogAlert;
|
||||
import io.xpipe.app.update.UpdateNagDialog;
|
||||
import io.xpipe.app.update.XPipeDistributionType;
|
||||
import io.xpipe.app.util.*;
|
||||
import io.xpipe.core.util.XPipeDaemonMode;
|
||||
|
||||
@@ -47,7 +45,7 @@ public class BaseMode extends OperationMode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwitchTo() throws Throwable {
|
||||
public void onSwitchTo() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
@@ -92,8 +90,9 @@ public class BaseMode extends OperationMode {
|
||||
shellLoaded.countDown();
|
||||
AppRosettaCheck.check();
|
||||
AppTestCommandCheck.check();
|
||||
XPipeDistributionType.init();
|
||||
AppPrefs.setLocalDefaultsIfNeeded();
|
||||
PlatformInit.init(true);
|
||||
AppMainWindow.addUpdateTitleListener();
|
||||
},
|
||||
() -> {
|
||||
shellLoaded.await();
|
||||
@@ -134,8 +133,9 @@ public class BaseMode extends OperationMode {
|
||||
() -> {
|
||||
PlatformInit.init(true);
|
||||
AppImages.init();
|
||||
SystemIcons.init();
|
||||
imagesLoaded.countDown();
|
||||
storageLoaded.await();
|
||||
SystemIconManager.init();
|
||||
},
|
||||
() -> {
|
||||
BrowserIconManager.loadIfNecessary();
|
||||
|
||||
@@ -92,6 +92,11 @@ public abstract class OperationMode {
|
||||
OperationMode.halt(1);
|
||||
}
|
||||
|
||||
if (ex instanceof OutOfMemoryError) {
|
||||
ex.printStackTrace();
|
||||
OperationMode.halt(1);
|
||||
}
|
||||
|
||||
ErrorEvent.fromThrowable(ex).unhandled(true).build().handle();
|
||||
});
|
||||
|
||||
@@ -103,7 +108,8 @@ public abstract class OperationMode {
|
||||
AppDebugModeCheck.printIfNeeded();
|
||||
AppProperties.logSystemProperties();
|
||||
AppProperties.get().logArguments();
|
||||
AppExtensionManager.init(true);
|
||||
AppDistributionType.init();
|
||||
AppExtensionManager.init();
|
||||
AppI18n.init();
|
||||
AppPrefs.initLocal();
|
||||
AppBeaconServer.setupPort();
|
||||
@@ -245,7 +251,13 @@ public abstract class OperationMode {
|
||||
var loc = AppProperties.get().isDevelopmentEnvironment()
|
||||
? XPipeInstallation.getLocalDefaultInstallationBasePath()
|
||||
: XPipeInstallation.getCurrentInstallationBasePath().toString();
|
||||
var exec = XPipeInstallation.createExternalAsyncLaunchCommand(loc, XPipeDaemonMode.GUI, "", true);
|
||||
var dataDir = AppProperties.get().getDataDir();
|
||||
// We have to quote the arguments like this to make it work in PowerShell as well
|
||||
var exec = XPipeInstallation.createExternalAsyncLaunchCommand(
|
||||
loc,
|
||||
XPipeDaemonMode.GUI,
|
||||
"\"-Dio.xpipe.app.acceptEula=true\" \"-Dio.xpipe.app.dataDir=" + dataDir + "\"",
|
||||
true);
|
||||
LocalShell.getShell().executeSimpleCommand(exec);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.xpipe.app.core.window;
|
||||
import io.xpipe.app.comp.Comp;
|
||||
import io.xpipe.app.comp.base.ModalButton;
|
||||
import io.xpipe.app.comp.base.ModalOverlay;
|
||||
import io.xpipe.app.core.AppFont;
|
||||
import io.xpipe.app.core.AppI18n;
|
||||
import io.xpipe.app.util.PlatformInit;
|
||||
import io.xpipe.app.util.PlatformThread;
|
||||
@@ -11,6 +10,7 @@ import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.animation.PauseTransition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
@@ -102,14 +102,24 @@ public class AppDialog {
|
||||
}
|
||||
|
||||
public static Comp<?> dialogTextKey(String s) {
|
||||
return dialogText(AppI18n.get(s));
|
||||
return dialogText(AppI18n.observable(s));
|
||||
}
|
||||
|
||||
public static Comp<?> dialogText(String s) {
|
||||
return Comp.of(() -> {
|
||||
var text = new Text(s);
|
||||
text.setWrappingWidth(450);
|
||||
AppFont.medium(text);
|
||||
var sp = new StackPane(text);
|
||||
return sp;
|
||||
})
|
||||
.prefWidth(450);
|
||||
}
|
||||
|
||||
public static Comp<?> dialogText(ObservableValue<String> s) {
|
||||
return Comp.of(() -> {
|
||||
var text = new Text();
|
||||
text.textProperty().bind(s);
|
||||
text.setWrappingWidth(450);
|
||||
var sp = new StackPane(text);
|
||||
return sp;
|
||||
})
|
||||
@@ -118,11 +128,20 @@ public class AppDialog {
|
||||
|
||||
public static boolean confirm(String translationKey) {
|
||||
var confirmed = new AtomicBoolean(false);
|
||||
var content = dialogTextKey(AppI18n.get(translationKey + "Content"));
|
||||
var content = dialogTextKey(translationKey + "Content");
|
||||
var modal = ModalOverlay.of(translationKey + "Title", content);
|
||||
modal.addButton(ModalButton.cancel());
|
||||
modal.addButton(ModalButton.ok(() -> confirmed.set(true)));
|
||||
showAndWait(modal);
|
||||
return confirmed.get();
|
||||
}
|
||||
|
||||
public static boolean confirm(String titleKey, ObservableValue<String> content) {
|
||||
var confirmed = new AtomicBoolean(false);
|
||||
var modal = ModalOverlay.of(titleKey, dialogText(content));
|
||||
modal.addButton(ModalButton.cancel());
|
||||
modal.addButton(ModalButton.ok(() -> confirmed.set(true)));
|
||||
showAndWait(modal);
|
||||
return confirmed.get();
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user