mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2025-03-04 11:47:34 +00:00
feat(login): add public users
This commit is contained in:
parent
e5452db4c1
commit
692f58939f
@ -7,6 +7,7 @@
|
||||
@submit.prevent="userLogin"
|
||||
>
|
||||
<v-text-field
|
||||
v-if="isEmpty(user)"
|
||||
v-model="login.username"
|
||||
outlined
|
||||
:label="$t('username')"
|
||||
@ -23,8 +24,11 @@
|
||||
></v-text-field>
|
||||
<v-row align="center" no-gutters>
|
||||
<v-col class="mr-2">
|
||||
<v-btn to="/selectServer" block large
|
||||
>{{ $t('changeServer') }}
|
||||
<v-btn v-if="!user" to="/selectServer" nuxt block large>
|
||||
{{ $t('changeServer') }}
|
||||
</v-btn>
|
||||
<v-btn v-else block large @click="$emit('change')">
|
||||
{{ $t('changeUser') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col class="mr-2">
|
||||
@ -47,10 +51,20 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { isEmpty } from 'lodash';
|
||||
import Vue from 'vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import { UserDto } from '~/api';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
user: {
|
||||
type: Object as () => UserDto,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
login: {
|
||||
@ -67,10 +81,17 @@ export default Vue.extend({
|
||||
...mapActions('deviceProfile', ['setDeviceProfile']),
|
||||
...mapActions('snackbar', ['pushSnackbarMessage']),
|
||||
async userLogin() {
|
||||
if (!isEmpty(this.user)) {
|
||||
// If we have a user from the public user selector, set it as login
|
||||
this.login.username = this.user.Name || '';
|
||||
}
|
||||
this.loading = true;
|
||||
this.setDeviceProfile();
|
||||
await this.loginRequest(this.login);
|
||||
this.loading = false;
|
||||
},
|
||||
isEmpty(value: Record<any, any>) {
|
||||
return isEmpty(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -44,6 +44,7 @@ export default Vue.extend({
|
||||
...mapActions('snackbar', ['pushSnackbarMessage']),
|
||||
...mapActions('servers', ['connectServer', 'removeServer']),
|
||||
async setServer() {
|
||||
// TODO: Merge with the identical method in AddServerForm
|
||||
this.loading = true;
|
||||
await this.connectServer(this.serverInfo.address);
|
||||
this.loading = false;
|
||||
|
105
components/UserCard.vue
Normal file
105
components/UserCard.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="portrait-card">
|
||||
<div class="card-content">
|
||||
<v-card
|
||||
class="mx-auto d-flex flex-column"
|
||||
height="100%"
|
||||
max-height="325px"
|
||||
max-width="200px"
|
||||
>
|
||||
<div class="user-image primary darken-4">
|
||||
<v-responsive :aspect-ratio="1 / 1">
|
||||
<v-img
|
||||
v-if="user.PrimaryImageTag"
|
||||
:src="`${$axios.defaults.baseURL}/Users/${user.Id}/Images/Primary?tag=${user.PrimaryImageTag}&quality=90`"
|
||||
/>
|
||||
<div
|
||||
v-if="!user.PrimaryImageTag"
|
||||
class="empty-picture d-flex align-center justify-center"
|
||||
>
|
||||
<v-icon size="96"> mdi-account </v-icon>
|
||||
</div>
|
||||
</v-responsive>
|
||||
</div>
|
||||
<v-card-title>
|
||||
{{ user.Name }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle class="pb-0 text-capitalize-first-letter">
|
||||
{{ formatDistance(user.LastActivityDate) }}
|
||||
</v-card-subtitle>
|
||||
<v-spacer />
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
text
|
||||
color="primary"
|
||||
width="100%"
|
||||
@click="$emit('connect', user)"
|
||||
>
|
||||
{{ $t('connect') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import imageHelper from '~/mixins/imageHelper';
|
||||
import { UserDto } from '~/api';
|
||||
|
||||
export default Vue.extend({
|
||||
mixins: [imageHelper],
|
||||
props: {
|
||||
user: {
|
||||
type: Object as () => UserDto,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDistance(value: string) {
|
||||
if (value) {
|
||||
return this.$dateFns.formatDistanceToNow(new Date(value), {
|
||||
addSuffix: true
|
||||
});
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.portrait-card {
|
||||
display: inline-block;
|
||||
width: 200px;
|
||||
height: 325px;
|
||||
position: relative;
|
||||
contain: strict;
|
||||
border-radius: 0.3em;
|
||||
margin: 0.6em;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
contain: strict;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.empty-picture {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.text-capitalize-first-letter::first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
@ -9,6 +9,7 @@
|
||||
"biography": "Biography",
|
||||
"browserNotSupported": "Your browser is not supported for playing this file.",
|
||||
"changeServer": "Change server",
|
||||
"changeUser": "Change user",
|
||||
"connect": "Connect",
|
||||
"byArtist": "By",
|
||||
"collections": "Collections",
|
||||
@ -34,7 +35,9 @@
|
||||
"liked": "Liked",
|
||||
"likes": "Likes",
|
||||
"login": "Login",
|
||||
"loginAs": "Login as {name}",
|
||||
"logout": "Logout",
|
||||
"manualLogin": "Manual login",
|
||||
"more": "More",
|
||||
"moreLikeArtist": "More like {artist}",
|
||||
"moreLikeThis": "More like this",
|
||||
@ -53,6 +56,7 @@
|
||||
"resumable": "Resumable",
|
||||
"selectServer": "Select server",
|
||||
"series": "Series",
|
||||
"selectUser": "Select a user",
|
||||
"serverAddress": "Server address",
|
||||
"serverAddressMustBeUrl": "Server address must be a valid URL",
|
||||
"serverAddressRequired": "Server address is required",
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { Context } from '@nuxt/types';
|
||||
|
||||
export default function (context: Context) {
|
||||
if (!context.$axios.defaults.baseURL)
|
||||
/**
|
||||
* Middleware providing a redirect to the server selection page in case the
|
||||
* Axios base URL is the default (non-working) one.
|
||||
*
|
||||
* @param {Context} context Nuxt application context
|
||||
* @returns {void}
|
||||
*/
|
||||
export default function (context: Context): void {
|
||||
if (!context.$axios.defaults.baseURL) {
|
||||
return context.redirect('/selectserver');
|
||||
if (context.$auth?.user?.Id) return context.redirect('/');
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,9 @@ const config: NuxtConfig = {
|
||||
** Axios module configuration
|
||||
** See https://axios.nuxtjs.org/options
|
||||
*/
|
||||
axios: {},
|
||||
axios: {
|
||||
baseURL: ''
|
||||
},
|
||||
/*
|
||||
** Axios-based Authentication
|
||||
** See https://auth.nuxtjs.org/schemes/local.html#options
|
||||
|
@ -23,7 +23,6 @@ import { mapActions } from 'vuex';
|
||||
export default Vue.extend({
|
||||
layout: 'fullpage',
|
||||
auth: false,
|
||||
middleware: 'serverMiddleware',
|
||||
head() {
|
||||
return {
|
||||
title: this.$store.state.page.title
|
||||
|
@ -1,21 +1,62 @@
|
||||
<template>
|
||||
<v-container fill-height>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col md="4">
|
||||
<h1 class="text-h4 mb-6 text-center">{{ $t('login') }}</h1>
|
||||
<login-form />
|
||||
<v-col v-if="isEmpty(currentUser) && !loginAsOther && publicUsers.length">
|
||||
<h1 class="text-h4 mb-6 text-center">{{ $t('selectUser') }}</h1>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col md="10">
|
||||
<div class="d-flex align-center justify-center">
|
||||
<user-card
|
||||
v-for="publicUser in publicUsers"
|
||||
:key="publicUser.Id"
|
||||
:user="publicUser"
|
||||
@connect="setCurrentUser"
|
||||
/>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row align="center" justify="center" no-gutters>
|
||||
<v-col md="4" class="d-flex flex-row">
|
||||
<v-btn class="flex-grow-1 mr-2" large @click="loginAsOther = true">
|
||||
{{ $t('manualLogin') }}
|
||||
</v-btn>
|
||||
<v-btn class="flex-grow-1 mr-2" to="/selectServer" nuxt large>
|
||||
{{ $t('changeServer') }}
|
||||
</v-btn>
|
||||
<locale-switcher />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
<v-col
|
||||
v-else-if="!isEmpty(currentUser) || loginAsOther || !publicUsers.length"
|
||||
md="4"
|
||||
>
|
||||
<h1 v-if="!isEmpty(currentUser)" class="text-h4 mb-6 text-center">
|
||||
{{ $t('loginAs', { name: currentUser.Name }) }}
|
||||
</h1>
|
||||
<h1 v-else class="text-h4 mb-6 text-center">{{ $t('login') }}</h1>
|
||||
<login-form :user="currentUser" @change="resetCurrentUser" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { isEmpty } from 'lodash';
|
||||
import Vue from 'vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import { UserDto } from '~/api';
|
||||
|
||||
export default Vue.extend({
|
||||
layout: 'fullpage',
|
||||
middleware: 'serverMiddleware',
|
||||
data() {
|
||||
return {
|
||||
loginAsOther: false,
|
||||
currentUser: {} as UserDto,
|
||||
publicUsers: [] as Array<UserDto>
|
||||
};
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
title: this.$store.state.page.title
|
||||
@ -24,8 +65,36 @@ export default Vue.extend({
|
||||
created() {
|
||||
this.setPageTitle({ title: this.$t('login') });
|
||||
},
|
||||
async beforeMount() {
|
||||
try {
|
||||
this.publicUsers = (await this.$api.user.getPublicUsers({})).data;
|
||||
} catch (error) {
|
||||
console.error('Unable to get public users:', error);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('page', ['setPageTitle'])
|
||||
...mapActions('page', ['setPageTitle']),
|
||||
...mapActions('deviceProfile', ['setDeviceProfile']),
|
||||
isEmpty(value: Record<any, any>) {
|
||||
return isEmpty(value);
|
||||
},
|
||||
setCurrentUser(user: UserDto) {
|
||||
console.dir(user);
|
||||
if (!user.HasPassword) {
|
||||
// If the user doesn't have a password, avoid showing the password form
|
||||
this.setDeviceProfile();
|
||||
this.$auth.loginWith('jellyfin', {
|
||||
username: user.Name,
|
||||
password: ''
|
||||
});
|
||||
return; // Avoid changing the form
|
||||
}
|
||||
this.currentUser = user;
|
||||
},
|
||||
resetCurrentUser() {
|
||||
this.currentUser = {};
|
||||
this.loginAsOther = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
Loading…
x
Reference in New Issue
Block a user