Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add subtitle search SFC #6727

Merged
merged 25 commits into from
Jun 6, 2019
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e0695d1
Add SFC subtitle-search.vue
p0psicles May 25, 2019
ce17f32
Add the component itself.
p0psicles May 25, 2019
3afb8e8
Add jest test
p0psicles May 25, 2019
754001e
Merge remote-tracking branch 'remotes/origin/develop' into feature/ad…
p0psicles May 26, 2019
cd8c922
Add new fixtures for jest tests.
p0psicles May 27, 2019
6740348
Added jest test for subtitle-search.vue component.
p0psicles May 27, 2019
63642d0
Added snapshot
p0psicles May 29, 2019
025366d
Fix eslint warnings.
p0psicles May 29, 2019
9be825f
Utilize the vue destroyed() method.
p0psicles May 29, 2019
e31e5f0
Merge remote-tracking branch 'remotes/origin/develop' into feature/ad…
p0psicles May 29, 2019
a77a3b9
Yarn dev after rebase.
p0psicles May 29, 2019
3c9abf0
* Implement review comments.
p0psicles May 30, 2019
c21052a
use computed instead of method, to create the params.
p0psicles May 30, 2019
6aabce1
Revert changes back making the component destroy itself (remove child…
p0psicles Jun 1, 2019
1c32bd7
Remove 'this'.
p0psicles Jun 1, 2019
b7dce65
Merge branch 'develop' into feature/add-subtitle-search-sfc
p0psicles Jun 2, 2019
a131dd3
Merge remote-tracking branch 'remotes/origin/develop' into feature/ad…
p0psicles Jun 5, 2019
cab1ca5
Update subtitle-search.vue snapshots.
p0psicles Jun 5, 2019
3c72e89
linting
p0psicles Jun 5, 2019
6193880
build
p0psicles Jun 5, 2019
7d8adc5
Remove comment at top of file
p0psicles Jun 5, 2019
9cda490
Fix `date-fns` getting bundled twice
sharkykh Jun 6, 2019
8b33d3c
Split `date-fns` to a separate bundle
sharkykh Jun 6, 2019
210f456
Remove unused eslint disable comments
sharkykh Jun 6, 2019
f6fecd1
Lint
sharkykh Jun 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions themes-default/slim/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export { default as ShowHeader } from './show-header.vue';
export { default as SnatchSelection } from './snatch-selection.vue';
export { default as Status } from './status.vue';
export { default as SubMenu } from './sub-menu.vue';
export { default as SubtitleSearch } from './subtitle-search.vue';
export * from './http';
export * from './helpers';
258 changes: 258 additions & 0 deletions themes-default/slim/src/components/subtitle-search.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
<template>
<!-- template for the subtitle-search component -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<!-- template for the subtitle-search component -->

<tr class='subtitle-search-wrapper'>
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<td colspan='9999' transition="expand">
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<span v-if="loading" class="loading-message">{{loadingMessage}} <state-switch :theme="config.themeName" state="loading"></state-switch></span>
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<div v-if="displayQuestion" class="search-question">
<div class="question">
<p>Do you want to manually pick subtitles or let us choose it for you?</p>
</div>
<div class="options">
<button type="button" class="btn-medusa btn-info" @click="autoSearch">Auto</button>
<button type="button" class="btn-medusa btn-success" @click="manualSearch">Manual</button>
</div>
</div>

<vue-good-table v-if="subtitles.length"
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
:columns="columns"
:rows="subtitles"
:search-options="{
enabled: false
}"
:sort-options="{
enabled: true,
initialSortBy: { field: 'score', type: 'desc' }
}"
styleClass="vgt-table condensed subtitle-table"
>
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<template slot="table-column" slot-scope="props">
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<span v-if="props.column.label == 'Download'">
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<span>{{props.column.label}}</span>
<span class="btn-medusa btn-xs pull-right" @click="$destroy">hide</span>
</span>
<span v-else>
{{props.column.label}}
</span>
</template>
<template slot="table-row" slot-scope="props">
<span v-if="props.column.field == 'provider'">
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<img :src="`images/subtitles/${props.row.provider}.png`" width="16" height="16"/>
<span :title="props.row.provider">{{props.row.provider}}</span>
</span>
<span v-else-if="props.column.field == 'lang'">
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<img :title="props.row.lang" :src="`images/subtitles/flags/${props.row.lang}.png`" width="16" height="11"/>
</span>
<span v-else-if="props.column.field == 'filename'">
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
<a :title="`Download ${props.row.hearing_impaired ? 'hearing impaired ' : ' '} subtitle: ${props.row.filename}`" @click="pickSubtitle(props.row.id)">
<img v-if="props.row.hearing_impaired" src="images/hearing_impaired.png" width="16" height="16"/>
<span class="subtitle-name">{{props.row.filename}}</span>
<img v-if="props.row.sub_score >= props.row.min_score" src="images/save.png" width="16" height="16"/>
</a>
</span>
<span v-else-if="props.column.field == 'download'">
<a :title="`Download ${props.row.hearing_impaired ? 'hearing impaired ' : ' '} subtitle: ${props.row.filename}`" @click="pickSubtitle(props.row.id)">
<img src="images/download.png" width="16" height="16"/>
</a>
</span>
<span v-else>
{{props.formattedRow[props.column.field]}}
</span>
</template>
</vue-good-table>
</td>
</tr>
</template>
<script>

