work on playlist & tracklist

This commit is contained in:
bkfox 2024-03-23 17:22:52 +01:00
parent 21f856e731
commit f41cc3ce0c
21 changed files with 451 additions and 142 deletions

View File

@ -1,12 +1,11 @@
from django import forms
from django.forms.models import modelformset_factory
from filer.models.filemodels import File
from aircox import models
from aircox.controllers.sound_file import SoundFile
__all__ = ("CommentForm", "PageForm", "ProgramForm", "EpisodeForm")
__all__ = ("CommentForm", "PageForm", "ProgramForm", "EpisodeForm", "SoundForm", "TrackFormSet")
class CommentForm(forms.ModelForm):
@ -40,18 +39,52 @@ class ProgramForm(PageForm):
class EpisodeForm(PageForm):
new_podcast = forms.FileField(required=False)
class Meta:
model = models.Episode
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)
# 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", "type", "position", "duration", "is_public", "is_downloadable"]
TrackFormSet = modelformset_factory(
models.Track,
fields=[
"position",
"artist",
"title",
"tags",
"album",
],
extra=0,
)
"""Track formset used in EpisodeUpdateView."""
SoundFormSet = modelformset_factory(
models.Sound,
fields=[
"position",
"name",
"type",
"is_public",
"is_downloadable",
],
extra=0,
)
"""Sound formset used in EpisodeUpdateView."""

View File

@ -148,12 +148,12 @@ class Sound(models.Model):
)
is_public = models.BooleanField(
_("public"),
help_text=_("whether it is publicly available as podcast"),
help_text=_("sound is available as podcast"),
default=False,
)
is_downloadable = models.BooleanField(
_("downloadable"),
help_text=_("whether it can be publicly downloaded by visitors (sound must be " "public)"),
help_text=_("sound can be downloaded by visitors (sound must be public)"),
default=False,
)

View File

@ -16,7 +16,7 @@
\**********************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _styles_admin_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./styles/admin.scss */ \"./src/styles/admin.scss\");\n/* harmony import */ var _index_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.js */ \"./src/index.js\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n/* harmony import */ var _track__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./track */ \"./src/track.js\");\n\n\n\n\n\nconst AdminApp = {\n ..._app__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n components: {\n ..._app__WEBPACK_IMPORTED_MODULE_2__[\"default\"].components,\n ..._components__WEBPACK_IMPORTED_MODULE_3__.admin\n },\n data() {\n return {\n ...super.data,\n Track: _track__WEBPACK_IMPORTED_MODULE_4__[\"default\"]\n };\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (AdminApp);\nwindow.App = AdminApp;\n\n//# sourceURL=webpack://aircox-assets/./src/admin.js?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _styles_admin_scss__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./styles/admin.scss */ \"./src/styles/admin.scss\");\n/* harmony import */ var _index_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index.js */ \"./src/index.js\");\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./app */ \"./src/app.js\");\n/* harmony import */ var _components__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./components */ \"./src/components/index.js\");\n\n\n\n\nconst AdminApp = {\n ..._app__WEBPACK_IMPORTED_MODULE_2__[\"default\"],\n components: {\n ..._app__WEBPACK_IMPORTED_MODULE_2__[\"default\"].components,\n ..._components__WEBPACK_IMPORTED_MODULE_3__.admin\n },\n data() {\n return {\n ...super.data\n };\n }\n};\n/* harmony default export */ __webpack_exports__[\"default\"] = (AdminApp);\nwindow.App = AdminApp;\n\n//# sourceURL=webpack://aircox-assets/./src/admin.js?");
/***/ })

File diff suppressed because one or more lines are too long

View File

