#39: Playlist Editor #81
@ -70,6 +70,20 @@ class SoundAdmin(SortableAdminBase, admin.ModelAdmin):
 | 
				
			|||||||
                if obj.type != Sound.TYPE_REMOVED else ''
 | 
					                if obj.type != Sound.TYPE_REMOVED else ''
 | 
				
			||||||
    audio.short_description = _('Audio')
 | 
					    audio.short_description = _('Audio')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def add_view(self, request, object_id, form_url='', context=None):
 | 
				
			||||||
 | 
					        context = context or {}
 | 
				
			||||||
 | 
					        context['init_app'] = True
 | 
				
			||||||
 | 
					        context['init_el'] = '#inline-tracks'
 | 
				
			||||||
 | 
					        context['track_timestamp'] = True
 | 
				
			||||||
 | 
					        return super().change_view(request, object_id, form_url, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def change_view(self, request, object_id, form_url='', context=None):
 | 
				
			||||||
 | 
					        context = context or {}
 | 
				
			||||||
 | 
					        context['init_app'] = True
 | 
				
			||||||
 | 
					        context['init_el'] = '#inline-tracks'
 | 
				
			||||||
 | 
					        context['track_timestamp'] = True
 | 
				
			||||||
 | 
					        return super().change_view(request, object_id, form_url, context)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@admin.register(Track)
 | 
					@admin.register(Track)
 | 
				
			||||||
class TrackAdmin(admin.ModelAdmin):
 | 
					class TrackAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ class TrackSerializer(TaggitSerializer, serializers.ModelSerializer):
 | 
				
			|||||||
    class Meta:
 | 
					    class Meta:
 | 
				
			||||||
        model = Track
 | 
					        model = Track
 | 
				
			||||||
        fields = ('pk', 'artist', 'title', 'album', 'year', 'position',
 | 
					        fields = ('pk', 'artist', 'title', 'album', 'year', 'position',
 | 
				
			||||||
                  'info', 'tags', 'episode', 'sound')
 | 
					                  'info', 'tags', 'episode', 'sound', 'timestamp')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserSettingsSerializer(serializers.ModelSerializer):
 | 
					class UserSettingsSerializer(serializers.ModelSerializer):
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -10,6 +10,9 @@
 | 
				
			|||||||
    <a-playlist-editor
 | 
					    <a-playlist-editor
 | 
				
			||||||
            :labels="{% track_inline_labels %}"
 | 
					            :labels="{% track_inline_labels %}"
 | 
				
			||||||
            :init-data="{% track_inline_data formset=formset %}"
 | 
					            :init-data="{% track_inline_data formset=formset %}"
 | 
				
			||||||
 | 
					            {% if not track_timestamp %}
 | 
				
			||||||
 | 
					            :hide-columns="['timestamp']"
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
            settings-url="{% url "api:user-settings" %}"
 | 
					            settings-url="{% url "api:user-settings" %}"
 | 
				
			||||||
            data-prefix="{{ formset.prefix }}-">
 | 
					            data-prefix="{{ formset.prefix }}-">
 | 
				
			||||||
        <template #title>
 | 
					        <template #title>
 | 
				
			||||||
@ -39,9 +42,9 @@
 | 
				
			|||||||
                <input type="hidden"
 | 
					                <input type="hidden"
 | 
				
			||||||
                    :name="'{{ formset.prefix }}-' + row + '-position'"
 | 
					                    :name="'{{ formset.prefix }}-' + row + '-position'"
 | 
				
			||||||
                    :value="row"/>
 | 
					                    :value="row"/>
 | 
				
			||||||
                <input t-if="item.id" type="hidden"
 | 
					                <input t-if="item.data.id" type="hidden"
 | 
				
			||||||
                    :name="'{{ formset.prefix }}-' + row + '-id'"
 | 
					                    :name="'{{ formset.prefix }}-' + row + '-id'"
 | 
				
			||||||
                    :value="item.data.id"/>
 | 
					                    :value="item.data.id || item.id"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                {% for field in admin_formset.fields %}
 | 
					                {% for field in admin_formset.fields %}
 | 
				
			||||||
                {% if field.name != 'position' and field.widget.is_hidden %}
 | 
					                {% if field.name != 'position' and field.widget.is_hidden %}
 | 
				
			||||||
@ -54,7 +57,7 @@
 | 
				
			|||||||
        </template>
 | 
					        </template>
 | 
				
			||||||
        {% for field in admin_formset.fields %}
 | 
					        {% for field in admin_formset.fields %}
 | 
				
			||||||
        {% if not field.widget.is_hidden and not field.is_readonly %}
 | 
					        {% if not field.widget.is_hidden and not field.is_readonly %}
 | 
				
			||||||
        <template v-slot:row-{{ field.name }}="{item,col,row,value,attr,emit}">
 | 
					        <template v-slot:row-{{ field.name }}="{item,cell,value,attr,emit}">
 | 
				
			||||||
            <div class="field">
 | 
					            <div class="field">
 | 
				
			||||||
                {% if field.name in 'artist,title,album' %}
 | 
					                {% if field.name in 'artist,title,album' %}
 | 
				
			||||||
                <a-autocomplete
 | 
					                <a-autocomplete
 | 
				
			||||||
@ -65,7 +68,7 @@
 | 
				
			|||||||
                    <input type="{{ widget.type }}"
 | 
					                    <input type="{{ widget.type }}"
 | 
				
			||||||
                        :class="['input', item.error(attr) ? 'is-danger' : 'half-field']"
 | 
					                        :class="['input', item.error(attr) ? 'is-danger' : 'half-field']"
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
                        :name="'{{ formset.prefix }}-' + row + '-{{ field.name }}'"
 | 
					                        :name="'{{ formset.prefix }}-' + cell.row + '-{{ field.name }}'"
 | 
				
			||||||
                        v-model="item.data[attr]"
 | 
					                        v-model="item.data[attr]"
 | 
				
			||||||
                        @change="emit('change', col)"/>
 | 
					                        @change="emit('change', col)"/>
 | 
				
			||||||
                {% if field.name not in 'artist,title,album' %}
 | 
					                {% if field.name not in 'artist,title,album' %}
 | 
				
			||||||
 | 
				
			|||||||
@ -56,5 +56,8 @@ def do_track_inline_labels():
 | 
				
			|||||||
        'save_settings': __('Save Settings'),
 | 
					        'save_settings': __('Save Settings'),
 | 
				
			||||||
        'discard_changes': __('Discard changes'),
 | 
					        'discard_changes': __('Discard changes'),
 | 
				
			||||||
        'columns': __('Columns'),
 | 
					        'columns': __('Columns'),
 | 
				
			||||||
 | 
					        'add_track': __('Add a track'),
 | 
				
			||||||
 | 
					        'remove_track': __('Remove'),
 | 
				
			||||||
 | 
					        'timestamp': __('Timestamp'),
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -140,7 +140,6 @@ export default {
 | 
				
			|||||||
            return this.labelField ? item && item[this.labelField] : item;
 | 
					            return this.labelField ? item && item[this.labelField] : item;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        hide() {
 | 
					        hide() {
 | 
				
			||||||
            this.cursor = -1;
 | 
					            this.cursor = -1;
 | 
				
			||||||
            this.selectedIndex = -1;
 | 
					            this.selectedIndex = -1;
 | 
				
			||||||
 | 
				
			|||||||
@ -41,7 +41,19 @@
 | 
				
			|||||||
                    @cell="onCellEvent">
 | 
					                    @cell="onCellEvent">
 | 
				
			||||||
                <template v-for="[name,slot] of rowsSlots" :key="slot"
 | 
					                <template v-for="[name,slot] of rowsSlots" :key="slot"
 | 
				
			||||||
                        v-slot:[slot]="data">
 | 
					                        v-slot:[slot]="data">
 | 
				
			||||||
                    <slot :name="name" v-bind="data"/>
 | 
					                    <slot v-if="name != 'row-tail'" :name="name" v-bind="data"/>
 | 
				
			||||||
 | 
					                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                <template v-slot:row-tail="data">
 | 
				
			||||||
 | 
					                    <slot v-if="$slots['row-tail']" :name="row-tail" v-bind="data"/>
 | 
				
			||||||
 | 
					                    <td>
 | 
				
			||||||
 | 
					                        <a class="button is-danger is-outlined p-3 is-size-6"
 | 
				
			||||||
 | 
					                                @click="items.splice(data.row,1)"
 | 
				
			||||||
 | 
					                                :title="labels.remove_track"
 | 
				
			||||||
 | 
					                                :aria-label="labels.remove_track">
 | 
				
			||||||
 | 
					                            <span class="icon"><i class="fa fa-trash" /></span>
 | 
				
			||||||
 | 
					                        </a>
 | 
				
			||||||
 | 
					                    </td>
 | 
				
			||||||
                </template>
 | 
					                </template>
 | 
				
			||||||
            </a-rows>
 | 
					            </a-rows>
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
@ -58,7 +70,7 @@
 | 
				
			|||||||
                        v-model="separator" @change="updateList()"/>
 | 
					                        v-model="separator" @change="updateList()"/>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="field is-inline-block is-vcentered mr-5">
 | 
					            <div class="field is-inline-block is-vcentered mr-3">
 | 
				
			||||||
                <label class="label is-inline mr-2"
 | 
					                <label class="label is-inline mr-2"
 | 
				
			||||||
                        style="vertical-align: middle">
 | 
					                        style="vertical-align: middle">
 | 
				
			||||||
                    {{ labels.columns }}</label>
 | 
					                    {{ labels.columns }}</label>
 | 
				
			||||||
@ -95,6 +107,11 @@
 | 
				
			|||||||
                    <span class="icon"><i class="fa fa-rotate" /></span>
 | 
					                    <span class="icon"><i class="fa fa-rotate" /></span>
 | 
				
			||||||
                    <span>{{ labels.discard_changes }}</span>
 | 
					                    <span>{{ labels.discard_changes }}</span>
 | 
				
			||||||
                </a>
 | 
					                </a>
 | 
				
			||||||
 | 
					                <a class="button is-primary p-2 ml-2" t-if="page == page.List"
 | 
				
			||||||
 | 
					                        @click="this.set.push(new this.set.model())">
 | 
				
			||||||
 | 
					                    <span class="icon"><i class="fa fa-plus"/></span>
 | 
				
			||||||
 | 
					                    <span>{{ labels.add_track }}</span>
 | 
				
			||||||
 | 
					                </a>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <slot name="bottom" :set="set" :columns="columns" :items="items"/>
 | 
					        <slot name="bottom" :set="set" :columns="columns" :items="items"/>
 | 
				
			||||||
@ -123,7 +140,7 @@ export default {
 | 
				
			|||||||
        settingsUrl: String,
 | 
					        settingsUrl: String,
 | 
				
			||||||
        defaultColumns: {
 | 
					        defaultColumns: {
 | 
				
			||||||
            type: Array,
 | 
					            type: Array,
 | 
				
			||||||
            default: () => ['artist', 'title', 'tags', 'album', 'year']},
 | 
					            default: () => ['artist', 'title', 'tags', 'album', 'year', 'timestamp']},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data() {
 | 
					    data() {
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,9 @@
 | 
				
			|||||||
                        :value="itemData && itemData[attr]">
 | 
					                        :value="itemData && itemData[attr]">
 | 
				
			||||||
                    {{ itemData && itemData[attr] }}
 | 
					                    {{ itemData && itemData[attr] }}
 | 
				
			||||||
                </slot>
 | 
					                </slot>
 | 
				
			||||||
 | 
					                <slot name="cell" :item="item" :cell="cells[col]"
 | 
				
			||||||
 | 
					                        :data="itemData" :attr="attr" :emit="cellEmit"
 | 
				
			||||||
 | 
					                        :value="itemData && itemData[attr]"/>
 | 
				
			||||||
            </component>
 | 
					            </component>
 | 
				
			||||||
            <slot name="cell-after" :item="item" :col="col" :cell="cells[col]"
 | 
					            <slot name="cell-after" :item="item" :col="col" :cell="cells[col]"
 | 
				
			||||||
                    :attr="attr"/>
 | 
					                    :attr="attr"/>
 | 
				
			||||||
 | 
				
			|||||||
@ -31,14 +31,6 @@
 | 
				
			|||||||
                    </template>
 | 
					                    </template>
 | 
				
			||||||
                </a-row>
 | 
					                </a-row>
 | 
				
			||||||
            </template>
 | 
					            </template>
 | 
				
			||||||
            <template v-if="allowCreate">
 | 
					 | 
				
			||||||
                <a-row :item="extraItem" :cell="{row:items.length, columns}" 
 | 
					 | 
				
			||||||
                        @keypress.enter.stop.prevent="validateExtraCell">
 | 
					 | 
				
			||||||
                    <template v-for="[name,slot] of rowSlots" :key="slot" v-slot:[slot]="data">
 | 
					 | 
				
			||||||
                        <slot :name="name" v-bind="data"/>
 | 
					 | 
				
			||||||
                    </template>
 | 
					 | 
				
			||||||
                </a-row>
 | 
					 | 
				
			||||||
            </template>
 | 
					 | 
				
			||||||
            <slot name="tail"/>
 | 
					            <slot name="tail"/>
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
@ -85,13 +77,6 @@ const Component = {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    methods: {
 | 
					    methods: {
 | 
				
			||||||
        validateExtraCell() {
 | 
					 | 
				
			||||||
            if(!this.allowCreate)
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            this.set.push(this.extraItem)
 | 
					 | 
				
			||||||
            this.extraItem = new this.set.model()
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        onControlKey(event, cell) {
 | 
					        onControlKey(event, cell) {
 | 
				
			||||||
            switch(event.key) {
 | 
					            switch(event.key) {
 | 
				
			||||||
                case "ArrowUp": this.focus(-1, 0, cell)
 | 
					                case "ArrowUp": this.focus(-1, 0, cell)
 | 
				
			||||||
 | 
				
			|||||||
@ -42,7 +42,7 @@ export default class Model {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    get errors() {
 | 
					    get errors() {
 | 
				
			||||||
        return this.data.__errors__
 | 
					        return this.data && this.data.__errors__
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -143,6 +143,13 @@ export default class Model {
 | 
				
			|||||||
        return item === null ? item : new this(JSON.parse(item));
 | 
					        return item === null ? item : new this(JSON.parse(item));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Return true if model instance has no data
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    get isEmpty() {
 | 
				
			||||||
 | 
					        return !this.data || Object.keys(this.data).findIndex(k => !!this.data[k] && this.data[k] !== 0) == -1
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return error for a specific attribute name if any 
 | 
					     * Return error for a specific attribute name if any 
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user