Migrate some components

This commit is contained in:
wvffle 2022-07-30 19:56:44 +00:00 committed by Georg Krause
parent 45740d510e
commit 2f2409f9f2
8 changed files with 333 additions and 336 deletions

View File

@ -1,3 +1,81 @@
<script setup lang="ts">
import type { BackendError } from '~/types'
import { useGettext } from 'vue3-gettext'
import { computed, ref } from 'vue'
import { useStore } from '~/store'
import axios from 'axios'
import PasswordInput from '~/components/forms/PasswordInput.vue'
const { $pgettext } = useGettext()
const store = useStore()
const subsonicEnabled = computed(() => store.state.instance.settings.subsonic.enabled.value)
const labels = computed(() => ({
subsonicField: $pgettext('Content/Password/Input.label', 'Your subsonic API password')
}))
const errors = ref([] as string[])
const success = ref(false)
const isLoading = ref(false)
const token = ref()
const fetchToken = async () => {
success.value = false
errors.value = []
isLoading.value = true
try {
const response = await axios.get(`users/${store.state.auth.username}/subsonic-token/`)
token.value = response.data.subsonic_api_token
} catch (error) {
errors.value = (error as BackendError).backendErrors
}
isLoading.value = false
}
const showToken = ref(false)
const successMessage = ref('')
const requestNewToken = async () => {
successMessage.value = $pgettext('Content/Settings/Message', 'Password updated')
success.value = false
errors.value = []
isLoading.value = true
try {
const response = await axios.post(`users/${store.state.auth.username}/subsonic-token/`)
showToken.value = true
token.value = response.data.subsonic_api_token
success.value = true
} catch (error) {
errors.value = (error as BackendError).backendErrors
}
isLoading.value = false
}
const disable = async () => {
successMessage.value = $pgettext('Content/Settings/Message', 'Access disabled')
success.value = false
errors.value = []
isLoading.value = true
try {
await axios.delete(`users/${store.state.auth.username}/subsonic-token/`)
token.value = null
success.value = true
showToken.value = false
} catch (error) {
errors.value = (error as BackendError).backendErrors
}
isLoading.value = false
}
fetchToken()
</script>
<template>
<form
class="ui form"
@ -154,86 +232,3 @@
</template>
</form>
</template>
<script>
import axios from 'axios'
import PasswordInput from '~/components/forms/PasswordInput.vue'
export default {
components: {
PasswordInput
},
data () {
return {
token: null,
errors: [],
success: false,
isLoading: false,
successMessage: '',
showToken: false
}
},
computed: {
subsonicEnabled () {
return this.$store.state.instance.settings.subsonic.enabled.value
},
labels () {
return {
subsonicField: this.$pgettext('Content/Password/Input.label', 'Your subsonic API password')
}
}
},
created () {
this.fetchToken()
},
methods: {
fetchToken () {
this.success = false
this.errors = []
this.isLoading = true
const self = this
const url = `users/${this.$store.state.auth.username}/subsonic-token/`
return axios.get(url).then(response => {
self.token = response.data.subsonic_api_token
self.isLoading = false
}, error => {
self.isLoading = false
self.errors = error.backendErrors
})
},
requestNewToken () {
this.successMessage = this.$pgettext('Content/Settings/Message', 'Password updated')
this.success = false
this.errors = []
this.isLoading = true
const self = this
const url = `users/${this.$store.state.auth.username}/subsonic-token/`
return axios.post(url, {}).then(response => {
self.showToken = true
self.token = response.data.subsonic_api_token
self.isLoading = false
self.success = true
}, error => {
self.isLoading = false
self.errors = error.backendErrors
})
},
disable () {
this.successMessage = this.$pgettext('Content/Settings/Message', 'Access disabled')
this.success = false
this.errors = []
this.isLoading = true
const self = this
const url = `users/${this.$store.state.auth.username}/subsonic-token/`
return axios.delete(url).then(response => {
self.isLoading = false
self.token = null
self.success = true
}, error => {
self.isLoading = false
self.errors = error.backendErrors
})
}
}
}
</script>

View File

