Interface improvements and fixes [release]

This commit is contained in:
crschnick 2023-03-03 14:33:41 +00:00
parent d879c13aa4
commit 1b23c833ed
19 changed files with 144 additions and 60 deletions

View file

@ -3,7 +3,6 @@ package io.xpipe.app.comp.base;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.application.Platform;
import javafx.collections.ListChangeListener;
@ -67,7 +66,7 @@ public class ListBoxViewComp<T> extends Comp<CompStructure<ScrollPane>> {
.toList();
if (!listView.getChildren().equals(newShown)) {
BindingsHelper.setContent(listView.getChildren(), newShown);
listView.getChildren().setAll(newShown);
listView.layout();
}
};

View file

@ -28,6 +28,7 @@ public class ListSelectorComp<T> extends SimpleComp {
vbox.getStyleClass().add("content");
for (var v : values) {
var cb = new CheckBox(null);
cb.setSelected(selected.contains(v));
cb.selectedProperty().addListener((c, o, n) -> {
if (n) {
selected.add(v);
@ -35,8 +36,8 @@ public class ListSelectorComp<T> extends SimpleComp {
selected.remove(v);
}
});
cb.setSelected(selected.contains(v));
var l = new Label(toString.apply(v), cb);
l.setOnMouseClicked(event -> cb.setSelected(!cb.isSelected()));
vbox.getChildren().add(l);
}
var sp = new ScrollPane(vbox);

View file

@ -82,8 +82,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
});
this.apply(r -> {
r.get().setPrefWidth(AppFont.em(32));
r.get().setPrefHeight(AppFont.em(38));
r.get().setPrefWidth(AppFont.em(36));
r.get().setPrefHeight(AppFont.em(42));
});
}
@ -92,7 +92,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
ThreadHelper.runAsync(() -> {
e.applyChanges(newE);
if (!DataStorage.get().getStores().contains(e)) {
DataStorage.get().addStore(e);
DataStorage.get().addStoreEntry(e);
ScanAlert.showIfNeeded(e.getStore());
}
DataStorage.get().refresh();
});
@ -102,7 +103,8 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
public static void showCreation(Predicate<DataStoreProvider> filter) {
show(null, null, null, filter, e -> {
try {
DataStorage.get().addStore(e);
DataStorage.get().addStoreEntry(e);
ScanAlert.showIfNeeded(e.getStore());
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
@ -218,11 +220,6 @@ public class GuiDsStoreCreator extends MultiStepComp.Step<CompStructure<?>> {
return new LoadingOverlayComp(Comp.of(() -> layout), busy).createStructure();
}
@Override
public void onContinue() {
ScanAlert.showIfNeeded(entry.getValue().getStore());
}
@Override
public boolean canContinue() {
if (provider.getValue() != null) {

View file

@ -5,6 +5,7 @@ import io.xpipe.app.comp.base.LazyTextFieldComp;
import io.xpipe.app.comp.base.LoadingOverlayComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.SimpleCompStructure;
@ -161,6 +162,14 @@ public class StoreEntryComp extends SimpleComp {
});
});
new PopupMenuAugment<>(false) {
@Override
protected ContextMenu createContextMenu() {
return StoreEntryComp.this.createContextMenu();
}
}.augment(new SimpleCompStructure<>(button));
return button;
}
@ -182,9 +191,9 @@ public class StoreEntryComp extends SimpleComp {
});
button.apply(new FancyTooltipAugment<>(
actionProvider.getName(entry.getEntry().getStore().asNeeded())));
if (!actionProvider.showIfDisabled()) {
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
button.hide(Bindings.not(p.getValue()));
} else {
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
button.disable(Bindings.not(p.getValue()));
}
list.add(button);
@ -242,9 +251,10 @@ public class StoreEntryComp extends SimpleComp {
});
});
item.textProperty().bind(name);
item.disableProperty().bind(Bindings.not(p.getValue()));
if (!actionProvider.showIfDisabled()) {
if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ONLY_SHOW_IF_ENABLED) {
item.visibleProperty().bind(p.getValue());
} else if (actionProvider.activeType() == ActionProvider.DataStoreCallSite.ActiveType.ALWAYS_SHOW) {
item.disableProperty().bind(Bindings.not(p.getValue()));
}
contextMenu.getItems().add(item);
}

