From 8a06fe4f72f1c22c714c3276edc4fa262510e268 Mon Sep 17 00:00:00 2001 From: crschnick Date: Tue, 20 Jan 2026 03:47:41 +0000 Subject: [PATCH] Rework --- .../xpipe/app/hub/comp/StoreCategoryComp.java | 38 ++++++------ .../app/hub/comp/StoreCategoryWrapper.java | 4 ++ .../app/hub/comp/StoreEntryListComp.java | 15 +++++ .../hub/comp/StoreScriptSourcesIntroComp.java | 29 +++++++++ .../io/xpipe/app/hub/comp/StoreViewState.java | 8 +++ .../io/xpipe/app/storage/DataStorage.java | 4 ++ .../xpipe/ext/base/script/ScriptSource.java | 11 +++- .../ScriptSourceBrowseActionProvider.java | 59 +++++++++++++++++++ .../ext/base/script/ScriptSourceEntry.java | 2 + ext/base/src/main/java/module-info.java | 1 + lang/strings/translations_en.properties | 3 + 11 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/io/xpipe/app/hub/comp/StoreScriptSourcesIntroComp.java create mode 100644 ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceBrowseActionProvider.java diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryComp.java index 9958afb12..93c519e8f 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryComp.java @@ -240,28 +240,24 @@ public class StoreCategoryComp extends SimpleComp { contextMenu.getItems().add(new SeparatorMenuItem()); - var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline")); - StoreViewState.get() - .getSortedCategories(getCategory().getRoot()) - .getList() - .forEach(storeCategoryWrapper -> { - MenuItem m = new MenuItem(); - m.textProperty() - .setValue(" ".repeat(storeCategoryWrapper.getDepth()) - + storeCategoryWrapper.getName().getValue()); - m.setOnAction(event -> { - category.moveToParent(storeCategoryWrapper.getCategory()); - event.consume(); - }); - if (storeCategoryWrapper.getParent() == null - || storeCategoryWrapper.equals(category) - || storeCategoryWrapper.equals(category.getParent())) { - m.setDisable(true); - } - - move.getItems().add(m); + if (category.canMove()) { + var move = new Menu(AppI18n.get("moveTo"), new FontIcon("mdi2f-folder-move-outline")); + StoreViewState.get().getSortedCategories(getCategory().getRoot()).getList().forEach(storeCategoryWrapper -> { + MenuItem m = new MenuItem(); + m.textProperty().setValue(" ".repeat(storeCategoryWrapper.getDepth()) + storeCategoryWrapper.getName().getValue()); + m.setOnAction(event -> { + category.moveToParent(storeCategoryWrapper.getCategory()); + event.consume(); }); - contextMenu.getItems().add(move); + if (storeCategoryWrapper.getParent() == null || storeCategoryWrapper.equals(category) || storeCategoryWrapper.equals( + category.getParent())) { + m.setDisable(true); + } + + move.getItems().add(m); + }); + contextMenu.getItems().add(move); + } var del = new MenuItem(AppI18n.get("remove"), new FontIcon("mdal-delete_outline")); del.setOnAction(event -> { diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryWrapper.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryWrapper.java index 2f1908581..c84101002 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryWrapper.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreCategoryWrapper.java @@ -73,6 +73,10 @@ public class StoreCategoryWrapper { nameProperty()); } + public boolean canMove() { + return DataStorage.get().canMoveStoreCategory(category); + } + public StoreCategoryWrapper getRoot() { return StoreViewState.get().getCategoryWrapper(root); } diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListComp.java index 766bb5a3b..f710e02ae 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreEntryListComp.java @@ -121,6 +121,20 @@ public class StoreEntryListComp extends SimpleComp { }, scriptsIntroShowing, StoreViewState.get().getActiveCategory()); + var showScriptSourcesIntro = Bindings.createBooleanBinding( + () -> { + var cat = StoreViewState.get().getScriptSourcesCategory(); + if (StoreViewState.get() + .getActiveCategory() + .getValue() + .equals(cat)) { + return cat.getAllContainedEntriesCount().get() == 0; + } + + return false; + }, + StoreViewState.get().getAllEntries().getList(), + StoreViewState.get().getActiveCategory()); var showList = Bindings.createBooleanBinding( () -> { if (StoreViewState.get() @@ -160,6 +174,7 @@ public class StoreEntryListComp extends SimpleComp { map.put(createList(), showList); map.put(new StoreIntroComp(), showIntro); map.put(new StoreScriptsIntroComp(scriptsIntroShowing), showScriptsIntro); + map.put(new StoreScriptSourcesIntroComp(), showScriptSourcesIntro); map.put(new StoreIdentitiesIntroComp(), showIdentitiesIntro); return new MultiContentComp(false, map, false).createRegion(); diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreScriptSourcesIntroComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreScriptSourcesIntroComp.java new file mode 100644 index 000000000..615e9aa61 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreScriptSourcesIntroComp.java @@ -0,0 +1,29 @@ +package io.xpipe.app.hub.comp; + +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.base.IntroComp; +import io.xpipe.app.comp.base.IntroListComp; +import io.xpipe.app.core.AppCache; +import io.xpipe.app.ext.DataStoreCreationCategory; +import io.xpipe.app.ext.DataStoreProviders; +import io.xpipe.app.platform.LabelGraphic; +import javafx.beans.property.BooleanProperty; +import javafx.scene.layout.Region; + +import java.util.List; + +public class StoreScriptSourcesIntroComp extends SimpleComp { + + + @Override + public Region createSimple() { + var intro = new IntroComp("scriptSourcesIntro", new LabelGraphic.IconGraphic("mdi2d-download")); + intro.setButtonGraphic(new LabelGraphic.IconGraphic("mdi2p-play-circle")); + intro.setButtonDefault(true); + intro.setButtonAction(() -> { + StoreCreationDialog.showCreation(DataStoreProviders.byId("scriptSource").orElseThrow(), DataStoreCreationCategory.SCRIPT); + }); + var list = new IntroListComp(List.of(intro)); + return list.createRegion(); + } +} diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java index ef7a0a60a..7d231b446 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreViewState.java @@ -616,6 +616,14 @@ public class StoreViewState { .orElseThrow(); } + public StoreCategoryWrapper getScriptSourcesCategory() { + return categories.getList().stream() + .filter(storeCategoryWrapper -> + storeCategoryWrapper.getCategory().getUuid().equals(DataStorage.SCRIPT_SOURCES_CATEGORY_UUID)) + .findFirst() + .orElseThrow(); + } + public StoreCategoryWrapper getAllIdentitiesCategory() { return categories.getList().stream() .filter(storeCategoryWrapper -> diff --git a/app/src/main/java/io/xpipe/app/storage/DataStorage.java b/app/src/main/java/io/xpipe/app/storage/DataStorage.java index 4468bda7a..d2678fd0f 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -1008,6 +1008,10 @@ public abstract class DataStorage { return true; } + public boolean canMoveStoreCategory(@NonNull DataStoreCategory cat) { + return canDeleteStoreCategory(cat); + } + public void deleteStoreCategory(@NonNull DataStoreCategory cat, boolean deleteChildren, boolean deleteEntries) { if (!canDeleteStoreCategory(cat)) { return; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSource.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSource.java index 27a4efd75..14df281b9 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSource.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSource.java @@ -12,6 +12,7 @@ import io.xpipe.app.ext.ValidationException; import io.xpipe.app.icon.SystemIconSource; import io.xpipe.app.issue.ErrorEventFactory; import io.xpipe.app.platform.OptionsBuilder; +import io.xpipe.app.process.ShellDialects; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.Validators; import io.xpipe.core.FilePath; @@ -157,12 +158,20 @@ public interface ScriptSource { String toName(); default List listScripts() throws Exception { + var availableDialects = List.of( + ShellDialects.SH, + ShellDialects.BASH, + ShellDialects.ZSH, + ShellDialects.FISH, + ShellDialects.CMD, + ShellDialects.POWERSHELL, + ShellDialects.POWERSHELL_CORE); var l = new ArrayList(); Files.walkFileTree(getLocalPath(), new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { var name = file.getFileName().toString(); - var dialect = ShellDialectChoiceComp.ICONS.keySet().stream().filter(shellDialect -> { + var dialect = availableDialects.stream().filter(shellDialect -> { return name.endsWith("." + shellDialect.getScriptFileEnding()); }).findFirst(); if (dialect.isEmpty()) { diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceBrowseActionProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceBrowseActionProvider.java new file mode 100644 index 000000000..b667c38a7 --- /dev/null +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceBrowseActionProvider.java @@ -0,0 +1,59 @@ +package io.xpipe.ext.base.script; + +import io.xpipe.app.action.AbstractAction; +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.ext.FileSystemStore; +import io.xpipe.app.hub.action.HubLeafProvider; +import io.xpipe.app.hub.action.StoreAction; +import io.xpipe.app.platform.LabelGraphic; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.util.DesktopHelper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ObservableValue; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +public class ScriptSourceBrowseActionProvider implements HubLeafProvider { + + @Override + public AbstractAction createAction(DataStoreEntryRef ref) { + return Action.builder().ref(ref).build(); + } + + @Override + public boolean isMajor() { + return true; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("browse"); + } + + @Override + public LabelGraphic getIcon(DataStoreEntryRef store) { + return new LabelGraphic.IconGraphic("mdi2f-folder-search-outline"); + } + + @Override + public Class getApplicableClass() { + return ScriptSourceStore.class; + } + + @Override + public String getId() { + return "browseScriptSource"; + } + + @Jacksonized + @SuperBuilder + public static class Action extends StoreAction { + + @Override + public void executeImpl() { + DesktopHelper.browseFile(ref.getStore().getSource().getLocalPath()); + } + } +} diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceEntry.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceEntry.java index 308c7d425..d78d81c22 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceEntry.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptSourceEntry.java @@ -3,11 +3,13 @@ package io.xpipe.ext.base.script; import io.xpipe.app.process.ShellDialect; import lombok.Builder; import lombok.Value; +import lombok.extern.jackson.Jacksonized; import java.nio.file.Path; @Value @Builder +@Jacksonized public class ScriptSourceEntry { String name; diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index 0d44711d6..6edc0dd46 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -44,6 +44,7 @@ open module io.xpipe.ext.base { RunHubScriptActionProvider, RunTerminalScriptActionProvider, ScriptSourceRefreshHubProvider, + ScriptSourceBrowseActionProvider, SimpleScriptQuickEditHubLeafProvider, StoreStartActionProvider, StoreStopActionProvider, diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index 5a61d6161..d6acab829 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -536,6 +536,9 @@ scriptsIntroBottomHeader=Using scripts #force scriptsIntroBottomContent=There are a variety of sample scripts to start out. You can click on the edit button of the individual scripts to see how they are implemented. Scripts first have to be enabled to run and show up in menus, there is a toggle on every script for that. scriptsIntroBottomButton=Get started +scriptSourcesIntroHeader=Script sources +scriptSourcesIntroContent=You can add custom script sources to have instant access to an entire collection of shell scripts. Both local sources and remote git repositories are supported as sources. All detected scripts from the source will become available automatically. +scriptSourcesIntroButton=Add source ... checkForSecurityUpdates=Check for security updates checkForSecurityUpdatesDescription=XPipe can check for potential security updates separately from normal feature updates. When this is enabled, at least important security updates will be recommended for installation even if the normal update check is disabled.\n\nDisabling this setting will result in no external version request being performed, and you won't be notified about any security updates. clickToDock=Click to dock terminal