Skip to content
This repository has been archived by the owner on May 24, 2021. It is now read-only.

Commit

Permalink
feat(transcripts): Improve transcripts rendering performance
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-heimbuch committed Jul 7, 2018
1 parent e1f436f commit 822f1d0
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 207 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"test:prepare": "rm -rf reports coverage && mkdir -p reports coverage",
"lint:commit": "conventional-changelog-lint",
"lint:standard": "standard --parser babel-eslint --verbose --plugin html 'src/**/*.{js,vue}'",
"lint": "npm run lint:standard | snazzy --format junit",
"lint": "npm run lint:standard | snazzy",
"lint:ci": "npm run test:prepare && npm run lint:standard | standard-reporter --checkstyle > reports/checkstyle.xml",
"commit": "git-cz",
"deploy:gh-pages": "scripts/deploy-ghpages.sh dist",
Expand Down Expand Up @@ -87,7 +87,7 @@
"autoprefixer": "^8.4.1",
"ava": "0.25.0",
"babel-core": "6.26.0",
"babel-eslint": "^8.2.3",
"babel-eslint": "^8.2.5",
"babel-loader": "7.1.4",
"babel-plugin-add-module-exports": "0.2.1",
"babel-plugin-es6-promise": "^1.1.1",
Expand Down Expand Up @@ -124,8 +124,8 @@
"sass-loader": "7.0.3",
"semver": "5.5.0",
"sinon": "6.0.1",
"snazzy": "7.1.1",
"standard": "11.0.1",
"snazzy": "^7.1.1",
"standard": "^11.0.1",
"standard-changelog": "2.0.0",
"standard-reporter": "^1.0.5",
"superagent-nock": "0.4.0",
Expand Down
101 changes: 58 additions & 43 deletions src/components/tabs/transcripts/Entry.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
<template>
<div class="entry" :class="{
chapter: entry.type === 'chapter',
transcript: entry.type === 'transcript',
speaker: entry.speaker
}">
<span class="chapter"
v-if="entry.type === 'chapter'"
:style="chapterStyle"
@dblclick="onDoubleClick(entry)"
@click="onClick(entry)">{{ $t('TRANSCRIPTS.CHAPTER', entry) }}</span>
<span class="transcript" v-else>
<span class="speaker" v-if="entry.speaker">
<span class="speaker-background" :style="speakerBackgroundStyle"></span>
<img class="speaker-avatar" v-if="entry.speaker.avatar" :src="entry.speaker.avatar" />
<span class="speaker-name" v-if="entry.speaker.name" :style="speakerTextStyle">{{ entry.speaker.name }}</span>
</span>
<span class="text"
v-for="(transcript, tindex) in entry.texts"
:key="tindex"
:style="activeStyle(transcript)"
:class="{ last: tindex === (entry.texts.length - 1), active: activePlaytime(transcript), inactive: playtime > transcript.end }"
@mouseover="onMouseOver(transcript)"
@mouseleave="onMouseLeave(transcript)"
@click="onClick(transcript)"
v-html="searchText(transcript.text)"
></span>
</span>
</div>
</template>