View file

@ -7,6 +7,7 @@ import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.fxcomps.impl.HorizontalComp;
import io.xpipe.app.fxcomps.impl.VerticalComp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.storage.DataStorage;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@ -28,12 +29,17 @@ public class StoreEntrySection implements StorageFilter.Filterable {
public static ObservableList<StoreEntrySection> createTopLevels() {
var filtered = BindingsHelper.filteredContentBinding(
StoreViewState.get().getAllEntries(),
storeEntryWrapper -> !storeEntryWrapper.getEntry().getState().isUsable()
|| storeEntryWrapper
.getEntry()
.getProvider()
.getParent(storeEntryWrapper.getEntry().getStore())
== null);
storeEntryWrapper -> {
if (!storeEntryWrapper.getEntry().getState().isUsable()) {
return true;
}
var parent = storeEntryWrapper
.getEntry()
.getProvider()
.getParent(storeEntryWrapper.getEntry().getStore());
return parent == null || (DataStorage.get().getStoreEntryIfPresent(parent).isEmpty());
});
var topLevel = BindingsHelper.mappedContentBinding(filtered, storeEntryWrapper -> create(storeEntryWrapper));
var ordered = BindingsHelper.orderedContentBinding(
topLevel,
@ -101,7 +107,7 @@ public class StoreEntrySection implements StorageFilter.Filterable {
new HorizontalComp(topEntryList),
new HorizontalComp(List.of(spacer, content))
.apply(struc -> struc.get().setFillHeight(true))
.hide(Bindings.size(children).isEqualTo(0))));
.hide(BindingsHelper.persist(Bindings.size(children).isEqualTo(0)))));
}
@Override

View file

