diff --git a/app/src/main/java/io/xpipe/app/core/window/AppDialog.java b/app/src/main/java/io/xpipe/app/core/window/AppDialog.java index 39d378fa1..f526c2bba 100644 --- a/app/src/main/java/io/xpipe/app/core/window/AppDialog.java +++ b/app/src/main/java/io/xpipe/app/core/window/AppDialog.java @@ -23,6 +23,7 @@ import lombok.Getter; import org.int4.fx.builders.common.AbstractRegionBuilder; import io.xpipe.app.comp.BaseRegionBuilder; +import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; public class AppDialog { @@ -30,6 +31,14 @@ public class AppDialog { @Getter private static final ObservableList modalOverlays = FXCollections.observableArrayList(); + public static Optional getCurrentModalOverlay() { + if (modalOverlays.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(modalOverlays.getLast()); + } + public static void closeDialog(ModalOverlay overlay) { PlatformThread.runLaterIfNeeded(() -> { synchronized (modalOverlays) { 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 67038b565..8808270fc 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 @@ -65,7 +65,11 @@ public class StoreCategoryComp extends SimpleRegionBuilder { category.getName().setValue(newValue); } }); - var name = new LazyTextFieldComp(prop).style("name").build(); + var name = new LazyTextFieldComp(prop).style("name").apply(sp -> { + category.getRenameTrigger().onFire(() -> { + sp.requestFocus(); + }); + }).build(); var showing = new SimpleBooleanProperty(); var expandIcon = Bindings.createObjectBinding( @@ -121,7 +125,7 @@ public class StoreCategoryComp extends SimpleRegionBuilder { }) .apply(new ContextMenuAugment<>( mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, null, () -> { - var cm = createContextMenu(name); + var cm = createContextMenu(); showing.bind(cm.showingProperty()); return cm; })) @@ -158,7 +162,7 @@ public class StoreCategoryComp extends SimpleRegionBuilder { categoryButton.apply(new ContextMenuAugment<>( mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, keyEvent -> keyEvent.getCode() == KeyCode.SPACE, - () -> createContextMenu(name))); + () -> createContextMenu())); categoryButton.apply(struc -> { struc.addEventFilter(KeyEvent.KEY_PRESSED, event -> { if (event.getCode() == KeyCode.SPACE) { @@ -200,7 +204,7 @@ public class StoreCategoryComp extends SimpleRegionBuilder { return v.build(); } - private ContextMenu createContextMenu(Region text) { + private ContextMenu createContextMenu() { var contextMenu = MenuHelper.createContextMenu(); if (AppPrefs.get().enableHttpApi().get()) { @@ -219,9 +223,7 @@ public class StoreCategoryComp extends SimpleRegionBuilder { var newCategory = new MenuItem(AppI18n.get("createNewCategory"), new FontIcon("mdi2p-plus-thick")); newCategory.setOnAction(event -> { - DataStorage.get() - .addStoreCategory( - DataStoreCategory.createNew(category.getCategory().getUuid(), AppI18n.get("newCategory"))); + StoreViewState.get().createNewCategory(category); }); contextMenu.getItems().add(newCategory); @@ -235,8 +237,8 @@ public class StoreCategoryComp extends SimpleRegionBuilder { var rename = new MenuItem(AppI18n.get("rename"), new FontIcon("mdal-edit")); rename.setOnAction(event -> { - text.setDisable(false); - text.requestFocus(); + category.getRenameTrigger().fire(null); + event.consume(); }); contextMenu.getItems().add(rename); 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 c84101002..6216b47b7 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 @@ -13,6 +13,7 @@ import javafx.beans.property.*; import javafx.beans.value.ObservableStringValue; import lombok.Getter; +import org.int4.fx.values.util.Trigger; import java.time.Duration; import java.time.Instant; @@ -35,6 +36,7 @@ public class StoreCategoryWrapper { private final BooleanProperty expanded = new SimpleBooleanProperty(); private final Property color = new SimpleObjectProperty<>(); private final BooleanProperty largeCategoryOptimizations = new SimpleBooleanProperty(); + private final Trigger renameTrigger = Trigger.of(); private StoreCategoryWrapper cachedParent; public StoreCategoryWrapper(DataStoreCategory category) { diff --git a/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java b/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java index 47937cd07..cbfb4716a 100644 --- a/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java +++ b/app/src/main/java/io/xpipe/app/hub/comp/StoreChoiceComp.java @@ -166,6 +166,7 @@ public class StoreChoiceComp extends SimpleRegionBuilder { AnchorPane.setBottomAnchor(struc, 3.0); }); pane.getChildren().add(clearButton.build()); + pane.getStyleClass().add("store-choice-comp"); return pane; } 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 7d231b446..b14606463 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 @@ -1,6 +1,7 @@ package io.xpipe.app.hub.comp; import io.xpipe.app.core.AppCache; +import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.mode.AppOperationMode; import io.xpipe.app.ext.DataStoreUsageCategory; import io.xpipe.app.issue.ErrorEventFactory; @@ -12,6 +13,7 @@ import io.xpipe.app.storage.DataStoreCategory; import io.xpipe.app.storage.DataStoreEntry; import io.xpipe.app.storage.StorageListener; +import io.xpipe.app.util.GlobalTimer; import javafx.application.Platform; import javafx.beans.Observable; import javafx.beans.binding.Bindings; @@ -23,6 +25,7 @@ import javafx.collections.ListChangeListener; import lombok.Getter; +import java.time.Duration; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -344,6 +347,18 @@ public class StoreViewState { }); } + public void createNewCategory(StoreCategoryWrapper parent) { + var cat = DataStoreCategory.createNew(parent.getCategory().getUuid(), AppI18n.get("newCategory")); + DataStorage.get().addStoreCategory(cat); + // Ugly solution to ensure that the category is added to the scene + GlobalTimer.delay(() -> { + var wrapper = getCategoryWrapper(cat); + Platform.runLater(() -> { + wrapper.getRenameTrigger().fire(null); + }); + }, Duration.ofMillis(500)); + } + private void addListeners() { if (AppPrefs.get() != null) { AppPrefs.get().condenseConnectionDisplay().addListener((observable, oldValue, newValue) -> { diff --git a/app/src/main/java/io/xpipe/app/process/ShellScript.java b/app/src/main/java/io/xpipe/app/process/ShellScript.java index 0c0b9ade8..fa0622149 100644 --- a/app/src/main/java/io/xpipe/app/process/ShellScript.java +++ b/app/src/main/java/io/xpipe/app/process/ShellScript.java @@ -11,6 +11,10 @@ public class ShellScript { String value; + public static ShellScript empty() { + return new ShellScript(""); + } + public static ShellScript of(String s) { return s != null ? new ShellScript(s) : null; } 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 d2678fd0f..e34d39255 100644 --- a/app/src/main/java/io/xpipe/app/storage/DataStorage.java +++ b/app/src/main/java/io/xpipe/app/storage/DataStorage.java @@ -208,15 +208,6 @@ public abstract class DataStorage { localIdentities.get().setParentCategory(ALL_IDENTITIES_CATEGORY_UUID); } - // var allMacros = getStoreCategoryIfPresent(ALL_MACROS_CATEGORY_UUID); - // if (allMacros.isEmpty()) { - // var cat = DataStoreCategory.createNew(null, ALL_MACROS_CATEGORY_UUID, "All macros"); - // cat.setDirectory(categoriesDir.resolve(ALL_MACROS_CATEGORY_UUID.toString())); - // storeCategories.add(cat); - // } else { - // allMacros.get().setParentCategory(null); - // } - if (supportsSync()) { var sharedIdentities = getStoreCategoryIfPresent(SYNCED_IDENTITIES_CATEGORY_UUID); if (sharedIdentities.isEmpty()) { @@ -230,17 +221,13 @@ public abstract class DataStorage { } } - if (getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID).isEmpty()) { - storeCategories.add(new DataStoreCategory( - categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()), - DEFAULT_CATEGORY_UUID, - "Default", - Instant.now(), - Instant.now(), - true, - ALL_CONNECTIONS_CATEGORY_UUID, - true, - DataStoreCategoryConfig.empty())); + var def = getStoreCategoryIfPresent(DEFAULT_CATEGORY_UUID); + if (def.isEmpty()) { + DataStoreCategory cat = new DataStoreCategory(categoriesDir.resolve(DEFAULT_CATEGORY_UUID.toString()), DEFAULT_CATEGORY_UUID, + "Default", Instant.now(), Instant.now(), true, ALL_CONNECTIONS_CATEGORY_UUID, true, DataStoreCategoryConfig.empty()); + storeCategories.add(cat); + } else { + def.get().setParentCategory(ALL_CONNECTIONS_CATEGORY_UUID); } storeCategories.forEach(dataStoreCategory -> { diff --git a/app/src/main/resources/io/xpipe/app/resources/style/choice-comp.css b/app/src/main/resources/io/xpipe/app/resources/style/choice-comp.css index bbc138478..080f77f6a 100644 --- a/app/src/main/resources/io/xpipe/app/resources/style/choice-comp.css +++ b/app/src/main/resources/io/xpipe/app/resources/style/choice-comp.css @@ -23,6 +23,11 @@ -fx-icon-color: -color-fg-default; } +.store-choice-comp.left-pill .choice-comp { + -fx-background-radius: 4 0 0 4; + -fx-border-radius: 4 0 0 4; +} + .choice-comp-content > .top { -fx-padding: 0.4em; -fx-background-color: -color-neutral-subtle; diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportDialog.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportDialog.java index b141ff1a4..11b9c0d49 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportDialog.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportDialog.java @@ -36,7 +36,7 @@ import java.util.UUID; public class ScriptCollectionSourceImportDialog { - private final ScriptCollectionSource source; + private final DataStoreEntryRef source; private final ObservableList available = FXCollections.observableArrayList(); private final ObservableList shown = FXCollections.observableArrayList(); private final ObservableList selected = FXCollections.observableArrayList(); @@ -45,9 +45,9 @@ public class ScriptCollectionSourceImportDialog { private final IntegerProperty count = new SimpleIntegerProperty(); private final ObjectProperty> targetGroup = new SimpleObjectProperty<>(); - public ScriptCollectionSourceImportDialog(ScriptCollectionSource source) { + public ScriptCollectionSourceImportDialog(DataStoreEntryRef source) { this.source = source; - available.setAll(source.listScripts()); + available.setAll(source.getStore().getSource().listScripts()); update(); filter.addListener((observable, oldValue, newValue) -> { @@ -74,8 +74,8 @@ public class ScriptCollectionSourceImportDialog { var refresh = new ButtonComp(null, new FontIcon("mdmz-refresh"), () -> { ThreadHelper.runAsync(() -> { try (var ignored = new BooleanScope(busy).exclusive().start()) { - source.prepare(); - var all = source.listScripts(); + source.getStore().getSource().prepare(); + var all = source.getStore().getSource().listScripts(); available.setAll(all); update(); } catch (Exception e) { @@ -151,7 +151,7 @@ public class ScriptCollectionSourceImportDialog { var added = new ArrayList(); for (ScriptCollectionSourceEntry e : selected) { var name = FilePath.of(e.getName()).getBaseName().toString(); - var textSource = ScriptTextSource.SourceReference.builder().entry(e).build(); + var textSource = ScriptTextSource.SourceReference.builder().ref(source).name(e.getName()).build(); var alreadyAdded = DataStorage.get().getStoreEntries().stream().anyMatch(entry -> entry.getStore() instanceof ScriptStore ss && textSource.equals(ss.getTextSource())); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportHubProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportHubProvider.java index b076ebee8..94064f674 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportHubProvider.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceImportHubProvider.java @@ -48,7 +48,7 @@ public class ScriptCollectionSourceImportHubProvider implements HubLeafProvider< @Override public void executeImpl() { - var dialog = new ScriptCollectionSourceImportDialog(ref.getStore().getSource()); + var dialog = new ScriptCollectionSourceImportDialog(ref); dialog.show(); } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStore.java index 6fe82d286..a6496ff5e 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptCollectionSourceStore.java @@ -32,7 +32,7 @@ public class ScriptCollectionSourceStore implements DataStore, StatefulDataStore @Override public void validate() throws Exception { - source.prepare(); + refresh(); } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java index 8f5092732..f45eeecce 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStore.java @@ -13,6 +13,7 @@ import io.xpipe.app.util.Validators; import com.fasterxml.jackson.annotation.JsonTypeName; import lombok.Singular; +import lombok.SneakyThrows; import lombok.Value; import lombok.experimental.SuperBuilder; import lombok.extern.jackson.Jacksonized; @@ -75,9 +76,15 @@ public class ScriptStore implements SelfReferentialStore, StatefulDataStore ref : all) { + ref.getStore().getTextSource().checkAvailable(); + } + var r = all.stream() .map(ref -> ref.getStore().assembleScript(shellControl, args)) .filter(s -> s != null) @@ -91,6 +98,7 @@ public class ScriptStore implements SelfReferentialStore, StatefulDataStore s != null).toList()); - if (!suffix.isEmpty()) { - suffix = "(" + suffix + ")"; - } else { - suffix = null; - } - return new SimpleStringProperty(DataStoreFormatter.join(type, suffix)); + var generic = st.getMinimumDialect() == null ? AppI18n.get("genericScript") : null; + var dialect = st.getMinimumDialect() != null ? st.getMinimumDialect().getScriptFileEnding() : null; + return new ReadOnlyObjectWrapper<>(new StoreStateFormat( + List.of(), st.getTextSource().toSummary(), init, file, shell, runnable, generic, dialect).format()); } @SneakyThrows diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java index a1d182152..4d0c8f73f 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptStoreSetup.java @@ -6,6 +6,7 @@ import io.xpipe.app.process.*; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.core.FilePath; +import lombok.SneakyThrows; import java.util.*; @@ -101,6 +102,7 @@ public class ScriptStoreSetup { } } + @SneakyThrows private static FilePath initScriptsDirectory(ShellControl sc, List> refs) throws Exception { if (refs.isEmpty()) { @@ -138,6 +140,10 @@ public class ScriptStoreSetup { } sc.view().mkdir(targetDir); + for (DataStoreEntryRef ref : refs) { + ref.getStore().getTextSource().checkAvailable(); + } + var d = sc.getShellDialect(); for (DataStoreEntryRef scriptStore : refs) { var src = scriptStore.getStore().getTextSource(); diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptTextSource.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptTextSource.java index 87723d964..269d9388c 100644 --- a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptTextSource.java +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptTextSource.java @@ -4,18 +4,21 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; -import io.xpipe.app.comp.base.ContextualFileReferenceChoiceComp; +import io.xpipe.app.comp.base.ButtonComp; +import io.xpipe.app.comp.base.InputGroupComp; import io.xpipe.app.comp.base.IntegratedTextAreaComp; import io.xpipe.app.core.AppCache; -import io.xpipe.app.core.AppI18n; -import io.xpipe.app.ext.ProcessControlProvider; +import io.xpipe.app.core.window.AppDialog; import io.xpipe.app.ext.ShellDialectChoiceComp; import io.xpipe.app.ext.ValidationException; +import io.xpipe.app.hub.comp.StoreChoiceComp; +import io.xpipe.app.hub.comp.StoreViewState; import io.xpipe.app.issue.ErrorEventFactory; +import io.xpipe.app.platform.LabelGraphic; import io.xpipe.app.platform.OptionsBuilder; import io.xpipe.app.process.ShellDialect; import io.xpipe.app.process.ShellScript; -import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.DocumentationLink; import io.xpipe.app.util.HttpHelper; import io.xpipe.app.util.Validators; @@ -23,7 +26,6 @@ import io.xpipe.core.FilePath; import io.xpipe.core.UuidHelper; import javafx.beans.binding.Bindings; import javafx.beans.property.Property; -import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import lombok.Builder; @@ -33,10 +35,9 @@ import lombok.extern.jackson.Jacksonized; import java.io.IOException; import java.net.URI; -import java.net.http.HttpClient; +import java.net.URISyntaxException; import java.net.http.HttpRequest; import java.net.http.HttpResponse; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -45,8 +46,8 @@ import java.util.List; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = ScriptTextSource.InPlace.class), - @JsonSubTypes.Type(value = ScriptTextSource.Url.class), - @JsonSubTypes.Type(value = ScriptTextSource.SourceReference.class) + @JsonSubTypes.Type(value = ScriptTextSource.SourceReference.class), + @JsonSubTypes.Type(value = ScriptTextSource.Url.class) }) public interface ScriptTextSource { @@ -94,9 +95,12 @@ public interface ScriptTextSource { Validators.nonNull(text); } + @Override + public void checkAvailable() throws Exception {} + @Override public String toSummary() { - return dialect.getDisplayName(); + return null; } } @@ -108,7 +112,6 @@ public interface ScriptTextSource { ShellDialect dialect; String url; - ShellScript lastText; @SuppressWarnings("unused") public static String getOptionsNameKey() { @@ -132,7 +135,7 @@ public interface ScriptTextSource { () -> Url.builder().dialect(dialect.get()).url(url.get()).build(), property); } - private void prepare() throws Exception { + public void refresh() throws Exception { var path = getLocalPath(); if (Files.exists(path)) { return; @@ -163,18 +166,31 @@ public interface ScriptTextSource { @Override public void checkComplete() throws ValidationException { Validators.nonNull(url); - Validators.nonNull(lastText); + } + + @Override + public void checkAvailable() throws Exception { + if (!Files.exists(getLocalPath())) { + throw ErrorEventFactory.expected(new IllegalStateException("Script URL " + url + " has not been initialized")); + } } @Override public String toSummary() { - return url; + try { + var uri = URI.create(url); + return FilePath.of(uri.getPath()).getFileName(); + } catch (IllegalArgumentException ignored) { + return null; + } } @Override @SneakyThrows public ShellScript getText() { - return lastText; + var path = getLocalPath(); + var s = Files.readString(path); + return ShellScript.of(s); } } @@ -184,7 +200,8 @@ public interface ScriptTextSource { @Builder class SourceReference implements ScriptTextSource { - ScriptCollectionSourceEntry entry; + DataStoreEntryRef ref; + String name; @SuppressWarnings("unused") public static String getOptionsNameKey() { @@ -193,38 +210,96 @@ public interface ScriptTextSource { @SuppressWarnings("unused") static OptionsBuilder createOptions(Property property) { - var entry = new SimpleObjectProperty<>(property.getValue().getEntry()); + var ref = new SimpleObjectProperty<>(property.getValue().getRef()); + var name = new SimpleStringProperty(property.getValue().getName()); - return new OptionsBuilder().bind( - () -> SourceReference.builder().entry(entry.get()).build(), property); + var sourceChoice = new StoreChoiceComp<>(null, ref, ScriptCollectionSourceStore.class, + ignored -> true, + StoreViewState.get().getAllScriptsCategory(), + StoreViewState.get().getScriptSourcesCategory()); + + var importButton = new ButtonComp(null, new LabelGraphic.IconGraphic("mdi2i-import"), () -> { + var current = AppDialog.getCurrentModalOverlay(); + current.ifPresent(modalOverlay -> modalOverlay.close()); + + var dialog = new ScriptCollectionSourceImportDialog(ref.get()); + dialog.show(); + }); + importButton.disable(ref.isNull()); + + return new OptionsBuilder() + .nameAndDescription("scriptCollectionSourceType") + .addComp(new InputGroupComp(List.of(sourceChoice, importButton)).setMainReference(0), ref) + .nonNull() + .nameAndDescription("scriptSourceName") + .addString(name) + .nonNull() + .bind( + () -> SourceReference.builder().ref(ref.getValue()).name(name.getValue()).build(), property); } @Override + @SneakyThrows public void checkComplete() throws ValidationException { - Validators.nonNull(entry); - entry.getSource().checkComplete(); + Validators.nonNull(ref); + ref.checkComplete(); + } + + @Override + public void checkAvailable() throws Exception { + var cached = ref.getStore().getState().getEntries(); + if (cached == null) { + throw ErrorEventFactory.expected(new IllegalStateException("Source " + ref.get().getName() + " has not been initialized")); + } + + var found = cached.stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null); + if (found == null) { + throw ErrorEventFactory.expected(new IllegalStateException("Script " + name + " not found in local source " + ref.get().getName())); + } } @Override public String toSummary() { - return entry.getName(); + return ref.get().getName() + "/" + name; } @Override public ShellDialect getDialect() { - return entry.getDialect(); + var found = findSourceEntryIfPossible(); + return found != null ? found.getDialect() : null; } @Override @SneakyThrows public ShellScript getText() { - var r = Files.readString(entry.getLocalFile()); + var found = findSourceEntryIfPossible(); + if (found == null) { + return ShellScript.empty(); + } + + var r = Files.readString(found.getLocalFile()); return ShellScript.of(r); } + + private ScriptCollectionSourceEntry findSourceEntryIfPossible() { + var cached = ref.getStore().getState().getEntries(); + if (cached == null) { + return null; + } + + var found = cached.stream().filter(e -> e.getName().equals(name)).findFirst().orElse(null); + if (found == null) { + return null; + } + + return found; + } } void checkComplete() throws ValidationException; + void checkAvailable() throws Exception; + String toSummary(); ShellDialect getDialect(); @@ -234,8 +309,8 @@ public interface ScriptTextSource { static List> getClasses() { var l = new ArrayList>(); l.add(InPlace.class); - l.add(Url.class); l.add(SourceReference.class); + l.add(Url.class); return l; } } diff --git a/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptUrlSourceRefreshHubProvider.java b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptUrlSourceRefreshHubProvider.java new file mode 100644 index 000000000..eca3835be --- /dev/null +++ b/ext/base/src/main/java/io/xpipe/ext/base/script/ScriptUrlSourceRefreshHubProvider.java @@ -0,0 +1,60 @@ +package io.xpipe.ext.base.script; + +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.hub.action.HubLeafProvider; +import io.xpipe.app.hub.action.StoreAction; +import io.xpipe.app.hub.action.StoreActionCategory; +import io.xpipe.app.platform.LabelGraphic; +import io.xpipe.app.storage.DataStoreEntryRef; +import javafx.beans.value.ObservableValue; +import lombok.experimental.SuperBuilder; +import lombok.extern.jackson.Jacksonized; + +public class ScriptUrlSourceRefreshHubProvider implements HubLeafProvider { + + @Override + public StoreActionCategory getCategory() { + return StoreActionCategory.CUSTOM; + } + + @Override + public boolean isApplicable(DataStoreEntryRef o) { + return o.getStore().getTextSource() instanceof ScriptTextSource.Url; + } + + @Override + public boolean isMajor() { + return true; + } + + @Override + public ObservableValue getName(DataStoreEntryRef store) { + return AppI18n.observable("refreshSource"); + } + + @Override + public LabelGraphic getIcon(DataStoreEntryRef store) { + return new LabelGraphic.IconGraphic("mdi2r-refresh"); + } + + @Override + public Class getApplicableClass() { + return ScriptStore.class; + } + + @Override + public String getId() { + return "refreshScriptUrlSource"; + } + + @Jacksonized + @SuperBuilder + public static class Action extends StoreAction { + + @Override + public void executeImpl() throws Exception { + var url = (ScriptTextSource.Url) ref.getStore().getTextSource(); + url.refresh(); + } + } +} diff --git a/ext/base/src/main/java/module-info.java b/ext/base/src/main/java/module-info.java index 5c2864f83..7f8200f64 100644 --- a/ext/base/src/main/java/module-info.java +++ b/ext/base/src/main/java/module-info.java @@ -43,7 +43,7 @@ open module io.xpipe.ext.base { RunBackgroundScriptActionProvider, RunHubBatchScriptActionProvider, RunHubScriptActionProvider, - RunTerminalScriptActionProvider, ScriptCollectionSourceImportHubProvider, ScriptCollectionSourceRefreshHubProvider, ScriptCollectionSourceBrowseActionProvider, ScriptQuickEditHubLeafProvider, + RunTerminalScriptActionProvider, ScriptCollectionSourceImportHubProvider, ScriptUrlSourceRefreshHubProvider, ScriptCollectionSourceRefreshHubProvider, ScriptCollectionSourceBrowseActionProvider, ScriptQuickEditHubLeafProvider, StoreStartActionProvider, StoreStopActionProvider, StorePauseActionProvider, diff --git a/lang/strings/translations_en.properties b/lang/strings/translations_en.properties index d185cbb11..b7b20be39 100644 --- a/lang/strings/translations_en.properties +++ b/lang/strings/translations_en.properties @@ -679,7 +679,8 @@ scriptGroup.displayName=Script group scriptGroup.displayDescription=Group scripts together and organize them within scriptGroup=Group scriptGroupDescription=The group to assign this script to -scriptGroupGroupDescription=The group to assign this script group to +#force +scriptGroupGroupDescription=The optional parent group to assign this script group to openInNewTab=Open in new tab executeInBackground=in background executeInTerminal=in $TERM$ @@ -778,7 +779,8 @@ shell=Shell hub=Hub #context: Computer script script=script -genericScript=Generic script +#force +genericScript=Generic archiveName=Archive name compress=Compress compressContents=Compress contents @@ -1922,9 +1924,9 @@ scriptTextSourceUrl=Script URL scriptTextSourceUrlDescription=The URL to retrieve the script file from scriptSourceType=Script source scriptSourceTypeDescription=From where to source the script -scriptSourceTypeInPlace=In-place -scriptSourceTypeUrl=URL -scriptSourceTypeSource=Source +scriptSourceTypeInPlace=In-place script +scriptSourceTypeUrl=External URL +scriptSourceTypeSource=Script collection source importScripts=Import scripts scriptsContained=$NUMBER$ scripts scriptSourceCollectionImportTitle=Import scripts from source ($COUNT$) @@ -1932,3 +1934,5 @@ noScriptsFound=No scripts found tunnel=Tunnel notInitialized=Not initialized selectCategory=Select category ... +scriptSourceName=Script name +scriptSourceNameDescription=The file name of the script in the source