diff --git a/src/App.vue b/src/App.vue index 638314cd..6bd794d1 100644 --- a/src/App.vue +++ b/src/App.vue @@ -23,8 +23,10 @@
+ + { + searchResultStore.TVFacets = response.data.facet_counts.facet_fields.creator_affiliation_facet.filter( + (entry) => typeof entry === 'string', + ); + }); + APIService.getRadioFacets().then((response) => { + searchResultStore.RadioFacets = response.data.facet_counts.facet_fields.creator_affiliation_facet.filter( + (entry) => typeof entry === 'string', + ); + }); let facetAPiCallFurfilled = false; // timeout promise for long responsetimes. (7 seconds pt.) const maximumWaitTime = new Promise((resolve) => { @@ -148,9 +165,28 @@ export default defineComponent({ Promise.race([facetAPICall, maximumWaitTime]) .then((response) => { if (response) { + const isTimeFiltersSet = + startDate.value.getTime() !== startYear.value.getTime() || + endDate.value.getTime() !== endYear.value.getTime(); + //if api comes back first, we get the results and set our boolean to true. const typedResponse = response as APISearchResponseType; searchResultStore.initFacets = typedResponse.data.facet_counts; + const years = searchResultStore.initFacets.facet_fields.temporal_start_year; + + startYear.value = new Date(startYear.value); + startYear.value.setFullYear(Number(years[0])); + + endYear.value = new Date(endYear.value); + endYear.value.setFullYear(Number(years[years.length - 2])); + + if (!isTimeFiltersSet) { + const startHolder = new Date(startYear.value.getTime()); + const endHolder = new Date(endYear.value.getTime()); + startDate.value = startHolder; + endDate.value = endHolder; + } + searchResultStore.firstBackendFetchExecuted = true; facetAPiCallFurfilled = true; } @@ -161,8 +197,26 @@ export default defineComponent({ // if the timeout was first, we still want the api promise returned here // unless it the results were already back - then we ignore this. if (!facetAPiCallFurfilled) { + const isTimeFiltersSet = + startDate.value.getTime() !== startYear.value.getTime() || + endDate.value.getTime() !== endYear.value.getTime(); const typedResponse = response as APISearchResponseType; searchResultStore.initFacets = typedResponse.data.facet_counts; + const years = searchResultStore.initFacets.facet_fields.temporal_start_year; + + startYear.value = new Date(startYear.value); + startYear.value.setFullYear(Number(years[0])); + + endYear.value = new Date(endYear.value); + endYear.value.setFullYear(Number(years[years.length - 2])); + + if (!isTimeFiltersSet) { + const startHolder = new Date(startYear.value.getTime()); + const endHolder = new Date(endYear.value.getTime()); + startDate.value = startHolder; + endDate.value = endHolder; + } + searchResultStore.firstBackendFetchExecuted = true; facetAPiCallFurfilled = true; } @@ -318,6 +372,9 @@ export default defineComponent({ DKB_Negativ_RGB + + diff --git a/src/components/common/CustomRadioButton.vue b/src/components/common/CustomRadioButton.vue new file mode 100644 index 00000000..2e54a591 --- /dev/null +++ b/src/components/common/CustomRadioButton.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/src/components/common/CustomRadioGroup.vue b/src/components/common/CustomRadioGroup.vue new file mode 100644 index 00000000..b3799272 --- /dev/null +++ b/src/components/common/CustomRadioGroup.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/components/common/CustomTimelineSelect.vue b/src/components/common/CustomTimelineSelect.vue index f4190015..24983482 100644 --- a/src/components/common/CustomTimelineSelect.vue +++ b/src/components/common/CustomTimelineSelect.vue @@ -40,7 +40,7 @@ export default defineComponent({ name: 'CustomTimelineSelect', props: { listItems: { - type: Array as PropType, + type: Array as PropType, default() { return []; }, @@ -99,7 +99,7 @@ export default defineComponent({ border: 1px solid #dadada; border-radius: 4px; padding: 4px 50px 5px 8px; - color: #002e70; + color: var(--color-default); -webkit-appearance: none; -moz-appearance: none; background-color: white; @@ -108,6 +108,8 @@ export default defineComponent({ background-position-x: 85px; background-position-y: 13px; width: 118px; + box-sizing: border-box; + height: 48px; } .select-container:hover select { @@ -117,7 +119,7 @@ export default defineComponent({ label { padding-right: 10px; - color: black; + color: var(--color-main); display: flex; gap: 5px; align-items: center; diff --git a/src/components/common/FilterExpander.vue b/src/components/common/FilterExpander.vue new file mode 100644 index 00000000..60dbe501 --- /dev/null +++ b/src/components/common/FilterExpander.vue @@ -0,0 +1,409 @@ + + + + diff --git a/src/components/common/GridDisplay.vue b/src/components/common/GridDisplay.vue index b24f3157..930bb26f 100644 --- a/src/components/common/GridDisplay.vue +++ b/src/components/common/GridDisplay.vue @@ -1,32 +1,42 @@ @@ -153,127 +62,23 @@ temporary styling until patterns from design system are implemented .grid-display { position: relative; margin-bottom: 80px; -} - -.record-grid { + margin-top: 34px; + width: 100%; display: flex; - flex-wrap: wrap; - padding-left: 0; - max-width: 100%; - overflow: hidden; - padding-top: 5px; -} - -.draggable { - flex-wrap: wrap; -} - -.current-record { - background-color: #c4f1ed !important; - border: 1px solid #0a2e70; - box-shadow: 0 0 15 black; -} - -@media (min-width: 321px) { - .draggable { - flex-wrap: nowrap; - } -} - -.draggable-item { - flex: 0 0 100%; - margin: 0px; + justify-content: center; + height: 100%; } .related-record.draggable-item { - margin-left: 20px; - flex: 0 0 90%; + margin-left: 15px; + flex: 0 0 30%; box-sizing: border-box; -} -.related-record.draggable-item:first-of-type { - margin-left: 0px; -} - -@media (min-width: 640px) { - .record-grid { - flex: 0 0 100%; - max-width: 100%; - } - .row-4 .related-record { - flex: 0 0 100%; - } - - .row-4 .related-record.draggable-item { - flex: 0 0 66.6666%; - } - .row-3 .related-record.draggable-item { - flex: 0 0 66.6666%; - } -} - -/* Second break for small screen */ -@media (min-width: 800px) { - .record-grid { - flex: 0 0 100%; - max-width: 100%; - flex-wrap: wrap; - } - .row-3 .related-record, - .row-3 .related-record.draggable-item { - margin: 0px; - flex: 0 0 33.3333%; - box-sizing: border-box; - } - .row-4 .related-record, - .row-4 .related-record.draggable-item { - margin: 0px; - flex: 0 0 25%; - box-sizing: border-box; - } - .row-3 .related-record:nth-of-type(3n + 1) { - padding-left: 0px; - padding-right: 10px; - } - .row-3 .related-record:nth-of-type(3n + 2) { - padding-left: 5px; - padding-right: 5px; - } - .row-3 .related-record:nth-of-type(3n) { - padding-right: 0px; - padding-left: 10px; - } - .row-4 .related-record:nth-of-type(4n + 1) { - padding-left: 0px; - padding-right: 10px; - } - .row-4 .related-record:nth-of-type(4n + 2) { - padding-left: 5px; - padding-right: 7.5px; - } - .row-4 .related-record:nth-of-type(4n + 3) { - padding-left: 7.5px; - padding-right: 5px; - } - .row-4 .related-record:nth-of-type(4n) { - padding-right: 0px; - padding-left: 10px; - } -} - -/* third break for large screen */ -@media (min-width: 990px) { - .row-3 div { - flex: 0 0 33.3333%; - box-sizing: border-box; - } - - .row-4 div { - flex: 0 0 25%; - box-sizing: border-box; - } - .related-records { - flex: 0 0 100%; - max-width: 100%; - } + background-color: var(--bg-secondary-light-20); + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + user-drag: none; } diff --git a/src/components/common/ImageComponent.vue b/src/components/common/ImageComponent.vue index 8ced51fb..abc5e5ee 100644 --- a/src/components/common/ImageComponent.vue +++ b/src/components/common/ImageComponent.vue @@ -146,8 +146,8 @@ export default defineComponent({ } .image-item { - width: calc(100% + 1px); - height: calc(100% + 1px); + width: calc(100% + 2px); + height: calc(100% + 2px); object-fit: cover; transition: opacity 0.3s ease-in-out 0s, diff --git a/src/components/common/KBButton.vue b/src/components/common/KBButton.vue index 387d26f8..1b5e2a73 100644 --- a/src/components/common/KBButton.vue +++ b/src/components/common/KBButton.vue @@ -9,15 +9,17 @@ :style="customStyle" > {{ leftIconName }} {{ buttonText }} {{ rightIconName }} @@ -33,15 +35,17 @@ :style="customStyle" > {{ leftIconName }} {{ buttonText }} {{ rightIconName }} @@ -57,15 +61,15 @@ :style="customStyle" > {{ leftIconName }} {{ buttonText }} {{ rightIconName }} @@ -105,6 +109,12 @@ export default defineComponent({ return false; }, }, + iconFilled: { + type: Boolean, + default() { + return false; + }, + }, leftIconName: { type: String, default() { @@ -135,6 +145,18 @@ export default defineComponent({ return false; }, }, + customIconLeft: { + type: String, + default() { + return ''; + }, + }, + customIconRight: { + type: String, + default() { + return ''; + }, + }, }, setup(props) { const attrs = useAttrs(); @@ -164,6 +186,10 @@ export default defineComponent({ width: fit-content; min-height: 34px; line-height: 1; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.15); +} +.btn-text::first-letter { + text-transform: capitalize; } .btn:disabled { background-color: var(--bg-disabled); @@ -199,7 +225,7 @@ export default defineComponent({ } .cta { background-color: var(--bg-cta); - color: var(--color-main); + color: var(--color-default); border: 1px solid var(--color-border-success); } .reset { @@ -236,7 +262,6 @@ export default defineComponent({ /* Call to action buttons */ .btn-cta { border-radius: var(--rounded-medium); - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.15); } .btn-cta .btn-text { border-bottom: 1px solid transparent; @@ -244,10 +269,10 @@ export default defineComponent({ transition: 200ms; } .btn-cta:not(:disabled):hover { - transition: all 5s ease 0s; - .btn-text { - border-color: var(--color-border-active); - } + transition: all 0.3s ease 0s; + background-color: var(--bg-main-2); + border-color: var(--color-border-light); + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0); } .btn-cta.medium .material-icons { font-size: calc(var(--fs-lead) + 8px); @@ -310,4 +335,32 @@ export default defineComponent({ background: var(--bg-golden); color: var(--color-main); } +.btn-tag-reset { + padding: var(--padding-02) var(--padding-01); + gap: var(--padding-small); + color: var(--color-main); + border-radius: var(--rounded-medium); + border: 1px solid var(--color-border-golden); + background: var(--bg-golden); +} +.btn-tag-reset .material-icons { + font-size: calc(var(--fs-meta) + 2px); +} + +.btn-tag-sub { + padding: var(--padding-01) var(--padding-medium); + gap: var(--padding-small); + color: var(--color-main); + border-radius: var(--rounded-medium); + border: 1px solid var(--color-border-light); + background: var(--bg-light); +} +.btn-tag-sub .material-icons { + font-size: calc(var(--fs-meta) + 2px); +} +.btn-tag-sub:hover { + border: 1px solid var(--color-border-golden); + background: var(--bg-golden); + color: var(--color-main); +} diff --git a/src/components/common/MainCategories.vue b/src/components/common/MainCategories.vue index 053877aa..2cd501c9 100644 --- a/src/components/common/MainCategories.vue +++ b/src/components/common/MainCategories.vue @@ -31,7 +31,9 @@ > {{ t(`categories.${santizeAndSimplify(entity.name)}`) }} {{ entity.number.toLocaleString('de-DE') }} - + + {{ returnCategoryIcon(entity.name) }} + @@ -72,6 +74,7 @@ import { useSearchResultStore } from '@/store/searchResultStore'; import NoFacetContent from '@/components/global/content-elements/NoFacetContent.vue'; import { Priority, Severity } from '@/types/NotificationType'; import { ErrorManagerType } from '@/types/ErrorManagerType'; +import { returnCategoryIcon, returnFilledIconStatus } from '@/utils/icon-utils'; export default defineComponent({ name: 'MainCategories', @@ -79,7 +82,7 @@ export default defineComponent({ props: { title: { type: String, default: '' }, subtitle: { type: String, default: '' }, - text: { type: String, default: 'black' }, + text: { type: String, default: 'var(--bg-default)' }, showHeader: { type: Boolean, default: false }, }, setup() { @@ -140,7 +143,17 @@ export default defineComponent({ }); }; - return { t, categories, addTestDataEnrichment, categoriesLoaded, quotation, santizeAndSimplify, scrollToTop }; + return { + t, + categories, + addTestDataEnrichment, + categoriesLoaded, + quotation, + santizeAndSimplify, + scrollToTop, + returnCategoryIcon, + returnFilledIconStatus, + }; }, }); @@ -239,7 +252,6 @@ export default defineComponent({ z-index: 20; top: -25px; left: -1px; - background-image: url('@/assets/icons/blue/diverse-blue.svg'); filter: hue-rotate(5deg); } @@ -261,40 +273,6 @@ export default defineComponent({ border-radius: 4px; } -.diverse { - background-image: url('@/assets/icons/blue/diverse-blue.svg'); -} -.dokumentar { - background-image: url('@/assets/icons/blue/dokumentar-blue.svg'); -} -.film-og-serier { - background-image: url('@/assets/icons/blue/fiktion-blue.svg'); -} -.kultur-og-oplysning { - background-image: url('@/assets/icons/blue/kultur-blue.svg'); -} -.livsstil { - background-image: url('@/assets/icons/blue/livsstil-blue.svg'); -} -.musik { - background-image: url('@/assets/icons/blue/musik-blue.svg'); -} -.nyheder-politik-og-samfund { - background-image: url('@/assets/icons/blue/nyheder-blue.svg'); -} -.sport { - background-image: url('@/assets/icons/blue/sport-blue.svg'); -} -.humor-quiz-og-underholdning { - background-image: url('@/assets/icons/blue/underholdning-blue.svg'); -} -.natur-og-videnskab { - background-image: url('@/assets/icons/blue/videnskab-blue.svg'); -} -.brn-og-unge { - background-image: url('@/assets/icons/blue/born-blue.svg'); -} - .container { width: 100%; } diff --git a/src/components/common/PortalContent.vue b/src/components/common/PortalContent.vue index 624f3b92..4f5f99b6 100644 --- a/src/components/common/PortalContent.vue +++ b/src/components/common/PortalContent.vue @@ -1,72 +1,37 @@ @@ -45,7 +45,7 @@ export default defineComponent({ backgroundColor: { type: String as PropType, default() { - return '#002e70'; + return 'var(--bg-default)'; }, }, dottedEdges: { @@ -81,10 +81,7 @@ export default defineComponent({ }, setup(props) { - const top = ref(); - const content = ref(); const container = ref(); - const bottom = ref(); const getClasses = (prefix: string) => { let classes = `${prefix}-edge`; @@ -95,13 +92,10 @@ export default defineComponent({ }; onMounted(() => { - if (top.value) top.value.style.backgroundColor = props.backgroundColor; - if (content.value) content.value.style.backgroundColor = props.backgroundColor; if (container.value) container.value.style.alignItems = props.alignItems; - if (bottom.value) bottom.value.style.backgroundColor = props.backgroundColor; }); - return { top, content, container, bottom, getClasses }; + return { container, getClasses }; }, }); @@ -133,6 +127,7 @@ temporary styling until patterns from design system are implemented align-items: center; padding: 0px 0px; flex-direction: column; + background-color: var(--bg-default); } .edge-content.lines { @@ -183,7 +178,7 @@ h3 { padding: 0; text-align: center; letter-spacing: 1.15px; - color: #002e70; + color: var(--color-main); top: calc(2.5vw + 2px); position: relative; transform: matrix(1, 0.03, -0.03, 1, 0, 0); @@ -196,6 +191,7 @@ h3:first-letter { text-transform: uppercase; } .container { + background-color: var(--bg-default); text-align: left; padding-right: 12px; padding-left: 12px; diff --git a/src/components/global/content-elements/NoFacetContent.vue b/src/components/global/content-elements/NoFacetContent.vue index 4fb1880d..317a8b09 100644 --- a/src/components/global/content-elements/NoFacetContent.vue +++ b/src/components/global/content-elements/NoFacetContent.vue @@ -1,24 +1,16 @@ diff --git a/src/components/search/ContactUs.vue b/src/components/search/ContactUs.vue index d372027d..37d06b7c 100644 --- a/src/components/search/ContactUs.vue +++ b/src/components/search/ContactUs.vue @@ -40,42 +40,13 @@ export default defineComponent({ diff --git a/src/components/search/Facets.vue b/src/components/search/Facets.vue index 54aecab4..eca4389f 100644 --- a/src/components/search/Facets.vue +++ b/src/components/search/Facets.vue @@ -1,169 +1,256 @@ diff --git a/src/components/search/Hero.vue b/src/components/search/Hero.vue index b80fce9a..aefc9eac 100644 --- a/src/components/search/Hero.vue +++ b/src/components/search/Hero.vue @@ -8,113 +8,43 @@ fetchpriority="high" />
-
-
-
-

