responsive menus

This commit is contained in:
bkfox 2023-11-29 15:41:15 +01:00
parent f5ce00795e
commit 8202a9324c
17 changed files with 639 additions and 336 deletions

View File

@ -42,6 +42,7 @@ class Schedule(Rerun):
second_and_fourth = 0b001010, _("2nd and 4th {day} of the month")
every = 0b011111, _("{day}")
one_on_two = 0b100000, _("one {day} on two")
# every_weekday = 0b10000000 _("from Monday to Friday")
date = models.DateField(
_("date"),
@ -71,6 +72,10 @@ class Schedule(Rerun):
verbose_name = _("Schedule")
verbose_name_plural = _("Schedules")
def __init__(self, *args, **kwargs):
self._initial = kwargs
super().__init__(*args, **kwargs)
def __str__(self):
return "{} - {}, {}".format(
self.program.title,
@ -110,16 +115,28 @@ class Schedule(Rerun):
date = tz.datetime.combine(date, self.time)
return date.replace(tzinfo=self.tz)
def dates_of_month(self, date):
"""Return normalized diffusion dates of provided date's month."""
if self.frequency == Schedule.Frequency.ponctual:
def dates_of_month(self, date, frequency=None, sched_date=None):
"""Return normalized diffusion dates of provided date's month.
:param Date date: date of the month to get dates from;
:param Schedule.Frequency frequency: frequency (defaults to ``self.frequency``)
:param Date sched_date: schedule start date (defaults to ``self.date``)
:return list of diffusion dates
"""
if frequency is None:
frequency = self.frequency
if sched_date is None:
sched_date = self.date
if frequency == Schedule.Frequency.ponctual:
return []
sched_wday, freq = self.date.weekday(), self.frequency
sched_wday = sched_date.weekday()
date = date.replace(day=1)
# last of the month
if freq == Schedule.Frequency.last:
if frequency == Schedule.Frequency.last:
date = date.replace(day=calendar.monthrange(date.year, date.month)[1])
date_wday = date.weekday()
@ -134,33 +151,42 @@ class Schedule(Rerun):
date_wday, month = date.weekday(), date.month
date += tz.timedelta(days=(7 if date_wday > sched_wday else 0) - date_wday + sched_wday)
if freq == Schedule.Frequency.one_on_two:
if frequency == Schedule.Frequency.one_on_two:
# - adjust date with modulo 14 (= 2 weeks in days)
# - there are max 3 "weeks on two" per month
if (date - self.date).days % 14:
if (date - sched_date).days % 14:
date += tz.timedelta(days=7)
dates = (date + tz.timedelta(days=14 * i) for i in range(0, 3))
else:
dates = (date + tz.timedelta(days=7 * week) for week in range(0, 5) if freq & (0b1 << week))
dates = (date + tz.timedelta(days=7 * week) for week in range(0, 5) if frequency & (0b1 << week))
return [self.normalize(date) for date in dates if date.month == month]
def diffusions_of_month(self, date):
def diffusions_of_month(self, date, frequency=None, sched_date=None):
"""Get episodes and diffusions for month of provided date, including
reruns.
:param Date date: date of the month to get diffusions from;
:param Schedule.Frequency frequency: frequency (defaults to ``self.frequency``)
:param Date sched_date: schedule start date (defaults to ``self.date``)
:returns: tuple([Episode], [Diffusion])
"""
from .diffusion import Diffusion
from .episode import Episode
if self.initial is not None or self.frequency == Schedule.Frequency.ponctual:
if frequency is None:
frequency = self.frequency
if sched_date is None:
sched_date = self.date
if self.initial is not None or frequency == Schedule.Frequency.ponctual:
return [], []
# dates for self and reruns as (date, initial)
reruns = [(rerun, rerun.date - self.date) for rerun in self.rerun_set.all()]
reruns = [(rerun, rerun.date - sched_date) for rerun in self.rerun_set.all()]
dates = {date: None for date in self.dates_of_month(date)}
dates = {date: None for date in self.dates_of_month(date, frequency, sched_date)}
dates.update(
(rerun.normalize(date.date() + delta), date) for date in list(dates.keys()) for rerun, delta in reruns
)

View File

@ -7970,82 +7970,12 @@ a.tag:hover {
font-size: 0.75rem;
}
.is-fullwidth {
width: 100%;
}
.is-fullheight {
height: 100%;
}
.is-fixed-bottom {
position: fixed;
bottom: 0;
margin-bottom: 0px;
border-radius: 0;
}
.is-borderless {
border: none;
}
.has-text-nowrap {
white-space: nowrap;
}
.has-background-transparent {
background-color: transparent;
}
.is-opacity-light {
opacity: 0.7;
}
.is-opacity-light:hover {
opacity: 1;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.overflow-hidden {
overflow: hidden;
}
.overflow-hidden.is-fullwidth {
max-width: 100%;
}
*[draggable=true] {
cursor: move;
}
input.half-field:not(:active):not(:hover) {
border: none;
background-color: rgba(0, 0, 0, 0);
cursor: pointer;
}
@keyframes blink {
from {
opacity: 1;
}
to {
opacity: 0.4;
}
}
.blink {
animation: 1s ease-in-out 2s infinite alternate blink;
}
.loading {
animation: 1s ease-in-out 3s infinite alternate blink;
}
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
box-shadow: 0em 0em 1em rgba(0, 0, 0, 0.1);
}
@ -8095,6 +8025,8 @@ a.navbar-item.is-active {
--highlight-color-2: rgb(0, 0, 254);
--highlight-color-2-alpha: rgb(0, 0, 254, 0.7);
--highlight-color-2-grey: rgba(50, 200, 200, 1);
--nav-primary-height: 4rem;
--nav-secondary-height: 3rem;
--header-height: 30rem;
--heading-height: 30rem;
--heading-title-bg-color: rgba(255, 255, 0, 1);
@ -8149,36 +8081,24 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
margin-top: 2rem;
}
.d-inline {
display: inline;
*[draggable=true] {
cursor: move;
}
.d-block {
display: block;
@keyframes blink {
from {
opacity: 1;
}
to {
opacity: 0.4;
}
}
.blink {
animation: 1s ease-in-out 3s infinite alternate blink;
}
.d-inline-block {
display: inline-block;
}
.p-relative {
position: relative;
}
.p-absolute {
position: absolute;
}
.p-fixed {
position: fixed;
}
.p-sticky {
position: sticky;
}
.p-static {
position: static;
.loading {
animation: 1s ease-in-out 1s infinite alternate blink;
}
.align-left {
@ -8191,8 +8111,16 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
justify-content: right;
}
.height-full {
height: 100%;
.d-inline {
display: inline !important;
}
.d-block {
display: block !important;
}
.d-inline-block {
display: inline-block !important;
}
.flex-push-right {
@ -8203,6 +8131,69 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
flex-grow: 0 !important;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.is-fullwidth {
width: 100%;
}
.is-fullheight {
height: 100%;
}
.is-fixed-bottom {
position: fixed;
bottom: 0;
margin-bottom: 0px;
border-radius: 0;
}
.is-borderless {
border: none;
}
.overflow-hidden {
overflow: hidden;
}
.overflow-hidden.is-fullwidth {
max-width: 100%;
}
.p-relative {
position: relative !important;
}
.p-absolute {
position: absolute !important;
}
.p-fixed {
position: fixed !important;
}
.p-sticky {
position: sticky !important;
}
.p-static {
position: static !important;
}
.height-full {
height: 100%;
}
.ws-nowrap {
white-space: nowrap;
}
.no-border {
border: 0px !important;
}
@ -8215,6 +8206,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
color: var(--highlight-color-2);
}
.bg-transparent {
background-color: transparent;
}
.is-success {
background-color: #0e0 !important;
border-color: #0b0 !important;
@ -8233,6 +8228,16 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
color: var(--highlight-color-2) !important;
}
.schedules {
margin-top: -0.6rem !important;
padding-top: 0;
}
.schedule .day {
font-weight: 700;
margin-right: 0.6rem;
}
.button, a.button, button.button, .nav-urls a {
display: inline-block;
padding: 0.6em;
@ -8246,10 +8251,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
.button .icon, a.button .icon, button.button .icon, .nav-urls a .icon {
vertical-align: middle;
}
.button .icon:first-child, a.button .icon:first-child, button.button .icon:first-child, .nav-urls a .icon:first-child {
.button .icon:not(:only-child):first-child, a.button .icon:not(:only-child):first-child, button.button .icon:not(:only-child):first-child, .nav-urls a .icon:not(:only-child):first-child {
margin-right: 0.6rem;
}
.button .icon:last-child, a.button .icon:last-child, button.button .icon:last-child, .nav-urls a .icon:last-child {
.button .icon:not(:only-child):last-child, a.button .icon:not(:only-child):last-child, button.button .icon:not(:only-child):last-child, .nav-urls a .icon:not(:only-child):last-child {
margin-left: 0.6rem;
}
.button:hover, a.button:hover, button.button:hover, .nav-urls a:hover {
@ -8279,16 +8284,17 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
border-radius: 1.5em;
}
.button-group .button {
.button-group .button, .nav .button {
border-radius: 0px;
background-color: transparent;
border-top: 0px;
border-bottom: 0px;
height: 100%;
}
.button-group .button:not(:first-child) {
.button-group .button:not(:first-child), .nav .button:not(:first-child) {
border-left: 0px;
}
.button-group .button:last-child {
.button-group .button:last-child, .nav .button:last-child {
border-right: 0px;
}
@ -8319,6 +8325,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
color: var(--highlight-color-2) !important;
}
.label, .textarea, .input, .select {
font-size: 1.4rem;
}
.title {
text-transform: uppercase;
}
@ -8350,6 +8360,9 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
.nav:empty {
display: none;
}
.nav .burger {
display: none;
}
.nav .nav-item {
padding: 0.6rem;
flex-grow: 1;
@ -8358,25 +8371,38 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
font-family: var(--heading-font-family);
text-transform: uppercase;
}
.nav .nav-item a, .nav .nav-item .button {
display: block;
width: 100%;
}
.nav .nav-item.active {
background-color: var(--highlight-color-2);
color: var(--highlight-color);
}
.nav.primary .nav-brand {
display: inline-block;
margin-right: 0.6rem;
padding: 0.6rem;
}
.nav.primary .nav-brand img {
width: 12rem !important;
}
.nav.primary .nav-menu {
.nav .nav-menu {
display: flex;
flex-grow: 1;
background-color: var(--highlight-color);
}
.nav.primary {
height: var(--nav-primary-height);
}
.nav.primary .nav-menu {
flex-grow: 1;
}
.nav.primary .nav-brand {
display: inline-block;
padding: 0.6rem;
flex-grow: 0;
flex-shrink: 1;
}
.nav.primary .nav-brand img {
height: 100%;
}
.nav.primary .nav-item {
font-size: 1.2rem;
font-weight: 700;
white-space: nowrap;
}
.nav.secondary {
justify-content: right;
@ -8385,6 +8411,52 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
font-size: 1rem;
}
@media screen and (max-width: 1024px) {
.page {
margin-top: var(--nav-primary-height);
}
.navs {
z-index: 100000;
position: fixed;
display: flex;
left: 0;
right: 0;
top: 0;
}
.navs .nav:first-child {
flex-grow: 1;
}
.navs .nav + .nav {
flew-grow: 0 !important;
}
.nav {
justify-content: space-between;
}
.nav .burger {
display: unset;
margin-left: auto;
}
.nav .nav-menu {
display: block;
position: absolute;
left: 0;
top: 100%;
width: 100%;
box-shadow: 0em 0.5em 0.5em rgba(0, 0, 0, 0.05);
}
.nav .nav-menu .nav-item {
display: block;
font-size: 1.4rem;
font-weight: 400;
}
.nav .nav-menu .nav-item:hover {
background-color: var(--highlight-color-2-alpha);
color: var(--highlight-color);
}
.nav .nav-menu:not(.active) {
display: none !important;
}
}
nav li {
list-style: none;
}
@ -8648,6 +8720,10 @@ preview-header:not(.no-cover) .preview-card-headings .heading {
}
}
@media screen and (max-width: 1024px) {
.container.header {
margin-right: 0 !important;
margin-left: 0 !important;
}
.page .container {
margin-left: 1.2rem;
margin-right: 1.2rem;
@ -8778,7 +8854,7 @@ preview-header:not(.no-cover) .preview-card-headings .heading {
height: 3.75em !important;
border-top: 1px hsl(0deg, 0%, 71%) solid;
background: var(--player-bar-bg);
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
}
.a-player-bar > * {
height: 100%;

View File

@ -169,6 +169,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _sou
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ASwitch.vue?vue&type=script&lang=js":
/*!*****************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ASwitch.vue?vue&type=script&lang=js ***!
\*****************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n props: {\n initialActive: {\n type: Boolean,\n default: null\n },\n el: {\n type: String,\n default: \"\"\n },\n label: {\n type: String,\n default: \"\"\n },\n icon: {\n type: String,\n default: \"fa fa-bars\"\n },\n ariaLabel: {\n type: String,\n default: \"\"\n },\n ariaDescription: {\n type: String,\n default: \"\"\n },\n activeClass: {\n type: String,\n default: \"active\"\n },\n /// switch toggle of all items of this group.\n group: {\n type: String,\n default: \"\"\n }\n },\n data() {\n return {\n active: this.initialActive\n };\n },\n computed: {\n groupClass() {\n return this.group && \"a-switch-\" + this.group || '';\n },\n buttonClass() {\n return [this.active && 'active' || '', this.groupClass];\n }\n },\n methods: {\n toggle() {\n this.set(!this.active);\n },\n set(active) {\n const el = document.querySelector(this.el);\n if (active) el.classList.add(this.activeClass);else el.classList.remove(this.activeClass);\n this.active = active;\n if (active) this.resetGroup();\n },\n resetGroup() {\n const els = document.querySelectorAll(\".\" + this.groupClass);\n for (var el of els) if (el != this.$el) el.__vnode.ctx.ctx.set(false);\n }\n },\n mounted() {\n if (this.initialActive !== null) this.set(this.initialActive);\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D");
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AActionButton.vue?vue&type=template&id=3f443389":
/*!***************************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/AActionButton.vue?vue&type=template&id=3f443389 ***!
@ -329,6 +339,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ASwitch.vue?vue&type=template&id=62a3c675":
/*!*********************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ASwitch.vue?vue&type=template&id=62a3c675 ***!
\*********************************************************************************************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ render: function() { return /* binding */ render; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\nconst _hoisted_1 = [\"title\", \"aria-label\", \"aria-description\"];\nconst _hoisted_2 = {\n class: \"icon\"\n};\nconst _hoisted_3 = {\n key: 0\n};\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"button\", {\n title: $props.ariaLabel,\n \"aria-label\": $props.ariaLabel || $props.label,\n \"aria-description\": $props.ariaDescription,\n onClick: _cache[0] || (_cache[0] = (...args) => $options.toggle && $options.toggle(...args)),\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($options.buttonClass)\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\", {\n active: $data.active\n }, () => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.icon)\n }, null, 2 /* CLASS */)]), $props.label ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"label\", _hoisted_3, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.label), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)])], 10 /* CLASS, PROPS */, _hoisted_1);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use%5B0%5D!./node_modules/vue-loader/dist/templateLoader.js??ruleSet%5B1%5D.rules%5B3%5D!./node_modules/vue-loader/dist/index.js??ruleSet%5B0%5D.use%5B0%5D");
/***/ }),
/***/ "./src/app.js":
/*!********************!*\
!*** ./src/app.js ***!
@ -345,7 +365,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\*********************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ admin: function() { return /* binding */ admin; },\n/* harmony export */ base: function() { return /* binding */ base; }\n/* harmony export */ });\n/* harmony import */ var _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AAutocomplete.vue */ \"./src/components/AAutocomplete.vue\");\n/* harmony import */ var _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ACarousel.vue */ \"./src/components/ACarousel.vue\");\n/* harmony import */ var _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ADropdown.vue */ \"./src/components/ADropdown.vue\");\n/* harmony import */ var _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/**\n * Core components\n */\nconst base = {\n AAutocomplete: _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"],\n ACarousel: _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n ADropdown: _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n AList: _AList_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n APage: _APage_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"],\n ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n ...base,\n AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_11__[\"default\"],\n AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_12__[\"default\"],\n APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"]\n};\n\n//# sourceURL=webpack://aircox-assets/./src/components/index.js?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ admin: function() { return /* binding */ admin; },\n/* harmony export */ base: function() { return /* binding */ base; }\n/* harmony export */ });\n/* harmony import */ var _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AAutocomplete.vue */ \"./src/components/AAutocomplete.vue\");\n/* harmony import */ var _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ACarousel.vue */ \"./src/components/ACarousel.vue\");\n/* harmony import */ var _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./ADropdown.vue */ \"./src/components/ADropdown.vue\");\n/* harmony import */ var _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _ASwitch_vue__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./ASwitch.vue */ \"./src/components/ASwitch.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n/**\n * Core components\n */\nconst base = {\n AAutocomplete: _AAutocomplete_vue__WEBPACK_IMPORTED_MODULE_0__[\"default\"],\n ACarousel: _ACarousel_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n ADropdown: _ADropdown_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n AList: _AList_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n APage: _APage_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"],\n ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"],\n ASwitch: _ASwitch_vue__WEBPACK_IMPORTED_MODULE_11__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n ...base,\n AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_12__[\"default\"],\n AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_13__[\"default\"],\n APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"]\n};\n\n//# sourceURL=webpack://aircox-assets/./src/components/index.js?");
/***/ }),
@ -619,6 +639,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ASt
/***/ }),
/***/ "./src/components/ASwitch.vue":
/*!************************************!*\
!*** ./src/components/ASwitch.vue ***!
\************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ASwitch_vue_vue_type_template_id_62a3c675__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ASwitch.vue?vue&type=template&id=62a3c675 */ \"./src/components/ASwitch.vue?vue&type=template&id=62a3c675\");\n/* harmony import */ var _ASwitch_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ASwitch.vue?vue&type=script&lang=js */ \"./src/components/ASwitch.vue?vue&type=script&lang=js\");\n/* harmony import */ var _media_data_code_projets_aircox_assets_node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./node_modules/vue-loader/dist/exportHelper.js */ \"./node_modules/vue-loader/dist/exportHelper.js\");\n\n\n\n\n;\nconst __exports__ = /*#__PURE__*/(0,_media_data_code_projets_aircox_assets_node_modules_vue_loader_dist_exportHelper_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(_ASwitch_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"], [['render',_ASwitch_vue_vue_type_template_id_62a3c675__WEBPACK_IMPORTED_MODULE_0__.render],['__file',\"src/components/ASwitch.vue\"]])\n/* hot reload */\nif (false) {}\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (__exports__);\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?");
/***/ }),
/***/ "./src/components/AActionButton.vue?vue&type=script&lang=js":
/*!******************************************************************!*\
!*** ./src/components/AActionButton.vue?vue&type=script&lang=js ***!
@ -779,6 +809,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "./src/components/ASwitch.vue?vue&type=script&lang=js":
/*!************************************************************!*\
!*** ./src/components/ASwitch.vue?vue&type=script&lang=js ***!
\************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": function() { return /* reexport safe */ _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ASwitch_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]; }\n/* harmony export */ });\n/* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ASwitch_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./ASwitch.vue?vue&type=script&lang=js */ \"./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ASwitch.vue?vue&type=script&lang=js\");\n \n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?");
/***/ }),
/***/ "./src/components/AActionButton.vue?vue&type=template&id=3f443389":
/*!************************************************************************!*\
!*** ./src/components/AActionButton.vue?vue&type=template&id=3f443389 ***!
@ -939,6 +979,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "./src/components/ASwitch.vue?vue&type=template&id=62a3c675":
/*!******************************************************************!*\
!*** ./src/components/ASwitch.vue?vue&type=template&id=62a3c675 ***!
\******************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ render: function() { return /* reexport safe */ _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_3_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ASwitch_vue_vue_type_template_id_62a3c675__WEBPACK_IMPORTED_MODULE_0__.render; }\n/* harmony export */ });\n/* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_40_use_0_node_modules_vue_loader_dist_templateLoader_js_ruleSet_1_rules_3_node_modules_vue_loader_dist_index_js_ruleSet_0_use_0_ASwitch_vue_vue_type_template_id_62a3c675__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!../../node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!../../node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./ASwitch.vue?vue&type=template&id=62a3c675 */ \"./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ASwitch.vue?vue&type=template&id=62a3c675\");\n\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASwitch.vue?");
/***/ }),
/***/ "./src/components/ACarousel.vue?vue&type=style&index=0&id=b79f173e&scoped=true&lang=css":
/*!**********************************************************************************************!*\
!*** ./src/components/ACarousel.vue?vue&type=style&index=0&id=b79f173e&scoped=true&lang=css ***!

View File

@ -48,13 +48,17 @@ Usefull context:
})
</script>
<div id="app">
<div class="container">
<div class="container navs">
{% block nav %}
<nav class="nav primary" role="navigation" aria-label="main navigation">
{% block nav-primary %}
<a class="nav-brand" href="{% url "home" %}">
<img src="{{ station.logo.url }}">
</a>
<a-switch class="button burger"
el=".nav.primary .nav-menu" group="nav"
aria-label="{% translate "Main menu" %}">
</a-switch>
<div class="nav-menu">
{% block nav-primary-menu %}
{% nav_items "top" css_class="nav-item" active_class="active" as items %}

View File

@ -49,41 +49,17 @@
{% endif %}
{% if last_podcasts %}
{% if podcasts %}
<section class="container">
<h2 class="title is-3 p-2">{% translate "Last podcasts" %}</h2>
<a-carousel>
{% for object in last_podcasts %}
{% page_widget "card" object open=True %}
{% endfor %}
</a-carousel>
<nav class="nav-urls">
<a href="{% url "podcast-list" %}"
aria-label="{% translate "Show all podcasts" %}">
{% translate "All podcasts" %}
</a>
</nav>
{% include "./widgets/carousel.html" with objects=podcasts url_name="podcast-list" url_label=_("All podcasts") %}
</section>
{% endif %}
{% if last_publications %}
{% if publications %}
<section class="container">
<h2 class="title is-3 p-2">{% translate "Last publications" %}</h2>
<div role="list">
{% for object in last_publications %}
{% page_widget "item" object open=True %}
{% endfor %}
</div>
<nav class="nav-urls">
<a href="{% url "page-list" %}"
aria-label="{% translate "Show all publications" %}">
{% translate "All publications" %}
</a>
</nav>
{% include "./widgets/carousel.html" with objects=publications url_name="page-list" url_label=_("All publications") %}
</section>
{% endif %}
{% endblock %}

View File

@ -3,12 +3,20 @@
{% load i18n aircox %}
{% block secondary-nav %}
{% if view.categories %}
<nav class="nav secondary">
{% for id, title in view.categories.items %}
<div class="nav-menu nav-categories">
{% for id, title in view.categories.items %}
<a class="nav-item{% if category_id == id or parent and parent.category_id == id %} active{% endif %}"
href="?category__id={{ id }}">{{ title }}</a>
{% endfor %}
{% endfor %}
</div>
<a-switch class="button burger"
el=".nav-categories" group="nav" icon="fas fa-tags"
aria-label="{% translate "Categories" %}">
</a-switch>
</nav>
{% endif %}
{% endblock %}
{% block header %}

View File

@ -6,21 +6,42 @@
{% block content-container %}
{% with schedules=program.schedule_set.all %}
{% if schedules %}
<section class="container schedules">
{% for schedule in schedules %}
<div class="schedule">
<div class="heading">
<span class="day">{{ schedule.get_frequency_display }}</span>
{% with schedule.start|date:"H:i" as start %}
{% with schedule.end|date:"H:i" as end %}
<time datetime="{{ start }}">{{ start }}</time>
&mdash;
<time datetime="{{ end }}">{{ end }}</time>
{% endwith %}
{% endwith %}
<small>
{% if schedule.initial %}
{% with schedule.initial.date as date %}
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
({% translate "Rerun" %})
</span>
{% endwith %}
{% endif %}
</small>
</div>
</div>
{% endfor %}
</section>
{% endif %}
{% endwith %}
{{ block.super }}
{% if episodes %}
<section class="container">
<h3 class="title is-3">{% translate "Last Episodes" %}</h3>
<a-carousel section-class="card-grid">
{% for object in episodes %}
{% page_widget "card" object %}
{% endfor %}
</a-carousel>
<nav class="nav-urls">
<a href="{% url "episode-list" parent_slug=program.slug %}">
{% translate "See more" %}
</a>
</nav>
{% include "./widgets/carousel.html" with objects=episodes url_name="episode-list" url_parent=object url_label=_("All episodes") %}
</section>
{% endif %}
@ -28,47 +49,8 @@
{% if articles %}
<section class="container">
<h3 class="title is-3">{% translate "Last Articles" %}</h3>
<a-carousel section-class="card-grid">
{% for object in articles %}
{% page_widget "card" object %}
{% endfor %}
</a-carousel>
<nav class="nav-urls">
<a href="{% url "article-list" parent_slug=program.slug %}"
aria-label="{% translate "Show all program's articles" %}">
{% translate "See more" %}
</a>
</nav>
{% include "./widgets/carousel.html" with objects=articles url_name="article-list" url_parent=object url_label=_("All articles") %}
</section>
{% endif %}
{% endblock %}
{% block sidebar %}
<section>
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
{% for schedule in program.schedule_set.all %}
{{ schedule.get_frequency_display }}
{% with schedule.start|date:"H:i" as start %}
{% with schedule.end|date:"H:i" as end %}
<time datetime="{{ start }}">{{ start }}</time>
&mdash;
<time datetime="{{ end }}">{{ end }}</time>
{% endwith %}
{% endwith %}
<small>
{% if schedule.initial %}
{% with schedule.initial.date as date %}
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
({% translate "Rerun" %})
</span>
{% endwith %}
{% endif %}
</small>
<br>
{% endfor %}
</section>
{{ block.super }}
{% endblock %}

View File

@ -1,7 +1,26 @@
{% load aircox %}
{% comment %}
Context:
- objects: list of objects to display
- url_name: url name to show the full list
- url_parent: parent page for the full list
- url_label: label of url button
{% endcomment %}
<div class="card-grid">
{% for object in object_list %}
{% page_widget "card" object.episode diffusion=object %}
{% endfor %}
</div>
<a-carousel section-class="card-grid">
{% for object in objects %}
{% page_widget "card" object %}
{% endfor %}
</a-carousel>
{% if url_name %}
<nav class="nav-urls">
{% if url_parent %}
<a href="{% url url_name parent_slug=program.slug %}">
{% else %}
<a href="{% url url_name %}">
{% endif %}
{{ url_label|default:_("Show all") }}
</a>
</nav>
{% endif %}

View File

@ -11,26 +11,33 @@ An empty date results to a title or a separator
{% endcomment %}
{% load i18n %}
{% for day in dates %}
<a href="{% url url_name date=day %}" class="nav-item {% if day == date %}active{% endif %}">
{{ day|date:"l d" }}
</a>
{% endfor %}
<a-switch class="button burger"
el=".nav-dates" icon="far fa-calendar" group="nav"
aria-label="{% translate "Dates" %}">
</a-switch>
<a-dropdown class="nav-item align-right flex-grow-0 dropdown is-right"
content-class="dropdown-menu"
button-tag="span" button-class="dropdown-trigger"
button-icon-open="fa-solid fa-plus" button-icon-close="fa-solid fa-minus">
<template #default>
<div class="dropdown-content">
<div class="dropdown-item">
<h4>{% translate "Pick a date" %}</h4>
<v-calendar mode="date" borderless
:initial-page="{month: {{date.month}}, year: {{date.year}}}"
@dayclick="(event) => window.aircox.pickDate({% url url_name %}, event)"
color="yellow"
/>
<div class="nav-menu nav-dates">
{% for day in dates %}
<a href="{% url url_name date=day %}" class="nav-item {% if day == date %}active{% endif %}">
{{ day|date:"l d" }}
</a>
{% endfor %}
<a-dropdown class="nav-item align-right flex-grow-0 dropdown is-right"
content-class="dropdown-menu"
button-tag="span" button-class="dropdown-trigger"
button-icon-open="fa-solid fa-plus" button-icon-close="fa-solid fa-minus">
<template #default>
<div class="dropdown-content">
<div class="dropdown-item">
<h4>{% translate "Pick a date" %}</h4>
<v-calendar mode="date" borderless
:initial-page="{month: {{date.month}}, year: {{date.year}}}"
@dayclick="(event) => window.aircox.pickDate({% url url_name %}, event)"
color="yellow"
/>
</div>
</div>
</div>
</template>
</a-dropdown>
</template>
</a-dropdown>
</div>

View File

@ -1,6 +1,5 @@
import pytest
from django.urls import reverse
from aircox import models
from aircox.test import Interface
@ -37,26 +36,13 @@ class TestBaseView:
def test_station(self, base_view, station):
assert base_view.station == station
@pytest.mark.django_db
def test_get_sidebar_queryset(self, base_view, pages, published_pages):
query = base_view.get_sidebar_queryset().values_list("id", flat=True)
page_ids = {r.id for r in published_pages}
assert set(query) == page_ids
@pytest.mark.django_db
def test_get_sidebar_url(self, base_view):
assert base_view.get_sidebar_url() == reverse("page-list")
@pytest.mark.django_db
def test_get_context_data(self, base_view, station, published_pages):
base_view.has_sidebar = True
base_view.get_sidebar_queryset = lambda: published_pages
context = base_view.get_context_data()
assert context == {
"view": base_view,
"station": station,
"page": None, # get_page() returns None
"has_sidebar": base_view.has_sidebar,
"audio_streams": station.streams,
"model": base_view.model,
}

View File

@ -57,6 +57,7 @@ urls = [
name="episode-detail",
),
path(_("podcasts/"), views.PodcastListView.as_view(), name="podcast-list"),
path(_("podcasts/<slug:parent_slug>"), views.PodcastListView.as_view(), name="podcast-list"),
path(_("week/"), views.DiffusionListView.as_view(), name="diffusion-list"),
path(
_("week/<date:date>/"),

View File

@ -42,7 +42,7 @@ class HomeView(AttachedToMixin, BaseView, ListView):
diffs = next_diffs[: self.related_carousel_count]
return diffs
def get_last_publications(self):
def get_publications(self):
# note: with postgres db, possible to use distinct()
qs = self.publications_queryset.all()
parents = set()
@ -56,7 +56,7 @@ class HomeView(AttachedToMixin, BaseView, ListView):
break
return items
def get_last_podcasts(self):
def get_podcasts(self):
return self.podcasts_queryset.all()[: self.related_carousel_count]
def get_context_data(self, **kwargs):
@ -69,8 +69,8 @@ class HomeView(AttachedToMixin, BaseView, ListView):
"diffusion": current_diff,
"logs": self.get_logs(self.object_list),
"next_diffs": next_diffs,
"last_publications": self.get_last_publications(),
"last_podcasts": self.get_last_podcasts(),
"publications": self.get_publications(),
"podcasts": self.get_podcasts(),
}
)
return super().get_context_data(**kwargs)

View File

@ -39,9 +39,15 @@ class ProgramDetailView(BaseProgramMixin, PageDetailView):
return reverse("program-list") + f"?category__id={self.object.category_id}"
def get_context_data(self, **kwargs):
episodes = Episode.objects.program(self.object).published().order_by("-pub_date")[: self.related_count]
articles = Article.objects.parent(self.object).published().order_by("-pub_date")[: self.related_count]
return super().get_context_data(articles=articles, episodes=episodes, **kwargs)
episodes = Episode.objects.program(self.object).published().order_by("-pub_date")
podcasts = episodes.with_podcasts()
articles = Article.objects.parent(self.object).published().order_by("-pub_date")
return super().get_context_data(
articles=articles[: self.related_count],
episodes=episodes[: self.related_count],
podcasts=podcasts[: self.related_count],
**kwargs,
)
class ProgramListView(PageListView):

View File

@ -91,9 +91,9 @@
<div class="column is-two-fifths">
<h6 class="subtitle is-6 is-marginless">Metadata</h6>
<table class="table has-background-transparent">
<table class="table bg-transparent">
<tbody>
<tr><th class="has-text-right has-text-nowrap">
<tr><th class="has-text-right ws-nowrap">
{% translate "Status" %}
</th>
<td :class="{'has-text-danger': source.isPlaying, 'has-text-warning': source.isPaused}">
@ -103,7 +103,7 @@
</td>
</tr>
<tr v-if="source.data.air_time">
<th class="has-text-right has-text-nowrap">
<th class="has-text-right ws-nowrap">
{% translate "Air time" %}
</th><td>
<span class="far fa-clock"></span>
@ -113,7 +113,7 @@
</time>
</td>
<tr v-if="source.remaining">
<th class="has-text-right has-text-nowrap">
<th class="has-text-right ws-nowrap">
{% translate "Time left" %}
</th><td>
<span class="far fa-hourglass"></span>
@ -121,7 +121,7 @@
</td>
</tr>
<tr v-if="source.data.uri">
<th class="has-text-right has-text-nowrap">
<th class="has-text-right ws-nowrap">
{% translate "Data source" %}
</th><td>
<span class="far fa-play-circle"></span>

View File

@ -59,41 +59,6 @@ $screen-wide: 1380px;
//-- helpers/modifiers
.is-fullwidth { width: 100%; }
.is-fullheight { height: 100%; }
.is-fixed-bottom {
position: fixed;
bottom: 0;
margin-bottom: 0px;
border-radius: 0;
}
.is-borderless { border: none; }
.has-text-nowrap {
white-space: nowrap;
}
.has-background-transparent {
background-color: transparent;
}
.is-opacity-light {
opacity: 0.7;
&:hover {
opacity: 1;
}
}
.float-right { float: right }
.float-left { float: left }
.overflow-hidden { overflow: hidden }
.overflow-hidden.is-fullwidth { max-width: 100%; }
*[draggable="true"] {
cursor: move;
}
//-- forms
input.half-field:not(:active):not(:hover) {
border: none;
@ -102,21 +67,6 @@ input.half-field:not(:active):not(:hover) {
}
//-- animations
@keyframes blink {
from { opacity: 1; }
to { opacity: 0.4; }
}
.blink {
animation: 1s ease-in-out 2s infinite alternate blink;
}
.loading {
animation: 1s ease-in-out 3s infinite alternate blink;
}
//-- navbar
.navbar.has-shadow, .navbar.is-fixed-bottom.has-shadow {
box-shadow: 0em 0em 1em rgba(0,0,0,0.1);
@ -178,6 +128,9 @@ a.navbar-item.is-active {
--highlight-color-2-alpha: rgb(0, 0, 254, 0.7);
--highlight-color-2-grey: rgba(50, 200, 200, 1);
--nav-primary-height: 4rem;
--nav-secondary-height: 3rem;
--header-height: 30rem;
--heading-height: 30rem;
@ -245,30 +198,65 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
// ---- helpers
*[draggable="true"] {
cursor: move;
}
//-- animations
@keyframes blink {
from { opacity: 1; }
to { opacity: 0.4; }
}
.blink { animation: 1s ease-in-out 3s infinite alternate blink; }
.loading { animation: 1s ease-in-out 1s infinite alternate blink; }
// -- layout
.d-inline { display: inline; }
.d-block { display: block; }
.d-inline-block { display: inline-block; }
.p-relative { position: relative }
.p-absolute { position: absolute }
.p-fixed { position: fixed }
.p-sticky { position: sticky }
.p-static { position: static }
.align-left { text-align: left; justify-content: left; }
.align-right { text-align: right; justify-content: right; }
.height-full { height: 100%; }
.d-inline { display: inline !important; }
.d-block { display: block !important; }
.d-inline-block { display: inline-block !important; }
.flex-push-right { margin-left: auto; }
.flex-grow-0 { flex-grow: 0 !important; }
.float-right { float: right }
.float-left { float: left }
.is-fullwidth { width: 100%; }
.is-fullheight { height: 100%; }
.is-fixed-bottom {
position: fixed;
bottom: 0;
margin-bottom: 0px;
border-radius: 0;
}
.is-borderless { border: none; }
.overflow-hidden { overflow: hidden }
.overflow-hidden.is-fullwidth { max-width: 100%; }
.p-relative { position: relative !important }
.p-absolute { position: absolute !important }
.p-fixed { position: fixed !important }
.p-sticky { position: sticky !important }
.p-static { position: static !important }
.height-full { height: 100%; }
.ws-nowrap { white-space: nowrap; }
.no-border { border: 0px !important; }
// -- colors
.highlight-color { color: var(--highlight-color); }
.highlight-color-2 { color: var(--highlight-color-2); }
.bg-transparent { background-color: transparent; }
.is-success {
background-color: $green !important;
border-color: $green-dark !important;
@ -288,6 +276,19 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
}
.schedules {
margin-top: calc(0rem - $mp-3) !important;
padding-top: 0;
}
.schedule {
.day {
font-weight: $weight-bold;
margin-right: $mp-3;
}
}
// -- buttons, forms
.button, a.button, button.button, .nav-urls a {
display: inline-block;
@ -302,8 +303,10 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
.icon {
vertical-align: middle;
&:first-child { margin-right: $mp-3; }
&:last-child { margin-left: $mp-3 }
&:not(:only-child) {
&:first-child { margin-right: $mp-3; }
&:last-child { margin-left: $mp-3 }
}
}
@ -341,8 +344,9 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
}
.button-group {
.button-group, .nav {
.button {
border-radius: 0px;
background-color: transparent;
border-top: 0px;
border-bottom: 0px;
@ -384,6 +388,9 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
}
.label, .textarea, .input, .select {
font-size: $text-size-medium;
}
// -- headings
.title {
@ -424,6 +431,8 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
display: none;
}
.burger { display: none; }
.nav-item {
padding: $mp-3;
flex-grow: 1;
@ -433,31 +442,45 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
font-family: var(--heading-font-family);
text-transform: uppercase;
a, .button {
display: block;
width: 100%;
}
&.active {
background-color: var(--highlight-color-2);
color: var(--highlight-color);
}
}
&.primary {
.nav-brand {
display: inline-block;
margin-right: $mp-3;
padding: $mp-3;
.nav-menu {
display: flex;
flex-grow: 1;
background-color: var(--highlight-color);
}
img {
width: 12rem !important;
}
}
&.primary {
height: var(--nav-primary-height);
.nav-menu {
display: flex;
flex-grow: 1;
}
.nav-brand {
display: inline-block;
padding: $mp-3;
flex-grow: 0;
flex-shrink: 1;
img {
height: 100%;
}
}
.nav-item {
font-size: $text-size-2;
font-weight: $weight-bold;
white-space: nowrap;
}
}
@ -470,6 +493,64 @@ h1, h2, h3, h4, h5, h6, .heading, .title, .subtitle {
}
}
@media screen and (max-width: $screen-normal) {
.page {
margin-top: var(--nav-primary-height);
}
.navs {
z-index: 100000;
position: fixed;
display: flex;
left: 0;
right: 0;
top: 0;
.nav:first-child {
flex-grow: 1;
}
.nav + .nav {
flew-grow: 0 !important;
}
}
.nav {
justify-content: space-between;
.burger {
display: unset;
margin-left: auto;
}
.nav-menu {
display: block;
position: absolute;
left: 0;
top: 100%;
width: 100%;
box-shadow: 0em 0.5em 0.5em rgba(0,0,0,0.05);
.nav-item {
display: block;
font-size: $text-size-medium;
font-weight: $weight-normal;
&:hover {
background-color: var(--highlight-color-2-alpha);
color: var(--highlight-color);
}
}
}
.nav-menu:not(.active) {
display: none !important
}
}
}
nav li {
list-style: none;
@ -792,6 +873,11 @@ nav li {
}
@media screen and (max-width: $screen-normal) {
.container.header {
margin-right: 0 !important;
margin-left: 0 !important;
}
.page .container {
margin-left: $mp-4;
margin-right: $mp-4;
@ -953,7 +1039,7 @@ nav li {
border-top: 1px $grey-light solid;
background: var(--player-bar-bg);
box-shadow: 0em 1.5em 2.5em rgba(0, 0, 0, 0.6);
box-shadow: 0em -0.5em 0.5em rgba(0, 0, 0, 0.05);
> * { height: 100%; }

View File

@ -0,0 +1,75 @@
<template>
<button :title="ariaLabel"
:aria-label="ariaLabel || label" :aria-description="ariaDescription"
@click="toggle" :class="buttonClass">
<slot name="default" :active="active">
<span class="icon">
<i :class="icon"></i>
</span>
<label v-if="label">{{ label }}</label>
</slot>
</button>
</template>
<script>
export default {
props: {
initialActive: {type: Boolean, default: null},
el: {type: String, default: ""},
label: {type: String, default: ""},
icon: {type: String, default: "fa fa-bars"},
ariaLabel: {type: String, default: ""},
ariaDescription: {type: String, default: ""},
activeClass: {type: String, default:"active"},
/// switch toggle of all items of this group.
group: {type: String, default: ""},
},
data() {
return {
active: this.initialActive,
}
},
computed: {
groupClass() {
return this.group && "a-switch-" + this.group || ''
},
buttonClass() {
return [
this.active && 'active' || '',
this.groupClass
]
}
},
methods: {
toggle() {
this.set(!this.active)
},
set(active) {
const el = document.querySelector(this.el)
if(active)
el.classList.add(this.activeClass)
else
el.classList.remove(this.activeClass)
this.active = active
if(active)
this.resetGroup()
},
resetGroup() {
const els = document.querySelectorAll("." + this.groupClass)
for(var el of els)
if(el != this.$el)
el.__vnode.ctx.ctx.set(false)
},
},
mounted() {
if(this.initialActive !== null)
this.set(this.initialActive)
},
}
</script>

View File

@ -9,6 +9,7 @@ import APlaylist from './APlaylist.vue'
import APlaylistEditor from './APlaylistEditor.vue'
import AProgress from './AProgress.vue'
import ASoundItem from './ASoundItem.vue'
import ASwitch from './ASwitch.vue'
import AStatistics from './AStatistics.vue'
import AStreamer from './AStreamer.vue'
@ -17,7 +18,7 @@ import AStreamer from './AStreamer.vue'
*/
export const base = {
AAutocomplete, ACarousel, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
AProgress, ASoundItem,
AProgress, ASoundItem, ASwitch
}
export default base