diff --git a/src/main/frontend/model/Run.ts b/src/main/frontend/model/Run.ts index 3c344eb..f07e667 100644 --- a/src/main/frontend/model/Run.ts +++ b/src/main/frontend/model/Run.ts @@ -104,8 +104,9 @@ export class Run { private async loadMessages(response: Promise) { for await (const message of streamingFetch(await response)) { if (typeof message !== 'string') { - if (message.type === 'file' && message.data) { - message.blob = new Blob([decodeBase64(message.data)], { type: message.mime }); + if (message.type === 'file') { + const data = message.data ? decodeBase64(message.data) : new Uint8Array(0); + message.blob = new Blob([data], { type: message.mime }); delete message.data; } } diff --git a/src/main/frontend/sections/editor/types/EachStep.tsx b/src/main/frontend/sections/editor/types/EachStep.tsx index 4ca1b85..2a559a8 100644 --- a/src/main/frontend/sections/editor/types/EachStep.tsx +++ b/src/main/frontend/sections/editor/types/EachStep.tsx @@ -36,7 +36,14 @@ export const EachStep = forwardRefJEXL Expression

The sub-pipeline given will be repeated for every element this expression produces.

Iterator
-

This is the name of the variable used to address the individual item.

+

+ This is the name of the variable used to address the individual item. +
+ The item index will be available as{' '} + + ${'{'}iterator{'}'}_index + +

); diff --git a/src/main/frontend/sections/output/FileMessageOutput.tsx b/src/main/frontend/sections/output/FileMessageOutput.tsx index 061405c..25eb557 100644 --- a/src/main/frontend/sections/output/FileMessageOutput.tsx +++ b/src/main/frontend/sections/output/FileMessageOutput.tsx @@ -15,7 +15,7 @@ const Elm = styled('div')` border-radius: 12px; display: grid; gap: 6px; - grid-template-areas: 'icon name . download' 'icon type . download'; + grid-template-areas: 'icon name name download' 'icon type size download'; grid-template-columns: max-content max-content 1fr max-content; > coral-icon { grid-area: icon; @@ -28,6 +28,9 @@ const Elm = styled('div')` > .type { grid-area: type; } + > .size { + grid-area: size; + } > a { grid-area: download; } @@ -71,13 +74,15 @@ function iconFor(mimeType: MimeType): CoralIcon { export const FileMessageOutput: FC<{ message: FileMessage }> = ({ message }) => { const icon = iconFor(message.mime); + const blob = message.blob!; return ( {message.name} {message.mime} - + {blob.size} bytes + diff --git a/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java b/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java index e5f31ce..1cc9744 100644 --- a/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java +++ b/src/main/java/com/swisscom/aem/tools/impl/hops/Each.java @@ -6,8 +6,10 @@ import com.swisscom.aem.tools.jcrhopper.context.HopContext; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.annotation.Nonnull; import javax.jcr.Node; import javax.jcr.RepositoryException; @@ -25,25 +27,30 @@ public class Each implements Hop { @Override public void run(Config config, Node node, HopContext context) throws RepositoryException, HopperException { final Object items = context.evaluate(config.expression); + int index = 0; if (items instanceof Iterable) { for (Object item : (Iterable) items) { - runWith(config, item, node, context); + runWith(config, item, index++, node, context); } } else if (items instanceof Iterator) { while (((Iterator) items).hasNext()) { - runWith(config, ((Iterator) items).next(), node, context); + runWith(config, ((Iterator) items).next(), index++, node, context); } } else if (items.getClass().isArray()) { for (Object item : (Object[]) items) { - runWith(config, item, node, context); + runWith(config, item, index++, node, context); } } else { - runWith(config, items, node, context); + runWith(config, items, index, node, context); } } - @SuppressFBWarnings(value = "ITC_INHERITANCE_TYPE_CHECKING", justification = "The item comes from scripting and can be an arbitrary type") - private void runWith(Config config, Object item, Node initialNode, HopContext context) throws HopperException, RepositoryException { + @SuppressFBWarnings( + value = { "ITC_INHERITANCE_TYPE_CHECKING", "STT_TOSTRING_MAP_KEYING" }, + justification = "The item comes from scripting and can be an arbitrary type. Dynamic Lookup Required." + ) + private void runWith(Config config, Object item, int index, Node initialNode, HopContext context) + throws HopperException, RepositoryException { Node node = initialNode; if (config.assumeNodes) { if (item instanceof Node) { @@ -59,7 +66,10 @@ private void runWith(Config config, Object item, Node initialNode, HopContext co } else { context.debug("Iterating non-node value {} accessible as {}", item, config.iterator); } - context.runHops(node, config.hops, Collections.singletonMap(config.iterator, item)); + final Map vars = new HashMap<>(); + vars.put(config.iterator, item); + vars.put(config.iterator + "_index", index); + context.runHops(node, config.hops, vars); } @Nonnull