kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
lint(front)
rodzic
7c2d414bc0
commit
7aed9c26f8
|
@ -76,15 +76,21 @@ useIntervalFn(() => {
|
|||
// NOTE: We're not checking if we're authenticated in the store,
|
||||
// because we want to learn if we are authenticated at all
|
||||
store.dispatch('auth/fetchUser')
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="funkwhale responsive">
|
||||
<Sidebar style="grid-area: sidebar;" />
|
||||
<RouterView v-slot="{ Component }" v-bind="color({}, ['default', 'solid'])()" :class="$style.layout"
|
||||
style="grid-area: main;">
|
||||
<Transition v-if="Component" mode="out-in">
|
||||
<RouterView
|
||||
v-slot="{ Component }"
|
||||
v-bind="color({}, ['default', 'solid'])()"
|
||||
:class="$style.layout"
|
||||
style="grid-area: main;"
|
||||
>
|
||||
<Transition
|
||||
v-if="Component"
|
||||
mode="out-in"
|
||||
>
|
||||
<KeepAlive :max="10">
|
||||
<Suspense>
|
||||
<component :is="Component" />
|
||||
|
@ -99,7 +105,10 @@ store.dispatch('auth/fetchUser')
|
|||
</transition>
|
||||
</RouterView>
|
||||
</div>
|
||||
<AudioPlayer class="funkwhale" v-bind="color({}, ['default', 'solid'])()" />
|
||||
<AudioPlayer
|
||||
class="funkwhale"
|
||||
v-bind="color({}, ['default', 'solid'])()"
|
||||
/>
|
||||
<ServiceMessages />
|
||||
<LanguagesModal />
|
||||
<ShortcutsModal />
|
||||
|
|
|
@ -37,7 +37,7 @@ const imageUrl = computed(() => props.album.cover?.urls.original
|
|||
:title="album.title"
|
||||
:image="imageUrl"
|
||||
:tags="album.tags"
|
||||
:to="{name: 'library.albums.detail', params: {id: album.id}}"
|
||||
:to="{ name: 'library.albums.detail', params: { id: album.id } }"
|
||||
small
|
||||
>
|
||||
<template #topright>
|
||||
|
@ -58,7 +58,7 @@ const imageUrl = computed(() => props.album.cover?.urls.original
|
|||
>
|
||||
<Link
|
||||
align-text="start"
|
||||
:to="{ name: 'library.artists.detail', params: { id: ac.artist.id }}"
|
||||
:to="{ name: 'library.artists.detail', params: { id: ac.artist.id } }"
|
||||
>
|
||||
{{ ac.credit ?? t('components.Queue.meta.unknownArtist') }}
|
||||
</Link>
|
||||
|
|
|
@ -162,7 +162,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
<div class="activity-content">
|
||||
<router-link
|
||||
class="funkwhale link artist"
|
||||
:to="{name: 'library.tracks.detail', params: {id: object.track.id}}"
|
||||
:to="{ name: 'library.tracks.detail', params: { id: object.track.id } }"
|
||||
>
|
||||
<Heading
|
||||
:h3="object.track.title"
|
||||
|
@ -202,7 +202,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
>
|
||||
<router-link
|
||||
class="funkwhale link user"
|
||||
:to="{name: 'profile.overview', params: {username: object.actor.name}}"
|
||||
:to="{ name: 'profile.overview', params: { username: object.actor.name } }"
|
||||
>
|
||||
<span class="at symbol" />{{ object.actor.name }}
|
||||
</router-link>
|
||||
|
@ -235,9 +235,11 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
@include light-theme {
|
||||
border-color: var(--fw-gray-300);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border-color: var(--fw-gray-800);
|
||||
}
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
gap: 12px;
|
||||
|
@ -247,25 +249,25 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
> .activity-image {
|
||||
>.activity-image {
|
||||
|
||||
width: 40px;
|
||||
aspect-ratio: 1;
|
||||
overflow: hidden;
|
||||
border-radius: var(--fw-border-radius);
|
||||
|
||||
> img {
|
||||
>img {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
> i {
|
||||
>i {
|
||||
font-size: 40px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
> .play-button {
|
||||
>.play-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -284,7 +286,7 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
}
|
||||
}
|
||||
|
||||
> .activity-content {
|
||||
>.activity-content {
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
@ -295,9 +297,10 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
}
|
||||
}
|
||||
|
||||
> .track-title {
|
||||
>.track-title {
|
||||
font-weight: 700;
|
||||
line-height: 1.5em;
|
||||
|
||||
@include dark-theme {
|
||||
color: var(--fw-gray-300);
|
||||
}
|
||||
|
@ -307,7 +310,8 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
|
|||
font-size: 15px;
|
||||
}
|
||||
|
||||
.user, time {
|
||||
.user,
|
||||
time {
|
||||
line-height: 1.5em;
|
||||
font-size: 0.8125rem;
|
||||
color: var(--fw-gray-500);
|
||||
|
|
|
@ -204,7 +204,7 @@ const remove = async () => {
|
|||
v-if="totalDuration > 0"
|
||||
:duration="totalDuration"
|
||||
/>
|
||||
<!--TODO: License -->
|
||||
<!--TODO: License -->
|
||||
</Layout>
|
||||
</Layout>
|
||||
<RenderedDescription
|
||||
|
@ -295,13 +295,17 @@ const remove = async () => {
|
|||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.meta {
|
||||
font-size: 15px;
|
||||
@include light-theme {
|
||||
color: var(--fw-gray-700);
|
||||
}
|
||||
@include dark-theme {
|
||||
color: var(--fw-gray-500);
|
||||
}
|
||||
@import '~/style/funkwhale.scss';
|
||||
|
||||
.meta {
|
||||
font-size: 15px;
|
||||
|
||||
@include light-theme {
|
||||
color: var(--fw-gray-700);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
color: var(--fw-gray-500);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -128,32 +128,79 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<Layout v-title="labels.title" stack main>
|
||||
<Header :h1="t('components.library.Artists.header.browse')" page-heading />
|
||||
<Layout form flex :class="['ui', { 'loading': isLoading }, 'form']" @submit.prevent="search">
|
||||
<Input id="artist-search" v-model="query" search name="search"
|
||||
:label="t('components.library.Artists.label.search')" autofocus :placeholder="labels.searchPlaceholder" />
|
||||
<Pills v-if="typeof tags === 'object'" :get="model => { tags = model.currents.map(({ label }) => label) }" :set="model => ({
|
||||
currents: tags.map(tag => ({ type: 'custom' as const, label: tag })),
|
||||
others: dataStore.tags().value
|
||||
.filter(({ name }) => result?.results?.some((object) => object.tags?.includes(name)) && !tags.includes(name))
|
||||
.map(({ name }) => ({ type: 'preset' as const, label: name })),
|
||||
})" :label="t('components.library.Artists.label.tags')" style="max-width: 350px;" />
|
||||
<Layout stack no-gap label for="artist-ordering">
|
||||
<Layout
|
||||
v-title="labels.title"
|
||||
stack
|
||||
main
|
||||
>
|
||||
<Header
|
||||
:h1="t('components.library.Artists.header.browse')"
|
||||
page-heading
|
||||
/>
|
||||
<Layout
|
||||
form
|
||||
flex
|
||||
:class="['ui', { 'loading': isLoading }, 'form']"
|
||||
@submit.prevent="search"
|
||||
>
|
||||
<Input
|
||||
id="artist-search"
|
||||
v-model="query"
|
||||
search
|
||||
name="search"
|
||||
:label="t('components.library.Artists.label.search')"
|
||||
autofocus
|
||||
:placeholder="labels.searchPlaceholder"
|
||||
/>
|
||||
<Pills
|
||||
v-if="typeof tags === 'object'"
|
||||
:get="model => { tags = model.currents.map(({ label }) => label) }"
|
||||
:set="model => ({
|
||||
currents: tags.map(tag => ({ type: 'custom' as const, label: tag })),
|
||||
others: dataStore.tags().value
|
||||
.filter(({ name }) => result?.results?.some((object) => object.tags?.includes(name)) && !tags.includes(name))
|
||||
.map(({ name }) => ({ type: 'preset' as const, label: name })),
|
||||
})"
|
||||
:label="t('components.library.Artists.label.tags')"
|
||||
style="max-width: 350px;"
|
||||
/>
|
||||
<Layout
|
||||
stack
|
||||
no-gap
|
||||
label
|
||||
for="artist-ordering"
|
||||
>
|
||||
<span class="label">
|
||||
{{ t('components.library.Artists.ordering.label') }}
|
||||
</span>
|
||||
<select id="artist-ordering" v-model="ordering" class="dropdown">
|
||||
<option v-for="(option, key) in orderingOptions" :key="key" :value="option[0]">
|
||||
<select
|
||||
id="artist-ordering"
|
||||
v-model="ordering"
|
||||
class="dropdown"
|
||||
>
|
||||
<option
|
||||
v-for="(option, key) in orderingOptions"
|
||||
:key="key"
|
||||
:value="option[0]"
|
||||
>
|
||||
{{ sharedLabels.filters[option[1]] }}
|
||||
</option>
|
||||
</select>
|
||||
</Layout>
|
||||
<Layout stack no-gap label for="artist-ordering-direction">
|
||||
<Layout
|
||||
stack
|
||||
no-gap
|
||||
label
|
||||
for="artist-ordering-direction"
|
||||
>
|
||||
<span class="label">
|
||||
{{ t('components.library.Artists.ordering.direction.label') }}
|
||||
</span>
|
||||
<select id="artist-ordering-direction" v-model="orderingDirection" class="dropdown">
|
||||
<select
|
||||
id="artist-ordering-direction"
|
||||
v-model="orderingDirection"
|
||||
class="dropdown"
|
||||
>
|
||||
<option value="+">
|
||||
{{ t('components.library.Artists.ordering.direction.ascending') }}
|
||||
</option>
|
||||
|
@ -162,42 +209,86 @@ const paginateOptions = computed(() => sortedUniq([12, 30, 50, paginateBy.value]
|
|||
</option>
|
||||
</select>
|
||||
</Layout>
|
||||
<Layout stack no-gap label for="artist-results">
|
||||
<Layout
|
||||
stack
|
||||
no-gap
|
||||
label
|
||||
for="artist-results"
|
||||
>
|
||||
<span class="label">
|
||||
{{ t('components.library.Artists.pagination.results') }}
|
||||
</span>
|
||||
<select id="artist-results" v-model="paginateBy" class="dropdown">
|
||||
<option v-for="opt in paginateOptions" :key="opt" :value="opt">
|
||||
<select
|
||||
id="artist-results"
|
||||
v-model="paginateBy"
|
||||
class="dropdown"
|
||||
>
|
||||
<option
|
||||
v-for="opt in paginateOptions"
|
||||
:key="opt"
|
||||
:value="opt"
|
||||
>
|
||||
{{ opt }}
|
||||
</option>
|
||||
</select>
|
||||
</Layout>
|
||||
<Toggle id="exclude-compilation" v-model="excludeCompilation"
|
||||
:label="t('components.library.Artists.label.excludeCompilation')" true-value="true" false-value="null"
|
||||
type="checkbox" />
|
||||
<Toggle
|
||||
id="exclude-compilation"
|
||||
v-model="excludeCompilation"
|
||||
:label="t('components.library.Artists.label.excludeCompilation')"
|
||||
true-value="true"
|
||||
false-value="null"
|
||||
type="checkbox"
|
||||
/>
|
||||
</Layout>
|
||||
<Loader v-if="isLoading" />
|
||||
<Pagination v-if="page && result && result.count > paginateBy" v-model:page="page"
|
||||
:pages="Math.ceil(result.count / paginateBy)" />
|
||||
<Layout v-if="result && result.results.length > 0" grid
|
||||
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;">
|
||||
<ArtistCard v-for="artist in result.results" :key="artist.id" :artist="artist" />
|
||||
<Pagination
|
||||
v-if="page && result && result.count > paginateBy"
|
||||
v-model:page="page"
|
||||
:pages="Math.ceil(result.count / paginateBy)"
|
||||
/>
|
||||
<Layout
|
||||
v-if="result && result.results.length > 0"
|
||||
grid
|
||||
style="display:flex; flex-wrap:wrap; gap: 32px; margin-top:32px;"
|
||||
>
|
||||
<ArtistCard
|
||||
v-for="artist in result.results"
|
||||
:key="artist.id"
|
||||
:artist="artist"
|
||||
/>
|
||||
</Layout>
|
||||
<Layout v-else-if="result && result.results.length === 0" stack>
|
||||
<Layout
|
||||
v-else-if="result && result.results.length === 0"
|
||||
stack
|
||||
>
|
||||
<Alert yellow>
|
||||
<i class="compact disc icon" />
|
||||
{{ t('components.library.Artists.empty.noResults') }}
|
||||
</Alert>
|
||||
<Card v-if="store.state.auth.authenticated" :title="t('components.library.Artists.button.upload')" solid small
|
||||
primary style="text-align: center;" :to="useModal('upload').to">
|
||||
<Card
|
||||
v-if="store.state.auth.authenticated"
|
||||
:title="t('components.library.Artists.button.upload')"
|
||||
solid
|
||||
small
|
||||
primary
|
||||
style="text-align: center;"
|
||||
:to="useModal('upload').to"
|
||||
>
|
||||
<template #image>
|
||||
<i class="bi bi-upload" style="font-size: 100px; position: relative; top: 50px;" />
|
||||
<i
|
||||
class="bi bi-upload"
|
||||
style="font-size: 100px; position: relative; top: 50px;"
|
||||
/>
|
||||
</template>
|
||||
</Card>
|
||||
</Layout>
|
||||
<Spacer grow />
|
||||
<Pagination v-if="page && result && result.count > paginateBy" v-model:page="page"
|
||||
:pages="Math.ceil(result.count / paginateBy)" />
|
||||
<Pagination
|
||||
v-if="page && result && result.count > paginateBy"
|
||||
v-model:page="page"
|
||||
:pages="Math.ceil(result.count / paginateBy)"
|
||||
/>
|
||||
</Layout>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@ const props = defineProps<Props>()
|
|||
...$attrs,
|
||||
...color(props, ['solid'])(
|
||||
align(props)(
|
||||
))}"
|
||||
))
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
|
||||
|
|
|
@ -80,71 +80,118 @@ onUnmounted(() =>
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="split" class="funkwhale split-button">
|
||||
<button v-if="!dropdownOnly" ref="button" v-bind="{
|
||||
...$attrs,
|
||||
...color(props, ['interactive'])(
|
||||
width(props, isIconOnly ? ['square'] : ['normalHeight', 'buttonWidth'])(
|
||||
align(props, { alignText: 'center' })(
|
||||
)))
|
||||
}" class="funkwhale button split-main" :autofocus="autofocus || undefined"
|
||||
:disabled="disabled || undefined" :aria-pressed="props.ariaPressed" :class="{
|
||||
<div
|
||||
v-if="split"
|
||||
class="funkwhale split-button"
|
||||
>
|
||||
<button
|
||||
v-if="!dropdownOnly"
|
||||
ref="button"
|
||||
v-bind="{
|
||||
...$attrs,
|
||||
...color(props, ['interactive'])(
|
||||
width(props, isIconOnly ? ['square'] : ['normalHeight', 'buttonWidth'])(
|
||||
align(props, { alignText: 'center' })(
|
||||
))
|
||||
)
|
||||
}"
|
||||
class="funkwhale button split-main"
|
||||
:autofocus="autofocus || undefined"
|
||||
:disabled="disabled || undefined"
|
||||
:aria-pressed="props.ariaPressed"
|
||||
:class="{
|
||||
'is-loading': isLoading,
|
||||
'is-icon-only': isIconOnly,
|
||||
'has-icon': !!icon,
|
||||
'is-round': round,
|
||||
'is-shadow': shadow,
|
||||
}" @click="click">
|
||||
}"
|
||||
@click="click"
|
||||
>
|
||||
<slot name="main">
|
||||
<i v-if="icon && !icon.startsWith('right ')" :class="['bi', icon]" />
|
||||
<i
|
||||
v-if="icon && !icon.startsWith('right ')"
|
||||
:class="['bi', icon]"
|
||||
/>
|
||||
|
||||
<span v-if="!isIconOnly">
|
||||
<slot />
|
||||
</span>
|
||||
|
||||
<i v-if="icon && icon.startsWith('right ')" :class="['bi', icon.replace('right ', '')]" />
|
||||
<i
|
||||
v-if="icon && icon.startsWith('right ')"
|
||||
:class="['bi', icon.replace('right ', '')]"
|
||||
/>
|
||||
</slot>
|
||||
<Loader v-if="isLoading" :container="false" />
|
||||
<Loader
|
||||
v-if="isLoading"
|
||||
:container="false"
|
||||
/>
|
||||
</button>
|
||||
<button v-bind="{
|
||||
...$attrs,
|
||||
...color(props, ['interactive'])(
|
||||
width(props, isSplitIconOnly ? ['square'] : ['normalHeight', 'buttonWidth'])(
|
||||
align(props, { alignSelf: 'start', alignText: 'center' })(
|
||||
)))
|
||||
}" :disabled="disabled || undefined" :autofocus="autofocus || undefined" :class="[
|
||||
'funkwhale',
|
||||
'button',
|
||||
{
|
||||
'split-toggle': true,
|
||||
'is-loading': isLoading,
|
||||
'is-icon-only': isSplitIconOnly,
|
||||
'has-icon': !!splitIcon,
|
||||
'is-round': round,
|
||||
'is-shadow': shadow
|
||||
}
|
||||
]" @click="onSplitClick">
|
||||
<button
|
||||
v-bind="{
|
||||
...$attrs,
|
||||
...color(props, ['interactive'])(
|
||||
width(props, isSplitIconOnly ? ['square'] : ['normalHeight', 'buttonWidth'])(
|
||||
align(props, { alignSelf: 'start', alignText: 'center' })(
|
||||
)))
|
||||
}"
|
||||
:disabled="disabled || undefined"
|
||||
:autofocus="autofocus || undefined"
|
||||
:class="[
|
||||
'funkwhale',
|
||||
'button',
|
||||
{
|
||||
'split-toggle': true,
|
||||
'is-loading': isLoading,
|
||||
'is-icon-only': isSplitIconOnly,
|
||||
'has-icon': !!splitIcon,
|
||||
'is-round': round,
|
||||
'is-shadow': shadow
|
||||
}
|
||||
]"
|
||||
@click="onSplitClick"
|
||||
>
|
||||
<span v-if="splitTitle">{{ splitTitle }}</span>
|
||||
<i :class="['bi', splitIcon]" />
|
||||
</button>
|
||||
</div>
|
||||
<button v-else ref="button" v-bind="color(props, ['interactive'])(
|
||||
width(props, isIconOnly ? ['square'] : ['normalHeight', 'buttonWidth'])(
|
||||
align(props, { alignText: 'center' })(
|
||||
)))" :disabled="disabled || undefined" :autofocus="autofocus || undefined" class="funkwhale button"
|
||||
:aria-pressed="props.ariaPressed" :class="{
|
||||
<button
|
||||
v-else
|
||||
ref="button"
|
||||
v-bind="color(props, ['interactive'])(
|
||||
width(props, isIconOnly ? ['square'] : ['normalHeight', 'buttonWidth'])(
|
||||
align(props, { alignText: 'center' })(
|
||||
)))"
|
||||
:disabled="disabled || undefined"
|
||||
:autofocus="autofocus || undefined"
|
||||
class="funkwhale button"
|
||||
:aria-pressed="props.ariaPressed"
|
||||
:class="{
|
||||
'is-loading': isLoading,
|
||||
'is-icon-only': isIconOnly,
|
||||
'has-icon': !!icon,
|
||||
'is-round': round,
|
||||
'is-shadow': shadow,
|
||||
}" :type="onClick ? 'button' : 'submit' /* Prevents default `submit` if onCLick is set */" @click="click">
|
||||
<i v-if="icon && !icon.startsWith('right ')" :class="['bi', icon]" />
|
||||
}"
|
||||
:type="onClick ? 'button' : 'submit' /* Prevents default `submit` if onCLick is set */"
|
||||
@click="click"
|
||||
>
|
||||
<i
|
||||
v-if="icon && !icon.startsWith('right ')"
|
||||
:class="['bi', icon]"
|
||||
/>
|
||||
<span v-if="!isIconOnly">
|
||||
<slot />
|
||||
</span>
|
||||
<i v-if="icon && icon.startsWith('right ')" :class="['bi', icon.replace('right ', '')]" />
|
||||
<Loader v-if="isLoading" :container="false" />
|
||||
<i
|
||||
v-if="icon && icon.startsWith('right ')"
|
||||
:class="['bi', icon.replace('right ', '')]"
|
||||
/>
|
||||
<Loader
|
||||
v-if="isLoading"
|
||||
:container="false"
|
||||
/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -9,18 +9,18 @@ import Button from '~/components/ui/Button.vue'
|
|||
import Layout from '~/components/ui/Layout.vue'
|
||||
|
||||
const { icon, placeholder, ...props } = defineProps<{
|
||||
icon?: string;
|
||||
placeholder?: string;
|
||||
password?: true;
|
||||
search?: true;
|
||||
numeric?: true;
|
||||
label?: string;
|
||||
autofocus?: boolean;
|
||||
reset?:() => void;
|
||||
} & (ColorProps | DefaultProps | PastelProps)
|
||||
& VariantProps
|
||||
& RaisedProps
|
||||
& WidthProps
|
||||
icon?: string;
|
||||
placeholder?: string;
|
||||
password?: true;
|
||||
search?: true;
|
||||
numeric?: true;
|
||||
label?: string;
|
||||
autofocus?: boolean;
|
||||
reset?: () => void;
|
||||
} & (ColorProps | DefaultProps | PastelProps)
|
||||
& VariantProps
|
||||
& RaisedProps
|
||||
& WidthProps
|
||||
>()
|
||||
|
||||
// TODO(A11y): Add `inputmode="numeric" pattern="[0-9]*"` to input if model type is number:
|
||||
|
@ -56,7 +56,7 @@ onUnmounted(() =>
|
|||
previouslyFocusedElement.value?.focus()
|
||||
)
|
||||
|
||||
const model = defineModel<string|number>({ required: true })
|
||||
const model = defineModel<string | number>({ required: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -82,7 +82,7 @@ const model = defineModel<string|number>({ required: true })
|
|||
</span>
|
||||
|
||||
<input
|
||||
v-bind="{...$attrs, ...attributes, ...color(props, ['solid', 'default', 'secondary'])(width(props)())}"
|
||||
v-bind="{ ...$attrs, ...attributes, ...color(props, ['solid', 'default', 'secondary'])(width(props)()) }"
|
||||
ref="input"
|
||||
v-model="model"
|
||||
:autofocus="autofocus || undefined"
|
||||
|
@ -122,7 +122,7 @@ const model = defineModel<string|number>({ required: true })
|
|||
<!-- Password -->
|
||||
<button
|
||||
v-if="props.password"
|
||||
v-bind="{...$attrs, ...attributes, ...color(props, ['solid', 'default', 'secondary'])()}"
|
||||
v-bind="{ ...$attrs, ...attributes, ...color(props, ['solid', 'default', 'secondary'])() }"
|
||||
style="background:transparent; border:none; appearance:none; height:calc(100% - 16px); color:var(--color); cursor:pointer;"
|
||||
role="switch"
|
||||
type="button"
|
||||
|
|
|
@ -16,10 +16,10 @@ const props = defineProps<{
|
|||
icon?: string;
|
||||
round?: true;
|
||||
|
||||
autofocus? : boolean
|
||||
forceUnderline? : true
|
||||
autofocus?: boolean
|
||||
forceUnderline?: true
|
||||
} & RouterLinkProps
|
||||
&(ColorProps | DefaultProps)
|
||||
& (ColorProps | DefaultProps)
|
||||
& VariantProps
|
||||
& WidthProps
|
||||
& AlignmentProps>()
|
||||
|
@ -40,8 +40,8 @@ const [fontWeight, activeFontWeight] = 'solid' in props || props.thickWhenActive
|
|||
const isIconOnly = computed(() =>
|
||||
!!props.icon && (
|
||||
!useSlots().default
|
||||
|| 'square' in props && props.square
|
||||
|| 'squareSmall' in props && props.squareSmall
|
||||
|| 'square' in props && props.square
|
||||
|| 'squareSmall' in props && props.squareSmall
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -55,13 +55,12 @@ onMounted(() => {
|
|||
<template>
|
||||
<component
|
||||
:is="isExternalLink ? 'a' : RouterLink"
|
||||
v-bind="
|
||||
color(props, ['interactive'])(
|
||||
width(props,
|
||||
isNoColors(props) ? [] : ['normalHeight', 'solid' in props ? 'buttonWidth' : 'auto']
|
||||
)(
|
||||
align(props, 'solid' in props ? {alignText: 'center'} : {})(
|
||||
)))"
|
||||
v-bind="color(props, ['interactive'])(
|
||||
width(props,
|
||||
isNoColors(props) ? [] : ['normalHeight', 'solid' in props ? 'buttonWidth' : 'auto']
|
||||
)(
|
||||
align(props, 'solid' in props ? { alignText: 'center' } : {})(
|
||||
)))"
|
||||
ref="button"
|
||||
:autofocus="autofocus || undefined"
|
||||
:class="[
|
||||
|
@ -88,81 +87,85 @@ onMounted(() => {
|
|||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.link {
|
||||
|
||||
.link {
|
||||
// Layout
|
||||
|
||||
// Layout
|
||||
--padding: 16px;
|
||||
--shift-by: 0.5px;
|
||||
|
||||
--padding: 16px;
|
||||
--shift-by: 0.5px;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
|
||||
padding: calc(var(--padding) / 2 - var(--shift-by)) var(--padding) calc(var(--padding) / 2 + var(--shift-by)) var(--padding);
|
||||
|
||||
&.is-icon-only {
|
||||
padding: var(--padding);
|
||||
}
|
||||
|
||||
&.no-spacing {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
// Font
|
||||
|
||||
font-family: var(--font-main);
|
||||
font-weight: v-bind(fontWeight);
|
||||
font-size: 14px;
|
||||
|
||||
line-height: 1em;
|
||||
|
||||
// Content
|
||||
|
||||
>span {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
top: calc(0px - var(--shift-by));
|
||||
}
|
||||
|
||||
padding: calc(var(--padding) / 2 - var(--shift-by)) var(--padding) calc(var(--padding) / 2 + var(--shift-by)) var(--padding);
|
||||
&.is-icon-only {
|
||||
padding: var(--padding);
|
||||
}
|
||||
&.no-spacing {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 1em;
|
||||
// Decoration
|
||||
|
||||
&:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
|
||||
transition:background-color .2s,
|
||||
border-color .3s;
|
||||
|
||||
&:not(.force-underline) {
|
||||
text-decoration: none;
|
||||
// background-color: transparent;
|
||||
// border-color: transparent;
|
||||
}
|
||||
|
||||
border-radius: var(--fw-border-radius);
|
||||
|
||||
&.is-round {
|
||||
border-radius: 100vh;
|
||||
}
|
||||
|
||||
// States
|
||||
|
||||
&:global(.router-link-exact-active) {
|
||||
font-weight: v-bind(activeFontWeight);
|
||||
}
|
||||
|
||||
// Icon
|
||||
|
||||
>i:global(.bi) {
|
||||
font-size: 1.2rem;
|
||||
|
||||
&.large {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
// Font
|
||||
|
||||
font-family: $font-main;
|
||||
font-weight: v-bind(fontWeight);
|
||||
font-size: 14px;
|
||||
|
||||
line-height: 1em;
|
||||
|
||||
// Content
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
top: calc(0px - var(--shift-by));
|
||||
}
|
||||
|
||||
// Decoration
|
||||
|
||||
&:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
|
||||
transition:background-color .2s, border-color .3s;
|
||||
|
||||
&:not(.force-underline) {
|
||||
text-decoration: none;
|
||||
// background-color: transparent;
|
||||
// border-color: transparent;
|
||||
}
|
||||
|
||||
border-radius: var(--fw-border-radius);
|
||||
|
||||
&.is-round {
|
||||
border-radius: 100vh;
|
||||
}
|
||||
|
||||
// States
|
||||
|
||||
&:global(.router-link-exact-active) {
|
||||
font-weight: v-bind(activeFontWeight);
|
||||
}
|
||||
|
||||
// Icon
|
||||
|
||||
> i:global(.bi) {
|
||||
font-size: 1.2rem;
|
||||
&.large {
|
||||
font-size:2rem;
|
||||
}
|
||||
&+span:not(:empty) {
|
||||
margin-left: 1ch;
|
||||
}
|
||||
&+span:not(:empty) {
|
||||
margin-left: 1ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -19,8 +19,8 @@ const shouldDelayClose = ref(true)
|
|||
const isOpenDelayed = refDebounced(isOpen, () => isOpen.value ? 0 : (shouldDelayClose.value ? 300 : 0))
|
||||
|
||||
const { positioning = 'vertical', ...colorProps } = defineProps<{
|
||||
positioning?:'horizontal' | 'vertical'
|
||||
} &(ColorProps | DefaultProps) & RaisedProps>()
|
||||
positioning?: 'horizontal' | 'vertical'
|
||||
} & (ColorProps | DefaultProps) & RaisedProps>()
|
||||
|
||||
// Template refs
|
||||
const popover = ref()
|
||||
|
|
|
@ -16,12 +16,12 @@ const keys = computed(() => Object.keys(props.options) as T[])
|
|||
const model = defineModel<T | undefined>({ required: true })
|
||||
|
||||
const index = computed({
|
||||
get () {
|
||||
get() {
|
||||
return model.value
|
||||
? keys.value.indexOf(model.value)
|
||||
: undefined
|
||||
},
|
||||
set (newIndex) {
|
||||
set(newIndex) {
|
||||
model.value = newIndex
|
||||
? keys.value[newIndex]
|
||||
: undefined
|
||||
|
@ -43,8 +43,8 @@ onMounted(() => {
|
|||
:style="`
|
||||
--step-size: calc(100% / ${keys.length + 2});
|
||||
--slider-width: calc(var(--step-size) * ${keys.length - 1} + 16px);
|
||||
--slider-opacity: ${ index === undefined ? .5 : 1 };
|
||||
--current-step: ${ index === undefined ? keys.length - 1 : index };
|
||||
--slider-opacity: ${index === undefined ? .5 : 1};
|
||||
--current-step: ${index === undefined ? keys.length - 1 : index};
|
||||
`"
|
||||
>
|
||||
<!-- Label -->
|
||||
|
@ -71,7 +71,7 @@ onMounted(() => {
|
|||
<button
|
||||
v-for="key in keys"
|
||||
:key="key"
|
||||
:class="[$style.key, { [$style.current]: key === model } ]"
|
||||
:class="[$style.key, { [$style.current]: key === model }]"
|
||||
style="flex-basis: var(--step-size); padding-bottom: 8px;"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
|
|
|
@ -230,7 +230,7 @@ onMounted(() => {
|
|||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:rows="initialLines"
|
||||
:style="`min-height:${((typeof(initialLines) === 'string' ? parseInt(initialLines) : (initialLines ?? 3)) + 1.2) * 1.5}rem`"
|
||||
:style="`min-height:${((typeof (initialLines) === 'string' ? parseInt(initialLines) : (initialLines ?? 3)) + 1.2) * 1.5}rem`"
|
||||
@click="updateLineNumber"
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
|
@ -343,7 +343,8 @@ onMounted(() => {
|
|||
<span
|
||||
v-if="charLimit !== Infinity && typeof charLimit === 'number'"
|
||||
class="letter-count"
|
||||
>{{ charLimit - model.length }}</span>
|
||||
>{{ charLimit -
|
||||
model.length }}</span>
|
||||
|
||||
<Spacer />
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useScroll } from '@vueuse/core'
|
|||
|
||||
import Button from '~/components/ui/Button.vue'
|
||||
|
||||
const { heading = 'h1' } = defineProps<{heading?:'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'}>()
|
||||
const { heading = 'h1' } = defineProps<{ heading?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' }>()
|
||||
|
||||
const toc = ref()
|
||||
|
||||
|
|
|
@ -45,24 +45,30 @@ const diameter = big ? '28px' : '20px'
|
|||
&[checked] {
|
||||
--void-color: var(--void-on-background-color);
|
||||
--pin-color: var(--void-on-pin-color);
|
||||
&::after{
|
||||
transform:translateX(var(--diameter));
|
||||
|
||||
&::after {
|
||||
transform: translateX(var(--diameter));
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:has(:focus-visible) {
|
||||
&:hover,
|
||||
&:has(:focus-visible) {
|
||||
--void-color: var(--void-off-hover-background-color);
|
||||
--pin-color: var(--void-off-hover-pin-color);
|
||||
|
||||
&[checked] {
|
||||
--void-color: var(--void-on-hover-background-color);
|
||||
--pin-color: var(--void-on-hover-pin-color);
|
||||
}
|
||||
}
|
||||
&::before, &::after {
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: var(--diameter);
|
||||
}
|
||||
|
||||
&::before {
|
||||
height: var(--diameter);
|
||||
aspect-ratio: 2;
|
||||
|
@ -70,6 +76,7 @@ const diameter = big ? '28px' : '20px'
|
|||
left: 0;
|
||||
top: calc(var(--padding) * 2 - var(--diameter) / 2);
|
||||
}
|
||||
|
||||
&::after {
|
||||
height: calc(var(--diameter) - var(--lineWidth) * 2);
|
||||
aspect-ratio: 1;
|
||||
|
@ -79,7 +86,7 @@ const diameter = big ? '28px' : '20px'
|
|||
transition: all .2s;
|
||||
}
|
||||
|
||||
> span {
|
||||
>span {
|
||||
padding-left: calc(var(--diameter) * 2 - 12px);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,14 +78,14 @@ const labels = computed(() => ({
|
|||
<template #items>
|
||||
<PopoverItem
|
||||
v-if="store.state.auth.authenticated"
|
||||
:to="{name: 'profile.overview', params: { username: store.state.auth.username },}"
|
||||
:to="{ name: 'profile.overview', params: { username: store.state.auth.username }, }"
|
||||
>
|
||||
<i class="bi bi-person-fill" />
|
||||
{{ labels.profile }}
|
||||
</PopoverItem>
|
||||
<PopoverItem
|
||||
v-if="store.state.auth.authenticated"
|
||||
:to="{name: 'notifications'}"
|
||||
:to="{ name: 'notifications' }"
|
||||
>
|
||||
<i class="bi bi-inbox-fill" />
|
||||
{{ labels.notifications }}
|
||||
|
@ -123,7 +123,7 @@ const labels = computed(() => ({
|
|||
<PopoverItem
|
||||
v-for="th in themes"
|
||||
:key="th.key"
|
||||
@click="theme=th.key"
|
||||
@click="theme = th.key"
|
||||
>
|
||||
<i :class="th.icon" />
|
||||
{{ th.name }}
|
||||
|
|
|
@ -88,7 +88,7 @@ const tabs = computed(() => [
|
|||
<!-- eslint-enable @intlify/vue-i18n/no-raw-text -->
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
font-weight: 900;
|
||||
|
@ -109,7 +109,8 @@ h1 {
|
|||
|
||||
.filesystem-stats {
|
||||
color: var(--fw-gray-700);
|
||||
> .flex {
|
||||
|
||||
>.flex {
|
||||
padding: 1ch;
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +167,7 @@ h1 {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.funkwhale.card {
|
||||
margin-bottom: 2rem;
|
||||
transition: margin-bottom 0.2s ease;
|
||||
|
@ -211,7 +213,7 @@ label {
|
|||
align-items: center;
|
||||
margin: 2rem 0 1rem;
|
||||
|
||||
> .file-count {
|
||||
>.file-count {
|
||||
margin-right: auto;
|
||||
color: var(--fw-gray-600);
|
||||
font-weight: 900;
|
||||
|
@ -228,7 +230,7 @@ label {
|
|||
border-top: 1px solid var(--fw-gray-200);
|
||||
}
|
||||
|
||||
> .track-cover {
|
||||
>.track-cover {
|
||||
height: 3rem;
|
||||
width: 3rem;
|
||||
border-radius: 0.5rem;
|
||||
|
@ -243,7 +245,7 @@ label {
|
|||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
> img {
|
||||
>img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -287,6 +289,7 @@ label {
|
|||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.track-progress {
|
||||
font-size: 0.875rem;
|
||||
color: var(--fw-gray-600);
|
||||
|
|
|
@ -162,7 +162,7 @@ const { state: items } = useAsyncState(
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.upload > .funkwhale.button {
|
||||
.upload>.funkwhale.button {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
|
@ -171,22 +171,22 @@ const { state: items } = useAsyncState(
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> .box {
|
||||
>.box {
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
flex-shrink: 0;
|
||||
background: var(--fw-pastel-blue-1);
|
||||
border-radius: 8px;
|
||||
margin-right:8px;
|
||||
margin-right: 8px;
|
||||
|
||||
+ div {
|
||||
+div {
|
||||
width: 100%;
|
||||
|
||||
> :last-child {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
> div {
|
||||
>div {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
@ -197,5 +197,4 @@ const { state: items } = useAsyncState(
|
|||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -459,6 +459,7 @@ h3.category {
|
|||
@include light-theme {
|
||||
border-color: var(--fw-gray-300);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border-color: var(--fw-gray-800);
|
||||
}
|
||||
|
|
|
@ -502,9 +502,7 @@ const open = ref(false)
|
|||
flex
|
||||
class="details"
|
||||
>
|
||||
<Link
|
||||
:to="{name: 'manage.library.tracks', query: {q: getQuery('album_id', object?.id) }}"
|
||||
>
|
||||
<Link :to="{ name: 'manage.library.tracks', query: { q: getQuery('album_id', object?.id) } }">
|
||||
{{ t('views.admin.library.AlbumDetail.link.tracks') }}
|
||||
</Link>
|
||||
<Spacer
|
||||
|
@ -540,6 +538,7 @@ h3.category {
|
|||
@include light-theme {
|
||||
border-color: var(--fw-gray-300);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border-color: var(--fw-gray-800);
|
||||
}
|
||||
|
@ -564,5 +563,4 @@ h3.category {
|
|||
border-bottom: 1px solid;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -141,7 +141,7 @@ const open = ref(false)
|
|||
primary
|
||||
low-height
|
||||
icon="bi-info-circle"
|
||||
:to="{name: 'library.artists.detail', params: {id: object.id }}"
|
||||
:to="{ name: 'library.artists.detail', params: { id: object.id } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.localProfile') }}
|
||||
</Link>
|
||||
|
@ -160,7 +160,7 @@ const open = ref(false)
|
|||
primary
|
||||
low-height
|
||||
icon="bi-pencil-fill"
|
||||
:to="{name: 'library.artists.edit', params: {id: object.id }}"
|
||||
:to="{ name: 'library.artists.edit', params: { id: object.id } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.button.edit') }}
|
||||
</Link>
|
||||
|
@ -256,7 +256,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.library.artists', query: {q: getQuery('category', object?.content_category) }}"
|
||||
:to="{ name: 'manage.library.artists', query: { q: getQuery('category', object?.content_category) } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.category') }}
|
||||
</Link>
|
||||
|
@ -273,7 +273,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.moderation.domains.detail', params: {id: object?.domain }}"
|
||||
:to="{ name: 'manage.moderation.domains.detail', params: { id: object?.domain } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.domain') }}
|
||||
</Link>
|
||||
|
@ -374,7 +374,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `artist:${object?.id}`) }}"
|
||||
:to="{ name: 'manage.moderation.reports.list', query: { q: getQuery('target', `artist:${object?.id}`) } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.reports') }}
|
||||
</Link>
|
||||
|
@ -390,7 +390,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.library.edits', query: {q: getQuery('target', 'artist ' + object?.id) }}"
|
||||
:to="{ name: 'manage.library.edits', query: { q: getQuery('target', 'artist ' + object?.id) } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.edits') }}
|
||||
</Link>
|
||||
|
@ -441,7 +441,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.library.libraries', query: {q: getQuery('artist_id', object?.id) }}"
|
||||
:to="{ name: 'manage.library.libraries', query: { q: getQuery('artist_id', object?.id) } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.libraries') }}
|
||||
</Link>
|
||||
|
@ -457,7 +457,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.library.uploads', query: {q: getQuery('artist_id', object?.id) }}"
|
||||
:to="{ name: 'manage.library.uploads', query: { q: getQuery('artist_id', object?.id) } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.uploads') }}
|
||||
</Link>
|
||||
|
@ -473,7 +473,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.library.albums', query: {q: getQuery('artist_id', object?.id) }}"
|
||||
:to="{ name: 'manage.library.albums', query: { q: getQuery('artist_id', object?.id) } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.albums') }}
|
||||
</Link>
|
||||
|
@ -489,7 +489,7 @@ const open = ref(false)
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.library.tracks', query: {q: getQuery('artist_id', object?.id) }}"
|
||||
:to="{ name: 'manage.library.tracks', query: { q: getQuery('artist_id', object?.id) } }"
|
||||
>
|
||||
{{ t('views.admin.library.ArtistDetail.link.tracks') }}
|
||||
</Link>
|
||||
|
@ -524,6 +524,7 @@ h3.category {
|
|||
@include light-theme {
|
||||
border-color: var(--fw-gray-300);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border-color: var(--fw-gray-800);
|
||||
}
|
||||
|
|
|
@ -250,6 +250,7 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
@include light-theme {
|
||||
border-color: var(--fw-gray-300);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border-color: var(--fw-gray-800);
|
||||
}
|
||||
|
|
|
@ -499,6 +499,8 @@ const getQuery = (field: string, value: string) => `${field}:"${value}"`
|
|||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~/style/funkwhale.scss';
|
||||
|
||||
.channel-image {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
|
@ -519,6 +521,7 @@ h3.category {
|
|||
@include light-theme {
|
||||
border-color: var(--fw-gray-300);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border-color: var(--fw-gray-800);
|
||||
}
|
||||
|
|
|
@ -392,7 +392,9 @@ const displayName = (object: any) => object?.filename ?? object?.source ?? objec
|
|||
/>
|
||||
<span class="value">
|
||||
<template v-if="object?.bitrate">
|
||||
{{ t('views.admin.library.UploadDetail.table.audioContent.bitrate.value', { bitrate: humanSize(object?.bitrate) }) }}
|
||||
{{ t('views.admin.library.UploadDetail.table.audioContent.bitrate.value', {
|
||||
bitrate:
|
||||
humanSize(object?.bitrate) }) }}
|
||||
</template>
|
||||
<span v-else>
|
||||
{{ t('views.admin.library.UploadDetail.notApplicable') }}
|
||||
|
|
|
@ -409,7 +409,9 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
flex
|
||||
class="details"
|
||||
>
|
||||
<router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `account:${object?.full_username}`) }}">
|
||||
<router-link
|
||||
:to="{ name: 'manage.moderation.reports.list', query: { q: getQuery('target', `account:${object?.full_username}`) } }"
|
||||
>
|
||||
{{ t('views.admin.moderation.AccountsDetail.link.linkedReports') }}
|
||||
</router-link>
|
||||
<Spacer
|
||||
|
@ -423,7 +425,9 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
flex
|
||||
class="details"
|
||||
>
|
||||
<router-link :to="{name: 'manage.moderation.requests.list', query: {q: getQuery('submitter', `${object?.full_username}`) }}">
|
||||
<router-link
|
||||
:to="{ name: 'manage.moderation.requests.list', query: { q: getQuery('submitter', `${object?.full_username}`) } }"
|
||||
>
|
||||
{{ t('views.admin.moderation.AccountsDetail.link.requests') }}
|
||||
</router-link>
|
||||
<Spacer
|
||||
|
@ -525,9 +529,11 @@ const updatePolicy = (newPolicy: InstancePolicy) => {
|
|||
text-align: center;
|
||||
align-content: center;
|
||||
border-radius: 50%;
|
||||
|
||||
@include light-theme {
|
||||
background-color: var(--fw-gray-200);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
background-color: var(--fw-gray-800);
|
||||
}
|
||||
|
@ -547,6 +553,7 @@ h3.category {
|
|||
@include light-theme {
|
||||
border-color: var(--fw-gray-300);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
border-color: var(--fw-gray-800);
|
||||
}
|
||||
|
|
|
@ -183,9 +183,7 @@ const setAllowList = async (value: boolean) => {
|
|||
</Layout>
|
||||
</Header>
|
||||
|
||||
<Alert
|
||||
blue
|
||||
>
|
||||
<Alert blue>
|
||||
<template v-if="isLoadingPolicy">
|
||||
<div class="paragraph">
|
||||
<div class="line" />
|
||||
|
@ -296,7 +294,11 @@ const setAllowList = async (value: boolean) => {
|
|||
grow
|
||||
/>
|
||||
<span class="value">
|
||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.software.value', {name: get(object, 'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version: get(object, 'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.software.value', {
|
||||
name: get(object,
|
||||
'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version:
|
||||
get(object,
|
||||
'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
||||
</span>
|
||||
</Layout>
|
||||
<Layout
|
||||
|
@ -312,7 +314,11 @@ const setAllowList = async (value: boolean) => {
|
|||
grow
|
||||
/>
|
||||
<span class="value">
|
||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.nodeInfoStatus.value', {name: get(object, 'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version: get(object, 'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
||||
{{ t('views.admin.moderation.DomainsDetail.table.instanceData.nodeInfoStatus.value', {
|
||||
name: get(object,
|
||||
'nodeinfo.payload.software.name', t('views.admin.moderation.DomainsDetail.notApplicable')), version:
|
||||
get(object,
|
||||
'nodeinfo.payload.software.version', t('views.admin.moderation.DomainsDetail.notApplicable'))}) }}
|
||||
</span>
|
||||
<span :data-tooltip="object.nodeinfo.error"><i class="bi bi-question-circle" /></span>
|
||||
</Layout>
|
||||
|
@ -358,7 +364,7 @@ const setAllowList = async (value: boolean) => {
|
|||
>
|
||||
<Link
|
||||
class="label"
|
||||
:to="{name: 'manage.moderation.accounts.list', query: {q: 'domain:' + object?.name }}"
|
||||
:to="{ name: 'manage.moderation.accounts.list', query: { q: 'domain:' + object?.name } }"
|
||||
>
|
||||
{{ t('views.admin.moderation.DomainsDetail.link.knownAccounts') }}
|
||||
</Link>
|
||||
|
@ -415,7 +421,7 @@ const setAllowList = async (value: boolean) => {
|
|||
class="details"
|
||||
>
|
||||
<span class="label">
|
||||
<router-link :to="{name: 'manage.channels', query: {q: getQuery('domain', object.name) }}">
|
||||
<router-link :to="{ name: 'manage.channels', query: { q: getQuery('domain', object.name) } }">
|
||||
{{ t('views.admin.moderation.DomainsDetail.link.channels') }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
@ -430,7 +436,7 @@ const setAllowList = async (value: boolean) => {
|
|||
class="details"
|
||||
>
|
||||
<span class="label">
|
||||
<router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('domain', object.name) }}">
|
||||
<router-link :to="{ name: 'manage.library.libraries', query: { q: getQuery('domain', object.name) } }">
|
||||
{{ t('views.admin.moderation.DomainsDetail.link.libraries') }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
@ -445,7 +451,7 @@ const setAllowList = async (value: boolean) => {
|
|||
class="details"
|
||||
>
|
||||
<span class="label">
|
||||
<router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('domain', object.name) }}">
|
||||
<router-link :to="{ name: 'manage.library.uploads', query: { q: getQuery('domain', object.name) } }">
|
||||
{{ t('views.admin.moderation.DomainsDetail.link.uploads') }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
@ -460,7 +466,7 @@ const setAllowList = async (value: boolean) => {
|
|||
class="details"
|
||||
>
|
||||
<span class="label">
|
||||
<router-link :to="{name: 'manage.library.artists', query: {q: getQuery('domain', object.name) }}">
|
||||
<router-link :to="{ name: 'manage.library.artists', query: { q: getQuery('domain', object.name) } }">
|
||||
{{ t('views.admin.moderation.DomainsDetail.link.artists') }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
@ -475,7 +481,7 @@ const setAllowList = async (value: boolean) => {
|
|||
class="details"
|
||||
>
|
||||
<span class="label">
|
||||
<router-link :to="{name: 'manage.library.albums', query: {q: getQuery('domain', object.name) }}">
|
||||
<router-link :to="{ name: 'manage.library.albums', query: { q: getQuery('domain', object.name) } }">
|
||||
{{ t('views.admin.moderation.DomainsDetail.link.albums') }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
@ -490,7 +496,7 @@ const setAllowList = async (value: boolean) => {
|
|||
class="details"
|
||||
>
|
||||
<span class="label">
|
||||
<router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('domain', object.name) }}">
|
||||
<router-link :to="{ name: 'manage.library.tracks', query: { q: getQuery('domain', object.name) } }">
|
||||
{{ t('views.admin.moderation.DomainsDetail.link.tracks') }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
|
|
@ -83,17 +83,17 @@ watch(props, fetchData, { immediate: true })
|
|||
const { copy, copied, isSupported } = useClipboard()
|
||||
|
||||
const tabs = ref([{
|
||||
title: t('views.auth.ProfileBase.link.overview') ,
|
||||
to: { name: 'profile.overview', params: routerParams }
|
||||
title: t('views.auth.ProfileBase.link.overview'),
|
||||
to: { name: 'profile.overview', params: routerParams }
|
||||
}, {
|
||||
title: t('views.auth.ProfileBase.link.activity') ,
|
||||
to: { name: 'profile.activity', params: routerParams }
|
||||
title: t('views.auth.ProfileBase.link.activity'),
|
||||
to: { name: 'profile.activity', params: routerParams }
|
||||
}, ...(
|
||||
store.state.auth.authenticated && fullUsername.value === store.state.auth.fullUsername
|
||||
? [{
|
||||
title: t('views.auth.ProfileBase.link.manageUploads') ,
|
||||
to: { name: 'profile.manageUploads', params: routerParams }
|
||||
}]
|
||||
title: t('views.auth.ProfileBase.link.manageUploads'),
|
||||
to: { name: 'profile.manageUploads', params: routerParams }
|
||||
}]
|
||||
: []
|
||||
)])
|
||||
|
||||
|
@ -115,7 +115,7 @@ const isOpen = useModal('artist-description').isOpen
|
|||
:action="{
|
||||
text: t('views.auth.ProfileBase.link.edit'),
|
||||
// @ts-ignore
|
||||
to:'/settings',
|
||||
to: '/settings',
|
||||
// @ts-ignore
|
||||
primary: true,
|
||||
// @ts-ignore
|
||||
|
|
|
@ -153,12 +153,12 @@ const updateSubscriptionCount = (delta: number) => {
|
|||
const tabs = ref([
|
||||
{
|
||||
title: t('views.channels.DetailBase.link.channelOverview'),
|
||||
to: {name: 'channels.detail', params: { id: props.id }}
|
||||
to: { name: 'channels.detail', params: { id: props.id } }
|
||||
|
||||
},
|
||||
{
|
||||
title: t('views.channels.DetailBase.link.channelEpisodes'),
|
||||
to: {name: 'channels.detail.episodes', params: { id: props.id }}
|
||||
to: { name: 'channels.detail.episodes', params: { id: props.id } }
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
@ -200,14 +200,10 @@ const tabs = ref([
|
|||
no-gap
|
||||
>
|
||||
<template v-if="totalTracks > 0">
|
||||
<span
|
||||
v-if="object.artist?.content_category === 'podcast'"
|
||||
>
|
||||
<span v-if="object.artist?.content_category === 'podcast'">
|
||||
{{ t('views.channels.DetailBase.meta.episodes', totalTracks) }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
<span v-else>
|
||||
{{ t('views.channels.DetailBase.meta.tracks', totalTracks) }}
|
||||
</span>
|
||||
<i class="bi bi-dot" />
|
||||
|
@ -232,9 +228,7 @@ const tabs = ref([
|
|||
<span>
|
||||
{{ t('views.channels.DetailBase.header.podcastChannel') }}
|
||||
</span>
|
||||
<span
|
||||
v-if="!object.actor"
|
||||
>
|
||||
<span v-if="!object.actor">
|
||||
<i class="bi bi-dot" />
|
||||
<a
|
||||
:href="object.url || object.rss_url"
|
||||
|
@ -242,7 +236,7 @@ const tabs = ref([
|
|||
target="_blank"
|
||||
>
|
||||
<i class="bi bi-box-arrow-up-right" />
|
||||
{{ t('views.channels.DetailBase.link.mirrored', {domain: externalDomain}) }}
|
||||
{{ t('views.channels.DetailBase.link.mirrored', { domain: externalDomain }) }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -327,11 +321,11 @@ const tabs = ref([
|
|||
target="_blank"
|
||||
icon="bi-box-arrow-up-right"
|
||||
>
|
||||
{{ t('views.channels.DetailBase.link.domainView', {domain: object.actor.domain}) }}
|
||||
{{ t('views.channels.DetailBase.link.domainView', { domain: object.actor.domain }) }}
|
||||
</PopoverItem>
|
||||
<hr>
|
||||
<PopoverItem
|
||||
v-for="obj in getReportableObjects({account: object.attributed_to, channel: object})"
|
||||
v-for="obj in getReportableObjects({ account: object.attributed_to, channel: object })"
|
||||
:key="obj.target.type + obj.target.id"
|
||||
icon="bi-share"
|
||||
@click.stop.prevent="report(obj)"
|
||||
|
@ -412,10 +406,9 @@ const tabs = ref([
|
|||
<Modal
|
||||
v-if="isOwner"
|
||||
v-model="showEditModal"
|
||||
:title="
|
||||
object.artist?.content_category === 'podcast'
|
||||
? t('views.channels.DetailBase.header.podcastChannel')
|
||||
: t('views.channels.DetailBase.header.artistChannel')
|
||||
:title="object.artist?.content_category === 'podcast'
|
||||
? t('views.channels.DetailBase.header.podcastChannel')
|
||||
: t('views.channels.DetailBase.header.artistChannel')
|
||||
"
|
||||
>
|
||||
<div class="scrolling content">
|
||||
|
|
|
@ -53,12 +53,12 @@ const fullPlaylistTracks = ref<FullPlaylistTrack[]>([])
|
|||
const tracks = computed(() => fullPlaylistTracks.value.map(({ track }, index) => ({ ...track as Track, position: index + 1 })))
|
||||
|
||||
const updateTrack = (updatedTrack: Track) => {
|
||||
fullPlaylistTracks.value = fullPlaylistTracks.value.map((item) =>
|
||||
item.track.id === updatedTrack.id ? { ...item, track: updatedTrack } : item
|
||||
);
|
||||
fullPlaylistTracks.value = fullPlaylistTracks.value.map((item) =>
|
||||
item.track.id === updatedTrack.id ? { ...item, track: updatedTrack } : item
|
||||
);
|
||||
};
|
||||
useWebSocketHandler('playlist.track_updated', async (event) => {
|
||||
updateTrack(event.track);
|
||||
updateTrack(event.track);
|
||||
});
|
||||
|
||||
const { t } = useI18n()
|
||||
|
@ -136,7 +136,7 @@ const bgcolors = ref([
|
|||
'#322f2f'
|
||||
])
|
||||
|
||||
function shuffleArray (array: string[]): string[] {
|
||||
function shuffleArray(array: string[]): string[] {
|
||||
return [...array].sort(() => Math.random() - 0.5)
|
||||
}
|
||||
|
||||
|
@ -149,7 +149,7 @@ const randomizedColors = computed(() => shuffleArray(bgcolors.value))
|
|||
// })
|
||||
|
||||
// TODO: Implement shuffle
|
||||
const shuffle = () => {}
|
||||
const shuffle = () => { }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -194,9 +194,7 @@ const shuffle = () => {}
|
|||
{{ playlist.actor.full_username }}
|
||||
<i class="bi bi-dot" />
|
||||
{{ t('views.playlists.Detail.meta.updated') }}
|
||||
<HumanDate
|
||||
:date="playlist.modification_date"
|
||||
/>
|
||||
<HumanDate :date="playlist.modification_date" />
|
||||
</Layout>
|
||||
</Layout>
|
||||
<RenderedDescription
|
||||
|
@ -337,9 +335,11 @@ const shuffle = () => {}
|
|||
|
||||
.meta {
|
||||
font-size: 15px;
|
||||
|
||||
@include light-theme {
|
||||
color: var(--fw-gray-700);
|
||||
}
|
||||
|
||||
@include dark-theme {
|
||||
color: var(--fw-gray-500);
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue