Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
dashboard
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
AURA
dashboard
Commits
2a68a7ac
Commit
2a68a7ac
authored
1 year ago
by
Konrad Mohrfeldt
Browse files
Options
Downloads
Patches
Plain Diff
feat: show intermissions and current time indicator in calendar day view
refs
#155
#156
parent
552edd3f
No related branches found
No related tags found
No related merge requests found
Pipeline
#3483
failed
1 year ago
Stage: test
Stage: release
Changes
3
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
src/components/CalendarDayView.vue
+330
-120
330 additions, 120 deletions
src/components/CalendarDayView.vue
src/i18n/de.js
+8
-4
8 additions, 4 deletions
src/i18n/de.js
src/i18n/en.js
+8
-4
8 additions, 4 deletions
src/i18n/en.js
with
346 additions
and
128 deletions
src/components/CalendarDayView.vue
+
330
−
120
View file @
2a68a7ac
<
template
>
<
template
>
<div
class=
"schedule-panel tw-w-full"
>
<div>
<div
class=
"tw-flex tw-items-center tw-justify-between tw-mb-4"
>
<div
class=
"schedule-panel tw-w-full"
>
<h3>
{{
prettyDate
(
selectedDay
)
}}
</h3>
<div
class=
"tw-flex tw-items-center tw-justify-between tw-mb-4"
>
<h3>
{{
selectedDay
.
toLocaleString
(
locale
,
{
dateStyle
:
'
long
'
}
)
}}
<
/h3
>
<div
class=
"fc fc-direction-ltr"
>
<div
class=
"fc-button-group"
>
<button
type=
"button"
class=
"fc-button fc-prev-button fc-button-primary"
@
click=
"emit('changeDay', -1)"
>
<span
class=
"fc-icon fc-icon-chevron-left"
></span>
</button>
<button
type=
"button"
class=
"fc-button fc-next-button fc-button-primary"
@
click=
"emit('changeDay', 1)"
>
<span
class=
"fc-icon fc-icon-chevron-right"
></span>
</button>
</div>
</div>
</div>
<div
v-for=
"
{
timeslot,
timeslotDurationInSeconds,
playlist,
playlistDurationInSeconds,
show,
} in slotData"
:key="timeslot.id"
class="timeslot tw-w-full tw-py-2 tw-px-3 tw-rounded hover:tw-bg-gray-200 hover:tw-text-gray-900 tw-cursor-pointer tw-border tw-border-solid tw-border-gray-200 tw-mb-2"
:class="{
'tw-bg-gray-900 tw-text-white':
now >= parseISO(timeslot.start)
&&
now
<
=
parseISO
(
timeslot.end
),
'
tw-bg-gray-200
tw-text-gray-600
tw-opacity-75
'
:
now
>
= parseISO(timeslot.end),
}"
@click="onTimeslotClick(timeslot)"
>
<div
class=
"tw-flex tw-justify-between tw-items-center"
>
<div>
<p
class=
"tw-mb-0 tw-leading-tight tw-font-bold"
>
<SafeHTML
v-if=
"show"
:html=
"show.name"
sanitize-preset=
"inline-noninteractive"
/>
</p>
<span
class=
"tw-text-sm"
>
{{
prettyTime
(
timeslot
.
start
)
}}
-
{{
prettyTime
(
timeslot
.
end
)
}}
<span
v-if=
"timeslot.repetitionOfId"
class=
"tw-text-gray-400"
>
{{
t
(
'
calendar.repetition
'
)
}}
</span>
</span>
</div>
<div
v-if=
"(timeslot.playlistId || show?.defaultPlaylistId) && playlist"
>
<
div
class
=
"
fc fc-direction-ltr
"
>
<p>
<
div
class
=
"
fc-button-group
"
>
<strong>
{{
t
(
'
emissionTable.playlist
'
)
}}
:
</strong>
<
button
{{
playlist
.
description
}}
type
=
"
button
"
<span
class
=
"
fc-button fc-prev-button fc-button-primary
"
v-if=
"!timeslot.playlistId && show?.defaultPlaylistId"
@
click
=
"
emit('changeDay', -1)
"
class=
"tw-text-xs tw-text-red-500"
>
>
{{
t
(
'
calendar.fallback
'
)
}}
<
span
class
=
"
fc-icon fc-icon-chevron-left
"
><
/span
>
</span>
<
/button
>
</p>
<
button
<p
v-if=
"timeslot.playlistId"
class=
"tw-text-sm"
>
type
=
"
button
"
<strong>
{{
t
(
'
emissionTable.duration
'
)
}}
:
</strong>
class
=
"
fc-button fc-next-button fc-button-primary
"
{{
secondsToDurationString
(
playlistDurationInSeconds
??
timeslotDurationInSeconds
)
}}
@
click
=
"
emit('changeDay', 1)
"
<span
v-if=
"
playlistDurationInSeconds !== null &&
timeslotDurationInSeconds !== playlistDurationInSeconds
"
class=
"is-mismatched"
>
>
{{
t
(
'
calendar.mismatchedLength
'
)
}}
<
span
class
=
"
fc-icon fc-icon-chevron-right
"
><
/span
>
</
spa
n>
<
/
butto
n
>
</
p
>
<
/
div
>
<
/div
>
<
/div
>
<p
v-else
>
{{
t
(
'
calendar.empty
'
)
}}
</p>
<
/div
>
<
/div
>
<
table
class
=
"
slot-table tw-block md:tw-table
"
role
=
"
table
"
>
<
colgroup
>
<
col
width
=
"
1%
"
/>
<
/colgroup
>
<
tbody
role
=
"
rowgroup
"
class
=
"
tw-block md:tw-table-row-group
"
>
<
template
v
-
for
=
"
(slot, index) in slots
"
:
key
=
"
index
"
>
<
tr
v
-
if
=
"
index !== 0
"
role
=
"
presentation
"
class
=
"
tw-block md:tw-table-row
"
>
<
td
colspan
=
"
2
"
class
=
"
tw-p-0 tw-block md:tw-table-cell tw-w-full
"
>
<
hr
class
=
"
tw-my-4
"
/>
<
/td
>
<
/tr
>
<
tr
class
=
"
slot tw-block md:tw-table-row tw-w-full tw-relative
"
role
=
"
row
"
:
class
=
"
{
'slot--today': nowDateString === selectedDayISODate,
'slot--past': now > slot.end,
'slot--current': now > slot.start && now < slot.end,
'slot--future': now < slot.start,
}
"
>
<
th
class
=
"
slot-time tw-block md:tw-table-cell tw-align-top tw-font-normal tw-text-gray-600
"
scope
=
"
row
"
role
=
"
rowheader
"
>
<
span
v
-
if
=
"
now > slot.start && now < slot.end
"
class
=
"
slot-time-indicator
"
>
<
span
:
style
=
"
{
top: `${mapToDomain(
now.getTime(),
[slot.start.getTime(), slot.end.getTime()],
[0, 100],
)
}
%`,
}
"
/>
<
/span
>
<
span
class
=
"
tw-whitespace-nowrap
"
>
{{
formatTime
(
slot
.
start
)
}}
-
{{
formatTime
(
slot
.
end
)
}}
<
/span
>
<
br
/>
<
span
class
=
"
tw-text-sm
"
>
{{
secondsToDurationString
(
slot
.
durationInSeconds
)
}}
<
/span
>
<
/th
>
<
td
role
=
"
cell
"
class
=
"
slot-data tw-block lg-tw-table-cell tw-w-full tw-align-top tw-rounded
"
:
class
=
"
{
'tw-bg-gray-50': slot.type === 'show',
'tw-bg-amber-50': slot.type === 'intermission',
}
"
>
<
template
v
-
if
=
"
slot.type === 'intermission'
"
>
{{
t
(
'
calendar.intermission
'
)
}}
<
/template
>
<
div
v
-
if
=
"
slot.type === 'show'
"
class
=
"
tw-flex tw-flex-col tw-gap-2
"
>
<
div
>
<
div
class
=
"
tw-float-right
"
>
<
button
v
-
if
=
"
slot.timeslot.showId === selectedShow.id
"
type
=
"
button
"
class
=
"
btn btn-sm btn-default
"
:
title
=
"
t('calendar.editTimeslot')
"
@
click
=
"
editTimeslot(slot.timeslot)
"
>
<
icon
-
system
-
uicons
-
pen
/>
<
/button
>
<
button
v
-
if
=
"
slot.timeslot.showId !== selectedShow.id
"
class
=
"
btn btn-sm btn-default
"
:
title
=
"
t('calendar.switchShow', { show: sanitizeHTML(slot.show?.name ?? '')
}
)
"
@
click
=
"
switchShow(slot.timeslot)
"
>
<
icon
-
system
-
uicons
-
reverse
/>
<
/button
>
<
/div
>
<
SafeHTML
:
html
=
"
slot.show?.name ?? ''
"
sanitize
-
preset
=
"
inline-noninteractive
"
as
=
"
p
"
class
=
"
tw-font-bold tw-m-0
"
/>
<
/div
>
<
div
class
=
"
tw-grid tw-gap-1 tw-text-sm empty:tw-hidden
"
style
=
"
grid-template-columns: max-content minmax(0, 1fr)
"
>
<
template
v
-
if
=
"
(slot.timeslot.playlistId || slot.show?.defaultPlaylistId) && slot.playlist
"
>
<
span
>
{{
t
(
'
emissionTable.playlist
'
)
}}
:
<
/span
>
<
span
>
{{
slot
.
playlist
.
description
}}
<
/span
>
<
/template
>
<
template
v
-
if
=
"
slot.playlistDurationInSeconds
"
>
<
span
>
{{
t
(
'
emissionTable.duration
'
)
}}
:
<
/span
>
<
span
>
{{
secondsToDurationString
(
slot
.
playlistDurationInSeconds
)
}}
<
/span
>
<
/template
>
<
/div
>
<
div
class
=
"
tw-flex tw-gap-2
"
>
<
span
v
-
if
=
"
now > slot.start && now < slot.end
"
:
class
=
"
pillClasses
"
class
=
"
tw-bg-teal-200
"
>
{{
t
(
'
calendar.playing
'
)
}}
<
/span
>
<
span
v
-
if
=
"
slot.timeslot.repetitionOfId
"
:
class
=
"
pillClasses
"
class
=
"
tw-bg-amber-200
"
>
{{
t
(
'
calendar.repetition
'
)
}}
<
/span
>
<
span
v
-
if
=
"
slot.playlistDurationInSeconds !== null &&
slot.timeslotDurationInSeconds !== slot.playlistDurationInSeconds
"
:
class
=
"
pillClasses
"
class
=
"
tw-bg-amber-200
"
>
{{
t
(
'
calendar.mismatchedLength
'
)
}}
<
/span
>
<
span
v
-
if
=
"
!slot.timeslot.playlistId && slot.show?.defaultPlaylistId
"
:
class
=
"
pillClasses
"
class
=
"
tw-bg-rose-200
"
>
{{
t
(
'
calendar.fallback
'
)
}}
<
/span
>
<
span
v
-
else
-
if
=
"
!slot.timeslot.playlistId && !slot.show?.defaultPlaylistId
"
:
class
=
"
pillClasses
"
class
=
"
tw-bg-rose-200
"
>
{{
t
(
'
calendar.empty
'
)
}}
<
/span
>
<
/div
>
<
/div
>
<
/td
>
<
/tr
>
<
/template
>
<
/tbody
>
<
/table
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
/template
>
<
script
lang
=
"
ts
"
setup
>
<
script
lang
=
"
ts
"
setup
>
import
SafeHTML
from
'
@/components/generic/SafeHTML
'
import
{
endOfDay
,
parseISO
,
startOfDay
}
from
'
date-fns
'
import
{
usePrett
y
}
from
'
@/mixins/prettyDate
'
import
{
sortB
y
}
from
'
lodash
'
import
{
use
I18n
}
from
'
@
/i18n
'
import
{
use
Now
}
from
'
@
vueuse/core
'
import
{
computed
}
from
'
vue
'
import
{
computed
}
from
'
vue
'
import
{
getISODateString
,
useSelectedShow
}
from
'
@/utilities
'
import
{
useStore
}
from
'
vuex
'
import
{
useStore
}
from
'
vuex
'
import
SafeHTML
from
'
@/components/generic/SafeHTML
'
import
{
useI18n
}
from
'
@/i18n
'
import
{
Playlist
,
Show
,
TimeSlot
}
from
'
@/types
'
import
{
Playlist
,
Show
,
TimeSlot
}
from
'
@/types
'
import
{
parseISO
}
from
'
date-fns
'
import
{
calculatePlaylistDurationInSeconds
,
usePlaylistStore
}
from
'
@/stores/playlists
'
import
{
calculatePlaylistDurationInSeconds
,
usePlaylistStore
}
from
'
@/stores/playlists
'
import
{
calculateDurationSeconds
,
secondsToDurationString
}
from
'
@/util
'
import
{
getISODateString
}
from
'
@/utilities
'
import
{
useNow
}
from
'
@vueuse/core
'
import
{
calculateDurationSeconds
,
mapToDomain
,
sanitizeHTML
,
secondsToDurationString
,
useFormattedISODate
,
useSelectedShow
,
}
from
'
@/util
'
const
pillClasses
=
'
tw-py-1 tw-px-2 tw-text-xs tw-rounded-full
'
type
SlotData
=
{
type
BaseSlot
=
{
start
:
Date
end
:
Date
durationInSeconds
:
number
}
type
IntermissionSlot
=
BaseSlot
&
{
type
:
'
intermission
'
}
type
ShowSlot
=
BaseSlot
&
{
type
:
'
show
'
timeslot
:
TimeSlot
timeslot
:
TimeSlot
timeslotDurationInSeconds
:
number
timeslotDurationInSeconds
:
number
show
:
Show
|
undefined
show
:
Show
|
undefined
...
@@ -110,6 +226,8 @@ type SlotData = {
...
@@ -110,6 +226,8 @@ type SlotData = {
playlistDurationInSeconds
:
number
|
null
playlistDurationInSeconds
:
number
|
null
}
}
type
Slot
=
IntermissionSlot
|
ShowSlot
const
props
=
defineProps
<
{
const
props
=
defineProps
<
{
selectedDay
:
Date
selectedDay
:
Date
}
>
()
}
>
()
...
@@ -117,50 +235,142 @@ const emit = defineEmits<{
...
@@ -117,50 +235,142 @@ const emit = defineEmits<{
changeDay
:
[
offset
:
number
]
changeDay
:
[
offset
:
number
]
editTimeslot
:
[
timeslot
:
TimeSlot
]
editTimeslot
:
[
timeslot
:
TimeSlot
]
}
>
()
}
>
()
const
{
t
}
=
useI18n
()
const
{
t
,
locale
}
=
useI18n
()
const
{
prettyDate
,
prettyTime
}
=
usePretty
()
const
now
=
useNow
({
interval
:
60
_000
})
const
store
=
useStore
()
const
store
=
useStore
()
const
selectedShow
=
useSelectedShow
()
const
selectedShow
=
useSelectedShow
()
const
{
itemMap
:
playlistMap
,
retrieve
:
retrievePlaylist
}
=
usePlaylistStore
()
const
shows
=
computed
<
Show
[]
>
(()
=>
store
.
state
.
shows
.
shows
)
const
shows
=
computed
<
Show
[]
>
(()
=>
store
.
state
.
shows
.
shows
)
const
timeslots
=
computed
<
TimeSlot
[]
>
(()
=>
store
.
state
.
shows
.
timeslots
)
const
timeslots
=
computed
<
TimeSlot
[]
>
(()
=>
store
.
state
.
shows
.
timeslots
)
const
{
itemMap
:
playlistMap
,
retrieve
:
retrievePlaylist
}
=
usePlaylistStore
()
const
now
=
useNow
({
interval
:
60
_000
}
)
const
nowDateString
=
useFormattedISODate
(
now
)
const
startOfSelectedDay
=
computed
(()
=>
startOfDay
(
props
.
selectedDay
))
const
endOfSelectedDay
=
computed
(()
=>
endOfDay
(
props
.
selectedDay
))
const
selectedDayISODate
=
computed
(()
=>
getISODateString
(
props
.
selectedDay
))
const
selectedDayISODate
=
computed
(()
=>
getISODateString
(
props
.
selectedDay
))
const
slotData
=
computed
<
SlotData
[]
>
(()
=>
{
const
slots
=
computed
<
Slot
[]
>
(()
=>
{
const
result
:
SlotData
[]
=
[]
const
result
:
Slot
[]
=
[]
const
daySlots
=
timeslots
.
value
.
filter
(
let
start
=
new
Date
(
startOfSelectedDay
.
value
)
(
timeslot
)
=>
timeslot
.
start
.
split
(
'
T
'
)[
0
]
===
selectedDayISODate
.
value
,
const
endOfToday
=
new
Date
(
endOfSelectedDay
.
value
)
const
daySlots
=
sortBy
(
timeslots
.
value
.
filter
((
timeslot
)
=>
// we want either the start date or the end date to be on the currently selected day
// so that timeslots that wrap around days are included
[
extractDateFromDateTimeString
(
timeslot
.
start
),
extractDateFromDateTimeString
(
timeslot
.
end
),
].
includes
(
selectedDayISODate
.
value
),
),
(
timeslot
)
=>
timeslot
.
start
,
)
)
for
(
const
timeslot
of
daySlots
)
{
for
(
const
timeslot
of
daySlots
)
{
const
show
=
shows
.
value
.
find
(({
id
})
=>
timeslot
.
showId
===
id
)
const
showSlot
=
createShowSlot
(
timeslot
)
const
playlistId
=
timeslot
.
playlistId
??
show
?.
defaultPlaylistId
if
(
showSlot
.
start
>
start
)
{
// enqueue a request for the playlist in case it’s not yet in the store
// this slot starts some time after the previous slot ended
if
(
playlistId
)
retrievePlaylist
(
playlistId
,
undefined
,
{
useCached
:
true
})
// that means we got an intermission between the last timeslot and the current one
const
playlist
=
playlistId
?
playlistMap
.
get
(
playlistId
)
:
undefined
result
.
push
(
createIntermissionSlot
(
start
,
showSlot
.
start
))
const
timeslotDurationInSeconds
=
calculateDurationSeconds
(
}
parseISO
(
timeslot
.
start
),
start
=
showSlot
.
end
parseISO
(
timeslot
.
end
),
result
.
push
(
showSlot
)
)
}
const
playlistDurationInSeconds
=
playlist
?
calculatePlaylistDurationInSeconds
(
playlist
)
:
null
if
(
start
<
endOfToday
)
{
result
.
push
({
// the last timeslot of this day ended before the end of the day
timeslot
,
// that means we got an intermission between the end of the last show and the end of the selected day
timeslotDurationInSeconds
,
result
.
push
(
createIntermissionSlot
(
start
,
endOfToday
))
playlist
,
playlistDurationInSeconds
,
show
,
})
}
}
return
result
return
result
}
)
}
)
function
onTimeslotClick
(
timeslot
:
TimeSlot
)
{
function
extractDateFromDateTimeString
(
date
:
string
)
{
if
(
selectedShow
.
value
.
id
!==
timeslot
.
showId
)
{
return
date
.
split
(
'
T
'
)[
0
]
selectedShow
.
value
=
{
id
:
timeslot
.
showId
}
as
Show
}
}
else
{
emit
(
'
editTimeslot
'
,
timeslot
)
function
createIntermissionSlot
(
start
:
Date
,
end
:
Date
):
IntermissionSlot
{
return
{
type
:
'
intermission
'
,
start
,
end
,
durationInSeconds
:
calculateDurationSeconds
(
start
,
end
),
}
}
}
}
function
createShowSlot
(
timeslot
:
TimeSlot
):
ShowSlot
{
const
show
=
shows
.
value
.
find
(({
id
}
)
=>
timeslot
.
showId
===
id
)
const
playlistId
=
timeslot
.
playlistId
??
show
?.
defaultPlaylistId
// enqueue a request for the playlist in case it’s not yet in the store
if
(
playlistId
)
retrievePlaylist
(
playlistId
,
undefined
,
{
useCached
:
true
}
)
const
playlist
=
playlistId
?
playlistMap
.
get
(
playlistId
)
:
undefined
const
start
=
parseISO
(
timeslot
.
start
)
const
end
=
parseISO
(
timeslot
.
end
)
const
timeslotDurationInSeconds
=
calculateDurationSeconds
(
start
,
end
)
const
playlistDurationInSeconds
=
playlist
?
calculatePlaylistDurationInSeconds
(
playlist
)
:
null
return
{
type
:
'
show
'
,
start
,
end
,
durationInSeconds
:
calculateDurationSeconds
(
start
,
end
),
timeslot
,
timeslotDurationInSeconds
,
playlist
,
playlistDurationInSeconds
,
show
,
}
}
function
formatTime
(
date
:
Date
)
{
if
(
date
<
startOfSelectedDay
.
value
||
date
>
endOfSelectedDay
.
value
)
{
return
date
.
toLocaleString
(
locale
.
value
,
{
dateStyle
:
'
short
'
,
timeStyle
:
'
short
'
,
}
)
}
return
date
.
toLocaleString
(
locale
.
value
,
{
timeStyle
:
'
short
'
,
}
)
}
function
switchShow
(
timeslot
:
TimeSlot
)
{
selectedShow
.
value
=
{
id
:
timeslot
.
showId
}
as
Show
}
function
editTimeslot
(
timeslot
:
TimeSlot
)
{
emit
(
'
editTimeslot
'
,
timeslot
)
}
<
/script
>
<
/script
>
<
style
lang
=
"
postcss
"
scoped
>
.
slot
-
table
{
width
:
min
(
800
px
,
100
%
);
}
.
slot
-
table
:
is
(
th
,
td
)
{
@
apply
tw
-
p
-
2
;
}
.
slot
-
table
th
{
@
apply
tw
-
pr
-
4
;
}
.
slot
.
slot
--
today
.
slot
--
past
{
@
apply
tw
-
opacity
-
75
;
}
.
slot
-
time
-
indicator
{
position
:
absolute
;
top
:
0
;
bottom
:
0
;
right
:
100
%
;
width
:
0.35
rem
;
border
:
solid
theme
(
'
colors.gray.300
'
);
border
-
width
:
1
px
1
px
1
px
0
;
&
>
span
{
position
:
absolute
;
right
:
0
;
width
:
0.5
rem
;
height
:
1
px
;
background
-
color
:
var
(
--
fc
-
now
-
indicator
-
color
);
}
}
<
/style
>
This diff is collapsed.
Click to expand it.
src/i18n/de.js
+
8
−
4
View file @
2a68a7ac
...
@@ -468,14 +468,18 @@ export default {
...
@@ -468,14 +468,18 @@ export default {
calendar
:
{
calendar
:
{
today
:
'
Heute
'
,
today
:
'
Heute
'
,
empty
:
'
(
Keine Playlist
!)
'
,
empty
:
'
Keine Playlist
'
,
fallback
:
'
Achtung!
Fallback-Playlist
!
'
,
fallback
:
'
Fallback-Playlist
'
,
repetition
:
'
(
Wiederholung
)
'
,
repetition
:
'
Wiederholung
'
,
view
:
{
view
:
{
day
:
'
Tagesansicht
'
,
day
:
'
Tagesansicht
'
,
week
:
'
Wochenansicht
'
,
week
:
'
Wochenansicht
'
,
},
},
mismatchedLength
:
'
(Unpassende Länge)
'
,
switchShow
:
'
Zur „%{show}“ Sendereihe wechseln
'
,
editTimeslot
:
'
Sendung bearbeiten
'
,
intermission
:
'
Programmunterbrechung
'
,
mismatchedLength
:
'
Unpassende Länge
'
,
playing
:
'
Spielt gerade
'
,
},
},
// Etc
// Etc
...
...
This diff is collapsed.
Click to expand it.
src/i18n/en.js
+
8
−
4
View file @
2a68a7ac
...
@@ -459,14 +459,18 @@ export default {
...
@@ -459,14 +459,18 @@ export default {
calendar
:
{
calendar
:
{
today
:
'
Today
'
,
today
:
'
Today
'
,
empty
:
'
(
No playlist
! Station fallback will be used)
'
,
empty
:
'
No playlist
'
,
fallback
:
'
Warning!
Fallback playlist
!
'
,
fallback
:
'
Fallback playlist
'
,
repetition
:
'
(
Repetition
)
'
,
repetition
:
'
Repetition
'
,
view
:
{
view
:
{
day
:
'
Day view
'
,
day
:
'
Day view
'
,
week
:
'
Week view
'
,
week
:
'
Week view
'
,
},
},
mismatchedLength
:
'
(wrong duration)
'
,
switchShow
:
'
Switch to “%{show}” show
'
,
editTimeslot
:
'
Sendung bearbeiten
'
,
intermission
:
'
Program intermission
'
,
mismatchedLength
:
'
wrong duration
'
,
playing
:
'
Currently playing
'
,
},
},
// Etc
// Etc
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment