.*)$"
- )
-
- def _into_name(self, name):
- name = name.replace("_", " ")
- return " ".join(r.capitalize() for r in name.split(" "))
-
- def read_file_info(self):
- """Read file information and metadata."""
- try:
- if os.path.exists(self.path):
- return mutagen.File(self.path)
- except Exception:
- pass
- return None
-
- def find_episode(self, sound, path_info):
- """For a given program, check if there is an initial diffusion to
- associate to, using the date info we have. Update self.sound and save
- it consequently.
-
- We only allow initial diffusion since there should be no rerun.
- """
- program, pi = sound.program, path_info
- if "year" not in pi or not sound or sound.episode:
- return None
-
- year, month, day = pi.get("year"), pi.get("month"), pi.get("day")
- if pi.get("hour") is not None:
- at = tz.datetime(year, month, day, pi.get("hour", 0), pi.get("minute", 0))
- at = tz.make_aware(at)
- else:
- at = date(year, month, day)
-
- diffusion = program.diffusion_set.at(at).first()
- if not diffusion:
- return None
-
- logger.debug("%s <--> %s", sound.file.name, str(diffusion.episode))
- return diffusion.episode
-
- def find_playlist(self, sound=None, use_meta=True):
- """Find a playlist file corresponding to the sound path, such as:
- my_sound.ogg => my_sound.csv.
-
- Use sound's file metadata if no corresponding playlist has been
- found and `use_meta` is True.
- """
- if sound is None:
- sound = self.sound
- if sound.track_set.count() > 1:
- return
-
- # import playlist
- path_noext, ext = os.path.splitext(self.sound.file.path)
- path = path_noext + ".csv"
- if os.path.exists(path):
- PlaylistImport(path, sound=sound).run()
- # use metadata
- elif use_meta:
- if self.info is None:
- self.read_file_info()
- if self.info and self.info.tags:
- tags = self.info.tags
- title, artist, album, year = tuple(
- t and ", ".join(t) for t in (tags.get(k) for k in ("title", "artist", "album", "year"))
- )
- title = title or (self.path_info and self.path_info.get("name")) or os.path.basename(path_noext)
- info = "{} ({})".format(album, year) if album and year else album or year or ""
- track = Track(
- sound=sound,
- position=int(tags.get("tracknumber", 0)),
- title=title,
- artist=artist or _("unknown"),
- info=info,
- )
- track.save()
+ if sound := Sound.objects.path(self.path).first():
+ sound.is_removed = True
+ sound.save(sync=False)
+ elif sound := Sound.objects.path(self.path):
+ sound.delete()
+ return sound
diff --git a/aircox/controllers/sound_monitor.py b/aircox/controllers/sound_monitor.py
index b7116a7..0822ed0 100644
--- a/aircox/controllers/sound_monitor.py
+++ b/aircox/controllers/sound_monitor.py
@@ -105,8 +105,7 @@ class MoveTask(Task):
def __call__(self, event, **kw):
sound = Sound.objects.filter(file=event.src_path).first()
if sound:
- kw["sound"] = sound
- kw["path"] = event.src_path
+ kw = {**kw, "sound": sound, "path": event.src_path}
else:
kw["path"] = event.dest_path
return super().__call__(event, **kw)
@@ -214,15 +213,15 @@ class SoundMonitor:
logger.info(f"#{program.id} {program.title}")
self.scan_for_program(
program,
- settings.SOUND_ARCHIVES_SUBDIR,
+ settings.SOUND_BROADCASTS_SUBDIR,
logger=logger,
- type=Sound.TYPE_ARCHIVE,
+ broadcast=True,
)
self.scan_for_program(
program,
settings.SOUND_EXCERPTS_SUBDIR,
logger=logger,
- type=Sound.TYPE_EXCERPT,
+ broadcast=False,
)
dirs.append(program.abspath)
return dirs
@@ -255,7 +254,7 @@ class SoundMonitor:
"""Only check for the sound existence or update."""
# check files
for sound in qs:
- if sound.check_on_file():
+ if sound.sync_fs(on_update=True):
SoundFile(sound.file.path).sync(sound=sound, **sync_kwargs)
_running = False
@@ -267,15 +266,15 @@ class SoundMonitor:
"""Run in monitor mode."""
with futures.ThreadPoolExecutor() as pool:
archives_handler = MonitorHandler(
- settings.SOUND_ARCHIVES_SUBDIR,
+ settings.SOUND_BROADCASTS_SUBDIR,
pool,
- type=Sound.TYPE_ARCHIVE,
+ broadcast=True,
logger=logger,
)
excerpts_handler = MonitorHandler(
settings.SOUND_EXCERPTS_SUBDIR,
pool,
- type=Sound.TYPE_EXCERPT,
+ broadcast=False,
logger=logger,
)
diff --git a/aircox/filters.py b/aircox/filters.py
index 2a3fb9f..be6a9a5 100644
--- a/aircox/filters.py
+++ b/aircox/filters.py
@@ -50,13 +50,13 @@ class ImageFilterSet(filters.FilterSet):
class SoundFilterSet(filters.FilterSet):
station = filters.NumberFilter(field_name="program__station__id")
program = filters.NumberFilter(field_name="program_id")
- episode = filters.NumberFilter(field_name="episode_id")
+ # episode = filters.NumberFilter(field_name="episode_id")
search = filters.CharFilter(field_name="search", method="search_filter")
class Meta:
model = models.Sound
fields = {
- "episode": ["in", "exact", "isnull"],
+ # "episode": ["in", "exact", "isnull"],
}
def search_filter(self, queryset, name, value):
diff --git a/aircox/forms.py b/aircox/forms.py
index 488c4e1..ed43a04 100644
--- a/aircox/forms.py
+++ b/aircox/forms.py
@@ -5,7 +5,7 @@ from django.forms.models import modelformset_factory
from aircox import models
-__all__ = ("CommentForm", "PageForm", "ProgramForm", "EpisodeForm", "SoundForm", "SoundFormSet", "TrackFormSet")
+__all__ = ("CommentForm", "PageForm", "ProgramForm", "EpisodeForm", "SoundForm", "EpisodeSoundFormSet", "TrackFormSet")
class CommentForm(forms.ModelForm):
@@ -44,23 +44,12 @@ class EpisodeForm(PageForm):
fields = PageForm.Meta.fields
-# def save(self, commit=True):
-# file_obj = self.cleaned_data["new_podcast"]
-# if file_obj:
-# obj, _ = File.objects.get_or_create(original_filename=file_obj.name, file=file_obj)
-# sound_file = SoundFile(obj.path)
-# sound_file.sync(
-# program=self.instance.program, episode=self.instance, type=0, is_public=True, is_downloadable=True
-# )
-# super().save(commit=commit)
-
-
class SoundForm(forms.ModelForm):
"""SoundForm used in EpisodeUpdateView."""
class Meta:
model = models.Sound
- fields = ["name", "program", "episode", "file", "type", "position", "duration", "is_public", "is_downloadable"]
+ fields = ["name", "program", "file", "broadcast", "duration", "is_public", "is_downloadable"]
class SoundCreateForm(forms.ModelForm):
@@ -68,33 +57,39 @@ class SoundCreateForm(forms.ModelForm):
class Meta:
model = models.Sound
- fields = ["name", "episode", "program", "file", "type", "is_public", "is_downloadable"]
+ fields = ["name", "program", "file", "broadcast", "is_public", "is_downloadable"]
TrackFormSet = modelformset_factory(
models.Track,
fields=[
+ "episode",
"position",
"artist",
"title",
"tags",
],
+ widgets={"episode": forms.HiddenInput(), "position": forms.HiddenInput()},
can_delete=True,
extra=0,
)
"""Track formset used in EpisodeUpdateView."""
-SoundFormSet = modelformset_factory(
- models.Sound,
- fields=[
+
+EpisodeSoundFormSet = modelformset_factory(
+ models.EpisodeSound,
+ fields=(
+ "episode",
+ "sound",
"position",
- "name",
- "type",
- "is_public",
- "is_downloadable",
- "duration",
- ],
+ "broadcast",
+ ),
+ widgets={
+ "broadcast": forms.CheckboxInput(),
+ "episode": forms.HiddenInput(),
+ "sound": forms.HiddenInput(),
+ "position": forms.HiddenInput(),
+ },
can_delete=True,
extra=0,
)
-"""Sound formset used in EpisodeUpdateView."""
diff --git a/aircox/models/__init__.py b/aircox/models/__init__.py
index 00f6673..34b778a 100644
--- a/aircox/models/__init__.py
+++ b/aircox/models/__init__.py
@@ -1,7 +1,7 @@
from . import signals
from .article import Article
from .diffusion import Diffusion, DiffusionQuerySet
-from .episode import Episode
+from .episode import Episode, EpisodeSound
from .log import Log, LogQuerySet
from .page import Category, Comment, NavItem, Page, PageQuerySet, StaticPage
from .program import Program, ProgramChildQuerySet, ProgramQuerySet, Stream
@@ -14,16 +14,17 @@ from .user_settings import UserSettings
__all__ = (
"signals",
"Article",
- "Episode",
+ "Category",
+ "Comment",
"Diffusion",
"DiffusionQuerySet",
+ "Episode",
+ "EpisodeSound",
"Log",
"LogQuerySet",
- "Category",
"PageQuerySet",
"Page",
"StaticPage",
- "Comment",
"NavItem",
"Program",
"ProgramQuerySet",
diff --git a/aircox/models/diffusion.py b/aircox/models/diffusion.py
index 2bb7948..c04d1c0 100644
--- a/aircox/models/diffusion.py
+++ b/aircox/models/diffusion.py
@@ -200,31 +200,7 @@ class Diffusion(Rerun):
@property
def is_live(self):
"""True if Diffusion is live (False if there are sounds files)."""
- return self.type == self.TYPE_ON_AIR and not self.episode.sound_set.archive().count()
-
- def get_playlist(self, **types):
- """Returns sounds as a playlist (list of *local* archive file path).
-
- The given arguments are passed to ``get_sounds``.
- """
- from .sound import Sound
-
- return list(
- self.get_sounds(**types).filter(path__isnull=False, type=Sound.TYPE_ARCHIVE).values_list("path", flat=True)
- )
-
- def get_sounds(self, **types):
- """Return a queryset of sounds related to this diffusion, ordered by
- type then path.
-
- **types: filter on the given sound types name, as `archive=True`
- """
- from .sound import Sound
-
- sounds = (self.initial or self).sound_set.order_by("type", "path")
- _in = [getattr(Sound.Type, name) for name, value in types.items() if value]
-
- return sounds.filter(type__in=_in)
+ return self.type == self.TYPE_ON_AIR and self.episode.episodesound_set.all().broadcast().empty()
def is_date_in_range(self, date=None):
"""Return true if the given date is in the diffusion's start-end
diff --git a/aircox/models/episode.py b/aircox/models/episode.py
index 4f05926..ea7b4f3 100644
--- a/aircox/models/episode.py
+++ b/aircox/models/episode.py
@@ -1,18 +1,21 @@
+import os
+
+from django.db import models
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
-from easy_thumbnails.files import get_thumbnailer
from aircox.conf import settings
from .page import Page
from .program import ProgramChildQuerySet
+from .sound import Sound
__all__ = ("Episode",)
class EpisodeQuerySet(ProgramChildQuerySet):
def with_podcasts(self):
- return self.filter(sound__is_public=True).distinct()
+ return self.filter(episodesound__sound__is_public=True).distinct()
class Episode(Page):
@@ -32,39 +35,21 @@ class Episode(Page):
@cached_property
def podcasts(self):
"""Return serialized data about podcasts."""
- from ..serializers import PodcastSerializer
-
- query = self.sound_set.public().order_by("type")
- return self._to_podcasts(query, PodcastSerializer)
+ query = self.episodesound_set.all().public().order_by("-broadcast", "position")
+ return self._to_podcasts(query)
@cached_property
def sounds(self):
"""Return serialized data about all related sounds."""
- from ..serializers import SoundSerializer
+ query = self.episodesound_set.all().order_by("-broadcast", "position")
+ return self._to_podcasts(query)
- query = self.sound_set.order_by("type")
- return self._to_podcasts(query, SoundSerializer)
+ def _to_podcasts(self, query):
+ from ..serializers import EpisodeSoundSerializer as serializer_class
- def _to_podcasts(self, items, serializer_class):
- from .sound import Sound
-
- podcasts = [serializer_class(s).data for s in items]
- if self.cover:
- options = {"size": (128, 128), "crop": "scale"}
- cover = get_thumbnailer(self.cover).get_thumbnail(options).url
- else:
- cover = None
-
- archive_index = 1
+ query = query.select_related("sound")
+ podcasts = [serializer_class(s).data for s in query]
for index, podcast in enumerate(podcasts):
- if podcast["type"] == Sound.TYPE_ARCHIVE:
- if archive_index > 1:
- 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()
podcasts[index]["page_title"] = self.title
return podcasts
@@ -102,3 +87,55 @@ class Episode(Page):
else title
)
return super().get_init_kwargs_from(page, title=title, program=page, **kwargs)
+
+
+class EpisodeSoundQuerySet(models.QuerySet):
+ def episode(self, episode):
+ if isinstance(episode, int):
+ return self.filter(episode_id=episode)
+ return self.filter(episode=episode)
+
+ def available(self):
+ return self.filter(sound__is_removed=False)
+
+ def public(self):
+ return self.filter(sound__is_public=True)
+
+ def broadcast(self):
+ return self.available().filter(broadcast=True)
+
+ def playlist(self, order="position"):
+ if order:
+ self = self.order_by(order)
+ return [
+ os.path.join(settings.MEDIA_ROOT, file)
+ for file in self.filter(file__isnull=False, is_removed=False).Values_list("file", flat=True)
+ ]
+
+
+class EpisodeSound(models.Model):
+ """Element of an episode playlist."""
+
+ episode = models.ForeignKey(Episode, on_delete=models.CASCADE)
+ sound = models.ForeignKey(Sound, on_delete=models.CASCADE)
+ position = models.PositiveSmallIntegerField(
+ _("order"),
+ default=0,
+ help_text=_("position in the playlist"),
+ )
+ broadcast = models.BooleanField(
+ _("Broadcast"),
+ blank=None,
+ help_text=_("The sound is broadcasted on air"),
+ )
+
+ objects = EpisodeSoundQuerySet.as_manager()
+
+ class Meta:
+ verbose_name = _("Episode Sound")
+ verbose_name_plural = _("Episode Sounds")
+
+ def save(self, *args, **kwargs):
+ if self.broadcast is None:
+ self.broadcast = self.sound.broadcast
+ super().save(*args, **kwargs)
diff --git a/aircox/models/file.py b/aircox/models/file.py
new file mode 100644
index 0000000..24ff1c3
--- /dev/null
+++ b/aircox/models/file.py
@@ -0,0 +1,150 @@
+import os
+from pathlib import Path
+
+from django.conf import settings
+from django.db import models
+from django.db.models import Q
+from django.utils.translation import gettext_lazy as _
+from django.utils import timezone as tz
+
+from .program import Program
+
+
+class FileQuerySet(models.QuerySet):
+ def station(self, station=None, id=None):
+ id = station.pk if id is None else id
+ return self.filter(program__station__id=id)
+
+ def available(self):
+ return self.exclude(is_removed=False)
+
+ def public(self):
+ """Return sounds available as podcasts."""
+ return self.filter(is_public=True)
+
+ def path(self, paths):
+ if isinstance(paths, str):
+ return self.filter(file=paths.replace(settings.MEDIA_ROOT + "/", ""))
+ return self.filter(file__in=(p.replace(settings.MEDIA_ROOT + "/", "") for p in paths))
+
+ def search(self, query):
+ return self.filter(Q(name__icontains=query) | Q(file__icontains=query) | Q(program__title__icontains=query))
+
+
+class File(models.Model):
+ def _upload_to(self, filename):
+ dir = self.program and self.program.path or self.default_upload_path
+ subdir = self.get_upload_dir()
+ if subdir:
+ return os.path.join(dir, subdir, filename)
+ return os.path.join(dir, filename)
+
+ program = models.ForeignKey(
+ Program,
+ models.SET_NULL,
+ verbose_name=_("Program"),
+ null=True,
+ blank=True,
+ )
+ file = models.FileField(
+ _("file"),
+ upload_to=_upload_to,
+ max_length=256,
+ db_index=True,
+ )
+ name = models.CharField(
+ _("name"),
+ max_length=64,
+ db_index=True,
+ )
+ description = models.TextField(
+ _("description"),
+ max_length=256,
+ blank=True,
+ default="",
+ )
+ mtime = models.DateTimeField(
+ _("modification time"),
+ blank=True,
+ null=True,
+ help_text=_("last modification date and time"),
+ )
+ is_public = models.BooleanField(
+ _("public"),
+ help_text=_("file is publicly accessible"),
+ default=False,
+ )
+ is_removed = models.BooleanField(
+ _("removed"),
+ help_text=_("file has been removed from server"),
+ default=False,
+ db_index=True,
+ )
+
+ class Meta:
+ abstract = True
+
+ objects = FileQuerySet.as_manager()
+
+ default_upload_path = Path(settings.MEDIA_ROOT)
+ """Default upload directory when no program is provided."""
+ upload_dir = "uploads"
+ """Upload sub-directory."""
+
+ @property
+ def url(self):
+ return self.file and self.file.url
+
+ def get_upload_dir(self):
+ return self.upload_dir
+
+ def get_mtime(self):
+ """Get the last modification date from file."""
+ mtime = os.stat(self.file.path).st_mtime
+ mtime = tz.datetime.fromtimestamp(mtime)
+ mtime = mtime.replace(microsecond=0)
+ return tz.make_aware(mtime, tz.get_current_timezone())
+
+ def file_updated(self):
+ """Return True when file has been updated on filesystem."""
+ return self.mtime != self.get_mtime() or self.is_removed != (not self.file_exists())
+
+ def file_exists(self):
+ """Return true if the file still exists."""
+ return os.path.exists(self.file.path)
+
+ def sync_fs(self, on_update=False):
+ """Sync model to file on the filesystem.
+
+ :param bool on_update: only check if `file_updated`.
+ :return True wether a change happened.
+ """
+ if on_update and not self.file_updated():
+ return
+
+ # check on name/remove/modification time
+ name = self.name
+ if not self.name and self.file and self.file.name:
+ name = os.path.basename(self.file.name)
+ name = os.path.splitext(name)[0]
+ name = name.replace("_", " ").strip()
+
+ is_removed = not self.file_exists()
+ mtime = self.get_mtime()
+
+ changed = is_removed != self.is_removed or mtime != self.mtime or name != self.name
+ self.name, self.is_removed, self.mtime = name, is_removed, mtime
+
+ # read metadata
+ if changed and not self.is_removed:
+ metadata = self.read_metadata()
+ metadata and self.__dict__.update(metadata)
+ return changed
+
+ def read_metadata(self):
+ return {}
+
+ def save(self, sync=True, *args, **kwargs):
+ if sync and self.file_exists():
+ self.sync_fs(on_update=True)
+ super().save(*args, **kwargs)
diff --git a/aircox/models/page.py b/aircox/models/page.py
index 327d3bd..0581fd1 100644
--- a/aircox/models/page.py
+++ b/aircox/models/page.py
@@ -183,10 +183,14 @@ class BasePage(Renderable, models.Model):
headline[-1] += suffix
return mark_safe(" ".join(headline))
- _url_re = re.compile("(https?://[^\s\n]+)")
+ _url_re = re.compile(
+ "((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\.([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*"
+ )
@cached_property
def display_content(self):
+ if "" in self.content:
+ return self.content
content = self._url_re.sub(r'\1 ', self.content)
return content.replace("\n\n", "\n").replace("\n", " ")
diff --git a/aircox/models/sound.py b/aircox/models/sound.py
index 243a8db..b3f807e 100644
--- a/aircox/models/sound.py
+++ b/aircox/models/sound.py
@@ -1,64 +1,41 @@
-import logging
+from datetime import date
import os
+import re
from django.conf import settings as conf
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 aircox import utils
from aircox.conf import settings
-from .episode import Episode
from .program import Program
-
-logger = logging.getLogger("aircox")
+from .file import File, FileQuerySet
+from .track import Track
+from .controllers.playlist_import import PlaylistImport
__all__ = ("Sound", "SoundQuerySet")
-class SoundQuerySet(models.QuerySet):
- def station(self, station=None, id=None):
- id = station.pk if id is None else id
- return self.filter(program__station__id=id)
-
- def episode(self, episode=None, id=None):
- id = episode.pk if id is None else id
- return self.filter(episode__id=id)
-
- def diffusion(self, diffusion=None, id=None):
- id = diffusion.pk if id is None else id
- return self.filter(episode__diffusion__id=id)
-
- def available(self):
- return self.exclude(is_removed=False)
-
- def public(self):
- """Return sounds available as podcasts."""
- return self.filter(is_public=True)
-
+class SoundQuerySet(FileQuerySet):
def downloadable(self):
"""Return sounds available as podcasts."""
return self.filter(is_downloadable=True)
- def archive(self):
+ def broadcast(self):
"""Return sounds that are archives."""
- return self.filter(type=Sound.TYPE_ARCHIVE)
+ return self.filter(broadcast=True)
- def path(self, paths):
- if isinstance(paths, str):
- return self.filter(file=paths.replace(conf.MEDIA_ROOT + "/", ""))
- return self.filter(file__in=(p.replace(conf.MEDIA_ROOT + "/", "") for p in paths))
-
- def playlist(self, archive=True, order_by=True):
+ def playlist(self, broadcast=True, order_by=True):
"""Return files absolute paths as a flat list (exclude sound without
path).
If `order_by` is True, order by path.
"""
- if archive:
- self = self.archive()
+ if broadcast:
+ self = self.broadcast()
if order_by:
self = self.order_by("file")
return [
@@ -66,175 +43,147 @@ class SoundQuerySet(models.QuerySet):
for file in self.filter(file__isnull=False).values_list("file", flat=True)
]
- def search(self, query):
- return self.filter(
- Q(name__icontains=query)
- | Q(file__icontains=query)
- | Q(program__title__icontains=query)
- | Q(episode__title__icontains=query)
- )
-
-# TODO:
-# - provide a default name based on program and episode
-class Sound(models.Model):
- """A Sound is the representation of a sound file that can be either an
- excerpt or a complete archive of the related diffusion."""
-
- TYPE_OTHER = 0x00
- TYPE_ARCHIVE = 0x01
- TYPE_EXCERPT = 0x02
- TYPE_CHOICES = (
- (TYPE_OTHER, _("other")),
- (TYPE_ARCHIVE, _("archive")),
- (TYPE_EXCERPT, _("excerpt")),
- )
-
- name = models.CharField(_("name"), max_length=64)
- program = models.ForeignKey(
- Program,
- models.CASCADE,
- blank=True, # NOT NULL
- verbose_name=_("program"),
- help_text=_("program related to it"),
- db_index=True,
- )
- episode = models.ForeignKey(
- Episode,
- models.SET_NULL,
- blank=True,
- null=True,
- verbose_name=_("episode"),
- db_index=True,
- )
- type = models.SmallIntegerField(_("type"), choices=TYPE_CHOICES)
- position = models.PositiveSmallIntegerField(
- _("order"),
- 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
- return os.path.join(self.program.path, subdir, filename)
-
- file = models.FileField(
- _("file"),
- upload_to=_upload_to,
- max_length=256,
- db_index=True,
- unique=True,
- )
+class Sound(File):
duration = models.TimeField(
_("duration"),
blank=True,
null=True,
help_text=_("duration of the sound"),
)
- mtime = models.DateTimeField(
- _("modification time"),
- blank=True,
- null=True,
- help_text=_("last modification date and time"),
- )
is_good_quality = models.BooleanField(
_("good quality"),
help_text=_("sound meets quality requirements"),
blank=True,
null=True,
)
- is_public = models.BooleanField(
- _("public"),
- help_text=_("sound is available as podcast"),
- default=False,
- )
is_downloadable = models.BooleanField(
_("downloadable"),
- help_text=_("sound can be downloaded by visitors (sound must be public)"),
+ help_text=_("sound can be downloaded by visitors"),
default=False,
)
-
- objects = SoundQuerySet.as_manager()
+ broadcast = models.BooleanField(
+ _("Broadcast"),
+ default=False,
+ help_text=_("The sound is broadcasted on air"),
+ )
class Meta:
- verbose_name = _("Sound")
- verbose_name_plural = _("Sounds")
+ verbose_name = _("Sound file")
+ verbose_name_plural = _("Sound files")
- @property
- def url(self):
- return self.file and self.file.url
+ _path_re = re.compile(
+ "^(?P[0-9]{4})(?P[0-9]{2})(?P[0-9]{2})"
+ "(_(?P[0-9]{2})h(?P[0-9]{2}))?"
+ "(_(?P[0-9]+))?"
+ "_?[ -]*(?P.*)$"
+ )
- def __str__(self):
- return "/".join(self.file.path.split("/")[-3:])
+ @classmethod
+ def read_path(cls, path):
+ """Parse path name returning dictionary of extracted info. It can
+ contain:
- def save(self, check=True, *args, **kwargs):
- if self.episode is not None and self.program is None:
- self.program = self.episode.program
- if check:
- self.check_on_file()
- if not self.is_public:
- self.is_downloadable = False
- self.__check_name()
- super().save(*args, **kwargs)
-
- # TODO: rename get_file_mtime(self)
- def get_mtime(self):
- """Get the last modification date from file."""
- mtime = os.stat(self.file.path).st_mtime
- mtime = tz.datetime.fromtimestamp(mtime)
- mtime = mtime.replace(microsecond=0)
- return tz.make_aware(mtime, tz.get_current_timezone())
-
- def file_exists(self):
- """Return true if the file still exists."""
-
- return os.path.exists(self.file.path)
-
- # TODO: rename to sync_fs()
- def check_on_file(self):
- """Check sound file info again'st self, and update informations if
- needed (do not save).
-
- Return True if there was changes.
+ - `year`, `month`, `day`: diffusion date
+ - `hour`, `minute`: diffusion time
+ - `n`: sound arbitrary number (used for sound ordering)
+ - `name`: cleaned name extracted or file name (without extension)
"""
- if not self.file_exists():
- if self.is_removed:
- return
- logger.debug("sound %s: has been removed", self.file.name)
- self.is_removed = True
- return True
+ basename = os.path.basename(path)
+ basename = os.path.splitext(basename)[0]
+ reg_match = cls._path_re.search(basename)
+ if reg_match:
+ info = reg_match.groupdict()
+ for k in ("year", "month", "day", "hour", "minute", "n"):
+ if info.get(k) is not None:
+ info[k] = int(info[k])
- # not anymore removed
- changed = False
+ name = info.get("name")
+ info["name"] = name and cls._as_name(name) or basename
+ else:
+ info = {"name": basename}
+ return info
- 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
+ @classmethod
+ def _as_name(cls, name):
+ name = name.replace("_", " ")
+ return " ".join(r.capitalize() for r in name.split(" "))
+
+ def find_episode(self, path_info=None):
+ """Base on self's file name, match date to an initial diffusion and
+ return corresponding episode or ``None``."""
+ pi = path_info or self.read_path(self.file.path)
+ if "year" not in pi:
+ return None
+
+ year, month, day = pi.get("year"), pi.get("month"), pi.get("day")
+ if pi.get("hour") is not None:
+ at = tz.datetime(year, month, day, pi.get("hour", 0), pi.get("minute", 0))
+ at = tz.make_aware(at)
+ else:
+ at = date(year, month, day)
+
+ diffusion = self.program.diffusion_set.at(at).first()
+ return diffusion and diffusion.episode or None
+
+ def find_playlist(self, meta=None):
+ """Find a playlist file corresponding to the sound path, such as:
+ my_sound.ogg => my_sound.csv.
+
+ Use provided sound's metadata if any and no csv file has been
+ found.
+ """
+ if self.track_set.count() > 1:
+ return
+
+ # import playlist
+ path_noext, ext = os.path.splitext(self.file.path)
+ path = path_noext + ".csv"
+ if os.path.exists(path):
+ PlaylistImport(path, sound=self).run()
+ # use metadata
+ elif meta and meta.tags:
+ title, artist, album, year = tuple(
+ t and ", ".join(t) for t in (meta.tags.get(k) for k in ("title", "artist", "album", "year"))
)
-
- # check mtime -> reset quality if changed (assume file changed)
- mtime = self.get_mtime()
-
- if self.mtime != mtime:
- self.mtime = mtime
- self.is_good_quality = None
- logger.debug(
- "sound %s: m_time has changed. Reset quality info",
- self.file.name,
+ title = title or path_noext
+ info = "{} ({})".format(album, year) if album and year else album or year or ""
+ track = Track(
+ sound=self,
+ position=int(meta.tags.get("tracknumber", 0)),
+ title=title,
+ artist=artist or _("unknown"),
+ info=info,
)
- return True
+ track.save()
+ def get_upload_dir(self):
+ if self.broadcast:
+ return settings.SOUND_BROADCASTS_SUBDIR
+ return settings.SOUND_EXCERPTS_SUBDIR
+
+ meta = None
+ """Provided by read_metadata: Mutagen's metadata."""
+
+ def sync_fs(self, *args, find_playlist=False, **kwargs):
+ changed = super().sync_fs(*args, **kwargs)
+ if changed and not self.is_removed:
+ if not self.program:
+ self.program = Program.get_from_path(self.file.path)
+ changed = True
+ if find_playlist and self.meta:
+ not self.pk and self.save(sync=False)
+ self.find_playlist(self.meta)
return changed
- def __check_name(self):
- if not self.name and self.file and self.file.name:
- # FIXME: later, remove date?
- name = os.path.basename(self.file.name)
- name = os.path.splitext(name)[0]
- self.name = name.replace("_", " ").strip()
+ def read_metadata(self):
+ import mutagen
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.__check_name()
+ meta = mutagen.File(self.file.path)
+
+ metadata = {"duration": utils.seconds_to_time(meta.info.length), "meta": meta}
+
+ path_info = self.read_path(self.file.path)
+ if name := path_info.get("name"):
+ metadata["name"] = name
+ return metadata
diff --git a/aircox/serializers/__init__.py b/aircox/serializers/__init__.py
index 531a3db..4518172 100644
--- a/aircox/serializers/__init__.py
+++ b/aircox/serializers/__init__.py
@@ -1,12 +1,14 @@
from .admin import TrackSerializer, UserSettingsSerializer
from .log import LogInfo, LogInfoSerializer
-from .sound import PodcastSerializer, SoundSerializer
+from .sound import SoundSerializer
+from .episode import EpisodeSoundSerializer, EpisodeSerializer
__all__ = (
- "TrackSerializer",
- "UserSettingsSerializer",
"LogInfo",
"LogInfoSerializer",
+ "EpisodeSoundSerializer",
+ "EpisodeSerializer",
"SoundSerializer",
- "PodcastSerializer",
+ "TrackSerializer",
+ "UserSettingsSerializer",
)
diff --git a/aircox/serializers/episode.py b/aircox/serializers/episode.py
new file mode 100644
index 0000000..fd413f5
--- /dev/null
+++ b/aircox/serializers/episode.py
@@ -0,0 +1,36 @@
+from rest_framework import serializers
+
+from .. import models
+from .sound import SoundSerializer
+from .admin import TrackSerializer
+
+
+class EpisodeSoundSerializer(serializers.ModelSerializer):
+ sound = SoundSerializer(read_only=True)
+
+ class Meta:
+ model = models.EpisodeSound
+ fields = [
+ "id",
+ "position",
+ "episode",
+ "broadcast",
+ "sound",
+ "sound_id",
+ ]
+
+
+class EpisodeSerializer(serializers.ModelSerializer):
+ playlist = EpisodeSoundSerializer(source="episodesound_set", many=True, read_only=True)
+ tracks = TrackSerializer(source="track_set", many=True, read_only=True)
+
+ class Meta:
+ model = models.Episode
+ fields = [
+ "id",
+ "title",
+ "content",
+ "pub_date",
+ "playlist",
+ "tracks",
+ ]
diff --git a/aircox/serializers/sound.py b/aircox/serializers/sound.py
index 6911310..37b5eb4 100644
--- a/aircox/serializers/sound.py
+++ b/aircox/serializers/sound.py
@@ -1,23 +1,19 @@
from rest_framework import serializers
-from ..models import Sound
+from .. import models
-__all__ = ("SoundSerializer", "PodcastSerializer")
+__all__ = ("SoundSerializer",)
class SoundSerializer(serializers.ModelSerializer):
file = serializers.FileField(use_url=False)
- type_display = serializers.SerializerMethodField()
class Meta:
- model = Sound
+ model = models.Sound
fields = [
- "pk",
+ "id",
"name",
"program",
- "episode",
- "type",
- "type_display",
"file",
"duration",
"mtime",
@@ -26,24 +22,3 @@ class SoundSerializer(serializers.ModelSerializer):
"is_downloadable",
"url",
]
-
- def get_type_display(self, obj):
- return obj.get_type_display()
-
-
-class PodcastSerializer(serializers.ModelSerializer):
- # serializers.HyperlinkedIdentityField(view_name='sound', format='html')
-
- class Meta:
- model = Sound
- fields = [
- "pk",
- "name",
- "program",
- "episode",
- "type",
- "duration",
- "mtime",
- "url",
- "is_downloadable",
- ]
diff --git a/aircox/static/aircox/js/chunk-common.js b/aircox/static/aircox/js/chunk-common.js
index c2f5829..2dfcc90 100644
--- a/aircox/static/aircox/js/chunk-common.js
+++ b/aircox/static/aircox/js/chunk-common.js
@@ -65,7 +65,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _mod
\*********************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n emit: [\"fileChange\", \"load\"],\n props: {\n url: {\n type: String\n },\n fieldName: {\n type: String,\n default: \"file\"\n },\n label: {\n type: String,\n default: \"Select a file\"\n },\n submitLabel: {\n type: String,\n default: \"Upload\"\n }\n },\n data() {\n return {\n STATE: {\n DEFAULT: 0,\n UPLOADING: 1\n },\n state: 0,\n upload: {},\n file: null,\n fileUrl: null,\n total: 0,\n loaded: 0,\n request: null\n };\n },\n methods: {\n abort() {\n this.request && this.request.abort();\n },\n onFileChange() {\n const [file] = this.$refs.uploadFile.files;\n if (!file) return;\n this._setUploadFile(file);\n this.$emit(\"fileChange\", {\n upload: this,\n file: this.file,\n fileUrl: this.fileUrl\n });\n },\n submit() {\n const req = new XMLHttpRequest();\n req.open(\"POST\", this.url);\n req.upload.addEventListener(\"progress\", e => this.onUploadProgress(e));\n req.addEventListener(\"load\", e => this.onUploadDone(e));\n req.addEventListener(\"abort\", e => this.onUploadDone(e));\n req.addEventListener(\"error\", e => this.onUploadDone(e));\n const formData = new FormData(this.$refs.form);\n formData.append('csrfmiddlewaretoken', (0,_model__WEBPACK_IMPORTED_MODULE_0__.getCsrf)());\n req.send(formData);\n this._resetUpload(this.STATE.UPLOADING, false, req);\n },\n onUploadProgress(event) {\n this.loaded = event.loaded;\n this.total = event.total;\n },\n onUploadDone(event) {\n this.$emit(\"load\", event);\n this._resetUpload(this.STATE.DEFAULT, true);\n },\n _setUploadFile(file) {\n this.file = file;\n this.fileURL = file && URL.createObjectURL(file);\n },\n _resetUpload(state, resetFile = false, request = null) {\n this.state = state;\n this.loaded = 0;\n this.total = 0;\n this.request = request;\n if (resetFile) this.file = null;\n }\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/AFileUpload.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");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n emit: [\"fileChange\", \"load\", \"abort\", \"error\"],\n props: {\n url: {\n type: String\n },\n fieldName: {\n type: String,\n default: \"file\"\n },\n label: {\n type: String,\n default: \"Select a file\"\n },\n submitLabel: {\n type: String,\n default: \"Upload\"\n }\n },\n data() {\n return {\n STATE: {\n DEFAULT: 0,\n UPLOADING: 1\n },\n state: 0,\n upload: {},\n file: null,\n fileUrl: null,\n total: 0,\n loaded: 0,\n request: null\n };\n },\n methods: {\n abort() {\n this.request && this.request.abort();\n },\n onFileChange() {\n const [file] = this.$refs.uploadFile.files;\n if (!file) return;\n this._setUploadFile(file);\n this.$emit(\"fileChange\", {\n upload: this,\n file: this.file,\n fileUrl: this.fileUrl\n });\n },\n submit() {\n const req = new XMLHttpRequest();\n req.open(\"POST\", this.url);\n req.upload.addEventListener(\"progress\", e => this.onUploadProgress(e));\n req.addEventListener(\"load\", e => this.onUploadDone(e, 'load'));\n req.addEventListener(\"abort\", e => this.onUploadDone(e, 'abort'));\n req.addEventListener(\"error\", e => this.onUploadDone(e, 'error'));\n const formData = new FormData(this.$refs.form);\n formData.append('csrfmiddlewaretoken', (0,_model__WEBPACK_IMPORTED_MODULE_0__.getCsrf)());\n req.send(formData);\n this._resetUpload(this.STATE.UPLOADING, false, req);\n },\n onUploadProgress(event) {\n this.loaded = event.loaded;\n this.total = event.total;\n },\n onUploadDone(event, eventName) {\n this.$emit(eventName, event);\n this._resetUpload(this.STATE.DEFAULT, true);\n },\n _setUploadFile(file) {\n this.file = file;\n this.fileURL = file && URL.createObjectURL(file);\n },\n _resetUpload(state, resetFile = false, request = null) {\n this.state = state;\n this.loaded = 0;\n this.total = 0;\n this.request = request;\n if (resetFile) this.file = null;\n }\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/AFileUpload.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");
/***/ }),
@@ -155,7 +155,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core
\*********************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n props: {\n name: {\n type: String\n },\n listClass: {\n type: String,\n default: \"\"\n },\n prevLabel: {\n type: String,\n default: \"Prev\"\n },\n nextLabel: {\n type: String,\n default: \"Next\"\n },\n listUrl: {\n type: String\n },\n uploadUrl: {\n type: String\n },\n uploadFieldName: {\n type: String,\n default: \"file\"\n },\n uploadLabel: {\n type: String,\n default: \"Upload a file\"\n }\n },\n data() {\n return {\n STATE: {\n DEFAULT: 0,\n UPLOADING: 1\n },\n state: 0,\n item: null,\n items: [],\n nextUrl: \"\",\n prevUrl: \"\",\n lastUrl: \"\",\n upload: {}\n };\n },\n methods: {\n load(url) {\n fetch(url || this.listUrl).then(response => response.ok ? response.json() : Promise.reject(response)).then(data => {\n this.lastUrl = url;\n this.nextUrl = data.next;\n this.prevUrl = data.previous;\n this.items = data.results;\n this.$forceUpdate();\n this.$refs.list.scroll(0, 0);\n });\n },\n select(item) {\n this.item = item;\n },\n // ---- upload\n uploadAbort() {\n this.upload.request && this.upload.request.abort();\n },\n onSubmit() {\n const [file] = this.$refs.uploadFile.files;\n if (!file) return;\n this._setUploadFile(file);\n const req = new XMLHttpRequest();\n req.open(\"POST\", this.uploadUrl || this.listUrl);\n req.upload.addEventListener(\"progress\", e => this.onUploadProgress(e));\n req.addEventListener(\"load\", e => this.onUploadDone(e, true));\n req.addEventListener(\"abort\", e => this.onUploadDone(e));\n req.addEventListener(\"error\", e => this.onUploadDone(e));\n const formData = new FormData(this.$refs.uploadForm);\n formData.append('csrfmiddlewaretoken', (0,_model__WEBPACK_IMPORTED_MODULE_0__.getCsrf)());\n req.send(formData);\n this._resetUpload(this.STATE.UPLOADING, false, req);\n },\n onUploadProgress(event) {\n this.upload.loaded = event.loaded;\n this.upload.total = event.total;\n },\n onUploadDone(reload = false) {\n this._resetUpload(this.STATE.DEFAULT, true);\n reload && this.load();\n },\n _setUploadFile(file) {\n this.upload.file = file;\n this.upload.fileURL = file && URL.createObjectURL(file);\n },\n _resetUpload(state, resetFile = false, request = null) {\n this.state = state;\n this.upload.loaded = 0;\n this.upload.total = 0;\n this.upload.request = request;\n if (resetFile) this.upload.file = null;\n }\n },\n mounted() {\n this.load();\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASelectFile.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");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _AModal__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./AModal */ \"./src/components/AModal.vue\");\n/* harmony import */ var _AActionButton__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./AActionButton */ \"./src/components/AActionButton.vue\");\n/* harmony import */ var _AFileUpload__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./AFileUpload */ \"./src/components/AFileUpload.vue\");\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n emit: [\"select\"],\n components: {\n AActionButton: _AActionButton__WEBPACK_IMPORTED_MODULE_1__[\"default\"],\n AFileUpload: _AFileUpload__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n AModal: _AModal__WEBPACK_IMPORTED_MODULE_0__[\"default\"]\n },\n props: {\n title: {\n type: String\n },\n labels: Object,\n listClass: {\n type: String,\n default: \"\"\n },\n // List url\n listUrl: {\n type: String\n },\n // URL to delete an item, where \"123\" is replaced by\n // the item id.\n deleteUrl: {\n type: String\n },\n uploadUrl: {\n type: String\n },\n uploadFieldName: {\n type: String,\n default: \"file\"\n },\n uploadLabel: {\n type: String,\n default: \"Upload a file\"\n }\n },\n data() {\n return {\n LIST: 0,\n UPLOAD: 1,\n panel: 0,\n item: null,\n items: [],\n nextUrl: \"\",\n prevUrl: \"\",\n lastUrl: \"\"\n };\n },\n methods: {\n open() {\n this.$refs.modal.open();\n },\n close() {\n this.$refs.modal.close();\n },\n showPanel(panel) {\n this.panel = panel;\n },\n load(url) {\n return fetch(url || this.listUrl).then(response => response.ok ? response.json() : Promise.reject(response)).then(data => {\n this.lastUrl = url;\n this.nextUrl = data.next;\n this.prevUrl = data.previous;\n this.items = data.results;\n this.showPanel(this.LIST);\n this.$forceUpdate();\n this.$refs.list.scroll(0, 0);\n return this.items;\n });\n },\n //! Select an item\n select(item) {\n this.item = item;\n },\n //! User click on select button (confirm selection)\n selected() {\n this.$emit(\"select\", this.item);\n this.close();\n },\n uploadDone(reload = false) {\n reload && this.load().then(items => {\n this.item = items[0];\n });\n }\n },\n mounted() {\n this.load();\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASelectFile.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");
/***/ }),
@@ -175,7 +175,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _mod
\**************************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash */ \"./node_modules/lodash/lodash.js\");\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _ARows__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ARows */ \"./src/components/ARows.vue\");\n/* harmony import */ var _AModal__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./AModal */ \"./src/components/AModal.vue\");\n/* harmony import */ var _AFileUpload__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./AFileUpload */ \"./src/components/AFileUpload.vue\");\n\n// import {dropRightWhile, cloneDeep, isEqual} from 'lodash'\n\n\n\n// import AActionButton from './AActionButton'\n\n\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n ARows: _ARows__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n AModal: _AModal__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n AFileUpload: _AFileUpload__WEBPACK_IMPORTED_MODULE_5__[\"default\"]\n },\n props: {\n initData: Object,\n dataPrefix: String,\n labels: Object,\n settingsUrl: String,\n soundListUrl: String,\n soundUploadUrl: String,\n player: Object,\n columns: {\n type: Array,\n default: () => ['name', \"type\", 'is_public', 'is_downloadable']\n }\n },\n data() {\n return {\n set: new _model__WEBPACK_IMPORTED_MODULE_2__.Set(_model__WEBPACK_IMPORTED_MODULE_2__[\"default\"])\n };\n },\n computed: {\n player_() {\n return this.player || window.aircox.player;\n },\n allColumns() {\n return [...this.columns, \"delete\"];\n },\n allColumnsLabels() {\n return {\n ...this.labels,\n ...this.initData.fields\n };\n },\n items() {\n return this.set.items;\n },\n rowsSlots() {\n return Object.keys(this.$slots).filter(x => x.startsWith('row-') || x.startsWith('rows-')).map(x => [x, x.startsWith('rows-') ? x.slice(5) : x]);\n }\n },\n methods: {\n listItemMove({\n from,\n to,\n set\n }) {\n set.move(from, to);\n },\n /**\n * Load initial data\n */\n loadData({\n items = [] /*, settings=null*/\n }, reset = false) {\n if (reset) {\n this.set.items = [];\n }\n for (var index in items) this.set.push((0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(items[index]));\n // if(settings)\n // this.settingsSaved(settings)\n },\n\n uploadDone(event) {\n const req = event.target;\n if (req.status == 201) {\n const item = JSON.parse(req.response);\n this.set.push(item);\n this.$refs.modal.close();\n }\n }\n },\n watch: {\n initData(val) {\n this.loadData(val);\n }\n },\n mounted() {\n this.initData && this.loadData(this.initData);\n }\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/index.js??ruleSet%5B0%5D.use%5B0%5D");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash */ \"./node_modules/lodash/lodash.js\");\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _ARows__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./ARows */ \"./src/components/ARows.vue\");\n/* harmony import */ var _ASelectFile__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ASelectFile */ \"./src/components/ASelectFile.vue\");\n\n// import {dropRightWhile, cloneDeep, isEqual} from 'lodash'\n\n\n\n//import AFileUpload from \"./AFileUpload\"\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n ARows: _ARows__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n ASelectFile: _ASelectFile__WEBPACK_IMPORTED_MODULE_4__[\"default\"]\n },\n props: {\n // default values of items\n itemDefaults: Object,\n // initial datas\n initData: Object,\n labels: Object,\n soundListUrl: String,\n soundUploadUrl: String,\n soundDeleteUrl: String,\n columns: {\n type: Array,\n default: () => ['name', \"broadcast\"]\n }\n },\n data() {\n return {\n set: new _model__WEBPACK_IMPORTED_MODULE_2__.Set(_model__WEBPACK_IMPORTED_MODULE_2__[\"default\"])\n };\n },\n computed: {\n player_() {\n return this.player || window.aircox.player;\n },\n allColumns() {\n return [\"sound\", ...this.columns, \"delete\"];\n },\n allColumnsLabels() {\n return {\n ...this.labels,\n ...this.initData.fields\n };\n },\n items() {\n return this.set.items;\n },\n rowsSlots() {\n return Object.keys(this.$slots).filter(x => x.startsWith('row-') || x.startsWith('rows-')).map(x => [x, x.startsWith('rows-') ? x.slice(5) : x]);\n }\n },\n methods: {\n listItemMove({\n from,\n to,\n set\n }) {\n set.move(from, to);\n },\n /**\n * Load initial data\n */\n loadData({\n items = [] /*, settings=null*/\n }, reset = false) {\n if (reset) {\n this.set.items = [];\n }\n for (var index in items) this.set.push((0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(items[index]));\n // if(settings)\n // this.settingsSaved(settings)\n },\n\n selected(item) {\n const data = {\n ...this.itemDefaults,\n \"sound\": item.id,\n \"name\": item.name,\n \"url\": item.url,\n \"broadcast\": item.broadcast\n };\n this.set.push(data);\n }\n },\n watch: {\n initData(val) {\n this.loadData(val);\n }\n },\n mounted() {\n this.initData && this.loadData(this.initData);\n }\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/index.js??ruleSet%5B0%5D.use%5B0%5D");
/***/ }),
@@ -215,7 +215,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony default export */
\**************************************************************************************************************************************************************************************************/
/***/ (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 */ Page: function() { return /* binding */ Page; }\n/* harmony export */ });\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash */ \"./node_modules/lodash/lodash.js\");\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _AActionButton__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AActionButton */ \"./src/components/AActionButton.vue\");\n/* harmony import */ var _ARow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ARow */ \"./src/components/ARow.vue\");\n/* harmony import */ var _ARows__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./ARows */ \"./src/components/ARows.vue\");\n/* harmony import */ var _AModal__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./AModal */ \"./src/components/AModal.vue\");\n\n\n\n\n\n\n\n\n/// Page display\nconst Page = {\n Text: 0,\n List: 1,\n Settings: 2\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n AActionButton: _AActionButton__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n ARow: _ARow__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n ARows: _ARows__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n AModal: _AModal__WEBPACK_IMPORTED_MODULE_6__[\"default\"]\n },\n props: {\n ///! initial data as: {items: [], fields: {column_name: label, settings: {}}\n initData: Object,\n dataPrefix: String,\n labels: Object,\n settingsUrl: String,\n defaultColumns: {\n type: Array,\n default: () => ['artist', 'title', 'tags', 'album', 'year', 'timestamp']\n }\n },\n data() {\n const settings = {\n tracklist_editor_columns: this.defaultColumns,\n tracklist_editor_sep: ' -- '\n };\n return {\n Page: Page,\n page: Page.Text,\n set: new _model__WEBPACK_IMPORTED_MODULE_2__.Set(_model__WEBPACK_IMPORTED_MODULE_2__[\"default\"]),\n extraData: {},\n settings,\n savedSettings: (0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(settings)\n };\n },\n computed: {\n settingsChanged() {\n var k = Object.keys(this.savedSettings).findIndex(k => !(0,lodash__WEBPACK_IMPORTED_MODULE_1__.isEqual)(this.settings[k], this.savedSettings[k]));\n return k != -1;\n },\n separator: {\n set(value) {\n this.settings.tracklist_editor_sep = value;\n if (this.page == Page.List) this.updateInput();\n },\n get() {\n return this.settings.tracklist_editor_sep;\n }\n },\n columns: {\n set(value) {\n var cols = value.filter(x => x in this.defaultColumns);\n var left = this.defaultColumns.filter(x => !(x in cols));\n value = cols.concat(left);\n this.settings.tracklist_editor_columns = value;\n },\n get() {\n return this.settings.tracklist_editor_columns;\n }\n },\n items() {\n return this.set.items;\n },\n rowsSlots() {\n return Object.keys(this.$slots).filter(x => x.startsWith('row-') || x.startsWith('rows-')).map(x => [x, x.startsWith('rows-') ? x.slice(5) : x]);\n }\n },\n methods: {\n onCellEvent(event) {\n switch (event.name) {\n case 'change':\n this.updateInput();\n break;\n }\n },\n formatMove({\n from,\n to\n }) {\n const value = this.columns[from];\n this.settings.tracklist_editor_columns.splice(from, 1);\n this.settings.tracklist_editor_columns.splice(to, 0, value);\n if (this.page == Page.Text) this.updateList();else this.updateInput();\n },\n columnMove({\n from,\n to\n }) {\n const value = this.columns[from];\n this.columns.splice(from, 1);\n this.columns.splice(to, 0, value);\n this.updateInput();\n },\n listItemMove({\n from,\n to,\n set\n }) {\n set.move(from, to);\n this.updateInput();\n },\n updateList() {\n const items = this.toList(this.$refs.textarea.value);\n this.set.reset(items);\n },\n updateInput() {\n const input = this.toText(this.items);\n this.$refs.textarea.value = input;\n },\n /**\n * From input and separator, return list of items.\n */\n toList(input) {\n var lines = input.split('\\n');\n var items = [];\n for (let line of lines) {\n line = line.trimLeft();\n if (!line) continue;\n var lineBits = line.split(this.separator);\n var item = {};\n for (var col in this.columns) {\n if (col >= lineBits.length) break;\n const attr = this.columns[col];\n item[attr] = lineBits[col].trim();\n }\n item && items.push(item);\n }\n return items;\n },\n /**\n * From items and separator return a string\n */\n toText(items) {\n const sep = ` ${this.separator.trim()} `;\n const lines = [];\n for (let item of items) {\n if (!item) continue;\n var line = [];\n for (var col of this.columns) line.push(item.data[col] || '');\n line = (0,lodash__WEBPACK_IMPORTED_MODULE_1__.dropRightWhile)(line, x => !x || !('' + x).trim());\n line = line.join(sep).trimRight();\n lines.push(line);\n }\n return lines.join('\\n');\n },\n _data_key(key) {\n key = key.slice(this.dataPrefix.length);\n try {\n var [index, attr] = key.split('-', 1);\n return [Number(index), attr];\n } catch (err) {\n return [null, key];\n }\n },\n //! Update saved settings from this.settings\n settingsSaved(settings = null) {\n if (settings !== null) this.settings = settings;\n if (this.$refs.settings) this.$refs.settings.close();\n this.savedSettings = (0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(this.settings);\n },\n /**\n * Load initial data\n */\n loadData({\n items = [],\n settings = null\n }, reset = false) {\n if (reset) {\n this.set.items = [];\n }\n for (var index in items) this.set.push((0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(items[index]));\n if (settings) this.settingsSaved(settings);\n this.updateInput();\n }\n },\n watch: {\n initData(val) {\n this.loadData(val);\n }\n },\n mounted() {\n this.initData && this.loadData(this.initData);\n this.page = this.items.length ? Page.List : Page.Text;\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ATrackListEditor.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");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ Page: function() { return /* binding */ Page; }\n/* harmony export */ });\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! lodash */ \"./node_modules/lodash/lodash.js\");\n/* harmony import */ var lodash__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(lodash__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../model */ \"./src/model.js\");\n/* harmony import */ var _AActionButton__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./AActionButton */ \"./src/components/AActionButton.vue\");\n/* harmony import */ var _ARow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./ARow */ \"./src/components/ARow.vue\");\n/* harmony import */ var _ARows__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./ARows */ \"./src/components/ARows.vue\");\n/* harmony import */ var _AModal__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./AModal */ \"./src/components/AModal.vue\");\n\n\n\n\n\n\n\n\n/// Page display\nconst Page = {\n Text: 0,\n List: 1,\n Settings: 2\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n components: {\n AActionButton: _AActionButton__WEBPACK_IMPORTED_MODULE_3__[\"default\"],\n ARow: _ARow__WEBPACK_IMPORTED_MODULE_4__[\"default\"],\n ARows: _ARows__WEBPACK_IMPORTED_MODULE_5__[\"default\"],\n AModal: _AModal__WEBPACK_IMPORTED_MODULE_6__[\"default\"]\n },\n props: {\n ///! initial data as: {items: [], fields: {column_name: label, settings: {}}\n initData: Object,\n dataPrefix: String,\n labels: Object,\n settingsUrl: String,\n defaultColumns: {\n type: Array,\n default: () => ['artist', 'title', 'tags', 'album', 'year', 'timestamp']\n }\n },\n data() {\n const settings = {\n tracklist_editor_columns: this.columns,\n tracklist_editor_sep: ' -- '\n };\n return {\n Page: Page,\n page: Page.Text,\n set: new _model__WEBPACK_IMPORTED_MODULE_2__.Set(_model__WEBPACK_IMPORTED_MODULE_2__[\"default\"]),\n extraData: {},\n settings,\n savedSettings: (0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(settings)\n };\n },\n computed: {\n settingsChanged() {\n var k = Object.keys(this.savedSettings).findIndex(k => !(0,lodash__WEBPACK_IMPORTED_MODULE_1__.isEqual)(this.settings[k], this.savedSettings[k]));\n return k != -1;\n },\n separator: {\n set(value) {\n this.settings.tracklist_editor_sep = value;\n if (this.page == Page.List) this.updateInput();\n },\n get() {\n return this.settings.tracklist_editor_sep;\n }\n },\n allColumns: {\n set(value) {\n var cols = value.filter(x => x in this.defaultColumns);\n var left = this.defaultColumns.filter(x => !(x in cols));\n value = cols.concat(left);\n this.settings.tracklist_editor_columns = value;\n },\n get() {\n return this.settings.tracklist_editor_columns;\n }\n },\n items() {\n return this.set.items;\n },\n rowsSlots() {\n return Object.keys(this.$slots).filter(x => x.startsWith('row-') || x.startsWith('rows-')).map(x => [x, x.startsWith('rows-') ? x.slice(5) : x]);\n }\n },\n methods: {\n onCellEvent(event) {\n switch (event.name) {\n case 'change':\n this.updateInput();\n break;\n }\n },\n formatMove({\n from,\n to\n }) {\n const value = this.allColumns[from];\n this.settings.tracklist_editor_columns.splice(from, 1);\n this.settings.tracklist_editor_columns.splice(to, 0, value);\n if (this.page == Page.Text) this.updateList();else this.updateInput();\n },\n columnMove({\n from,\n to\n }) {\n const value = this.allColumns[from];\n this.allColumns.splice(from, 1);\n this.allColumns.splice(to, 0, value);\n this.updateInput();\n },\n listItemMove({\n from,\n to,\n set\n }) {\n set.move(from, to);\n this.updateInput();\n },\n updateList() {\n const items = this.toList(this.$refs.textarea.value);\n this.set.reset(items);\n },\n updateInput() {\n const input = this.toText(this.items);\n this.$refs.textarea.value = input;\n },\n /**\n * From input and separator, return list of items.\n */\n toList(input) {\n var lines = input.split('\\n');\n var items = [];\n for (let line of lines) {\n line = line.trimLeft();\n if (!line) continue;\n var lineBits = line.split(this.separator);\n var item = {};\n for (var col in this.allColumns) {\n if (col >= lineBits.length) break;\n const attr = this.allColumns[col];\n item[attr] = lineBits[col].trim();\n }\n item && items.push(item);\n }\n return items;\n },\n /**\n * From items and separator return a string\n */\n toText(items) {\n const sep = ` ${this.separator.trim()} `;\n const lines = [];\n for (let item of items) {\n if (!item) continue;\n var line = [];\n for (var col of this.allColumns) line.push(item.data[col] || '');\n line = (0,lodash__WEBPACK_IMPORTED_MODULE_1__.dropRightWhile)(line, x => !x || !('' + x).trim());\n line = line.join(sep).trimRight();\n lines.push(line);\n }\n return lines.join('\\n');\n },\n _data_key(key) {\n key = key.slice(this.dataPrefix.length);\n try {\n var [index, attr] = key.split('-', 1);\n return [Number(index), attr];\n } catch (err) {\n return [null, key];\n }\n },\n //! Update saved settings from this.settings\n settingsSaved(settings = null) {\n if (settings !== null) this.settings = settings;\n if (this.$refs.settings) this.$refs.settings.close();\n this.savedSettings = (0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(this.settings);\n },\n /**\n * Load initial data\n */\n loadData({\n items = [],\n settings = null\n }, reset = false) {\n if (reset) {\n this.set.items = [];\n }\n for (var index in items) this.set.push((0,lodash__WEBPACK_IMPORTED_MODULE_1__.cloneDeep)(items[index]));\n if (settings) this.settingsSaved(settings);\n this.updateInput();\n }\n },\n watch: {\n initData(val) {\n this.loadData(val);\n }\n },\n mounted() {\n this.initData && this.loadData(this.initData);\n this.page = this.items.length ? Page.List : Page.Text;\n }\n});\n\n//# sourceURL=webpack://aircox-assets/./src/components/ATrackListEditor.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");
/***/ }),
@@ -225,7 +225,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 key: 0\n};\nconst _hoisted_2 = {\n key: 1,\n class: \"icon is-small\"\n};\nconst _hoisted_3 = {\n key: 2\n};\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)((0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveDynamicComponent)($props.tag), {\n onClickCapture: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($options.call, [\"stop\"]),\n type: \"button\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($options.buttonClass)\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [$data.promise && $props.runIcon ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.runIcon)\n }, null, 2 /* CLASS */)])) : $props.icon ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.icon)\n }, null, 2 /* CLASS */)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), _ctx.$slots.default ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\")])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]),\n _: 3 /* FORWARDED */\n }, 40 /* PROPS, HYDRATE_EVENTS */, [\"onClickCapture\", \"class\"]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/AActionButton.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 key: 0\n};\nconst _hoisted_2 = {\n key: 1,\n class: \"icon is-small\"\n};\nconst _hoisted_3 = {\n key: 2\n};\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)((0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveDynamicComponent)($props.tag), {\n onClickCapture: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withModifiers)($options.call, [\"stop\"]),\n type: \"button\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)([$options.buttonClass, this.promise && 'blink' || ''])\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [$data.promise && $props.runIcon ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.runIcon)\n }, null, 2 /* CLASS */)])) : $props.icon ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)($props.icon)\n }, null, 2 /* CLASS */)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), _ctx.$slots.default ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\")])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]),\n _: 3 /* FORWARDED */\n }, 40 /* PROPS, HYDRATE_EVENTS */, [\"onClickCapture\", \"class\"]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/AActionButton.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");
/***/ }),
@@ -295,7 +295,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: \"modal-card\"\n};\nconst _hoisted_2 = {\n class: \"modal-card-head\"\n};\nconst _hoisted_3 = {\n class: \"modal-card-title\"\n};\nconst _hoisted_4 = /*#__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-close\"\n})], -1 /* HOISTED */);\nconst _hoisted_5 = [_hoisted_4];\nconst _hoisted_6 = {\n class: \"modal-card-body\"\n};\nconst _hoisted_7 = {\n class: \"modal-card-foot align-right\"\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)(\"section\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['modal', $data.active && 'is-active' || ''])\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: \"modal-background\",\n onClick: _cache[0] || (_cache[0] = (...args) => $options.close && $options.close(...args))\n }), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"header\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"title\", {}, () => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)((0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.title), 1 /* TEXT */)])]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"delete square\",\n \"aria-label\": \"close\",\n onClick: _cache[1] || (_cache[1] = (...args) => $options.close && $options.close(...args))\n }, _hoisted_5)]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"section\", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\", {\n item: $data.item\n })]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_7, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"footer\", {\n item: $data.item,\n close: $options.close\n })])])], 2 /* CLASS */);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/AModal.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: \"modal-card\"\n};\nconst _hoisted_2 = {\n class: \"modal-card-head\"\n};\nconst _hoisted_3 = {\n class: \"modal-card-title\"\n};\nconst _hoisted_4 = /*#__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-close\"\n})], -1 /* HOISTED */);\nconst _hoisted_5 = [_hoisted_4];\nconst _hoisted_6 = {\n class: \"modal-card-body\"\n};\nconst _hoisted_7 = {\n class: \"modal-card-foot align-right\"\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)(\"section\", {\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['modal', $data.active && 'is-active' || ''])\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: \"modal-background\",\n onClick: _cache[0] || (_cache[0] = (...args) => $options.close && $options.close(...args))\n }), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"header\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"title\", {}, () => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createTextVNode)((0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.title), 1 /* TEXT */)])]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"bar\"), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"delete square\",\n \"aria-label\": \"close\",\n onClick: _cache[1] || (_cache[1] = (...args) => $options.close && $options.close(...args))\n }, _hoisted_5)]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"section\", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\", {\n item: $data.item\n })]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_7, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"footer\", {\n item: $data.item,\n close: $options.close\n })])])], 2 /* CLASS */);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/AModal.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");
/***/ }),
@@ -365,7 +365,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\*************************************************************************************************************************************************************************************************************************************************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
-eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ 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-select-file\"\n};\nconst _hoisted_2 = {\n key: 0,\n ref: \"uploadForm\",\n class: \"flex-column\"\n};\nconst _hoisted_3 = {\n class: \"field flex-grow-1\"\n};\nconst _hoisted_4 = {\n class: \"label\"\n};\nconst _hoisted_5 = [\"name\"];\nconst _hoisted_6 = {\n class: \"flex-grow-1\"\n};\nconst _hoisted_7 = {\n key: 1,\n class: \"flex-column\"\n};\nconst _hoisted_8 = {\n class: \"flex-row\"\n};\nconst _hoisted_9 = [\"max\", \"value\"];\nconst _hoisted_10 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", {\n class: \"icon small\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"i\", {\n class: \"fa fa-close\"\n})], -1 /* HOISTED */);\nconst _hoisted_11 = [_hoisted_10];\nconst _hoisted_12 = {\n key: 2\n};\nconst _hoisted_13 = [\"onClick\"];\nconst _hoisted_14 = {\n key: 3\n};\nconst _hoisted_15 = {\n class: \"a-select-footer\"\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\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n ref: \"list\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['a-select-file-list', $props.listClass])\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\" upload \"), $data.state == $data.STATE.DEFAULT ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"form\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"label\", _hoisted_4, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.uploadLabel), 1 /* TEXT */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"input\", {\n type: \"file\",\n ref: \"uploadFile\",\n name: $props.uploadFieldName,\n onChange: _cache[0] || (_cache[0] = (...args) => $options.onSubmit && $options.onSubmit(...args))\n }, null, 40 /* PROPS, HYDRATE_EVENTS */, _hoisted_5)]), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-form\")])], 512 /* NEED_PATCH */)) : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_7, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-preview\", {\n upload: $data.upload\n }), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_8, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"progress\", {\n max: $data.upload.total,\n value: $data.upload.loaded\n }, null, 8 /* PROPS */, _hoisted_9), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button small square ml-2\",\n onClick: _cache[1] || (_cache[1] = (...args) => $options.uploadAbort && $options.uploadAbort(...args))\n }, _hoisted_11)])])), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\" tiles \"), $data.prevUrl ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_12, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"a\", {\n href: \"#\",\n onClick: _cache[2] || (_cache[2] = $event => $options.load($data.prevUrl))\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.prevLabel), 1 /* TEXT */)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($data.items, item => {\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", {\n key: item.id,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['file-preview', this.item && item.id == this.item.id && 'active']),\n onClick: $event => $options.select(item)\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\", {\n item: item,\n load: $options.load,\n lastUrl: $data.lastUrl\n })], 10 /* CLASS, PROPS */, _hoisted_13);\n }), 128 /* KEYED_FRAGMENT */)), $data.nextUrl ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_14, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"a\", {\n href: \"#\",\n onClick: _cache[3] || (_cache[3] = $event => $options.load($data.nextUrl))\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.nextLabel), 1 /* TEXT */)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)], 2 /* CLASS */), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_15, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"footer\", {\n item: $data.item,\n items: $data.items\n })])]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASelectFile.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 = /*#__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_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-list\"\n})], -1 /* HOISTED */);\nconst _hoisted_3 = {\n class: \"a-select-file\"\n};\nconst _hoisted_4 = {\n key: 0\n};\nconst _hoisted_5 = [\"onClick\"];\nconst _hoisted_6 = {\n key: 1\n};\nconst _hoisted_7 = {\n key: 0,\n class: \"mr-3\"\n};\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_action_button = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-action-button\");\n const _component_a_modal = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-modal\");\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_a_modal, {\n ref: \"modal\",\n title: $props.title\n }, {\n bar: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [$data.panel == $data.LIST ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"button\", {\n key: 0,\n type: \"button\",\n class: \"button small mr-3\",\n onClick: _cache[0] || (_cache[0] = $event => $options.showPanel($data.UPLOAD))\n }, [_hoisted_1, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.labels.upload), 1 /* TEXT */)])) : ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"button\", {\n key: 1,\n type: \"button\",\n class: \"button small mr-3\",\n onClick: _cache[1] || (_cache[1] = $event => $options.showPanel($data.LIST))\n }, [_hoisted_2, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"span\", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.labels.list), 1 /* TEXT */)]))]),\n\n default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_3, [$data.panel == $data.UPLOAD ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_a_file_upload, {\n key: 0,\n ref: \"upload\",\n url: $props.uploadUrl,\n label: $props.uploadLabel,\n \"field-name\": $props.uploadFieldName,\n onLoad: $options.uploadDone\n }, {\n form: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(data => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-form\", (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_0__.guardReactiveProps)(data)))]),\n preview: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(data => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-preview\", (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_0__.guardReactiveProps)(data)))]),\n _: 3 /* FORWARDED */\n }, 8 /* PROPS */, [\"url\", \"label\", \"field-name\", \"onLoad\"])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n ref: \"list\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['a-select-file-list', $props.listClass])\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\" tiles \"), $data.prevUrl ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_4, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"a\", {\n href: \"#\",\n onClick: _cache[2] || (_cache[2] = $event => $options.load($data.prevUrl))\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.labels.show_previous), 1 /* TEXT */)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true), ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(true), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.renderList)($data.items, item => {\n return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", {\n key: item.id,\n class: (0,vue__WEBPACK_IMPORTED_MODULE_0__.normalizeClass)(['file-preview', this.item && item.id == this.item.id && 'active']),\n onClick: $event => $options.select(item)\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"default\", {\n item: item,\n load: $options.load,\n lastUrl: $data.lastUrl\n }), $props.deleteUrl ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createBlock)(_component_a_action_button, {\n key: 0,\n class: \"has-text-danger small float-right\",\n icon: \"fa fa-trash\",\n confirm: $props.labels.confirm_delete,\n method: \"DELETE\",\n url: $props.deleteUrl.replace('123', item.id),\n onDone: _cache[3] || (_cache[3] = $event => $options.load($data.lastUrl))\n }, null, 8 /* PROPS */, [\"confirm\", \"url\"])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)], 10 /* CLASS, PROPS */, _hoisted_5);\n }), 128 /* KEYED_FRAGMENT */)), $data.nextUrl ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"div\", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"a\", {\n href: \"#\",\n onClick: _cache[4] || (_cache[4] = $event => $options.load($data.nextUrl))\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.labels.show_next), 1 /* TEXT */)])) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)], 2 /* CLASS */), [[vue__WEBPACK_IMPORTED_MODULE_0__.vShow, $data.panel == $data.LIST]])])]),\n footer: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"footer\", {\n item: $data.item\n }, () => [$data.item ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"span\", _hoisted_7, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($data.item.name), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]), $data.panel == $data.LIST ? ((0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(\"button\", {\n key: 0,\n type: \"button\",\n class: \"button align-right\",\n onClick: _cache[5] || (_cache[5] = (...args) => $options.selected && $options.selected(...args))\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)($props.labels.select_file), 1 /* TEXT */)) : (0,vue__WEBPACK_IMPORTED_MODULE_0__.createCommentVNode)(\"v-if\", true)]),\n _: 3 /* FORWARDED */\n }, 8 /* PROPS */, [\"title\"]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ASelectFile.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");
/***/ }),
@@ -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__.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: $options.allColumns,\n labels: $options.allColumnsLabels,\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 = [\"src\"];\nconst _hoisted_3 = {\n class: \"label small flex-grow-1\"\n};\nconst _hoisted_4 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"br\", null, null, -1 /* HOISTED */);\nconst _hoisted_5 = [\"src\"];\nconst _hoisted_6 = {\n class: \"flex-row\"\n};\nconst _hoisted_7 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", {\n class: \"flex-grow-1 flex-row\"\n}, null, -1 /* HOISTED */);\nconst _hoisted_8 = {\n class: \"flex-grow-1 align-right\"\n};\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-rotate\"\n})], -1 /* HOISTED */);\nconst _hoisted_11 = [_hoisted_10];\nconst _hoisted_12 = [\"title\", \"aria-label\"];\nconst _hoisted_13 = /*#__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_14 = [_hoisted_13];\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n const _component_a_select_file = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)(\"a-select-file\");\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_select_file, {\n ref: \"select-file\",\n title: $props.labels && $props.labels.add_sound,\n labels: $props.labels,\n \"list-url\": $props.soundListUrl,\n deleteUrl: $props.soundDeleteUrl,\n uploadUrl: $props.soundUploadUrl,\n uploadLabel: $props.labels.select_file,\n onSelect: $options.selected\n }, {\n \"upload-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 \"upload-form\": (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.renderSlot)(_ctx.$slots, \"upload-form\")]),\n default: (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(({\n item\n }) => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"audio\", {\n controls: \"\",\n src: item.url\n }, null, 8 /* PROPS */, _hoisted_2), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"label\", _hoisted_3, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(item.name), 1 /* TEXT */)]),\n\n _: 3 /* FORWARDED */\n }, 8 /* PROPS */, [\"title\", \"labels\", \"list-url\", \"deleteUrl\", \"uploadUrl\", \"uploadLabel\", \"onSelect\"]), (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: $options.allColumns,\n labels: $options.allColumnsLabels,\n \"allow-create\": true,\n orderable: true,\n onMove: $options.listItemMove\n }, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createSlots)({\n \"row-sound\": (0,vue__WEBPACK_IMPORTED_MODULE_0__.withCtx)(({\n item\n }) => [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"label\", null, (0,vue__WEBPACK_IMPORTED_MODULE_0__.toDisplayString)(item.data.name), 1 /* TEXT */), _hoisted_4, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"audio\", {\n controls: \"\",\n src: item.data.url\n }, null, 8 /* PROPS */, _hoisted_5)]),\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_6, [_hoisted_7, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"div\", _hoisted_8, [(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-warning p-2\",\n onClick: _cache[0] || (_cache[0] = $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_11, 8 /* PROPS */, _hoisted_9), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-primary p-2\",\n onClick: _cache[1] || (_cache[1] = $event => _ctx.$refs['select-file'].open()),\n title: $props.labels.add_sound,\n \"aria-label\": $props.labels.add_sound\n }, _hoisted_14, 8 /* PROPS */, _hoisted_12)])])]);\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");
/***/ }),
@@ -425,7 +425,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 core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\n\nconst _hoisted_1 = {\n class: \"a-tracklist-editor\"\n};\nconst _hoisted_2 = {\n class: \"flex-row\"\n};\nconst _hoisted_3 = {\n class: \"flex-grow-1\"\n};\nconst _hoisted_4 = {\n class: \"flex-row align-right\"\n};\nconst _hoisted_5 = {\n class: \"field has-addons\"\n};\nconst _hoisted_6 = {\n class: \"control\"\n};\nconst _hoisted_7 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon is-small\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-pencil\"\n})], -1 /* HOISTED */);\nconst _hoisted_8 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", null, \"Texte\", -1 /* HOISTED */);\nconst _hoisted_9 = [_hoisted_7, _hoisted_8];\nconst _hoisted_10 = {\n class: \"control\"\n};\nconst _hoisted_11 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon is-small\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-list\"\n})], -1 /* HOISTED */);\nconst _hoisted_12 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", null, \"Liste\", -1 /* HOISTED */);\nconst _hoisted_13 = [_hoisted_11, _hoisted_12];\nconst _hoisted_14 = {\n class: \"panel\"\n};\nconst _hoisted_15 = {\n class: \"panel\"\n};\nconst _hoisted_16 = {\n class: \"align-right pr-0\"\n};\nconst _hoisted_17 = [\"onClick\", \"title\", \"aria-label\"];\nconst _hoisted_18 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-trash\"\n})], -1 /* HOISTED */);\nconst _hoisted_19 = [_hoisted_18];\nconst _hoisted_20 = {\n class: \"flex-row\"\n};\nconst _hoisted_21 = {\n class: \"flex-grow-1 flex-row\"\n};\nconst _hoisted_22 = {\n class: \"field\"\n};\nconst _hoisted_23 = {\n class: \"control\"\n};\nconst _hoisted_24 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon is-small\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-cog\"\n})], -1 /* HOISTED */);\nconst _hoisted_25 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", null, \"Options\", -1 /* HOISTED */);\nconst _hoisted_26 = [_hoisted_24, _hoisted_25];\nconst _hoisted_27 = {\n class: \"flex-grow-1 align-right\"\n};\nconst _hoisted_28 = [\"title\", \"aria-label\"];\nconst _hoisted_29 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-rotate\"\n})], -1 /* HOISTED */);\nconst _hoisted_30 = [_hoisted_29];\nconst _hoisted_31 = [\"title\", \"aria-label\"];\nconst _hoisted_32 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-plus\"\n})], -1 /* HOISTED */);\nconst _hoisted_33 = [_hoisted_32];\nconst _hoisted_34 = {\n class: \"field\"\n};\nconst _hoisted_35 = {\n class: \"label\",\n style: {\n \"vertical-align\": \"middle\"\n }\n};\nconst _hoisted_36 = {\n class: \"table is-bordered\",\n style: {\n \"vertical-align\": \"middle\"\n }\n};\nconst _hoisted_37 = {\n key: 0,\n style: {\n \"cursor\": \"pointer\"\n }\n};\nconst _hoisted_38 = [\"onClick\"];\nconst _hoisted_39 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-left-right\"\n}, null, -1 /* HOISTED */);\nconst _hoisted_40 = [_hoisted_39];\nconst _hoisted_41 = {\n class: \"flex-row\"\n};\nconst _hoisted_42 = {\n class: \"field is-inline-block is-vcentered flex-grow-1\"\n};\nconst _hoisted_43 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"label\", {\n class: \"label is-inline mr-2\",\n style: {\n \"vertical-align\": \"middle\"\n }\n}, \" Séparateur\", -1 /* HOISTED */);\nconst _hoisted_44 = {\n class: \"control is-inline-block\",\n style: {\n \"vertical-align\": \"middle\"\n }\n};\nconst _hoisted_45 = {\n class: \"flex-row align-right\"\n};\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n const _component_a_rows = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-rows\");\n const _component_a_row = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-row\");\n const _component_a_action_button = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-action-button\");\n const _component_a_modal = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-modal\");\n return (0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementBlock)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, \"title\")]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_4, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_5, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"p\", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeClass)(['button', 'p-2', $data.page == $data.Page.Text ? 'is-primary' : 'is-light']),\n onClick: _cache[0] || (_cache[0] = $event => $data.page = $data.Page.Text)\n }, _hoisted_9, 2 /* CLASS */)]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"p\", _hoisted_10, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeClass)(['button', 'p-2', $data.page == $data.Page.List ? 'is-primary' : 'is-light']),\n onClick: _cache[1] || (_cache[1] = $event => $data.page = $data.Page.List)\n }, _hoisted_13, 2 /* CLASS */)])])])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, \"top\", {\n set: $data.set,\n columns: $options.columns,\n items: $options.items\n }), (0,vue__WEBPACK_IMPORTED_MODULE_1__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"section\", _hoisted_14, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"textarea\", {\n ref: \"textarea\",\n class: \"is-fullwidth is-size-6\",\n rows: \"20\",\n onChange: _cache[2] || (_cache[2] = (...args) => $options.updateList && $options.updateList(...args))\n }, null, 544 /* HYDRATE_EVENTS, NEED_PATCH */)], 512 /* NEED_PATCH */), [[vue__WEBPACK_IMPORTED_MODULE_1__.vShow, $data.page == $data.Page.Text]]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"section\", _hoisted_15, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createVNode)(_component_a_rows, {\n set: $data.set,\n columns: $options.columns,\n labels: $props.initData.fields,\n orderable: true,\n onMove: $options.listItemMove,\n onColmove: $options.columnMove,\n onCell: $options.onCellEvent\n }, (0,vue__WEBPACK_IMPORTED_MODULE_1__.createSlots)({\n \"row-tail\": (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(data => [_ctx.$slots['row-tail'] ? (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, _ctx.row - _ctx.tail, (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_1__.mergeProps)({\n key: 0\n }, data))) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"td\", _hoisted_16, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square\",\n onClick: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withModifiers)($event => $options.items.splice(data.row, 1), [\"stop\"]),\n title: $props.labels.remove_item,\n \"aria-label\": $props.labels.remove_item\n }, _hoisted_19, 8 /* PROPS */, _hoisted_17)])]),\n _: 2 /* DYNAMIC */\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.renderList)($options.rowsSlots, ([name, slot]) => {\n return {\n name: slot,\n fn: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(data => [name != 'row-tail' ? (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, name, (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_1__.mergeProps)({\n key: 0\n }, data))) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true)])\n };\n })]), 1032 /* PROPS, DYNAMIC_SLOTS */, [\"set\", \"columns\", \"labels\", \"onMove\", \"onColmove\", \"onCell\"])], 512 /* NEED_PATCH */), [[vue__WEBPACK_IMPORTED_MODULE_1__.vShow, $data.page == $data.Page.List]]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_20, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_21, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_22, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"p\", _hoisted_23, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button is-info\",\n onClick: _cache[3] || (_cache[3] = $event => _ctx.$refs.settings.open())\n }, _hoisted_26)])])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_27, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-warning p-2\",\n onClick: _cache[4] || (_cache[4] = $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_30, 8 /* PROPS */, _hoisted_28), $data.page == $data.Page.List ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementBlock)(\"button\", {\n key: 0,\n type: \"button\",\n class: \"button square is-primary p-2\",\n onClick: _cache[5] || (_cache[5] = $event => this.set.push(new this.set.model())),\n title: $props.labels.add_item,\n \"aria-label\": $props.labels.add_item\n }, _hoisted_33, 8 /* PROPS */, _hoisted_31)) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true)])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createVNode)(_component_a_modal, {\n ref: \"settings\",\n title: \"Options\"\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_34, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"label\", _hoisted_35, (0,vue__WEBPACK_IMPORTED_MODULE_1__.toDisplayString)($props.labels.columns), 1 /* TEXT */), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"table\", _hoisted_36, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"tr\", null, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createVNode)(_component_a_row, {\n columns: $options.columns,\n item: $props.initData.fields,\n onMove: $options.formatMove,\n orderable: true\n }, {\n \"cell-after\": (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(({\n cell\n }) => [cell.col < $options.columns.length - 1 ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementBlock)(\"td\", _hoisted_37, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\",\n onClick: $event => $options.formatMove({\n from: cell.col,\n to: cell.col + 1\n })\n }, _hoisted_40, 8 /* PROPS */, _hoisted_38)])) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true)]),\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"columns\", \"item\", \"onMove\"])])])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_41, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_42, [_hoisted_43, (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_44, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"input\", {\n type: \"text\",\n ref: \"sep\",\n class: \"input is-inline is-text-centered is-small\",\n style: {\n \"max-width\": \"5em\"\n },\n \"onUpdate:modelValue\": _cache[6] || (_cache[6] = $event => $options.separator = $event),\n onChange: _cache[7] || (_cache[7] = $event => $options.updateList())\n }, null, 544 /* HYDRATE_EVENTS, NEED_PATCH */), [[vue__WEBPACK_IMPORTED_MODULE_1__.vModelText, $options.separator]])])])])]),\n footer: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_45, [$options.settingsChanged ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createBlock)(_component_a_action_button, {\n key: 0,\n icon: \"fa fa-floppy-disk\",\n class: \"button control p-2 mr-3 is-secondary\",\n \"run-class\": \"blink\",\n url: $props.settingsUrl,\n method: \"POST\",\n data: $data.settings,\n \"aria-label\": $props.labels.save_settings,\n onDone: _cache[8] || (_cache[8] = $event => $options.settingsSaved())\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createTextVNode)((0,vue__WEBPACK_IMPORTED_MODULE_1__.toDisplayString)($props.labels.save_settings), 1 /* TEXT */)]),\n\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"url\", \"data\", \"aria-label\"])) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n class: \"button\",\n type: \"button\",\n onClick: _cache[9] || (_cache[9] = $event => _ctx.$refs.settings.close())\n }, \" Fermer \")])]),\n _: 1 /* STABLE */\n }, 512 /* NEED_PATCH */), (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, \"bottom\", {\n set: $data.set,\n columns: $options.columns,\n items: $options.items\n })]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ATrackListEditor.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 core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es.array.push.js */ \"./node_modules/core-js/modules/es.array.push.js\");\n/* harmony import */ var core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es_array_push_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! vue */ \"./node_modules/vue/dist/vue.esm-bundler.js\");\n\n\nconst _hoisted_1 = {\n class: \"a-tracklist-editor\"\n};\nconst _hoisted_2 = {\n class: \"flex-row\"\n};\nconst _hoisted_3 = {\n class: \"flex-grow-1\"\n};\nconst _hoisted_4 = {\n class: \"flex-row align-right\"\n};\nconst _hoisted_5 = {\n class: \"field has-addons\"\n};\nconst _hoisted_6 = {\n class: \"control\"\n};\nconst _hoisted_7 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon is-small\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-pencil\"\n})], -1 /* HOISTED */);\nconst _hoisted_8 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", null, \"Texte\", -1 /* HOISTED */);\nconst _hoisted_9 = [_hoisted_7, _hoisted_8];\nconst _hoisted_10 = {\n class: \"control\"\n};\nconst _hoisted_11 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon is-small\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-list\"\n})], -1 /* HOISTED */);\nconst _hoisted_12 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", null, \"Liste\", -1 /* HOISTED */);\nconst _hoisted_13 = [_hoisted_11, _hoisted_12];\nconst _hoisted_14 = {\n class: \"panel\"\n};\nconst _hoisted_15 = {\n class: \"panel\"\n};\nconst _hoisted_16 = {\n class: \"align-right pr-0\"\n};\nconst _hoisted_17 = [\"onClick\", \"title\", \"aria-label\"];\nconst _hoisted_18 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-trash\"\n})], -1 /* HOISTED */);\nconst _hoisted_19 = [_hoisted_18];\nconst _hoisted_20 = {\n class: \"flex-row\"\n};\nconst _hoisted_21 = {\n class: \"flex-grow-1 flex-row\"\n};\nconst _hoisted_22 = {\n class: \"field\"\n};\nconst _hoisted_23 = {\n class: \"control\"\n};\nconst _hoisted_24 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon is-small\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-cog\"\n})], -1 /* HOISTED */);\nconst _hoisted_25 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", null, \"Options\", -1 /* HOISTED */);\nconst _hoisted_26 = [_hoisted_24, _hoisted_25];\nconst _hoisted_27 = {\n class: \"flex-grow-1 align-right\"\n};\nconst _hoisted_28 = [\"title\", \"aria-label\"];\nconst _hoisted_29 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-rotate\"\n})], -1 /* HOISTED */);\nconst _hoisted_30 = [_hoisted_29];\nconst _hoisted_31 = [\"title\", \"aria-label\"];\nconst _hoisted_32 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\"\n}, [/*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-plus\"\n})], -1 /* HOISTED */);\nconst _hoisted_33 = [_hoisted_32];\nconst _hoisted_34 = {\n class: \"field\"\n};\nconst _hoisted_35 = {\n class: \"label\",\n style: {\n \"vertical-align\": \"middle\"\n }\n};\nconst _hoisted_36 = {\n class: \"table is-bordered\",\n style: {\n \"vertical-align\": \"middle\"\n }\n};\nconst _hoisted_37 = {\n key: 0,\n style: {\n \"cursor\": \"pointer\"\n }\n};\nconst _hoisted_38 = [\"onClick\"];\nconst _hoisted_39 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"i\", {\n class: \"fa fa-left-right\"\n}, null, -1 /* HOISTED */);\nconst _hoisted_40 = [_hoisted_39];\nconst _hoisted_41 = {\n class: \"flex-row\"\n};\nconst _hoisted_42 = {\n class: \"field is-inline-block is-vcentered flex-grow-1\"\n};\nconst _hoisted_43 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"label\", {\n class: \"label is-inline mr-2\",\n style: {\n \"vertical-align\": \"middle\"\n }\n}, \" Séparateur\", -1 /* HOISTED */);\nconst _hoisted_44 = {\n class: \"control is-inline-block\",\n style: {\n \"vertical-align\": \"middle\"\n }\n};\nconst _hoisted_45 = {\n class: \"flex-row align-right\"\n};\nfunction render(_ctx, _cache, $props, $setup, $data, $options) {\n const _component_a_rows = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-rows\");\n const _component_a_row = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-row\");\n const _component_a_action_button = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-action-button\");\n const _component_a_modal = (0,vue__WEBPACK_IMPORTED_MODULE_1__.resolveComponent)(\"a-modal\");\n return (0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementBlock)(\"div\", _hoisted_1, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_2, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_3, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, \"title\")]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_4, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_5, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"p\", _hoisted_6, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeClass)(['button', 'p-2', $data.page == $data.Page.Text ? 'is-primary' : 'is-light']),\n onClick: _cache[0] || (_cache[0] = $event => $data.page = $data.Page.Text)\n }, _hoisted_9, 2 /* CLASS */)]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"p\", _hoisted_10, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeClass)(['button', 'p-2', $data.page == $data.Page.List ? 'is-primary' : 'is-light']),\n onClick: _cache[1] || (_cache[1] = $event => $data.page = $data.Page.List)\n }, _hoisted_13, 2 /* CLASS */)])])])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, \"top\", {\n set: $data.set,\n columns: $options.allColumns,\n items: $options.items\n }), (0,vue__WEBPACK_IMPORTED_MODULE_1__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"section\", _hoisted_14, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"textarea\", {\n ref: \"textarea\",\n class: \"is-fullwidth is-size-6\",\n rows: \"20\",\n onChange: _cache[2] || (_cache[2] = (...args) => $options.updateList && $options.updateList(...args))\n }, null, 544 /* HYDRATE_EVENTS, NEED_PATCH */)], 512 /* NEED_PATCH */), [[vue__WEBPACK_IMPORTED_MODULE_1__.vShow, $data.page == $data.Page.Text]]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"section\", _hoisted_15, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createVNode)(_component_a_rows, {\n set: $data.set,\n columns: $options.allColumns,\n labels: $props.initData.fields,\n orderable: true,\n onMove: $options.listItemMove,\n onColmove: $options.columnMove,\n onCell: $options.onCellEvent\n }, (0,vue__WEBPACK_IMPORTED_MODULE_1__.createSlots)({\n \"row-tail\": (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(data => [_ctx.$slots['row-tail'] ? (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, _ctx.row - _ctx.tail, (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_1__.mergeProps)({\n key: 0\n }, data))) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"td\", _hoisted_16, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square\",\n onClick: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withModifiers)($event => $options.items.splice(data.row, 1), [\"stop\"]),\n title: $props.labels.remove_item,\n \"aria-label\": $props.labels.remove_item\n }, _hoisted_19, 8 /* PROPS */, _hoisted_17)])]),\n _: 2 /* DYNAMIC */\n }, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.renderList)($options.rowsSlots, ([name, slot]) => {\n return {\n name: slot,\n fn: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(data => [name != 'row-tail' ? (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, name, (0,vue__WEBPACK_IMPORTED_MODULE_1__.normalizeProps)((0,vue__WEBPACK_IMPORTED_MODULE_1__.mergeProps)({\n key: 0\n }, data))) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true)])\n };\n })]), 1032 /* PROPS, DYNAMIC_SLOTS */, [\"set\", \"columns\", \"labels\", \"onMove\", \"onColmove\", \"onCell\"])], 512 /* NEED_PATCH */), [[vue__WEBPACK_IMPORTED_MODULE_1__.vShow, $data.page == $data.Page.List]]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_20, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_21, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_22, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"p\", _hoisted_23, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button is-info\",\n onClick: _cache[3] || (_cache[3] = $event => _ctx.$refs.settings.open())\n }, _hoisted_26)])])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_27, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n type: \"button\",\n class: \"button square is-warning p-2\",\n onClick: _cache[4] || (_cache[4] = $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_30, 8 /* PROPS */, _hoisted_28), $data.page == $data.Page.List ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementBlock)(\"button\", {\n key: 0,\n type: \"button\",\n class: \"button square is-primary p-2\",\n onClick: _cache[5] || (_cache[5] = $event => this.set.push(new this.set.model())),\n title: $props.labels.add_item,\n \"aria-label\": $props.labels.add_item\n }, _hoisted_33, 8 /* PROPS */, _hoisted_31)) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true)])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createVNode)(_component_a_modal, {\n ref: \"settings\",\n title: \"Options\"\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_34, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"label\", _hoisted_35, (0,vue__WEBPACK_IMPORTED_MODULE_1__.toDisplayString)($props.labels.columns), 1 /* TEXT */), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"table\", _hoisted_36, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"tr\", null, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createVNode)(_component_a_row, {\n columns: $options.allColumns,\n item: $props.initData.fields,\n onMove: $options.formatMove,\n orderable: true\n }, {\n \"cell-after\": (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(({\n cell\n }) => [cell.col < $options.allColumns.length - 1 ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementBlock)(\"td\", _hoisted_37, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"span\", {\n class: \"icon\",\n onClick: $event => $options.formatMove({\n from: cell.col,\n to: cell.col + 1\n })\n }, _hoisted_40, 8 /* PROPS */, _hoisted_38)])) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true)]),\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"columns\", \"item\", \"onMove\"])])])]), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_41, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_42, [_hoisted_43, (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_44, [(0,vue__WEBPACK_IMPORTED_MODULE_1__.withDirectives)((0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"input\", {\n type: \"text\",\n ref: \"sep\",\n class: \"input is-inline is-text-centered is-small\",\n style: {\n \"max-width\": \"5em\"\n },\n \"onUpdate:modelValue\": _cache[6] || (_cache[6] = $event => $options.separator = $event),\n onChange: _cache[7] || (_cache[7] = $event => $options.updateList())\n }, null, 544 /* HYDRATE_EVENTS, NEED_PATCH */), [[vue__WEBPACK_IMPORTED_MODULE_1__.vModelText, $options.separator]])])])])]),\n footer: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"div\", _hoisted_45, [$options.settingsChanged ? ((0,vue__WEBPACK_IMPORTED_MODULE_1__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createBlock)(_component_a_action_button, {\n key: 0,\n icon: \"fa fa-floppy-disk\",\n class: \"button control p-2 mr-3 is-secondary\",\n \"run-class\": \"blink\",\n url: $props.settingsUrl,\n method: \"POST\",\n data: $data.settings,\n \"aria-label\": $props.labels.save_settings,\n onDone: _cache[8] || (_cache[8] = $event => $options.settingsSaved())\n }, {\n default: (0,vue__WEBPACK_IMPORTED_MODULE_1__.withCtx)(() => [(0,vue__WEBPACK_IMPORTED_MODULE_1__.createTextVNode)((0,vue__WEBPACK_IMPORTED_MODULE_1__.toDisplayString)($props.labels.save_settings), 1 /* TEXT */)]),\n\n _: 1 /* STABLE */\n }, 8 /* PROPS */, [\"url\", \"data\", \"aria-label\"])) : (0,vue__WEBPACK_IMPORTED_MODULE_1__.createCommentVNode)(\"v-if\", true), (0,vue__WEBPACK_IMPORTED_MODULE_1__.createElementVNode)(\"button\", {\n class: \"button\",\n type: \"button\",\n onClick: _cache[9] || (_cache[9] = $event => _ctx.$refs.settings.close())\n }, \" Fermer \")])]),\n _: 1 /* STABLE */\n }, 512 /* NEED_PATCH */), (0,vue__WEBPACK_IMPORTED_MODULE_1__.renderSlot)(_ctx.$slots, \"bottom\", {\n set: $data.set,\n columns: $options.allColumns,\n items: $options.items\n })]);\n}\n\n//# sourceURL=webpack://aircox-assets/./src/components/ATrackListEditor.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");
/***/ }),
@@ -495,7 +495,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 */ Sound; }\n/* harmony export */ });\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n\nclass Sound extends _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n get name() {\n return this.data.name;\n }\n get src() {\n return this.data.url;\n }\n static getId(data) {\n return data.pk;\n }\n}\n\n//# sourceURL=webpack://aircox-assets/./src/sound.js?");
+eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": function() { return /* binding */ Sound; }\n/* harmony export */ });\n/* harmony import */ var _model__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./model */ \"./src/model.js\");\n\nclass Sound extends _model__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n constructor({\n sound = {},\n ...data\n } = {}, options = {}) {\n // flatten EpisodeSound and sound data\n super({\n ...sound,\n ...data\n }, options);\n }\n get name() {\n return this.data.name;\n }\n get src() {\n return this.data.url;\n }\n}\n\n//# sourceURL=webpack://aircox-assets/./src/sound.js?");
/***/ }),
diff --git a/aircox/templates/aircox/dashboard/list_editor.html b/aircox/templates/aircox/dashboard/list_editor.html
index 82b1355..7ec60f5 100644
--- a/aircox/templates/aircox/dashboard/list_editor.html
+++ b/aircox/templates/aircox/dashboard/list_editor.html
@@ -4,6 +4,8 @@ Base template for list editor based on formsets (tracklist_editor, playlist_edit
Context:
- tag_id: id of parent component
- tag: vue component tag (a-playlist-editor, etc.)
+- related_field: field name that target object
+- object: related object
- formset: formset used to render the list editor
{% endcomment %}
@@ -17,9 +19,9 @@ Context:
<{{ tag }}
{% block tag-attrs %}
- :labels="{% inline_labels %}"
+ :labels="window.aircox.labels"
:init-data="{% formset_inline_data formset=formset %}"
- :default-columns="[{% for f in fields.keys %}{% if f != "position" %}'{{ f }}',{% endif %}{% endfor %}]"
+ :columns="[{% for n, f in fields.items %}{% if not f.widget.is_hidden %}'{{ n }}',{% endif %}{% endfor %} ]"
settings-url="{% url "api:user-settings" %}"
data-prefix="{{ formset.prefix }}-"
{% endblock %}>
@@ -29,11 +31,7 @@ Context:
@@ -51,29 +49,33 @@ Context:
{% endblock %}
-
- {% block row-head %}
+
+ {% block row-head %}
[[ row+1 ]]
-
+ :value="item.data.id || item.id "/>
{% for name, field in fields.items %}
- {% if name != 'position' and field.widget.is_hidden %}
+ {% if name == related_field %}
+ value="{{ object.id }}"/>
+ {% elif name != 'position' and field.widget.is_hidden %}
+
{% endif %}
{% endfor %}
+ {% endblock %}
- {% endblock %}
{% for name, field in fields.items %}
- {% if not field.widget.is_hidden and not field.is_readonly %}
+ {% if name != related_field and not field.widget.is_hidden and not field.is_readonly %}
{% with full_name="'"|add:formset.prefix|add:"-' + cell.row + '-"|add:name|add:"'" %}
diff --git a/aircox/templates/aircox/dashboard/soundlist_editor.html b/aircox/templates/aircox/dashboard/soundlist_editor.html
index b37beea..9053df1 100644
--- a/aircox/templates/aircox/dashboard/soundlist_editor.html
+++ b/aircox/templates/aircox/dashboard/soundlist_editor.html
@@ -1,9 +1,13 @@
{% extends "./list_editor.html" %}
+{% comment %}
+Context:
+- object: episode
+{% endcomment %}
{% block outer %}
- {% with no_initial_form_count=True %}
{% with tag_id="inline-sounds" %}
{% with tag="a-sound-list-editor" %}
+ {% with related_field="episode" %}
{{ block.super }}
{% endwith %}
{% endwith %}
@@ -13,8 +17,11 @@
{% block tag-attrs %}
{{ block.super }}
-sound-list-url="{% url "api:sound-list" %}?program={{ object.pk }}&episode__isnull"
+list-url="{% url "api:episodesound-list" %}"
+sound-list-url="{% url "api:sound-list" %}?program={{ object.parent_id }}"
sound-upload-url="{% url "api:sound-list" %}"
+sound-delete-url="{% url "api:sound-detail" pk=123 %}"
+:item-defaults="{episode: {{ object.pk }}}"
{% endblock %}
{% block inner %}
@@ -25,7 +32,7 @@ sound-upload-url="{% url "api:sound-list" %}"
{% with field.name as name %}
{% with field.initial as value %}
{% with field.field as field %}
- {% if name in "episode,program" %}
+ {% if name in "episode,program,sound" %}
{% include "./form_field.html" with value=value hidden=True %}
{% elif name != "file" %}
diff --git a/aircox/templates/aircox/dashboard/tracklist_editor.html b/aircox/templates/aircox/dashboard/tracklist_editor.html
index 3e09def..d99a169 100644
--- a/aircox/templates/aircox/dashboard/tracklist_editor.html
+++ b/aircox/templates/aircox/dashboard/tracklist_editor.html
@@ -4,9 +4,11 @@
{% block outer %}
{% with tag_id="inline-tracks" %}
{% with tag="a-track-list-editor" %}
+ {% with related_field="episode" %}
{{ block.super }}
{% endwith %}
{% endwith %}
+ {% endwith %}
{% endblock %}
{% block inner %}
diff --git a/aircox/templates/aircox/episode_form.html b/aircox/templates/aircox/episode_form.html
index c14378a..ae1bead 100644
--- a/aircox/templates/aircox/episode_form.html
+++ b/aircox/templates/aircox/episode_form.html
@@ -8,10 +8,8 @@
{% include "./dashboard/tracklist_editor.html" with formset=tracklist_formset %}
-
- {% translate "Podcasts" %}
- {% include "./dashboard/soundlist_editor.html" with formset=soundlist_formset %}
-
+
{% translate "Podcasts" %}
+ {% include "./dashboard/soundlist_editor.html" with formset=soundlist_formset %}
{% endblock %}
diff --git a/aircox/templates/aircox/page_form.html b/aircox/templates/aircox/page_form.html
index 87d6280..d25aade 100644
--- a/aircox/templates/aircox/page_form.html
+++ b/aircox/templates/aircox/page_form.html
@@ -1,59 +1,56 @@
{% extends "./page_detail.html" %}
-{% load static i18n %}
+{% load static aircox_admin i18n %}
{% block assets %}
{{ block.super }}
{% endblock %}
+{% block init-scripts %}
+aircox.labels = {% inline_labels %}
+{{ block.super }}
+{% endblock %}
+
{% block header-cover %}
-
+
{% translate "Change cover" %}
{% endblock %}
{% block content-container %}
-
-
-
-
-
-
-
-
-
-
-
-
-
[[ item.name || item.original_filename ]]
-
-
-
-
-
-
+ fileSelected('cover-select', 'cover-input', $refs.cover)"
+ >
+
+
-
- fileSelected('cover-select', 'cover', 'cover-input', 'cover-modal')">
- {% translate "Select" %}
-
+
+
+
+
+
+
+
[[ item.name || item.original_filename ]]
+
+
+
+
-
+