Skip to content

Commit

Permalink
Merge pull request #521 from ThibaultNocchi/items_improvs
Browse files Browse the repository at this point in the history
Item page improvements
  • Loading branch information
ferferga authored Jan 17, 2021
2 parents 1382ba4 + 226b1c3 commit 6b85dbd
Show file tree
Hide file tree
Showing 3 changed files with 393 additions and 238 deletions.
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 @@ -120,11 +121,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

0 comments on commit 6b85dbd

Please sign in to comment.