mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-07-31 09:06:11 -04:00
Rework
This commit is contained in:
@@ -36,6 +36,8 @@ public interface QuickConnectProvider extends ActionProvider {
|
||||
|
||||
String getPlaceholder();
|
||||
|
||||
String getTemplate();
|
||||
|
||||
boolean skipDialogIfPossible();
|
||||
|
||||
default void open(DataStoreEntry e) {
|
||||
|
||||
@@ -5,14 +5,18 @@ import io.xpipe.app.beacon.BeaconInterface;
|
||||
|
||||
import io.xpipe.app.beacon.BeaconServerException;
|
||||
import io.xpipe.app.core.AppOpenArguments;
|
||||
import io.xpipe.app.core.AppProperties;
|
||||
import io.xpipe.app.core.mode.AppOperationMode;
|
||||
import io.xpipe.app.platform.PlatformInit;
|
||||
import io.xpipe.app.util.FilePath;
|
||||
import io.xpipe.app.util.OsType;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import lombok.Builder;
|
||||
import lombok.NonNull;
|
||||
import lombok.Value;
|
||||
import lombok.extern.jackson.Jacksonized;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public class DaemonOpenExchange extends BeaconInterface<DaemonOpenExchange.Request> {
|
||||
@@ -31,6 +35,14 @@ public class DaemonOpenExchange extends BeaconInterface<DaemonOpenExchange.Reque
|
||||
|
||||
@Override
|
||||
public Object handle(HttpExchange exchange, Request msg) throws BeaconServerException {
|
||||
if (msg.getDirectory() != null && !AppProperties.get().getDataDir().equals(msg.getDirectory())) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
ThreadHelper.sleep(1000);
|
||||
AppOperationMode.close();
|
||||
});
|
||||
return Response.builder().restartRequired(true).build();
|
||||
}
|
||||
|
||||
if (msg.getArguments().isEmpty()) {
|
||||
try {
|
||||
// At this point we are already loading this on another thread
|
||||
@@ -64,10 +76,15 @@ public class DaemonOpenExchange extends BeaconInterface<DaemonOpenExchange.Reque
|
||||
public static class Request {
|
||||
@NonNull
|
||||
List<String> arguments;
|
||||
|
||||
Path directory;
|
||||
}
|
||||
|
||||
@Jacksonized
|
||||
@Builder
|
||||
@Value
|
||||
public static class Response {}
|
||||
public static class Response {
|
||||
|
||||
boolean restartRequired;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public class AppI18n {
|
||||
}
|
||||
|
||||
public static ObservableValue<String> observable(ObservableValue<String> s, Object... vars) {
|
||||
return BindingsHelper.flatMap(s, v -> INSTANCE.observableImpl(v, vars));
|
||||
return BindingsHelper.flatMap(s, v -> v != null ? INSTANCE.observableImpl(v, vars) : null);
|
||||
}
|
||||
|
||||
public static String get(String s, Object... vars) {
|
||||
|
||||
@@ -78,6 +78,7 @@ public class AppInstance {
|
||||
client.get()
|
||||
.performRequest(DaemonOpenExchange.Request.builder()
|
||||
.arguments(inputs)
|
||||
.directory(AppProperties.get().getDataDir())
|
||||
.build());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@@ -108,6 +109,7 @@ public class AppInstance {
|
||||
client.get()
|
||||
.performRequest(DaemonOpenExchange.Request.builder()
|
||||
.arguments(List.of(e.getURI().toString()))
|
||||
.directory(AppProperties.get().getDataDir())
|
||||
.build());
|
||||
} catch (Exception ex) {
|
||||
ErrorEventFactory.fromThrowable(ex).expected().omit().handle();
|
||||
|
||||
@@ -58,7 +58,7 @@ public class AppDialog {
|
||||
var firstElement = o.equals(modalOverlays.stream()
|
||||
.filter(modalOverlay -> modalOverlay != null)
|
||||
.findFirst().orElse(null));
|
||||
var lastElement = modalOverlays.getLast().equals(o);
|
||||
var lastElement = modalOverlays.size() > 0 && modalOverlays.getLast().equals(o);
|
||||
// Prevent indices from being moved when closing a modal in the back
|
||||
if (firstElement && !lastElement) {
|
||||
modalOverlays.set(modalOverlays.indexOf(o), null);
|
||||
|
||||
@@ -62,7 +62,7 @@ public class CloneHubLeafProvider implements HubLeafProvider<DataStore> {
|
||||
entry.setIcon(ref.get().getIcon(), true);
|
||||
entry.setColor(ref.get().getColor());
|
||||
entry.setExpanded(ref.get().isExpanded());
|
||||
entry.setFreeze(ref.get().isFreeze());
|
||||
entry.setTemplate(ref.get().isTemplate());
|
||||
entry.setCategoryUuid(ref.get().getCategoryUuid());
|
||||
entry.setPinToTop(ref.get().isPinToTop());
|
||||
entry.setOrderIndex(ref.get().getOrderIndex());
|
||||
|
||||
@@ -87,6 +87,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
},
|
||||
grid.widthProperty()));
|
||||
var notes = new StoreNotesComp(getWrapper()).build();
|
||||
var templateIcon = createTemplateIcon().build();
|
||||
var userIcon = createUserIcon().build();
|
||||
var pinIcon = createPinIcon().build();
|
||||
var active = new StoreActiveComp(getWrapper()).build();
|
||||
@@ -134,7 +135,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
nameCC.setHgrow(Priority.ALWAYS);
|
||||
grid.getColumnConstraints().addAll(nameCC);
|
||||
|
||||
var nameBox = new HBox(name, tags, index, active, userIcon, pinIcon, notes);
|
||||
var nameBox = new HBox(name, tags, index, active, templateIcon, userIcon, pinIcon, notes);
|
||||
nameBox.setSpacing(4);
|
||||
nameBox.setAlignment(Pos.CENTER_LEFT);
|
||||
grid.addRow(0, nameBox);
|
||||
|
||||
@@ -35,6 +35,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
var tags = createTags().build();
|
||||
var index = createOrderIndex().build();
|
||||
var notes = new StoreNotesComp(getWrapper()).build();
|
||||
var templateIcon = createTemplateIcon().build();
|
||||
var userIcon = createUserIcon().build();
|
||||
var pinIcon = createPinIcon().build();
|
||||
var active = new StoreActiveComp(getWrapper()).build();
|
||||
@@ -60,7 +61,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
grid.add(storeIcon.build(), 1, 0, 1, 2);
|
||||
grid.getColumnConstraints().add(new ColumnConstraints(52));
|
||||
|
||||
var nameBox = new HBox(name, tags, index, active, userIcon, pinIcon, notes);
|
||||
var nameBox = new HBox(name, tags, index, active, templateIcon, userIcon, pinIcon, notes);
|
||||
nameBox.setSpacing(4);
|
||||
nameBox.setAlignment(Pos.CENTER_LEFT);
|
||||
grid.add(nameBox, 2, 0);
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ReadOnlyBooleanWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.*;
|
||||
@@ -37,16 +38,9 @@ public class StoreCreationComp extends ModalOverlayContentComp {
|
||||
var nameKey = model.storeTypeNameKey();
|
||||
var built = new OptionsBuilder()
|
||||
.name(nameKey + "Name")
|
||||
.description(nameKey + "NameDescription")
|
||||
.description(model.isTemplate() ? nameKey + "TemplateNameDescription" : nameKey + "NameDescription")
|
||||
.addString(model.getName())
|
||||
.nonNull()
|
||||
.check(val -> Validator.create(val, AppI18n.observable("readOnlyStoreError"), model.getName(), s -> {
|
||||
var same = s != null
|
||||
&& model.getExistingEntry() != null
|
||||
&& DataStorage.get().getEffectiveReadOnlyState(model.getExistingEntry())
|
||||
&& s.equals(model.getExistingEntry().getName());
|
||||
return !same;
|
||||
}));
|
||||
.nonNullIf(new ReadOnlyBooleanWrapper(!model.isTemplate()));
|
||||
return built;
|
||||
}
|
||||
|
||||
|
||||
@@ -180,7 +180,7 @@ public class StoreCreationDialog {
|
||||
private static ModalOverlay createModalOverlay(StoreCreationModel model) {
|
||||
var comp = new StoreCreationComp(model);
|
||||
comp.prefWidth(650);
|
||||
var nameKey = model.isQuickConnect() ? "quickConnect" : model.getTitleKey();
|
||||
var nameKey = model.isTemplate() ? "quickConnect" : model.getTitleKey();
|
||||
var modal = ModalOverlay.of(nameKey, comp);
|
||||
comp.apply(struc -> {
|
||||
struc.addEventHandler(KeyEvent.KEY_PRESSED, e -> {
|
||||
@@ -194,25 +194,23 @@ public class StoreCreationDialog {
|
||||
});
|
||||
});
|
||||
|
||||
if (!model.isQuickConnect()) {
|
||||
var queueEntry = StoreCreationQueueEntry.of(model, modal);
|
||||
var queueEntry = StoreCreationQueueEntry.of(model, modal);
|
||||
|
||||
modal.setHideAction(() -> {
|
||||
AppLayoutModel.get().getQueueEntries().add(queueEntry);
|
||||
showNotice();
|
||||
});
|
||||
modal.setHideAction(() -> {
|
||||
AppLayoutModel.get().getQueueEntries().add(queueEntry);
|
||||
showNotice();
|
||||
});
|
||||
|
||||
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
if (model.getFinished().get() || !modal.isShowing()) {
|
||||
return;
|
||||
}
|
||||
AppLayoutModel.get().getSelected().addListener((observable, oldValue, newValue) -> {
|
||||
if (model.getFinished().get() || !modal.isShowing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.hide();
|
||||
AppLayoutModel.get().getQueueEntries().add(queueEntry);
|
||||
showNotice();
|
||||
});
|
||||
modal.setRequireCloseButtonForClose(true);
|
||||
}
|
||||
modal.hide();
|
||||
AppLayoutModel.get().getQueueEntries().add(queueEntry);
|
||||
showNotice();
|
||||
});
|
||||
modal.setRequireCloseButtonForClose(true);
|
||||
|
||||
var loadingLabel = new LabelComp(Bindings.createStringBinding(
|
||||
() -> {
|
||||
@@ -232,17 +230,25 @@ public class StoreCreationDialog {
|
||||
.augment(button -> {
|
||||
button.visibleProperty().bind(Bindings.not(model.canShowDocs()));
|
||||
}));
|
||||
modal.addButton(new ModalButton(
|
||||
"connect",
|
||||
() -> {
|
||||
model.connect();
|
||||
},
|
||||
false,
|
||||
false)
|
||||
.augment(button -> {
|
||||
button.visibleProperty().bind(Bindings.not(model.canConnect()));
|
||||
}));
|
||||
if (!model.isQuickConnect()) {
|
||||
if (model.isTemplate() && !model.isQuickConnect()) {
|
||||
modal.addButton(new ModalButton("saveAs", () -> {
|
||||
model.commit(false);
|
||||
modal.close();
|
||||
}, false, false).augment(button -> {
|
||||
button.disableProperty().bind(Bindings.not(model.canSave()));
|
||||
}));
|
||||
}
|
||||
if (!model.isTemplate()) {
|
||||
modal.addButton(new ModalButton(
|
||||
"connect",
|
||||
() -> {
|
||||
model.connect();
|
||||
},
|
||||
false,
|
||||
false)
|
||||
.augment(button -> {
|
||||
button.visibleProperty().bind(Bindings.not(model.canConnect()));
|
||||
}));
|
||||
modal.addButton(new ModalButton(
|
||||
"skip",
|
||||
() -> {
|
||||
@@ -258,7 +264,7 @@ public class StoreCreationDialog {
|
||||
}
|
||||
|
||||
modal.addButton(new ModalButton(
|
||||
model.isQuickConnect() ? "connect" : "finish",
|
||||
model.isTemplate() ? "connect" : "finish",
|
||||
() -> {
|
||||
model.finish();
|
||||
},
|
||||
@@ -279,7 +285,7 @@ public class StoreCreationDialog {
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
return !model.getBusy().get()
|
||||
? AppI18n.get(model.isQuickConnect() ? "connect" : "finish")
|
||||
? AppI18n.get(model.isTemplate() ? "connect" : "finish")
|
||||
: null;
|
||||
},
|
||||
PlatformThread.sync(model.getBusy()),
|
||||
|
||||
@@ -50,6 +50,7 @@ public class StoreCreationModel {
|
||||
StoreCreationConsumer consumer;
|
||||
ObservableBooleanValue syncable;
|
||||
UUID uuid;
|
||||
String defaultName;
|
||||
|
||||
public StoreCreationModel(
|
||||
Property<DataStoreProvider> provider,
|
||||
@@ -62,8 +63,9 @@ public class StoreCreationModel {
|
||||
this.provider = provider;
|
||||
this.store = store;
|
||||
this.filter = filter;
|
||||
this.name = new SimpleStringProperty(initialName != null && !initialName.isEmpty() ? initialName : null);
|
||||
this.existingEntry = existingEntry;
|
||||
this.name = new SimpleStringProperty(!isTemplate() ? initialName != null && !initialName.isEmpty() ? initialName : null : null);
|
||||
this.defaultName = isTemplate() && initialName != null && !initialName.isEmpty() ? initialName : null;
|
||||
this.existingDependencies = existingEntry != null
|
||||
? DataStorage.get().getDependencies(existingEntry).stream()
|
||||
.<DataStore>map(ref -> ref.getStore())
|
||||
@@ -71,7 +73,7 @@ public class StoreCreationModel {
|
||||
: null;
|
||||
this.staticDisplay = staticDisplay;
|
||||
this.consumer = consumer;
|
||||
this.uuid = existingEntry != null ? existingEntry.getUuid() : UUID.randomUUID();
|
||||
this.uuid = existingEntry != null && !DataStorage.get().getEffectiveReadOnlyState(existingEntry) ? existingEntry.getUuid() : UUID.randomUUID();
|
||||
|
||||
this.provider.addListener((c, o, n) -> {
|
||||
store.unbind();
|
||||
@@ -96,19 +98,18 @@ public class StoreCreationModel {
|
||||
|
||||
this.entry = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
if (name.getValue() == null
|
||||
|| store.getValue() == null
|
||||
|| name.getValue().isBlank()) {
|
||||
var effectiveName = name.getValue() != null ? name.getValue() : defaultName;
|
||||
if (effectiveName == null || store.getValue() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var initial = DataStoreEntry.createNew(
|
||||
uuid, DataStorage.get().getSelectedCategory().getUuid(), name.getValue(), store.getValue());
|
||||
uuid, DataStorage.get().getSelectedCategory().getUuid(), effectiveName, store.getValue());
|
||||
var entryRef = existingEntry != null
|
||||
? existingEntry
|
||||
: DataStorage.get().getDefaultDisplayParent(initial).orElse(initial);
|
||||
var targetCategory = getTargetCategory(entryRef.getCategoryUuid());
|
||||
return DataStoreEntry.createNew(uuid, targetCategory.getUuid(), name.getValue(), store.getValue());
|
||||
return DataStoreEntry.createNew(uuid, targetCategory.getUuid(), effectiveName, store.getValue());
|
||||
},
|
||||
name,
|
||||
store);
|
||||
@@ -136,7 +137,7 @@ public class StoreCreationModel {
|
||||
&& store.get().isComplete()
|
||||
&& store.get() instanceof ValidatableStore) {
|
||||
if (existingEntry != null) {
|
||||
return !existingEntry.isFreeze()
|
||||
return !existingEntry.isTemplate()
|
||||
|| !existingEntry.getName().equals(name.getValue());
|
||||
} else {
|
||||
return true;
|
||||
@@ -194,8 +195,16 @@ public class StoreCreationModel {
|
||||
store));
|
||||
}
|
||||
|
||||
boolean isQuickConnect() {
|
||||
return existingEntry != null && existingEntry.getUuid().equals(StoreQuickConnect.STORE_ID);
|
||||
ObservableBooleanValue canSave() {
|
||||
return Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
return name.getValue() != null && store.get() != null && store.get().isComplete();
|
||||
},
|
||||
name, store);
|
||||
}
|
||||
|
||||
boolean isTemplate() {
|
||||
return existingEntry != null && (existingEntry.getUuid().equals(StoreQuickConnect.STORE_ID) || existingEntry.isTemplate());
|
||||
}
|
||||
|
||||
void connect() {
|
||||
@@ -204,6 +213,10 @@ public class StoreCreationModel {
|
||||
action.executeAsync();
|
||||
}
|
||||
|
||||
boolean isQuickConnect() {
|
||||
return existingEntry != null && existingEntry.getUuid().equals(StoreQuickConnect.STORE_ID);
|
||||
}
|
||||
|
||||
boolean hasBeenModified() {
|
||||
if (initialStore.getValue() == null) {
|
||||
return true;
|
||||
@@ -253,7 +266,7 @@ public class StoreCreationModel {
|
||||
}
|
||||
|
||||
// We didn't change anything
|
||||
if (store.getValue().isComplete() && !wasChanged() && !isQuickConnect()) {
|
||||
if (store.getValue().isComplete() && !wasChanged() && !isTemplate()) {
|
||||
commit(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -280,6 +280,20 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
return button;
|
||||
}
|
||||
|
||||
protected BaseRegionBuilder<?, ?> createTemplateIcon() {
|
||||
var button = new IconButtonComp("mdal-content_copy");
|
||||
button.style("template-icon");
|
||||
button.describe(d -> d.nameKey("template"));
|
||||
button.apply(struc -> {
|
||||
AppFontSizes.base(struc);
|
||||
struc.setDisable(true);
|
||||
struc.setOpacity(1.0);
|
||||
});
|
||||
button.hide(Bindings.not(getWrapper().getTemplate()).or(AppSizeBreakpoints.compactMode()));
|
||||
button.apply(struc -> struc.setOpacity(0.85));
|
||||
return button;
|
||||
}
|
||||
|
||||
protected BaseRegionBuilder<?, ?> createPinIcon() {
|
||||
var button = new IconButtonComp("mdi2p-pin-outline");
|
||||
button.disable(new SimpleBooleanProperty(true));
|
||||
@@ -455,30 +469,19 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
notes.visibleProperty().bind(BindingsHelper.map(getWrapper().getNotes(), s -> s == null));
|
||||
items.add(items.size(), notes);
|
||||
|
||||
var freeze = new MenuItem();
|
||||
freeze.graphicProperty()
|
||||
.bind(Bindings.createObjectBinding(
|
||||
() -> {
|
||||
var is = getWrapper().getReadOnly().get();
|
||||
return is
|
||||
? new FontIcon("mdi2l-lock-open-variant-outline")
|
||||
: new FontIcon("mdi2l-lock-open-outline");
|
||||
},
|
||||
getWrapper().getReadOnly()));
|
||||
freeze.textProperty()
|
||||
.bind(Bindings.createStringBinding(
|
||||
() -> {
|
||||
var is = getWrapper().getReadOnly().get();
|
||||
return is
|
||||
? AppI18n.get("unfreezeConfiguration")
|
||||
: AppI18n.get("freezeConfiguration");
|
||||
},
|
||||
AppI18n.activeLanguage(),
|
||||
getWrapper().getReadOnly()));
|
||||
freeze.setOnAction(event -> getWrapper()
|
||||
.getEntry()
|
||||
.setFreeze(!getWrapper().getReadOnly().get()));
|
||||
items.add(freeze);
|
||||
if (getWrapper().getEntry().getProvider() != null && getWrapper().getEntry().getProvider().getCreationCategory() != null) {
|
||||
var template = new MenuItem();
|
||||
template.graphicProperty().bind(Bindings.createObjectBinding(() -> {
|
||||
var is = getWrapper().getTemplate().get();
|
||||
return is ? new FontIcon("mdi2c-credit-card-off-outline") : new FontIcon("mdi2c-credit-card-multiple-outline");
|
||||
}, getWrapper().getTemplate()));
|
||||
template.textProperty().bind(Bindings.createStringBinding(() -> {
|
||||
var is = getWrapper().getTemplate().get();
|
||||
return is ? AppI18n.get("untemplateConfiguration") : AppI18n.get("templateConfiguration");
|
||||
}, AppI18n.activeLanguage(), getWrapper().getTemplate()));
|
||||
template.setOnAction(event -> getWrapper().getEntry().setTemplate(!getWrapper().getTemplate().get()));
|
||||
items.add(template);
|
||||
}
|
||||
}
|
||||
|
||||
if (cat == StoreActionCategory.DEVELOPER) {
|
||||
|
||||
@@ -69,7 +69,7 @@ public class StoreEntryWrapper {
|
||||
private final ObservableValue<String> shownSummary;
|
||||
private final ObservableValue<String> shownDescription;
|
||||
private final Property<String> shownInformation;
|
||||
private final BooleanProperty readOnly = new SimpleBooleanProperty();
|
||||
private final BooleanProperty template = new SimpleBooleanProperty();
|
||||
private final BooleanProperty renaming = new SimpleBooleanProperty();
|
||||
private final BooleanProperty pinToTop = new SimpleBooleanProperty();
|
||||
private final IntegerProperty orderIndex = new SimpleIntegerProperty();
|
||||
@@ -218,7 +218,7 @@ public class StoreEntryWrapper {
|
||||
orderIndex.setValue(entry.getOrderIndex());
|
||||
color.setValue(entry.getColor());
|
||||
notes.setValue(entry.getNotes());
|
||||
readOnly.setValue(entry.isFreeze());
|
||||
template.setValue(entry.isTemplate());
|
||||
iconFile.setValue(entry.getEffectiveIconFile());
|
||||
busy.setValue(entry.getBusyCounter().get() != 0);
|
||||
deletable.setValue(
|
||||
@@ -300,60 +300,60 @@ public class StoreEntryWrapper {
|
||||
}
|
||||
|
||||
if (!isInStorage()) {
|
||||
minorActionProviders.clear();
|
||||
majorActionProviders.clear();
|
||||
defaultActionProvider.setValue(null);
|
||||
} else {
|
||||
try {
|
||||
var defaultProvider = ActionProvider.ALL.stream()
|
||||
.filter(e -> entry.getStore() != null
|
||||
&& e instanceof HubLeafProvider<?> def
|
||||
&& (entry.getValidity().isUsable()
|
||||
|| (!def.requiresValidStore() && entry.getProvider() != null))
|
||||
&& def.getApplicableClass()
|
||||
.isAssignableFrom(entry.getStore().getClass())
|
||||
&& def.isApplicable(entry.ref())
|
||||
&& def.isDefault())
|
||||
.findFirst()
|
||||
.or(() -> {
|
||||
if (entry.getStore() instanceof GroupStore<?>) {
|
||||
return Optional.empty();
|
||||
} else if (entry.getProvider() != null
|
||||
&& entry.getProvider().canConfigure()) {
|
||||
return Optional.of(new EditHubLeafProvider());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.orElse(null);
|
||||
this.defaultActionProvider.setValue(defaultProvider);
|
||||
if (!template.get()) {
|
||||
var defaultProvider = ActionProvider.ALL.stream().filter(e -> entry.getStore() != null &&
|
||||
e instanceof HubLeafProvider<?> def &&
|
||||
(entry.getValidity().isUsable() || (!def.requiresValidStore() && entry.getProvider() != null)) &&
|
||||
def.getApplicableClass().isAssignableFrom(entry.getStore().getClass()) &&
|
||||
def.isApplicable(entry.ref()) &&
|
||||
def.isDefault()).findFirst().or(() -> {
|
||||
if (entry.getStore() instanceof GroupStore<?>) {
|
||||
return Optional.empty();
|
||||
} else if (entry.getProvider() != null && entry.getProvider().canConfigure()) {
|
||||
return Optional.of(new EditHubLeafProvider());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}).orElse(null);
|
||||
this.defaultActionProvider.setValue(defaultProvider);
|
||||
|
||||
var newMajorProviders = ActionProvider.ALL.stream()
|
||||
.map(actionProvider -> actionProvider instanceof HubMenuItemProvider<?> sa ? sa : null)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(dataStoreActionProvider -> {
|
||||
return showActionProvider(dataStoreActionProvider, true);
|
||||
})
|
||||
.toList();
|
||||
if (!majorActionProviders.equals(newMajorProviders)) {
|
||||
majorActionProviders.setAll(newMajorProviders);
|
||||
}
|
||||
var newMajorProviders = ActionProvider.ALL.stream()
|
||||
.map(actionProvider -> actionProvider instanceof HubMenuItemProvider<?> sa ? sa : null)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(dataStoreActionProvider -> {
|
||||
return showActionProvider(dataStoreActionProvider, true);
|
||||
})
|
||||
.toList();
|
||||
if (!majorActionProviders.equals(newMajorProviders)) {
|
||||
majorActionProviders.setAll(newMajorProviders);
|
||||
}
|
||||
|
||||
var newMinorProviders = ActionProvider.ALL.stream()
|
||||
.map(actionProvider -> actionProvider instanceof HubMenuItemProvider<?> sa ? sa : null)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(dataStoreActionProvider -> {
|
||||
return showActionProvider(dataStoreActionProvider, false);
|
||||
})
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
newMinorProviders.removeIf(storeActionProvider -> {
|
||||
return newMajorProviders.stream().anyMatch(mj -> {
|
||||
return mj instanceof HubBranchProvider<?> branch
|
||||
&& branch.getChildren(entry.ref()).stream()
|
||||
.anyMatch(c -> c.getClass().equals(storeActionProvider.getClass()));
|
||||
var newMinorProviders = ActionProvider.ALL.stream()
|
||||
.map(actionProvider -> actionProvider instanceof HubMenuItemProvider<?> sa ? sa : null)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(dataStoreActionProvider -> {
|
||||
return showActionProvider(dataStoreActionProvider, false);
|
||||
})
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
newMinorProviders.removeIf(storeActionProvider -> {
|
||||
return newMajorProviders.stream().anyMatch(mj -> {
|
||||
return mj instanceof HubBranchProvider<?> branch
|
||||
&& branch.getChildren(entry.ref()).stream()
|
||||
.anyMatch(c -> c.getClass().equals(storeActionProvider.getClass()));
|
||||
});
|
||||
});
|
||||
});
|
||||
if (!minorActionProviders.equals(newMinorProviders)) {
|
||||
minorActionProviders.setAll(newMinorProviders);
|
||||
if (!minorActionProviders.equals(newMinorProviders)) {
|
||||
minorActionProviders.setAll(newMinorProviders);
|
||||
}
|
||||
} else {
|
||||
minorActionProviders.clear();
|
||||
majorActionProviders.clear();
|
||||
this.defaultActionProvider.setValue(new EditHubLeafProvider());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ErrorEventFactory.fromThrowable(ex).omit().handle();
|
||||
|
||||
@@ -53,9 +53,6 @@ public class StoreFilterFieldComp extends SimpleRegionBuilder {
|
||||
field.focusedProperty().subscribe(focus -> {
|
||||
if (focus) {
|
||||
popover.hide();
|
||||
Platform.runLater(() -> {
|
||||
field.selectAll();
|
||||
});
|
||||
} else {
|
||||
state.onFocusLost();
|
||||
}
|
||||
|
||||
@@ -56,13 +56,11 @@ public class StoreFilterStateComp extends SimpleRegionBuilder {
|
||||
var quickConnectionsList =
|
||||
new ListBoxViewComp<>(quickConnections, quickConnections, s -> createButton(s, s), false);
|
||||
|
||||
var quickConnectionsPlaceholders = FXCollections.observableArrayList(QuickConnectProvider.getAll().stream()
|
||||
.map(p -> p.getPlaceholder())
|
||||
.toList());
|
||||
var quickConnectionsPlaceholders = FXCollections.observableArrayList(QuickConnectProvider.getAll());
|
||||
var quickConnectionsEmptyList = new ListBoxViewComp<>(
|
||||
quickConnectionsPlaceholders,
|
||||
quickConnectionsPlaceholders,
|
||||
s -> createButton(s, s.split(" ")[0] + " "),
|
||||
p -> createButton(p.getPlaceholder(), p.getTemplate()),
|
||||
false);
|
||||
|
||||
var options = new OptionsBuilder()
|
||||
|
||||
@@ -83,16 +83,19 @@ public class StoreQuickConnect {
|
||||
|
||||
StoreCreationDialog.showEdit(quickConnectEntry, newStore, false, true, finished -> {
|
||||
update(finished.getStore());
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try {
|
||||
DataStorage.get().addStoreEntryInProgress(quickConnectEntry);
|
||||
provider.get().open(quickConnectEntry);
|
||||
} catch (Exception e) {
|
||||
ErrorEventFactory.fromThrowable(e).handle();
|
||||
} finally {
|
||||
DataStorage.get().removeStoreEntryInProgress(quickConnectEntry);
|
||||
}
|
||||
});
|
||||
var saved = !finished.getName().equals("quick-connect");
|
||||
if (!saved) {
|
||||
ThreadHelper.runAsync(() -> {
|
||||
try {
|
||||
DataStorage.get().addStoreEntryInProgress(quickConnectEntry);
|
||||
provider.get().open(quickConnectEntry);
|
||||
} catch (Exception e) {
|
||||
ErrorEventFactory.fromThrowable(e).handle();
|
||||
} finally {
|
||||
DataStorage.get().removeStoreEntryInProgress(quickConnectEntry);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
|
||||
@@ -85,6 +85,10 @@ public abstract class StoreSectionBaseComp extends RegionBuilder<VBox> {
|
||||
BindingsHelper.attach(vbox, section.getWrapper().getPerUser(), val -> {
|
||||
vbox.pseudoClassStateChanged(PseudoClass.getPseudoClass("per-user"), val);
|
||||
});
|
||||
|
||||
BindingsHelper.attach(vbox, section.getWrapper().getTemplate(), val -> {
|
||||
vbox.pseudoClassStateChanged(PseudoClass.getPseudoClass("template"), val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,13 @@ public class BindingsHelper {
|
||||
ObservableValue<T> observableValue, Function<? super T, ? extends ObservableValue<? extends U>> mapper) {
|
||||
var prop = new SimpleObjectProperty<U>();
|
||||
Runnable runnable = () -> {
|
||||
prop.bind(mapper.apply(observableValue.getValue()));
|
||||
var observable = mapper.apply(observableValue.getValue());
|
||||
if (observable != null) {
|
||||
prop.bind(observable);
|
||||
} else {
|
||||
prop.unbind();
|
||||
prop.setValue(null);
|
||||
}
|
||||
};
|
||||
runnable.run();
|
||||
observableValue.addListener((observable, oldValue, newValue) -> {
|
||||
|
||||
@@ -103,10 +103,14 @@ public class AppPrefsComp extends SimpleRegionBuilder {
|
||||
private double computeCategoryOffset(Region box, ScrollPane scrollPane, AppPrefsCategory val) {
|
||||
var node = val != null ? box.lookup("." + val.getId()) : null;
|
||||
if (node != null && scrollPane.getHeight() > 0.0) {
|
||||
var minY = node.getBoundsInParent().getMinY();
|
||||
if (minY <= 40.0) {
|
||||
minY = 0.0;
|
||||
}
|
||||
var s = Math.min(
|
||||
box.getHeight(),
|
||||
node.getBoundsInParent().getMinY() > 0.0
|
||||
? node.getBoundsInParent().getMinY() + 20
|
||||
minY > 0.0
|
||||
? minY + 20
|
||||
: 0.0)
|
||||
/ box.getHeight();
|
||||
var off = (scrollPane.getHeight() * s * 1.02) / box.getHeight();
|
||||
|
||||
@@ -1226,7 +1226,7 @@ public abstract class DataStorage {
|
||||
}
|
||||
|
||||
var catConfig = getEffectiveCategoryConfig(cat.get());
|
||||
return catConfig.getFreezeConfigurations() != null ? catConfig.getFreezeConfigurations() : entry.isFreeze();
|
||||
return catConfig.getFreezeConfigurations() != null ? catConfig.getFreezeConfigurations() : entry.isTemplate();
|
||||
}
|
||||
|
||||
public DataStoreColor getEffectiveColor(DataStoreEntry entry) {
|
||||
|
||||
@@ -73,7 +73,7 @@ public class DataStoreEntry extends StorageElement {
|
||||
|
||||
@NonFinal
|
||||
@Getter
|
||||
boolean freeze;
|
||||
boolean template;
|
||||
|
||||
@NonFinal
|
||||
@Getter
|
||||
@@ -105,7 +105,7 @@ public class DataStoreEntry extends StorageElement {
|
||||
DataStoreColor color,
|
||||
String notes,
|
||||
String icon,
|
||||
boolean freeze,
|
||||
boolean template,
|
||||
boolean pinToTop,
|
||||
int orderIndex,
|
||||
UUID breakOutCategory,
|
||||
@@ -120,7 +120,7 @@ public class DataStoreEntry extends StorageElement {
|
||||
this.validity = this.provider != null ? validity : Validity.LOAD_FAILED;
|
||||
this.storePersistentStateNode = storePersistentState;
|
||||
this.notes = notes;
|
||||
this.freeze = freeze;
|
||||
this.template = template;
|
||||
this.pinToTop = pinToTop;
|
||||
this.orderIndex = orderIndex;
|
||||
this.breakOutCategory = breakOutCategory;
|
||||
@@ -256,7 +256,7 @@ public class DataStoreEntry extends StorageElement {
|
||||
}
|
||||
})
|
||||
.orElse(null);
|
||||
var freeze = Optional.ofNullable(json.get("freeze"))
|
||||
var template = Optional.ofNullable(json.get("template"))
|
||||
.map(jsonNode -> jsonNode.booleanValue())
|
||||
.orElse(false);
|
||||
var pinToTop = Optional.ofNullable(json.get("pinToTop"))
|
||||
@@ -350,7 +350,7 @@ public class DataStoreEntry extends StorageElement {
|
||||
color,
|
||||
notes,
|
||||
icon,
|
||||
freeze,
|
||||
template,
|
||||
pinToTop,
|
||||
orderIndex,
|
||||
breakOutCategory,
|
||||
@@ -543,7 +543,7 @@ public class DataStoreEntry extends StorageElement {
|
||||
obj.put("breakOutCategoryUuid", breakOutCategory != null ? breakOutCategory.toString() : null);
|
||||
obj.set("color", mapper.valueToTree(color));
|
||||
obj.set("icon", mapper.valueToTree(icon));
|
||||
obj.put("freeze", freeze);
|
||||
obj.put("template", template);
|
||||
obj.put("pinToTop", pinToTop);
|
||||
obj.put("orderIndex", orderIndex);
|
||||
|
||||
@@ -597,9 +597,9 @@ public class DataStoreEntry extends StorageElement {
|
||||
}
|
||||
}
|
||||
|
||||
public void setFreeze(boolean newValue) {
|
||||
var changed = freeze != newValue;
|
||||
this.freeze = newValue;
|
||||
public void setTemplate(boolean newValue) {
|
||||
var changed = template != newValue;
|
||||
this.template = newValue;
|
||||
if (changed) {
|
||||
notifyUpdate(false, true);
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ public class RemoteDesktopWindow {
|
||||
() -> {
|
||||
var selected = this.selected.get();
|
||||
if (selected == null) {
|
||||
return "Remote Desktop Dock";
|
||||
return "XPipe - Remote Desktop Connection";
|
||||
}
|
||||
|
||||
var name = selected.getName();
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
-fx-icon-color: #5b2cffff;
|
||||
}
|
||||
|
||||
.store-entry-grid .template-icon .ikonli-font-icon {
|
||||
-fx-icon-color: #E6A616CC;
|
||||
}
|
||||
|
||||
.store-entry-grid .icon > .background {
|
||||
-fx-background-radius: 5px;
|
||||
-fx-background-color: -color-bg-overlay;
|
||||
@@ -207,6 +211,10 @@
|
||||
-fx-border-color: #5b2cffcc;
|
||||
}
|
||||
|
||||
.root .store-entry-section-comp.color-box.vertical-comp:template {
|
||||
-fx-border-color: #E6A616CC;
|
||||
}
|
||||
|
||||
/* Light sub backgrounds */
|
||||
|
||||
.root:light .store-entry-section-comp:sub:expanded {
|
||||
|
||||
35
dist/changelog/24.0.md
vendored
Normal file
35
dist/changelog/24.0.md
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
## Wake-on-LAN
|
||||
|
||||
This release introduces support for Wake-on-LAN:
|
||||
|
||||

|
||||
|
||||
You can create a Wake-on-LAN entry for any network interface and broadcast address. For convenience, there is an automatic finder for available network interfaces and broadcast addresses on a system, so you won't have to fill in anything manually:
|
||||
|
||||

|
||||
|
||||
The Wake-on-LAN support works through shell sessions, meaning that it also supports remote networks and is not limited to your local one.
|
||||
|
||||
## Connection templates
|
||||
|
||||
You can now create connection templates to allow for a more dynamic connection usage without having to create new entries every time. Any connection can be designated as a template:
|
||||
|
||||

|
||||
|
||||
You can then use this template to connect to an individual system directly without having to store this connection configuration. You can still do so if you want to however.
|
||||
|
||||
## macOS container machines
|
||||
|
||||
The latest release of the [apple container runtime](https://github.com/apple/container) introduces support for container machines, which is essentially just WSL for macOS. This releases adds support for those environments as well:
|
||||
|
||||

|
||||
|
||||
## Other
|
||||
|
||||
- There are now multiple new UI themes available for you to choose from
|
||||
- The API endpoints for /connection/... have been renamed to /store/...
|
||||
|
||||
## Fixes
|
||||
|
||||
|
||||
|
||||
17
lang/strings/translations_en.properties
generated
17
lang/strings/translations_en.properties
generated
@@ -124,6 +124,7 @@ hostFeatureUnsupported=$FEATURE$ is not installed on the host
|
||||
missingStore=$NAME$ does not exist
|
||||
connectionName=Connection name
|
||||
connectionNameDescription=Give this connection a custom name
|
||||
connectionTemplateNameDescription=Save a copy of this connection template with a custom name
|
||||
openFileTitle=Open file
|
||||
openDirectoryTitle=Open directory
|
||||
saveFileTitle=Save file
|
||||
@@ -1398,10 +1399,11 @@ tailscalePassword=Password
|
||||
tailscalePasswordDescription=The user password that can be used for sudo
|
||||
scriptName=Script name
|
||||
scriptNameDescription=Give this script a custom name
|
||||
scriptTemplateNameDescription=Save a copy of this script template with a custom name
|
||||
scriptGroupName=Script group name
|
||||
scriptGroupNameDescription=Give this script group a custom name
|
||||
identityName=Identity name
|
||||
identityNameDescription=Give this identity a custom name
|
||||
identityTemplateNameDescription=Save a copy of this connection template with a custom name
|
||||
#context: Tailscale VPN
|
||||
tailscaleTailnet.displayName=Tailnet
|
||||
#context: Tailscale VPN
|
||||
@@ -1555,7 +1557,6 @@ gitPasswordDescription=The password or personal access token to use to authentic
|
||||
gitPasswordValue=Password / personal access token
|
||||
setReadOnly=Set read-only
|
||||
unsetReadOnly=Unset read-only
|
||||
readOnlyStoreError=This entry's configuration is frozen. Choose a different name to save your changes to a new copy.
|
||||
categoryFreeze=Freeze connection configurations
|
||||
categoryFreezeDescription=Marks connection configurations as read-only. This means that no existing connection entry configuration in this category can be modified. New connections can be added though.
|
||||
updateFail=Update installation did not succeed
|
||||
@@ -1635,8 +1636,9 @@ moveToLast=Move to last
|
||||
category=Category
|
||||
includeRoot=Include root
|
||||
excludeRoot=Exclude root
|
||||
freezeConfiguration=Freeze configuration
|
||||
unfreezeConfiguration=Unfreeze configuration
|
||||
templateConfiguration=Make template
|
||||
untemplateConfiguration=Unset template
|
||||
template=Template
|
||||
waylandScalingTitle=Wayland scaling
|
||||
actionApiUrl=$URL$ (Copy json body)
|
||||
copyBody=Copy request body
|
||||
@@ -2010,6 +2012,8 @@ scriptSourceName=Script name
|
||||
scriptSourceNameDescription=The file name of the script in the source
|
||||
workspaceRestartTitle=Workspace ready
|
||||
workspaceRestartContent=The new workspace was created and a desktop shortcut was created for it. You can navigate to the shortcut or restart XPipe now to open the new workspace automatically.
|
||||
#force
|
||||
#context: verb, to browse
|
||||
browseShortcut=Browse shortcut
|
||||
syncModeInstant=Sync instantly
|
||||
syncModeSession=Sync on startup and exit
|
||||
@@ -2274,4 +2278,9 @@ webtopSettingsApplyContent=Do you want to transfer your local XPipe settings to
|
||||
deploy=Deploy
|
||||
#context: verb
|
||||
transferSettings=Transfer
|
||||
webtopDeployStatusPull=Pulling image ...
|
||||
webtopDeployStatusCreate=Creating container ...
|
||||
webtopDeployStatusTest=Testing container ...
|
||||
webtopDeployStatusTransferSettings=Applying state ...
|
||||
saveAs=Save as
|
||||
|
||||
|
||||
Reference in New Issue
Block a user