<script>
const container = (h, c) => (children = []) =>
h('div', {
class: {
entry: true,
chapter: c.entry.type === 'chapter',
transcript: c.entry.type === 'transcript',
speaker: c.entry.speaker
}
}, [...children])
const chapter = (h, c) => (children = []) =>
h('span', {
class: { chapter: true },
style: c.prerender ? {} : c.chapterStyle,
on: c.prerender ? {} : {
click: () => c.onClick(c.entry)
}
}, [c.$t('TRANSCRIPTS.CHAPTER', c.entry), ...children])
const speaker = (h, c) =>
h('span', { class: { speaker: true } }, [
h('span', { class: { 'speaker-background': true }, style: c.speakerBackgroundStyle }),
c.entry.speaker.avatar ? h('img', { class: { 'speaker-avatar': true }, domProps: { src: c.entry.speaker.avatar } }) : null,
c.entry.speaker.name ? h('span', { class: { 'speaker-name': true }, style: c.prerender ? {} : c.speakerTextStyle }, c.entry.speaker.name) : null
])
const transcript = (h, c) => (children = []) =>
h('span', { class: { 'transcript': true } }, [
c.entry.speaker ? speaker(h, c) : null,
...children
])
const text = (h, c) => (transcript, index) =>
h('span', {
class: {
text: true,
last: index === (c.entry.texts.length - 1),
active: c.activePlaytime(transcript),
inactive: c.playtime > transcript.end
},
style: c.prerender ? {} : c.activeStyle(transcript),
on: c.prerender ? {} : {
click: () => c.onClick(transcript),
mouseover: () => c.onMouseOver(transcript),
mouseleave: () => c.onMouseLeave(transcript)
}
}, [c.searchText(transcript.text)])
export default {
data () {
return {
Expand All @@ -38,31 +55,29 @@ export default {
}
},
props: ['entry', 'playtime', 'ghost', 'prerender', 'query'],
render (h) {
const entryContainer = container(h, this)
const entryChapter = chapter(h, this)
const entryTranscript = transcript(h, this)
const entryTexts = text(h, this)
return entryContainer([
this.entry.type === 'chapter' ? entryChapter() : entryTranscript(this.entry.texts.map(entryTexts))
])
},
computed: {
chapterStyle () {
if (this.prerender) {
return {}
}
return {
background: this.theme.tabs.transcripts.chapter.background,
color: this.theme.tabs.transcripts.chapter.color
}
},
speakerBackgroundStyle () {
if (this.prerender) {
return {}
}
return {
background: this.theme.tabs.transcripts.chapter.background
}
},
speakerTextStyle () {
if (this.prerender) {
return {}
}
return {
color: this.theme.tabs.transcripts.chapter.color
}
Expand Down
7 changes: 5 additions & 2 deletions src/components/tabs/transcripts/Prerender.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<script>
import { map } from 'lodash'
import { asyncAnimation } from 'utils/helper'
import TranscriptEntry from './Entry'
Expand All @@ -16,9 +17,11 @@
},
mounted () {
this.$nextTick(() => {
const entries = map(this.$el.children, entry => entry.clientHeight)
const entries = map(this.$el.children, asyncAnimation(entry => entry.clientHeight))
this.$emit('load', entries)
Promise.all(entries).then(resolved => {
this.$emit('load', resolved)
})
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/utils/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { isUndefinedOrNull } from './predicates'
*/

export const inAnimationFrame = func => (...args) => window.requestAnimationFrame(() => func.apply(null, args))
export const asyncAnimation = func => (...args) => new Promise(resolve => {
window.requestAnimationFrame(resolve(func.apply(null, args)))
})

export const callWith = (...args) => func => func.apply(null, args)

Expand Down
29 changes: 28 additions & 1 deletion src/utils/helper.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@ import test from 'ava'
import sinon from 'sinon'
import browserEnv from 'browser-env'

import { inAnimationFrame, callWith } from './helper'
import { inAnimationFrame, asyncAnimation, callWith } from './helper'

browserEnv(['window'])

test(`it exports a function called inAnimationFrame`, t => {
t.is(typeof inAnimationFrame, 'function')
})

test(`it exports a function called asyncAnimation`, t => {
t.is(typeof asyncAnimation, 'function')
})

test(`it exports a function called callWith`, t => {
t.is(typeof callWith, 'function')
})
Expand All @@ -25,6 +29,29 @@ test(`inAnimationFrame: calls a function on next animation frame`, t => {
t.is(testStub.getCall(0).args[1], 'bar')
})

test(`asyncAnimation: returns a promise`, t => {
const testStub = sinon.stub()

window.requestAnimationFrame = cb => cb()

const result = asyncAnimation(testStub)('foo', 'bar')
t.is(typeof result.then, 'function')
})

test.cb(`asyncAnimation: resolves stub in promise`, t => {
const testStub = sinon.stub()

window.requestAnimationFrame = cb => cb()
const result = asyncAnimation(testStub)('foo', 'bar')

t.plan(2)
result.then(() => {
t.is(testStub.getCall(0).args[0], 'foo')
t.is(testStub.getCall(0).args[1], 'bar')
t.end()
})
})

test(`callWith: calls a function with given args`, t => {
const testStub = sinon.stub()

Expand Down
Loading

0 comments on commit 822f1d0

Please sign in to comment.