Skip to content

Commit 7e15d51

Browse files
authored
Move all log autoscroll logic into component (#2106)
1 parent f574b09 commit 7e15d51

File tree

5 files changed

+82
-76
lines changed

5 files changed

+82
-76
lines changed

src/components/cylc/log/Log.vue

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
-->
1717

1818
<template>
19-
<div ref="wrapper">
20-
<pre data-cy="log-text"><span
19+
<div
20+
ref="scrollWrapper"
21+
class="h-100 overflow-auto px-4 pb-2"
22+
>
23+
<pre ref="logText" data-cy="log-text"><span
2124
v-for="(log, index) in computedLogs"
2225
:key="index"
2326
:class="wordWrap ? 'text-pre-wrap' : 'text-pre'"
@@ -26,21 +29,24 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2629
v-if="logs.length"
2730
position="fixed"
2831
location="bottom right"
29-
class="ma-3"
32+
class="ma-5"
3033
@click="scrollToTop"
3134
:icon="$options.icons.mdiMouseMoveUp"
35+
data-cy="log-scroll-top"
3236
/>
3337
<!-- a div to use for autoscrolling -->
3438
<div ref="autoScrollEnd"></div>
3539
</div>
3640
</template>
3741

3842
<script>
39-
import { useTemplateRef, watch, onBeforeUnmount } from 'vue'
43+
import { useTemplateRef, watch, onBeforeUnmount, nextTick } from 'vue'
44+
import { useScroll, useVModel, whenever } from '@vueuse/core'
4045
import { when } from '@/utils'
4146
import {
4247
mdiMouseMoveUp
4348
} from '@mdi/js'
49+
4450
export default {
4551
name: 'LogComponent',
4652
@@ -70,28 +76,51 @@ export default {
7076
}
7177
},
7278
73-
setup (props) {
74-
const wrapperRef = useTemplateRef('wrapper')
79+
emits: [
80+
'update:autoScroll'
81+
],
82+
83+
setup (props, { emit }) {
84+
const logText = useTemplateRef('logText')
85+
const scrollWrapper = useTemplateRef('scrollWrapper')
7586
const autoScrollEndRef = useTemplateRef('autoScrollEnd')
7687
88+
const autoScroll = useVModel(props, 'autoScroll', emit)
89+
const { arrivedState, directions } = useScroll(scrollWrapper)
90+
91+
// Turn on autoscroll when user scrolls to bottom:
92+
whenever(() => arrivedState.bottom && !arrivedState.top, () => {
93+
// (when page first loads both top and bottom are true)
94+
autoScroll.value = true
95+
})
96+
// Turn off autoscroll when user scrolls up:
97+
whenever(() => props.logs.length && directions.top, () => {
98+
autoScroll.value = false
99+
})
100+
77101
function scrollToEnd () {
78102
autoScrollEndRef.value?.scrollIntoView({ behavior: 'smooth' })
79103
}
80104
81-
function scrollToTop () {
82-
wrapperRef.value?.scrollIntoView({ behavior: 'smooth' })
105+
async function scrollToTop () {
106+
autoScroll.value = false
107+
// Wait for smooth scroll cancel to happen
108+
await nextTick()
109+
scrollWrapper.value?.scroll({ top: 0, left: 0, behavior: 'smooth' })
83110
}
84111
85112
const ro = new ResizeObserver(scrollToEnd)
86113
87-
when(wrapperRef, () => {
114+
when(logText, () => {
88115
watch(
89-
() => props.autoScroll,
116+
autoScroll,
90117
(val) => {
91118
if (val) {
92119
scrollToEnd()
93-
ro.observe(wrapperRef.value)
120+
ro.observe(logText.value)
94121
} else {
122+
// When autoscroll is turned off, cancel any smooth scroll in progress:
123+
scrollWrapper.value.scrollBy(0, 0)
95124
ro.disconnect()
96125
}
97126
},

src/views/Log.vue

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -139,49 +139,40 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
139139
/>
140140
</v-col>
141141
</v-row>
142+
<v-alert
143+
v-if="results.error"
144+
type="error"
145+
variant="tonal"
146+
density="compact"
147+
class="mt-2"
148+
:icon="$options.icons.mdiFileAlertOutline"
149+
>
150+
<span class="text-pre-wrap text-break">
151+
{{ results.error }}
152+
</span>
153+
</v-alert>
142154
</v-container>
143155

144156
<!-- the log file viewer -->
145-
<div
146-
ref="logScroll"
147-
no-gutters
148-
class="h-100 overflow-auto px-4 pb-2"
149-
>
150-
<v-skeleton-loader
151-
v-if="id && file && results.connected == null"
152-
type="text@5"
153-
class="mx-n4 align-content-start"
154-
/>
155-
<template v-else >
156-
<div class="d-flex flex-column justify-center">
157-
<v-alert
158-
v-if="results.error"
159-
type="error"
160-
variant="tonal"
161-
density="comfortable"
162-
class="mb-4"
163-
:icon="$options.icons.mdiFileAlertOutline"
164-
>
165-
<span class="text-pre-wrap text-break">
166-
{{ results.error }}
167-
</span>
168-
</v-alert>
169-
<log-component
170-
data-cy="log-viewer"
171-
:logs="results.lines"
172-
:timestamps="timestamps"
173-
:word-wrap="wordWrap"
174-
:autoScroll="autoScroll"
175-
/>
176-
</div>
177-
</template>
178-
</div>
157+
<v-skeleton-loader
158+
v-if="id && file && results.connected == null"
159+
type="text@5"
160+
class="align-content-start"
161+
/>
162+
<log-component
163+
v-else
164+
data-cy="log-viewer"
165+
:logs="results.lines"
166+
:timestamps="timestamps"
167+
:word-wrap="wordWrap"
168+
v-model:autoScroll="autoScroll"
169+
/>
179170
</v-container>
180171
</template>
181172

182173
<script>
183-
import { ref, computed, useTemplateRef } from 'vue'
184-
import { usePrevious, useScroll, whenever } from '@vueuse/core'
174+
import { ref, computed } from 'vue'
175+
import { usePrevious, whenever } from '@vueuse/core'
185176
import { useStore } from 'vuex'
186177
import {
187178
mdiClockOutline,
@@ -385,23 +376,6 @@ export default {
385376
386377
/** AutoScroll? */
387378
const autoScroll = useInitialOptions('autoScroll', { props, emit }, true)
388-
const logScrollEl = useTemplateRef('logScroll')
389-
const { arrivedState, directions } = useScroll(logScrollEl)
390-
// Turn on autoscroll when user scrolls to bottom:
391-
whenever(() => arrivedState.bottom && !arrivedState.top, () => {
392-
// (when page first loads both top and bottom are true)
393-
autoScroll.value = true
394-
})
395-
// Turn off autoscroll when user scrolls up:
396-
whenever(() => directions.top, () => {
397-
if (results.value.lines.length) {
398-
autoScroll.value = false
399-
}
400-
})
401-
// When autoscroll is turned off, cancel any smooth scroll in progress:
402-
whenever(() => !autoScroll.value, () => {
403-
logScrollEl.value?.scrollBy(0, 0)
404-
})
405379
406380
/** View toolbar button size */
407381
const toolbarBtnSize = '40'
@@ -430,7 +404,6 @@ export default {
430404
debouncedUpdateRelativeID,
431405
toolbarBtnSize,
432406
toolbarBtnProps: btnProps(toolbarBtnSize),
433-
logScrollEl
434407
}
435408
},
436409

tests/component/specs/log.cy.js

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,6 @@ function logLines (length) {
3737
}
3838

3939
describe('Log Component', () => {
40-
beforeEach(() => {
41-
// Cypress needs an ancestor that hides overflow for its visibility assertions
42-
// to detect if an element is scrolled out of view
43-
// https://docs.cypress.io/app/core-concepts/interacting-with-elements#An-element-is-considered-hidden-if
44-
cy.get('[data-cy-root]')
45-
.then(($el) => {
46-
$el.css({ height: '100vh', overflow: 'auto' })
47-
})
48-
})
49-
5040
it('renders', () => {
5141
// see: https://on.cypress.io/mounting-vue
5242
cy.mount(LogComponent, merge(mountOpts, {
@@ -101,7 +91,10 @@ describe('Log Component', () => {
10191
autoScroll: true,
10292
},
10393
})).as('component')
104-
cy.get('.v-btn').click({ force: true }).click({ force: true })
94+
// should be scrolled to bottom initially with autoScroll prop set to true
95+
cy.get('span').contains('Line 30')
96+
.should('be.visible')
97+
cy.get('[data-cy=log-scroll-top').click()
10598
cy.get('span').contains('Line 1')
10699
.should('be.visible')
107100
})

tests/component/support/component-index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
<meta http-equiv="X-UA-Compatible" content="IE=edge">
66
<meta name="viewport" content="width=device-width,initial-scale=1.0">
77
<title>Components App</title>
8+
<style>
9+
[data-v-app] {
10+
/* Ensure we can use visibility assertions to test if an element has scrolled out of view.
11+
See https://docs.cypress.io/app/core-concepts/interacting-with-elements#An-element-is-considered-hidden-if */
12+
height: 100vh;
13+
}
14+
</style>
815
</head>
916
<body>
1017
<div data-cy-root></div>

tests/unit/setup.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ class ResizeObserverStub {
1010
}
1111

1212
window.ResizeObserver ??= ResizeObserverStub
13+
14+
// Mock element scroll API as not currently included in jsdom:
15+
// https://github.com/jsdom/jsdom/issues/1422
16+
Element.prototype.scrollBy ??= function () { }

0 commit comments

Comments
 (0)