#132 | #121: backoffice / dev-1.0-121 #131

Merged
thomas merged 151 commits from dev-1.0-121 into develop-1.0 2024-04-28 20:02:14 +00:00
50 changed files with 1934 additions and 669 deletions
Showing only changes of commit 1661601caf - Show all commits

View File

@ -30,9 +30,9 @@ class DiffusionAdmin(DiffusionBaseAdmin, admin.ModelAdmin):
end_date.short_description = _("end")
list_display = ("episode", "start_date", "end_date", "type", "initial")
list_display = ("episode", "start", "end", "type", "initial")
list_filter = ("type", "start", "program")
list_editable = ("type",)
list_editable = ("type", "start", "end")
ordering = ("-start", "id")
fields = ("type", "start", "end", "initial", "program", "schedule")

View File

@ -137,8 +137,6 @@ class Diffusion(Rerun):
# help_text = _('use this input port'),
# )
item_template_name = "aircox/widgets/diffusion_item.html"
class Meta:
verbose_name = _("Diffusion")
verbose_name_plural = _("Diffusions")
@ -208,6 +206,11 @@ class Diffusion(Rerun):
and self.end >= now
)
@property
def is_today(self):
"""True if diffusion is currently today."""
return self.start.date() == datetime.date.today()
@property
def is_live(self):
"""True if Diffusion is live (False if there are sounds files)."""

View File

@ -13,7 +13,9 @@ __all__ = ("Episode",)
class Episode(Page):
objects = ProgramChildQuerySet.as_manager()
detail_url_name = "episode-detail"
template_prefix = "episode"
item_template_name = "aircox/widgets/episode_item.html"
card_template_name = "aircox/widgets/episode_card.html"
@property
def program(self):

View File

@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _
from .diffusion import Diffusion
from .sound import Sound, Track
from .station import Station
from .page import Renderable
logger = logging.getLogger("aircox")
@ -54,13 +55,15 @@ class LogQuerySet(models.QuerySet):
return self.filter(track__isnull=not with_it)
class Log(models.Model):
class Log(Renderable, models.Model):
"""Log sounds and diffusions that are played on the station.
This only remember what has been played on the outputs, not on each
source; Source designate here which source is responsible of that.
"""
template_prefix = "log"
TYPE_STOP = 0x00
"""Source has been stopped, e.g. manually."""
# Rule: \/ diffusion != null \/ sound != null

View File