@ -18,6 +18,7 @@ import org.apache.commons.io.FilenameUtils;
import org.ocpsoft.prettytime.PrettyTime;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@ -37,6 +38,7 @@ public class AppI18n {
private static final Pattern VAR_PATTERN = Pattern.compile("\\$\\w+?\\$");
private Map<String, String> translations;
private Map<String, String> markdownDocumentations;
private PrettyTime prettyTime;
private static AppI18n INSTANCE = new AppI18n();
@ -189,6 +191,10 @@ private static AppI18n INSTANCE = new AppI18n();
return name.endsWith(ending);
}
public String getMarkdownDocumentation(String name) {
return markdownDocumentations.getOrDefault(name, "");
}
private void load() {
TrackEvent.info("Loading translations ...");
@ -210,6 +216,10 @@ private static AppI18n INSTANCE = new AppI18n();
return FileVisitResult.CONTINUE;
}
if (!file.getFileName().toString().endsWith(".properties")) {
return FileVisitResult.CONTINUE;
}
fileCounter.incrementAndGet();
try (var in = Files.newInputStream(file)) {
var props = new Properties();
@ -233,6 +243,39 @@ private static AppI18n INSTANCE = new AppI18n();
.handle();
});
}
markdownDocumentations = new HashMap<>();
for (var module : AppExtensionManager.getInstance().getContentModules()) {
AppResources.with(module.getName(), "lang", basePath -> {
if (!Files.exists(basePath)) {
return;
}
var moduleName = FilenameUtils.getExtension(module.getName());
Files.walkFileTree(basePath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (!matchesLocale(file)) {
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)) {
var usedPrefix = moduleName + ":";
markdownDocumentations.put(usedPrefix + name, new String(in.readAllBytes(), StandardCharsets.UTF_8));
} catch (IOException ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
return FileVisitResult.CONTINUE;
}
});
});
}
this.prettyTime = new PrettyTime(
AppPrefs.get() != null
? AppPrefs.get().language.getValue().getLocale()

View file

@ -75,6 +75,12 @@ public interface ActionProvider {
public static interface DataStoreCallSite<T extends DataStore> {
enum ActiveType {
ONLY_SHOW_IF_ENABLED,
ALWAYS_SHOW,
ALWAYS_ENABLE
}
Action createAction(T store);
Class<T> getApplicableClass();
@ -95,8 +101,8 @@ public interface ActionProvider {
String getIcon(T store);
default boolean showIfDisabled() {
return true;
default ActiveType activeType() {
return ActiveType.ONLY_SHOW_IF_ENABLED;
}
}

View file

@ -9,6 +9,7 @@ import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.geometry.Insets;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
@ -55,10 +56,12 @@ public class ChoicePaneComp extends Comp<CompStructure<VBox>> {
if (n == null) {
vbox.getChildren().remove(1);
} else {
var region = n.comp().createRegion();
region.setPadding(new Insets(0, 0, 0, 10));
if (vbox.getChildren().size() == 1) {
vbox.getChildren().add(n.comp().createRegion());
vbox.getChildren().add(region);
} else {
vbox.getChildren().set(1, n.comp().createRegion());
vbox.getChildren().set(1, region);
}
}
});

View file

@ -2,6 +2,7 @@ package io.xpipe.app.fxcomps.impl;
import atlantafx.base.controls.Popover;
import atlantafx.base.controls.Spacer;
import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.AppFont;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
@ -10,8 +11,10 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
@ -76,16 +79,21 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
description.managedProperty().bind(PlatformThread.sync(compRegion.managedProperty()));
}
if (entry.longDescription() != null) {
var popover = new Popover(new Label(entry.longDescription().getValue()));
if (entry.longDescriptionSource() != null) {
var markDown = new MarkdownComp(entry.longDescriptionSource(), s -> s)
.apply(struc -> struc.get().setMaxWidth(500))
.apply(struc -> struc.get().setMaxHeight(400));
var popover = new Popover(markDown.createRegion());
popover.setCloseButtonEnabled(false);
popover.setHeaderAlwaysVisible(false);
popover.setDetachable(true);
AppFont.small(popover.getContentNode());
var descriptionHover = new Label("?");
var descriptionHover = new Button("... ?");
descriptionHover.setPadding(new Insets(0, 6, 0, 6));
descriptionHover.getStyleClass().add("long-description");
AppFont.header(descriptionHover);
descriptionHover.setOnMouseClicked(e -> popover.show(descriptionHover));
descriptionHover.setOnAction(e -> popover.show(descriptionHover));
var descriptionBox = new HBox(description, new Spacer(Orientation.HORIZONTAL), descriptionHover);
HBox.setHgrow(descriptionBox, Priority.ALWAYS);
@ -159,5 +167,5 @@ public class OptionsComp extends Comp<CompStructure<Pane>> {
return entries;
}
public record Entry(String key, ObservableValue<String> description, ObservableValue<String> longDescription, ObservableValue<String> name, Comp<?> comp) {}
public record Entry(String key, ObservableValue<String> description, String longDescriptionSource, ObservableValue<String> name, Comp<?> comp) {}
}

View file

@ -69,7 +69,7 @@ public abstract class LauncherInput {
if (scheme.equalsIgnoreCase("xpipe")) {
var action = uri.getAuthority();
var args = Arrays.asList(uri.getPath().split("/"));
var args = Arrays.asList(uri.getPath().substring(1).split("/"));
var found = ActionProvider.ALL.stream()
.filter(actionProvider -> actionProvider.getLauncherCallSite() != null
&& actionProvider

View file

@ -339,31 +339,41 @@ public abstract class DataStorage {
});
}
private void propagateUpdate() throws Exception {
private void propagateUpdate() {
for (DataStoreEntry dataStoreEntry : getStores()) {
dataStoreEntry.refresh(false);
dataStoreEntry.simpleRefresh();
}
for (var dataStoreEntry : sourceEntries) {
dataStoreEntry.refresh(false);
for (var e : sourceEntries) {
e.simpleRefresh();
}
}
public void addStore(@NonNull DataStoreEntry e) {
public void addStoreEntry(@NonNull DataStoreEntry e) {
if (getStoreIfPresent(e.getName()).isPresent()) {
throw new IllegalArgumentException("Store with name " + e.getName() + " already exists");
}
e.setDirectory(getStoresDir().resolve(e.getUuid().toString()));
this.storeEntries.add(e);
propagateUpdate();
save();
this.listeners.forEach(l -> l.onStoreAdd(e));
}
public void addStoreIfNotPresent(@NonNull String name, DataStore store) {
if (getStoreEntryIfPresent(store).isPresent()) {
return;
}
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store);
addStoreEntry(e);
}
public DataStoreEntry addStore(@NonNull String name, DataStore store) {
var e = DataStoreEntry.createNew(UUID.randomUUID(), createUniqueStoreEntryName(name), store);
addStore(e);
addStoreEntry(e);
return e;
}

View file

@ -1,7 +1,7 @@
package io.xpipe.app.storage;
import io.xpipe.app.issue.ErrorEvent;
import lombok.Builder;
import lombok.SneakyThrows;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import org.apache.commons.io.FileUtils;
@ -38,9 +38,12 @@ public abstract class StorageElement {
return elementState;
}
@SneakyThrows
public void simpleRefresh() {
refresh(false);
try {
refresh(false);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).handle();
}
}
public abstract void refresh(boolean deep) throws Exception;

View file

@ -21,7 +21,7 @@ public class OptionsBuilder {
private ObservableValue<String> name;
private ObservableValue<String> description;
private ObservableValue<String> longDescription;
private String longDescription;
private Comp<?> comp;
private void finishCurrent() {
@ -106,7 +106,7 @@ public class OptionsBuilder {
public OptionsBuilder longDescription(String descriptionKey) {
finishCurrent();
longDescription = AppI18n.observable(descriptionKey);
longDescription = AppI18n.getInstance().getMarkdownDocumentation(descriptionKey);
return this;
}

View file

@ -38,9 +38,12 @@ public class ScanAlert {
alert.setTitle(AppI18n.get("scanAlertTitle"));
alert.setWidth(300);
var content = new VerticalComp(List.of(
new LabelComp(AppI18n.get("scanAlertHeader")).apply(struc -> struc.get().setWrapText(true)),
new LabelComp(AppI18n.get("scanAlertHeader"))
.apply(struc -> struc.get().setWrapText(true)),
new ListSelectorComp<>(
applicable, scanOperation -> AppI18n.get(scanOperation.getNameKey()), selected)))
applicable,
scanOperation -> AppI18n.get(scanOperation.getNameKey()),
selected)))
.apply(struc -> struc.get().setSpacing(15))
.styleClass("window-content")
.createRegion();
@ -51,12 +54,12 @@ public class ScanAlert {
buttonType -> {
if (buttonType.isPresent()
&& buttonType.get().getButtonData().isDefaultButton()) {
try (var ignored = new BusyProperty(busy)) {
for (var a : applicable) {
for (var a : selected) {
try (var ignored = new BusyProperty(busy)) {
a.getScanner().run();
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
} catch (Exception ex) {
ErrorEvent.fromThrowable(ex).handle();
}
}
});

View file

@ -40,7 +40,7 @@ public class LocalStoreProvider implements DataStoreProvider {
}
var e = DataStoreEntry.createNew(UUID.randomUUID(), "Local Machine", new LocalStore());
DataStorage.get().addStore(e);
DataStorage.get().addStoreEntry(e);
e.setConfiguration(StorageElement.Configuration.builder()
.deletable(false)
.editable(false)

View file

@ -38,7 +38,7 @@ public class AddStoreAction implements ActionProvider {
return new LauncherCallSite() {
@Override
public String getId() {
return "addStore";
return "add";
}
@Override

View file

@ -37,8 +37,8 @@ public class EditStoreAction implements ActionProvider {
}
@Override
public boolean showIfDisabled() {
return false;
public ActiveType activeType() {
return ActiveType.ALWAYS_ENABLE;
}
@Override

View file

@ -43,11 +43,6 @@ public class ShareStoreAction implements ActionProvider {
public DataStoreCallSite<?> getDataStoreCallSite() {
return new DataStoreCallSite<DataStore>() {
@Override
public boolean showIfDisabled() {
return false;
}
@Override
public ActionProvider.Action createAction(DataStore store) {
return new Action(store);

View file

@ -1 +1 @@
0.5.3
0.5.4