@ -2,8 +2,29 @@
{% load static i18n humanize honeypot aircox %}
{% block page_form %}
<a-modal ref="sound-edit-modal" title="{% translate "Edit sound" %}">
<template #default>
<a-modal ref="sound-edit-modal" v-if="item" title="{% translate "Edit sound" %}">
<template #default="{item}">
{% for field in sound_form %}
{% if field.name in "episode,program" %}
<input type="hidden" name="{{ field.name }}" value="{{ field.value }}" />
{% else %}
<div class="field">
{% if field|is_checkbox %}
<label class="label">
<input type="text" name="{{ field.name }}" :value="item.{{ field.name }}">
{{ field.label }}
</label>
{% else %}
<label class="label">{{ field.label }}</label>
<div class="control">
<input type="text" name="{{ field.name }}" :value="item.{{ field.name }}">
</div>
{% endif %}
<p class="help">{{ field.help_text }}</p>
</div>
{% endif %}
{% endfor %}
</template>
</a-modal>
@ -11,37 +32,11 @@
<template v-slot="{podcasts,page}">
{{ block.super }}
<hr/>
{% include "./widgets/tracklist_editor.html" with formset=playlist_formset %}
{% include "./widgets/tracklist_editor.html" with formset=tracklist_formset %}
<hr/>
<section class="container">
<h3 class="title">{% translate "Sound files" %}</h3>
<a-playlist v-if="page" :set="podcasts"
name="{{ page.title }}"
list-class="menu-list" item-class="menu-item"
:player="player" :actions="['play']"
@select="player.playItems('queue', $event.item)">
<template #after-title="{item}">
<span class="flex-grow-1">
[[ item.data.type_display ]]
<span v-if="item.data.is_public">
/
{% translate "public" %}
</span>
<span v-if="item.data.is_downloadable">
/
{% translate "downloadable" %}
</span>
</span>
</template>
<template #actions="{item}">
<button type="button" class="button"
title="{% translate "Edit" %}">
<span class="icon">
<i class="fa fa-edit"></i>
</span>
</button>
</template>
</a-playlist>
{% include "./widgets/playlist_editor.html" with formset=playlist_formset %}
</section>
</template>
</a-episode>

View File

@ -75,6 +75,7 @@
{{ field }}
{% endif %}
</div>
<p class="help">{{ field.help_text }}</p>
</div>
{% endif %}
{% if field.errors %}

View File

