Color rework and refactor

This commit is contained in:
crschnick 2023-10-08 04:39:53 +00:00
parent ee41792491
commit 65a1ee2e73
27 changed files with 204 additions and 81 deletions

View file

@ -14,6 +14,7 @@ import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.BooleanScope;
import io.xpipe.app.util.ThreadHelper;
import javafx.application.Platform;
@ -262,13 +263,11 @@ public class BrowserComp extends SimpleComp {
PlatformThread.sync(model.getBusy())));
tab.setText(model.getName());
// new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(tab);
tab.setContent(new OpenFileSystemComp(model).createSimple());
var id = UUID.randomUUID().toString();
tab.setId(id);
var found = tabs.lookupAll("tab-header-area");
SimpleChangeListener.apply(tabs.skinProperty(), newValue -> {
if (newValue != null) {
Platform.runLater(() -> {
@ -281,6 +280,11 @@ public class BrowserComp extends SimpleComp {
close.setPrefWidth(30);
StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container");
c.getStyleClass().add("color-box");
var color = DataStorage.get().getRootForEntry(model.getEntry().get()).getColor();
if (color != null) {
c.getStyleClass().add(color.getId());
}
new FancyTooltipAugment<>(new SimpleStringProperty(model.getTooltip())).augment(c);
c.addEventHandler(
DragEvent.DRAG_ENTERED,

View file

@ -68,7 +68,7 @@ public class BrowserWelcomeComp extends SimpleComp {
var view = PrettyImageHelper.ofFixedSquare(graphic, 45);
view.padding(new Insets(2, 8, 2, 8));
var tile = new Tile(
DataStorage.get().getStoreBrowserDisplayName(entry.get()),
DataStorage.get().getStoreDisplayName(entry.get()),
e.getPath(),
view.createRegion());
tile.setActionHandler(() -> {

View file

@ -45,7 +45,7 @@ public final class OpenFileSystemModel {
public OpenFileSystemModel(BrowserModel browserModel, DataStoreEntryRef<? extends FileSystemStore> entry) {
this.browserModel = browserModel;
this.entry = entry;
this.name = DataStorage.get().getStoreBrowserDisplayName(entry.get());
this.name = DataStorage.get().getStoreDisplayName(entry.get());
this.tooltip = DataStorage.get().getId(entry.getEntry()).toString();
this.inOverview.bind(Bindings.createBooleanBinding(
() -> {
@ -363,7 +363,7 @@ public final class OpenFileSystemModel {
fs.open();
this.fileSystem = fs;
this.local =
fs.getShell().map(shellControl -> shellControl.isLocal()).orElse(false);
fs.getShell().map(shellControl -> shellControl.hasLocalSystemAccess()).orElse(false);
this.cache.init();
});
}

View file

@ -17,6 +17,7 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.fxcomps.util.SimpleChangeListener;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreColor;
import io.xpipe.app.update.XPipeDistributionType;
import io.xpipe.app.util.DataStoreFormatter;
@ -355,7 +356,7 @@ public abstract class StoreEntryComp extends SimpleComp {
contextMenu.getItems().add(move);
}
{
if (DataStorage.get().isRootEntry(wrapper.getEntry())) {
var color = new Menu(AppI18n.get("color"), new FontIcon("mdi2f-format-color-fill"));
var none = new MenuItem("None");
none.setOnAction(event -> {

View file

@ -108,18 +108,11 @@ public class StoreSection {
var topLevel = BindingsHelper.filteredContentBinding(
ordered,
section -> {
var noParent = DataStorage.get()
.getDisplayParent(section.getWrapper().getEntry())
.isEmpty();
var sameCategory =
category.getValue().contains(section.getWrapper().getEntry());
var diffParentCategory = DataStorage.get()
.getDisplayParent(section.getWrapper().getEntry())
.map(entry -> !category.getValue().contains(entry))
.orElse(false);
var showFilter = section.shouldShow(filterString.get());
var matchesSelector = section.anyMatches(entryFilter);
return (noParent || diffParentCategory) && showFilter && sameCategory && matchesSelector;
return DataStorage.get().isRootEntry(section.getWrapper().getEntry()) && showFilter && sameCategory && matchesSelector;
},
category,
filterString);

View file

@ -6,7 +6,7 @@ import io.xpipe.app.comp.DeveloperTabComp;
import io.xpipe.app.comp.storage.store.StoreLayoutComp;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.prefs.PrefsComp;
import io.xpipe.app.util.FeatureProvider;
import io.xpipe.app.util.LicenseProvider;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
@ -70,7 +70,7 @@ public class AppLayoutModel {
l.add(new Entry(
AppI18n.observable("explorePlans"),
"mdi2p-professional-hexagon",
FeatureProvider.get().overviewPage()));
LicenseProvider.get().overviewPage()));
return l;
}

View file

@ -244,7 +244,7 @@ public class AppMainWindow {
var contentR = content.createRegion();
contentR.requestFocus();
stage.getScene().setRoot(contentR);
AppTheme.initTheme(stage);
AppTheme.initThemeHandlers(stage);
TrackEvent.debug("Set content scene");
contentR.prefWidthProperty().bind(stage.getScene().widthProperty());
@ -263,7 +263,7 @@ public class AppMainWindow {
if (AppProperties.get().isDeveloperMode() && event.getCode().equals(KeyCode.F6)) {
var newR = content.createRegion();
stage.getScene().setRoot(newR);
AppTheme.initTheme(stage);
AppTheme.initThemeHandlers(stage);
newR.requestFocus();
TrackEvent.debug("Rebuilt content");

View file

@ -15,7 +15,7 @@ import java.util.*;
public class AppStyle {
private static final Map<Path, String> STYLESHEET_CONTENTS = new HashMap<>();
private static final Map<Path, String> STYLESHEET_CONTENTS = new LinkedHashMap<>();
private static final List<Scene> scenes = new ArrayList<>();
private static String FONT_CONTENTS = "";

View file

@ -35,17 +35,18 @@ public class AppTheme {
private static final PseudoClass PRETTY = PseudoClass.getPseudoClass("pretty");
private static final PseudoClass PERFORMANCE = PseudoClass.getPseudoClass("performance");
public static void initTheme(Window stage) {
var t = AppPrefs.get().theme.getValue();
if (t == null) {
return;
}
public static void initThemeHandlers(Window stage) {
SimpleChangeListener.apply(AppPrefs.get().theme, t -> {
Theme.ALL.forEach(theme -> stage.getScene().getRoot().getStyleClass().remove(theme.getCssId()));
if (t == null) {
return;
}
Theme.ALL.forEach(theme -> stage.getScene().getRoot().getStyleClass().remove(theme.getCssId()));
stage.getScene().getRoot().getStyleClass().add(t.getCssId());
stage.getScene().getRoot().getStyleClass().add(t.getCssId());
stage.getScene().getRoot().pseudoClassStateChanged(LIGHT, !t.isDark());
stage.getScene().getRoot().pseudoClassStateChanged(DARK, t.isDark());
});
stage.getScene().getRoot().pseudoClassStateChanged(LIGHT, !t.isDark());
stage.getScene().getRoot().pseudoClassStateChanged(DARK, t.isDark());
SimpleChangeListener.apply(AppPrefs.get().performanceMode(),val -> {
stage.getScene().getRoot().pseudoClassStateChanged(PRETTY, !val);
stage.getScene().getRoot().pseudoClassStateChanged(PERFORMANCE, val);
@ -106,7 +107,6 @@ public class AppTheme {
for (Window window : Window.getWindows()) {
var scene = window.getScene();
Image snapshot = scene.snapshot(null);
initTheme(window);
Pane root = (Pane) scene.getRoot();
ImageView imageView = new ImageView(snapshot);

View file

@ -62,6 +62,7 @@ public class AppWindowHelper {
stage.setTitle(title);
addIcons(stage);
setupContent(stage, contentFunc, bindSize, loading);
AppTheme.initThemeHandlers(stage);
setupStylesheets(stage.getScene());
stage.setOnShown(e -> {

View file

@ -6,7 +6,7 @@ import io.xpipe.app.core.*;
import io.xpipe.app.issue.*;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.util.FeatureProvider;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.FileBridge;
import io.xpipe.app.util.LockedSecretValue;
import io.xpipe.core.store.LocalStore;
@ -41,7 +41,7 @@ public class BaseMode extends OperationMode {
// Load translations before storage initialization to localize store error messages
// Also loaded before antivirus alert to localize that
AppI18n.init();
FeatureProvider.get().init();
LicenseProvider.get().init();
AppAntivirusAlert.showIfNeeded();
LocalStore.init();
AppPrefs.init();

View file

@ -94,7 +94,7 @@ public class StoreCategoryComp extends SimpleComp {
}));
h.apply(new ContextMenuAugment<>(
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, () -> createContextMenu(name)));
h.padding(new Insets(0, 10, 0, (category.getDepth() * 8)));
h.padding(new Insets(0, 10, 0, (category.getDepth() * 10)));
h.styleClass("category-button");
var l = category.getChildren()
.sorted(Comparator.comparing(
@ -102,7 +102,7 @@ public class StoreCategoryComp extends SimpleComp {
var children = new ListBoxViewComp<>(l, l, storeCategoryWrapper -> new StoreCategoryComp(storeCategoryWrapper));
var emptyBinding = Bindings.isEmpty(category.getChildren());
var v = new VerticalComp(List.of(h, Comp.separator().hide(emptyBinding), children.hide(emptyBinding)));
var v = new VerticalComp(List.of(h, Comp.separator().hide(emptyBinding), Comp.vspacer(5).hide(emptyBinding), children.hide(emptyBinding)));
v.styleClass("category");
v.apply(struc -> {
SimpleChangeListener.apply(StoreViewState.get().getActiveCategory(), val -> {

View file

@ -10,7 +10,7 @@ import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.augment.GrowAugment;
import io.xpipe.app.util.JfxHelper;
import io.xpipe.app.util.LicenseException;
import io.xpipe.app.util.LicenseRequiredException;
import io.xpipe.app.util.PlatformState;
import javafx.application.Platform;
import javafx.beans.property.Property;
@ -194,7 +194,7 @@ public class ErrorHandlerComp extends SimpleComp {
actionBox.getStyleClass().add("actions");
actionBox.setFillWidth(true);
if (event.getThrowable() instanceof LicenseException) {
if (event.getThrowable() instanceof LicenseRequiredException) {
event.getCustomActions().add(new ErrorAction() {
@Override
public String getName() {

View file

@ -1,6 +1,8 @@
package io.xpipe.app.issue;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.LicenseRequiredException;
public class GuiErrorHandler extends GuiErrorHandlerBase implements ErrorHandler {
@ -26,6 +28,11 @@ public class GuiErrorHandler extends GuiErrorHandlerBase implements ErrorHandler
return;
}
ErrorHandlerComp.showAndTryWait(event, true);
if (event.getThrowable() instanceof LicenseRequiredException lex) {
LicenseProvider.get().showLicenseAlert(lex);
} else {
ErrorHandlerComp.showAndTryWait(event, true);
}
}
}

View file

@ -200,6 +200,34 @@ public abstract class DataStorage {
saveAsync();
}
public boolean isRootEntry(DataStoreEntry entry) {
var noParent = DataStorage.get()
.getDisplayParent(entry)
.isEmpty();
var diffParentCategory = DataStorage.get()
.getDisplayParent(entry)
.map(p -> !p.getCategoryUuid().equals(entry.getCategoryUuid()))
.orElse(false);
return noParent || diffParentCategory;
}
public DataStoreEntry getRootForEntry(DataStoreEntry entry) {
if (isRootEntry(entry)) {
return entry;
}
var current = entry;
Optional<DataStoreEntry> parent;
while ((parent = getDisplayParent(current)).isPresent()) {
current = parent.get();
if (isRootEntry(current)) {
break;
}
}
return current;
}
public Optional<DataStoreEntry> getDisplayParent(DataStoreEntry entry) {
if (entry.getValidity() == DataStoreEntry.Validity.LOAD_FAILED) {
return Optional.empty();
@ -555,7 +583,7 @@ public abstract class DataStorage {
return findEntry(store).map(dataStoreEntry -> dataStoreEntry.getName());
}
public String getStoreBrowserDisplayName(DataStoreEntry store) {
public String getStoreDisplayName(DataStoreEntry store) {
if (store == null) {
return "?";
}

View file

@ -1,13 +1,13 @@
package io.xpipe.app.storage;
import io.xpipe.app.util.FeatureProvider;
import io.xpipe.app.util.LicenseProvider;
import java.nio.file.Path;
public interface GitStorageHandler {
static GitStorageHandler getInstance() {
return FeatureProvider.get().createStorageHandler();
return LicenseProvider.get().createStorageHandler();
}
boolean supportsShare();

View file

@ -4,7 +4,7 @@ import io.xpipe.app.comp.storage.store.StoreSortMode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.util.FeatureProvider;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.XPipeSession;
import io.xpipe.core.store.LocalStore;
import lombok.Getter;
@ -28,7 +28,7 @@ public class StandardStorage extends DataStorage {
private final GitStorageHandler gitStorageHandler;
StandardStorage() {
this.gitStorageHandler = FeatureProvider.get().createStorageHandler();
this.gitStorageHandler = LicenseProvider.get().createStorageHandler();
this.gitStorageHandler.init(dir);
}
@ -257,6 +257,7 @@ public class StandardStorage extends DataStorage {
e.setDirectory(getStoresDir().resolve(LOCAL_ID.toString()));
e.setConfiguration(
StorageElement.Configuration.builder().deletable(false).build());
e.setColor(DataStoreColor.BLUE);
storeEntries.add(e);
e.validate();
}

View file

@ -38,7 +38,7 @@ public class AppInstaller {
public static void installFile(ShellControl s, InstallerAssetType asset, Path localFile) throws Exception {
String targetFile;
if (s.isLocal()) {
if (s.hasLocalSystemAccess()) {
targetFile = localFile.toString();
} else {
targetFile = FileNames.join(

View file

@ -1,27 +0,0 @@
package io.xpipe.app.util;
public class LicenseException extends RuntimeException {
public LicenseException() {
}
public LicenseException(String message) {
super(message);
}
public LicenseException(String message, Throwable cause) {
super(message, cause);
}
public LicenseException(Throwable cause) {
super(cause);
}
public LicenseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public LicenseException(String featureName, LicenseType min) {
this(featureName + " are only supported with a " + min.name().toLowerCase() + " license");
}
}

View file

@ -2,15 +2,16 @@ package io.xpipe.app.util;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.storage.GitStorageHandler;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.ModuleLayerLoader;
import java.util.ServiceLoader;
public abstract class FeatureProvider {
public abstract class LicenseProvider {
private static FeatureProvider INSTANCE = null;
private static LicenseProvider INSTANCE = null;
public static FeatureProvider get() {
public static LicenseProvider get() {
return INSTANCE;
}
@ -18,7 +19,7 @@ public abstract class FeatureProvider {
@Override
public void init(ModuleLayer layer) {
INSTANCE = ServiceLoader.load(layer, FeatureProvider.class).stream()
INSTANCE = ServiceLoader.load(layer, LicenseProvider.class).stream()
.map(ServiceLoader.Provider::get)
.findFirst().orElseThrow();
}
@ -34,6 +35,10 @@ public abstract class FeatureProvider {
}
}
public abstract void handleShellControl(ShellControl sc);
public abstract void showLicenseAlert(LicenseRequiredException ex);
public abstract LicenseType getLicenseType();
public abstract void init();

View file

@ -0,0 +1,20 @@
package io.xpipe.app.util;
import lombok.EqualsAndHashCode;
import lombok.Value;
@EqualsAndHashCode(callSuper = true)
@Value
public class LicenseRequiredException extends RuntimeException {
String featureName;
boolean plural;
LicenseType minLicense;
public LicenseRequiredException(String featureName, boolean plural, LicenseType minLicense) {
super(featureName + " are only supported with a " + minLicense.name().toLowerCase() + " license");
this.featureName = featureName;
this.plural = plural;
this.minLicense = minLicense;
}
}

View file

@ -4,6 +4,7 @@ import io.xpipe.app.core.AppI18n;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.prefs.ExternalTerminalType;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;
import io.xpipe.core.process.ProcessControl;
@ -22,12 +23,13 @@ public class TerminalHelper {
throw ErrorEvent.unreportable(new IllegalStateException(AppI18n.get("noTerminalSet")));
}
var prefix = entry != null && entry.getColor() != null && type.supportsColoredTitle()
? entry.getColor().getEmoji() + " "
var color = DataStorage.get().getRootForEntry(entry).getColor();
var prefix = entry != null && color != null && type.supportsColoredTitle()
? color.getEmoji() + " "
: "";
var fixedTitle = prefix + (title != null ? title : entry != null ? entry.getName() : "?");
var file = ScriptHelper.createLocalExecScript(cc.prepareTerminalOpen(fixedTitle));
var config = new ExternalTerminalType.LaunchConfiguration(entry != null ? entry.getColor() : null, title, file);
var config = new ExternalTerminalType.LaunchConfiguration(entry != null ? color : null, title, file);
try {
type.launch(config);
} catch (Exception ex) {

View file

@ -8,7 +8,7 @@ import io.xpipe.app.issue.EventHandler;
import io.xpipe.app.issue.EventHandlerImpl;
import io.xpipe.app.storage.DataStateProviderImpl;
import io.xpipe.app.storage.StorageJacksonModule;
import io.xpipe.app.util.FeatureProvider;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.ProxyManagerProviderImpl;
import io.xpipe.app.util.TerminalHelper;
import io.xpipe.core.util.DataStateProvider;
@ -119,14 +119,14 @@ open module io.xpipe.app {
uses ModuleLayerLoader;
uses ScanProvider;
uses BrowserAction;
uses io.xpipe.app.util.FeatureProvider;
uses LicenseProvider;
provides Module with StorageJacksonModule;
provides ModuleLayerLoader with
ActionProvider.Loader,
PrefsProvider.Loader,
BrowserAction.Loader,
FeatureProvider.Loader,
LicenseProvider.Loader,
ScanProvider.Loader;
provides DataStateProvider with
DataStateProviderImpl;

View file

@ -0,0 +1,71 @@
.root:light .color-box.blue {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(130, 130, 250, 0.2) 40%, rgb(57, 57, 200, 0.2) 50%, rgb(137, 137, 250, 0.2) 100%);
-fx-border-color: rgba(80, 100, 150, 0.3);
}
.root:light .color-box.blue > .separator .line {
-fx-border-color: rgba(80, 100, 150, 0.4);
}
.root:dark .color-box.blue {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(30, 30, 80, 0.8) 40%, rgb(27, 27, 65, 0.8) 50%, rgb(37, 37, 100, 0.8) 100%);
-fx-border-color: rgba(80, 100, 150, 0.7);
}
.root:dark .color-box.blue > .separator .line {
-fx-border-color: rgba(80, 100, 150, 0.7);
}
.root:light .color-box.red {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(220, 100, 100, 0.15) 40%, rgb(205, 50, 50, 0.15) 50%, rgb(200, 90, 90, 0.15) 100%);
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:light .color-box.red > .separator .line {
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:dark .color-box.red {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(80, 30, 30, 0.4) 40%, rgb(65, 27, 27, 0.4) 50%, rgb(100, 37, 37, 0.4) 100%);
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:dark .color-box.red > .separator .line {
-fx-border-color: rgba(150, 100, 80, 0.4);
}
.root:light .color-box.yellow {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(180, 180, 30, 0.2) 40%, rgb(135, 135, 27, 0.2) 50%, rgb(200, 200, 37, 0.2) 100%);
-fx-border-color: rgba(170, 170, 80, 0.3);
}
.root:light .color-box.yellow > .separator .line {
-fx-border-color: rgba(170, 170, 80, 0.5);
}
.root:dark .color-box.yellow {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(80, 80, 30, 0.4) 40%, rgb(65, 65, 27, 0.4) 50%, rgb(100, 100, 37, 0.4) 100%);
-fx-border-color: rgba(150, 150, 80, 0.4);
}
.root:dark .color-box.yellow > .separator .line {
-fx-border-color: rgba(170, 170, 80, 0.3);
}
.root:light .color-box.green {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(30, 180, 30, 0.1) 40%, rgb(20, 120, 20, 0.15) 50%, rgb(37, 200, 37, 0.1) 100%);
-fx-border-color: rgba(100, 150, 80, 0.2);
}
.root:light .color-box.green > .separator .line {
-fx-border-color: rgba(100, 150, 80, 0.4);
}
.root:dark .color-box.green {
-fx-background-color: linear-gradient(from 100% 0% to 0% 100% , rgb(30, 80, 30, 0.3) 40%, rgb(20, 60, 20, 0.3) 50%, rgb(37, 100, 37, 0.3) 100%);
-fx-border-color: rgba(100, 190, 80, 0.3);
}
.root:dark .color-box.green > .separator .line {
-fx-border-color: rgba(100, 190, 80, 0.2);
}

View file

@ -0,0 +1,11 @@
.license-required {
-fx-padding: 1em;
-fx-font-family: Roboto;
}
.license-required .message {
-fx-border-width: 1px;
-fx-border-radius: 15px;
-fx-background-radius: 15px;
-fx-padding: 1em;
}

View file

@ -15,10 +15,16 @@ import java.util.function.Predicate;
public interface ShellControl extends ProcessControl {
default boolean isLocal() {
default boolean hasLocalSystemAccess() {
return getSystemId().equals(XPipeSystemId.getLocal());
}
boolean isLocal();
ShellControl changesHosts();
ShellControl getMachineRootSession();
String getOsName();
UUID getSystemId();

View file

@ -25,7 +25,7 @@ public class LaunchAction implements ActionProvider {
@Override
public void execute() throws Exception {
var storeName = entry.getName();
var storeName = DataStorage.get().getStoreDisplayName(entry);
if (entry.getStore() instanceof ShellStore s) {
TerminalHelper.open(entry, storeName, ScriptStore.controlWithDefaultScripts(s.control()));
return;