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

Item page improvements #521

Merged
merged 12 commits into from
Jan 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
257 changes: 257 additions & 0 deletions components/Item/TrackSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<template>
<v-select
v-model="trackIndex"
outlined
filled
flat
dense
single-line
hide-details
class="text-truncate"
:items="selectItems"
:placeholder="placeholder"
:disabled="disabled"
:clearable="clearable"
>
<template slot="selection" slot-scope="{ item: i }">
{{ getTrackSelection(i.text) }}
</template>

<template slot="item" slot-scope="{ item: i, on, attrs }">
<v-list-item
v-bind="attrs"
:two-line="!!getTrackSubtitle(i.text)"
v-on="on"
>
<v-list-item-avatar v-if="getTrackIcon(i.text)">
<v-icon>{{ getTrackIcon(i.text) }}</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ getTrackTitle(i.text) }}</v-list-item-title>
<v-list-item-subtitle v-if="getTrackSubtitle(i.text)">
{{ getTrackSubtitle(i.text) }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</template>
</v-select>
</template>

<script lang="ts">
import Vue, { PropType } from 'vue';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- Temporary module while waiting for fixes to language names on the server
// @ts-ignore
import langs from 'langs';
import {
BaseItemDto,
MediaStream,
MediaSourceInfo
} from '@jellyfin/client-axios';

export default Vue.extend({
props: {
/**
* Media item
*/
item: {
type: Object as PropType<BaseItemDto>,
required: true
},
/**
* Current media source used (current movie version for instance)
*/
mediaSourceIndex: {
type: Number,
default: 0
},
/**
* Which media type to consider for this selector
*/
type: {
type: String,
required: true,
validator(value: string): boolean {
return ['Audio', 'Subtitle', 'Video'].includes(value);
}
}
},
data() {
return { trackIndex: undefined as number | undefined };
},
computed: {
mediaSourceItem: {
/**
* @returns {MediaSourceInfo} The current source object (or empty if the index or media sources array don't exist)
*/
get(): MediaSourceInfo {
return this.item.MediaSources &&
this.item.MediaSources[this.mediaSourceIndex]
? this.item.MediaSources[this.mediaSourceIndex]
: {};
}
},
tracks: {
/**
* @returns {MediaStream[]} List of MediaStream of the specified type
*/
get(): MediaStream[] {
if (!this.mediaSourceItem.MediaStreams) return [];
return this.mediaSourceItem.MediaStreams.filter(
(mediaStream) => mediaStream.Type === this.type
);
}
},
selectItems: {
/**
* Used to model the index as a value and use the object items for the different displays
*
* @returns {{text: MediaStream; value: number}[]} List of objects prepared for Vuetify v-select with the tracks as "text" and index number as "value".
*/
get(): { text: MediaStream; value: number }[] {
return this.tracks.map((value, idx) => {
return { text: value, value: idx };
});
}
},
disabled: {
/**
* @returns {boolean} Whether to disable the v-select
*/
get(): boolean {
if (this.tracks.length <= 0) return true;
if (this.type !== 'Subtitle' && this.tracks.length <= 1) return true;
return false;
}
},
placeholder: {
/**
* @returns {string} Placeholder to use
*/
get(): string {
if (this.type === 'Audio' && this.tracks.length === 0)
return this.$t('noAudioTracksAvailable');
if (this.type === 'Audio' && this.tracks.length !== 0)
return this.$t('noAudioTrackSelected');
if (this.type === 'Subtitle' && this.tracks.length === 0)
return this.$t('noSubtitlesAvailable');
if (this.type === 'Subtitle' && this.tracks.length !== 0)
return this.$t('noSubtitleSelected');
if (this.type === 'Video' && this.tracks.length === 0)
return this.$t('noVideoTracksAvailable');
if (this.type === 'Video' && this.tracks.length !== 0)
return this.$t('noVideoTrackSelected');
return this.$t('noTracksAvailable');
}
},
clearable: {
/**
* @returns {boolean} Whether the v-select is clearable
*/
get(): boolean {
return this.type === 'Subtitle';
}
},
/**
* @returns {number|undefined} Default index to use (undefined if empty by default)
*/
defaultIndex: {
get(): number | undefined {
const defaultTrack = this.tracks.findIndex((track) => track.IsDefault);
if (defaultTrack !== -1) return defaultTrack;
else if (this.type === 'Subtitle') return undefined;
return 0;
}
}
},
watch: {
/**
* @param {number} newVal - New index value choosen in the v-select
*/
trackIndex(newVal: number): void {
this.$emit('input', newVal);
},
/**
* When the media source index is changed by the parent, we reset the selected track as it has changed
*/
mediaSourceIndex(): void {
this.resetDefaultTrack();
}
},
/**
* Sets the default track when loading the component
*/
beforeMount() {
this.resetDefaultTrack();
},
methods: {
/**
* Sets the model default track to the computed one, used at component (re)set
*/
resetDefaultTrack(): void {
this.trackIndex = this.defaultIndex;
},
/**
* @param {MediaStream} track - Track to parse
* @returns {string} Text to display in select when track is choosen
*/
getTrackSelection(track: MediaStream): string {
if (track.DisplayTitle) return track.DisplayTitle;
return '';
},
/**
* @param {MediaStream} track - Track to parse
* @returns {string|undefined} Optional icon to use for the track line in the v-select menu
*/
getTrackIcon(track: MediaStream): string | undefined {
if (this.type === 'Audio' && track.ChannelLayout)
return this.getSurroundIcon(track.ChannelLayout);
return undefined;
},
/**
* @param {MediaStream} track - Track to parse
* @returns {string} Text to use for the track line in the v-select menu
*/
getTrackTitle(track: MediaStream): string {
if (track.DisplayTitle) return track.DisplayTitle;
return '';
},
/**
* @param {MediaStream} track - Track to parse
* @returns {string|undefined} Optional subtitle to use for the track line in the v-select menu
*/
getTrackSubtitle(track: MediaStream): string | undefined {
if ((this.type === 'Audio' || this.type === 'Subtitle') && track.Language)
return this.getLanguageName(track.Language);
else if (this.type === 'Audio' || this.type === 'Subtitle')
return this.$t('undefined');

return undefined;
},
/**
* @param {string} code - Converts a two letters language code to full word
* @returns {string} Full word
*/
getLanguageName(code: string): string {
return langs.where('2B', code).name;
},
/**
* @param {string} layout - Audio layout to get related icon
* @returns {string} Icon name
*/
getSurroundIcon(layout: string): string {
switch (layout) {
case '2.0':
return 'mdi-surround-sound-2-0';
case '3.1':
return 'mdi-surround-sound-3-1';
case '5.1':
return 'mdi-surround-sound-5-1';
case '7.1':
return 'mdi-surround-sound-7-1';
default:
return 'mdi-surround-sound';
}
}
}
});
</script>
8 changes: 7 additions & 1 deletion locales/en-US.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"3DFormat": "3D format",
"NoMediaSourcesAvailable": "No media sources available",
"actor": "Actor",
"actors": "Actors",
"addNewPerson": "Add a new person",
Expand Down Expand Up @@ -119,11 +120,16 @@
"name": "Name",
"networks": "Networks",
"nextUp": "Next up",
"noAudioTrackSelected": "No audio track selected",
"noAudioTracksAvailable": "No audio tracks available",
"noImagesFound": "No images found",
"noNetworkConnection": "No network connection",
"noResultsFound": "There is nothing here",
"noSubtitleAvailable": "No subtitle available",
"noSubtitleSelected": "No subtitle selected",
"noSubtitlesAvailable": "No subtitles available",
"noTracksAvailable": "No tracks available",
"noVideoTrackSelected": "No video track selected",
"noVideoTracksAvailable": "No video tracks available",
"numberTracks": "{number} tracks",
"operatingSystem": "Operating system",
"originalAspectRatio": "Original aspect ratio",
Expand Down
Loading