@ -1,3 +1,53 @@
<script setup lang="ts">
import type { BackendError, Channel } from '~/types'
import { computed, watch, ref } from 'vue'
import axios from 'axios'
interface Emits {
(e: 'submittable', value: boolean): void
(e: 'loading', value: boolean): void
(e: 'created'): void
}
interface Props {
channel: Channel
}
const emit = defineEmits<Emits>()
const props = defineProps<Props>()
const title = ref('')
const errors = ref([] as string[])
const isLoading = ref(false)
const submit = async () => {
isLoading.value = true
errors.value = []
try {
await axios.post('albums/', {
title: title.value,
artist: props.channel.artist?.id
})
emit('created')
} catch (error) {
errors.value = (error as BackendError).backendErrors
}
isLoading.value = false
}
const submittable = computed(() => title.value.length > 0)
watch(submittable, (value) => emit('submittable', value))
watch(isLoading, (value) => emit('loading', value))
defineExpose({
submit
})
</script>
<template>
<form
:class="['ui', {loading: isLoading}, 'form']"
@ -27,63 +77,9 @@
<translate translate-context="*/*/*/Noun">Title</translate>
</label>
<input
v-model="values.title"
v-model="title"
type="text"
>
</div>
</form>
</template>
<script>
import axios from 'axios'
export default {
components: {},
props: {
channel: { type: Object, required: true }
},
data () {
return {
errors: [],
isLoading: false,
values: {
title: ''
}
}
},
computed: {
submittable () {
return this.values.title.length > 0
}
},
watch: {
submittable (v) {
this.$emit('submittable', v)
},
isLoading (v) {
this.$emit('loading', v)
}
},
methods: {
submit () {
const self = this
self.isLoading = true
self.errors = []
const payload = {
...this.values,
artist: this.channel.artist.id
}
return axios.post('albums/', payload).then(
response => {
self.isLoading = false
self.$emit('created')
},
error => {
self.errors = error.backendErrors
self.isLoading = false
}
)
}
}
}
</script>

View File

@ -1,3 +1,43 @@
<script setup lang="ts">
import type { Channel } from '~/types'
import { useGettext } from 'vue3-gettext'
import { useStore } from '~/store'
import { computed } from 'vue'
import LoginModal from '~/components/common/LoginModal.vue'
interface Emits {
(e: 'unsubscribed'): void
(e: 'subscribed'): void
}
interface Props {
channel: Channel
}
const emit = defineEmits<Emits>()
const props = defineProps<Props>()
const { $pgettext } = useGettext()
const store = useStore()
const isSubscribed = computed(() => store.getters['channels/isSubscribed'](props.channel.uuid))
const title = computed(() => isSubscribed.value
? $pgettext('Content/Channel/Button/Verb', 'Subscribe')
: $pgettext('Content/Channel/Button/Verb', 'Unsubscribe')
)
const message = computed(() => ({
authMessage: $pgettext('Popup/Message/Paragraph', 'You need to be logged in to subscribe to this channel')
}))
const toggle = async () => {
await store.dispatch('channels/toggle', props.channel.uuid)
emit(isSubscribed.value ? 'unsubscribed' : 'subscribed')
}
</script>
<template>
<button
v-if="$store.state.auth.authenticated"
@ -5,18 +45,7 @@
@click.stop="toggle"
>
<i class="heart icon" />
<translate
v-if="isSubscribed"
translate-context="Content/Track/Button.Message"
>
Unsubscribe
</translate>
<translate
v-else
translate-context="Content/Track/*/Verb"
>
Subscribe
</translate>
{{ title }}
</button>
<button
v-else
@ -24,57 +53,14 @@
@click="$refs.loginModal.show = true"
>
<i class="heart icon" />
<translate translate-context="Content/Track/*/Verb">
Subscribe
</translate>
{{ title }}
<login-modal
ref="loginModal"
class="small"
:next-route="$route.fullPath"
:message="message.authMessage"
:cover="channel.artist.cover"
:cover="channel.artist.cover!"
@created="$refs.loginModal.show = false;"
/>
</button>
</template>
<script>
import LoginModal from '~/components/common/LoginModal.vue'
export default {
components: {
LoginModal
},
props: {
channel: { type: Object, required: true }
},
computed: {
title () {
if (this.isSubscribed) {
return this.$pgettext('Content/Channel/Button/Verb', 'Subscribe')
} else {
return this.$pgettext('Content/Channel/Button/Verb', 'Unsubscribe')
}
},
isSubscribed () {
return this.$store.getters['channels/isSubscribed'](this.channel.uuid)
},
message () {
return {
authMessage: this.$pgettext('Popup/Message/Paragraph', 'You need to be logged in to subscribe to this channel')
}
}
},
methods: {
toggle () {
if (this.isSubscribed) {
this.$emit('unsubscribed')
} else {
this.$emit('subscribed')
}
this.$store.dispatch('channels/toggle', this.channel.uuid)
}
}
}
</script>

