diff --git a/aircox/admin/sound.py b/aircox/admin/sound.py index 74ea9fb..d2925ba 100644 --- a/aircox/admin/sound.py +++ b/aircox/admin/sound.py @@ -32,6 +32,7 @@ class SoundInline(admin.TabularInline): "is_good_quality", "is_public", "is_downloadable", + "is_removed", ] readonly_fields = ["type", "audio", "duration", "is_good_quality"] extra = 0 @@ -89,11 +90,7 @@ class SoundAdmin(SortableAdminBase, admin.ModelAdmin): related.short_description = _("Program / Episode") def audio(self, obj): - return ( - mark_safe(''.format(obj.file.url)) - if obj.type != Sound.TYPE_REMOVED - else "" - ) + return mark_safe(''.format(obj.file.url)) if not obj.is_removed else "" audio.short_description = _("Audio") diff --git a/aircox/controllers/sound_file.py b/aircox/controllers/sound_file.py index 752da3e..6ba7cf2 100644 --- a/aircox/controllers/sound_file.py +++ b/aircox/controllers/sound_file.py @@ -99,7 +99,7 @@ class SoundFile: sound = Sound.objects.path(self.path).first() if sound: if keep_deleted: - sound.type = sound.TYPE_REMOVED + sound.is_removed = True sound.check_on_file() sound.save() return sound diff --git a/aircox/forms.py b/aircox/forms.py index 8872f8e..72faf51 100644 --- a/aircox/forms.py +++ b/aircox/forms.py @@ -80,6 +80,7 @@ TrackFormSet = modelformset_factory( "tags", "album", ], + can_delete=True, extra=0, ) """Track formset used in EpisodeUpdateView.""" @@ -94,6 +95,7 @@ SoundFormSet = modelformset_factory( "is_downloadable", "duration", ], + can_delete=True, extra=0, ) """Sound formset used in EpisodeUpdateView.""" diff --git a/aircox/models/__init__.py b/aircox/models/__init__.py index 246ca3d..00f6673 100644 --- a/aircox/models/__init__.py +++ b/aircox/models/__init__.py @@ -6,8 +6,9 @@ from .log import Log, LogQuerySet from .page import Category, Comment, NavItem, Page, PageQuerySet, StaticPage from .program import Program, ProgramChildQuerySet, ProgramQuerySet, Stream from .schedule import Schedule -from .sound import Sound, SoundQuerySet, Track +from .sound import Sound, SoundQuerySet from .station import Port, Station, StationQuerySet +from .track import Track from .user_settings import UserSettings __all__ = ( diff --git a/aircox/models/episode.py b/aircox/models/episode.py index 9b7e8dd..4f05926 100644 --- a/aircox/models/episode.py +++ b/aircox/models/episode.py @@ -62,6 +62,7 @@ class Episode(Page): podcast["name"] = f"{self.title} - {archive_index}" else: podcast["name"] = self.title + archive_index += 1 podcasts[index]["cover"] = cover podcasts[index]["page_url"] = self.get_absolute_url() diff --git a/aircox/models/log.py b/aircox/models/log.py index 1009583..a58f2fe 100644 --- a/aircox/models/log.py +++ b/aircox/models/log.py @@ -8,8 +8,9 @@ from django.utils import timezone as tz from django.utils.translation import gettext_lazy as _ from .diffusion import Diffusion -from .sound import Sound, Track +from .sound import Sound from .station import Station +from .track import Track from .page import Renderable logger = logging.getLogger("aircox") diff --git a/aircox/models/signals.py b/aircox/models/signals.py index 02bd232..e2380a8 100755 --- a/aircox/models/signals.py +++ b/aircox/models/signals.py @@ -1,3 +1,5 @@ +import os + from django.contrib.auth.models import Group, Permission, User from django.db import transaction from django.db.models import signals @@ -11,6 +13,7 @@ from .episode import Episode from .page import Page from .program import Program from .schedule import Schedule +from .sound import Sound # Add a default group to a user when it is created. It also assigns a list @@ -94,3 +97,15 @@ def schedule_pre_delete(sender, instance, *args, **kwargs): @receiver(signals.post_delete, sender=Diffusion) def diffusion_post_delete(sender, instance, *args, **kwargs): Episode.objects.filter(diffusion__isnull=True, content__isnull=True, sound__isnull=True).delete() + + +@receiver(signals.post_delete, sender=Sound) +def delete_file(sender, instance, *args, **kwargs): + """Deletes file on `post_delete`""" + if not instance.file: + return + + path = instance.file.path + qs = sender.objects.filter(file=path) + if not qs.exists() and os.path.exists(path): + os.remove(path) diff --git a/aircox/models/sound.py b/aircox/models/sound.py index 0ff7bfb..243a8db 100644 --- a/aircox/models/sound.py +++ b/aircox/models/sound.py @@ -6,7 +6,6 @@ from django.db import models from django.db.models import Q from django.utils import timezone as tz from django.utils.translation import gettext_lazy as _ -from taggit.managers import TaggableManager from aircox.conf import settings @@ -16,7 +15,7 @@ from .program import Program logger = logging.getLogger("aircox") -__all__ = ("Sound", "SoundQuerySet", "Track") +__all__ = ("Sound", "SoundQuerySet") class SoundQuerySet(models.QuerySet): @@ -33,7 +32,7 @@ class SoundQuerySet(models.QuerySet): return self.filter(episode__diffusion__id=id) def available(self): - return self.exclude(type=Sound.TYPE_REMOVED) + return self.exclude(is_removed=False) def public(self): """Return sounds available as podcasts.""" @@ -85,12 +84,10 @@ class Sound(models.Model): TYPE_OTHER = 0x00 TYPE_ARCHIVE = 0x01 TYPE_EXCERPT = 0x02 - TYPE_REMOVED = 0x03 TYPE_CHOICES = ( (TYPE_OTHER, _("other")), (TYPE_ARCHIVE, _("archive")), (TYPE_EXCERPT, _("excerpt")), - (TYPE_REMOVED, _("removed")), ) name = models.CharField(_("name"), max_length=64) @@ -116,6 +113,7 @@ class Sound(models.Model): default=0, help_text=_("position in the playlist"), ) + is_removed = models.BooleanField(_("removed"), default=False, help_text=_("file has been removed")) def _upload_to(self, filename): subdir = settings.SOUND_ARCHIVES_SUBDIR if self.type == self.TYPE_ARCHIVE else settings.SOUND_EXCERPTS_SUBDIR @@ -201,16 +199,16 @@ class Sound(models.Model): Return True if there was changes. """ if not self.file_exists(): - if self.type == self.TYPE_REMOVED: + if self.is_removed: return logger.debug("sound %s: has been removed", self.file.name) - self.type = self.TYPE_REMOVED + self.is_removed = True return True # not anymore removed changed = False - if self.type == self.TYPE_REMOVED and self.program: + if self.is_removed and self.program: changed = True self.type = ( self.TYPE_ARCHIVE if self.file.name.startswith(self.program.archives_path) else self.TYPE_EXCERPT @@ -240,65 +238,3 @@ class Sound(models.Model): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__check_name() - - -class Track(models.Model): - """Track of a playlist of an object. - - The position can either be expressed as the position in the playlist - or as the moment in seconds it started. - """ - - episode = models.ForeignKey( - Episode, - models.CASCADE, - blank=True, - null=True, - verbose_name=_("episode"), - ) - sound = models.ForeignKey( - Sound, - models.CASCADE, - blank=True, - null=True, - verbose_name=_("sound"), - ) - position = models.PositiveSmallIntegerField( - _("order"), - default=0, - help_text=_("position in the playlist"), - ) - timestamp = models.PositiveSmallIntegerField( - _("timestamp"), - blank=True, - null=True, - help_text=_("position (in seconds)"), - ) - title = models.CharField(_("title"), max_length=128) - artist = models.CharField(_("artist"), max_length=128) - album = models.CharField(_("album"), max_length=128, null=True, blank=True) - tags = TaggableManager(verbose_name=_("tags"), blank=True) - year = models.IntegerField(_("year"), blank=True, null=True) - # FIXME: remove? - info = models.CharField( - _("information"), - max_length=128, - blank=True, - null=True, - help_text=_( - "additional informations about this track, such as " "the version, if is it a remix, features, etc." - ), - ) - - class Meta: - verbose_name = _("Track") - verbose_name_plural = _("Tracks") - ordering = ("position",) - - def __str__(self): - return "{self.artist} -- {self.title} -- {self.position}".format(self=self) - - def save(self, *args, **kwargs): - if (self.sound is None and self.episode is None) or (self.sound is not None and self.episode is not None): - raise ValueError("sound XOR episode is required") - super().save(*args, **kwargs) diff --git a/aircox/models/track.py b/aircox/models/track.py new file mode 100644 index 0000000..8421d2c --- /dev/null +++ b/aircox/models/track.py @@ -0,0 +1,72 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ +from taggit.managers import TaggableManager + + +from .episode import Episode +from .sound import Sound + + +__all__ = ("Track",) + + +class Track(models.Model): + """Track of a playlist of an object. + + The position can either be expressed as the position in the playlist + or as the moment in seconds it started. + """ + + episode = models.ForeignKey( + Episode, + models.CASCADE, + blank=True, + null=True, + verbose_name=_("episode"), + ) + sound = models.ForeignKey( + Sound, + models.CASCADE, + blank=True, + null=True, + verbose_name=_("sound"), + ) + position = models.PositiveSmallIntegerField( + _("order"), + default=0, + help_text=_("position in the playlist"), + ) + timestamp = models.PositiveSmallIntegerField( + _("timestamp"), + blank=True, + null=True, + help_text=_("position (in seconds)"), + ) + title = models.CharField(_("title"), max_length=128) + artist = models.CharField(_("artist"), max_length=128) + album = models.CharField(_("album"), max_length=128, null=True, blank=True) + tags = TaggableManager(verbose_name=_("tags"), blank=True) + year = models.IntegerField(_("year"), blank=True, null=True) + # FIXME: remove? + info = models.CharField( + _("information"), + max_length=128, + blank=True, + null=True, + help_text=_( + "additional informations about this track, such as " "the version, if is it a remix, features, etc." + ), + ) + + class Meta: + verbose_name = _("Track") + verbose_name_plural = _("Tracks") + ordering = ("position",) + + def __str__(self): + return "{self.artist} -- {self.title} -- {self.position}".format(self=self) + + def save(self, *args, **kwargs): + if (self.sound is None and self.episode is None) or (self.sound is not None and self.episode is not None): + raise ValueError("sound XOR episode is required") + super().save(*args, **kwargs) diff --git a/aircox/static/aircox/js/chunk-common.js b/aircox/static/aircox/js/chunk-common.js index f884254..6c871b1 100644 --- a/aircox/static/aircox/js/chunk-common.js +++ b/aircox/static/aircox/js/chunk-common.js @@ -385,7 +385,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 */ 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: \"a-playlist-editor\"\n};\nconst _hoisted_2 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: \"fa fa-upload\"\n})], -1 /* HOISTED */);\nconst _hoisted_3 = {\n class: \"flex-row\"\n};\nconst _hoisted_4 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: \"flex-grow-1 flex-row\"\n}, null, -1 /* HOISTED */);\nconst _hoisted_5 = {\n class: \"flex-grow-1 align-right\"\n};\nconst _hoisted_6 = [\"title\", \"aria-label\"];\nconst _hoisted_7 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: \"fa fa-rotate\"\n})], -1 /* HOISTED */);\nconst _hoisted_8 = [_hoisted_7];\nconst _hoisted_9 = [\"title\", \"aria-label\"];\nconst _hoisted_10 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: \"fa fa-plus\"\n})], -1 /* HOISTED */);\nconst _hoisted_11 = [_hoisted_10];\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n const _component_a_file_upload = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-file-upload\");\n const _component_a_modal = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-modal\");\n const _component_a_rows = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-rows\");\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_a_modal, {\n ref: \"modal\",\n title: $props.labels && $props.labels.add_sound\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_a_file_upload, {\n ref: \"file-upload\",\n url: $props.soundUploadUrl,\n label: $props.labels.select_file,\n submitLabel: \"\",\n onLoad: $options.uploadDone\n }, {\n preview: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(({\n upload\n }) => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-preview\", {\n upload: upload\n })]),\n form: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-form\")]),\n _: 3 /* FORWARDED */\n }, 8 /* PROPS */, [\"url\", \"label\", \"onLoad\"])]),\n footer: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button\",\n onClick: _cache[0] || (_cache[0] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($event => _ctx.$refs['file-upload'].submit(), [\"stop\"]))\n }, [_hoisted_2, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.labels.submit), 1 /* TEXT */)])]),\n\n _: 3 /* FORWARDED */\n }, 8 /* PROPS */, [\"title\"]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_a_rows, {\n set: $data.set,\n columns: $props.columns,\n labels: $props.initData.fields,\n \"allow-create\": true,\n orderable: true,\n onMove: $options.listItemMove\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createSlots)({\n _: 2 /* DYNAMIC */\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($options.rowsSlots, ([name, slot]) => {\n return {\n name: slot,\n fn: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(data => [name != 'row-tail' ? (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, name, (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_0__.mergeProps)({\n key: 0\n }, data))) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)])\n };\n })]), 1032 /* PROPS, DYNAMIC_SLOTS */, [\"set\", \"columns\", \"labels\", \"onMove\"]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_3, [_hoisted_4, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_5, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-warning p-2\",\n onClick: _cache[1] || (_cache[1] = $event => $options.loadData({\n items: this.initData.items\n }, true)),\n title: $props.labels.discard_changes,\n \"aria-label\": $props.labels.discard_changes\n }, _hoisted_8, 8 /* PROPS */, _hoisted_6), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-primary p-2\",\n onClick: _cache[2] || (_cache[2] = $event => _ctx.$refs.modal.open()),\n title: $props.labels.add_sound,\n \"aria-label\": $props.labels.add_sound\n }, _hoisted_11, 8 /* PROPS */, _hoisted_9)])])]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASoundListEditor.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"); +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: \"a-playlist-editor\"\n};\nconst _hoisted_2 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: \"fa fa-upload\"\n})], -1 /* HOISTED */);\nconst _hoisted_3 = {\n class: \"flex-row\"\n};\nconst _hoisted_4 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: \"flex-grow-1 flex-row\"\n}, null, -1 /* HOISTED */);\nconst _hoisted_5 = {\n class: \"flex-grow-1 align-right\"\n};\nconst _hoisted_6 = [\"title\", \"aria-label\"];\nconst _hoisted_7 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: \"fa fa-rotate\"\n})], -1 /* HOISTED */);\nconst _hoisted_8 = [_hoisted_7];\nconst _hoisted_9 = [\"title\", \"aria-label\"];\nconst _hoisted_10 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: \"fa fa-plus\"\n})], -1 /* HOISTED */);\nconst _hoisted_11 = [_hoisted_10];\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n const _component_a_file_upload = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-file-upload\");\n const _component_a_modal = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-modal\");\n const _component_a_rows = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-rows\");\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_a_modal, {\n ref: \"modal\",\n title: $props.labels && $props.labels.add_sound\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_a_file_upload, {\n ref: \"file-upload\",\n url: $props.soundUploadUrl,\n label: $props.labels.select_file,\n submitLabel: \"\",\n onLoad: $options.uploadDone\n }, {\n preview: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(({\n upload\n }) => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-preview\", {\n upload: upload\n })]),\n form: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-form\")]),\n _: 3 /* FORWARDED */\n }, 8 /* PROPS */, [\"url\", \"label\", \"onLoad\"])]),\n footer: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button\",\n onClick: _cache[0] || (_cache[0] = (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($event => _ctx.$refs['file-upload'].submit(), [\"stop\"]))\n }, [_hoisted_2, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.labels.submit), 1 /* TEXT */)])]),\n\n _: 3 /* FORWARDED */\n }, 8 /* PROPS */, [\"title\"]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"top\", {\n set: $data.set,\n items: $data.set.items\n }), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_a_rows, {\n set: $data.set,\n columns: $props.columns,\n labels: $props.initData.fields,\n \"allow-create\": true,\n orderable: true,\n onMove: $options.listItemMove\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createSlots)({\n _: 2 /* DYNAMIC */\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($options.rowsSlots, ([name, slot]) => {\n return {\n name: slot,\n fn: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(data => [name != 'row-tail' ? (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, name, (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_0__.mergeProps)({\n key: 0\n }, data))) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)])\n };\n })]), 1032 /* PROPS, DYNAMIC_SLOTS */, [\"set\", \"columns\", \"labels\", \"onMove\"]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_3, [_hoisted_4, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_5, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-warning p-2\",\n onClick: _cache[1] || (_cache[1] = $event => $options.loadData({\n items: this.initData.items\n }, true)),\n title: $props.labels.discard_changes,\n \"aria-label\": $props.labels.discard_changes\n }, _hoisted_8, 8 /* PROPS */, _hoisted_6), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-primary p-2\",\n onClick: _cache[2] || (_cache[2] = $event => _ctx.$refs.modal.open()),\n title: $props.labels.add_sound,\n \"aria-label\": $props.labels.add_sound\n }, _hoisted_11, 8 /* PROPS */, _hoisted_9)])])]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASoundListEditor.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"); /***/ }), @@ -455,7 +455,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 import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _vueLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./vueLoader */ \"./src/vueLoader.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n/* harmony import */ var _styles_common_scss__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./styles/common.scss */ \"./src/styles/common.scss\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n//-- aircox\n\n\n\n\n\nwindow.aircox = {\n // main application\n loader: null,\n get app() {\n return this.loader.app;\n },\n // player application\n playerLoader: null,\n get playerApp() {\n return this.playerLoader && this.playerLoader.app;\n },\n get player() {\n return this.playerLoader.vm && this.playerLoader.vm.$refs.player;\n },\n Set: _model__WEBPACK_IMPORTED_MODULE_4__.Set,\n Sound: _sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n /**\n * Initialize main application and player.\n */\n init(props = null, {\n hotReload = false,\n el = null,\n config = null,\n playerConfig = null,\n initApp = true,\n initPlayer = true,\n loader = null,\n playerLoader = null\n } = {}) {\n if (initPlayer) {\n playerConfig = playerConfig || _app__WEBPACK_IMPORTED_MODULE_1__.PlayerApp;\n playerLoader = playerLoader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"](playerConfig);\n playerLoader.enable(false);\n this.playerLoader = playerLoader;\n document.addEventListener(\"keyup\", e => this.onKeyPress(e), false);\n }\n if (initApp) {\n config = config || window.App || _app__WEBPACK_IMPORTED_MODULE_1__[\"default\"];\n config.el = el || config.el;\n loader = loader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"]({\n el,\n props,\n ...config\n });\n loader.enable(hotReload);\n this.loader = loader;\n }\n },\n onKeyPress(event) {\n if (event.key == \" \") {\n this.player.togglePlay();\n event.stopPropagation();\n }\n },\n /**\n * Filter navbar dropdown menu items\n */\n filter_menu(event) {\n var filter = new RegExp(event.target.value, 'gi');\n var container = event.target.closest('.navbar-dropdown');\n if (event.target.value) for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;else for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = null;\n },\n pickDate(url, date) {\n url = `${url}?date=${date.id}`;\n this.loader.pageLoad.load(url);\n }\n};\n\n//# sourceURL=webpack://aircox-assets/./src/index.js?"); +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _fortawesome_fontawesome_free_css_all_min_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @fortawesome/fontawesome-free/css/all.min.css */ \"./node_modules/@fortawesome/fontawesome-free/css/all.min.css\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _vueLoader__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./vueLoader */ \"./src/vueLoader.js\");\n/* harmony import */ var _sound__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./sound */ \"./src/sound.js\");\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n/* harmony import */ var _styles_common_scss__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./styles/common.scss */ \"./src/styles/common.scss\");\n/**\n * This module includes code available for both the public website and\n * administration interface)\n */\n//-- vendor\n\n\n//-- aircox\n\n\n\n\n\nwindow.aircox = {\n // main application\n loader: null,\n get app() {\n return this.loader.app;\n },\n // player application\n playerLoader: null,\n get playerApp() {\n return this.playerLoader && this.playerLoader.app;\n },\n get player() {\n return this.playerLoader.vm && this.playerLoader.vm.$refs.player;\n },\n Set: _model__WEBPACK_IMPORTED_MODULE_4__.Set,\n Sound: _sound__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n /**\n * Initialize main application and player.\n */\n init(props = null, {\n hotReload = false,\n el = null,\n config = null,\n playerConfig = null,\n initApp = true,\n initPlayer = true,\n loader = null,\n playerLoader = null\n } = {}) {\n if (initPlayer) {\n playerConfig = playerConfig || _app__WEBPACK_IMPORTED_MODULE_1__.PlayerApp;\n playerLoader = playerLoader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"](playerConfig);\n playerLoader.enable(false);\n this.playerLoader = playerLoader;\n document.addEventListener(\"keyup\", e => this.onKeyPress(e), false);\n }\n if (initApp) {\n config = config || window.App || _app__WEBPACK_IMPORTED_MODULE_1__[\"default\"];\n config.el = el || config.el;\n loader = loader || new _vueLoader__WEBPACK_IMPORTED_MODULE_2__[\"default\"]({\n el,\n props,\n ...config\n });\n loader.enable(hotReload);\n this.loader = loader;\n }\n },\n onKeyPress( /*event*/\n ) {\n /*\n if(event.key == \" \") {\n this.player.togglePlay()\n event.stopPropagation()\n }\n */\n },\n /**\n * Filter navbar dropdown menu items\n */\n filter_menu(event) {\n var filter = new RegExp(event.target.value, 'gi');\n var container = event.target.closest('.navbar-dropdown');\n if (event.target.value) for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = item.innerHTML.search(filter) == -1 ? 'none' : null;else for (let item of container.querySelectorAll('a.navbar-item')) item.style.display = null;\n },\n pickDate(url, date) {\n url = `${url}?date=${date.id}`;\n this.loader.pageLoad.load(url);\n }\n};\n\n//# sourceURL=webpack://aircox-assets/./src/index.js?"); /***/ }), diff --git a/aircox/templates/admin/aircox/playlist_inline.html b/aircox/templates/admin/aircox/playlist_inline.html index 5c02148..0aa47b6 100644 --- a/aircox/templates/admin/aircox/playlist_inline.html +++ b/aircox/templates/admin/aircox/playlist_inline.html @@ -8,8 +8,8 @@ {{ admin_formset.non_form_errors }}