feat: finish pinia implementation, remove all vuex mentions and lint files

This commit is contained in:
Fernando Fernández 2022-04-09 21:36:28 +02:00 committed by Thibault
parent 4fbe61eb54
commit c26a656818
44 changed files with 207 additions and 172 deletions

2
.github/labeler.yml vendored
View File

@ -4,7 +4,7 @@ tests:
github_actions:
- '.github/workflows/*'
vuex:
pinia:
- 'frontend/store/**/*.ts'
plugins:

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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('/');
}

View File

@ -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();

View File

@ -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
*/

View File

@ -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]

View File

@ -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';

View File

@ -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>

View File

@ -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);
},

View File

@ -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>

View File

@ -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}`;
}
}
});

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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.
*

View File

@ -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,

View File

@ -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);

View File

@ -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(() => {

View File

@ -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);

View File

@ -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();

View File

@ -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;
}
});
});

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -1,11 +1,11 @@
import { setMapStoreSuffix } from 'pinia';
declare module 'pinia' {
export interface MapStoresCustomization {
suffix: '';
}
}
import { setMapStoreSuffix } from 'pinia';
setMapStoreSuffix('');
export * from './clientSettings';

View File

@ -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);
}

View File

@ -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) => {

View File

@ -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[];