import { mapState } from 'vuex';
import { VueGoodTable } from 'vue-good-table';
p0psicles marked this conversation as resolved.
Show resolved Hide resolved

export default {
name: 'subtitle-search',
components: {
VueGoodTable
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
},
props: {
show: {
type: Object,
required: true
},
season: {
type: [String, Number],
required: true
},
episode: {
type: [String, Number],
required: true
}
},
data() {
return {
columns: [{
label: 'Filename',
field: 'filename'
}, {
label: 'Language',
field: 'lang'
}, {
label: 'Provider',
field: 'provider'
}, {
label: 'Score',
field: 'score',
type: 'number'
}, {
label: 'Sub Score',
field: 'sub_score',
type: 'number'
}, {
label: 'Missing Matches',
field: rowObj => {
if (rowObj.missing_guess) {
return rowObj.missing_guess.join(', ');
}
},
type: 'array'
}, {
label: 'Download',
field: 'download'
}],
subtitles: [],
displayQuestion: false,
loading: false,
loadingMessage: ''
};
},
computed: {
...mapState({
config: state => state.config
})
},
mounted() {
this.displayQuestion = true;
},
destroyed() {
// Remove the element from the DOM
this.$el.parentNode.removeChild(this.$el);
Copy link
Contributor

@sharkykh sharkykh May 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😬
Okay... two things.

  1. I think that when a component gets destroyed, it means it removes anything it rendered.
    So... you shouldn't need to do that. If you encountered an issue with that logic, let me know and we'll try to work around it.

  2. I would not do it (hide/remove the component) this way.
    Vue uses a VDOM, as in Virtual DOM.
    If you remove an element that is tied to a component or a state, my guess is as soon as the state changes, or when the component needs to update the DOM, it will just re-create it.

    My suggestion?
    This component should emit an event to let its parent know that it is no longer "needed",
    and the parent component should use v-if="subtitleSelectionVisible" on this component.
    This will trigger an automatic (and more reliable IMO) destruction of this component.
    Plus it makes this component more detached from its parent, in the sense that it doesn't manipulate it.

P.S.
Actually I look at this and I think of how complex the DOM manipulation is on ajax-episode-subtitles.js/ajax-episode-search.js, and we don't want that, do we? 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's really needed. I can call $destory() as much as I want. But it won't remove the node.
I really need to have it removed. I tested, it won't recreate it.

},
methods: {
autoSearch() {
const { episode, season, show } = this;

this.displayQuestion = false;
const url = `home/searchEpisodeSubtitles?indexername=${show.indexer}&seriesid=${show.id[show.indexer]}&season=${season}&episode=${episode}`;
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
this.loadingMessage = 'Searching for subtitles and downloading if available... ';
this.loading = true;
apiRoute(url) // eslint-disable-line no-undef
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
.then(response => {
if (response.data.result !== 'failure') {
// Update the show, as we have new information (subtitles)
// Let's emit an event, telling the displayShow component, to update the show using the api/store.
this.$emit('update', {
reason: 'new subtitles found',
codes: response.data.subtitles,
languages: response.data.languages
});
}
})
.catch(error => {
console.log(`Error trying to search for subtitles. Error: ${error}`);
})
.finally(() => {
// Destroy this component.
this.loadingMessage = '';
this.loading = false;
this.$destroy();
});
},
manualSearch() {
const { show, season, episode } = this;

this.displayQuestion = false;
this.loading = true;
this.loadingMessage = 'Searching for subtitles... ';
const url = `home/manualSearchSubtitles?indexername=${show.indexer}&seriesid=${show.id[show.indexer]}&season=${season}&episode=${episode}`;
apiRoute(url) // eslint-disable-line no-undef
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
.then(response => {
if (response.data.result === 'success') {
this.subtitles.push(...response.data.subtitles);
}
}).catch(error => {
console.log(`Error trying to search for subtitles. Error: ${error}`);
this.$destroy();
}).finally(() => {
this.loading = false;
});
},
pickSubtitle(subtitleId) {
// Download and save this subtitle with the episode.
const { show, season, episode } = this;

this.displayQuestion = false;
this.loadingMessage = 'downloading subtitle... ';
this.loading = true;
const url = `home/manualSearchSubtitles?indexername=${show.indexer}&seriesid=${show.id[show.indexer]}&season=${season}&episode=${episode}&picked_id=${subtitleId}`;
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
apiRoute(url) // eslint-disable-line no-undef
.then(response => {
if (response.data.result === 'success') {
// Update the show, as we have new information (subtitles)
// Let's emit an event, telling the displayShow component, to update the show using the api/store.
this.$emit('update', {
reason: 'new subtitles found',
codes: response.data.subtitles,
languages: response.data.languages
});
}
})
.catch(error => {
console.log(`Error trying to search for subtitles. Error: ${error}`);
})
.finally(() => {
// Destroy this component.
this.loadingMessage = '';
this.loading = false;
this.$destroy();
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
});
}
}
};
</script>
<style>
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
.v--modal-overlay .v--modal-box {
p0psicles marked this conversation as resolved.
Show resolved Hide resolved
overflow: inherit!important;
}
table.subtitle-table tr {
background-color: rgb(190, 222, 237);
}
.subtitle-search-wrapper {
display: table-row;
column-span: all;
}
tr.subtitle-search-wrapper > td {
padding: 0;
}
/* always present */
.expand-transition {
transition: all .3s ease;
height: 30px;
padding: 10px;
background-color: #eee;
overflow: hidden;
}
/* .expand-enter defines the starting state for entering */
/* .expand-leave defines the ending state for leaving */
.expand-enter, .expand-leave {
height: 0;
padding: 0 10px;
opacity: 0;
}
.search-question, .loading-message {
background-color: rgb(51, 51, 51);
color: rgb(255,255,255);
padding: 10px;
line-height: 55px;
}
span.subtitle-name {
color: rgb(0, 0, 0);
}
</style>
125 changes: 125 additions & 0 deletions themes-default/slim/test/__fixtures__/show-detailed.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
{
"plot": "A television anthology series that shows the dark side of life and technology.",
"status": "Continuing",
"network": "Netflix",
"language": "en",
"countries": ["UNITED KINGDOM"],
"seasonCount": {
"0": 2,
"1": 3,
"2": 3,
"3": 6,
"4": 6,
"5": 3
},
"allSceneExceptions": {
"-1": []
},
"classification": "",
"countryCodes": ["gb"],
"id": {
"trakt": 41793,
"tvdb": 253463,
"imdb": "tt2085059",
"slug": "tvdb253463"
},
"sceneNumbering": [],
"xemAbsoluteNumbering": [],
"config": {
"locationValid": true,
"sports": false,
"scene": false,
"airdateOffset": 0,
"defaultEpisodeStatus": "Wanted",
"airByDate": false,
"seasonFolders": true,
"dvdOrder": false,
"location": "/Shows/Black Mirror",
"paused": false,
"release": {
"requiredWordsExclude": false,
"requiredWords": [],
"ignoredWordsExclude": false,
"ignoredWords": []
},
"anime": false,
"qualities": {
"allowed": [8, 32, 64, 128, 256, 512],
"preferred": []
},
"subtitlesEnabled": true,
"aliases": []
},
"size": 29281778284,
"nextAirDate": "2019-06-05T09:00:00+02:00",
"type": "Scripted",
"imdbInfo": {
"certificates": "",
"lastUpdate": 737202,
"plot": "An anthology series exploring a twisted, high-tech world where humanity's greatest innovations and darkest instincts collide.",
"rating": "8.9",
"title": "Black Mirror",
"countries": "UNITED KINGDOM",
"votes": 312785,
"imdbId": "tt2085059",
"runtimes": 60,
"genres": "Drama|Sci-Fi|Thriller",
"indexer": 1,
"countryCodes": "gb",
"year": 2011,
"indexerId": 253463,
"imdbInfoId": 28,
"akas": ""
},
"rating": {
"imdb": {
"votes": 312785,
"rating": "8.9"
}
},
"showType": "series",
"xemNumbering": [],
"showQueueStatus": [{
"message": "This show is in the process of being downloaded - the info below is incomplete",
"active": false,
"action": "isBeingAdded"
}, {
"message": "The information on this page is in the process of being updated",
"active": false,
"action": "isBeingUpdated"
}, {
"message": "The episodes below are currently being refreshed from disk",
"active": false,
"action": "isBeingRefreshed"
}, {
"message": "Currently downloading subtitles for this show",
"active": false,
"action": "isBeingSubtitled"
}, {
"message": "This show is queued to be refreshed",
"active": false,
"action": "isInRefreshQueue"
}, {
"message": "This show is queued and awaiting an update",
"active": false,
"action": "isInUpdateQueue"
}, {
"message": "This show is queued and awaiting subtitles download",
"active": false,
"action": "isInSubtitleQueue"
}],
"runtime": 60,
"airs": "Wednesday 3:00 AM",
"cache": {
"poster": "/medusa-data/cache/images/tvdb/253463.poster.jpg",
"banner": "/medusa-data/cache/images/tvdb/253463.poster.jpg"
},
"sceneAbsoluteNumbering": {},
"title": "Black Mirror",
"genres": ["Science-Fiction", "Thriller", "Drama"],
"indexer": "tvdb",
"airsFormatValid": true,
"year": {
"start": 2011
}
}
Loading