- - {{ t('hero.subtitle') }} - - - {{ t('hero.title') }} - -

- -
-
-
-

{{ t('hero.progress', { index: Math.round(currentProgress) }) }}

-

- {{ t('hero.explanation') }} -

-

- - link - {{ t('hero.linkText') }} - -

-
-
-
- {{ Math.round(currentProgress) }}% -
-
-
-
-
-
-
+ + diff --git a/src/components/viewers/AudioVideo/AudioPlayer.vue b/src/components/viewers/AudioVideo/AudioPlayer.vue index b1e02c4b..cdb1f501 100644 --- a/src/components/viewers/AudioVideo/AudioPlayer.vue +++ b/src/components/viewers/AudioVideo/AudioPlayer.vue @@ -211,7 +211,7 @@ export default defineComponent({ overflow-y: hidden; padding-top: 31px; padding-bottom: 31px; - background-color: white; + background-color: var(--bg-default); position: relative; } diff --git a/src/locales/da.json b/src/locales/da.json index e33e7247..3f4188c2 100644 --- a/src/locales/da.json +++ b/src/locales/da.json @@ -42,21 +42,6 @@ "secondary": [ { - "title": "Lån og aflevering", - "link": "https://www.kb.dk/services/laan-og-aflevering" - },{ - "title": "Bliv bruger", - "link": "https://www.kb.dk/bliv-bruger" - },{ - "title": "Om os", - "link": "https://www.kb.dk/om-os" - },{ - "title": "Webshop", - "link": "https://webshop.kb.dk/" - },{ - "title": "Åbningstider", - "link": "https://www.kb.dk/aabningstider" - },{ "title": "In english", "link": "#", "id": "localeSwitcher" @@ -65,49 +50,78 @@ "link": "https://soeg.kb.dk/discovery/account?vid=45KBDK_KGL%3AKGL§ion=overview&lang=da", "icon": "account_circle" } - ], - "primary": - [ - { - "title": "Find materiale", - "link": "https://www.kb.dk/find-materiale" - },{ - "title": "Inspiration", - "link": "https://www.kb.dk/inspiration" - },{ - "title": "Arrangementer", - "link": "https://www.kb.dk/arrangementer" - },{ - "title": "Services", - "link": "https://www.kb.dk/services" - },{ - "title": "Besøg os", - "link": "https://www.kb.dk/besoeg-os" - },{ - "title": "Luk", - "altTitle": "Søg", - "id": "searchToggle", - "icon": "search" - } ] }, "frontpage": { "fromTheArchive":"{month} måned på DR gennem tiden" }, "facets" : { + "headline":"Udvælg filtre, til at indsnævre din søgning på:", + "starHeadline": "Udvælg filtre, til at indsnævre din søgning.", "allGenres": "Alle kategorier", "genres": "kategori | kategorier", - "genresSubtitle": "Vælg, hvilken kategori du gerne vil søge i.", + "selectedGenres": "{selected} af {total} kategorier valgt", + "genresSubtitle": "Vælg, hvilken kategori du gerne vil søge i.", "allChannels":"Alle kanaler", + "selectedTVChannels":"{selected} af {total} kanaler valgt", + "selectedRadioChannels":"{selected} af {total} kanaler valgt", "channels":"kanal | kanaler", + "tvChannels":"TV kanal | TV kanaler", + "radioChannels": "RADIO kanal | RADIO kanaler", "chosen":"Valgt", + "contentFrom": "Indhold tilbage fra ", "reset":"nulstil", - "choose": "Vælg", - "slowResponse": { - "title": "Lang respons tid", - "text": "Vi beklager for lang respons tid, vi oplever meget trafik i øjeblikket" - }, - "remove":"Fjern" + "choose": "Vælg", + "seeResults": "Se dit {resultCount} resultat | Se dine {resultCount} resultater", + "to":"Til", + "from":"Fra", + "year":"år", + "slowResponse": { + "title": "Lang respons tid", + "text": "Vi beklager for lang respons tid, vi oplever meget trafik i øjeblikket" + }, + "remove":"Fjern", + "searchingIn":"Søger kun på", + "desc":"beskrivelse", + "title":"titel", + "searchMethod": { + "all": { + "title":"Søg i alt", + "desc":"Der søges i alle metadata-felter." + }, + "title": { + "title":"Søg kun på titler", + "desc":"Der søges kun i titel-feltet." + }, + "desc": { + "title":"Søg kun på beskrivelser", + "desc":"Der søges kun i beskrivelses-feltet." + } + }, + "searchMaterial": { + "both": { + "title":"Søg på både TV og RADIO", + "desc":"Der søges både i RADIO-poster og TV-poster." + }, + "tv": { + "title":"Søg kun på TV", + "desc":"Der søges kun i TV-poster." + }, + "radio": { + "title":"Søg kun på RADIO", + "desc":"Der søges kun i RADIO-poster." + } + }, + "timePeriod": { + "date": { + "title":"Dato", + "desc":"Der søges på en dag." + }, + "period": { + "title":"Periode", + "desc":"Der søges på en år-række." + } + } }, "breadcrumb": { "frontpage":"Forside", @@ -143,7 +157,7 @@ "allTimeslots":"Hele døgnet", "selection":"Et udpluk af resultaterne", "dateSubtitle": "Her kan du vælge en bestemt dato og se, hvad der blev sendt den dag.", - "timeMachineSubtitle": "Her kan du vælge årstal, måneder og ugedage, hvis du søger udsendelser sendt i en bestemt periode. Når du klikker dig videre til resultatlisten, får du flere filtreringsmuligheder.", + "timeMachineSubtitle": "Her kan du vælge årstal, hvis du søger udsendelser sendt i en bestemt periode. Når du klikker dig videre til resultatlisten, får du flere filtreringsmuligheder.", "searchCategoriesSubtitle": "Vælg en kategori og gå på opdagelse i udsendelserne. Kategorierne indeholder primært TV-udsendelser, mens du finder de fleste af radioudsendelserne i “Radio-rodekassen.”", "monthHeadline":"Vælg måneder", "dayHeadline":"Vælg ugedage", @@ -261,12 +275,7 @@ "seeAllMaterials":"Se alt materiale", "allMaterialsLink":"https://www.kb.dk/find-materiale" }, - "placeholders": { - "drArchive":"Søg for eksempel på titel eller år", - "booksAndMaterials":"Søg på titel, forfatter, tidsskrift, database etc.", - "kbdk":"Søg i hjemmesiden", - "webshop":"Søg i webshoppen" - }, + "placeholder": "Søg...", "limitedFilters":{ "title":"For mange filtre", "subtitle":"Du er nået grænsen for antal af filtre vi understøtter. For at tilføjer et andet filter, bliver du nød til at fjerne et andet." @@ -538,7 +547,8 @@ "natur-og-videnskab":"Natur og videnskab", "nyheder-politik-og-samfund":"Nyheder, politik og samfund", "radio-rodekasse":"Blandet radio", - "tv-rodekasse":"Blandet TV" + "tv-rodekasse":"Blandet TV", + "sport": "Sport" }, "find":{ "headline":"Hvad vil du søge efter?", @@ -546,5 +556,13 @@ "maybeYouWantTo":"Eller måske vil du hellere:", "restoreFilters":"Gendanne filtre fra sidste søgning", "GoToFrontpage":"Til forsiden af DR-arkivet" + }, + "calendar": { + "open":"Åben kalender", + "close": "Luk kalender", + "explanation": "Skriv en dato i formatet DD-MM-ÅÅÅÅ eller vælg fra kalenderen.", + "select": "Vælg dato", + "nextMonth": "Næste måned", + "prevMonth":"Forrige måned" } } diff --git a/src/locales/en.json b/src/locales/en.json index ee921928..90e0ae11 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -42,21 +42,6 @@ "secondary": [ { - "title": "Collect and return", - "link": "https://www.kb.dk/en/services/loans-and-returns" - },{ - "title": "New user", - "link": "https://www.kb.dk/en/become-library-user" - },{ - "title": "About us", - "link": "https://www.kb.dk/en/about-us" - },{ - "title": "Webshop", - "link": "https://webshop.kb.dk/" - },{ - "title": "Opening hours", - "link": "https://www.kb.dk/en/opening-hours" - },{ "title": "På dansk", "link": "#", "id": "localeSwitcher" @@ -65,50 +50,78 @@ "link": "https://soeg.kb.dk/discovery/account?vid=45KBDK_KGL%3AKGL§ion=overview&lang=en", "icon": "account_circle" } - ], - "primary": - [ - { - "title": "Find materials", - "link": "https://www.kb.dk/en/find-materials" - },{ - "title": "Inspiration", - "link": "https://www.kb.dk/en/inspiration" - },{ - "title": "Events", - "link": "https://www.kb.dk/en/events" - },{ - "title": "Services", - "link": "https://www.kb.dk/en/services" - },{ - "title": "Visit us", - "link": "https://www.kb.dk/en/visit-us" - },{ - "title": "Close", - "altTitle": "Search", - "id": "searchToggle", - "icon": "search" - } ] }, "frontpage": { "fromTheArchive":"The month of {month} on DR through time" }, "facets" : { + "headline":"Choose filters, to limit your search for:", + "starHeadline": "Choose filters to refine your search.", "allGenres": "all categories", "genres": "category | categories", + "selectedGenres": "{selected} of {total} categories selected", "genresSubtitle": "Select a category and explore the programmes.", "allChannels":"all channels", + "selectedTVChannels":"{selected} of {total} channels selected", + "selectedRadioChannels":"{selected} of {total} kanalchannelser selected", "channels":"channel | channels", + "tvChannels":"TV channel | TV channels", + "radioChannels": "RADIO channel | RADIO channels", "chosen":"chosen", + "contentFrom": "Content dating back to", "reset":"reset", "choose": "Select", + "seeResults": "See your {resultCount} result | See your {resultCount} results", + "to":"To", + "from":"From", + "year":"year", "slowResponse": { "title": "Slow response time", "text": "We're sorry for long response, we are experiencing a lot of traffic at the moment" }, - "remove":"Remove" - + "remove":"Remove", + "searchingIn":"Only searching with", + "desc":"description", + "title":"title", + "searchMethod": { + "all": { + "title": "Search everything", + "desc": "Searches across all metadata fields." + }, + "title": { + "title": "Search titles only", + "desc": "Searches only in the title field." + }, + "desc": { + "title": "Search descriptions only", + "desc": "Searches only in the description field." + } + }, + "searchMaterial": { + "both": { + "title": "Search both TV and RADIO", + "desc": "Searches both RADIO and TV records." + }, + "tv": { + "title": "Search TV only", + "desc": "Searches only in TV records." + }, + "radio": { + "title": "Search RADIO only", + "desc": "Searches only in RADIO records." + } + }, + "timePeriod": { + "date": { + "title":"Date", + "desc":"Searches for a single day." + }, + "period": { + "title":"Period", + "desc":"Searches for a period of years." + } + } }, "breadcrumb": { "frontpage":"Frontpage", @@ -144,7 +157,7 @@ "allTimeslots":"All hours", "selection":"A selection of the results", "dateSubtitle": "Here you can select a specific date and see what was aired that day.", - "timeMachineSubtitle": "Here you can select years, months, and days of the week, if you are looking for programmes from a specific period in time. When you click through to the list of results, you will get more filtering options.", + "timeMachineSubtitle": "Here you can select years, if you are looking for programmes from a specific period in time. When you click through to the list of results, you will get more filtering options.", "searchCategoriesSubtitle": "Select a category and explore the programmes. The categories primarily contain TV programmes, while you will find most of the radio programmes in the “Radio jumble box.”", "monthHeadline":"Select months", "dayHeadline":"Select days of the week", @@ -264,13 +277,7 @@ "allMaterialsLink":"https://www.kb.dk/en/find-materials" }, - "placeholders": { - "drArchive":"Search for example by title or year", - "booksAndMaterials":"Search title, author, journal, database etc. ", - "kbdk":"Search on website", - "webshop":"Search in our webshop", - "seeAllMaterials":"See all materials" - }, + "placeholders": "Search...", "limitedFilters":{ "title":"Too many filters", "subtitle":"You have reached the limit of filters we support. To add another filter, you have to remove another." @@ -543,7 +550,9 @@ "natur-og-videnskab":"Nature and science", "nyheder-politik-og-samfund":"News, politics, and society", "radio-rodekasse":"Mixed radio", - "tv-rodekasse":"Mixed TV" + "tv-rodekasse":"Mixed TV", + "sport": "Sport" + }, "find":{ "headline":"What do you want to search for?", @@ -551,5 +560,13 @@ "maybeYouWantTo":"Or maybe you would rather:", "restoreFilters":"Restore filters from previous search", "GoToFrontpage":"Go to the front page of DR-arkivet" + }, + "calendar": { + "open":"Open calendar", + "close": "Close calendar", + "explanation": "Type a date in format DD-MM-YYYY or pick from the calendar.", + "select": "Select date", + "nextMonth": "Next month", + "prevMonth":"Previous month" } } \ No newline at end of file diff --git a/src/store/searchResultStore.ts b/src/store/searchResultStore.ts index 74c1d478..f5d77722 100644 --- a/src/store/searchResultStore.ts +++ b/src/store/searchResultStore.ts @@ -43,7 +43,11 @@ export const useSearchResultStore = defineStore('searchResults', () => { const filters = ref([] as Array); const channelFilters = ref([] as Array); const categoryFilters = ref([] as Array); + const TVFacets = ref([] as Array); + const RadioFacets = ref([] as Array); const preliminaryFilter = ref(''); + const preliminarySearchMethod = ref(''); + const preliminaryPeriodSearch = ref('date'); const showFacets = ref(false); const blockAutocomplete = ref(false); const resultGrid = ref(false); @@ -114,7 +118,7 @@ export const useSearchResultStore = defineStore('searchResults', () => { if (URLFilters instanceof Array) { URLFilters.forEach((filter) => { if (filter?.split('%3A')[0].includes('origin')) { - preliminaryFilter.value = filter; + preliminaryFilter.value = decodeURIComponent(filter); } else if (filter?.split('%3A')[0].includes('creator_affiliation_facet')) { const cleanedString = filter.replace(/[()]/g, ''); channelFilters.value = cleanedString.split(' OR '); @@ -128,6 +132,7 @@ export const useSearchResultStore = defineStore('searchResults', () => { } else { const str = URLFilters; if (str.split('%3A')[0].includes('origin')) { + preliminaryFilter.value = decodeURIComponent(str); filters.value.push(`fq=${str}`); } else if (str.split('%3A')[0].includes('creator_affiliation_facet')) { const cleanedString = str.replace(/[()]/g, ''); @@ -142,6 +147,16 @@ export const useSearchResultStore = defineStore('searchResults', () => { } }; + const setPreliminarySearchMethodFromURL = (query: string) => { + if (query.includes('title:')) { + preliminarySearchMethod.value = 'title'; + } else if (query.includes('description:')) { + preliminarySearchMethod.value = 'desc'; + } else { + preliminarySearchMethod.value = 'all'; + } + }; + const setRowCountFromURL = (rows: string | undefined) => { if (rows) { rowCount.value = rows; @@ -213,6 +228,10 @@ export const useSearchResultStore = defineStore('searchResults', () => { channelFilters.value = []; categoryFilters.value = []; preliminaryFilter.value = ''; + preliminarySearchMethod.value = 'all'; + if (currentQuery.value.includes('title:') || currentQuery.value.includes('description:')) { + currentQuery.value = currentQuery.value.split(':')[1].replaceAll('"', ''); + } }; const resetAutocomplete = () => { @@ -286,9 +305,6 @@ export const useSearchResultStore = defineStore('searchResults', () => { }; const getSearchResults = async (query: string) => { - if (currentQuery.value === '*:*') { - currentQuery.value = ''; - } setBlockAutocomplete(true); lastSearchQuery.value = query; @@ -316,11 +332,6 @@ export const useSearchResultStore = defineStore('searchResults', () => { presetChannelFilters += `&fq=(${categoryFilters.value.join(' OR ')})`; } searchFilters += categoryFilterString; - if (preliminaryFilter.value) { - searchFilters += `&fq=${preliminaryFilter.value}`; - presetGenreFilters += `&fq=${preliminaryFilter.value}`; - presetChannelFilters += `&fq=${preliminaryFilter.value}`; - } currentChannelFacetString.value = presetChannelFilters; currentGenreFacetString.value = presetGenreFilters; @@ -378,6 +389,9 @@ export const useSearchResultStore = defineStore('searchResults', () => { loading.value = false; } } + if (currentQuery.value === '*:*') { + currentQuery.value = ''; + } }; const filterQueryLength = computed(() => { @@ -447,6 +461,7 @@ export const useSearchResultStore = defineStore('searchResults', () => { setFiltersFromURL, setStartFromURL, setCurrentQueryFromURL, + setPreliminarySearchMethodFromURL, resetStart, setSortFromURL, resetSort, @@ -467,5 +482,9 @@ export const useSearchResultStore = defineStore('searchResults', () => { maxPages, queryLimitReached, filterQueryLength, + TVFacets, + RadioFacets, + preliminarySearchMethod, + preliminaryPeriodSearch, }; }); diff --git a/src/types/CalendarTypes.ts b/src/types/CalendarTypes.ts new file mode 100644 index 00000000..b4a18d6a --- /dev/null +++ b/src/types/CalendarTypes.ts @@ -0,0 +1,8 @@ +export interface CalendarDay { + date: Date; + label: number; + inMonth: boolean; + key: string; + disabled: boolean; + ariaLabel: string; +} diff --git a/src/utils/datepicker-utils.ts b/src/utils/datepicker-utils.ts index aadc4b46..4fd117f5 100644 --- a/src/utils/datepicker-utils.ts +++ b/src/utils/datepicker-utils.ts @@ -1,11 +1,58 @@ import { endYear, startYear } from '@/components/common/timeSearch/TimeSearchInitValues'; import { Ref } from 'vue'; +const updateDate = ( + timer: ReturnType | null, + isDateValid: Ref, + inputVar: Ref, + updateLink: () => void, +) => { + if (timer) { + clearTimeout(timer); + } + if (inputVar.value === undefined || inputVar.value === null) { + return; + } + isDateValid.value = false; + + const constructedDate = + inputVar.value.getDate() + '/' + Number(inputVar.value.getMonth() + 1) + '/' + inputVar.value.getFullYear(); + const dateInput = constructedDate.replace(/[.-]/g, '/'); + const splitDateInput = dateInput.split('/'); + if (splitDateInput.length === 3) { + const [day, month, yearInitial] = splitDateInput.map(Number); + let year = yearInitial; + // Validate if day, month, and year are numbers + if (!isNaN(day) && !isNaN(month) && !isNaN(year)) { + // Months in JavaScript Date are 0-indexed, so subtract 1 from the month + if (year.toString().length === 2) { + if (year < Number(new Date().getFullYear().toString().slice(-2))) { + year = Number(`20${year}`); + } else { + year = Number(`19${year}`); + } + } + const setDate = new Date(year, month - 1, day); + // Ensure the date is valid + if (setDate.getDate() === day && setDate.getMonth() === month - 1 && setDate.getFullYear() === year) { + // Validate if the date is within the range + const minDate = new Date(startYear.value); + const maxDate = new Date(endYear.value); + + if (setDate >= minDate && setDate <= maxDate) { + updateLink(); + isDateValid.value = true; + } + } + } + } +}; + const updateSelectedDate = ( e: Event, timer: ReturnType | null, isDateValid: Ref, - inputVar: Ref, + inputVar: Ref, updateLink: () => void, ) => { if (timer) { @@ -54,4 +101,4 @@ const updateSelectedDate = ( } }; -export { updateSelectedDate }; +export { updateSelectedDate, updateDate }; diff --git a/src/utils/filter-utils.ts b/src/utils/filter-utils.ts index ccb9ef68..3d92ebdf 100644 --- a/src/utils/filter-utils.ts +++ b/src/utils/filter-utils.ts @@ -1,7 +1,8 @@ -import { RouteLocationNormalizedLoaded } from 'vue-router'; +import { Router, RouteLocationNormalizedLoaded } from 'vue-router'; import { FacetPair } from '@/types/GenericRecordTypes'; import { SelectorData } from '@/types/TimeSearchTypes'; import { santizeAndSimplify } from './test-enrichments'; + const createTagFilter = (key: string) => { return `${'categories:"' + key + '"'}`; }; @@ -179,6 +180,70 @@ const normalizeFq = (fq: string | string[] | undefined): string[] => { return Array.isArray(fq) ? fq : [fq]; }; +export function removeSingleFacetEntry(fqString: string, key: string, valueToRemove: string): string | null { + // Match key:"value" (value may be URL encoded in your current strings) + const regex = new RegExp(`${key}%3A%22(.*?)%22`, 'g'); + + const matches = [...fqString.matchAll(regex)]; + + if (matches.length === 0) { + return fqString; + } + + const remainingValues = matches.map((m) => decodeURIComponent(m[1])).filter((v) => v !== valueToRemove); + + if (remainingValues.length === 0) { + return null; // entire fq clause should be removed + } + + if (remainingValues.length === 1) { + return `${key}%3A%22${encodeURIComponent(remainingValues[0])}%22`; + } + + const rebuilt = remainingValues.map((v) => `${key}%3A%22${encodeURIComponent(v)}%22`).join(' OR '); + + return `(${rebuilt})`; +} + +const removeFilterAndSearch = ( + facet: string, + router: Router, + route: RouteLocationNormalizedLoaded, + selected?: Array, +) => { + const routeQueries = cloneRouteQuery(route); + let fq = normalizeFq(routeQueries.fq); + if (facet !== 'genre_facet') { + if (!selected) { + fq = fq.filter((query) => !query.includes(facet)); + } else { + const removable = selected?.filter((entity: SelectorData) => entity.selected) ?? []; + fq = fq + .map((query) => { + if (!query.includes(facet)) { + return query; + } + let updated: string | null = query; + removable.forEach((item) => { + if (updated) { + updated = removeSingleFacetEntry(updated, facet, item.name); + } + }); + return updated; + }) + .filter((q): q is string => Boolean(q)); + } + } else { + fq = fq.filter((query) => !query.includes(facet)); + } + + routeQueries.fq = fq; + router.push({ + name: 'Search', + query: routeQueries, + }); +}; + // A simple method to arrange the facets in an orderly fasion, so they're easier to loop through. // Might not be relevant when we know more about the backend structure. const simplifyFacets = (facet: Array): FacetPair[] => { @@ -234,4 +299,5 @@ export { normalizeFq, removeTimeFacetsFromRoute, extendFacetPairToSelectorData, + removeFilterAndSearch, }; diff --git a/src/utils/focus-trap.ts b/src/utils/focus-trap.ts new file mode 100644 index 00000000..f49edb1b --- /dev/null +++ b/src/utils/focus-trap.ts @@ -0,0 +1,60 @@ +export const createFocusTrap = (container: HTMLElement, onEscape?: () => void) => { + let previousFocus: HTMLElement | null = null; + + //all our selectors for focus elements + const focusableSelector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; + + const handleKeydown = (e: KeyboardEvent) => { + if (!container) { + return; + } + + // we close the the entire trap on escape. + if (e.key === 'Escape') { + onEscape?.(); + return; + } + //otherwise, we only care about tap when we change focus. + if (e.key !== 'Tab') { + return; + } + + const focusables = [...container.querySelectorAll(focusableSelector)].filter( + (el) => el.offsetParent !== null, + ); + + if (!focusables.length) { + return; + } + + const first = focusables[0]; + const last = focusables[focusables.length - 1]; + + if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } + }; + + const activate = () => { + previousFocus = document.activeElement as HTMLElement; + container.addEventListener('keydown', handleKeydown); + container.querySelector(focusableSelector)?.focus(); + }; + + // When we deactivate, we retore focus to the elemet it was on before we opened. + const deactivate = () => { + container.removeEventListener('keydown', handleKeydown); + previousFocus?.focus(); + }; + + return { + activate, + deactivate, + }; +}; diff --git a/src/utils/icon-utils.ts b/src/utils/icon-utils.ts new file mode 100644 index 00000000..e73dec75 --- /dev/null +++ b/src/utils/icon-utils.ts @@ -0,0 +1,89 @@ +const returnCategoryIcon = (name: string): string => { + switch (name) { + case 'Børn og unge': + return 'child_care'; + + case 'Dokumentar': + return 'videocam'; + + case 'Film og serier': + return 'movie'; + + case 'Humor, quiz og underholdning': + return 'theater_comedy'; + + case 'Kultur og oplysning': + return 'account_balance'; + + case 'Livsstil': + return 'self_improvement'; + + case 'Musik': + return 'music_note'; + + case 'Natur og videnskab': + return 'science'; + + case 'Nyheder, politik og samfund': + return 'newspaper'; + + case 'Radio-rodekasse': + return 'inventory_2'; + + case 'Sport': + return 'trophy'; + + case 'TV-rodekasse': + return 'inventory_2'; + + default: { + return 'inventory_2'; + } + } +}; + +const returnFilledIconStatus = (name: string): boolean => { + switch (name) { + case 'Børn og unge': + return true; + + case 'Dokumentar': + return false; + + case 'Film og serier': + return false; + + case 'Humor, quiz og underholdning': + return false; + + case 'Kultur og oplysning': + return true; + + case 'Livsstil': + return false; + + case 'Musik': + return false; + + case 'Natur og videnskab': + return true; + + case 'Nyheder, politik og samfund': + return false; + + case 'Radio-rodekasse': + return false; + + case 'Sport': + return false; + + case 'TV-rodekasse': + return false; + + default: { + return false; + } + } +}; + +export { returnCategoryIcon, returnFilledIconStatus }; diff --git a/src/utils/record-utils.ts b/src/utils/record-utils.ts index 962f7389..e96b8374 100644 --- a/src/utils/record-utils.ts +++ b/src/utils/record-utils.ts @@ -8,11 +8,73 @@ import p5 from '@/assets/images/audio-thumbnails/p5.jpg'; import p6 from '@/assets/images/audio-thumbnails/p6.jpg'; import p8 from '@/assets/images/audio-thumbnails/p8.jpg'; +import fdr from '@/assets/images/filter-thumbnails/dr.jpg'; +import fdr1 from '@/assets/images/filter-thumbnails/dr1.jpg'; +import fdr2 from '@/assets/images/filter-thumbnails/dr2.jpg'; +import fdrk from '@/assets/images/filter-thumbnails/drk.jpg'; +import fdrultra from '@/assets/images/filter-thumbnails/drultra.jpg'; +import fdruppdate from '@/assets/images/filter-thumbnails/drupdate.jpg'; +import fp1 from '@/assets/images/filter-thumbnails/p1.jpg'; +import fp2 from '@/assets/images/filter-thumbnails/p2.jpg'; +import fp3 from '@/assets/images/filter-thumbnails/p3.jpg'; +import fp4 from '@/assets/images/filter-thumbnails/p4.jpg'; +import fp5 from '@/assets/images/filter-thumbnails/p5.jpg'; +import fp6 from '@/assets/images/filter-thumbnails/p6.jpg'; +import fp8 from '@/assets/images/filter-thumbnails/p8.jpg'; +import frama from '@/assets/images/filter-thumbnails/ramasjang-tv.jpg'; + const getEntryId = (recordData: BroadcastRecordType) => { const kalturaIdObj = recordData.identifier.find((obj) => obj.PropertyID === 'KalturaID'); return kalturaIdObj ? kalturaIdObj.value : ''; }; +const getFilterThumbnail = (channel: string) => { + channel = channel.toLowerCase(); + switch (true) { + case channel.includes('dr 1'): + return fdr1; + + case channel.includes('dr 2'): + return fdr2; + + case channel.includes('dr update'): + return fdruppdate; + + case channel.includes('dr ultra'): + return fdrultra; + + case channel.includes('dr k'): + return fdrk; + + case channel.includes('ramasjang'): + return frama; + + case channel.includes('p1'): + return fp1; + + case channel.includes('p2'): + return fp2; + + case channel.includes('p3'): + return fp3; + + case channel.includes('p4'): + return fp4; + + case channel.includes('p5'): + return fp5; + + case channel.includes('p6'): + return fp6; + + case channel.includes('p8'): + return fp8; + + default: + return fdr; + } +}; + const getThumbnailPicture = (channel: string) => { channel = channel.toLowerCase(); switch (true) { @@ -42,4 +104,4 @@ const getThumbnailPicture = (channel: string) => { } }; -export { getEntryId, getThumbnailPicture }; +export { getEntryId, getThumbnailPicture, getFilterThumbnail }; diff --git a/src/utils/svg-graph.ts b/src/utils/svg-graph.ts index 731ba01d..62642fcd 100644 --- a/src/utils/svg-graph.ts +++ b/src/utils/svg-graph.ts @@ -8,10 +8,10 @@ function createSVGCurvedLine(points: pointItem[]) { svg.setAttribute('viewBox', '0 0 100 100'); // Use a square viewBox for simplicity svg.setAttribute('preserveAspectRatio', 'none'); const path = document.createElementNS(svgns, 'path'); - path.setAttribute('fill', '#caf0fe'); // No fill to avoid closing the path - path.setAttribute('stroke', '#F7AE3B4D'); - path.setAttribute('stroke-width', '1'); - path.setAttribute('opacity', '1'); + path.setAttribute('fill', '#ea9fbc'); // No fill to avoid closing the path + path.setAttribute('stroke', '#ea9fbc'); + path.setAttribute('stroke-width', '2'); + path.setAttribute('fill-opacity', '1'); path.setAttribute('vector-effect', 'non-scaling-stroke'); // Normalize y coordinates to fit within [0, 100] range @@ -50,6 +50,39 @@ function createSVGCurvedLine(points: pointItem[]) { path.setAttribute('d', d); svg.appendChild(path); + + const defs = document.createElementNS(svgns, 'defs'); + const clipPath = document.createElementNS(svgns, 'clipPath'); + const clipId = `clip-${Math.random().toString(36).slice(2)}`; + + clipPath.setAttribute('id', clipId); + + const clipShape = path.cloneNode(true) as SVGElement; + clipShape.removeAttribute('fill'); + clipShape.removeAttribute('stroke'); + + clipPath.appendChild(clipShape); + defs.appendChild(clipPath); + svg.appendChild(defs); + + for (let i = 0; i < normalizedPoints.length; i += 1) { + const p = normalizedPoints[i]; + + const line = document.createElementNS(svgns, 'line'); + + line.setAttribute('x1', String(p.x)); + line.setAttribute('y1', '100'); + line.setAttribute('x2', String(p.x)); + line.setAttribute('y2', String(Number(p.y - 10))); + line.setAttribute('class', 'line-class'); + line.setAttribute('stroke', '#ffffff27'); + line.setAttribute('stroke-width', '1'); + line.setAttribute('vector-effect', 'non-scaling-stroke'); + line.setAttribute('clip-path', `url(#${clipId})`); + + svg.appendChild(line); + } + return svg; } diff --git a/src/views/404.vue b/src/views/404.vue index 6bb0243d..404386b1 100644 --- a/src/views/404.vue +++ b/src/views/404.vue @@ -19,7 +19,7 @@