@ -0,0 +1,21 @@
{% comment %}
Render a form field instance as field (to be used when no model instance is provided). Value is binded as vue, class to Bulma
Context:
- name: field name
- field: form field
- value: input ":value" attribute
{% endcomment %}
{% load aircox %}
{% if field|is_checkbox %}
<input type="checkbox" class="checkbox" name="{{ name }}" :checked="{{ value }}">
{% elif field|is_select %}
<select name="{{ name }}" class="select" :value="{{ value }}">
{% for value, label in field.widget.choices %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" class="input" name="{{ name }}" :value="{{ value }}">
{% endif %}

View File

@ -0,0 +1,74 @@
{% comment %}
Context:
- formset: formset
{% endcomment %}
{% load aircox aircox_admin static i18n %}
{% with formset.form.base_fields as fields %}
<div id="inline-sounds">
{{ formset.non_form_errors }}
<!-- formset.management_form -->
<a-playlist-editor
:labels="{% inline_labels %}"
:init-data="{% formset_inline_data formset=formset %}"
:default-columns="[{% for f in fields.keys %}{% if f != "position" %}'{{ f }}',{% endif %}{% endfor %}]"
settings-url="{% url "api:user-settings" %}"
data-prefix="{{ formset.prefix }}-">
<template #title>
<h3 class="title is-2">{% trans "Playlist" %}</h3>
</template>
<template #top="{items}">
<input type="hidden" name="{{ formset.prefix }}-TOTAL_FORMS"
:value="items.length || 0"/>
<input type="hidden" name="{{ formset.prefix }}-INITIAL_FORMS"
value="{{ formset.initial_form_count }}"/>
<input type="hidden" name="{{ formset.prefix }}-MIN_NUM_FORMS"
value="{{ formset.min_num }}"/>
<input type="hidden" name="{{ formset.prefix }}-MAX_NUM_FORMS"
value="{{ formset.max_num }}"/>
</template>
<template #rows-header-head>
<th style="max-width:2em" title="{% trans "Sound Position" %}"
aria-description="{% trans "Sound Position" %}">
<span class="icon">
<i class="fa fa-arrow-down-1-9"></i>
</span>
</th>
</template>
<template v-slot:row-head="{item,row}">
<td>
[[ row+1 ]]
<input type="hidden"
:name="'{{ formset.prefix }}-' + row + '-position'"
:value="row"/>
<input t-if="item.data.id" type="hidden"
:name="'{{ formset.prefix }}-' + row + '-id'"
:value="item.data.id || item.id"/>
{% for name, field in fields.items %}
{% if name != 'position' and field.widget.is_hidden %}
<input type="hidden"
:name="'{{ formset.prefix }}-' + row + '-{{ name }}'"
v-model="item.data[attr]"/>
{% endif %}
{% endfor %}
</td>
</template>
{% for name, field in fields.items %}
{% if not field.widget.is_hidden and not field.is_readonly %}
<template v-slot:row-{{ name }}="{item,cell,value,attr,emit}">
<div class="field">
<div class="control">
{% include "./form_field.html" with field=field name=name value="item.data."|add:name %}
</div>
<p v-for="error in item.error(attr)" class="help is-danger">
[[ error ]] !
</p>
</div>
</template>
{% endif %}
{% endfor %}
</a-playlist-editor>
</div>
{% endwith %}

View File

@ -5,15 +5,15 @@ Context:
{% endcomment %}
{% load aircox aircox_admin static i18n %}
{% with formset.form.fields as fields %}
{% with formset.form.base_fields as fields %}
<div id="inline-tracks">
{{ formset.non_form_errors }}
<!-- formset.management_form -->
<a-tracklist-editor
:labels="{% track_inline_labels %}"
:init-data="{% track_inline_data formset=formset %}"
:default-columns="[{% for f in fields %}{% if f != "position" %}'{{ f }}',{% endif %}{% endfor %}]"
:labels="{% inline_labels %}"
:init-data="{% formset_inline_data formset=formset %}"
:default-columns="[{% for f in fields.keys %}{% if f != "position" %}'{{ f }}',{% endif %}{% endfor %}]"
settings-url="{% url "api:user-settings" %}"
data-prefix="{{ formset.prefix }}-">
<template #title>
@ -47,25 +47,26 @@ Context:
:name="'{{ formset.prefix }}-' + row + '-id'"
:value="item.data.id || item.id"/>
{% for field in fields %}
{% if field != 'position' and field.widget.is_hidden %}
{% for name, field in fields.items %}
{% if name != 'position' and field.widget.is_hidden %}
<input type="hidden"
:name="'{{ formset.prefix }}-' + row + '-{{ field.name }}'"
:name="'{{ formset.prefix }}-' + row + '-{{ name }}'"
v-model="item.data[attr]"/>
{% endif %}
{% endfor %}
</td>
</template>
{% for field in fields %}
{% for name, field in fields.items %}
{% if not field.widget.is_hidden and not field.is_readonly %}
<template v-slot:row-{{ field }}="{item,cell,value,attr,emit}">
---
<template v-slot:row-{{ name }}="{item,cell,value,attr,emit}">
<div class="field">
<a-autocomplete
:input-class="['input', item.error(attr) ? 'is-danger' : 'half-field']"
url="{% url 'api:track-autocomplete' %}?{{ field }}=${query}&field={{ field }}"
:name="'{{ formset.prefix }}-' + cell.row + '-{{ field }}'"
url="{% url 'api:track-autocomplete' %}?{{ name }}=${query}&field={{ name }}"
:name="'{{ formset.prefix }}-' + cell.row + '-{{ name }}'"
v-model="item.data[attr]"
title="{{ field }}"
title="{{ name }}"
@change="emit('change', col)"/>
<p v-for="error in item.error(attr)" class="help is-danger">
[[ error ]] !

View File

@ -1,7 +1,7 @@
import json
import random
from django import template
from django import template, forms
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.template.loader import render_to_string
from django.urls import reverse
@ -148,3 +148,15 @@ def do_edit_view(obj):
@register.filter(name="detail_view")
def do_detail_view(obj):
return "%s-detail" % obj.split("-")[0]
@register.filter(name="is_checkbox")
def is_checkbox(field):
"""Return True if field is a checkbox."""
return isinstance(field.widget, forms.CheckboxInput)
@register.filter(name="is_select")
def is_select(field):
"""Return True if field is a select."""
return isinstance(field.widget, forms.Select)

View File

@ -6,7 +6,7 @@ from django.utils.translation import gettext_lazy as _
from aircox.serializers.admin import UserSettingsSerializer
__all__ = ("register", "do_get_admin_tools", "do_track_inline_data")
__all__ = ("register", "do_get_admin_tools", "do_formset_inline_data", "do_inline_labels")
register = template.Library()
@ -17,14 +17,24 @@ def do_get_admin_tools():
return admin.site.get_tools()
@register.simple_tag(name="track_inline_data", takes_context=True)
def do_track_inline_data(context, formset):
"""Return initial data for playlist editor as dict. Keys are:
@register.simple_tag(name="formset_inline_data", takes_context=True)
def do_formset_inline_data(context, formset):
"""Return initial data of formset as dict (used by TrackListEditor and
PlaylistEditor). Keys are:
- ``items``: list of items. Extra keys:
- ``__error__``: dict of form fields errors
- ``settings``: user's settings
"""
# --- get fields labels
model = formset.form.Meta.model
fields = {}
for field_name in formset.form.Meta.fields:
field = model._meta.get_field(field_name)
fields[field_name] = str(field.verbose_name).capitalize()
# --- get items
items = []
for form in formset.forms:
item = {name: form[name].value() for name in form.fields.keys()}
@ -36,7 +46,7 @@ def do_track_inline_data(context, formset):
item["tags"] = ", ".join(tag.name for tag in tags)
items.append(item)
data = {"items": items}
data = {"items": items, "fields": fields}
user = context["request"].user
settings = getattr(user, "aircox_settings", None)
data["settings"] = settings and UserSettingsSerializer(settings).data
@ -44,22 +54,19 @@ def do_track_inline_data(context, formset):
return source
track_inline_labels_ = {
"artist": _("Artist"),
"album": _("Album"),
"title": _("Title"),
"tags": _("Tags"),
"year": _("Year"),
inline_labels_ = {
# list editor
"add_item": _("Add an item"),
"remove_item": _("Remove"),
"save_settings": _("Save Settings"),
"discard_changes": _("Discard changes"),
# track list
"columns": _("Columns"),
"add_track": _("Add a track"),
"remove_track": _("Remove"),
"timestamp": _("Timestamp"),
}
@register.simple_tag(name="track_inline_labels")
def do_track_inline_labels():
@register.simple_tag(name="inline_labels")
def do_inline_labels():
"""Return labels for columns in playlist editor as dict."""
return json.dumps({k: str(v) for k, v in track_inline_labels_.items()})
return json.dumps({k: str(v) for k, v in inline_labels_.items()})

View File

@ -1,9 +1,8 @@
from django.contrib.auth.mixins import UserPassesTestMixin
from django.forms.models import modelformset_factory
from django.urls import reverse
from aircox.forms import EpisodeForm
from aircox.models import Episode, Program, StaticPage, Track
from aircox import forms
from ..filters import EpisodeFilters
from .page import PageListView
from .program import ProgramPageDetailView, BaseProgramMixin
@ -49,18 +48,9 @@ class PodcastListView(EpisodeListView):
class EpisodeUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
model = Episode
form_class = EpisodeForm
form_class = forms.EpisodeForm
template_name = "aircox/episode_form.html"
playlist_fields = (
"position",
"artist",
"title",
"tags",
"album",
)
"""Playlist editor's ordered fields."""
def test_func(self):
program = self.get_object().program
return self.request.user.has_perm("aircox.%s" % program.change_permission_codename)
@ -68,16 +58,42 @@ class EpisodeUpdateView(UserPassesTestMixin, BaseProgramMixin, PageUpdateView):
def get_success_url(self):
return reverse("episode-detail", kwargs={"slug": self.get_object().slug})
def get_playlist_queryset(self, episode):
def get_tracklist_queryset(self, episode):
return Track.objects.filter(episode=episode)
def get_tracklist_formset(self, episode, **kwargs):
kwargs.update(
{
"queryset": self.get_tracklist_queryset(episode),
"initial": {
"episode": episode.id,
},
}
)
return forms.TrackFormSet(**kwargs)
def get_playlist_queryset(self, episode):
return episode.sound_set.all()
def get_playlist_formset(self, episode, **kwargs):
kwargs["queryset"] = self.get_playlist_queryset(episode)
TrackFormSet = modelformset_factory(Track, fields=self.playlist_fields, extra=0)
return TrackFormSet(**kwargs)
kwargs.update(
{
"queryset": self.get_playlist_queryset(episode),
"initial": {
"program": episode.parent_id,
"episode": episode.id,
},
}
)
return forms.SoundFormSet(**kwargs)
def get_context_data(self, **kwargs):
kwargs["playlist_formset"] = self.get_playlist_formset(self.object)
kwargs.update(
{
"playlist_formset": self.get_playlist_formset(self.object),
"tracklist_formset": self.get_tracklist_formset(self.object),
}
)
return super().get_context_data(**kwargs)
def post(self, request, *args, **kwargs):

View File

@ -3,7 +3,6 @@ import './index.js'
import App from './app';
import {admin as components} from './components'
import Track from './track'
const AdminApp = {
...App,
@ -12,7 +11,6 @@ const AdminApp = {
data() {
return {
...super.data,
Track,
}
}
}

View File

@ -6,12 +6,17 @@
<div class="modal-card-title">
<slot name="title">{{ title }}</slot>
</div>
<button type="button" class="delete square" aria-label="close" @click="close">
<span class="icon">
<i class="fa fa-close"></i>
</span>
</button>
</header>
<section class="modal-card-body">
<slot name="default"></slot>
<slot name="default" :item="item"></slot>
</section>
<div class="modal-card-foot align-right">
<slot name="footer" :close="close"></slot>
<slot name="footer" :item="item" :close="close"></slot>
</div>
</div>
</section>
@ -24,13 +29,24 @@ export default {
data() {
return {
///! If true, modal is open
active: false,
///! Item or data passed down to slots.
item: null,
}
},
methods: {
open() { this.active = true; },
close() { this.active = false; },
///! Open modal dialog. Set provided `item` to dialog's one.
open(item=null) {
this.active = true
this.item = item
},
///! Close modal and reset item to null.
close() {
this.active = false
this.item = null
},
}
}
</script>

View File

@ -37,10 +37,6 @@
<span class="fas fa-pause" v-if="playing"></span>
<span class="fas fa-play" v-else></span>
</button>
<!--
<div class="media-cover" v-if="current && current.data.cover">
<img :src="current.data.cover" class="cover" />
</div> -->
<div :class="['a-player-bar-content', loaded && duration ? 'has-progress' : '']">
<slot name="content" :loaded="loaded" :live="live" :current="current"></slot>
</div>

View File

@ -0,0 +1,105 @@
<template>
<div class="a-playlist-editor">
<a-rows :set="set" :columns="columns"
:labels="initData.fields" :allow-create="true" :orderable="true"
@move="listItemMove">
<template v-for="[name,slot] of rowsSlots" :key="slot"
v-slot:[slot]="data">
<slot v-if="name != 'row-tail'" :name="name" v-bind="data"/>
</template>
</a-rows>
<div class="flex-row">
<div class="flex-grow-1 flex-row">
</div>
<div class="flex-grow-1 align-right">
<button type="button" class="button square is-warning p-2"
@click="loadData({items: this.initData.items},true)"
:title="labels.discard_changes"
:aria-label="labels.discard_changes"
>
<span class="icon"><i class="fa fa-rotate" /></span>
</button>
<button type="button" class="button square is-primary p-2"
@click="this.set.push(new this.set.model())"
:title="labels.add_sound"
:aria-label="labels.add_sound"
>
<span class="icon"><i class="fa fa-plus"/></span>
</button>
</div>
</div>
</div>
</template>
<script>
// import {dropRightWhile, cloneDeep, isEqual} from 'lodash'
import {cloneDeep} from 'lodash'
import Model, {Set} from '../model'
// import AActionButton from './AActionButton'
import ARows from './ARows'
// import AModal from "./AModal"
export default {
components: {ARows},
props: {
initData: Object,
dataPrefix: String,
labels: Object,
settingsUrl: String,
columns: {
type: Array,
default: () => ['name', "type", 'is_public', 'is_downloadable']
},
},
data() {
return {
set: new Set(Model),
}
},
computed: {
items() {
return this.set.items
},
rowsSlots() {
return Object.keys(this.$slots)
.filter(x => x.startsWith('row-') || x.startsWith('rows-'))
.map(x => [x, x.startsWith('rows-') ? x.slice(5) : x])
},
},
methods: {
listItemMove({from, to, set}) {
set.move(from, to);
},
/**
* Load initial data
*/
loadData({items=[] /*, settings=null*/}, reset=false) {
if(reset) {
this.set.items = []
}
for(var index in items)
this.set.push(cloneDeep(items[index]))
// if(settings)
// this.settingsSaved(settings)
},
},
watch: {
initData(val) {
this.loadData(val)
},
},
mounted() {
this.initData && this.loadData(this.initData)
},
}
</script>

View File

@ -3,7 +3,7 @@
<div ref="list" :class="['a-select-file-list', listClass]">
<!-- upload -->
<form ref="uploadForm" class="flex-column" v-if="state == STATE.DEFAULT">
<div class="field flex-grow-1" v-if="!uploadFile">
<div class="field flex-grow-1">
<label class="label">{{ uploadLabel }}</label>
<input type="file" ref="uploadFile" :name="uploadFieldName" @change="onSubmit"/>
</div>

View File

@ -35,7 +35,7 @@
</section>
<section v-show="page == Page.List" class="panel">
<a-rows :set="set" :columns="columns" :labels="labels"
<a-rows :set="set" :columns="columns" :labels="initData.fields"
:allow-create="true"
:orderable="true" @move="listItemMove" @colmove="columnMove"
@cell="onCellEvent">
@ -49,8 +49,8 @@
<td class="align-right pr-0">
<button type="button" class="button square"
@click.stop="items.splice(data.row,1)"
:title="labels.remove_track"
:aria-label="labels.remove_track">
:title="labels.remove_item"
:aria-label="labels.remove_item">
<span class="icon"><i class="fa fa-trash" /></span>
</button>
</td>
@ -82,8 +82,8 @@
</button>
<button type="button" class="button square is-primary p-2" v-if="page == Page.List"
@click="this.set.push(new this.set.model())"
:title="labels.add_track"
:aria-label="labels.add_track"
:title="labels.add_item"
:aria-label="labels.add_item"
>
<span class="icon"><i class="fa fa-plus"/></span>
</button>
@ -99,7 +99,7 @@
<table class="table is-bordered"
style="vertical-align: middle">
<tr>
<a-row :columns="columns" :item="labels"
<a-row :columns="columns" :item="initData.fields"
@move="formatMove" :orderable="true">
<template v-slot:cell-after="{cell}">
<td style="cursor:pointer;" v-if="cell.col < columns.length-1">
@ -149,8 +149,7 @@
</template>
<script>
import {dropRightWhile, cloneDeep, isEqual} from 'lodash'
import {Set} from '../model'
import Track from '../track'
import Model, {Set} from '../model'
import AActionButton from './AActionButton'
import ARow from './ARow'
@ -165,6 +164,7 @@ export const Page = {
export default {
components: { AActionButton, ARow, ARows, AModal },
props: {
///! initial data as: {items: [], fields: {column_name: label, settings: {}}
initData: Object,
dataPrefix: String,
labels: Object,
@ -182,7 +182,7 @@ export default {
return {
Page: Page,
page: Page.Text,
set: new Set(Track),
set: new Set(Model),
extraData: {},
settings,
savedSettings: cloneDeep(settings),

View File

@ -7,15 +7,16 @@ import AList from './AList'
import APage from './APage'
import APlayer from './APlayer'
import APlaylist from './APlaylist'
import ATracklistEditor from './ATracklistEditor'
import AProgress from './AProgress'
import ASoundItem from './ASoundItem'
import ASwitch from './ASwitch'
import AStatistics from './AStatistics'
import AStreamer from './AStreamer'
import AModal from "./AModal"
import ASelectFile from "./ASelectFile"
import AStatistics from './AStatistics'
import AStreamer from './AStreamer'
import ATracklistEditor from './ATracklistEditor'
import APlaylistEditor from './APlaylistEditor'
/**
* Core components
@ -35,5 +36,5 @@ export const admin = {
export const dashboard = {
...base,
AActionButton, ASelectFile, AModal, ATracklistEditor,
AActionButton, ASelectFile, AModal, ATracklistEditor, APlaylistEditor
}

View File

@ -8,13 +8,11 @@ const DashboardApp = {
...App,
components: {...App.components, ...components},
/*
data() {
return {
editPageContent: null,
modalItem: null,
}
},
*/
methods: {
...App.methods,

View File

@ -1,5 +0,0 @@
import Model from './model'
export default class Track extends Model {
static getId(data) { return data.pk }
}