parent
916deaea6a
commit
0ad60238a9
30 changed files with 874 additions and 103 deletions
@ -0,0 +1,45 @@ |
||||
from rest_framework import serializers |
||||
|
||||
from api.v1.serializers.content import ContentSerializer, ContentCreateSerializer, ImageObjectSerializer |
||||
from api.v1.serializers.mixins import DispatchContentMixin |
||||
|
||||
from apps.content.models import (Contest, ContestWork) |
||||
|
||||
|
||||
class ContestSerializer(serializers.ModelSerializer): |
||||
cover = ImageObjectSerializer() |
||||
content = ContentSerializer(many=True) |
||||
class Meta: |
||||
model = Contest |
||||
fields = '__all__' |
||||
|
||||
|
||||
class ContestCreateSerializer(DispatchContentMixin, serializers.ModelSerializer): |
||||
content = serializers.ListSerializer( |
||||
child=ContentCreateSerializer(), |
||||
required=False, |
||||
) |
||||
class Meta: |
||||
model = Contest |
||||
fields = '__all__' |
||||
|
||||
def create(self, validated_data): |
||||
content = validated_data.pop('content', []) |
||||
contest = super().create(validated_data) |
||||
self.dispatch_content(contest, content) |
||||
return contest |
||||
|
||||
def update(self, instance, validated_data): |
||||
content = validated_data.pop('content', []) |
||||
contest = super().update(instance, validated_data) |
||||
self.dispatch_content(contest, content) |
||||
return contest |
||||
|
||||
def to_representation(self, instance): |
||||
return ContestSerializer(instance=instance, context=self.context).to_representation(instance) |
||||
|
||||
|
||||
class ContestWorkSerializer(serializers.ModelSerializer): |
||||
class Meta: |
||||
model = ContestWork |
||||
fields = '__all__' |
||||
@ -0,0 +1,48 @@ |
||||
# Generated by Django 2.0.6 on 2018-08-13 13:06 |
||||
|
||||
from django.conf import settings |
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('course', '0040_course_age'), |
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
('content', '0020_auto_20180424_1607'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Contest', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('title', models.CharField(max_length=255)), |
||||
('description', models.TextField(blank=True, default='', max_length=1000)), |
||||
('slug', models.SlugField(allow_unicode=True, blank=True, max_length=100, null=True, unique=True)), |
||||
('date_start', models.DateField(blank=True, null=True, verbose_name='Дата начала')), |
||||
('date_end', models.DateField(blank=True, null=True, verbose_name='Дата окончания')), |
||||
('active', models.BooleanField(default=True)), |
||||
('cover', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='contest_covers', to='content.ImageObject', verbose_name='Фоновая картинка')), |
||||
], |
||||
), |
||||
migrations.CreateModel( |
||||
name='ContestWork', |
||||
fields=[ |
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
||||
('child_full_name', models.CharField(max_length=255)), |
||||
('age', models.SmallIntegerField()), |
||||
('created_at', models.DateTimeField(auto_now_add=True)), |
||||
('contest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='content.Contest')), |
||||
('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contest_work_images', to='content.ImageObject', verbose_name='Работа участника')), |
||||
('likes', models.ManyToManyField(blank=True, to='course.Like')), |
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), |
||||
], |
||||
), |
||||
migrations.AddField( |
||||
model_name='content', |
||||
name='contest', |
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content', to='content.Contest', verbose_name='Конкурс'), |
||||
), |
||||
] |
||||
@ -0,0 +1,3 @@ |
||||
<div> |
||||
|
||||
</div> |
||||
@ -0,0 +1,44 @@ |
||||
{% extends "templates/lilcity/index.html" %} |
||||
|
||||
|
||||
{% block content %} |
||||
<div class="main main_default"> |
||||
<div class="main__center center"> |
||||
<div class="main__title"> |
||||
<span class="main__bold">Lil School</span> — первая образовательная онлайн-платформа креативного мышления для детей |
||||
</div> |
||||
<div class="main__subtitle"> |
||||
Приглашаем вас на месяц открытых дверей в Lil School |
||||
</div> |
||||
<div class="main__actions"> |
||||
<a class="main__btn btn btn_white" href="#">Загрузить свою работу</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% for content in contest.content.all %} |
||||
|
||||
{% with template="content/blocks/"|add:content.ctype|add:".html" %} |
||||
{% include template %} |
||||
{% endwith %} |
||||
|
||||
{% endfor %} |
||||
<div class="section"> |
||||
<div class="section__center center"> |
||||
<a id="gallery" name="gallery"> |
||||
<div class="title title_center">Галерея</div> |
||||
</a> |
||||
<div class="text"> |
||||
<p>Тысячи шедевров уже созданы благодаря Lil School. Более 10000 работ можно |
||||
<a target="_blank" href='{{ config.SERVICE_INSTAGRAM_URL }}'>увидеть</a> в Инстаграм</p> |
||||
<img class="text__curve text__curve_three" src="{% static 'img/curve-3.svg' %}"> |
||||
</div> |
||||
<div class="gallery"> |
||||
<div class="gallery__grid"> |
||||
{% for contest_work in contest_works %} |
||||
{% include '' %} |
||||
{% endfor %} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock content %} |
||||
@ -0,0 +1,18 @@ |
||||
{% extends "templates/lilcity/edit_index.html" %} |
||||
{% load static %} |
||||
|
||||
{% block title %} |
||||
{% if object %} |
||||
Редактирование конкурса {{ object.title }} |
||||
{% else %} |
||||
Создание конкурса |
||||
{% endif %} |
||||
{% endblock title %} |
||||
|
||||
{% block content %} |
||||
<contest-redactor {% if object and object.id %}:contest-id="{{ object.id }}"{% endif %}></contest-redactor> |
||||
{% endblock content %} |
||||
{% block foot %} |
||||
<script type="text/javascript" src="{% static 'contestRedactor.js' %}"></script> |
||||
<link rel="stylesheet" href="{% static 'contestRedactor.css' %}" /> |
||||
{% endblock foot %} |
||||
@ -1,2 +1,24 @@ |
||||
from django.shortcuts import render |
||||
from django.contrib.auth.decorators import login_required |
||||
from django.shortcuts import get_object_or_404 |
||||
from django.utils.decorators import method_decorator |
||||
from django.views.generic import TemplateView |
||||
|
||||
from apps.content.models import Contest |
||||
|
||||
|
||||
@method_decorator(login_required, name='dispatch') |
||||
class ContestEditView(TemplateView): |
||||
template_name = 'content/contest_edit.html' |
||||
|
||||
def get(self, request, pk=None, lesson=None): |
||||
if pk: |
||||
self.object = get_object_or_404(Contest, pk=pk) |
||||
else: |
||||
self.object = Contest() |
||||
|
||||
return super().get(request) |
||||
|
||||
def get_context_data(self): |
||||
context = super().get_context_data() |
||||
context['object'] = self.object |
||||
return context |
||||
|
||||
@ -0,0 +1,18 @@ |
||||
# Generated by Django 2.0.6 on 2018-08-08 01:54 |
||||
|
||||
from django.db import migrations, models |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('course', '0039_lesson_position'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='course', |
||||
name='age', |
||||
field=models.SmallIntegerField(choices=[(0, ''), (1, 'до 5'), (2, '5-7'), (3, '7-9'), (4, '9-12'), (5, '12-15'), (6, '15-18'), (7, 'от 18')], default=0), |
||||
), |
||||
] |
||||
@ -0,0 +1,36 @@ |
||||
# Generated by Django 2.0.6 on 2018-08-13 13:06 |
||||
|
||||
from django.db import migrations, models |
||||
import django.db.models.deletion |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('content', '0021_auto_20180813_1306'), |
||||
('course', '0040_course_age'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='ContestWorkComment', |
||||
fields=[ |
||||
('comment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='course.Comment')), |
||||
('contest_work', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='content.ContestWork')), |
||||
], |
||||
options={ |
||||
'ordering': ('tree_id', 'lft'), |
||||
'abstract': False, |
||||
'base_manager_name': 'objects', |
||||
}, |
||||
bases=('course.comment',), |
||||
), |
||||
migrations.AlterModelOptions( |
||||
name='course', |
||||
options={'ordering': ['-is_featured'], 'verbose_name': 'Курс', 'verbose_name_plural': 'Курсы'}, |
||||
), |
||||
migrations.RemoveField( |
||||
model_name='course', |
||||
name='age', |
||||
), |
||||
] |
||||
@ -0,0 +1,8 @@ |
||||
<script> |
||||
window.LIL_STORE = { |
||||
accessToken: '{{ request.user.auth_token }}', |
||||
user: { |
||||
id: '{{ request.user.id }}', |
||||
} |
||||
}; |
||||
</script> |
||||
@ -0,0 +1,361 @@ |
||||
<template> |
||||
<form @submit.prevent="save"> |
||||
<div class="info"> |
||||
<div class="info__section" :style="coverBackgroundStyle"> |
||||
<div class="info__main"> |
||||
<div class="info__head"> |
||||
<div class="info__upload upload"> |
||||
Загрузить фон |
||||
<input type="file" class="upload__file" @change="onCoverImageSelected"> |
||||
</div> |
||||
</div> |
||||
<div class="info__title"> |
||||
<div class="info__field field field_info" |
||||
v-bind:class="{ error: $v.contest.title.$dirty && $v.contest.title.$invalid }"> |
||||
<div class="field__label">Название</div> |
||||
<div class="field__wrap"> |
||||
<textarea class="field__textarea" |
||||
rows="1" |
||||
v-autosize="contest.title" |
||||
@change="onTitleInput" |
||||
v-model="contest.title"></textarea> |
||||
</div> |
||||
</div> |
||||
<div class="info__field field field_info field_short_description"> |
||||
<div class="field__label">Описание</div> |
||||
<div class="field__wrap"> |
||||
<textarea class="field__textarea" |
||||
v-autosize="contest.description" |
||||
v-model="contest.description"></textarea> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="info__foot"> |
||||
<div class="info__field field"> |
||||
<div class="field__label field__label_gray">ССЫЛКА</div> |
||||
<div class="field__wrap"> |
||||
<input type="text" class="field__input" v-model="contest.slug" @input="slugChanged = true"> |
||||
</div> |
||||
<div class="field__wrap field__wrap--additional">{{ contestFullUrl }}</div> |
||||
</div> |
||||
<div class="info__field field"> |
||||
<div class="field__label">ДАТА НАЧАЛА</div> |
||||
<div class="field__wrap"> |
||||
<vue-datepicker input-class="field__input" v-model="contest.date_start" language="ru" format="dd/MM/yyyy"/> |
||||
</div> |
||||
</div> |
||||
<div class="info__field field"> |
||||
<div class="field__label">ДАТА ОКОНЧАНИЯ</div> |
||||
<div class="field__wrap"> |
||||
<vue-datepicker input-class="field__input" v-model="contest.date_end" language="ru" format="dd/MM/yyyy"/> |
||||
</div> |
||||
</div> |
||||
<button type="submit">Save</button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="info__sidebar"> |
||||
<div class="info__wrap"> |
||||
<div class="info__fieldset"> |
||||
|
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="section"> |
||||
<div class="section__center center"> |
||||
<div class="kit"> |
||||
<div class="kit__body"> |
||||
<vue-draggable v-model="contest.content" @start="drag=true" @end="drag=false" :options="{ handle: '.sortable__handle' }"> |
||||
<div v-for="(block, index) in contest.content" :key="block.data.id ? block.data.id : block.data.guid"> |
||||
<block-text v-if="block.type === 'text'" |
||||
:index="index" |
||||
:title.sync="block.data.title" |
||||
:text.sync="block.data.text" |
||||
v-on:remove="onBlockRemoved" |
||||
:access-token="accessToken"/> |
||||
<block-image-text v-if="block.type === 'image-text'" |
||||
:index="index" |
||||
:title.sync="block.data.title" |
||||
:text.sync="block.data.text" |
||||
:image-id.sync="block.data.image_id" |
||||
:image-url.sync="block.data.image_url" |
||||
v-on:remove="onBlockRemoved" |
||||
:access-token="accessToken"/> |
||||
<block-image v-if="block.type === 'image'" |
||||
:index="index" |
||||
:title.sync="block.data.title" |
||||
:image-id.sync="block.data.image_id" |
||||
:image-url.sync="block.data.image_url" |
||||
v-on:remove="onBlockRemoved" |
||||
:access-token="accessToken"/> |
||||
<block-images v-if="block.type === 'images'" |
||||
:index="index" |
||||
:title.sync="block.data.title" |
||||
:text.sync="block.data.text" |
||||
:images.sync="block.data.images" |
||||
v-on:remove="onBlockRemoved" |
||||
:access-token="accessToken"/> |
||||
<block-video v-if="block.type === 'video'" |
||||
:index="index" |
||||
:title.sync="block.data.title" |
||||
v-on:remove="onBlockRemoved" |
||||
:video-url.sync="block.data.video_url"/> |
||||
</div> |
||||
</vue-draggable> |
||||
|
||||
<block-add v-on:added="onBlockAdded"/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</form> |
||||
</template> |
||||
|
||||
<script> |
||||
import BlockText from './blocks/BlockText' |
||||
import BlockImage from './blocks/BlockImage' |
||||
import BlockImages from './blocks/BlockImages' |
||||
import BlockImageText from './blocks/BlockImageText' |
||||
import BlockVideo from './blocks/BlockVideo' |
||||
import BlockAdd from "./blocks/BlockAdd"; |
||||
import {api} from "../js/modules/api"; |
||||
import DatePicker from 'vuejs-datepicker'; |
||||
import Draggable from 'vuedraggable'; |
||||
import slugify from 'slugify'; |
||||
import {required, minValue, numeric, url } from 'vuelidate/lib/validators' |
||||
import _ from 'lodash'; |
||||
import moment from 'moment' |
||||
|
||||
export default { |
||||
name: 'contest-redactor', |
||||
props: ["contestId"], |
||||
data() { |
||||
return { |
||||
loading: false, |
||||
slugChanged: false, |
||||
contest: { |
||||
coverImage: '', |
||||
coverImageId: null, |
||||
|
||||
title: '', |
||||
description: '', |
||||
content: [], |
||||
date_start: '', |
||||
date_end: '', |
||||
slug: '', |
||||
active: true, |
||||
} |
||||
} |
||||
}, |
||||
computed: { |
||||
accessToken() { |
||||
return window.LIL_STORE.accessToken; |
||||
}, |
||||
coverBackgroundStyle() { |
||||
return this.contest.coverImage ? `background-image: url(${this.contest.coverImage});` : ''; |
||||
}, |
||||
contestFullUrl() { |
||||
return `https://lil.city/contest/${this.contest.slug}`; |
||||
}, |
||||
}, |
||||
|
||||
validations() { |
||||
return { |
||||
contest: { |
||||
title: { |
||||
required |
||||
}, |
||||
}, |
||||
}; |
||||
}, |
||||
mounted() { |
||||
if (this.contestId) { |
||||
this.load(); |
||||
} |
||||
}, |
||||
methods: { |
||||
onBlockRemoved(blockIndex) { |
||||
const blockToRemove = this.contest.content[blockIndex]; |
||||
// Удаляем блок из Vue |
||||
this.contest.content.splice(blockIndex, 1); |
||||
// Если блок уже был записан в БД, отправляем запрос на сервер на удаление блока из БД |
||||
if (blockToRemove.data.id) { |
||||
api.removeContentBlock(blockToRemove, this.accessToken); |
||||
} |
||||
}, |
||||
onBlockAdded(blockData) { |
||||
this.contest.content.push(blockData); |
||||
}, |
||||
onTitleInput() { |
||||
this.$v.contest.title.$touch(); |
||||
if (!this.slugChanged) { |
||||
this.contest.slug = slugify(this.contest.title); |
||||
} |
||||
}, |
||||
onCoverImageSelected(event) { |
||||
let file = event.target.files[0]; |
||||
let reader = new FileReader(); |
||||
reader.onload = () => { |
||||
this.$set(this.contest, 'coverImage', reader.result); |
||||
api.uploadImage(reader.result, this.accessToken) |
||||
.then((response) => { |
||||
this.contest.coverImageId = response.data.id; |
||||
}) |
||||
.catch((error) => { |
||||
//console.log('error', error); |
||||
}); |
||||
}; |
||||
if (file) { |
||||
reader.readAsDataURL(file); |
||||
} |
||||
}, |
||||
|
||||
processContestJson(data) { |
||||
this.contest = { |
||||
coverImage: data.cover && data.cover.image || null, |
||||
coverImageId: data.cover && data.cover.id || null, |
||||
|
||||
id: data.id, |
||||
title: data.title, |
||||
description: data.description, |
||||
content: api.convertContentResponse(data.content), |
||||
date_start: data.date_start, |
||||
date_end: data.date_end, |
||||
slug: data.slug, |
||||
active: data.active, |
||||
|
||||
}; |
||||
}, |
||||
load() { |
||||
this.loading = true; |
||||
const request = api.get(`/api/v1/contests/${this.contestId}/`, { |
||||
headers: { |
||||
'Authorization': `Token ${window.LIL_STORE.accessToken}`, |
||||
} |
||||
}); |
||||
request |
||||
.then((response) => { |
||||
this.processContestJson(response.data); |
||||
this.$nextTick(() => { |
||||
this.loading = false; |
||||
}); |
||||
}) |
||||
.catch((err) => { |
||||
this.loading = false; |
||||
}); |
||||
|
||||
return request; |
||||
}, |
||||
save() { |
||||
let data = _.pick(this.contest, ['title', 'description', 'slug', 'active']); |
||||
data.date_start = data.date_start ? moment(data.date_start).format('MM-DD-YYYY') : null; |
||||
data.date_end = data.date_end ? moment(data.date_end).format('MM-DD-YYYY') : null; |
||||
data.cover = this.contest.coverImageId || ''; |
||||
data.content = this.contest.content.map((block, index) => { |
||||
if (block.type === 'text') { |
||||
return { |
||||
'type': 'text', |
||||
'data': { |
||||
'id': block.data.id ? block.data.id : null, |
||||
'uuid': block.uuid, |
||||
'position': ++index, |
||||
'title': block.data.title, |
||||
'txt': block.data.text, |
||||
} |
||||
} |
||||
} else if (block.type === 'image') { |
||||
return { |
||||
'type': 'image', |
||||
'data': { |
||||
'id': block.data.id ? block.data.id : null, |
||||
'uuid': block.uuid, |
||||
'position': ++index, |
||||
'title': block.data.title, |
||||
'img': block.data.image_id, |
||||
} |
||||
} |
||||
} else if (block.type === 'image-text') { |
||||
return { |
||||
'type': 'image-text', |
||||
'data': { |
||||
'id': block.data.id ? block.data.id : null, |
||||
'uuid': block.uuid, |
||||
'position': ++index, |
||||
'title': block.data.title, |
||||
'img': block.data.image_id, |
||||
'txt': block.data.text, |
||||
} |
||||
} |
||||
} else if (block.type === 'images') { |
||||
return { |
||||
'type': 'images', |
||||
'data': { |
||||
'id': block.data.id ? block.data.id : null, |
||||
'uuid': block.uuid, |
||||
'position': ++index, |
||||
'title': block.data.title, |
||||
'images': block.data.images.map((galleryImage) => { |
||||
return { |
||||
'id': galleryImage.id ? galleryImage.id : null, |
||||
'img': galleryImage.img, |
||||
} |
||||
}), |
||||
} |
||||
} |
||||
} else if (block.type === 'video') { |
||||
return { |
||||
'type': 'video', |
||||
'data': { |
||||
'id': block.data.id ? block.data.id : null, |
||||
'uuid': block.uuid, |
||||
'position': ++index, |
||||
'title': block.data.title, |
||||
'url': block.data.video_url, |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
|
||||
const request = this.contest.id |
||||
? api.put(`/api/v1/contests/${this.contest.id}/`, data, { |
||||
headers: { |
||||
'Authorization': `Token ${window.LIL_STORE.accessToken}`, |
||||
} |
||||
}) |
||||
: api.post('/api/v1/contests/', data, { |
||||
headers: { |
||||
'Authorization': `Token ${window.LIL_STORE.accessToken}`, |
||||
} |
||||
}); |
||||
request.then((response) => { |
||||
if(this.contest.id) { |
||||
this.contest = this.processContestJson(response.data); |
||||
} |
||||
else { |
||||
window.location.href = `/contest/${response.data.id}/edit`; |
||||
} |
||||
}) |
||||
.catch((err) => { |
||||
//this.contestSaving = false; |
||||
}); |
||||
|
||||
} |
||||
}, |
||||
components: { |
||||
BlockAdd, |
||||
'vue-datepicker': DatePicker, |
||||
'block-text': BlockText, |
||||
'block-image': BlockImage, |
||||
'block-image-text': BlockImageText, |
||||
'block-images': BlockImages, |
||||
'block-video': BlockVideo, |
||||
'vue-draggable': Draggable, |
||||
|
||||
} |
||||
}; |
||||
</script> |
||||
|
||||
<style> |
||||
|
||||
</style> |
||||
@ -0,0 +1,30 @@ |
||||
<template> |
||||
<div class="contest-works"></div> |
||||
</template> |
||||
|
||||
<script> |
||||
import {api} from "../js/modules/api"; |
||||
import ContestWork from './blocks/ContestWork'; |
||||
|
||||
export default { |
||||
name: "contest-work", |
||||
prop: ['contestId'], |
||||
data(){ |
||||
return { |
||||
|
||||
}; |
||||
}, |
||||
mounted() { |
||||
api.getContestWorks(this.contestId).then((response) => { |
||||
|
||||
}); |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
.contest-works::after { |
||||
content ' ' |
||||
flex-grow 99999999 |
||||
} |
||||
</style> |
||||
@ -0,0 +1,38 @@ |
||||
<template> |
||||
<div class="contest-work" :style="style"> |
||||
<i :style="{paddingBottom: imgHeight / imgWidth * 100 + '%'}"></i> |
||||
<img class="contest-work__img" :src="contestWork.image" /> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: "contest-work", |
||||
props: ['contestWork'], |
||||
computed: { |
||||
imgHeight() { |
||||
return contestWork.imgHeight; |
||||
}, |
||||
imgWidth() { |
||||
return contestWork.imgWidth; |
||||
}, |
||||
style() { |
||||
const aspectRatio = this.imgWidth / this.imgHeight; |
||||
return { |
||||
width: `${this.averageFileCardHeight * aspectRatio}px`, |
||||
flexGrow: this.averageFileCardHeight * aspectRatio, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
} |
||||
</script> |
||||
|
||||
<style> |
||||
.contest-work__img { |
||||
position: absolute; |
||||
max-height 100%; |
||||
max-width 100%; |
||||
width auto; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,19 @@ |
||||
import Vue from 'vue' |
||||
import Vuelidate from 'vuelidate' |
||||
import VueAutosize from '../components/directives/autosize' |
||||
import ContestRedactor from '../components/ContestRedactor.vue' |
||||
|
||||
if (process.env.NODE_ENV === 'development') { |
||||
// Enable vue-devtools
|
||||
Vue.config.devtools = true; |
||||
} |
||||
|
||||
Vue.use(VueAutosize); |
||||
Vue.use(Vuelidate); |
||||
|
||||
let app = new Vue({ |
||||
el: '#lilcity-vue-app', |
||||
components: { |
||||
'contest-redactor': ContestRedactor, |
||||
} |
||||
}); |
||||
Loading…
Reference in new issue