@ -16,6 +16,7 @@ from model_utils.managers import InheritanceQuerySet
from .station import Station
__all__ = (
"Renderable",
"Category",
"PageQuerySet",
"Page",
@ -30,6 +31,17 @@ headline_re = re.compile(
)
class Renderable:
template_prefix = "page"
template_name = "aircox/widgets/{prefix}.html"
def get_template_name(self, widget):
"""Return template name for the provided widget."""
return self.template_name.format(
prefix=self.template_prefix, widget=widget
)
class Category(models.Model):
title = models.CharField(_("title"), max_length=64)
slug = models.SlugField(_("slug"), max_length=64, db_index=True)
@ -68,7 +80,7 @@ class BasePageQuerySet(InheritanceQuerySet):
return self.filter(title__icontains=q)
class BasePage(models.Model):
class BasePage(Renderable, models.Model):
"""Base class for publishable content."""
STATUS_DRAFT = 0x00
@ -112,7 +124,6 @@ class BasePage(models.Model):
objects = BasePageQuerySet.as_manager()
detail_url_name = None
item_template_name = "aircox/widgets/page_item.html"
class Meta:
abstract = True
@ -152,14 +163,14 @@ class BasePage(models.Model):
@property
def display_title(self):
if self.is_published():
if self.is_published:
return self.title
return self.parent.display_title()
return self.parent and self.parent.display_title or ""
@cached_property
def headline(self):
if not self.content:
return ""
def display_headline(self):
if not self.content or not self.is_published:
return self.parent and self.parent.display_headline or ""
content = bleach.clean(self.content, tags=[], strip=True)
headline = headline_re.search(content)
return mark_safe(headline.groupdict()["headline"]) if headline else ""

View File

@ -43,6 +43,7 @@ def user_default_groups(sender, instance, created, *args, **kwargs):
@receiver(signals.post_save, sender=Page)
def page_post_save(sender, instance, created, *args, **kwargs):
return
if not created and instance.cover:
Page.objects.filter(parent=instance, cover__isnull=True).update(
cover=instance.cover
@ -67,6 +68,7 @@ def program_post_save(sender, instance, created, *args, **kwargs):
@receiver(signals.pre_save, sender=Schedule)
def schedule_pre_save(sender, instance, *args, **kwargs):
return
if getattr(instance, "pk") is not None:
instance._initial = Schedule.objects.get(pk=instance.pk)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -29,6 +29,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _mod
/***/ }),
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/components/ADropdown.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/ADropdown.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 data() {\n return {\n \"active\": this.open\n };\n },\n props: {\n itemClass: String,\n buttonClass: String,\n buttonIconOpen: {\n type: String,\n default: \"fa fa-angle-down\"\n },\n buttonIconClose: {\n type: String,\n default: \"fa fa-angle-up\"\n },\n contentClass: String,\n open: {\n type: Boolean,\n default: false\n },\n noButton: {\n type: Boolean,\n default: false\n }\n },\n methods: {\n toggle() {\n this.active = !this.active;\n }\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.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/index.js??ruleSet[0].use[0]!./src/components/AEpisode.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/AEpisode.vue?vue&type=script&lang=js ***!
@ -169,6 +179,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/ADropdown.vue?vue&type=template&id=a87ed2dc":
/*!***********************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./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/ADropdown.vue?vue&type=template&id=a87ed2dc ***!
\***********************************************************************************************************************************************************************************************************************************************************************/
/***/ (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 = {\n class: \"icon\"\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)(\"div\", null, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.itemClass),\n onClick: _cache[1] || (_cache[1] = $event => $props.noButton && $options.toggle())\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"button\", {}, () => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['float-right', $props.buttonClass]),\n onClick: _cache[0] || (_cache[0] = $event => $options.toggle())\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", _hoisted_1, [!$data.active ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"i\", {\n key: 0,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.buttonIconOpen)\n }, null, 2 /* CLASS */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), $data.active ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"i\", {\n key: 1,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.buttonIconClose)\n }, null, 2 /* CLASS */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)])], 2 /* CLASS */)]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"item\")], 2 /* CLASS */), $data.active ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", {\n key: 0,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.contentClass)\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\")], 2 /* CLASS */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.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");
/***/ }),
/***/ "./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/AEpisode.vue?vue&type=template&id=2e4db98a":
/*!**********************************************************************************************************************************************************************************************************************************************************************!*\
!*** ./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/AEpisode.vue?vue&type=template&id=2e4db98a ***!
@ -295,7 +315,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 */ \"PlayerApp\": function() { return /* binding */ PlayerApp; }\n/* harmony export */ });\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n\nconst App = {\n el: '#app',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n computed: {\n player() {\n return window.aircox.player;\n }\n }\n};\nconst PlayerApp = {\n el: '#player',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (App);\n\n//# sourceURL=webpack://aircox-assets/./src/app.js?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"PlayerApp\": function() { return /* binding */ PlayerApp; }\n/* harmony export */ });\n/* harmony import */ var vue3_carousel_dist_carousel_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue3-carousel/dist/carousel.css */ \"./node_modules/vue3-carousel/dist/carousel.css\");\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n/* harmony import */ var vue3_carousel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! vue3-carousel */ \"./node_modules/vue3-carousel/dist/carousel.es.js\");\n\n\n\nconst App = {\n el: '#app',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n Slide: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Slide,\n Carousel: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Carousel,\n Pagination: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Pagination,\n Navigation: vue3_carousel__WEBPACK_IMPORTED_MODULE_2__.Navigation\n },\n computed: {\n player() {\n return window.aircox.player;\n }\n },\n data() {\n return {\n carouselBreakpoints: {\n 400: {\n itemsToShow: 1\n },\n 600: {\n itemsToShow: 1.75\n },\n 800: {\n itemsToShow: 3\n },\n 1024: {\n itemsToShow: 4\n },\n 1280: {\n itemsToShow: 4\n },\n 1380: {\n itemsToShow: 5\n }\n }\n };\n }\n};\nconst PlayerApp = {\n el: '#player',\n delimiters: ['[[', ']]'],\n components: {\n ..._components__WEBPACK_IMPORTED_MODULE_1__[\"default\"]\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (App);\n\n//# sourceURL=webpack://aircox-assets/./src/app.js?");
/***/ }),
@ -305,7 +325,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 */ \"default\": function() { return /* binding */ Builder; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\n\n/**\n * Utility class used to handle Vue applications. It provides way to load\n * remote application and update history.\n */\nclass Builder {\n constructor(config = {}) {\n this.config = config;\n this.title = null;\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Fetch app from remote and mount application.\n */\n fetch(url, {\n el = '#app',\n ...options\n } = {}) {\n return fetch(url, options).then(response => response.text()).then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.querySelector(el);\n content = app ? app.innerHTML : content;\n return this.mount({\n content,\n title: doc.title,\n reset: true,\n url\n });\n });\n }\n\n /**\n * Mount application, using `create_app` if required.\n *\n * @param {String} options.content: replace app container content with it\n * @param {String} options.title: set DOM document title.\n * @param {String} [options.el=this.config.el]: mount application on this element (querySelector argument)\n * @param {Boolean} [reset=False]: if True, force application recreation.\n * @return `app.mount`'s result.\n */\n mount({\n content = null,\n title = null,\n el = null,\n reset = false,\n props = null\n } = {}) {\n try {\n this.unmount();\n let config = this.config;\n if (el === null) el = config.el;\n if (reset || !this.app) this.app = this.createApp({\n title,\n content,\n el,\n ...config\n }, props);\n this.vm = this.app.mount(el);\n window.scroll(0, 0);\n return this.vm;\n } catch (error) {\n this.unmount();\n throw error;\n }\n }\n createApp({\n el,\n title = null,\n content = null,\n ...config\n }, props) {\n const container = document.querySelector(el);\n if (!container) return;\n if (content) container.innerHTML = content;\n if (title) document.title = title;\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.createApp)(config, props);\n }\n unmount() {\n this.app && this.app.unmount();\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Enable hot reload: catch page change in order to fetch them and\n * load page without actually leaving current one.\n */\n enableHotReload(node = null, historySave = true) {\n if (historySave) this.historySave(document.location, true);\n node.addEventListener('click', event => this._onPageChange(event), true);\n node.addEventListener('submit', event => this._onPageChange(event), true);\n node.addEventListener('popstate', event => this._onPopState(event), true);\n }\n _onPageChange(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A' ? event.target : event.target.closest('a');\n if (!target || target.hasAttribute('target')) return;\n let url = submit ? target.getAttribute('action') || '' : target.getAttribute('href');\n if (url === null || !(url === '' || url.startsWith('/') || url.startsWith('?'))) return;\n let options = {};\n if (submit) {\n let formData = new FormData(event.target);\n if (target.method == 'get') url += '?' + new URLSearchParams(formData).toString();else options = {\n ...options,\n method: target.method,\n body: formData\n };\n }\n this.fetch(url, options).then(() => this.historySave(url));\n event.preventDefault();\n event.stopPropagation();\n }\n _onPopState(event) {\n if (event.state && event.state.content)\n // document.title = this.title;\n this.historyLoad(event.state);\n }\n\n /// Save application state into browser history\n historySave(url, replace = false) {\n const el = document.querySelector(this.config.el);\n const state = {\n content: el.innerHTML,\n title: document.title\n };\n if (replace) history.replaceState(state, '', url);else history.pushState(state, '', url);\n }\n\n /// Load application from browser history's state\n historyLoad(state) {\n return this.mount({\n content: state.content,\n title: state.title\n });\n }\n}\n\n//# sourceURL=webpack://aircox-assets/./src/appBuilder.js?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": function() { return /* binding */ Builder; }\n/* harmony export */ });\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\n\n/**\n * Utility class used to handle Vue applications. It provides way to load\n * remote application and update history.\n */\nclass Builder {\n constructor(config = {}) {\n this.config = config;\n this.title = null;\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Fetch app from remote and mount application.\n */\n fetch(url, {\n el = '#app',\n ...options\n } = {}) {\n return fetch(url, options).then(response => response.text()).then(content => {\n let doc = new DOMParser().parseFromString(content, 'text/html');\n let app = doc.querySelector(el);\n content = app ? app.innerHTML : content;\n return this.mount({\n content,\n title: doc.title,\n reset: true,\n url\n });\n });\n }\n\n /**\n * Mount application, using `create_app` if required.\n *\n * @param {String} options.content: replace app container content with it\n * @param {String} options.title: set DOM document title.\n * @param {String} [options.el=this.config.el]: mount application on this element (querySelector argument)\n * @param {Boolean} [reset=False]: if True, force application recreation.\n * @return `app.mount`'s result.\n */\n mount({\n content = null,\n title = null,\n el = null,\n reset = false,\n props = null\n } = {}) {\n try {\n this.unmount();\n let config = this.config;\n if (el === null) el = config.el;\n if (reset || !this.app) this.app = this.createApp({\n title,\n content,\n el,\n ...config\n }, props);\n this.vm = this.app.mount(el);\n window.scroll(0, 0);\n return this.vm;\n } catch (error) {\n this.unmount();\n throw error;\n }\n }\n createApp({\n el,\n title = null,\n content = null,\n ...config\n }, props) {\n const container = document.querySelector(el);\n if (!container) return;\n if (content) container.innerHTML = content;\n if (title) document.title = title;\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.createApp)(config, props);\n }\n unmount() {\n this.app && this.app.unmount();\n this.app = null;\n this.vm = null;\n }\n\n /**\n * Enable hot reload: catch page change in order to fetch them and\n * load page without actually leaving current one.\n */\n enableHotReload(node = null, historySave = true) {\n if (historySave) this.historySave(document.location, true);\n node.addEventListener('click', event => this.pageChanged(event), true);\n node.addEventListener('submit', event => this.pageChanged(event), true);\n node.addEventListener('popstate', event => this.statePopped(event), true);\n }\n pageChanged(event) {\n let submit = event.type == 'submit';\n let target = submit || event.target.tagName == 'A' ? event.target : event.target.closest('a');\n if (!target || target.hasAttribute('target')) return;\n let url = submit ? target.getAttribute('action') || '' : target.getAttribute('href');\n let domain = window.location.protocol + '//' + window.location.hostname;\n let stay = (url === '' || url.startsWith('/') || url.startsWith('?') || url.startsWith(domain)) && url.indexOf('wp-admin') == -1;\n if (url === null || !stay) {\n return;\n }\n let options = {};\n if (submit) {\n let formData = new FormData(event.target);\n if (target.method == 'get') url += '?' + new URLSearchParams(formData).toString();else options = {\n ...options,\n method: target.method,\n body: formData\n };\n }\n this.fetch(url, options).then(() => this.historySave(url));\n event.preventDefault();\n event.stopPropagation();\n }\n statePopped(event) {\n const state = event.state;\n if (state && state.content)\n // document.title = this.title;\n this.historyLoad(state);\n }\n\n /// Save application state into browser history\n historySave(url, replace = false) {\n const el = document.querySelector(this.config.el);\n const state = {\n content: el.innerHTML,\n title: document.title\n };\n if (replace) history.replaceState(state, '', url);else history.pushState(state, '', url);\n }\n\n /// Load application from browser history's state\n historyLoad(state) {\n return this.mount({\n content: state.content,\n title: state.title\n });\n }\n}\n\n//# sourceURL=webpack://aircox-assets/./src/appBuilder.js?");
/***/ }),
@ -315,7 +335,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 _AEpisode_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\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 AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n AList: _AList_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n APage: _APage_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"],\n ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n ...base,\n AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"],\n AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"],\n APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_6__[\"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 _ADropdown_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ADropdown.vue */ \"./src/components/ADropdown.vue\");\n/* harmony import */ var _AEpisode_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AEpisode.vue */ \"./src/components/AEpisode.vue\");\n/* harmony import */ var _AList_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AList.vue */ \"./src/components/AList.vue\");\n/* harmony import */ var _APage_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./APage.vue */ \"./src/components/APage.vue\");\n/* harmony import */ var _APlayer_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./APlayer.vue */ \"./src/components/APlayer.vue\");\n/* harmony import */ var _APlaylist_vue__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./APlaylist.vue */ \"./src/components/APlaylist.vue\");\n/* harmony import */ var _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./APlaylistEditor.vue */ \"./src/components/APlaylistEditor.vue\");\n/* harmony import */ var _AProgress_vue__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! ./AProgress.vue */ \"./src/components/AProgress.vue\");\n/* harmony import */ var _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./ASoundItem.vue */ \"./src/components/ASoundItem.vue\");\n/* harmony import */ var _AStatistics_vue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./AStatistics.vue */ \"./src/components/AStatistics.vue\");\n/* harmony import */ var _AStreamer_vue__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./AStreamer.vue */ \"./src/components/AStreamer.vue\");\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 ADropdown: _ADropdown_vue__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n AEpisode: _AEpisode_vue__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n AList: _AList_vue__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n APage: _APage_vue__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n APlayer: _APlayer_vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n APlaylist: _APlaylist_vue__WEBPACK_IMPORTED_MODULE_6__[\"default\"],\n AProgress: _AProgress_vue__WEBPACK_IMPORTED_MODULE_8__[\"default\"],\n ASoundItem: _ASoundItem_vue__WEBPACK_IMPORTED_MODULE_9__[\"default\"]\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (base);\nconst admin = {\n ...base,\n AStatistics: _AStatistics_vue__WEBPACK_IMPORTED_MODULE_10__[\"default\"],\n AStreamer: _AStreamer_vue__WEBPACK_IMPORTED_MODULE_11__[\"default\"],\n APlaylistEditor: _APlaylistEditor_vue__WEBPACK_IMPORTED_MODULE_7__[\"default\"]\n};\n\n//# sourceURL=webpack://aircox-assets/./src/components/index.js?");
/***/ }),
@ -419,6 +439,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _AAu
/***/ }),
/***/ "./src/components/ADropdown.vue":
/*!**************************************!*\
!*** ./src/components/ADropdown.vue ***!
\**************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _ADropdown_vue_vue_type_template_id_a87ed2dc__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./ADropdown.vue?vue&type=template&id=a87ed2dc */ \"./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc\");\n/* harmony import */ var _ADropdown_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ADropdown.vue?vue&type=script&lang=js */ \"./src/components/ADropdown.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\"])(_ADropdown_vue_vue_type_script_lang_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"], [['render',_ADropdown_vue_vue_type_template_id_a87ed2dc__WEBPACK_IMPORTED_MODULE_0__.render],['__file',\"src/components/ADropdown.vue\"]])\n/* hot reload */\nif (false) {}\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = (__exports__);\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?");
/***/ }),
/***/ "./src/components/AEpisode.vue":
/*!*************************************!*\
!*** ./src/components/AEpisode.vue ***!
@ -559,6 +589,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "./src/components/ADropdown.vue?vue&type=script&lang=js":
/*!**************************************************************!*\
!*** ./src/components/ADropdown.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_ADropdown_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_ADropdown_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]!./ADropdown.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/ADropdown.vue?vue&type=script&lang=js\");\n \n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?");
/***/ }),
/***/ "./src/components/AEpisode.vue?vue&type=script&lang=js":
/*!*************************************************************!*\
!*** ./src/components/AEpisode.vue?vue&type=script&lang=js ***!
@ -699,6 +739,16 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc":
/*!********************************************************************!*\
!*** ./src/components/ADropdown.vue?vue&type=template&id=a87ed2dc ***!
\********************************************************************/
/***/ (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_ADropdown_vue_vue_type_template_id_a87ed2dc__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_ADropdown_vue_vue_type_template_id_a87ed2dc__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]!./ADropdown.vue?vue&type=template&id=a87ed2dc */ \"./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/ADropdown.vue?vue&type=template&id=a87ed2dc\");\n\n\n//# sourceURL=webpack://aircox-assets/./src/components/ADropdown.vue?");
/***/ }),
/***/ "./src/components/AEpisode.vue?vue&type=template&id=2e4db98a":
/*!*******************************************************************!*\
!*** ./src/components/AEpisode.vue?vue&type=template&id=2e4db98a ***!

File diff suppressed because one or more lines are too long

View File

@ -49,7 +49,7 @@ Usefull context:
</script>
<div id="app">
{% block top-nav-container %}
<nav class="navbar has-shadow" role="navigation" aria-label="main navigation">
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="container">
<div class="navbar-brand">
<a href="/" title="{% translate "Home" %}" class="navbar-item">
@ -87,81 +87,31 @@ Usefull context:
</nav>
{% endblock %}
<div class="container">
<div class="columns is-desktop">
<main class="column page">
<header class="header">
{% block main-container %}
<main class="page">
{% block main %}
<header class="container header {% if cover %}has-cover{% endif %}">
{% block header %}
<h1 class="title is-1">
{% block title %}
{% if page and page.title %}
{{ page.title }}
{% if object %}
{% include header_template_name|default:"./widgets/header.html" with title=object.title cover=object.cover.url %}
{% else %}
{% include header_template_name|default:"./widgets/header.html" %}
{% endif %}
{% endblock %}
</h1>
<h3 class="subtitle is-3">
{% block subtitle %}{% endblock %}
</h3>
<div class="columns is-size-4">
{% block header_nav %}
<span class="column">
{% block header_crumbs %}
{% if parent %}
<a href="{{ parent.get_absolute_url }}">
{{ parent.title }}</a></li>
{% endif %}
{% endblock %}
</span>
{% endblock %}
</div>
{% endblock %}
</header>
{% block main %}
{% block content-container %}
<div class="container content">
{% block content %}
{% if page and page.content %}
<section class="page-content mb-2">{{ page.content|safe }}</section>
{% endif %}
{% endblock %}
{% endblock main %}
</div>
{% endblock %}
{% endblock %}
</main>
{% if has_sidebar %}
{% comment %}Translators: main sidebar {% endcomment %}
<aside class="column is-one-third-desktop">
{# FIXME: block cover into sidebar one #}
{% block cover %}
{% if page and page.cover %}
<img class="cover mb-4" src="{{ page.cover.url }}" class="cover"/>
{% endif %}
{% endblock %}
{% with is_thin=True %}
{% block sidebar %}
{% if sidebar_object_list %}
{% with object_list=sidebar_object_list %}
{% with list_url=sidebar_list_url %}
{% with has_headline=False %}
<section>
<h4 class="title is-4">
{% block sidebar_title %}{% translate "Recently" %}{% endblock %}
</h4>
{% include "aircox/widgets/page_list.html" %}
{% endwith %}
{% endwith %}
{% endwith %}
{% endif %}
</section>
{% endblock %}
{% endwith %}
</aside>
{% endif %}
</div>
</div>
<hr>
</div>
{% block player-container %}
<div id="player">{% include "aircox/widgets/player.html" %}</div>

View File

@ -22,7 +22,9 @@
{{ station.name }}
{% endblock %}
{% block main %}{{ block.super }}
{% block content-container %}
{{ block.super }}
<div class="container">
{% block before_list %}{% endblock %}
@ -31,7 +33,7 @@
{% with has_headline=True %}
{% for object in object_list %}
{% block list_object %}
{% include object.item_template_name|default:item_template_name %}
{% page_widget item_widget|default:"item" object %}
{% endblock %}
{% empty %}
{% blocktranslate %}There is nothing published here...{% endblocktranslate %}
@ -83,4 +85,5 @@
{% endwith %}
{% endif %}
</div>
{% endblock %}

View File

@ -12,18 +12,24 @@
{% endif %}
{% endblock %}
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
{% block filters-item %}
<span class="control">
<input type="date">
</span>
{% endblock %}
{% block before_list %}
{% with "diffusion-list" as url_name %}
{% include "aircox/widgets/dates_menu.html" %}
{% block header %}
{% with "./widgets/diffusion_list_header.html" as header_template_name %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block pages_list %}
{% with hide_schedule=True %}
<section role="list">
{% include 'aircox/widgets/diffusion_list.html' %}
<section role="list" class="list">
{% for object in object_list %}
{% page_widget "item" object.episode diffusion=object timetable=True %}
{% endfor %}
</section>
{% endwith %}
{% endblock %}

View File

@ -2,14 +2,32 @@
{% comment %}List of a show's episodes for a specific{% endcomment %}
{% load i18n aircox %}
{% include "aircox/program_sidebar.html" %}
{% block content %}
<a-episode :page="{title: &quot;{{ page.title }}&quot;, podcasts: {{ object.podcasts|json }}}">
<template v-slot="{podcasts,page}">
{{ block.super }}
{% if tracks %}
<section>
<h3 class="title is-3">{% translate "Playlist" %}</h3>
<table class="table is-hoverable is-fullwidth">
<tbody>
{% for track in tracks %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ track.artist }}</td>
<td>{{ track.title }}</td>
<td>{{ track.info|default:"" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<ol>
</ol>
</section>
{% endif %}
{% if object.podcasts %}
<section>
<a-playlist v-if="page" :set="podcasts"
@ -18,63 +36,11 @@
:player="player" :actions="['play']"
@select="player.playItems('queue', $event.item)">
<template v-slot:header>
<h4 class="title is-4">{% translate "Podcasts" %}</h4>
<h3 class="title is-3">{% translate "Podcasts" %}</h3>
</template>
</a-playlist>
{% comment %}
{% for object in podcasts %}
{% include "aircox/widgets/podcast_item.html" %}
{% endfor %}
{% endcomment %}
</section>
{% endif %}
{% if tracks %}
<section>
<h4 class="title is-4">{% translate "Playlist" %}</h4>
<ol>
{% for track in tracks %}
<li><span>{{ track.title }}</span>
<span class="has-text-grey-dark has-text-weight-light">
&mdash; {{ track.artist }}
{% if track.info %}(<i>{{ track.info }}</i>){% endif %}
</span>
</li>
{% endfor %}
</ol>
</section>
{% endif %}
</template></a-episode>
{% endblock %}
{% block sidebar %}
<section>
<h4 class="title is-4">{% translate "Diffusions" %}</h4>
<ul>
{% for diffusion in object.diffusion_set.all %}
<li>
{% with diffusion.start as start %}
{% with diffusion.end as end %}
<time datetime="{{ start }}">{{ start|date:"D. d F Y, H:i" }}</time>
&mdash;
<time datetime="{{ end }}">{{ end|date:"H:i" }}</time>
{% endwith %}
{% endwith %}
<small>
{% if diffusion.initial %}
{% with diffusion.initial.date as date %}
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
({% translate "rerun" %})
</span>
{% endwith %}
{% endif %}
</small>
<br>
</li>
{% endfor %}
</ul>
</section>
{{ block.super }}
</template>
</a-episode>
{% endblock %}

View File

@ -1,65 +1,46 @@
{% extends "aircox/page_list.html" %}
{% comment %}Home page{% endcomment %}
{% load i18n %}
{% load i18n aircox %}
{% block head_title %}{{ station.name }}{% endblock %}
{% block title %}
{% if not page or not page.title %}{{ station.name }}
{% else %}{{ block.super }}
{% endif %}
{% endblock %}
{% block before_list %}{% endblock %}
{% block pages_list %}
{% if page and page.content %}<hr/>{% endif %}
{% block content-container %}
{% if next_diffs %}
<div class="columns">
{% with render_card=True %}
{% for object in next_diffs %}
{% with is_primary=object.is_now %}
<div class="column is-relative">
<h4 class="card-super-title" title="{{ object.start }}">
{% if is_primary %}
<span class="fas fa-play"></span>
<time datetime="{{ object.start }}">
{% translate "Currently" %}
</time>
{% else %}
{{ object.start|date:"H:i" }}
<section class="container card-grid">
{% for obj in next_diffs|slice:"1:4" %}
{% if object != diffusion %}
{% page_widget "card" obj.episode diffusion=obj timetable=True %}
{% endif %}
{% if object.episode.category %}
// {{ object.episode.category.title }}
{% endif %}
</h4>
{% include object.item_template_name %}
</div>
{% endwith %}
{% endfor %}
{% endwith %}
</div>
{% endif %}
{% if object_list %}
<h4 class="title is-4">{% translate "Today" %}</h4>
<section role="list">
{% include 'aircox/widgets/diffusion_list.html' %}
</section>
{% endif %}
{% if object_list %}
<section class="container">
<h2 class="title is-3 p-2">
{% with station.name as station %}
{% blocktrans %}
Today on {{ station }}
{% endblocktrans %}
{% endwith %}
</h2>
<div role="list">
{% for object in object_list %}
{% page_widget "item" object.episode diffusion=object timetable=True open=True %}
{% endfor %}
</div>
</section>
{% endif %}
{% endblock %}
{% block pagination %}
<ul class="pagination-list">
<li>
<a href="{% url "page-list" %}" class="pagination-link"
aria-label="{% translate "Show all publication" %}">
{% translate "More publications..." %}
</a>
</li>
</ul>
{% endblock %}
{% block pages_list %}{% endblock %}
{% block sidebar %}

View File

@ -15,15 +15,29 @@
{% block subtitle %}{{ date|date:"l d F Y" }}{% endblock %}
{% block before_list %}
{% with "log-list" as url_name %}
{% include "aircox/widgets/dates_menu.html" %}
{% block header %}
{% with "./widgets/log_list_header.html" as header_template_name %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block pages_list %}
{% block pages_list_ %}
<section>
{# <h4 class="subtitle size-4">{{ date }}</h4> #}
{% include "aircox/widgets/log_list.html" %}
</section>
{% endblock %}
{% block pages_list %}
{% with hide_schedule=True %}
<section role="list" class="list">
{% for object in object_list %}
{% if object.episode %}
{% page_widget "item" object.episode diffusion=object timetable=True %}
{% else %}
{% page_widget "item" object timetable=True %}
{% endif %}
{% endfor %}
</section>
{% endwith %}
{% endblock %}

View File

@ -33,7 +33,7 @@ Context:
{% block comments %}
{% if comments or comment_form %}
<section class="mt-6">
<section class="container mt-6">
<h4 class="title is-4">{% translate "Comments" %}</h4>
{% for comment in comments %}

View File

@ -2,11 +2,48 @@
{% comment %}Display a list of Pages{% endcomment %}
{% load i18n aircox %}
{% block before_list %}
{% block header %}
{% if page and not object %}
{% with page as object %}
{{ block.super }}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block before_list %}
{% if view.has_filters and object_list %}
<form method="GET" action="" class="media">
<form method="GET" action="" class="list-filters">
<a-dropdown :item-class="dropdown-trigger" :content-class="dropdown-menu">
<template #item>
{% block filters-item %}
<span class="icon">
<i class="fa fa-search"></i>
</span>
{% endblock %}
</template>
<template #default>
<div class="dropdown-menu">
{% block filters-content %}
<div class="dropdown-item">
<div class="field is-grouped is-grouped-right">
<div class="control">
<button class="button is-primary"/>{% translate "Apply" %}</button>
</div>
<div class="control">
<a href="?" class="button is-secondary">{% translate "Reset" %}</a>
</div>
</div>
</div>
{% endblock %}
</div>
</template>
</a-dropdown>
</form>
{% comment %}
<div class="media-content">
{% block filters %}
<div class="field is-horizontal">
@ -48,15 +85,8 @@
{% endblock %}
</div>
<div class="media-right">
<div class="field is-grouped is-grouped-right">
<div class="control">
<button class="button is-primary"/>{% translate "Apply" %}</button>
</div>
<div class="control">
<a href="?" class="button is-secondary">{% translate "Reset" %}</a>
</div>
</div>
</div>
{% endcomment %}
</form>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,48 @@
{% load i18n %}
{% block outer %}
<article class="preview preview-card {% if wide %}wide{% endif %}{% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}"
style="background-image: url({{ object.cover.url }})">
{% if with_container %}
<div class="container">
{% endif %}
{% block inner %}
<header class="headings" >
{% block headings %}
<div>
<h2 class="heading title">{% block title %}{% endblock %}</h2>
</div>
<div>
<span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
<summary class="heading-container">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|striptags|truncatewords:64|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</summary>
<div class="actions">
{% block actions %}
<a class="button btn-hg float-right" href="{{ object.get_absolute_url|escape }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
<label>{% translate "More infos" %}</label>
</a>
{% endblock %}
</div>
</header>
{% endblock %}
{% if with_container %}
</div>
{% endif %}
</article>
{% endblock %}

View File

@ -11,63 +11,48 @@ Context variables:
- is_thin (=False): if True, smaller cover and display less info
{% endcomment %}
{% if render_card %}
<article class="card {% if is_primary %}is-primary{% endif %}">
<header class="card-image">
<a href="{{ object.get_absolute_url }}">
<figure class="image is-4by3">
<img src="{% thumbnail object.cover|default:station.default_cover 480x480 %}">
</figure>
</a>
{% block outer %}
<article class="preview preview-item{% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}">
{% block inner %}
<header class="headings"
style="background-image: url({{ object.cover.url }})">
{% block headings %}
<div>
<span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
</header>
<div class="card-header">
<h4 class="title">
<a href="{{ object.get_absolute_url }}">
{% block card_title %}{{ object.title }}{% endblock %}
<div class="">
<div>
<h2 class="heading title">{% block title %}{% endblock %}</h2>
</div>
<summary class="heading-container">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|striptags|truncatewords:64|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</summary>
<div class="actions">
{% block actions %}
<a class="button btn-hg float-right" href="{{ object.get_absolute_url|escape }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
<label>{% translate "More infos" %}</label>
</a>
</h4>
</div>
</article>
{% else %}
<article class="media item {% block css %}{% endblock%}">
{% if has_cover|default_if_none:True %}
<div class="media-left">
{% if is_thin %}
<img src="{% thumbnail object.cover|default:station.default_cover 64x64 crop=scale %}"
class="cover is-tiny">
{% else %}
<img src="{% thumbnail object.cover|default:station.default_cover 128x128 crop=scale %}"
class="cover is-small">
{% endif %}
</div>
{% endif %}
<div class="media-content">
<h5 class="title is-5 has-text-weight-normal">
{% block title %}
{% if object.is_published %}
<a href="{{ object.get_absolute_url }}">{{ object.title }}</a>
{% else %}
{{ object.title }}
{% endif %}
{% endblock %}
</h5>
<div class="subtitle is-6 has-text-weight-light">
{% block subtitle %}
{% if object.category %}{{ object.category.title }}{% endif %}
{% endblock %}
</div>
{% if has_headline|default_if_none:True %}
<div class="headline">
{% block headline %}{{ object.headline }}{% endblock %}
</div>
{% endif %}
</div>
{% endblock %}
{% if not no_actions %}
{% block actions %}{% endblock %}
{% if with_container %}
</div>
{% endif %}
</article>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,23 @@
{% load i18n %}
{% block outer %}
<article class="preview preview-card {% if not cover %}no-cover {% endif %}{% if is_primary %}is-primary{% endif %}{% block container_class %}{% endblock %}"
style="background-image: url({{ cover }})">
{% block inner %}
<header class="headings" >
{% block headings %}
<div>
<a href="{{ url|escape }}" class="heading title">{% block title %}{{ title|default:"" }}{% endblock %}</a>
</div>
<div>
<span class="heading subtitle">{% block subtitle %}{{ subtitle|default:"" }}{% endblock %}</span>
</div>
{% endblock %}
<div class="actions">
{% block actions %}{% endblock %}
</div>
</header>
{% endblock %}
</article>
{% endblock %}

View File

@ -0,0 +1,7 @@
{% load aircox %}
<div class="card-grid">
{% for object in object_list %}
{% page_widget "card" object.episode diffusion=object %}
{% endfor %}
</div>

View File

@ -11,36 +11,10 @@ An empty date results to a title or a separator
{% endcomment %}
{% load i18n %}
<div class="media" role="menu"
aria-label="{% translate "pick a date" %}">
<div class="media-content">
<div class="tabs is-toggle">
<ul>
{% for day in dates %}
<li class="{% if day == date %}is-active{% endif %}">
<a href="{% url url_name date=day %}">
{{ day|date:"D. d" }}
<div>
<a class="heading {% if day == date %}highlight{% endif %}" href="{% url url_name date=day %}">
{{ day|date:"l d" }}
</a>
</li>
</div>
{% endfor %}
</ul>
</div>
</div>
<div class="media-right">
<form action="{% url url_name %}" method="GET" class="navbar-body"
aria-label="{% translate "Jump to date" %}">
<div class="field has-addons">
<div class="control has-icons-left">
<span class="icon is-small is-left"><span class="far fa-calendar"></span></span>
<input type="{{ date_input|default:"date" }}" class="input date"
name="date" value="{{ date|date:"Y-m-d" }}">
</div>
<div class="control">
{% comment %}Translators: form button to select a date{% endcomment %}
<button class="button is-primary">{% translate "Go" %}</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,10 @@
{% comment %}
Context:
- object: diffusion
- "episode_item"'s context (except object and diffusion)
{% endcomment %}
{% with object as diffusion %}
{% with object.episode as object %}
{% include "aircox/widgets/episode_card.html" %}
{% endwith %}
{% endwith %}

View File

@ -3,19 +3,4 @@ Context:
- object_list: object list
- date: date for list
{% endcomment %}
<table id="timetable{% if date %}-{{ date|date:"Y-m-d" }}{% endif %}" class="timetable">
{% for diffusion in object_list %}
<tr class="{% if diffusion.is_now %}has-background-primary{% endif %}">
<td class="pr-2 pb-2">
<time datetime="{{ diffusion.start|date:"c" }}">
{{ diffusion.start|date:"H:i" }} - {{ diffusion.end|date:"H:i" }}
</time>
</td>
<td class="pb-2">
{% with diffusion.episode as object %}
{% include "aircox/widgets/episode_item.html" %}
{% endwith %}
</td>
</tr>
{% endfor %}
</table>
{% load aircox %}

View File

@ -0,0 +1,15 @@
{% extends "./header.html" %}
{% block outer %}
{% with date|date:"l d F Y" as subtitle %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block header-nav %}
<nav class="column has-text-right">
{{ block.super }}
{% include "./dates_menu.html" with url_name="diffusion-list" %}
</nav>
{% endblock %}

View File

@ -0,0 +1,32 @@
{% extends "./page.html" %}
{% load i18n humanize aircox %}
{% block subtitle %}
{% if diffusion %}
{% if timetable %}
{{ diffusion.start|date:"g:i" }}
&mdash;
{{ diffusion.end|date:"g:i" }}
{% else %}
{{ diffusion.start|naturalday }},
{{ diffusion.start|date:"g:i" }}
{% endif %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block actions %}
{{ block.super }}
{% if object.sound_set.count %}
<button class="button action" @click="player.playButtonClick($event)"
data-sounds="{{ object.podcasts|json }}">
<span class="icon is-small">
<span class="fas fa-play"></span>
</span>
<label>{% translate "Listen" %}</label>
</button>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,36 @@
{% extends "./basepage_card.html" %}
{% load humanize %}
{% block title %}
{% if not object.is_published and object.program.is_published %}
<a href="{{ object.program.get_absolute_url }}">
{{ object.program.title }}
</a>
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block class %}
{% if object.is_now %}is-active{% endif %}
{% endblock %}
{% block subtitle %}
{% if diffusion %}
{{ diffusion.start|naturalday }},
{{ diffusion.start|date:"g:i" }}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block content %}
{% if not object.content %}
{% with object.parent.content as content %}
{{ block.super }}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}

View File

@ -1,58 +1,36 @@
{% extends "aircox/widgets/page_item.html" %}
{% comment %}
List item for an episode.
Context variables:
- object: episode
- diffusion: episode's diffusion
- hide_schedule: if True, do not display start time
{% endcomment %}
{% load i18n easy_thumbnails_tags aircox %}
{% extends "./basepage_item.html" %}
{% load i18n humanize %}
{% block title %}
{% if not object.is_published and object.program.is_published %}
<a href="{{ object.program.get_absolute_url }}">
{{ object.program.title }}
{% if diffusion %}
&mdash;
{{ diffusion.start|date:"d F" }}
{% endif %}
</a>
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% block class %}
{% if object.is_now %}is-active{% endif %}
{% endblock %}
{% block subtitle %}
{{ block.super }}
{% if diffusion %}
{% if not hide_schedule %}
{% if object.category %}&mdash;{% endif %}
<time datetime="{{ diffusion.start|date:"c" }}" title="{{ diffusion.start }}">
{{ diffusion.start|date:"d M, H:i" }}
</time>
{{ diffusion.start|naturalday }},
{{ diffusion.start|date:"g:i" }}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
{% if diffusion.initial %}
{% with diffusion.initial.date as date %}
<span title="{% blocktranslate %}Rerun of {{ date }}{% endblocktranslate %}">
{% translate "(rerun)" %}
</span>
{% block content %}
{% if not object.content %}
{% with object.parent.content as content %}
{{ block.super }}
{% endwith %}
{% endif %}
{% endif %}
{% endblock %}
{% block actions %}
{% if object.sound_set.public.count %}
<button class="button" @click="player.playButtonClick($event)"
data-sounds="{{ object.podcasts|json }}">
<span class="icon is-small">
<span class="fas fa-play"></span>
</span>
</button>
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends "./card.html" %}
{% block container_class %}wide{% endblock %}
{% block headings %}
<div class="columns">
<div class="column">
{{ block.super }}
<section class="heading content">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</section>
</div>
{% block header-nav %}{% endblock %}
</div>
{% endblock %}

View File

@ -0,0 +1,38 @@
{% extends "./base_header.html" %}
{% load i18n humanize %}
{% block subtitle %}
{% if diffusion %}
{{ diffusion.start|naturalday }},
{{ diffusion.start|date:"g:i" }}
{% endif %}
{% endblock %}
{% block headings %}
{{ block.super }}
{% with diffusion.episode.content|default:diffusion.episode.program.content|striptags|safe as content %}
{% if content %}
<div class="heading-container content">
{{ content|truncatewords:64|linebreaks }}
</div>
{% endif %}
<div class="actions">
<button class="btn-outline-hg button">
<span class="icon">
<i class="fas fa-play"></i>
</span>
{% trans "Listen" %}
</button>
<a class="btn-outline-hg button" href="{{ diffusion.episode.get_absolute_url }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
{% trans "More informations" %}
</a>
</div>
{% endwith %}
{% endblock %}

View File

@ -0,0 +1,42 @@
{% load i18n aircox %}
{% block outer %}
<article class="preview list-item {% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}">
{% block inner %}
<a href="{{ url|escape }}" class="headings is-fullwidth columns">
{% block headings %}
<div class="column">
<span class="heading title">{% block title %}{% endblock %}</span>
</div>
<div>
<span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
</a>
<div class="media">
{% if object.cover %}
<div class="media-left preview-cover small"
style="background-image: url({{ object.cover.url }})">
</div>
{% endif %}
<div class="media-content">
<section class="content">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|striptags|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</section>
<div class="actions is-flex-grow-0">
{% block actions %}{% endblock %}
</div>
</template>
{% endblock %}
</div>
</article>
{% endblock %}

View File

@ -1,3 +1,4 @@
{% extends "./page.html" %}
{% load i18n aircox %}
{% comment %}
List item for a log, either for a logged track or diffusion (as diffusion).
@ -11,12 +12,10 @@ for design review.
{% endcomment %}
{% block outer %}
{% if object|is_diffusion %}
{% with object as diffusion %}
{% include "aircox/widgets/diffusion_item.html" %}
{% endwith %}
{% include "./diffusion.html" with object=diffusion.episode %}
{% else %}
{% with object.track as object %}
{% include "aircox/widgets/track_item.html" %}
{% endwith %}
{% include "aircox/widgets/track_item.html" with object=object.track log=object %}
{% endif %}
{% endblock %}

View File

@ -1,30 +0,0 @@
{% comment %}
Render list of logs (as widget).
Context:
- object_list: list of logs to display
- is_thin: if True, hide some information in order to fit in a thin container
{% endcomment %}
{% load aircox %}
{% with True as hide_schedule %}
<table class="table is-striped is-hoverable is-fullwidth" role="list">
{% for object in object_list %}
<tr {% if object|is_diffusion and object.is_now %}class="is-selected"{% endif %}>
<td>
{% if object|is_diffusion %}
<time datetime="{{ object.start }}" title="{{ object.start }}">
{{ object.start|date:"H:i" }}
{% if not is_thin %} - {{ object.end|date:"H:i" }}{% endif %}
</time>
{% else %}
<time datetime="{{ object.date }}" title="{{ object.date }}">
{{ object.date|date:"H:i" }}
</time>
{% endif %}
</td>
<td>{% include "aircox/widgets/log_item.html" %}</td>
</tr>
{% endfor %}
</table>
{% endwith %}

View File

@ -0,0 +1,15 @@
{% extends "./header.html" %}
{% block outer %}
{% with date|date:"l d F Y" as subtitle %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block header-nav %}
<nav class="column has-text-right">
{{ block.super }}
{% include "./dates_menu.html" with url_name="log-list" %}
</nav>
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends widget_template %}
{% load i18n %}
{% block outer %}
{% with object.get_absolute_url as url %}
{% with object.cover.url as cover %}
{{ block.super }}
{% endwith %}
{% endwith %}
{% endblock %}
{% block title %}
{% if title %}
{{ block.super }}
{% elif object %}
{{ object.display_title }}
{% endif %}
{% endblock %}
{% block content %}
{% if content %}
{{ content }}
{% elif object %}
{{ block.super }}
{{ object.display_headline }}
{% endif %}
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends widget|default:"./card.html" %}
{% block outer %}
{% if object %}
{% with content=object.get_display_excerpt() %}
{% with title=object.get_display_title() %}
{{ block.super }}
{% endwith %}
{% endwith %}
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}

View File

@ -3,3 +3,11 @@
{% block card_title %}
{% block title %}{{ block.super }}{% endblock %}
{% endblock %}
{% block card_subtitle %}
{% block subtitle %}{{ block.super }}{% endblock %}
{% endblock %}
{% block card_class %}
{% block class %}{{ block.super }}{% endblock %}
{% endblock %}

View File

@ -5,10 +5,10 @@ Context:
- object_list: object list
- list_url: url to complete list page
{% endcomment %}
{% load i18n %}
{% load i18n aircox %}
{% for object in object_list %}
{% include object.item_template_name %}
{% widget object.item_template_name %}
{% endfor %}
{% if list_url %}

View File

@ -5,9 +5,16 @@ Context:
- object: track to render
{% endcomment %}
<span class="content">
<span class="has-text-info is-size-5">&#9836;</span>
<span>{{ object.title }}</span>
<span class="has-text-grey-dark has-text-weight-light">
{% if log %}
<span>{{ log.date|date:"H:i" }} &mdash; </span>
{% endif %}
<span class="has-text-weight-bold">{{ object.title }}</span>
{% if object.artist and object.artist != object.title %}
<span>
&mdash; {{ object.artist }}
{% if object.info %}(<i>{{ object.info }}</i>){% endif %}
</span>
{% endif %}
</span>

View File

@ -0,0 +1,42 @@
{% load i18n %}
{% block outer %}
<article class="preview preview-card columns wide{% if is_primary %}is-primary{% endif %}{% block card_class %}{% endblock %}">
{% block inner %}
<header class="headings column preview-cover"
style="background-image: url({{ object.cover.url }})">
{% block headings %}
<div>
<a href="{{ url|escape }}" class="heading title">{% block title %}{% endblock %}</a>
</div>
<div>
<span class="heading subtitle">{% block subtitle %}{% endblock %}</span>
</div>
{% endblock %}
</header>
<div class="height-full column">
<section class="content headings-container">
{% block content %}
{% if content and with_content %}
{% autoescape off %}
{{ content|striptags|linebreaks }}
{% endautoescape %}
{% endif %}
{% endblock %}
</section>
<div class="actions">
{% block actions %}
<a class="button btn-hg float-right" href="{{ object.get_absolute_url|escape }}">
<span class="icon">
<i class="fas fa-external-link"></i>
</span>
<label>{% translate "More infos" %}</label>
</a>
{% endblock %}
</div>
</div>
{% endblock %}
</article>
{% endblock %}

View File

@ -3,14 +3,32 @@ import random
from django import template
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.template.loader import render_to_string
from django.urls import reverse
from aircox.models import Diffusion, Log
random.seed()
register = template.Library()
@register.simple_tag(name="page_widget", takes_context=True)
def do_page_widget(context, widget, object, dir="aircox/widgets", **ctx):
"""Render widget for the provided page and context."""
ctx["request"] = context["request"]
ctx["object"] = object
ctx["widget"] = widget
ctx["widget_template"] = f"{dir}/{widget}.html"
return render_to_string(object.get_template_name(widget), ctx)
@register.filter(name="page_template")
def do_page_template(self, page, component):
"""For a provided page object and component name, return template name."""
return page.get_template(component)
@register.filter(name="admin_url")
def do_admin_url(obj, arg, pass_id=True):
"""Reverse admin url for object."""

View File

@ -8,6 +8,7 @@ __all__ = ("BaseView", "BaseAPIView")
class BaseView(TemplateResponseMixin, ContextMixin):
header_template_name = "aircox/widgets/header.html"
has_sidebar = True
"""Show side navigation."""
has_filters = False
@ -38,6 +39,7 @@ class BaseView(TemplateResponseMixin, ContextMixin):
kwargs.setdefault("station", self.station)
kwargs.setdefault("page", self.get_page())
kwargs.setdefault("has_filters", self.has_filters)
kwargs.setdefault("header_template_name", self.header_template_name)
has_sidebar = kwargs.setdefault("has_sidebar", self.has_sidebar)
if has_sidebar and "sidebar_object_list" not in kwargs:

View File

@ -27,14 +27,15 @@ class HomeView(BaseView, ListView):
def get_next_diffs(self):
now = tz.now()
current_diff = Diffusion.objects.on_air().now(now).first()
next_diffs = Diffusion.objects.on_air().after(now)
query = Diffusion.objects.on_air().select_related("episode")
current_diff = query.now(now).first()
next_diffs = query.after(now)
if current_diff:
diffs = [current_diff] + list(
next_diffs.exclude(pk=current_diff.pk)[:2]
next_diffs.exclude(pk=current_diff.pk)[:9]
)
else:
diffs = next_diffs[:3]
diffs = next_diffs[:10]
return diffs
def get_last_publications(self):
@ -52,8 +53,16 @@ class HomeView(BaseView, ListView):
return items
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["logs"] = self.get_logs(context["object_list"])
context["next_diffs"] = self.get_next_diffs()
context["last_publications"] = self.get_last_publications()[:5]
return context
next_diffs = self.get_next_diffs()
current_diff = next_diffs and next_diffs[0]
kwargs.update(
{
"object": current_diff.episode,
"diffusion": current_diff,
"logs": self.get_logs(self.object_list),
"next_diffs": next_diffs,
"last_publications": self.get_last_publications()[:5],
}
)
return super().get_context_data(**kwargs)

View File

@ -12,7 +12,8 @@
"@fortawesome/fontawesome-free": "^6.0.0",
"core-js": "^3.8.3",
"lodash": "^4.17.21",
"vue": "^3.2.13"
"vue": "^3.2.13",
"vue3-carousel": "^0.3.1"
},
"devDependencies": {
"@babel/core": "^7.12.16",
@ -20,7 +21,7 @@
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"bulma": "^0.9.3",
"bulma": "^0.9.4",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"sass": "^1.49.9",

View File

@ -1,13 +1,47 @@
import 'vue3-carousel/dist/carousel.css'
import components from './components'
import { Carousel, Pagination, Navigation, Slide } from 'vue3-carousel'
const App = {
el: '#app',
delimiters: ['[[', ']]'],
components: {...components},
components: {
...components,
Slide,
Carousel,
Pagination,
Navigation,
},
computed: {
player() { return window.aircox.player; },
},
data() {
return {
carouselBreakpoints: {
400: {
itemsToShow: 1
},
600: {
itemsToShow: 1.75
},
800: {
itemsToShow: 3
},
1024: {
itemsToShow: 4
},
1280: {
itemsToShow: 4
},
1380: {
itemsToShow: 5
},
}
}
}
}
export const PlayerApp = {

View File

@ -1,5 +1,50 @@
@charset "utf-8";
$font-special: "bagnard";
$font-special-url: url("assets/Bagnard.otf");
$black: #000;
$white: #fff;
$mp-1: 0.2em;
$mp-2: 0.4em;
$mp-3: 0.8em;
$mp-4: 1.2em;
$mp-5: 1.6em;
$mp-6: 2em;
$mp-7: 4em;
$text-size-small: 0.6em;
$text-size-smaller: 0.8em;
$text-size: 1em;
$text-size-medium: 1.2em;
$text-size-bigger: 1.6em;
$text-size-big: 2em;
$h1-size: 40px;
$h2-size: 32px;
$h3-size: 28px;
$h4-size: 24px;
$h5-size: 20px;
$h6-size: 14px;
$weight-light: 100;
$weight-lighter: 300;
$weight-normal: 400;
$weight-bolder: 500;
$weight-bold: 700;
$screen-very-small: 400px;
//TODO: switch small & smaller
$screen-small: 600px;
$screen-smaller: 800px;
$screen-normal: 1024px;
$screen-wider: 1280px;
$screen-wide: 1380px;
@import "~bulma/sass/utilities/_all.sass";
@import "~bulma/sass/components/dropdown.sass";
@ -325,3 +370,324 @@ aside {
width: 100%;
border: none;
}
// -- layout
:root {
--text-color: black;
--highlight-color: rgba(255, 255, 0, 1);
--highlight-color-alpha: rgba(255, 255, 0, 0.6);
--highlight-color-2: rgb(0, 0, 254);
--highlight-color-2-alpha: rgb(0, 0, 254, 0.6);
--heading-height: 30em;
--heading-title-bg-color: rgba(255, 255, 0, 1);
--heading-bg-color: var(--highlight-color);
--heading-bg-highlight-color: var(--highlight-color-2);
--preview-media-height: 10em;
--preview-media-cover-size: 10em;
--preview-cover-size: 24em;
--preview-cover-small-size: 14em;
}
.page a {
background-color: var(--highlight-color-alpha);
color: var(--text-color);
}
// ---- helpers
.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 }
.height-full { height: 100%; }
.flex-push-right { margin-left: auto; }
.is-clickable { cursor: pointer; }
// ---- components
.btn-hg, .btn-outline-hg {
border: 0.1em var(--highlight-color) solid;
background-color: var(--highlight-color-alpha) !important;
border-radius: 0.2em;
}
.button {
&.action {
border-color: var(--highlight-color-2-alpha);
justify-content: center;
padding: $mp-2 !important;
min-width: 2em;
.icon { margin: 0em !important; }
label {
margin-left: $mp-2;
}
}
}
.list-filters {
text-align: right;
}
.content {
font-size: $text-size-medium;
}
// ---- items
.preview {
position: relative;
background-size: cover;
&.preview-card {
&:not(.wide) {
max-width: 30em;
}
}
&.preview-item {
width: 100%;
}
&.columns, .headings.columns {
margin: 0em;
.column { padding: 0em; }
}
.title {
font-weight: $weight-bold;
font-size: $text-size-bigger;
}
.subtitle {
font-weight: $weight-bolder;
font-size: $text-size-bigger;
}
.content, .actions {
font-size: $text-size-medium;
}
.headings {
background-size: cover;
* { margin: 0em; padding: 0em; }
a {
color: var(--text-color);
}
}
}
.heading, .headings-container > * {
display: inline-block;
&:not(:empty) {
background-color: var(--heading-bg-color);
padding: $mp-1;
margin-top: 0em !important;
vertical-align: top;
&.highlight {
background-color: var(--heading-bg-highlight-color);
color: var(--highlight-color);
}
}
&.title {
background-color: var(--heading-title-bg-color);
}
}
.actions {
&.no-label label {
display: none;
}
}
.list-item {
width: 100%;
&:not(:last-child) {
margin-bottom: calc($mp-4 / 2);
}
&:not(:first-child) {
margin-top: calc($mp-4 / 2);
}
.headings {
padding-top: 0em;
margin-bottom: $mp-3 !important;
background-color: var(--heading-bg-color);
}
.media-content {
height: var(--preview-cover-small-size);
display: flex;
flex-direction: column;
.content { flex-grow: 1; }
.actions {
flex-grow: 0;
text-align: right;
}
}
}
// ---- cards
.preview-cover {
background-size: cover;
height: var(--preview-cover-size);
width: var(--preview-cover-size);
&.small {
min-width: unset;
height: var(--preview-cover-small-size);
width: var(--preview-cover-small-size) !important;
}
}
.preview-card {
height: var(--preview-cover-size);
min-width: var(--preview-cover-size);
.card-grid & {
min-width: unset;
}
.headings {
width: 100%;
min-height: 100%;
padding-top: $mp-3;
& > div:not(:last-child) {
margin-bottom: $mp-3;
}
nav a {
font-size: 1em;
}
}
.heading {
margin-bottom: $mp-3;
}
.actions {
position: absolute;
bottom: $mp-3;
right: $mp-3;
label {
display: none;
}
}
}
.header {
background-size: cover;
.preview {
height: var(--heading-height);
&.no-cover {
height: unset;
}
.headings, > .container {
width: 100%;
}
> .container, {
height: 100%;
}
}
.preview-card {
max-width: unset;
}
.title {
font-size: $h1-size;
}
.subtitle {
font-size: $h2-size;
}
.content {
display: inline !important;
font-size: $text-size-medium;
font-weight: $weight-bolder;
text-align: right;
p {
margin-bottom: $mp-3
}
}
}
// -- program grid
.preview-card {
header {
.info {
float: right;
}
}
&.is-active {
border-bottom: 1px var(--highlight-color) solid;
}
}
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: $mp-4;
}
@media screen and (max-width: $screen-wide) {
.preview:not(.list-item) {
height: 20em !important;
}
.card-grid .preview-card {
height: 20em;
}
}
@media screen and (max-width: $screen-normal) {
.card-grid {
grid-template-columns: 1fr 1fr;
.preview-card:nth-child(3) {
display: none;
}
}
}

View File

@ -0,0 +1,23 @@
<template>
<carousel :items-to-show="1.5">
<slot></slot>
<template #addons>
<navigation />
<pagination />
</template>
</carousel>
</template>
<script>
// If you are using PurgeCSS, make sure to whitelist the carousel CSS classes
import 'vue3-carousel/dist/carousel.css'
import { Carousel, Pagination, Navigation } from 'vue3-carousel'
export default {
components: {
Carousel,
Pagination,
Navigation,
},
}
</script>

View File

@ -0,0 +1,43 @@
<template>
<div>
<div :class="itemClass" @click="noButton && toggle()">
<slot name="button">
<span :class="['float-right', buttonClass]" @click="toggle()">
<span class="icon">
<i v-if="!active" :class="buttonIconOpen"></i>
<i v-if="active" :class="buttonIconClose"></i>
</span>
</span>
</slot>
<slot name="item"></slot>
</div>
<div :class="contentClass" v-if="active">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
data() {
return {
"active": this.open,
}
},
props: {
itemClass: String,
buttonClass: String,
buttonIconOpen: { type: String, default:"fa fa-angle-down"},
buttonIconClose: { type: String, default:"fa fa-angle-up"},
contentClass: String,
open: {type: Boolean, default: false},
noButton: {type: Boolean, default: false},
},
methods: {
toggle() {
this.active = !this.active
}
},
}
</script>

View File

@ -1,4 +1,5 @@
import AAutocomplete from './AAutocomplete.vue'
import ADropdown from "./ADropdown.vue"
import AEpisode from './AEpisode.vue'
import AList from './AList.vue'
import APage from './APage.vue'
@ -14,7 +15,7 @@ import AStreamer from './AStreamer.vue'
* Core components
*/
export const base = {
AAutocomplete, AEpisode, AList, APage, APlayer, APlaylist,
AAutocomplete, ADropdown, AEpisode, AList, APage, APlayer, APlaylist,
AProgress, ASoundItem,
}