diff --git a/.changelog/19452.txt b/.changelog/19452.txt new file mode 100644 index 00000000000..3be80b92bb2 --- /dev/null +++ b/.changelog/19452.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: when an Action has long output, anchor to the latest messages +``` diff --git a/ui/app/components/action-card.hbs b/ui/app/components/action-card.hbs index ba4d797fb56..7bd8db9ad8e 100644 --- a/ui/app/components/action-card.hbs +++ b/ui/app/components/action-card.hbs @@ -71,7 +71,12 @@
Error: {{this.instance.error}}
{{/if}} {{#if this.instance.messages.length}} -
{{this.instance.messages}}
+ +
+          {{this.instance.messages}}
+        
+
+ {{else}} {{#if (eq this.instance.state "complete")}}

Action completed with no output

diff --git a/ui/app/components/action-card.js b/ui/app/components/action-card.js index 5f129695f6c..b02ed044533 100644 --- a/ui/app/components/action-card.js +++ b/ui/app/components/action-card.js @@ -48,4 +48,30 @@ export default class ActionCardComponent extends Component { // Either the passed instance, or the peer-selected instance return this.selectedPeer || this.args.instance; } + + @tracked hasBeenAnchored = false; + + /** + * Runs from the action-card template whenever instance.messages updates, + * and serves to keep the user's view anchored to the bottom of the messages. + * This uses a hidden element and the overflow-anchor css attribute, which + * keeps the element visible within the scrollable block parent. + * A trick here is that, if the user scrolls up from the bottom of the block, + * we don't want to force them down to the bottom again on update, but we do + * want to keep them there by default (so they have the latest output). + * The hasBeenAnchored flag is used to track this state, and we do a little + * trick when the messages get long enough to cause a scroll to start the + * anchoring process here. + * + * @param {HTMLElement} element + */ + @action anchorToBottom(element) { + if (this.hasBeenAnchored) return; + const parentHeight = element.parentElement.clientHeight; + const elementHeight = element.clientHeight; + if (elementHeight > parentHeight) { + this.hasBeenAnchored = true; + element.parentElement.scroll(0, elementHeight); + } + } } diff --git a/ui/app/styles/components/actions.scss b/ui/app/styles/components/actions.scss index cca1e695c3c..017650121f5 100644 --- a/ui/app/styles/components/actions.scss +++ b/ui/app/styles/components/actions.scss @@ -117,14 +117,30 @@ } .messages { + width: 100%; overflow: hidden; - code > pre { - height: 200px; + code { background-color: #0a0a0a; color: whitesmoke; + display: block; + overflow: auto; + height: 200px; border-radius: 6px; resize: vertical; + pre { + background-color: transparent; + color: unset; + overflow-anchor: none; + min-height: 100%; + white-space: pre-wrap; + } + .anchor { + overflow-anchor: auto; + height: 1px; + margin-top: -1px; + visibility: hidden; + } } }