View File

@ -230,6 +230,7 @@
</template>
</form>
</template>
<script>
import axios from 'axios'
import $ from 'jquery'
@ -247,6 +248,8 @@ function setIfEmpty (obj, k, v) {
obj[k] = v
}
// TODO (wvffle): Find types in UploadMetadataForm.vue
export default {
components: {
AlbumSelect,

View File

@ -1,3 +1,38 @@
<script setup lang="ts">
import type { Upload, Track } from '~/types'
import { ref, computed, watch } from 'vue'
import TagsSelector from '~/components/library/TagsSelector.vue'
import AttachmentInput from '~/components/common/AttachmentInput.vue'
interface Emits {
// TODO (wvffle): Find correct type
(e: 'values', values: any): void
}
interface Props {
upload: Upload
values?: Track | null
}
const emit = defineEmits<Emits>()
const props = withDefaults(defineProps<Props>(), {
values: null
})
// TODO (wvffle): This is something like a Track, but `cover` is a plain uuid
const newValues = ref({ ...(props.values ?? props.upload.import_metadata) } as any)
// computed: {
// isLoading () {
// return !!this.metadata
// }
// },
const isLoading = computed(() => !props.upload)
watch(newValues, (values) => emit('values', values), { immediate: true })
</script>
<template>
<div :class="['ui', {loading: isLoading}, 'form']">
<div class="ui required field">
@ -52,37 +87,3 @@
</div>
</div>
</template>
<script>
import TagsSelector from '~/components/library/TagsSelector.vue'
import AttachmentInput from '~/components/common/AttachmentInput.vue'
export default {
components: {
TagsSelector,
AttachmentInput
},
props: {
upload: { type: Object, required: true },
values: { type: Object, required: true }
},
data () {
return {
newValues: { ...this.values } || this.upload.import_metadata
}
},
computed: {
isLoading () {
return !!this.metadata
}
},
watch: {
newValues: {
handler (v) {
this.$emit('values', v)
},
immediate: true
}
}
}
</script>

View File

@ -1,3 +1,39 @@
<script setup lang="ts">
import type { BackendError } from '~/types'
import { ref } from 'vue'
// TODO (wvffle): Remove this component
import axios from 'axios'
interface Emits {
(e: 'action-done', data: any): void
(e: 'action-error', error: BackendError): void
}
interface Props {
method: 'get' | 'post' | 'put' | 'patch' | 'delete'
url: string
}
const emit = defineEmits<Emits>()
const props = defineProps<Props>()
const isLoading = ref(false)
const ajaxCall = async () => {
isLoading.value = true
try {
const response = await axios[props.method](props.url)
emit('action-done', response.data)
} catch (error) {
emit('action-error', error as BackendError)
}
isLoading.value = false
}
</script>
<template>
<button
:class="['ui', {loading: isLoading}, 'button']"
@ -6,31 +42,3 @@
<slot />
</button>
</template>
<script>
import axios from 'axios'
export default {
props: {
url: { type: String, required: true },
method: { type: String, required: true }
},
data () {
return {
isLoading: false
}
},
methods: {
ajaxCall () {
const self = this
this.isLoading = true
axios[this.method](this.url).then(response => {
self.$emit('action-done', response.data)
self.isLoading = false
}, error => {
self.isLoading = false
self.$emit('action-error', error)
})
}
}
}
</script>

View File

@ -1,3 +1,86 @@
<script setup lang="ts">
import type { BackendError } from '~/types'
import { ref, computed } from 'vue'
import { whenever } from '@vueuse/core'
import axios from 'axios'
import clip from 'text-clipper'
interface Emits {
// TODO (wvffle): Find correct type
(e: 'updated', data: unknown): void
}
interface Props {
content?: { text?: string, html?: string } | null
fieldName?: string
updateUrl?: string
canUpdate?: boolean
fetchHtml?: boolean
permissive?: boolean
truncateLength?: number
}
const emit = defineEmits<Emits>()
const props = withDefaults(defineProps<Props>(), {
content: null,
fieldName: 'description',
updateUrl: '',
canUpdate: true,
fetchHtml: false,
permissive: false,
truncateLength: 500
})
const preview = ref('')
const fetchPreview = async () => {
const response = await axios.post('text-preview/', { text: props.content?.text ?? '', permissive: props.permissive })
preview.value = response.data.rendered
}
whenever(() => props.fetchHtml, fetchPreview)
const truncatedHtml = computed(() => clip(props.content?.html ?? '', props.truncateLength, {
html: true,
maxLines: 3
}))
const showMore = ref(false)
const html = computed(() => props.fetchHtml
? preview.value
: props.truncateLength > 0 && !showMore.value
? truncatedHtml.value
: props.content?.html ?? ''
)
const isTruncated = computed(() => props.truncateLength > 0 && truncatedHtml.value.length < (props.content?.html ?? '').length)
const isUpdating = ref(false)
const text = ref(props.content?.text ?? '')
const isLoading = ref(false)
const errors = ref([] as string[])
const submit = async () => {
errors.value = []
isLoading.value = true
try {
const response = await axios.patch(props.updateUrl, {
[props.fieldName]: text.value
? { content_type: 'text/markdown', text: text.value }
: null
})
emit('updated', response.data)
isUpdating.value = false
} catch (error) {
errors.value = (error as BackendError).backendErrors
}
isLoading.value = false
}
</script>
<template>
<div>
<template v-if="content && !isUpdating">
@ -60,7 +143,7 @@
</ul>
</div>
<content-form
v-model="newText"
v-model="text"
:autofocus="true"
/>
<a
@ -72,7 +155,7 @@
<button
:class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
type="submit"
:disabled="isLoading || null"
:disabled="isLoading"
>
<translate translate-context="Content/Channels/Button.Label/Verb">
Update description
@ -82,80 +165,3 @@
</form>
</div>
</template>
<script>
import axios from 'axios'
import clip from 'text-clipper'
export default {
props: {
content: { type: Object, required: false, default: null },
fieldName: { type: String, required: false, default: 'description' },
updateUrl: { required: false, type: String, default: '' },
canUpdate: { required: false, default: true, type: Boolean },
fetchHtml: { required: false, default: false, type: Boolean },
permissive: { required: false, default: false, type: Boolean },
truncateLength: { required: false, default: 500, type: Number }
},
data () {
return {
isUpdating: false,
showMore: false,
newText: (this.content || { text: '' }).text,
isLoading: false,
errors: [],
preview: null
}
},
computed: {
html () {
if (this.fetchHtml) {
return this.preview
}
if (this.truncateLength > 0 && !this.showMore) {
return this.truncatedHtml
}
return this.content.html
},
truncatedHtml () {
return clip(this.content.html, this.truncateLength, { html: true, maxLines: 3 })
},
isTruncated () {
return this.truncateLength > 0 && this.truncatedHtml.length < this.content.html.length
}
},
async created () {
if (this.fetchHtml) {
await this.fetchPreview()
}
},
methods: {
async fetchPreview () {
const response = await axios.post('text-preview/', { text: this.content.text, permissive: this.permissive })
this.preview = response.data.rendered
},
submit () {
const self = this
this.isLoading = true
this.errors = []
const payload = {}
payload[this.fieldName] = null
if (this.newText) {
payload[this.fieldName] = {
content_type: 'text/markdown',
text: this.newText
}
}
axios.patch(this.updateUrl, payload).then((response) => {
self.$emit('updated', response.data)
self.isLoading = false
self.isUpdating = false
}, error => {
self.errors = error.backendErrors
self.isLoading = false
})
}
}
}
</script>

View File

@ -307,6 +307,8 @@ export interface Upload {
detail: object
error_code: string
}
import_metadata?: Track
}
// FileSystem Logs