Skip to content
Snippets Groups Projects
Commit f9762d85 authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt :koala:
Browse files

refactor: rework show selector

We’ve decided to move the show selector into the navbar because the
selected show is the primary state of the dashboard from which almost
all other state is derived.

Apart from that we now have support for filtering the list based on
active and inactive shows (and possible other criteria in the future) as
it was suggested in #122.
parent 76c19adf
No related branches found
No related tags found
No related merge requests found
Pipeline #3039 passed
......@@ -9,7 +9,6 @@
{{ $t('calendar.view.week') }}
</b-button>
</b-button-group>
<ShowSelector />
</PageHeader>
<template v-if="!loaded.shows">
......@@ -218,7 +217,6 @@ import FullCalendar from '@fullcalendar/vue3'
import fullCalendarTimeGridPlugin from '@fullcalendar/timegrid'
import fullCalendarInteractionPlugin from '@fullcalendar/interaction'
import ShowSelector from '@/components/ShowSelector.vue'
import modalEmissionManagerCreate from '@/components/emissions/ModalCreate.vue'
import modalEmissionManagerResolve from '@/components/emissions/ModalResolve.vue'
import modalEmissionManagerEdit from '@/components/emissions/ModalEdit.vue'
......@@ -236,7 +234,6 @@ export default {
ServerErrors,
FullCalendar,
AuthWall,
ShowSelector,
'app-modalEmissionManagerCreate': modalEmissionManagerCreate,
'app-modalEmissionManagerResolve': modalEmissionManagerResolve,
'app-modalEmissionManagerEdit': modalEmissionManagerEdit,
......
<template>
<b-container>
<PageHeader :title="$t('filePlaylistManager.title')">
<ShowSelector />
</PageHeader>
<PageHeader :title="$t('filePlaylistManager.title')" />
<template v-if="!loaded.shows">
<div class="tw-text-center">
......@@ -30,7 +28,6 @@
<script>
import { mapGetters } from 'vuex'
import ShowSelector from '../components/ShowSelector.vue'
import jumbotron from '../components/filemanager/Jumbotron.vue'
import files from '../components/filemanager/Files.vue'
import playlists from '../components/filemanager/Playlists.vue'
......@@ -39,7 +36,6 @@ import PageHeader from '@/components/PageHeader.vue'
export default {
components: {
PageHeader,
ShowSelector,
jumbotron: jumbotron,
files: files,
playlists: playlists,
......
<template>
<b-container>
<PageHeader :title="t('showManager.title')">
<ShowSelector />
</PageHeader>
<PageHeader :title="t('showManager.title')" />
<template v-if="!hasLoadedShows">
<div class="tw-text-center">
......@@ -40,7 +38,6 @@ import ShowMetaSimpleTypes from '../components/shows/MetaSimpleTypes.vue'
import ShowMetaArrays from '../components/shows/MetaArrays.vue'
import ShowMetaOwners from '../components/shows/MetaOwners.vue'
import ShowMetaImages from '../components/shows/MetaImages.vue'
import ShowSelector from '../components/ShowSelector.vue'
import { useStore } from 'vuex'
import PageHeader from '@/components/PageHeader.vue'
import { useAuthStore, useUserStore } from '@/stores/auth'
......
......@@ -25,6 +25,11 @@
</b-navbar-nav>
<b-navbar-nav class="tw-flex tw-items-center">
<ShowSelector
input-class="tw-bg-white/10 tw-rounded-full tw-border-gray-900 tw-border tw-border-solid tw-shadow-inner"
input-container-class="tw-text-gray-200"
drawer-class="tw-text-gray-900"
/>
<b-nav-item-dropdown :text="locale.toUpperCase()" right>
<b-dropdown-item
v-for="availableLocale in availableLocales"
......@@ -77,6 +82,7 @@ import { useI18n } from '@/i18n'
import { logoutRedirect } from '@/oidc'
import { Module } from '@/types'
import { useAuthStore } from '@/stores/auth'
import ShowSelector from '@/components/shows/ShowSelector.vue'
defineProps({
modules: { type: Array as PropType<Module[]>, required: true },
......
<template>
<div v-if="loaded.shows">
<AddShowModal v-if="!modal" ref="addShowModal" />
<b-button
v-if="authStore.isSuperuser"
variant="primary"
data-testid="show-selector:add-show"
class="md:tw-whitespace-nowrap"
@click="resolvedModal.openModal()"
>
{{ t('showCreator.title') }}
</b-button>
</div>
</template>
<script lang="ts" setup>
import { useStore } from 'vuex'
import { computed, ref } from 'vue'
import AddShowModal from './AddShowModal.vue'
import { useI18n } from '@/i18n'
import { useAuthStore } from '@/stores/auth'
type Modal = {
openModal: () => void
}
const props = defineProps<{
modal?: Modal
}>()
const store = useStore()
const authStore = useAuthStore()
const { t } = useI18n()
const addShowModal = ref<Modal>()
const resolvedModal = computed(() => (props.modal ?? addShowModal) as unknown as Modal)
const loaded = computed(() => ({
shows: store.state.shows.loaded.shows,
types: store.state.shows.loaded.types,
fundingCategories: store.state.shows.loaded.fundingcategories,
}))
// TODO: not sure these belong here.
if (!loaded.value.types) {
store.dispatch('shows/fetchMetaArray', { property: 'types', onlyActive: true })
}
if (!loaded.value.fundingCategories) {
store.dispatch('shows/fetchMetaArray', { property: 'fundingcategories', onlyActive: true })
}
</script>
<template>
<ComboBox
v-if="!areShowsLoading"
v-model="selectedShow"
:label="t('showSelector.selectShow')"
:no-data-label="hasShows ? t('showSelector.noDataMatch') : t('showSelector.noData')"
:keyboard-shortcut="keys.ctrl_b"
:keyboard-shortcut-label="t('showSelector.keyboardShortcut')"
:choices="filteredShows"
data-testid="show-selector"
@search="showSearchQuery = $event"
@close="filterActive = null"
>
<template #default="{ choice: show, index, activeIndex, ...attrs }">
<li v-bind="attrs">
<p class="tw-m-0 tw-font-bold tw-flex tw-items-baseline tw-justify-between">
<span>{{ show.name }}</span>
<span class="tw-text-xs tw-opacity-75 tw-flex-none tw-font-normal">
ID: {{ show.id }}
</span>
</p>
<span
v-if="!show.is_active"
class="tw-text-sm tw-rounded-full tw-bg-black/10 tw-text-black/75 tw-px-2 tw-inline-block tw-float-right tw-m-0 tw-ml-1 tw-mb-1"
>
{{ t('showSelector.showState.inactive') }}
</span>
<p v-show="activeIndex === index" class="tw-my-1 tw-text-sm tw-opacity-90">
{{ show.short_description }}
</p>
<span class="tw-clear-both" />
</li>
</template>
<template #filter>
<div v-if="hasShows">
<p class="mb-2 tw-text-sm tw-font-bold">{{ t('showSelector.showState.label') }}</p>
<div class="tw-flex tw-text-sm">
<SwitchButton
:model-value="filterActive === true"
class="tw-whitespace-nowrap"
:label="t('showSelector.showState.active')"
@update:model-value="toggleFilterActive(true)"
/>
<SwitchButton
:model-value="filterActive === false"
class="tw-whitespace-nowrap"
:label="t('showSelector.showState.inactive')"
@update:model-value="toggleFilterActive(false)"
/>
</div>
</div>
<AddShowButton class="tw-block" :class="{ 'tw-mt-auto': hasShows }" :modal="addShowModal" />
</template>
<template #pre>
<AddShowModal ref="addShowModal" />
</template>
</ComboBox>
</template>
<script lang="ts" setup>
import { sort } from 'fast-sort'
import { computed, ref } from 'vue'
import { useStore } from 'vuex'
import { useMagicKeys } from '@vueuse/core'
import { useI18n } from '@/i18n'
import { useSelectedShow } from '@/utilities'
import SwitchButton from '@/components/SwitchButton.vue'
import ComboBox from '@/components/ComboBox.vue'
import AddShowButton from '@/components/shows/AddShowButton.vue'
import AddShowModal from '@/components/shows/AddShowModal.vue'
type Show = {
id: number
is_active: boolean
name: string
short_description: string
}
const keys = useMagicKeys()
const store = useStore()
const { t } = useI18n()
const selectedShow = useSelectedShow()
const showSearchQuery = ref('')
const filterActive = ref<null | boolean>(null)
const addShowModal = ref()
const shows = computed<Show[]>(() => store.state.shows.shows)
const areShowsLoading = computed(() => !store.state.shows.loaded.shows)
const hasShows = computed(() => shows.value.length > 0)
const sortedShows = computed(() => {
return sort(shows.value).by([
{ desc: (show) => show.is_active },
{ asc: (show) => show.name.toLowerCase() },
])
})
const filteredShows = computed(() => {
return sortedShows.value
.filter((show) => (filterActive.value !== null ? show.is_active === filterActive.value : true))
.filter(
(show) =>
show.name.includes(showSearchQuery.value) || show.id.toString() === showSearchQuery.value,
)
})
function toggleFilterActive(toggleState: boolean) {
if (filterActive.value === toggleState) {
filterActive.value = null
return
}
filterActive.value = toggleState
}
</script>
<script lang="ts">
export default {
compatConfig: {
MODE: 3,
},
}
</script>
......@@ -187,9 +187,16 @@ export default {
},
showSelector: {
keyboardShortcut: 'Strg + B',
selectShowMany: 'Sendereihe wählen',
selectShow: 'Sendereihe auswählen',
inactiveShow: 'inaktiv',
noData: 'Es wurden bisher keine Sendereihen angelegt.',
noDataMatch: 'Es gibt keine Sendereihen, die deinen Suchkriterien entsprechen.',
showState: {
label: 'Status der Sendereihe',
active: 'Aktiv',
inactive: 'Inaktiv',
},
},
showJumbotron: {
......
......@@ -187,9 +187,16 @@ export default {
},
showSelector: {
keyboardShortcut: 'Ctrl + B',
selectShowMany: 'Select show:',
selectShow: 'Select a radio show',
inactiveShow: 'inactive',
noData: 'No shows have been created yet.',
noDataMatch: 'There are no shows that match your search criteria.',
showState: {
label: 'Show status',
active: 'Active',
inactive: 'Inactive',
},
},
showJumbotron: {
......
......@@ -35,8 +35,13 @@ export function getTimeString(date, withSeconds = false) {
export function useSelectedShow() {
const store = useStore()
return computed(() => {
return store.state.shows.shows[store.state.shows.selected.index]
return computed({
get() {
return store.state.shows.shows[store.state.shows.selected.index]
},
set(show) {
store.commit('shows/switchShowById', show.id)
},
})
}
export function shouldLog(message, instance, trace) {
......
......@@ -3,6 +3,7 @@ import { expect, test } from '@playwright/test'
test('Can create new show.', async ({ page }) => {
await page.goto('/')
await page.getByTestId('navbar:shows').click()
await page.getByTestId('show-selector').locator('input[id^="combobox-input-"]').focus()
await page.getByTestId('show-selector:add-show').click()
await page.getByTestId('add-show-modal:show-name').fill('my series')
await page.getByTestId('add-show-modal:show-description').fill('my series description')
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment