mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2024-10-07 03:23:37 +00:00
feat: finish pinia implementation, remove all vuex mentions and lint files
This commit is contained in:
parent
4fbe61eb54
commit
c26a656818
2
.github/labeler.yml
vendored
2
.github/labeler.yml
vendored
@ -4,7 +4,7 @@ tests:
|
||||
github_actions:
|
||||
- '.github/workflows/*'
|
||||
|
||||
vuex:
|
||||
pinia:
|
||||
- 'frontend/store/**/*.ts'
|
||||
|
||||
plugins:
|
||||
|
@ -290,6 +290,9 @@ export default Vue.extend({
|
||||
selectedYearFilters: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
},
|
||||
watch: {
|
||||
itemsType(): void {
|
||||
this.refreshItems();
|
||||
@ -331,9 +334,6 @@ export default Vue.extend({
|
||||
years: this.selectedYearFilters
|
||||
});
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -6,10 +6,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import Vue, { PropType } from 'vue';
|
||||
import { BaseItemDto } from '@jellyfin/client-axios';
|
||||
import { mapStores } from 'pinia';
|
||||
import { PropType } from 'vue';
|
||||
import { authStore, snackbarStore, socketStore } from '~/store';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -28,6 +27,9 @@ export default Vue.extend({
|
||||
isFavorite: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore, socketStore)
|
||||
},
|
||||
watch: {
|
||||
item: {
|
||||
immediate: true,
|
||||
@ -54,7 +56,7 @@ export default Vue.extend({
|
||||
async toggleFavorite(): Promise<void> {
|
||||
try {
|
||||
if (!this.item.Id) {
|
||||
throw new Error();
|
||||
throw new Error('Item has no Id');
|
||||
}
|
||||
|
||||
if (!this.isFavorite) {
|
||||
@ -78,9 +80,6 @@ export default Vue.extend({
|
||||
this.isFavorite = !this.isFavorite;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore, socketStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -33,6 +33,9 @@ export default Vue.extend({
|
||||
isPlayed: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
},
|
||||
watch: {
|
||||
item: {
|
||||
immediate: true,
|
||||
@ -48,7 +51,7 @@ export default Vue.extend({
|
||||
async togglePlayed(): Promise<void> {
|
||||
try {
|
||||
if (!this.item.Id) {
|
||||
throw new Error();
|
||||
throw new Error('Item has no Id');
|
||||
}
|
||||
|
||||
if (this.isPlayed) {
|
||||
@ -69,9 +72,6 @@ export default Vue.extend({
|
||||
this.isPlayed = !this.isPlayed;
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -21,13 +21,13 @@ import { mapStores } from 'pinia';
|
||||
import { pageStore } from '~/store';
|
||||
|
||||
export default Vue.extend({
|
||||
computed: {
|
||||
...mapStores(pageStore)
|
||||
},
|
||||
methods: {
|
||||
scrollToTop(): void {
|
||||
window.scrollTo({ top: 0 });
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(pageStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -94,6 +94,9 @@ export default Vue.extend({
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore)
|
||||
},
|
||||
methods: {
|
||||
async userLogin(): Promise<void> {
|
||||
if (!isEmpty(this.user)) {
|
||||
@ -117,9 +120,6 @@ export default Vue.extend({
|
||||
isEmpty(value: Record<never, never>): boolean {
|
||||
return isEmpty(value);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -41,6 +41,9 @@ export default Vue.extend({
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore)
|
||||
},
|
||||
methods: {
|
||||
async setServer(): Promise<void> {
|
||||
this.loading = true;
|
||||
@ -55,9 +58,6 @@ export default Vue.extend({
|
||||
async removeServerFromStore(): Promise<void> {
|
||||
await this.auth.deleteServer(this.serverInfo.PublicAddress);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -84,6 +84,9 @@ export default Vue.extend({
|
||||
loading: true
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
},
|
||||
watch: {
|
||||
item(): void {
|
||||
this.refreshItems();
|
||||
@ -118,9 +121,6 @@ export default Vue.extend({
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -63,37 +63,6 @@ export default Vue.extend({
|
||||
required: true
|
||||
}
|
||||
},
|
||||
async asyncData({ $api }) {
|
||||
const auth = authStore();
|
||||
const seasons = (
|
||||
await $api.tvShows.getSeasons({
|
||||
userId: auth.currentUserId,
|
||||
seriesId: this.item.Id
|
||||
})
|
||||
).data.Items;
|
||||
|
||||
let seasonEpisodes = {} as TvShowItem['seasonEpisodes'];
|
||||
|
||||
if (seasons) {
|
||||
for (const season of seasons) {
|
||||
if (season.Id) {
|
||||
const episodes = (
|
||||
await $api.items.getItems({
|
||||
userId: auth.currentUserId,
|
||||
parentId: season.Id,
|
||||
fields: [ItemFields.Overview, ItemFields.PrimaryImageAspectRatio]
|
||||
})
|
||||
).data;
|
||||
|
||||
if (episodes.Items) {
|
||||
seasonEpisodes[season.Id] = episodes.Items;
|
||||
}
|
||||
}
|
||||
|
||||
return { seasons, seasonEpisodes };
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTab: 0,
|
||||
@ -114,6 +83,38 @@ export default Vue.extend({
|
||||
seasons: [] as BaseItemDto[],
|
||||
seasonEpisodes: {} as TvShowItem['seasonEpisodes']
|
||||
};
|
||||
},
|
||||
async fetch() {
|
||||
const auth = authStore();
|
||||
const seasons = (
|
||||
await this.$api.tvShows.getSeasons({
|
||||
userId: auth.currentUserId,
|
||||
seriesId: this.item.Id
|
||||
})
|
||||
).data.Items;
|
||||
|
||||
const seasonEpisodes = {} as TvShowItem['seasonEpisodes'];
|
||||
|
||||
if (seasons) {
|
||||
for (const season of seasons) {
|
||||
if (season.Id) {
|
||||
const episodes = (
|
||||
await this.$api.items.getItems({
|
||||
userId: auth.currentUserId,
|
||||
parentId: season.Id,
|
||||
fields: [ItemFields.Overview, ItemFields.PrimaryImageAspectRatio]
|
||||
})
|
||||
).data;
|
||||
|
||||
if (episodes.Items) {
|
||||
seasonEpisodes[season.Id] = episodes.Items;
|
||||
}
|
||||
}
|
||||
|
||||
this.seasons = seasons;
|
||||
this.seasonEpisodes = seasonEpisodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -77,6 +77,9 @@ export default Vue.extend({
|
||||
relatedItems: {} as { [k: number]: BaseItemDto }
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, pageStore)
|
||||
},
|
||||
async mounted() {
|
||||
// TODO: Server should include a ParentImageBlurhashes property, so we don't need to do a call
|
||||
// for the parent items. Revisit this once proper changes are done.
|
||||
@ -132,9 +135,6 @@ export default Vue.extend({
|
||||
onSlideChange(index: number): void {
|
||||
this.updateBackdrop(index);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, pageStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -30,8 +30,6 @@ import SubtitlesOctopusWorkerLegacy from 'libass-wasm/dist/js/subtitles-octopus-
|
||||
import isNil from 'lodash/isNil';
|
||||
import { mapStores } from 'pinia';
|
||||
import {
|
||||
BaseItemDto,
|
||||
MediaSourceInfo,
|
||||
PlaybackInfoResponse,
|
||||
SubtitleDeliveryMethod
|
||||
} from '@jellyfin/client-axios';
|
||||
@ -71,11 +69,13 @@ export default Vue.extend({
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, deviceProfileStore, playbackManagerStore),
|
||||
poster(): ImageUrlInfo | string | undefined {
|
||||
poster(): ImageUrlInfo | string | null {
|
||||
if (!isNil(this.playbackManager.getCurrentItem)) {
|
||||
return this.getImageInfo(this.playbackManager.getCurrentItem, {
|
||||
preferBackdrop: true
|
||||
});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
videoElement(): HTMLVideoElement | undefined {
|
||||
@ -91,6 +91,7 @@ export default Vue.extend({
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
@ -160,6 +161,7 @@ export default Vue.extend({
|
||||
},
|
||||
source(newSource): void {
|
||||
this.destroy();
|
||||
|
||||
const mediaSource = this.playbackManager.currentMediaSource;
|
||||
const item = this.playbackManager.getCurrentItem;
|
||||
|
||||
@ -308,6 +310,7 @@ export default Vue.extend({
|
||||
// Set the restart time so that the function knows where to restart
|
||||
this.restartTime = this.videoElement.currentTime;
|
||||
await this.getPlaybackUrl();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -37,13 +37,13 @@ export default Vue.extend({
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(clientSettingsStore)
|
||||
},
|
||||
methods: {
|
||||
toggleDarkMode(): void {
|
||||
this.clientSettings.setDarkMode(!this.clientSettings.darkMode);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(clientSettingsStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -15,6 +15,9 @@ export default Vue.extend({
|
||||
model: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
},
|
||||
watch: {
|
||||
'snackbar.message': {
|
||||
immediate: true,
|
||||
@ -29,9 +32,6 @@ export default Vue.extend({
|
||||
this.snackbar.$reset();
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -70,6 +70,9 @@ export default Vue.extend({
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
},
|
||||
methods: {
|
||||
async createAdminAccount(): Promise<void> {
|
||||
this.loading = true;
|
||||
@ -90,9 +93,6 @@ export default Vue.extend({
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -34,6 +34,9 @@ export default Vue.extend({
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
},
|
||||
async created() {
|
||||
this.loading = true;
|
||||
|
||||
@ -76,9 +79,6 @@ export default Vue.extend({
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -48,6 +48,9 @@ export default Vue.extend({
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
},
|
||||
async created() {
|
||||
this.initialConfig = (
|
||||
await this.$api.startup.getStartupConfiguration()
|
||||
@ -81,9 +84,6 @@ export default Vue.extend({
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -27,6 +27,9 @@ export default Vue.extend({
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
},
|
||||
methods: {
|
||||
async setRemoteAccess(): Promise<void> {
|
||||
this.loading = true;
|
||||
@ -48,9 +51,6 @@ export default Vue.extend({
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(snackbarStore)
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -10,6 +10,7 @@ import { authStore } from '~/store';
|
||||
*/
|
||||
export default function (context: Context): void {
|
||||
const auth = authStore();
|
||||
|
||||
if (!auth.currentUser?.Policy?.IsAdministrator) {
|
||||
return context.redirect('/');
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ function handleAuthRedirections(
|
||||
context: Context,
|
||||
auth: ReturnType<typeof authStore>,
|
||||
useContext: boolean
|
||||
) {
|
||||
): void {
|
||||
const servers = auth.servers || [];
|
||||
const userToken = auth.getCurrentUserAccessToken;
|
||||
const currentRoute = context.app.router?.currentRoute?.fullPath || '';
|
||||
@ -83,7 +83,7 @@ function handleAuthRedirections(
|
||||
export function setHeaderAndBaseUrl(
|
||||
ctx: Context,
|
||||
auth: ReturnType<typeof authStore>
|
||||
) {
|
||||
): void {
|
||||
const currentServer = auth.currentServer?.PublicAddress || '';
|
||||
|
||||
ctx.$axios.setBaseURL(currentServer);
|
||||
@ -105,7 +105,7 @@ export function authLogic(
|
||||
ctx: Context,
|
||||
auth: ReturnType<typeof authStore>,
|
||||
useContext: boolean
|
||||
) {
|
||||
): void {
|
||||
setHeaderAndBaseUrl(ctx, auth);
|
||||
handleAuthRedirections(ctx, auth, useContext);
|
||||
}
|
||||
@ -116,6 +116,9 @@ export function authLogic(
|
||||
*/
|
||||
let appBooting = true;
|
||||
|
||||
/**
|
||||
* @param context
|
||||
*/
|
||||
export default function (context: Context): void {
|
||||
const auth = authStore();
|
||||
|
||||
|
@ -88,16 +88,24 @@ const config: NuxtConfig = {
|
||||
** https://nuxtjs.org/guide/plugins
|
||||
*/
|
||||
plugins: [
|
||||
/**
|
||||
* THE LOAD ORDER OF THE PLUGINS IS RELEVANT
|
||||
*/
|
||||
/*
|
||||
** Pinia plugins (need to be loaded first to ensure persistence)
|
||||
*/
|
||||
'plugins/store/index.ts',
|
||||
/*
|
||||
** Nuxt plugins
|
||||
/**
|
||||
* Axios plugins
|
||||
*
|
||||
* Load first our custom interceptors and the, the API
|
||||
*/
|
||||
'plugins/nuxt/apiPlugin.ts',
|
||||
'plugins/nuxt/appInit.ts',
|
||||
'plugins/nuxt/axiosInterceptors.ts',
|
||||
'plugins/nuxt/apiPlugin.ts',
|
||||
/**
|
||||
* Rest of Nuxt plugins
|
||||
*/
|
||||
'plugins/nuxt/appInit.ts',
|
||||
'plugins/nuxt/axe.ts',
|
||||
'plugins/nuxt/veeValidate.ts',
|
||||
'plugins/nuxt/browserDetectionPlugin.ts',
|
||||
@ -127,12 +135,7 @@ const config: NuxtConfig = {
|
||||
/*
|
||||
** Nuxt.js modules
|
||||
*/
|
||||
modules: [
|
||||
'@nuxtjs/i18n',
|
||||
// Doc: https://axios.nuxtjs.org/usage
|
||||
'@nuxtjs/axios',
|
||||
'@nuxtjs/pwa'
|
||||
],
|
||||
modules: ['@nuxtjs/i18n', '@nuxtjs/axios', '@nuxtjs/pwa'],
|
||||
/*
|
||||
** Router configuration
|
||||
*/
|
||||
|
@ -57,7 +57,7 @@ export default Vue.extend({
|
||||
validate(ctx: Context) {
|
||||
return isValidMD5(ctx.route.params.itemId);
|
||||
},
|
||||
async asyncData({ params, $api }) {
|
||||
async asyncData({ params, $api, route }) {
|
||||
const items = itemsStore();
|
||||
const auth = authStore();
|
||||
|
||||
@ -76,7 +76,7 @@ export default Vue.extend({
|
||||
let genres = (
|
||||
await $api.items.getItems({
|
||||
genreIds: [item.Id as string],
|
||||
includeItemTypes: [this.$route.query.type.toString()],
|
||||
includeItemTypes: [route.query.type.toString()],
|
||||
recursive: true,
|
||||
sortBy: ['SortName'],
|
||||
sortOrder: [SortOrder.Ascending]
|
||||
|
@ -147,7 +147,6 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { BaseItemDto, ImageType, SortOrder } from '@jellyfin/client-axios';
|
||||
import { Context } from '@nuxt/types';
|
||||
import imageHelper from '~/mixins/imageHelper';
|
||||
|
@ -23,11 +23,11 @@ export default Vue.extend({
|
||||
title: this.page.title
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.page.title = this.$t('login.addServer');
|
||||
},
|
||||
computed: {
|
||||
...mapStores(pageStore)
|
||||
},
|
||||
mounted() {
|
||||
this.page.title = this.$t('login.addServer');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -56,7 +56,6 @@
|
||||
import Vue from 'vue';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { mapStores } from 'pinia';
|
||||
import { mapActions } from 'vuex';
|
||||
import { UserDto } from '@jellyfin/client-axios';
|
||||
import { authStore, deviceProfileStore, pageStore } from '~/store';
|
||||
|
||||
@ -90,7 +89,6 @@ export default Vue.extend({
|
||||
this.page.title = this.$t('login.login');
|
||||
},
|
||||
methods: {
|
||||
...mapActions('servers', ['connectServer']),
|
||||
isEmpty(value: Record<never, never>): boolean {
|
||||
return isEmpty(value);
|
||||
},
|
||||
|
@ -112,15 +112,13 @@ export default Vue.extend({
|
||||
mixins: [htmlHelper],
|
||||
async asyncData({ $api }) {
|
||||
const auth = authStore();
|
||||
|
||||
if (auth.currentUser?.Policy?.IsAdministrator) {
|
||||
const systemInfo = (await $api.system.getSystemInfo()).data;
|
||||
|
||||
return { systemInfo };
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, pageStore)
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
systemInfo: {} as SystemInfo,
|
||||
@ -234,14 +232,15 @@ export default Vue.extend({
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(authStore, pageStore)
|
||||
},
|
||||
mounted() {
|
||||
this.page.opaqueAppBar = true;
|
||||
this.page.title = this.$t('settings.settings');
|
||||
},
|
||||
methods: {
|
||||
isEmpty(object: never): boolean {
|
||||
return isEmpty(object);
|
||||
}
|
||||
isEmpty
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -118,10 +118,9 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { mapStores } from 'pinia';
|
||||
import { mapState } from 'vuex';
|
||||
import colors from 'vuetify/lib/util/colors';
|
||||
import { ActivityLogEntry, LogFile, LogLevel } from '@jellyfin/client-axios';
|
||||
import { pageStore } from '~/store';
|
||||
import { authStore, pageStore } from '~/store';
|
||||
|
||||
interface LoadingStatus {
|
||||
status: 'loading' | 'loaded' | 'error';
|
||||
@ -156,8 +155,7 @@ export default Vue.extend({
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapStores(pageStore),
|
||||
...mapState('user', ['accessToken'])
|
||||
...mapStores(authStore, pageStore)
|
||||
},
|
||||
mounted() {
|
||||
this.page.title = this.$t('settingsSections.logs.name');
|
||||
@ -228,7 +226,7 @@ export default Vue.extend({
|
||||
return this.$dateFns.format(date, 'Ppp');
|
||||
},
|
||||
getLogFileLink(name: string): string {
|
||||
return `${this.$axios.defaults.baseURL}/System/Logs/Log?name=${name}&api_key=${this.accessToken}`;
|
||||
return `${this.$axios.defaults.baseURL}/System/Logs/Log?name=${name}&api_key=${this.auth.currentUserToken}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -143,13 +143,6 @@ declare module 'vue/types/vue' {
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vuex/types/index' {
|
||||
// eslint-disable-next-line -- Current TypeScript rules flag S as unused, but Nuxt requires identical types
|
||||
interface Store<S> {
|
||||
$api: ApiPlugin;
|
||||
}
|
||||
}
|
||||
|
||||
const apiPlugin: Plugin = (context, inject) => {
|
||||
const config = new Configuration();
|
||||
const contextAxios = context.$axios as AxiosInstance;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Context, Plugin } from '@nuxt/types';
|
||||
import isNil from 'lodash/isNil';
|
||||
import { authStore, ServerInfo } from '~/store';
|
||||
import { parseServerListString } from '~/utils/servers';
|
||||
import { setHeaderAndBaseUrl } from '~/middleware/auth';
|
||||
@ -26,7 +27,7 @@ const appInit: Plugin = (ctx: Context) => {
|
||||
(lsServer: ServerInfo) => lsServer.PublicAddress === serverUrl
|
||||
);
|
||||
|
||||
return server ? true : false;
|
||||
return !isNil(server);
|
||||
});
|
||||
|
||||
for (const serverUrl of missingServers) {
|
||||
|
@ -16,13 +16,6 @@ declare module 'vue/types/vue' {
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vuex/types/index' {
|
||||
// eslint-disable-next-line -- Current TypeScript rules flag S as unused, but Nuxt requires identical types
|
||||
interface Store<S> {
|
||||
$browser: BrowserDetector;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities to detect the browser and get information on the current environment
|
||||
* Based on https://github.com/google/shaka-player/blob/master/lib/util/platform.js
|
||||
|
@ -22,13 +22,6 @@ declare module 'vue/types/vue' {
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vuex/types/index' {
|
||||
// eslint-disable-next-line -- Current TypeScript rules flag S as unused, but Nuxt requires identical types
|
||||
interface Store<S> {
|
||||
$playbackProfile: DeviceProfile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a device profile containing supported codecs for the active Cast device.
|
||||
*
|
||||
|
@ -24,13 +24,6 @@ declare module 'vue/types/vue' {
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vuex/types/index' {
|
||||
// eslint-disable-next-line -- Current TypeScript rules flag S as unused, but Nuxt requires identical types
|
||||
interface Store<S> {
|
||||
$features: SupportedFeatures;
|
||||
}
|
||||
}
|
||||
|
||||
const supportedFeaturesPlugin: Plugin = ({ $browser }, inject) => {
|
||||
const supportedFeatures: SupportedFeatures = {
|
||||
pictureInPicture: false,
|
||||
|
@ -5,7 +5,7 @@ import watchAuth from './watchers/auth';
|
||||
import watchPlaybackReporting from './watchers/playbackReporting';
|
||||
import watchSocket from './watchers/socket';
|
||||
|
||||
const piniaPlugins: Plugin = (ctx: Context) => {
|
||||
const piniaPlugins: Plugin = (ctx: Context): void => {
|
||||
ctx.$pinia.use(persistence);
|
||||
ctx.$pinia.use(preferencesSync);
|
||||
watchAuth(ctx);
|
||||
|
@ -8,7 +8,7 @@ localStorage.removeItem('vuex');
|
||||
*/
|
||||
const storageStores = ['deviceProfile', 'clientSettings', 'auth'];
|
||||
|
||||
export default function persistence({ store }: PiniaPluginContext) {
|
||||
export default function persistence({ store }: PiniaPluginContext): void {
|
||||
if (storageStores.includes(store.$id)) {
|
||||
store.$state = destr(localStorage.getItem(store.$id));
|
||||
store.$subscribe(() => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { DisplayPreferencesDto } from '@jellyfin/client-axios';
|
||||
import { Context } from '@nuxt/types';
|
||||
import destr from 'destr';
|
||||
import { isNil } from 'lodash';
|
||||
import isNil from 'lodash/isNil';
|
||||
import {
|
||||
PiniaPluginContext,
|
||||
StateTree,
|
||||
@ -17,6 +17,7 @@ const syncedStores = ['clientSettings'];
|
||||
* Cast custom preferences returned from the server from strings to the correct Javascript type
|
||||
*
|
||||
* @param {DisplayPreferencesDto} data - Response from the server
|
||||
* @param store
|
||||
*/
|
||||
function castDisplayPreferencesResponse(
|
||||
data: DisplayPreferencesDto,
|
||||
@ -47,6 +48,11 @@ function castDisplayPreferencesResponse(
|
||||
|
||||
/**
|
||||
* Fetches settings from server
|
||||
*
|
||||
* @param ctx
|
||||
* @param auth
|
||||
* @param store
|
||||
* @param cast
|
||||
*/
|
||||
export async function fetchSettingsFromServer(
|
||||
ctx: Context,
|
||||
@ -71,6 +77,12 @@ export async function fetchSettingsFromServer(
|
||||
: response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ctx
|
||||
* @param auth
|
||||
* @param storeId
|
||||
* @param prefs
|
||||
*/
|
||||
export async function pushSettingsToServer(
|
||||
ctx: Context,
|
||||
auth: ReturnType<typeof authStore>,
|
||||
@ -114,8 +126,9 @@ export async function pushSettingsToServer(
|
||||
* Make sure your store implements a 'lastSync' property of type 'number | null'
|
||||
*
|
||||
* It will automatically be synced when a property changes.
|
||||
*
|
||||
*/
|
||||
export default function preferencesSync({ store }: PiniaPluginContext) {
|
||||
export default function preferencesSync({ store }: PiniaPluginContext): void {
|
||||
if (syncedStores.includes(store.$id)) {
|
||||
const page = pageStore();
|
||||
const auth = authStore();
|
||||
@ -146,6 +159,7 @@ export default function preferencesSync({ store }: PiniaPluginContext) {
|
||||
store,
|
||||
false
|
||||
);
|
||||
|
||||
displayPrefs.CustomPrefs = {};
|
||||
|
||||
Object.assign(displayPrefs.CustomPrefs, store.$state);
|
||||
|
@ -16,8 +16,10 @@ import { authLogic } from '~/middleware/auth';
|
||||
* Authentication logic on app runtime
|
||||
*
|
||||
* The logic to handle logouts and user switches during initialization lives inside Nuxt's auth plugin (~/plugins/nuxt/auth)
|
||||
*
|
||||
* @param ctx
|
||||
*/
|
||||
export default function watchAuth(ctx: Context) {
|
||||
export default function watchAuth(ctx: Context): void {
|
||||
const auth = authStore();
|
||||
const clientSettings = clientSettingsStore();
|
||||
const homeSection = homeSectionStore();
|
||||
|
@ -1,14 +1,16 @@
|
||||
import { Context } from '@nuxt/types';
|
||||
import isNil from 'lodash/isNil';
|
||||
import { playbackManagerStore, PlaybackStatus } from '~/store';
|
||||
import { msToTicks } from '~/mixins/timeUtils';
|
||||
import { isNil } from 'lodash';
|
||||
|
||||
/**
|
||||
* Playback reporting logic
|
||||
*
|
||||
* Reports the state of the playback to the server
|
||||
*
|
||||
* @param ctx
|
||||
*/
|
||||
export default function watchPlaybackReporting(ctx: Context) {
|
||||
export default function watchPlaybackReporting(ctx: Context): void {
|
||||
const playbackManager = playbackManagerStore();
|
||||
|
||||
playbackManager.$onAction(({ name, after }) => {
|
||||
@ -59,8 +61,9 @@ export default function watchPlaybackReporting(ctx: Context) {
|
||||
);
|
||||
|
||||
playbackManager.setLastProgressUpdate(new Date().getTime());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'setCurrentTime':
|
||||
if (playbackManager.status === PlaybackStatus.Playing) {
|
||||
const now = new Date().getTime();
|
||||
@ -116,10 +119,7 @@ export default function watchPlaybackReporting(ctx: Context) {
|
||||
playbackProgressInfo: {
|
||||
ItemId: playbackManager.getCurrentItem?.Id,
|
||||
PlaySessionId: playbackManager.playSessionId,
|
||||
IsPaused:
|
||||
playbackManager.status === PlaybackStatus.Playing
|
||||
? false
|
||||
: true,
|
||||
IsPaused: playbackManager.status !== PlaybackStatus.Playing,
|
||||
PositionTicks: Math.round(
|
||||
msToTicks(playbackManager.currentTime * 1000)
|
||||
)
|
||||
@ -129,8 +129,9 @@ export default function watchPlaybackReporting(ctx: Context) {
|
||||
);
|
||||
|
||||
playbackManager.setLastProgressUpdate(new Date().getTime());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -5,8 +5,9 @@ import { authStore, itemsStore, socketStore } from '~/store';
|
||||
/**
|
||||
* Handle socket messages that are relevant to items inside the items store.
|
||||
*
|
||||
* @param ctx
|
||||
*/
|
||||
export default function watchSocket(ctx: Context) {
|
||||
export default function watchSocket(ctx: Context): void {
|
||||
const auth = authStore();
|
||||
const socket = socketStore();
|
||||
const items = itemsStore();
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Vue from 'vue';
|
||||
import { PublicSystemInfo, UserDto } from '@jellyfin/client-axios';
|
||||
import { defineStore } from 'pinia';
|
||||
import { deviceProfileStore, snackbarStore } from '.';
|
||||
import { AxiosError } from 'axios';
|
||||
import { deviceProfileStore, snackbarStore } from '.';
|
||||
|
||||
export interface ServerInfo extends PublicSystemInfo {
|
||||
PublicAddress: string;
|
||||
@ -35,10 +35,15 @@ export const authStore = defineStore('auth', {
|
||||
actions: {
|
||||
/**
|
||||
* Adds a new server to the store and sets it as the default one
|
||||
*
|
||||
* @param serverUrl
|
||||
* @param isDefault
|
||||
*/
|
||||
async connectServer(serverUrl: string, isDefault?: boolean) {
|
||||
serverUrl = serverUrl.replace(/\/$/, '');
|
||||
|
||||
const snackbar = snackbarStore();
|
||||
|
||||
this.$nuxt.$axios.setBaseURL(serverUrl);
|
||||
this.setAxiosHeader();
|
||||
|
||||
@ -48,7 +53,7 @@ export const authStore = defineStore('auth', {
|
||||
data = (await this.$nuxt.$api.system.getPublicSystemInfo())
|
||||
.data as ServerInfo;
|
||||
data.PublicAddress = serverUrl;
|
||||
data.isDefault = isDefault ? true : false;
|
||||
data.isDefault = !!isDefault;
|
||||
} catch (err) {
|
||||
snackbar.push(this.$nuxt.i18n.t('login.serverNotFound'), 'error');
|
||||
throw new Error(err as string);
|
||||
@ -80,6 +85,10 @@ export const authStore = defineStore('auth', {
|
||||
},
|
||||
/**
|
||||
* Logs the user to the current server
|
||||
*
|
||||
* @param username
|
||||
* @param password
|
||||
* @param rememberMe
|
||||
*/
|
||||
async loginUser(username: string, password: string, rememberMe: boolean) {
|
||||
if (!this.currentServer) {
|
||||
@ -97,6 +106,7 @@ export const authStore = defineStore('auth', {
|
||||
).data;
|
||||
|
||||
this.rememberMe = rememberMe;
|
||||
|
||||
if (authenticateResponse.User?.Id && authenticateResponse.AccessToken) {
|
||||
Vue.set(
|
||||
this.accessTokens,
|
||||
@ -124,6 +134,7 @@ export const authStore = defineStore('auth', {
|
||||
} else if (error.response.status === 400) {
|
||||
errorMessage = this.$nuxt.i18n.t('badRequest');
|
||||
}
|
||||
|
||||
snackbar.push(errorMessage, 'error');
|
||||
/**
|
||||
* Pass the error up, so it's handled successfully in the action's subscriptions
|
||||
@ -133,7 +144,9 @@ export const authStore = defineStore('auth', {
|
||||
},
|
||||
/**
|
||||
* Logs out the user from the server using the current base url and access token parameters.
|
||||
*
|
||||
* @param clearUser - Removes the user from the store
|
||||
* @param skipRequest
|
||||
*/
|
||||
async logoutUser(clearUser = true, skipRequest = false): Promise<void> {
|
||||
try {
|
||||
@ -143,6 +156,7 @@ export const authStore = defineStore('auth', {
|
||||
} finally {
|
||||
if (clearUser === true) {
|
||||
const currentUser = this.currentUser;
|
||||
|
||||
if (currentUser) {
|
||||
Vue.delete(this.accessTokens, currentUser.Id as string);
|
||||
this.users = this.users.filter((user) => user === this.currentUser);
|
||||
@ -153,6 +167,8 @@ export const authStore = defineStore('auth', {
|
||||
},
|
||||
/**
|
||||
* Logs out all the user sessions from the provided server and removes it from the store
|
||||
*
|
||||
* @param serverUrl
|
||||
*/
|
||||
async deleteServer(serverUrl: string): Promise<void> {
|
||||
const server = this.servers.find(
|
||||
@ -170,6 +186,7 @@ export const authStore = defineStore('auth', {
|
||||
* We set the baseUrl to the one of the server to log out users of that server properly
|
||||
*/
|
||||
this.$nuxt.$axios.setBaseURL(server.PublicAddress);
|
||||
|
||||
for (const user of users) {
|
||||
this.setAxiosHeader(this.getUserAccessToken(user));
|
||||
await this.logoutUser(false);
|
||||
@ -193,6 +210,7 @@ export const authStore = defineStore('auth', {
|
||||
*/
|
||||
setAxiosHeader(accessToken?: string): void {
|
||||
const deviceProfile = deviceProfileStore();
|
||||
|
||||
if (!accessToken) {
|
||||
accessToken = this.getCurrentUserAccessToken;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { authStore, snackbarStore } from '.';
|
||||
import nuxtConfig from '~/nuxt.config';
|
||||
import { fetchSettingsFromServer } from '~/plugins/store/plugins/preferencesSync';
|
||||
import { authStore, snackbarStore } from '.';
|
||||
|
||||
/**
|
||||
* Cast typings for the CustomPrefs property of DisplayPreferencesDto
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Vue from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
import { authStore, userViewsStore, snackbarStore } from '.';
|
||||
import { BaseItemDto, ImageType, ItemFields } from '@jellyfin/client-axios';
|
||||
import { authStore, userViewsStore, snackbarStore } from '.';
|
||||
import { CardShapes } from '~/utils/items';
|
||||
|
||||
export interface HomeSection {
|
||||
@ -37,6 +37,7 @@ export const homeSectionStore = defineStore('homeSection', {
|
||||
actions: {
|
||||
async getLibraries(): Promise<void> {
|
||||
const userViews = userViewsStore();
|
||||
|
||||
await userViews.refreshUserViews();
|
||||
|
||||
this.libraries = Array.from(userViews.views);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { setMapStoreSuffix } from 'pinia';
|
||||
|
||||
declare module 'pinia' {
|
||||
export interface MapStoresCustomization {
|
||||
suffix: '';
|
||||
}
|
||||
}
|
||||
|
||||
import { setMapStoreSuffix } from 'pinia';
|
||||
|
||||
setMapStoreSuffix('');
|
||||
|
||||
export * from './clientSettings';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Vue from 'vue';
|
||||
import { BaseItem, BaseItemDto, ItemFields } from '@jellyfin/client-axios';
|
||||
import { BaseItemDto, ItemFields } from '@jellyfin/client-axios';
|
||||
import { defineStore } from 'pinia';
|
||||
import { authStore } from '.';
|
||||
|
||||
@ -21,6 +21,7 @@ export const itemsStore = defineStore('items', {
|
||||
/**
|
||||
* Add or update an item or items to the store
|
||||
*
|
||||
* @param payload
|
||||
* @returns - The reactive references
|
||||
*/
|
||||
add(payload: BaseItemDto | BaseItemDto[]): BaseItemDto | BaseItemDto[] {
|
||||
@ -47,6 +48,8 @@ export const itemsStore = defineStore('items', {
|
||||
},
|
||||
/**
|
||||
* Deletes a single or multiple items from the store
|
||||
*
|
||||
* @param payload
|
||||
*/
|
||||
delete(payload: string | string[]): void {
|
||||
if (!Array.isArray(payload)) {
|
||||
@ -60,6 +63,8 @@ export const itemsStore = defineStore('items', {
|
||||
/**
|
||||
* Associate an item that has children with its children
|
||||
*
|
||||
* @param parent
|
||||
* @param children
|
||||
* @returns - The children of the item
|
||||
*/
|
||||
addCollection(parent: BaseItemDto, children: BaseItemDto[]): BaseItemDto[] {
|
||||
@ -74,6 +79,7 @@ export const itemsStore = defineStore('items', {
|
||||
if (!this.getItemById(child.Id)) {
|
||||
this.add(child);
|
||||
}
|
||||
|
||||
childIds.push(child.Id);
|
||||
}
|
||||
}
|
||||
@ -84,6 +90,8 @@ export const itemsStore = defineStore('items', {
|
||||
},
|
||||
/**
|
||||
* Fetches a parent and its children and adds thecollection to the store
|
||||
*
|
||||
* @param parentId
|
||||
*/
|
||||
async fetchAndAddCollection(
|
||||
parentId: string | undefined
|
||||
@ -98,6 +106,7 @@ export const itemsStore = defineStore('items', {
|
||||
fields: allFields
|
||||
})
|
||||
).data;
|
||||
|
||||
if (!parentItem.Items?.[0]) {
|
||||
throw new Error("This parent doesn't exist");
|
||||
}
|
||||
@ -136,6 +145,7 @@ export const itemsStore = defineStore('items', {
|
||||
if (!item) {
|
||||
throw new Error(`Item ${id} doesn't exist in the store`);
|
||||
}
|
||||
|
||||
res.push(item);
|
||||
}
|
||||
|
||||
|
@ -9,9 +9,8 @@ import {
|
||||
MediaStream
|
||||
} from '@jellyfin/client-axios';
|
||||
import { defineStore } from 'pinia';
|
||||
import { itemsStore } from './items';
|
||||
import { isNil } from 'lodash';
|
||||
import { authStore } from '.';
|
||||
import isNil from 'lodash/isNil';
|
||||
import { authStore, itemsStore } from '.';
|
||||
|
||||
export enum PlaybackStatus {
|
||||
Stopped = 0,
|
||||
@ -107,6 +106,16 @@ export const playbackManagerStore = defineStore('playbackManager', {
|
||||
},
|
||||
/**
|
||||
* Plays an item and initializes playbackManager's state
|
||||
*
|
||||
* @param root0
|
||||
* @param root0.item
|
||||
* @param root0.audioTrackIndex
|
||||
* @param root0.subtitleTrackIndex
|
||||
* @param root0.videoTrackIndex
|
||||
* @param root0.startFromIndex
|
||||
* @param root0.startFromTime
|
||||
* @param root0.initiator
|
||||
* @param root0.startShuffled
|
||||
*/
|
||||
async play({
|
||||
item,
|
||||
@ -163,6 +172,8 @@ export const playbackManagerStore = defineStore('playbackManager', {
|
||||
},
|
||||
/**
|
||||
* Adds to the queue the items of a collection item (i.e album, tv show, etc...)
|
||||
*
|
||||
* @param item
|
||||
*/
|
||||
async playNext(item: BaseItemDto): Promise<void> {
|
||||
const queue = Array.from(this.queue);
|
||||
@ -360,6 +371,7 @@ export const playbackManagerStore = defineStore('playbackManager', {
|
||||
if (this.currentVolume === 0 && this.isMuted) {
|
||||
this.currentVolume = 100;
|
||||
}
|
||||
|
||||
this.isMuted = !this.isMuted;
|
||||
},
|
||||
toggleMinimized() {
|
||||
@ -367,10 +379,13 @@ export const playbackManagerStore = defineStore('playbackManager', {
|
||||
},
|
||||
/**
|
||||
* Builds an array of item ids based on a collection item (i.e album, tv show, etc...)
|
||||
*
|
||||
* @param item
|
||||
* @param shuffle
|
||||
*/
|
||||
async translateItemsForPlayback(
|
||||
item: BaseItemDto,
|
||||
shuffle: boolean = false
|
||||
shuffle = false
|
||||
): Promise<string[]> {
|
||||
const auth = authStore();
|
||||
let responseItems: BaseItemDto[] = [];
|
||||
@ -548,8 +563,6 @@ export const playbackManagerStore = defineStore('playbackManager', {
|
||||
* Get current's item audio tracks
|
||||
*/
|
||||
getCurrentItemAudioTracks(): MediaStream[] | undefined {
|
||||
const items = itemsStore();
|
||||
|
||||
if (!isNil(this.currentMediaSource?.MediaStreams)) {
|
||||
// @ts-expect-error - TODO: Check why typechecking this fails
|
||||
return this.currentMediaSource.MediaStreams.filter((stream) => {
|
||||
@ -561,8 +574,6 @@ export const playbackManagerStore = defineStore('playbackManager', {
|
||||
* Get current's item subtitle tracks
|
||||
*/
|
||||
getCurrentItemSubtitleTracks(): MediaStream[] | undefined {
|
||||
const items = itemsStore();
|
||||
|
||||
if (!isNil(this.currentMediaSource?.MediaStreams)) {
|
||||
// @ts-expect-error - TODO: Check why typechecking this fails
|
||||
return this.currentMediaSource.MediaStreams.filter((stream) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { BaseItemDto } from '@jellyfin/client-axios';
|
||||
import { getLibraryIcon } from '~/utils/items';
|
||||
import { authStore } from '.';
|
||||
import { getLibraryIcon } from '~/utils/items';
|
||||
|
||||
export interface UserViewsState {
|
||||
views: BaseItemDto[];
|
||||
|
Loading…
Reference in New Issue
Block a user